Finally have Atkinson or FloydSteinberg dithering with the index palette and sharpening, so that's primarily what I need.

master
Zed A. Shaw 1 month ago
parent a4746a3e73
commit ef045f18aa
  1. 5
      Makefile
  2. 108
      filters/posterize.go
  3. 14
      main.go

@ -1,6 +1,11 @@
build:
go build .
compare:
./jankifier -input ./tests/images/bash_test.png -output none.png -pixel-width 4 -color-depth 16 -dither 0
./jankifier -input ./tests/images/bash_test.png -output floyd.png -pixel-width 4 -color-depth 16 -dither 1
./jankifier -input ./tests/images/bash_test.png -output atkinson.png -pixel-width 4 -color-depth 16 -dither 2
test:
go test MY/webapp/tests -c
./tests.test

@ -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)
}
}
}
}

@ -7,6 +7,7 @@ import (
"image/png"
"github.com/disintegration/gift"
"flag"
"math"
"lcthw.dev/go/jankifier/filters"
)
@ -38,6 +39,8 @@ type Opts struct {
InFile string
OutFile string
PixelWidth int
ColorDepth int
DitherType int
}
func ParseOpts() Opts {
@ -46,8 +49,14 @@ func ParseOpts() Opts {
flag.StringVar(&opts.InFile, "input", "", "input file.png")
flag.StringVar(&opts.OutFile, "output", "", "output file.png")
flag.IntVar(&opts.PixelWidth, "pixel-width", 4, "pixel width")
flag.IntVar(&opts.ColorDepth, "color-depth", 16, "number of colors in the palette")
flag.IntVar(&opts.DitherType, "dither", 0, "0=none, 1=floyd, 2=atkinson")
flag.Parse()
if opts.ColorDepth > math.MaxUint8 {
log.Fatalf("color-depth can't be greater than %d", math.MaxUint8);
}
return opts
}
@ -58,10 +67,11 @@ func main() {
bounds := src.Bounds()
resize := gift.Resize(bounds.Max.X / opts.PixelWidth, 0, gift.NearestNeighborResampling)
posterize := filters.Posterize(16)
posterize := filters.Posterize(uint16(opts.ColorDepth), opts.DitherType)
upscale := filters.Upscale(bounds, opts.PixelWidth)
sharpen := gift.UnsharpMask(1, 1, 0)
g := gift.New(posterize, resize, upscale)
g := gift.New(resize, posterize, upscale, sharpen)
smaller := image.NewNRGBA(g.Bounds(bounds))
g.Draw(smaller, src)

Loading…
Cancel
Save