Skip to content

Instantly share code, notes, and snippets.

@mieki256
Created April 28, 2017 07:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mieki256/c8e939389f0324f805b52078d3d9b399 to your computer and use it in GitHub Desktop.
Save mieki256/c8e939389f0324f805b52078d3d9b399 to your computer and use it in GitHub Desktop.
Generate Tiny Pixelart with GIMP + Python-Fu
#!/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()
@mieki256
Copy link
Author

tinypixelartgen_ss_02

tinypixelartgen_ss_01

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment