|
|
|
|
package filters
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"image"
|
|
|
|
|
"image/draw"
|
|
|
|
|
"image/color"
|
|
|
|
|
"math"
|
|
|
|
|
"github.com/disintegration/gift"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
NoDither=0
|
|
|
|
|
FloydSteinDither=1
|
|
|
|
|
AtkinsonDither=2
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type PosterizeFilter struct {
|
|
|
|
|
Depth uint16
|
|
|
|
|
Bins map[uint16]uint16
|
|
|
|
|
DitherType int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 (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 := 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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|