Skip to content

Instantly share code, notes, and snippets.

@zeffii
Last active December 27, 2016 14:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zeffii/8f3ddcde3f18898e7cb1b4990e418f52 to your computer and use it in GitHub Desktop.
Save zeffii/8f3ddcde3f18898e7cb1b4990e418f52 to your computer and use it in GitHub Desktop.
import numpy as np
import bpy
class Image():
def __init__(self, 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()
self.resize()
self.generate_dither()
def make_greyscale(self):
# 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):
''' 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):
# 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"]
Image('lena_crop.bmp')
@nortikin
Copy link

where it works?

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