Created
April 27, 2017 11:00
-
-
Save mieki256/2ba67d7c846efd7fcf165d94e25ae54b to your computer and use it in GitHub Desktop.
Python+cairo(pycairo)でドット絵モドキを生成
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!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.") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!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