|
|
|
|
@ -4,40 +4,120 @@ import ( |
|
|
|
|
"image" |
|
|
|
|
"image/draw" |
|
|
|
|
"image/color" |
|
|
|
|
"math" |
|
|
|
|
"github.com/disintegration/gift" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
const ( |
|
|
|
|
NoDither=0 |
|
|
|
|
FloydSteinDither=1 |
|
|
|
|
AtkinsonDither=2 |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type PosterizeFilter struct { |
|
|
|
|
Depth uint8 |
|
|
|
|
Depth uint16 |
|
|
|
|
Bins map[uint16]uint16 |
|
|
|
|
DitherType int |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func Posterize(depth uint8) gift.Filter { |
|
|
|
|
return PosterizeFilter{depth} |
|
|
|
|
func Posterize(depth uint16, dither_type int) gift.Filter { |
|
|
|
|
var i uint16 |
|
|
|
|
bins := make(map[uint16]uint16) |
|
|
|
|
chunk := uint16((math.MaxUint16 + 1) / int(depth)) |
|
|
|
|
|
|
|
|
|
// BUG: this was fine with uint8, now it's dumb as hell
|
|
|
|
|
for i = 0; i < math.MaxUint16; i++ { |
|
|
|
|
bins[i] = (i / chunk) * chunk |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return PosterizeFilter{depth, bins, dither_type} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (filter PosterizeFilter) Bounds(src image.Rectangle) (dst_bounds image.Rectangle) { |
|
|
|
|
return src |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func bin(c uint8) uint8 { |
|
|
|
|
if c <= 64 { |
|
|
|
|
return 0 |
|
|
|
|
} else { |
|
|
|
|
return 255 |
|
|
|
|
func (filter PosterizeFilter) Quantize(c color.NRGBA64) (color.NRGBA64, color.NRGBA64) { |
|
|
|
|
|
|
|
|
|
newpixel := color.NRGBA64{ |
|
|
|
|
filter.Bins[c.R], |
|
|
|
|
filter.Bins[c.G], |
|
|
|
|
filter.Bins[c.B], |
|
|
|
|
c.A, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
err := color.NRGBA64{ |
|
|
|
|
c.R - newpixel.R, |
|
|
|
|
c.G - newpixel.G, |
|
|
|
|
c.B - newpixel.B, |
|
|
|
|
c.A, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return newpixel, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func DistError(old color.Color, quant_err color.NRGBA64, term float32) color.Color { |
|
|
|
|
dumb := old.(color.NRGBA64) |
|
|
|
|
|
|
|
|
|
c := color.NRGBA64{ |
|
|
|
|
dumb.R + uint16(float32(quant_err.R) * term), |
|
|
|
|
dumb.G + uint16(float32(quant_err.G) * term), |
|
|
|
|
dumb.B + uint16(float32(quant_err.B) * term), |
|
|
|
|
dumb.A, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return c |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (filter PosterizeFilter) FloydStein(x int, y int, dst draw.Image, quant_err color.NRGBA64) { |
|
|
|
|
dst.Set(x+1, y, |
|
|
|
|
DistError(dst.At(x+1, y), quant_err, 7.0/16.0)) |
|
|
|
|
dst.Set(x-1, y+1, |
|
|
|
|
DistError(dst.At(x-1, y+1), quant_err, 3.0/16.0)) |
|
|
|
|
dst.Set(x, y+1, |
|
|
|
|
DistError(dst.At(x, y+1), quant_err, 5.0/16.0)) |
|
|
|
|
dst.Set(x+1, y+1, |
|
|
|
|
DistError(dst.At(x+1, y+1), quant_err, 1.0/16.0)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (filter PosterizeFilter) Atkinson(x int, y int, dst draw.Image, quant_err color.NRGBA64) { |
|
|
|
|
delta := [6]image.Point{ |
|
|
|
|
{x+1, y+0}, |
|
|
|
|
{x+2, y+0}, |
|
|
|
|
{x-1, y+1}, |
|
|
|
|
{x+0, y+1}, |
|
|
|
|
{x+1, y+1}, |
|
|
|
|
{x+0, y+2}, |
|
|
|
|
} |
|
|
|
|
bounds := dst.Bounds() |
|
|
|
|
|
|
|
|
|
for _, d := range(delta) { |
|
|
|
|
if d.In(bounds) { |
|
|
|
|
dst.Set(d.X, d.Y, DistError(dst.At(d.X, d.Y), quant_err, 1.0/8.0)) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (filter PosterizeFilter) Draw(dst draw.Image, src image.Image, _ *gift.Options) { |
|
|
|
|
bounds := src.Bounds() |
|
|
|
|
|
|
|
|
|
gift.New().Draw(dst, src) |
|
|
|
|
|
|
|
|
|
for y := bounds.Min.Y; y < bounds.Max.Y; y++ { |
|
|
|
|
for x := bounds.Min.X; x < bounds.Max.X; x++ { |
|
|
|
|
from := src.At(x, y) |
|
|
|
|
c := from.(color.NRGBA) |
|
|
|
|
c.R = bin(c.R) |
|
|
|
|
c.G = bin(c.G) |
|
|
|
|
c.B = bin(c.B) |
|
|
|
|
dst.Set(x, y, c) |
|
|
|
|
from := dst.At(x, y) |
|
|
|
|
c := from.(color.NRGBA64) |
|
|
|
|
newpixel, quant_err := filter.Quantize(c) |
|
|
|
|
|
|
|
|
|
dst.Set(x, y, newpixel) |
|
|
|
|
|
|
|
|
|
switch filter.DitherType { |
|
|
|
|
case FloydSteinDither: |
|
|
|
|
filter.FloydStein(x, y, dst, quant_err) |
|
|
|
|
case AtkinsonDither: |
|
|
|
|
filter.Atkinson(x, y, dst, quant_err) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|