Added the dither plug-in for the GIMP

* Renamed the directories to be a little less ambiguous.
* Added the JACE NTSC color palette.
* Added a GIMP plug-in written in python with more dithering options.
This commit is contained in:
cybernesto 2017-03-27 20:01:21 +02:00
parent 81987a3960
commit 93304e3af2
20 changed files with 273 additions and 0 deletions

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

20
palettes/JACE-NTSC.gpl Normal file
View File

@ -0,0 +1,20 @@
GIMP Palette
Name: JACE NTSC
Columns: 16
#
0 1 0 #0
178 0 95 #1
92 87 2 #2
255 84 0 #3
0 127 34 #4
80 82 79 #5
27 214 0 #6
221 214 0 #7
24 42 255 #8
207 43 255 #9
126 128 125 #10
255 126 218 #11
17 167 255 #12
164 167 254 #13
77 255 159 #14
253 255 252 #15

36
plug-ins/README.md Normal file
View File

@ -0,0 +1,36 @@
Retro dither Plug-In for the GIMP
=================================
Python plug-in for color reduction of low resolution images for displaying on retro computers.
Introduction
============
This plug-in adds some dithering algorithms intended for converting images into a format that can be displayed on the low resolution graphic modes of old computers like the Apple II.
Installation
============
* Copy the file retro-dither.py to the GIMP plug-ins directory. This directory can be changed so check first what is configured under the folders category of the GIMP settings dialog.
* On mac or linux the plug in must be made executable. For this use the context menu of your favorite file browser or from the console use following command:
chmod +x retro-dither.py
* Copy your preferred palettes into the GIMP palettes directory. For converting color images you should choose a palette that matches the system that will be used to display them. Some sample palettes have been included in the palettes directory.
* Restart GIMP. After this the plug-in will appear under the Colors menu as "Dither..."
Usage
=====
* Load the picture you would like to convert. Depending on the mode you want to use it would make sense to resize and/or crop the picture to match the resolution you intend to use.
* Select the mode. In monochrome mode, the plug-in will automatically convert a color image into a BW image. In color mode you should select a palette. Which palette is the best will depend if your system is NTSC/PAL and the color calibration of your TV set/ monitor so you should experiment with different palettes to see which works best for your setup.
* Select the dithering algorithm. Which one to choose depends on the image you are converting and your personal taste so make sure to try which one works best.
Dither algorithms
=================
The algorithms implemented here are based on the excellent explanation from Tanner Helland:
<http://www.tannerhelland.com/4660/dithering-eleven-algorithms-source-code/>
The performance of this plug-in is limited by the fact that is written in python and surely some optimizations could still be done. For the low resolutions of the retro computers this is not critical so expandability and simplicity was preferred over a compiled plug-in.

217
plug-ins/retro_dither.py Executable file
View File

