Closed zeffii closed 7 years ago
especially sierra dithering looks neat , implementing all should be doable.
https://gist.github.com/zeffii/8f3ddcde3f18898e7cb1b4990e418f52 - don't bother trying yet.
didn't expect this to work..
#
# parts taken from G. Landini at bham. ac. uk
#
import numpy as np
import bpy
class DitherImage():
def __init__(self, np, img_name, greyscale_mode='lightness', dither_method="Floyd-Steinberg"):
self.bpy_img = bpy.data.images.get(img_name)
if not self.bpy_img:
return
self.width, self.height = self.bpy_img.size
self.tmp = np.array(self.bpy_img.pixels[:])
self.image = self.tmp.reshape(self.height, self.width, 4)
self.greyscale_mode, self.dither_method = greyscale_mode, dither_method
self.make_greyscale(np)
self.resize(np)
self.generate_dither(np)
def make_greyscale(self, np):
# reorder from bottom left to top left
image_ud = np.flipud(self.image)
self.just_rgb = image_ud[:, :, :-1]
self.greyscale_mode = 'luminosity'
if self.greyscale_mode == 'lightness':
# max(r, g, b) + min(r, g, b) / 2
self.greyscale = (np.max(self.just_rgb, axis=2) + np.min(self.just_rgb, axis=2)) / 2
elif self.greyscale_mode == 'average':
# (r, g, b) / 3
self.greyscale = np.mean(self.just_rgb, axis=2)
elif self.greyscale_mode == 'luminosity':
# 0.21*r + 0.72*g + 0.07*b
def myfunc(rgb):
return 0.21*rgb[0] + 0.72*rgb[1] + 0.07*rgb[2]
self.greyscale = np.apply_along_axis(myfunc, 2, self.just_rgb)
def resize(self, np):
''' expand by required padding for error carrying '''
# name #x, y
padding_x, padding_y = {
"Floyd-Steinberg": (1, 1),
"Atkinson": (2, 2),
"Jarvis-Judice-Ninke": (2, 2),
"Stucki": (2, 2)
}.get(self.dither_method)
# add columns
for _ in range(padding_x):
b = np.zeros((self.greyscale.shape[0], self.greyscale.shape[1]+1))
b[:, :-1] = self.greyscale
self.greyscale = b
# add rows
for _ in range(padding_y):
new_row = np.zeros(self.greyscale.shape[1])
self.greyscale = np.vstack([self.greyscale, new_row])
def generate_dither(self, np):
# pylint: disable=C0326
def getPixel(x, y):
return self.greyscale[x][y]
def setPixel(x, y, m):
self.greyscale[x][y] = m
w = self.width
h = self.height
if self.dither_method == "Floyd-Steinberg":
w1 = 7/16
w2 = 3/16
w3 = 5/16
w4 = 1/16
for y in range(h):
for x in range(w):
oldpixel = getPixel(x, y)
newpixel = 0 if oldpixel < 0.5 else 1.0
setPixel(x, y, newpixel)
quant_error = oldpixel - newpixel
setPixel(x+1, y, getPixel(x+1, y) + w1 * quant_error)
setPixel(x-1, y+1, getPixel(x-1, y+1) + w2 * quant_error)
setPixel(x, y+1, getPixel(x, y+1) + w3 * quant_error)
setPixel(x+1, y+1, getPixel(x+1, y+1) + w4 * quant_error)
elif self.dither_method == "Atkinson":
w1 = 1/8
for y in range(h):
for x in range(w):
oldpixel = getPixel(x, y)
newpixel = 0 if oldpixel < 0.5 else 1.0
setPixel(x, y, newpixel)
quant_error = oldpixel - newpixel
setPixel(x+1, y, getPixel(x+1, y) + w1 * quant_error)
setPixel(x+2, y, getPixel(x+2, y) + w1 * quant_error)
setPixel(x-1, y+1, getPixel(x-1, y+1) + w1 * quant_error)
setPixel(x, y+1, getPixel(x, y+1) + w1 * quant_error)
setPixel(x+1, y+1, getPixel(x+1, y+1) + w1 * quant_error)
setPixel(x, y+2, getPixel(x, y+2) + w1 * quant_error)
elif self.dither_method == "Jarvis-Judice-Ninke":
w7 = 7/48
w5 = 5/48
w3 = 3/48
w1 = 1/48
for y in range(h):
for x in range(w):
oldpixel = getPixel(x, y)
newpixel = 0 if oldpixel < 0.5 else 1.0
setPixel(x, y, newpixel)
quant_error = oldpixel - newpixel
setPixel(x+1, y, getPixel(x+1, y) + w7 * quant_error)
setPixel(x+2, y, getPixel(x+2, y) + w5 * quant_error)
setPixel(x-2, y+1, getPixel(x-2, y+1) + w3 * quant_error)
setPixel(x-1, y+1, getPixel(x-1, y+1) + w5 * quant_error)
setPixel(x, y+1, getPixel(x, y+1) + w7 * quant_error)
setPixel(x+1, y+1, getPixel(x+1, y+1) + w5 * quant_error)
setPixel(x+2, y+1, getPixel(x+2, y+1) + w3 * quant_error)
setPixel(x-2, y+2, getPixel(x-2, y+2) + w1 * quant_error)
setPixel(x-1, y+2, getPixel(x-1, y+2) + w3 * quant_error)
setPixel(x, y+2, getPixel(x, y+2) + w5 * quant_error)
setPixel(x+1, y+2, getPixel(x+1, y+2) + w3 * quant_error)
setPixel(x+2, y+2, getPixel(x+2, y+2) + w1 * quant_error)
elif self.dither_method == "Stucki":
w8 = 8/42
w7 = 7/42
w5 = 5/42
w4 = 4/42
w2 = 2/42
w1 = 1/42
for y in range(h):
for x in range(w):
oldpixel = getPixel(x, y)
newpixel = 0 if oldpixel < 0.5 else 1.0
setPixel(x, y, newpixel)
quant_error = oldpixel - newpixel
setPixel(x+1, y, getPixel(x+1, y) + w7 * quant_error)
setPixel(x+2, y, getPixel(x+2, y) + w5 * quant_error)
setPixel(x-2, y+1, getPixel(x-2, y+1) + w2 * quant_error)
setPixel(x-1, y+1, getPixel(x-1, y+1) + w4 * quant_error)
setPixel(x, y+1, getPixel(x, y+1) + w8 * quant_error)
setPixel(x+1, y+1, getPixel(x+1, y+1) + w4 * quant_error)
setPixel(x+2, y+1, getPixel(x+2, y+1) + w2 * quant_error)
setPixel(x-2, y+2, getPixel(x-2, y+2) + w1 * quant_error)
setPixel(x-1, y+2, getPixel(x-1, y+2) + w2 * quant_error)
setPixel(x, y+2, getPixel(x, y+2) + w4 * quant_error)
setPixel(x+1, y+2, getPixel(x+1, y+2) + w2 * quant_error)
setPixel(x+2, y+2, getPixel(x+2, y+2) + w1 * quant_error)
modes = ["Floyd-Steinberg", "Atkinson", "Jarvis-Judice-Ninke", "Stucki"]
img = DitherImage(np, 'lena.bmp')
def sv_main(scale=0.5):
verts = [[]]
add = verts[0].append
in_sockets = [
['s', 'scale', scale]]
for y in range(img.height):
for x in range(img.width):
if img.greyscale[x][y] == 1:
add([x*scale, y*scale, 0.0])
out_sockets = [
['v', 'verts', verts]
]
return in_sockets, out_sockets
Zeffii is the Man, thanks!!
Although most unusual error....
well, it is a SN1 script... so I haven't looked at this in ages...
i think that dither scripting adventure made me want to write SNLite, for stateful stuff like this.
snlite version, requires a square image. with height == width. welcome to try and fix that :)
"""
in scale s d=0.5 n=2
out verts v
"""
def setup():
import numpy as np
import dither_network8
# file must be square at the moment..
img = dither_network8.DitherImage(np, 'lena_208_208.jpg')
print(img.height)
verts.append([])
add = verts[0].append
for y in range(img.height):
for x in range(img.width):
if img.greyscale[x][y] == 1:
add([x*scale, y*scale, 0.0])
then a separate text datablok named dither_network8.py
#
# parts taken from G. Landini at bham. ac. uk
#
import numpy as np
import bpy
class DitherImage():
def __init__(self, np, img_name, greyscale_mode='lightness', dither_method="Floyd-Steinberg"):
self.bpy_img = bpy.data.images.get(img_name)
if not self.bpy_img:
return
self.width, self.height = self.bpy_img.size
self.tmp = np.array(self.bpy_img.pixels[:])
self.image = self.tmp.reshape(self.height, self.width, 4)
self.greyscale_mode, self.dither_method = greyscale_mode, dither_method
self.make_greyscale(np)
self.resize(np)
self.generate_dither(np)
def make_greyscale(self, np):
# reorder from bottom left to top left
image_ud = np.flipud(self.image)
self.just_rgb = image_ud[:, :, :-1]
self.greyscale_mode = 'luminosity'
if self.greyscale_mode == 'lightness':
# max(r, g, b) + min(r, g, b) / 2
self.greyscale = (np.max(self.just_rgb, axis=2) + np.min(self.just_rgb, axis=2)) / 2
elif self.greyscale_mode == 'average':
# (r, g, b) / 3
self.greyscale = np.mean(self.just_rgb, axis=2)
elif self.greyscale_mode == 'luminosity':
# 0.21*r + 0.72*g + 0.07*b
def myfunc(rgb):
return 0.21*rgb[0] + 0.72*rgb[1] + 0.07*rgb[2]
self.greyscale = np.apply_along_axis(myfunc, 2, self.just_rgb)
def resize(self, np):
''' expand by required padding for error carrying '''
# name #x, y
padding_x, padding_y = {
"Floyd-Steinberg": (1, 1),
"Atkinson": (2, 2),
"Jarvis-Judice-Ninke": (2, 2),
"Stucki": (2, 2)
}.get(self.dither_method)
# add columns
for _ in range(padding_x):
b = np.zeros((self.greyscale.shape[0], self.greyscale.shape[1]+1))
b[:, :-1] = self.greyscale
self.greyscale = b
# add rows
for _ in range(padding_y):
new_row = np.zeros(self.greyscale.shape[1])
self.greyscale = np.vstack([self.greyscale, new_row])
def generate_dither(self, np):
# pylint: disable=C0326
def getPixel(x, y):
return self.greyscale[x][y]
def setPixel(x, y, m):
self.greyscale[x][y] = m
w = self.width
h = self.height
if self.dither_method == "Floyd-Steinberg":
w1 = 7/16
w2 = 3/16
w3 = 5/16
w4 = 1/16
for y in range(h):
for x in range(w):
oldpixel = getPixel(x, y)
newpixel = 0 if oldpixel < 0.5 else 1.0
setPixel(x, y, newpixel)
quant_error = oldpixel - newpixel
setPixel(x+1, y, getPixel(x+1, y) + w1 * quant_error)
setPixel(x-1, y+1, getPixel(x-1, y+1) + w2 * quant_error)
setPixel(x, y+1, getPixel(x, y+1) + w3 * quant_error)
setPixel(x+1, y+1, getPixel(x+1, y+1) + w4 * quant_error)
elif self.dither_method == "Atkinson":
w1 = 1/8
for y in range(h):
for x in range(w):
oldpixel = getPixel(x, y)
newpixel = 0 if oldpixel < 0.5 else 1.0
setPixel(x, y, newpixel)
quant_error = oldpixel - newpixel
setPixel(x+1, y, getPixel(x+1, y) + w1 * quant_error)
setPixel(x+2, y, getPixel(x+2, y) + w1 * quant_error)
setPixel(x-1, y+1, getPixel(x-1, y+1) + w1 * quant_error)
setPixel(x, y+1, getPixel(x, y+1) + w1 * quant_error)
setPixel(x+1, y+1, getPixel(x+1, y+1) + w1 * quant_error)
setPixel(x, y+2, getPixel(x, y+2) + w1 * quant_error)
elif self.dither_method == "Jarvis-Judice-Ninke":
w7 = 7/48
w5 = 5/48
w3 = 3/48
w1 = 1/48
for y in range(h):
for x in range(w):
oldpixel = getPixel(x, y)
newpixel = 0 if oldpixel < 0.5 else 1.0
setPixel(x, y, newpixel)
quant_error = oldpixel - newpixel
setPixel(x+1, y, getPixel(x+1, y) + w7 * quant_error)
setPixel(x+2, y, getPixel(x+2, y) + w5 * quant_error)
setPixel(x-2, y+1, getPixel(x-2, y+1) + w3 * quant_error)
setPixel(x-1, y+1, getPixel(x-1, y+1) + w5 * quant_error)
setPixel(x, y+1, getPixel(x, y+1) + w7 * quant_error)
setPixel(x+1, y+1, getPixel(x+1, y+1) + w5 * quant_error)
setPixel(x+2, y+1, getPixel(x+2, y+1) + w3 * quant_error)
setPixel(x-2, y+2, getPixel(x-2, y+2) + w1 * quant_error)
setPixel(x-1, y+2, getPixel(x-1, y+2) + w3 * quant_error)
setPixel(x, y+2, getPixel(x, y+2) + w5 * quant_error)
setPixel(x+1, y+2, getPixel(x+1, y+2) + w3 * quant_error)
setPixel(x+2, y+2, getPixel(x+2, y+2) + w1 * quant_error)
elif self.dither_method == "Stucki":
w8 = 8/42
w7 = 7/42
w5 = 5/42
w4 = 4/42
w2 = 2/42
w1 = 1/42
for y in range(h):
for x in range(w):
oldpixel = getPixel(x, y)
newpixel = 0 if oldpixel < 0.5 else 1.0
setPixel(x, y, newpixel)
quant_error = oldpixel - newpixel
setPixel(x+1, y, getPixel(x+1, y) + w7 * quant_error)
setPixel(x+2, y, getPixel(x+2, y) + w5 * quant_error)
setPixel(x-2, y+1, getPixel(x-2, y+1) + w2 * quant_error)
setPixel(x-1, y+1, getPixel(x-1, y+1) + w4 * quant_error)
setPixel(x, y+1, getPixel(x, y+1) + w8 * quant_error)
setPixel(x+1, y+1, getPixel(x+1, y+1) + w4 * quant_error)
setPixel(x+2, y+1, getPixel(x+2, y+1) + w2 * quant_error)
setPixel(x-2, y+2, getPixel(x-2, y+2) + w1 * quant_error)
setPixel(x-1, y+2, getPixel(x-1, y+2) + w2 * quant_error)
setPixel(x, y+2, getPixel(x, y+2) + w4 * quant_error)
setPixel(x+1, y+2, getPixel(x+1, y+2) + w2 * quant_error)
setPixel(x+2, y+2, getPixel(x+2, y+2) + w1 * quant_error)
modes = ["Floyd-Steinberg", "Atkinson", "Jarvis-Judice-Ninke", "Stucki"]
# img = DitherImage(np, 'lena.bmp')
i remember now.. (and tested) it needs pictures that have an even
pixel width/height. (512*512) works
either that or the image needs to be square... (which suggest i flipped height-width somewhere )
not sure when to use. http://www.tannerhelland.com/4660/dithering-eleven-algorithms-source-code/