Last active
February 27, 2017 12:41
-
-
Save pashango2/487e5147015655a7fd6db3cbc1c7c833 to your computer and use it in GitHub Desktop.
Halftone effect for Python3 and pillow
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
# coding: utf-8 | |
from PIL import Image, ImageOps, ImageChops, ImageFilter, ImageDraw, ImageDraw2 | |
import math | |
def create_mat(_rot, _pitch): | |
_pi = math.pi * (_rot / 180.) | |
_scale = 1.0 / _pitch | |
def _mul(x, y): | |
return ( | |
(x * math.cos(_pi) - y * math.sin(_pi)) * _scale, | |
(x * math.sin(_pi) + y * math.cos(_pi)) * _scale, | |
) | |
def _imul(x, y): | |
return ( | |
(x * math.cos(_pi) + y * math.sin(_pi)) * _pitch, | |
(x * -math.sin(_pi) + y * math.cos(_pi)) * _pitch, | |
) | |
return _mul, _imul | |
def x_y_iter(w, h, sx, ex, sy, ey): | |
fw, fh = float(w), float(h) | |
for y in range(h + 1): | |
ty = y / fh | |
yy = (1. - ty) * sy + ty * ey | |
for x in range(w + 1): | |
tx = x / fw | |
xx = (1. - tx) * sx + tx * ex | |
yield xx, yy | |
def halftone(img, rot, pitch): | |
mat, imat = create_mat(rot, pitch) | |
w_half = img.size[0] // 2 | |
h_half = img.size[1] // 2 | |
pitch_2 = pitch / 2. | |
# バウンディングボックスを計算 | |
bounding_rect = [ | |
(-w_half - pitch_2, -h_half - pitch_2), | |
(-w_half - pitch_2, h_half + pitch_2), | |
] | |
x, y = zip(*[mat(x, y) for x, y in bounding_rect]) | |
w, h = max(abs(t) for t in x), max(abs(t) for t in y) | |
# ガウシアンフィルターで平均化する | |
gmono = img.filter(ImageFilter.GaussianBlur(pitch / 2)) | |
# スキャンを実行し、(x, y, color)の配列を生成する | |
dots = [] | |
for x, y in x_y_iter(int(w * 2) + 1, int(h * 2) - 1, -w, w, -h + 1., h - 1.): | |
x, y = imat(x, y) | |
x += w_half | |
y += h_half | |
if -pitch_2 < x < img.size[0] + pitch_2 and -pitch_2 < y < img.size[1] + pitch_2: | |
color = gmono.getpixel(( | |
min(max(x, 0), img.size[0]-1), | |
min(max(y, 0), img.size[1]-1) | |
)) | |
t = pitch_2 * (1.0 - (color / 255)) | |
dots.append((x, y, color)) | |
return dots | |
def halftone_cmyk(img, pitch, dot_radius, scale=1., rots=(15, 75, 35, 45)): | |
c, m, y, k = cmyk.split() | |
cdots = halftone(c, rots[0], pitch) | |
mdots = halftone(m, rots[1], pitch) | |
ydots = halftone(y, rots[2], pitch) | |
kdots = halftone(k, rots[3], pitch) | |
nc = dot_image(img.size, cdots, dot_radius, scale=scale) | |
nm = dot_image(img.size, mdots, dot_radius, scale=scale) | |
ny = dot_image(img.size, ydots, dot_radius, scale=scale) | |
nk = dot_image(img.size, kdots, dot_radius, scale=scale) | |
return Image.merge("CMYK", [nc, nm, ny, nk]).convert("RGB") | |
def dot_image(size, dots, dot_radius, base_color=0, dot_color=0xFF, scale=1.0): | |
img = Image.new("L", tuple(int(x * scale) for x in size), base_color) | |
draw = ImageDraw.Draw(img) | |
for x, y, color in dots: | |
t = dot_radius * (color / 255) * scale | |
x *= scale | |
y *= scale | |
draw.ellipse((x - t, y - t, x + t, y + t), dot_color) | |
return img | |
def dot_image_by_cairo(size, dots, dot_radius, base_color=(0, 0, 0), dot_color=(1., 1., 1.), scale=1.0): | |
import cairo | |
w, h = tuple(int(x * scale) for x in img.size) | |
surface = cairo.ImageSurface(cairo.FORMAT_RGB24, w, h) | |
ctx = cairo.Context(surface) | |
ctx.set_source_rgb(*base_color) | |
ctx.rectangle(0, 0, w, h) | |
ctx.fill() | |
for x, y, color in dots: | |
fcolor = color / 255. | |
t = dot_radius * fcolor * scale | |
ctx.set_source_rgb(*dot_color) | |
ctx.arc(x, y, t, 0, 2 * math.pi) | |
ctx.fill() | |
return Image.frombuffer("RGBA", img.size, surface.get_data(), "raw", "RGBA", 0, 1) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment