Created
April 28, 2017 07:51
-
-
Save mieki256/c8e939389f0324f805b52078d3d9b399 to your computer and use it in GitHub Desktop.
Generate Tiny Pixelart with GIMP + Python-Fu
This file contains 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
#!/usr/bin/env python | |
# -*- mode: python; Encoding: utf-8; coding: utf-8 -*- | |
# Last updated: <2017/04/28 16:43:08 +0900> | |
u""" | |
Python-Fu : Generate tiny pixelart. | |
usage: Filter -> Render -> Tiny Pixelart | |
Author : mieki256 | |
License : CC0 / Public Domain | |
testing environment : | |
* GIMP 2.8.20 Portable + Windows10 x64 | |
* GIMP 2.8.16 + Ubuntu Linux 16.04 LTS | |
ver. 1.0.0 first. | |
""" | |
from gimpfu import * # NOQA | |
import cairo | |
import struct | |
import random | |
import math | |
import colorsys | |
import time | |
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) | |
def get_rgba_str(src): | |
"""Convert cairo surface data to RGBA.""" | |
rgba_buf = "" | |
l = len(src) | |
for i in range(l / 4): | |
i0 = i * 4 | |
i1 = i0 + 4 | |
bgra = struct.unpack('=L', src[i0: i1])[0] | |
a = (bgra >> 24) & 0x0ff | |
r = (bgra >> 16) & 0x0ff | |
g = (bgra >> 8) & 0x0ff | |
b = bgra & 0x0ff | |
rgba = struct.pack('4B', r, g, b, a) | |
rgba_buf += rgba | |
return rgba_buf | |
def python_fu_tiny_pixelart_gen(img, layer, w, h, row, column, rnd_on, seed, | |
x_mirror, y_mirror, createtype, layername, | |
grid_on): | |
"""Main func.""" | |
w = int(w) | |
h = int(h) | |
row = int(row) | |
column = int(column) | |
layer_w = w * row | |
layer_h = h * column | |
seed = int(time.time()) if rnd_on else int(seed) | |
if createtype == "Image": | |
img = gimp.Image(layer_w, layer_h, RGB) | |
gimp.Display(img) | |
pdb.gimp_undo_push_group_start(img) | |
layer = gimp.Layer(img, layername, layer_w, layer_h, | |
RGBA_IMAGE, 100, NORMAL_MODE) | |
layer.fill(TRANSPARENT_FILL) | |
img.add_layer(layer, 0) | |
# draw | |
gimp.progress_init("drawing pixelart") | |
ni = 0 | |
total = row * column | |
for yi in range(column): | |
for xi in range(row): | |
x = w * xi | |
y = h * yi | |
art = TinyPixelartGrad(w, h, seed, x_mirror, y_mirror) | |
surface = art.surface | |
# transfer gimp layer | |
rgn = layer.get_pixel_rgn(x, y, w, h, True, True) | |
src = surface.get_data() | |
dst = get_rgba_str(src) | |
rgn[x:(x + w), y:(y + h)] = str(dst) | |
seed += 1 | |
ni += 1 | |
gimp.progress_update(float(ni) / float(total)) | |
layer.flush() | |
layer.merge_shadow() | |
layer.update(0, 0, layer_w, layer_h) | |
if grid_on: | |
pdb.gimp_image_grid_set_spacing(img, w, h) | |
pdb.gimp_image_grid_set_offset(img, 0, 0) | |
pdb.gimp_progress_end() | |
pdb.gimp_undo_push_group_end(img) | |
pdb.gimp_displays_flush() | |
# gimp.message("Done.") | |
register( | |
"python_fu_tiny_pixelart_gen", | |
"generate tiny pixelart", | |
"generate tiny pixelart with Python-Fu", | |
"mieki256", | |
"CC0 / Public Domain", | |
"2017/04/28", | |
"Tiny Pixelart", # menu name | |
"RGB*", # Alternately use RGB, RGB*, GRAY*, INDEXED etc. | |
# params | |
[ | |
# (type, name, description, default [, extra]) | |
(PF_IMAGE, "timg", "Input image", None), | |
(PF_DRAWABLE, "tdrawable", "Input drawable", None), | |
(PF_INT, "width", "Width", 64), | |
(PF_INT, "height", "Height", 64), | |
(PF_INT, "row", "Row", 8), | |
(PF_INT, "column", "Column", 8), | |
(PF_TOGGLE, "randomize", "Randomize:", 1), | |
(PF_INT, "seed", "Random seed", 0), | |
(PF_TOGGLE, "x_mirror", "X mirror:", 1), | |
(PF_TOGGLE, "y_mirror", "Y mirror:", 0), | |
(PF_RADIO, "createtype", "Create", "Image", | |
(("Image", "Image"), ("Layer", "Layer"))), | |
(PF_STRING, "layername", "Layer name", 'pixelart_layer'), | |
(PF_TOGGLE, "grid", "Set grid w x h", 1) | |
], | |
# return vals | |
[], | |
python_fu_tiny_pixelart_gen, # function name | |
menu="<Image>/Filters/Render" | |
) | |
main() |
Author
mieki256
commented
Apr 28, 2017
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment