-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy patherror.go
168 lines (150 loc) · 4 KB
/
error.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
159
160
161
162
163
164
165
166
167
168
package dithering
import (
"image"
"image/color"
"math"
)
// PixelError represents the error for each canal in the image
// when dithering an image
// Errors are floats because they are the result of a division
type PixelError struct {
// TODO(brouxco): the alpha value does not make a lot of sense in a PixelError
R, G, B, A float32
}
// RGBA returns the errors for each canal in the image
func (c PixelError) RGBA() (r, g, b, a uint32) {
return uint32(c.R), uint32(c.G), uint32(c.B), uint32(c.A)
}
// Add adds two PixelError
func (c PixelError) Add(c2 PixelError) PixelError {
r := c.R + c2.R
g := c.G + c2.G
b := c.B + c2.B
return PixelError{r, g, b, 0}
}
// Mul multiplies two PixelError
func (c PixelError) Mul(v float32) PixelError {
r := c.R * v
g := c.G * v
b := c.B * v
return PixelError{r, g, b, 0}
}
func pixelErrorModel(c color.Color) color.Color {
if _, ok := c.(PixelError); ok {
return c
}
r, g, b, a := c.RGBA()
return PixelError{float32(r), float32(g), float32(b), float32(a)}
}
// ErrorImage is an in-memory image whose At method returns dithering.PixelError values
type ErrorImage struct {
// Pix holds the image's pixels, in R, G, B, A order. The pixel at
// (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*4].
Pix []float32
// Stride is the Pix stride between vertically adjacent pixels.
Stride int
// Rect is the image's bounds.
Rect image.Rectangle
// Min & Max values in the image
Min, Max PixelError
}
// ColorModel returns the ErrorImage color model
func (p *ErrorImage) ColorModel() color.Model {
return color.ModelFunc(pixelErrorModel)
}
// Bounds returns the domain for which At can return non-zero color
func (p *ErrorImage) Bounds() image.Rectangle { return p.Rect }
// At returns the color of the pixel at (x, y)
func (p *ErrorImage) At(x, y int) color.Color {
if !(image.Point{x, y}.In(p.Rect)) {
return PixelError{}
}
i := p.PixOffset(x, y)
r := (p.Pix[i+0]) + float32(math.Abs(float64(p.Min.R)))/(p.Max.R-p.Min.R)*255
g := (p.Pix[i+1]) + float32(math.Abs(float64(p.Min.G)))/(p.Max.G-p.Min.G)*255
b := (p.Pix[i+2]) + float32(math.Abs(float64(p.Min.B)))/(p.Max.B-p.Min.B)*255
return color.RGBA{uint8(r), uint8(g), uint8(b), 255}
}
// PixelErrorAt returns the pixel error at (x, y)
func (p *ErrorImage) PixelErrorAt(x, y int) PixelError {
if !(image.Point{x, y}.In(p.Rect)) {
return PixelError{}
}
i := p.PixOffset(x, y)
r := p.Pix[i+0]
g := p.Pix[i+1]
b := p.Pix[i+2]
a := p.Pix[i+3]
return PixelError{r, g, b, a}
}
// PixOffset returns the index of the first element of Pix that corresponds to
// the pixel at (x, y).
func (p *ErrorImage) PixOffset(x, y int) int {
return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4
}
// Set sets the error of the pixel at (x, y)
func (p *ErrorImage) Set(x, y int, c color.Color) {
if !(image.Point{x, y}.In(p.Rect)) {
return
}
i := p.PixOffset(x, y)
c1 := color.ModelFunc(pixelErrorModel).Convert(c).(PixelError)
// TODO(brouxco): use min and max functions maybe ?
if c1.R > p.Max.R {
p.Max.R = c1.R
}
if c1.G > p.Max.G {
p.Max.G = c1.G
}
if c1.B > p.Max.B {
p.Max.B = c1.B
}
if c1.R < p.Min.R {
p.Min.R = c1.R
}
if c1.G < p.Min.G {
p.Min.G = c1.G
}
if c1.B < p.Min.B {
p.Min.B = c1.B
}
p.Pix[i+0] = c1.R
p.Pix[i+1] = c1.G
p.Pix[i+2] = c1.B
p.Pix[i+3] = c1.A
}
// SetPixelError sets the error of the pixel at (x, y)
func (p *ErrorImage) SetPixelError(x, y int, c PixelError) {
if !(image.Point{x, y}.In(p.Rect)) {
return
}
if c.R > p.Max.R {
p.Max.R = c.R
}
if c.G > p.Max.G {
p.Max.G = c.G
}
if c.B > p.Max.B {
p.Max.B = c.B
}
if c.R < p.Min.R {
p.Min.R = c.R
}
if c.G < p.Min.G {
p.Min.G = c.G
}
if c.B < p.Min.B {
p.Min.B = c.B
}
i := p.PixOffset(x, y)
p.Pix[i+0] = c.R
p.Pix[i+1] = c.G
p.Pix[i+2] = c.B
p.Pix[i+3] = c.A
}
// NewErrorImage returns a new ErrorImage image with the given width and height
func NewErrorImage(r image.Rectangle) *ErrorImage {
w, h := r.Dx(), r.Dy()
buf := make([]float32, 4*w*h)
return &ErrorImage{buf, 4 * w, r, PixelError{}, PixelError{}}
}