nortikin / sverchok

Sverchok
http://nortikin.github.io/sverchok/
GNU General Public License v3.0
2.26k stars 233 forks source link

Image Dithering (11 techniques) #947

Closed zeffii closed 7 years ago

zeffii commented 8 years ago

not sure when to use. http://www.tannerhelland.com/4660/dithering-eleven-algorithms-source-code/

zeffii commented 7 years ago

especially sierra dithering looks neat , implementing all should be doable.

zeffii commented 7 years ago

https://gist.github.com/zeffii/8f3ddcde3f18898e7cb1b4990e418f52 - don't bother trying yet.

zeffii commented 7 years ago

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

image

enzyme69 commented 7 years ago

Zeffii is the Man, thanks!!

enzyme69 commented 7 years ago

Although most unusual error.... screen shot 2017-10-28 at 20 48 24

zeffii commented 7 years ago

well, it is a SN1 script... so I haven't looked at this in ages...

zeffii commented 7 years ago

i think that dither scripting adventure made me want to write SNLite, for stateful stuff like this.

zeffii commented 7 years ago

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')
zeffii commented 7 years ago

i remember now.. (and tested) it needs pictures that have an even pixel width/height. (512*512) works

zeffii commented 7 years ago

either that or the image needs to be square... (which suggest i flipped height-width somewhere )

zeffii commented 7 years ago

image