@ -0,0 +1,217 @@
#!/usr/bin/env python
# Error Diffusion Dither script for the GIMP
# Version 1.0
# Author: Mario Patino - cybernesto@gmail.com
# based on the dither script of Andy Hefner - ahefner@gmail.com
# dithering algorithms based on the information contained here:
# http://www.tannerhelland.com/4660/dithering-eleven-algorithms-source-code/
# Copyright Mario Patino, 2014
import math
from gimpfu import *
from array import array
def change_pixel(dim, src, x, y, px_d):
w, h, p = dim
if x > w:
return
if y > h:
return
i = (x + y * w)*p
src[i:i+p] = array("B",[max(min((a+b),255),0) for a,b in zip(px_d,src[i:i+p])])
def floyd_stein(dim, src_pixels, x, y, px_d):
px_delta = [a*7/16 for a in px_d]
change_pixel(dim, src_pixels, x+1, y , px_delta)
px_delta = [a*5/16 for a in px_d]
change_pixel(dim, src_pixels, x , y+1, px_delta)
px_delta = [a*3/16 for a in px_d]
change_pixel(dim, src_pixels, x-1, y+1, px_delta)
px_delta = [a*1/16 for a in px_d]
change_pixel(dim, src_pixels, x+1, y+1, px_delta)
def jarvis(dim, src_pixels, x, y, px_d):
px_delta = [a*7/48 for a in px_d]
change_pixel(dim, src_pixels,x+1 ,y, px_delta)
change_pixel(dim, src_pixels,x ,y+1, px_delta)
px_delta = [a*5/48 for a in px_d]
change_pixel(dim, src_pixels,x+2 ,y, px_delta)
change_pixel(dim, src_pixels,x-1 ,y+1, px_delta)
change_pixel(dim, src_pixels,x+1 ,y+1, px_delta)
change_pixel(dim, src_pixels,x ,y+2, px_delta)
px_delta = [a*3/48 for a in px_d]
change_pixel(dim, src_pixels,x-2 ,y+1, px_delta)
change_pixel(dim, src_pixels,x+2 ,y+1, px_delta)
change_pixel(dim, src_pixels,x-1 ,y+2, px_delta)
change_pixel(dim, src_pixels,x+1 ,y+2, px_delta)
px_delta = [a*1/48 for a in px_d]
change_pixel(dim, src_pixels,x-2 ,y+2, px_delta)
change_pixel(dim, src_pixels,x+2 ,y+2, px_delta)
def stucki(dim, src_pixels, x, y, px_d):
px_delta = [a*8/42 for a in px_d]
change_pixel(dim, src_pixels,x+1 ,y, px_delta)
change_pixel(dim, src_pixels,x ,y+1, px_delta)
px_delta = [a*4/42 for a in px_d]
change_pixel(dim, src_pixels,x+2 ,y, px_delta)
change_pixel(dim, src_pixels,x-1 ,y+1, px_delta)
change_pixel(dim, src_pixels,x+1 ,y+1, px_delta)
change_pixel(dim, src_pixels,x ,y+2, px_delta)
px_delta = [a*2/42 for a in px_d]
change_pixel(dim, src_pixels,x-2 ,y+1, px_delta)
change_pixel(dim, src_pixels,x+2 ,y+1, px_delta)
change_pixel(dim, src_pixels,x-1 ,y+2, px_delta)
change_pixel(dim, src_pixels,x+1 ,y+2, px_delta)
px_delta = [a*1/42 for a in px_d]
change_pixel(dim, src_pixels,x-2 ,y+2, px_delta)
change_pixel(dim, src_pixels,x+2 ,y+2, px_delta)
def atkinson(dim, src_pixels, x, y, px_d):
px_delta = [a*1/8 for a in px_d]
change_pixel(dim, src_pixels,x+1 ,y, px_delta)
change_pixel(dim, src_pixels,x+2 ,y, px_delta)
change_pixel(dim, src_pixels,x-1 ,y+1, px_delta)
change_pixel(dim, src_pixels,x ,y+1, px_delta)
change_pixel(dim, src_pixels,x+1 ,y+1, px_delta)
change_pixel(dim, src_pixels,x ,y+2, px_delta)
def burkes(dim, src_pixels, x, y, px_d):
px_delta = [a*8/32 for a in px_d]
change_pixel(dim, src_pixels,x+1 ,y, px_delta)
change_pixel(dim, src_pixels,x ,y+1, px_delta)
px_delta = [a*4/32 for a in px_d]
change_pixel(dim, src_pixels,x+2 ,y, px_delta)
change_pixel(dim, src_pixels,x-1 ,y+1, px_delta)
change_pixel(dim, src_pixels,x+1 ,y+1, px_delta)
px_delta = [a*2/32 for a in px_d]
change_pixel(dim, src_pixels,x-2 ,y+1, px_delta)
change_pixel(dim, src_pixels,x+2 ,y+1, px_delta)
def sierra3(dim, src_pixels, x, y, px_d):
px_delta = [a*5/32 for a in px_d]
change_pixel(dim, src_pixels,x+1 ,y, px_delta)
change_pixel(dim, src_pixels,x ,y+1, px_delta)
px_delta = [a*4/32 for a in px_d]
change_pixel(dim, src_pixels,x-1 ,y+1, px_delta)
change_pixel(dim, src_pixels,x+1 ,y+1, px_delta)
px_delta = [a*3/32 for a in px_d]
change_pixel(dim, src_pixels,x+2 ,y, px_delta)
change_pixel(dim, src_pixels,x ,y+2, px_delta)
px_delta = [a*2/42 for a in px_d]
change_pixel(dim, src_pixels,x-2 ,y+1, px_delta)
change_pixel(dim, src_pixels,x-1 ,y+2, px_delta)
change_pixel(dim, src_pixels,x+1 ,y+2, px_delta)
change_pixel(dim, src_pixels,x+2 ,y+1, px_delta)
def sierra2(dim, src_pixels, x, y, px_d):
px_delta = [a*4/16 for a in px_d]
change_pixel(dim, src_pixels,x+1 ,y, px_delta)
px_delta = [a*3/16 for a in px_d]
change_pixel(dim, src_pixels,x+2 ,y, px_delta)
change_pixel(dim, src_pixels,x ,y+1, px_delta)
px_delta = [a*2/16 for a in px_d]
change_pixel(dim, src_pixels,x-1 ,y+1, px_delta)
change_pixel(dim, src_pixels,x+1 ,y+1, px_delta)
px_delta = [a*1/16 for a in px_d]
change_pixel(dim, src_pixels,x-2 ,y+1, px_delta)
change_pixel(dim, src_pixels,x+2 ,y+1, px_delta)
def sierra1(dim, src_pixels, x, y, px_d):
px_delta = [a*2/4 for a in px_d]
change_pixel(dim, src_pixels, x+1, y , px_delta)
px_delta = [a*1/4 for a in px_d]
change_pixel(dim, src_pixels, x , y+1, px_delta)
change_pixel(dim, src_pixels, x-1, y+1, px_delta)
def px_dist(p1,p2):
return sum([(a-b)**2 for a,b in zip(p1,p2)])
def retro_dither(timg, tdrawable, mode, palette, alg):
dither = {0 : floyd_stein,
1 : jarvis,
2 : stucki,
3 : atkinson,
4 : burkes,
5 : sierra3,
6 : sierra2,
7 : sierra1,
}
pdb.gimp_image_undo_group_start(timg)
try:
layer = tdrawable.copy()
timg.add_layer(layer, 0)
width = layer.width
height = layer.height
px_size = pdb.gimp_drawable_bpp(tdrawable)
dim = width, height, px_size
rgn = layer.get_pixel_rgn(0, 0, width, height, TRUE, FALSE)
src_pixels = array("B", rgn[0:width, 0:height])
i=0
total=px_size*width*height
if mode == 1:
num_colors, colors = pdb.gimp_palette_get_colors(palette)
for y in range(height):
for x in range(width):
pixel = src_pixels[i:i+px_size]
dist = [px_dist(colors[c],pixel) for c in range(num_colors)]
nearest = colors[dist.index(min(dist))][0:px_size]
src_pixels[i:i+px_size] = array("B",nearest)
px_delta = [(a-b) for a,b in zip(pixel,src_pixels[i:i+px_size])]
dither[alg](dim, src_pixels, x, y, px_delta)
gimp.progress_update(i*1.0/total)
i = i + px_size
rgn[0:width, 0:height] = src_pixels.tostring()
layer.flush()
layer.update(0,0,width,height)
pdb.gimp_image_convert_indexed(timg, NO_DITHER, CUSTOM_PALETTE, 0, FALSE, FALSE, palette)
else:
white = [255] * px_size
black = [0] * px_size
for y in range(height):
for x in range(width):
pixel = src_pixels[i:i+px_size]
dist = sum(pixel)/px_size
if dist > 127:
nearest = white
else:
nearest = black
src_pixels[i:i+px_size] = array("B",nearest)
px_delta = [(a-b) for a,b in zip(pixel,src_pixels[i:i+px_size])]
dither[alg](dim, src_pixels, x, y, px_delta)
gimp.progress_update(i*1.0/total)
i = i + px_size
rgn[0:width, 0:height] = src_pixels.tostring()
layer.flush()
layer.update(0,0,width,height)
pdb.gimp_image_convert_indexed(timg, NO_DITHER, MONO_PALETTE, 0, FALSE, FALSE, palette)
pdb.gimp_image_undo_group_end(timg)
except Exception, err:
pdb.gimp_message("ERR: " + str(err))
pdb.gimp_image_undo_group_end(timg)
register(
"retro_dither",
"Error-diffusion dithering algorithms to create low resolution pictures in B&W or with a limited palette",
"Error-diffusion dithering algorithms",
"Mario Patino",
"Mario Patino",
"2014",
"<Image>/Colors/Dither...",
"RGB*, GRAY*",
[
(PF_RADIO, "mode", "Dithering mode:", 0, (("Monochrome",0),("Color", 1))),
(PF_PALETTE, "palette", "Palette:", "Default"),
(PF_OPTION,"alg", "Dithering algorithm:", 0, ["Floyd-Steinberg","Jarvis, Judice, and Ninke","Stucki","Atkinson", "Burkes", "Sierra", "Two-Row Sierra", "Sierra Lite"])
],
[],
retro_dither)
main()