aboutsummaryrefslogtreecommitdiff
path: root/dither.go
blob: ac8f4eb2188f5cd99612c5b39c5e015e927834f6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package epd7in3e

import (
	"image"
	"image/draw"
)

// Quantise maps img to a *image.Paletted on the panel's Palette. With
// dither it applies Floyd-Steinberg error diffusion; otherwise each
// pixel becomes its nearest palette colour.
func Quantise(img image.Image, dither bool) *image.Paletted {
	if dither {
		return floydSteinberg(img)
	}
	r := img.Bounds()
	p := image.NewPaletted(r, Palette)
	// Drawing into a Paletted quantises each pixel to the nearest
	// palette colour.
	draw.Draw(p, r, img, r.Min, draw.Src)
	return p
}

// floydSteinberg dithers img to the panel's Palette using
// Floyd-Steinberg error diffusion.
func floydSteinberg(img image.Image) *image.Paletted {
	r := img.Bounds()
	w, h := r.Dx(), r.Dy()

	// Work on a plain NRGBA copy so the loop reads raw bytes.
	src := image.NewNRGBA(r)
	draw.Draw(src, r, img, r.Min, draw.Src)

	// Float copy so diffused error may overshoot 0..255.
	f := make([]float32, w*h*3)
	for y := 0; y < h; y++ {
		i := (y-r.Min.Y)*src.Stride + 4*r.Min.X
		for x := 0; x < w; x++ {
			k := (y*w + x) * 3
			f[k] = float32(src.Pix[i])
			f[k+1] = float32(src.Pix[i+1])
			f[k+2] = float32(src.Pix[i+2])
			i += 4
		}
	}

	p := image.NewPaletted(r, Palette)
	for y := 0; y < h; y++ {
		i := (y-r.Min.Y)*p.Stride + r.Min.X
		for x := 0; x < w; x++ {
			k := (y*w + x) * 3
			cr, cg, cb := f[k], f[k+1], f[k+2]
			idx := nearestIndex(cr, cg, cb)
			p.Pix[i+x] = idx
			pp := paletteRGB[idx]
			er := cr - float32(pp[0])
			eg := cg - float32(pp[1])
			eb := cb - float32(pp[2])
			if x+1 < w { // right: 7/16
				j := k + 3
				f[j] += er * 7 / 16
				f[j+1] += eg * 7 / 16
				f[j+2] += eb * 7 / 16
			}
			if y+1 < h {
				jb := k + 3*w // below: 5/16
				f[jb] += er * 5 / 16
				f[jb+1] += eg * 5 / 16
				f[jb+2] += eb * 5 / 16
				if x > 0 { // bottom-left: 3/16
					j := jb - 3
					f[j] += er * 3 / 16
					f[j+1] += eg * 3 / 16
					f[j+2] += eb * 3 / 16
				}
				if x+1 < w { // bottom-right: 1/16
					j := jb + 3
					f[j] += er / 16
					f[j+1] += eg / 16
					f[j+2] += eb / 16
				}
			}
		}
	}
	return p
}

// nearestIndex returns the Palette index whose RGB value is closest
// to (r, g, b).
func nearestIndex(r, g, b float32) uint8 {
	var best uint8
	bestD := float32(1 << 30)
	for i := range paletteRGB {
		p := paletteRGB[i]
		dr := r - float32(p[0])
		dg := g - float32(p[1])
		db := b - float32(p[2])
		if d := dr*dr + dg*dg + db*db; d < bestD {
			best, bestD = uint8(i), d
		}
	}
	return best
}