#!python # -*- mode: python; Encoding: utf-8; coding: utf-8 -*- # Last updated: <2022/03/25 06:28:01 +0900> u""" Drawing a rectangle using pycairo and displaying it in tk. Author: mieki256 License: CC0 / Public Domain * Windows10 x64 21H2 + Python 2.7.18 32bit + pycairo 1.8.10 + Pillow 6.2.2 * Windows10 x64 21H2 + Python 3.9.11 64bit + pycairo 1.21.0 + Pillow 9.0.1 """ import sys import platform if sys.version_info.major == 2: # Python 2.7 import Tkinter as tk else: # Python 3.x import tkinter as tk from PIL import Image from PIL import ImageTk import cairo import math import random import dividerectangle as dr IMG_W, IMG_H = 512, 512 # IMG_W, IMG_H = 1024, 1024 def draw_rounder_rectangle(ctx, x, y, w, h, ra): """Set sub path rounded rectangle.""" deg = math.pi / 180.0 ctx.new_sub_path() ctx.arc(x + w - ra, y + ra, ra, -90 * deg, 0 * deg) ctx.arc(x + w - ra, y + h - ra, ra, 0 * deg, 90 * deg) ctx.arc(x + ra, y + h - ra, ra, 90 * deg, 180 * deg) ctx.arc(x + ra, y + ra, ra, 180 * deg, 270 * deg) ctx.close_path() def draw_rect(ctx, x0, y0, x1, y1, fill_col=None, line_col=None, line_width=None, borderradius=0): """Draw rectangle.""" w, h = x1 - x0, y1 - y0 ctx.set_line_join(cairo.LINE_JOIN_ROUND) ctx.set_line_cap(cairo.LINE_CAP_ROUND) ctx.set_line_width(line_width if line_width is not None else 0) if fill_col is not None: ctx.set_source_rgba(fill_col, fill_col, fill_col, 1.0) if fill_col is not None or line_col is not None: r = borderradius d = r * 2 if d > w or d > h: r = math.floor(float(min([w, h])) / 2.0) if r <= 0: ctx.rectangle(x0, y0, w, h) else: draw_rounder_rectangle(ctx, x0, y0, w, h, r) if fill_col is not None: if line_col is None: ctx.fill() return else: ctx.fill_preserve() if line_col is not None: ctx.set_source_rgba(line_col, line_col, line_col, 1.0) ctx.stroke() def fill_rects(imgw, imgh, surface, rects, spc, borderradius, fill_col=None): """Fill rectangles.""" ctx = cairo.Context(surface) # fill backgorund x0, y0 = 0, 0 x1, y1 = imgw - 1, imgh - 1 bg_col = 0.25 draw_rect(ctx, x0, y0, x1, y1, bg_col, None, None, 0) for rect in rects: bx0, by0, bx1, by1 = rect x0 = math.floor(bx0 + spc) y0 = math.floor(by0 + spc) x1 = math.floor(bx1 - spc) y1 = math.floor(by1 - spc) w0 = x1 - x0 h0 = y1 - y0 if w0 <= 0 or h0 <= 0 or x0 >= imgw or y0 >= imgh: continue col = fill_col if col is None: col = bg_col + random.uniform((24.0 / 256.0), (80.0 / 256.0)) draw_rect(ctx, x0, y0, x1, y1, col, None, None, borderradius) return surface def conv_surface_to_photo(surface): """Convert pycairo surface to Tk PhotoImage.""" w, h = surface.get_width(), surface.get_height() m = surface.get_data() if sys.version_info.major == 2: # Python 2.7 im = Image.frombuffer("RGBA", (w, h), m, "raw", "BGRA", 0, 1) else: # Python 3.x im = Image.frombuffer("RGBA", (w, h), m, "raw", "RGBA", 0, 1) # BGRA -> RGBA r, g, b, a = im.split() im = Image.merge("RGBA", (b, g, r, a)) return ImageTk.PhotoImage(im) class MyApp(tk.Tk, object): """MyApp class.""" def __init__(self): super(MyApp, self).__init__() # self.geometry("{}x{}".format(cv.W, cv.H)) self.title("Draw rectangles with pycairo (Python %s)" % platform.python_version()) frm = tk.Frame(self, padx=4, pady=4) frm.grid() # reserve canvas _w, _h = IMG_W + 32, IMG_H + 32 self.canvas = tk.Canvas(frm, width=_w, height=_h, bg="white") # reserve button b1 = tk.Button(frm, text="Generate") b1.bind("", self.clicked_generate) b2 = tk.Button(frm, text="Reset Parameter") b2.bind("", self.clicked_reset_button) # reserve variable self.dmin = tk.IntVar(self) self.dmax = tk.IntVar(self) self.cntmax = tk.IntVar(self) self.spc = tk.IntVar(self) self.borderradius = tk.IntVar(self) self.seed = tk.StringVar(self) self.randomize = tk.BooleanVar(self) self.fillcol = tk.IntVar(self) self.colrandomize = tk.BooleanVar(self) self.reset_param() # create Tk Label labels = [ "Divide min", "Divide max", "Repeat", "Spacing", "Border radius", "Fill color", "Random seed" ] lb = [] for i, lbl in enumerate(labels): lb.append(tk.Label(frm, text=lbl)) # create Tk Spinbox _w = 6 sb = [] sb.append(tk.Spinbox(frm, textvariable=self.dmin, from_=1, to=10, increment=1, width=_w)) sb.append(tk.Spinbox(frm, textvariable=self.dmax, from_=1, to=10, increment=1, width=_w)) sb.append(tk.Spinbox(frm, textvariable=self.cntmax, from_=1, to=10, increment=1, width=_w)) sb.append(tk.Spinbox(frm, textvariable=self.spc, from_=0, to=16, increment=1, width=_w)) sb.append(tk.Spinbox(frm, textvariable=self.borderradius, from_=0, to=16, increment=1, width=_w)) sb.append(tk.Spinbox(frm, textvariable=self.fillcol, from_=0, to=160, increment=1, width=_w)) e1 = tk.Entry(frm, textvariable=self.seed, width=12) c1 = tk.Checkbutton(frm, variable=self.randomize, text="Randomize") c2 = tk.Checkbutton(frm, variable=self.colrandomize, text="Fill color Randomize") # layout r = 0 b1.grid(row=r, column=0, columnspan=2, sticky=tk.W + tk.E + tk.N + tk.S, padx=8, pady=8) r = r + 1 for i, l in enumerate(lb): l.grid(row=i + r, column=0, sticky=tk.W, padx=8) for i, s in enumerate(sb): s.grid(row=i + r, column=1, sticky=tk.W) r = r + len(lb) - 1 e1.grid(row=r, column=1, sticky=tk.W) c1.grid(row=r + 1, column=0, columnspan=2) c2.grid(row=r + 2, column=0, columnspan=2) b2.grid(row=r + 3, column=0, columnspan=2) self.canvas.grid(row=0, column=2, rowspan=16) # draw canvas self.set_image() def reset_param(self): """Reset parameter.""" self.dmin.set(1) # 1 - 10, default : 1 self.dmax.set(3) # 1 - 10, default : 3 self.cntmax.set(4) # 1 - 10, default : 4 self.spc.set(2) # 0 - 16, default : 2 self.borderradius.set(2) # 0 - 16, default : 2 self.seed.set(42) # static random seed self.randomize.set(True) # randomize self.fillcol.set(160) # 0 - 160 self.colrandomize.set(True) # fill color randomize def set_image(self): """Set image to Tk canvas.""" # get parameter w, h = IMG_W, IMG_H dmin = int(self.dmin.get()) dmax = int(self.dmax.get()) cntmax = int(self.cntmax.get()) spc = int(self.spc.get()) borderradius = int(self.borderradius.get()) seed = int(self.seed.get()) randomize = self.randomize.get() fillcol = float(self.fillcol.get()) / 256.0 fillrand = self.colrandomize.get() dr.init_random_seed(seed, randomize) # get divide rectangles rects = dr.get_divide_rectangles(w, h, dmin, dmax, cntmax) # draw fill rectangles self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h) if fillrand: fillcol = None self.new_surface = fill_rects(w, h, self.surface, rects, spc, borderradius, fillcol) # convert cairo surface to ImageTk.PhotoImage self.image_ref = conv_surface_to_photo(self.new_surface) # draw to Tk canvas self.canvas.create_image(16, 16, image=self.image_ref, anchor=tk.NW) def clicked_generate(self, _): """Generate button clicked.""" self.set_image() def clicked_reset_button(self, _): """Reset button clicked.""" self.reset_param() def run(self): """Start main loop.""" self.mainloop() def main(): """Main.""" app = MyApp() app.run() if __name__ == "__main__": main()