le75
clone your own copy | download snapshot

Snapshots | iceberg

Inside this repository

dither.py
text/x-python

Download raw (3.1 KB)

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from PIL import Image, ImagePalette
import re

def color (color):
    if type(color) is str:
        m = re.match(r'(\d+),\s*(\d+),\s*(\d+)', color)
        
        if m:
            return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
        else:
            return (0,0,0)
    else:
       return color

def alpha_dither (file_in, file_out, threshold=125, color=(0,0,0,255), scale=1, postscale=1, colormap=None):
    im = Image.open(file_in).convert('L')
    
    im = im.resize((int(im.size[0]*scale), int(im.size[1]*scale)), resample=Image.BICUBIC)
    if colormap:
        im_out = Image.open(colormap).resize((im.size[0], im.size[1]), resample=Image.BICUBIC).convert('RGBA')
    else:
        im_out = Image.new(mode='RGBA', size=(im.size[0], im.size[1]), color=color)
    
    px_in = im.load()
    px_out = im_out.load()
    
    neighbours = ((1,0), (2,0), (-1,1), (0,1), (1,1), (0,2))

    for y in range(im.size[1]):
        for x in range(im.size[0]):
            old = px_in[x, y]
            new = 255 if old > threshold else 0
            err = (old - new) >> 3
            
            if new == 255:
                px_out[x,y] = (color[0], color[1], color[2], 0)
            
            for c in neighbours:
                try:
                    px_in[x+c[0], y+c[1]] += err
                except IndexError:
                    pass
    
    if postscale > 1:
        im_out = im_out.resize((im_out.size[0]*postscale, im_out.size[1]*postscale), resample=Image.NEAREST)
    
    im_out.save(file_out)

if __name__ == '__main__':
    #dither('assets/images/dave.png', 'assets/images/dave-dithered.png', 80, (255,255,0), (255,255,255))
    
    import argparse
    
    parser = argparse.ArgumentParser()
    parser.add_argument('-o', '--output', dest='output', help="Destination of the dithered image. If none is given the original image is over-written", default=None)
    parser.add_argument('-c', '--color', dest='color', default=(0,0,0), type=color, help="Color to dither the image.\n\nIn the format of a comma-separated list \d+,\d+,\d+ Defaults to black ( 0,0,0 ).")
    parser.add_argument('-t', '--threshold', dest='threshold', default=128, type=int, help="Threshold after which a pixel is either turned on or of.")
    parser.add_argument('-s', '--scale', dest='scale', default=1.0, type=float, help="Sets the amount the source-image is scaled before dithering")
    parser.add_argument('-ps', '--post-scale', dest='postscale', default=1, type=int, help="Sets the amount of scaling performed after the dithering. Given in integers. Uses neirest neighbour for the scaling")
    parser.add_argument('-cm', '--color-map', dest='colormap', default=None, type=str, help="Take colors from given image to color the the dithering pixels")
    parser.add_argument('source', help="The location of the image to dither.")
    args = parser.parse_args()
    
    alpha_dither(args.source, args.output if args.output <> None else args.source, threshold=args.threshold, color=args.color, scale=args.scale, postscale=args.postscale, colormap=args.colormap)