-
-
Notifications
You must be signed in to change notification settings - Fork 15
/
draw.go
156 lines (142 loc) · 5.23 KB
/
draw.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
package dither
// This file contains code that implements some image/draw interfaces, namely
// draw.Drawer and draw.Quantizer.
import (
"image"
"image/color"
"image/draw"
)
// subImager is a draw.Image that also implements SubImage. All stdlib image types
// that are already draw.Image implement this.
type subImager interface {
draw.Image
SubImage(r image.Rectangle) image.Image
}
func sameColor(c1 color.Color, c2 color.Color) bool {
r1, g1, b1, a1 := c1.RGBA()
r2, g2, b2, a2 := c2.RGBA()
return r1 == r2 && g1 == g2 && b1 == b2 && a1 == a2
}
// subset returns true if p1 is a subset of p2, regardless of the order
// of elements.
func subset(p1 []color.Color, p2 []color.Color) bool {
for i := range p1 {
for j := range p2 {
if sameColor(p1[i], p2[j]) {
// Move on to the next color of the provided palette
break
}
if j == len(p2)-1 && !sameColor(p1[i], p2[j]) {
// The last color of the second palette, and no match with
// the first palette's color has been found.
// This means that p1 is not a subset of p2
return false
}
}
}
return true
}
// Draw implements draw.Drawer. This means you can use a Ditherer
// in many places, such as for encoding GIFs.
//
// Draw ignores whether dst has a palette or not, and just uses the internal Ditherer
// palette. If the dst image passed has a palette (i.e. is of the type *image.Paletted),
// and the palette is the not the same as the Ditherer's palette, it will panic.
func (d *Ditherer) Draw(dst draw.Image, r image.Rectangle, src image.Image, sp image.Point) {
if d.invalid() {
panic("dither: invalid Ditherer")
}
dst2 := dst
paletted := false
if p, ok := dst.(*image.Paletted); ok {
if !samePalette(d.palette, p.Palette) {
panic("dither: Draw: dst was an *image.Paletted that doesn't have the same palette")
}
// src needs to copied onto dst, and then dst is dithered
// But dst is paletted and so the copy will change colors
// So instead an RGBA copy of dst is made, and then values are copied back
// into the paletted image after dithering, at the bottom of the function.
dst2 = copyOfImage(dst)
paletted = true
}
// No longer use dst, only dst2
dst3, ok := dst2.(subImager)
if !ok {
panic("dither: Draw: dst Image passed does not have SubImage method")
}
// No longer use dst2, only dst3 - they are the same object but it's easier
// to stick to one
// Like Go stdlib does with their Drawer:
// https://github.com/golang/go/blob/go1.15.7/src/image/draw/draw.go#L62
//
// This is done here, even though draw.Draw will take care of it. That's
// because the rectangle I have needs to be clipped because it's used later
// to only dither the correct area.
clip(dst3, &r, src, &sp, nil, nil)
if r.Empty() {
return
}
// Copy src onto dst, using the provided boundaries (see draw.Drawer for more)
draw.Draw(dst3, r, src, sp, draw.Src)
// Then dither only the newly-copied area
d.Dither(dst3.SubImage(r).(draw.Image))
if paletted {
// The dithered values in the RGBA image need to copied back into the
// original paletted image. See above.
copyImage(dst, dst2)
}
}
// clip clips r against each image's bounds (after translating into the
// destination image's coordinate space) and shifts the points sp and mp by
// the same amount as the change in r.Min.
//
// Copied from Go stdlib, see
// https://github.com/golang/go/blob/go1.15.7/src/image/draw/draw.go#L73
func clip(dst draw.Image, r *image.Rectangle, src image.Image, sp *image.Point, mask image.Image, mp *image.Point) {
orig := r.Min
*r = r.Intersect(dst.Bounds())
*r = r.Intersect(src.Bounds().Add(orig.Sub(*sp)))
if mask != nil {
*r = r.Intersect(mask.Bounds().Add(orig.Sub(*mp)))
}
dx := r.Min.X - orig.X
dy := r.Min.Y - orig.Y
if dx == 0 && dy == 0 {
return
}
sp.X += dx
sp.Y += dy
if mp != nil {
mp.X += dx
mp.Y += dy
}
}
// Quantize implements draw.Quantizer. It ignores the provided image
// and just returns the Ditherer's palette each time. This is useful for places that
// only allow you to set the palette through a draw.Quantizer, like the image/gif
// package.
//
// This function will panic if the Ditherer's palette has more colors than the
// caller wants, which the caller indicates by cap(p).
//
// It will also panic if there's already colors in the color.Palette provided
// to the func and not all of those colors are included in the Ditherer's palette.
// This is because the caller is indicating that certain colors must be in the
// palette, but the user who created the Ditherer does not want those colors.
func (d *Ditherer) Quantize(p color.Palette, m image.Image) color.Palette {
if cap(p) < len(d.palette) {
// The Ditherer palette has more colors than allowed
panic("dither: Quantize: Ditherer palette has too many colors for this Quantize call")
}
if len(p) > len(d.palette) {
// There's already colors in the palette, more than the Ditherer's
// Note this assumes there aren't duplicate colors in the palette
panic("dither: Quantize: provided palette has colors the Ditherer palette doesn't")
}
if len(p) > 0 && !subset(p, d.palette) {
// There's already colors in the palette, but they aren't all included
// in the Ditherer's palette
panic("dither: Quantize: provided palette has colors the Ditherer palette doesn't")
}
return d.palette
}