Skip to content

Instantly share code, notes, and snippets.

@mieki256
Created April 27, 2017 11:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mieki256/2ba67d7c846efd7fcf165d94e25ae54b to your computer and use it in GitHub Desktop.
Save mieki256/2ba67d7c846efd7fcf165d94e25ae54b to your computer and use it in GitHub Desktop.
Python+cairo(pycairo)でドット絵モドキを生成
#!python
# -*- mode: python; Encoding: utf-8; coding: utf-8 -*-
# Last updated: <2017/04/27 09:46:11 +0900>
u"""
tinypixelargrad.py - generate tiny pixelart.
Drawing with cairo(pycairo).
testing environment :
Windows10 x64 + Python 2.7.13 32bit + pycairo 1.8.10
Author: mieki256
License: CC0 / Public Domain
"""
import cairo
import random
import colorsys
import math
class TinyPixelartGrad(object):
"""Generate tiny pixelart."""
def __init__(self, w, h, seed=None,
x_mirror=None, y_mirror=None, hue=None,
border_width=1.0, border_alpha=1.0):
"""Generate tiny pixelart."""
if seed is None:
random.seed()
else:
random.seed(seed)
if x_mirror is None:
self.x_mirror = True if random.random() > 0.5 else False
else:
self.x_mirror = x_mirror
if y_mirror is None:
self.y_mirror = True if random.random() > 0.5 else False
else:
self.y_mirror = y_mirror
self.border_width = border_width
self.border_alpha = border_alpha
self.hue_base = random.random() * 360 if hue is None else hue
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
ctx = cairo.Context(surface)
ctx.set_antialias(cairo.ANTIALIAS_NONE)
ctx.set_line_width(self.border_width)
ctx.set_line_join(cairo.LINE_JOIN_MITER)
ctx.set_line_cap(cairo.LINE_CAP_SQUARE)
cnt = random.randint(16, 32)
for i in range(cnt):
hue = (self.hue_base + random.random() * 45) % 360
sat = 10 + random.random() * 90
lum = 30 + random.random() * 70
wh, hh = int(w * 0.8), int(x=h * 0.8)
rx = (random.randint(0, wh - 1) +
random.randint(0, wh - 1) +
random.randint(0, wh - 1)) / 3
ry = (random.randint(0, hh - 1) +
random.randint(0, hh - 1) +
random.randint(0, hh - 1)) / 3
if random.random() < 0.8:
# draw rectangle
rw = int(random.random() * ((w - rx) / 2) + 1)
rh = int(random.random() * ((h - ry) / 2) + 1)
self.draw_rect(ctx, rx, ry, rw, rh, hue, sat, lum)
else:
# draw circle
mm = min((rx, ry, (w - rx), (h - ry)))
rr = int(random.random() * (mm * 0.4) + 2)
self.draw_circle(ctx, rx, ry, rr, hue, sat, lum)
if self.x_mirror:
surface = self.mirror_x(surface)
if self.y_mirror:
surface = self.mirror_y(surface)
self.surface = surface
def get_linear_pattern(self, x, y, w, h, hue, sat, lum, hor, rpt):
"""Get gradation pattern."""
hue = hue / 360.0
sat = sat / 100.0
l1 = lum
l0 = max(((l1 - random.randint(20, 49)), 0)) / 100.0
l2 = min(((l1 - random.randint(20, 49)), 100.0)) / 100.0
l1 = l1 / 100.0
rgb0 = colorsys.hls_to_rgb(hue, l0, sat)
rgb1 = colorsys.hls_to_rgb(hue, l1, sat)
rgb2 = colorsys.hls_to_rgb(hue, l2, sat)
if hor:
# horizontal gradation
lg = cairo.LinearGradient(x, y, x + w, y)
else:
# vertical gradation
lg = cairo.LinearGradient(x, y, x, y + h)
if rpt:
# repeat gradation
lg.add_color_stop_rgb(0.0, rgb0[0], rgb0[1], rgb0[2])
lg.add_color_stop_rgb(0.2, rgb1[0], rgb1[1], rgb1[2])
lg.add_color_stop_rgb(0.5, rgb2[0], rgb2[1], rgb2[2])
lg.add_color_stop_rgb(0.8, rgb1[0], rgb1[1], rgb1[2])
lg.add_color_stop_rgb(1.0, rgb0[0], rgb0[1], rgb0[2])
else:
lg.add_color_stop_rgb(0.0, rgb0[0], rgb0[1], rgb0[2])
lg.add_color_stop_rgb(0.5, rgb1[0], rgb1[1], rgb1[2])
lg.add_color_stop_rgb(1.0, rgb2[0], rgb2[1], rgb2[2])
return lg
def draw_rect(self, ctx, x, y, w, h, hue, sat, lum):
"""Draw rectangle."""
hor = True if random.random() < 0.5 else False
rpt = True if random.random() < 0.3 else False
if w <= 2 or h <= 2:
# no border
lg = self.get_linear_pattern(x, y, w, h, hue, sat, lum, hor, rpt)
ctx.set_source(lg)
ctx.rectangle(x, y, w, h)
ctx.fill()
else:
# with border
lg = self.get_linear_pattern(x, y, w, h, hue, sat, lum, hor, rpt)
ctx.set_source(lg)
ra = min((w, h)) / 3
if ra <= 1 or random.random() < 0.5:
# rectangle
ctx.rectangle(x, y, w, h)
else:
# round rectangle
self.draw_rounder_rectangle(ctx, x, y, w, h, ra)
ctx.fill_preserve()
ctx.set_source_rgba(0, 0, 0, self.border_alpha)
ctx.stroke()
def draw_rounder_rectangle(self, 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_circle(self, ctx, x, y, r, hue, sat, lum):
"""Draw cirlce."""
l1 = lum
la = random.randint(20, 49)
l0 = max([(l1 - la), 0]) / 100.0
l2 = max([(l1 + la), 100]) / 100.0
l1 = l1 / 100.0
hue = hue / 360.0
sat = sat / 100.0
rgb0 = colorsys.hls_to_rgb(hue, l0, sat)
rgb1 = colorsys.hls_to_rgb(hue, l1, sat)
rgb2 = colorsys.hls_to_rgb(hue, l2, sat)
rg = cairo.RadialGradient(x, y, 0, x, y, r)
rg.add_color_stop_rgb(1.0, rgb0[0], rgb0[1], rgb0[2])
rg.add_color_stop_rgb(0.7, rgb1[0], rgb1[1], rgb1[2])
rg.add_color_stop_rgb(0.0, rgb2[0], rgb2[1], rgb2[2])
ctx.set_source(rg)
ctx.arc(x, y, r, 0, 2 * math.pi)
ctx.fill_preserve()
ctx.set_source_rgba(0, 0, 0, self.border_alpha)
ctx.stroke()
def mirror_x(self, surface):
"""Surface X mirror."""
w = surface.get_width()
h = surface.get_height()
src = surface.get_data()
xx = int((w / 2.0) + 0.5)
x = 0
x1 = w - 1
while x < xx:
for y in range(h):
for i in range(4):
src[(y * w + x1) * 4 + i] = src[(y * w + x) * 4 + i]
x += 1
x1 -= 1
fmt = cairo.FORMAT_ARGB32
return cairo.ImageSurface.create_for_data(src, fmt, w, h, w * 4)
def mirror_y(self, surface):
"""Surface Y mirror."""
w = surface.get_width()
h = surface.get_height()
src = surface.get_data()
yy = int((h / 2.0) + 0.5)
y = 0
y1 = h - 1
while y < yy:
for x in range(w):
for i in range(4):
src[(y1 * w + x) * 4 + i] = src[(y * w + x) * 4 + i]
y += 1
y1 -= 1
fmt = cairo.FORMAT_ARGB32
return cairo.ImageSurface.create_for_data(src, fmt, w, h, w * 4)
if __name__ == '__main__':
# testing
art = TinyPixelartGrad(256, 256)
surface = art.surface
surface.write_to_png("result.png")
print("Done.")
#!python
# -*- mode: python; Encoding: utf-8; coding: utf-8 -*-
# Last updated: <2017/04/27 14:22:40 +0900>
u"""
preview tinypixelartgrad results with tkinter.
usage: python tinypixelartgrad_gui.py
testing environment :
Windows10 x64 + Python 2.7.13 32bit + pycairo 1.8.10
Author: mieki256
License: CC0 / Public Domain
"""
import tinypixelartgrad
import Tkinter
from PIL import Image
from PIL import ImageTk
import time
import os.path
import sys
OUTDIR = "output"
DEF_W, DEF_H = 64, 64
def generate_pixelart():
"""Generate pixelart."""
global label
global img
global art
global wentry
global hentry
w = int(wentry.get())
h = int(hentry.get())
xm = True if x_mirror.get() else False
ym = True if y_mirror.get() else False
art = tinypixelartgrad.TinyPixelartGrad(w, h, seed, xm, ym)
surface = art.surface
# convert cairo surface to tk image (little endian only)
img = ImageTk.PhotoImage(Image.frombuffer("RGBA", (w, h),
surface.get_data(),
"raw", "BGRA", 0, 1))
label.configure(image=img)
def gen_art(event):
"""Generate pixelart by button push."""
global seed
seed += 1
generate_pixelart()
set_status("generate pixelart. seed = %d" % seed)
def save_art(event):
"""Save tiny pixelart."""
filepath = os.path.relpath(os.path.normpath(os.path.abspath(
os.path.join(dpath, OUTDIR, str(seed) + ".png"))))
art.surface.write_to_png(filepath.encode("utf-8"))
set_status("Save %s" % filepath)
def set_status(msg):
"""Set status message."""
global status
status.configure(text=msg)
dpath = os.path.dirname(sys.argv[0])
root = Tkinter.Tk()
root.title(u"Preview Tiny Pixelart")
root.geometry()
img = None
art = None
seed = int(time.time()) # random seed is UNIX TIME
x_mirror = Tkinter.BooleanVar()
y_mirror = Tkinter.BooleanVar()
x_mirror.set(True)
y_mirror.set(False)
frame1 = Tkinter.Frame(relief=Tkinter.GROOVE, borderwidth=2)
frame1.pack(padx=4, pady=4, ipadx=8, ipady=4)
wentry = Tkinter.Entry(frame1, width=6)
wentry.insert(Tkinter.END, str(DEF_W))
wentry.pack(side=Tkinter.LEFT, padx=8)
l0 = Tkinter.Label(frame1, text=u"x")
l0.pack(side=Tkinter.LEFT)
hentry = Tkinter.Entry(frame1, width=6)
hentry.insert(Tkinter.END, str(DEF_H))
hentry.pack(side=Tkinter.LEFT, padx=8)
xmchk = Tkinter.Checkbutton(frame1, text="X mirror", variable=x_mirror)
xmchk.pack(side=Tkinter.LEFT)
ymchk = Tkinter.Checkbutton(frame1, text="Y mirror", variable=y_mirror)
ymchk.pack(side=Tkinter.LEFT)
label = Tkinter.Label()
label.pack(expand=True, fill="both")
generate_pixelart()
frame2 = Tkinter.Frame()
frame2.pack(fill="both")
savebtn = Tkinter.Button(master=frame2, text=u"Save PNG")
savebtn.bind("<Button-1>", save_art)
savebtn.pack(side=Tkinter.LEFT, padx=6)
button = Tkinter.Button(frame2, text=u"Generate", borderwidth=3)
button.bind("<Button-1>", gen_art)
button.pack(side=Tkinter.RIGHT, padx=6, ipadx=16, ipady=8)
status = Tkinter.Label(text=u"status.", relief=Tkinter.RIDGE, borderwidth=2)
status.pack(fill=Tkinter.X)
root.mainloop()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment