-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathsierra.go
158 lines (150 loc) · 4.03 KB
/
sierra.go
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// Copyright 2013 Sonia Keys.
// Licensed under MIT license. See "license" file in this source tree.
package quant
import (
"image"
"image/color"
"image/draw"
"math"
)
// Sierra24A satisfies draw.Drawer
type Sierra24A struct{}
var _ draw.Drawer = Sierra24A{}
// Draw performs error diffusion dithering.
//
// This method satisfies the draw.Drawer interface, implementing a dithering
// filter attributed to Frankie Sierra. It uses the kernel
//
// X 2
// 1 1
func (d Sierra24A) Draw(dst draw.Image, r image.Rectangle, src image.Image, sp image.Point) {
pd, ok := dst.(*image.Paletted)
if !ok {
// dither211 currently requires a palette
draw.Draw(dst, r, src, sp, draw.Src)
return
}
// intersect r with both dst and src bounds, fix up sp.
ir := r.Intersect(pd.Bounds()).
Intersect(src.Bounds().Add(r.Min.Sub(sp)))
if ir.Empty() {
return // no work to do.
}
sp = ir.Min.Sub(r.Min)
// get subimage of src
sr := ir.Add(sp)
if !sr.Eq(src.Bounds()) {
s, ok := src.(interface {
SubImage(image.Rectangle) image.Image
})
if !ok {
// dither211 currently works on whole images
draw.Draw(dst, r, src, sp, draw.Src)
return
}
src = s.SubImage(sr)
}
// dither211 currently returns a new image, or nil if dithering not
// possible.
if s := dither211(src, pd.Palette); s != nil {
src = s
}
// this avoids any problem of src dst overlap but it would usually
// work to render directly into dst. todo.
draw.Draw(dst, r, src, image.Point{}, draw.Src)
}
// signed color type, no alpha. signed to represent color deltas as well as
// color values 0-ffff as with colorRGBA64
type sRGB struct{ r, g, b int32 }
type sPalette []sRGB
func (p sPalette) index(c sRGB) int {
// still the awful linear search
i, min := 0, int64(math.MaxInt64)
for j, pc := range p {
d := int64(c.r) - int64(pc.r)
s := d * d
d = int64(c.g) - int64(pc.g)
s += d * d
d = int64(c.b) - int64(pc.b)
s += d * d
if s < min {
min = s
i = j
}
}
return i
}
// currently this is strictly a helper function for Dither211.Draw, so
// not generalized to use Palette from this package.
func dither211(i0 image.Image, cp color.Palette) *image.Paletted {
if len(cp) > 256 {
// representation limit of image.Paletted. a little sketchy to return
// nil, but unworkable results are always better than wrong results.
return nil
}
b := i0.Bounds()
pi := image.NewPaletted(b, cp)
if b.Empty() {
return pi // no work to do
}
sp := make(sPalette, len(cp))
for i, c := range cp {
r, g, b, _ := c.RGBA()
sp[i] = sRGB{int32(r), int32(g), int32(b)}
}
// afc is adjustd full color. e, rt, dn hold diffused errors.
var afc, e, rt sRGB
dn := make([]sRGB, b.Dx()+1)
for y := b.Min.Y; y < b.Max.Y; y++ {
rt = dn[0]
dn[0] = sRGB{}
for x := b.Min.X; x < b.Max.X; x++ {
// full color from original image
r0, g0, b0, _ := i0.At(x, y).RGBA()
// adjusted full color = original color + diffused error
afc.r = int32(r0) + rt.r>>2
afc.g = int32(g0) + rt.g>>2
afc.b = int32(b0) + rt.b>>2
// clipping or clamping is usually explained as necessary
// to avoid integer overflow but with palettes that do not
// represent the full color space of the image, it is needed
// to keep areas of excess color from saturating at palette
// limits and bleeding into neighboring areas.
if afc.r < 0 {
afc.r = 0
} else if afc.r > 0xffff {
afc.r = 0xffff
}
if afc.g < 0 {
afc.g = 0
} else if afc.g > 0xffff {
afc.g = 0xffff
}
if afc.b < 0 {
afc.b = 0
} else if afc.b > 0xffff {
afc.b = 0xffff
}
// nearest palette entry
i := sp.index(afc)
// set pixel in destination image
pi.SetColorIndex(x, y, uint8(i))
// error to be diffused = full color - palette color.
pc := sp[i]
e.r = afc.r - pc.r
e.g = afc.g - pc.g
e.b = afc.b - pc.b
// half of error*4 goes right
dx := x - b.Min.X + 1
rt.r = dn[dx].r + e.r*2
rt.g = dn[dx].g + e.g*2
rt.b = dn[dx].b + e.b*2
// the other half goes down
dn[dx] = e
dn[dx-1].r += e.r
dn[dx-1].g += e.g
dn[dx-1].b += e.b
}
}
return pi
}