Skip to content

Instantly share code, notes, and snippets.

@nitori
Created April 6, 2024 15:27
Show Gist options
  • Save nitori/848d67336853342b72449cb8cdb3bac5 to your computer and use it in GitHub Desktop.
Save nitori/848d67336853342b72449cb8cdb3bac5 to your computer and use it in GitHub Desktop.
sample of how to quantize an image with a predefined palette of colors
import random
from PIL import Image, ImageDraw
COLORS = [
(0x5e, 0x1f, 0x1d), (0xaa, 0x38, 0x37), (0x41, 0x2c, 0x33), (0xb3, 0x5a, 0x48), (0xfd, 0x63, 0x65),
(0xa0, 0xa5, 0xa9), (0x6c, 0x6e, 0x68), (0xb2, 0x00, 0x2a), (0xc0, 0x12, 0x26), (0xe1, 0x53, 0x85),
(0x93, 0x37, 0x4e), (0xe6, 0xa0, 0xc5), (0xe3, 0xb4, 0xa4), (0x00, 0x00, 0x00), (0x24, 0x27, 0x28),
(0x3f, 0x41, 0x41), (0x67, 0x1b, 0x56), (0x90, 0x00, 0x45), (0x34, 0x21, 0x50), (0x83, 0x58, 0x8b),
(0xa1, 0x89, 0xae), (0xd1, 0xb4, 0xc8), (0x87, 0x8e, 0x90), (0xa2, 0xa8, 0xab), (0xff, 0xff, 0xff),
(0x0a, 0x1a, 0x19), (0x23, 0x28, 0x1c), (0x34, 0x3b, 0x22), (0x61, 0x5c, 0x3d), (0x4f, 0x73, 0x6e),
(0xa5, 0x83, 0x8e), (0xe0, 0x3c, 0x1e), (0xf4, 0x68, 0x24), (0xe9, 0x76, 0x3c), (0x30, 0x51, 0x2f),
(0x4c, 0x9e, 0x60), (0xc9, 0xd2, 0x74), (0xbd, 0xd8, 0x8e), (0xa6, 0xb9, 0xa6), (0xb1, 0xdd, 0xd9),
(0x4f, 0x1e, 0x0f), (0x42, 0x1b, 0x19), (0x86, 0x34, 0x0f), (0x02, 0x2d, 0x47), (0x00, 0x63, 0x76),
(0x00, 0x44, 0x34), (0x00, 0x68, 0x46), (0x7d, 0x98, 0x35), (0x26, 0x18, 0x13), (0x6b, 0x46, 0x2e),
(0x79, 0x46, 0x18), (0x83, 0x50, 0x30), (0x00, 0x41, 0x5e), (0x00, 0x4d, 0x61), (0x42, 0x7b, 0x88),
(0x2c, 0x40, 0x47), (0x51, 0x62, 0x78), (0xcd, 0x73, 0x52), (0xc8, 0x49, 0x82), (0xda, 0xac, 0x94),
(0xe0, 0xd3, 0xcc), (0x05, 0x14, 0x30), (0x02, 0x1d, 0x53), (0x00, 0x3a, 0x7f), (0x2e, 0x61, 0x8f),
(0x3c, 0x93, 0xb5), (0xe9, 0xb8, 0x22), (0xdb, 0x97, 0x00), (0xe5, 0xcf, 0x85), (0xe1, 0xc9, 0x8d),
(0x00, 0x94, 0xbc), (0x00, 0x6a, 0xa5), (0x43, 0x7a, 0xac), (0x61, 0x7f, 0xb2), (0xa7, 0xae, 0xbe),
(0x70, 0x59, 0x45), (0x9d, 0x7b, 0x56), (0xa5, 0x81, 0x51), (0xb2, 0xa0, 0x76),
]
# COLORS = [(
# random.randrange(256),
# random.randrange(256),
# random.randrange(256),
# ) for _ in range(256)]
def make_palette(colors: list[tuple[int, int, int]]) -> Image.Image:
palette = Image.new('P', (len(colors), 1))
flat_colors = [c for color in colors for c in color]
palette.putpalette(flat_colors)
return palette
def make_palette_image(colors: list[tuple[int, int, int]]) -> Image.Image:
palette_img = Image.new('RGB', (32, 32), (255, 255, 255))
draw = ImageDraw.Draw(palette_img)
for i, color in enumerate(colors):
x = i % 16
y = i // 16
draw.rectangle((x * 2, y * 2, x * 2 + 2, y * 2 + 2), fill=color)
return palette_img
def create_sample(im: Image.Image, colors: list[tuple[int, int, int]]) -> Image.Image:
w, h = im.size
palette = make_palette(colors)
# good version
quantized = im.quantize(colors=len(colors), palette=palette)
used_colors_quantized = [color for (_, color) in quantized.convert('RGB').getcolors()]
assert set(used_colors_quantized) - set(colors) == set()
# bad version
no_dither = im.quantize(colors=len(colors), palette=palette, dither=0)
used_colors_no_dither = [color for (_, color) in quantized.convert('RGB').getcolors()]
assert set(used_colors_no_dither) - set(colors) == set()
bg_img = Image.new('RGB', (w * 3, h + 32), (255, 255, 255))
bg_img.paste(im, (0, 32))
bg_img.paste(quantized, (w, 32))
bg_img.paste(no_dither, (w * 2, 32))
original_palette_img = make_palette_image(colors)
bg_img.paste(original_palette_img, (0, 0))
quantized_palette_img = make_palette_image(used_colors_quantized)
bg_img.paste(quantized_palette_img, (w, 0))
no_dither_palette_img = make_palette_image(used_colors_no_dither)
bg_img.paste(no_dither_palette_img, (w * 2, 0))
return bg_img
def main() -> None:
im = Image.open('Billie_Eilish_cropped.png').convert('RGB')
sample = create_sample(im, COLORS)
sample.save('sample.png')
if __name__ == '__main__':
main()
@nitori
Copy link
Author

nitori commented Apr 6, 2024

Important is really only the make_palette function and line 54, where it's passed to im.quantize.

The rest is just extra stuff to make a comparison chart.

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