Dither using linear RGB, not gamma-compressed
This commit is contained in:
@@ -1,46 +1,60 @@
|
|||||||
# coding: utf-8
|
import numpy as np
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
import tqdm
|
||||||
|
|
||||||
img = Image.open('peacock.jpg')
|
img = Image.open('peacock.jpg')
|
||||||
resized = img.resize((128,128))
|
resized = img.resize((128,128))
|
||||||
hex_colors = """#000000
|
|
||||||
#1D2B53
|
|
||||||
#7E2553
|
|
||||||
#008751
|
|
||||||
#AB5236
|
|
||||||
#5F574F
|
|
||||||
#C2C3C7
|
|
||||||
#FFF1E8
|
|
||||||
#FF004D
|
|
||||||
#FFA300
|
|
||||||
#FFEC27
|
|
||||||
#00E436
|
|
||||||
#29ADFF
|
|
||||||
#83769C
|
|
||||||
#FF77A8
|
|
||||||
#FFCCAA
|
|
||||||
#291814
|
|
||||||
#111D35
|
|
||||||
#422136
|
|
||||||
#125359
|
|
||||||
#742F29
|
|
||||||
#49333B
|
|
||||||
#A28879
|
|
||||||
#F3EF7D
|
|
||||||
#BE1250
|
|
||||||
#FF6C24
|
|
||||||
#A8E72E
|
|
||||||
#00B543
|
|
||||||
#065AB5
|
|
||||||
#754665
|
|
||||||
#FF6E59
|
|
||||||
#FF9D81"""
|
|
||||||
pico_palette_32 = [x.strip() for x in hex_colors.splitlines()]
|
|
||||||
def hex_to_rgb(h):
|
|
||||||
dec = lambda x: int(x, base=16)
|
|
||||||
return (dec(h[1:3]), dec(h[3:5]), dec(h[5:7]))
|
|
||||||
|
|
||||||
pico_rgb = [hex_to_rgb(x) for x in pico_palette_32]
|
# sRGB values for the PICO-8 palette.
|
||||||
import numpy as np
|
# The first 16 entries correspond with the default palette (0-15).
|
||||||
|
# The last 16 entries correspond with the "secret" palette (128-143).
|
||||||
|
PICO_RGB = [
|
||||||
|
(0, 0, 0),
|
||||||
|
(29, 43, 83),
|
||||||
|
(126, 37, 83),
|
||||||
|
(0, 135, 81),
|
||||||
|
(171, 82, 54),
|
||||||
|
(95, 87, 79),
|
||||||
|
(194, 195, 199),
|
||||||
|
(255, 241, 232),
|
||||||
|
(255, 0, 77),
|
||||||
|
(255, 163, 0),
|
||||||
|
(255, 236, 39),
|
||||||
|
(0, 228, 54),
|
||||||
|
(41, 173, 255),
|
||||||
|
(131, 118, 156),
|
||||||
|
(255, 119, 168),
|
||||||
|
(255, 204, 170),
|
||||||
|
(41, 24, 20),
|
||||||
|
(17, 29, 53),
|
||||||
|
(66, 33, 54),
|
||||||
|
(18, 83, 89),
|
||||||
|
(116, 47, 41),
|
||||||
|
(73, 51, 59),
|
||||||
|
(162, 136, 121),
|
||||||
|
(243, 239, 125),
|
||||||
|
(190, 18, 80),
|
||||||
|
(255, 108, 36),
|
||||||
|
(168, 231, 46),
|
||||||
|
(0, 181, 67),
|
||||||
|
(6, 90, 181),
|
||||||
|
(117, 70, 101),
|
||||||
|
(255, 110, 89),
|
||||||
|
(255, 157, 129)
|
||||||
|
]
|
||||||
|
|
||||||
|
def component_to_linear(x):
|
||||||
|
x /= 255
|
||||||
|
if x <= 0.04045:
|
||||||
|
return x/12.92
|
||||||
|
else:
|
||||||
|
return ((x + 0.055)/1.055)**2.4
|
||||||
|
|
||||||
|
def rgb_to_linear(rgb):
|
||||||
|
return tuple(component_to_linear(c) for c in rgb)
|
||||||
|
|
||||||
|
PICO_RGB_LINEAR = [rgb_to_linear(x) for x in PICO_RGB]
|
||||||
|
|
||||||
bayer = np.array([[0]])
|
bayer = np.array([[0]])
|
||||||
np.block([[4*bayer, 4*bayer+3],[4*bayer+2, 4*bayer+1]])
|
np.block([[4*bayer, 4*bayer+3],[4*bayer+2, 4*bayer+1]])
|
||||||
bayer = np.block([[4*bayer, 4*bayer+3],[4*bayer+2, 4*bayer+1]])
|
bayer = np.block([[4*bayer, 4*bayer+3],[4*bayer+2, 4*bayer+1]])
|
||||||
@@ -48,7 +62,7 @@ bayer = np.block([[4*bayer, 4*bayer+3],[4*bayer+2, 4*bayer+1]])
|
|||||||
bayer = np.block([[4*bayer, 4*bayer+3],[4*bayer+2, 4*bayer+1]])
|
bayer = np.block([[4*bayer, 4*bayer+3],[4*bayer+2, 4*bayer+1]])
|
||||||
def dither_pixel(img, xy, pal):
|
def dither_pixel(img, xy, pal):
|
||||||
global bayer
|
global bayer
|
||||||
pal2 = [np.array(list(c)) for c in pal]
|
pal2 = [np.array(rgb_to_linear(c)) for c in pal]
|
||||||
counts = [0]*len(pal)
|
counts = [0]*len(pal)
|
||||||
x, y = xy
|
x, y = xy
|
||||||
def diff(c1, c2):
|
def diff(c1, c2):
|
||||||
@@ -56,8 +70,8 @@ def dither_pixel(img, xy, pal):
|
|||||||
def nearest(rgb):
|
def nearest(rgb):
|
||||||
cost, index = min((diff(rgb,c), i) for i,c in enumerate(pal2))
|
cost, index = min((diff(rgb,c), i) for i,c in enumerate(pal2))
|
||||||
return index
|
return index
|
||||||
color = np.array(list(img.getpixel(xy)))
|
color = np.array(rgb_to_linear(img.getpixel(xy)))
|
||||||
err = np.array([0,0,0])
|
err = np.array([0.0,0.0,0.0])
|
||||||
for _ in range(64):
|
for _ in range(64):
|
||||||
err += color
|
err += color
|
||||||
i = nearest(err)
|
i = nearest(err)
|
||||||
@@ -70,10 +84,13 @@ def dither_pixel(img, xy, pal):
|
|||||||
return i
|
return i
|
||||||
|
|
||||||
dithered = Image.new('P', resized.size)
|
dithered = Image.new('P', resized.size)
|
||||||
dithered.putpalette([x for rgb in pico_rgb[:16] for x in rgb])
|
dithered.putpalette([x for rgb in PICO_RGB[:16] for x in rgb])
|
||||||
import qtdm
|
def do_it():
|
||||||
import tqdm
|
global dithered, resized
|
||||||
for x in tqdm.tqdm(range(128)):
|
for x in tqdm.tqdm(range(128)):
|
||||||
for y in range(128):
|
for y in range(128):
|
||||||
i = dither_pixel(resized, (x,y), pico_rgb[:16])
|
i = dither_pixel(resized, (x,y), PICO_RGB[:16])
|
||||||
dithered.putpixel((x,y), i)
|
dithered.putpixel((x,y), i)
|
||||||
|
# import cProfile
|
||||||
|
# cProfile.run('do_it()')
|
||||||
|
do_it()
|
||||||
|
|||||||
Reference in New Issue
Block a user