🥧
【Python】 カラーハーフトーンの作り方
元画像
Cyan + Magenta + Yellow
Cyan + Magenta
この記事では、画像をカラーモデルのCMYKに分離してハーフトーンを生成する、カラーハーフトーンの作り方について解説する。ただし、見よう見まねの我流で作ったものなので、出力のクォリティについては大目に見てほしい。
そもそもハーフトーンとは?
ハーフトーンは画像内の範囲ピクセル、たとえば10x10ピクセルのような範囲の明るさ平均を算出し、その値をもとに特定の形で描画し直す処理である。上記画像では明るさが大きいほど、大きい白円を描く仕組みになっている。
これから作るカラーハーフトーンも基本的には同じ仕組みで、画像をCMYKの4つのチャンネルに分離して、それらから生成したハーフトーン画像を合成したものがカラーハーフトーンの作成結果になる。
アルゴリズム
まず画像をCMYKの各チャンネルに分離する。
https://ja.wikipedia.org/wiki/CMYK より引用
次にチャンネルごとに画像を傾ける。これはもともとモアレ防止の対策らしいが、デザインとして利用ができるので、ここでもそのまま傾ける処理を採用する。
そして上記引用画像のように、傾けた画像から一定範囲おきに明るさを計算して、その明るさに応じた大きさの円を描く。
描かれた4つの画像を合成して一つの画像としてまとめる。ただし、下記の実装例ではCMYKのKを利用していない。これは合成を行うとCMYだけで黒が表現できるのでKが不要になるからである。
制作環境
- Python3
- Pillow (PIL)
Pythonでの実装例
上記実装例の解説
from PIL import Image, ImageDraw, ImageStat
def norm(v, a, b):
return (v - a) / (b - a)
def lerp(a, b, t):
return a + (b - a) * t
def map(v, a, b, c, d):
return lerp(c, d, norm(v, a, b))
# src: Image.open() で開いた元画像
# size: この範囲おきに明るさを計算して円を描く
# angle: チャンネルをずらす角度
# max_radius: 円の最大半径
# channel_index: CMYに対応するインデックス C = 0, M = 1, Y = 2
def convert(src, size, angle, max_radius, channel_index):
width, height = src.size
# 画像をCMYKに分離する
cmyk = src.convert("CMYK").split()
channel = cmyk[channel_index]
# angleの角度でチャンネルを回転。はみ出た部分は拡張する。
channel = channel.rotate(angle, expand=True)
channel_width, channel_height = channel.size
# 円を描くための新しい画像
img = Image.new("L", (channel_width, channel_height))
canvas = ImageDraw.Draw(img)
# size x sizeおきに円を描く
for y in range(0, channel_height, size):
for x in range(0, channel_width, size):
# 領域を取り出して、平均色を計算する。
area = channel.crop((x, y, x + size, y + size))
stat = ImageStat.Stat(area)
avg = stat.mean[0]
# 平均色の値を1~max_radiusの範囲にマッピングする。
r = map(avg, 0, 255, 1, max_radius)
# 半径rの円を描く。
x0 = x + size / 2 - r
y0 = y + size / 2 - r
x1 = x + size / 2 + r
y1 = y + size / 2 + r
canvas.ellipse((x0, y0, x1, y1), fill=255)
# チャンネルの回転を元に戻す。
img = img.rotate(-angle, expand=True)
# 回転のために拡大した画像から、
# 元画像のサイズ分を中央を軸に切り抜く。
new_width, new_height = img.size
px = (new_width - width) // 2
py = (new_height - height) // 2
img = img.crop((px, py, px + width, py + height))
return img
angles = [0, 15, 30] # CMY毎の回転角度
size = 20 # 各領域のサイズ
max_radius = 15 # 円の最大半径
src = Image.open("input.jpg") # 元画像
width, height = src.size
# CMY毎にハーフトーン画像を作る。
c = convert(src, size, angles[0], max_radius, 0)
m = convert(src, size, angles[1], max_radius, 1)
y = convert(src, size, angles[2], max_radius, 2)
# 今回Kは必要ないが、下記のImage.merge()を行うのに画像が必要になるので、
# 黒画像を用意しておく。
k = Image.new("L", (width, height), 0)
# 合成して1つの画像にまとめて出力。
output = Image.merge("CMYK", [c, m, y, k])
output.convert("RGB").save("output.png")
Discussion