Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(381)

Side by Side Diff: draw/scale.go

Issue 101670045: code review 101670045: go.image/draw: new package, a superset of the standard ...
Patch Set: diff -r e492ffa4cad4 https://code.google.com/p/go.image Created 9 years, 8 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2014 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package draw
6
7 import (
8 "image"
9 "image/color"
10 "math"
11 )
12
13 // Quality is a desired resampling quality, ranging from the fastest
14 // computation (lowest quality) to the slowest computation (highest quality).
15 type Quality int
16
17 const (
18 FastestQuality Quality = -1
19 DefaultQuality Quality = 0
20 NicestQuality Quality = +1
r 2014/07/08 19:44:54 not sure i like these names. they don't convey rea
21 )
22
23 // Scale scales the part of the source image defined by src and sr and writes
24 // to the part of the destination image defined by dst and dr.
25 func Scale(dst Image, dr image.Rectangle, src image.Image, sr image.Rectangle, q Quality) {
26 NewScaler(dr.Size(), sr.Size(), q).Scale(dst, dr.Min, src, sr.Min)
27 }
28
29 // Scaler scales part of a source image, starting from sp, and writes to a
30 // destination image, starting from dp. The destination and source width and
31 // heights are pre-determined, as part of the Scaler.
32 //
33 // A Scaler is safe to use concurrently.
34 type Scaler interface {
35 Scale(dst Image, dp image.Point, src image.Image, sp image.Point)
36 }
37
38 // TODO: should Scale and NewScaler also take an Op argument?
39
40 // NewScaler returns a Scaler that scales a source image of the given size to a
41 // destination image of the given size.
42 func NewScaler(dstSize, srcSize image.Point, q Quality) Scaler {
43 dw := int32(dstSize.X)
44 dh := int32(dstSize.Y)
45 sw := int32(srcSize.X)
46 sh := int32(srcSize.Y)
47 if dw <= 0 || dh <= 0 || sw <= 0 || sh <= 0 {
48 return nopScaler{}
49 }
50 switch q {
51 case FastestQuality:
52 return &fastestScaler{
53 dw: dw,
54 dh: dh,
55 sw: sw,
56 sh: sh,
57 }
58 case NicestQuality:
59 return &nicestScaler{
60 dw: dw,
61 dh: dh,
62 sw: sw,
63 sh: sh,
64 horizontal: newDistrib(dw, sw),
65 vertical: newDistrib(dh, sh),
66 }
67 }
68 return &defaultScaler{
69 dw: dw,
70 dh: dh,
71 sw: sw,
72 sh: sh,
73 }
74 }
75
76 type nopScaler struct{}
77
78 func (nopScaler) Scale(dst Image, dp image.Point, src image.Image, sp image.Poin t) {}
79
80 // fastestScaler implements a nearest-neighbor image scaler.
81 type fastestScaler struct {
82 dw, dh, sw, sh int32
83 }
84
85 func (z *fastestScaler) Scale(dst Image, dp image.Point, src image.Image, sp ima ge.Point) {
86 dstColorRGBA64 := &color.RGBA64{}
87 dstColor := color.Color(dstColorRGBA64)
88 for dy := int32(0); dy < z.dh; dy++ {
89 sy := (2*uint64(dy) + 1) * uint64(z.sh) / (2 * uint64(z.dh))
90 for dx := int32(0); dx < z.dw; dx++ {
91 sx := (2*uint64(dx) + 1) * uint64(z.sw) / (2 * uint64(z. dw))
92 pr, pg, pb, pa := src.At(sp.X+int(sx), sp.Y+int(sy)).RGB A()
93 dstColorRGBA64.R = uint16(pr)
94 dstColorRGBA64.G = uint16(pg)
95 dstColorRGBA64.B = uint16(pb)
96 dstColorRGBA64.A = uint16(pa)
97 dst.Set(dp.X+int(dx), dp.Y+int(dy), dstColor)
98 }
99 }
100 }
101
102 // defaultScaler implements a bi-linear image scaler.
103 type defaultScaler struct {
104 dw, dh, sw, sh int32
105 }
106
107 func (z *defaultScaler) Scale(dst Image, dp image.Point, src image.Image, sp ima ge.Point) {
108 yscale := float64(z.sh) / float64(z.dh)
109 xscale := float64(z.sw) / float64(z.dw)
110 dstColorRGBA64 := &color.RGBA64{}
111 dstColor := color.Color(dstColorRGBA64)
112 for dy := int32(0); dy < z.dh; dy++ {
113 sy := (float64(dy)+0.5)*yscale - 0.5
114 sy0 := int32(sy)
115 yFrac0 := sy - float64(sy0)
116 yFrac1 := 1 - yFrac0
117 sy1 := sy0 + 1
118 if sy < 0 {
119 sy0, sy1 = 0, 0
120 yFrac0, yFrac1 = 0, 1
121 } else if sy1 >= z.sh {
122 sy1 = sy0
123 yFrac0, yFrac1 = 1, 0
124 }
125 for dx := int32(0); dx < z.dw; dx++ {
126 sx := (float64(dx)+0.5)*xscale - 0.5
127 sx0 := int32(sx)
128 xFrac0 := sx - float64(sx0)
129 xFrac1 := 1 - xFrac0
130 sx1 := sx0 + 1
131 if sx < 0 {
132 sx0, sx1 = 0, 0
133 xFrac0, xFrac1 = 0, 1
134 } else if sx1 >= z.sw {
135 sx1 = sx0
136 xFrac0, xFrac1 = 1, 0
137 }
138 s00ru, s00gu, s00bu, s00au := src.At(sp.X+int(sx0), sp.Y +int(sy0)).RGBA()
139 s00r := float64(s00ru)
140 s00g := float64(s00gu)
141 s00b := float64(s00bu)
142 s00a := float64(s00au)
143 s10ru, s10gu, s10bu, s10au := src.At(sp.X+int(sx1), sp.Y +int(sy0)).RGBA()
144 s10r := float64(s10ru)
145 s10g := float64(s10gu)
146 s10b := float64(s10bu)
147 s10a := float64(s10au)
148 s10r = xFrac1*s00r + xFrac0*s10r
149 s10g = xFrac1*s00g + xFrac0*s10g
150 s10b = xFrac1*s00b + xFrac0*s10b
151 s10a = xFrac1*s00a + xFrac0*s10a
152 s01ru, s01gu, s01bu, s01au := src.At(sp.X+int(sx0), sp.Y +int(sy1)).RGBA()
153 s01r := float64(s01ru)
154 s01g := float64(s01gu)
155 s01b := float64(s01bu)
156 s01a := float64(s01au)
157 s11ru, s11gu, s11bu, s11au := src.At(sp.X+int(sx1), sp.Y +int(sy1)).RGBA()
158 s11r := float64(s11ru)
159 s11g := float64(s11gu)
160 s11b := float64(s11bu)
161 s11a := float64(s11au)
162 s11r = xFrac1*s01r + xFrac0*s11r
163 s11g = xFrac1*s01g + xFrac0*s11g
164 s11b = xFrac1*s01b + xFrac0*s11b
165 s11a = xFrac1*s01a + xFrac0*s11a
166 s11r = yFrac1*s10r + yFrac0*s11r
167 s11g = yFrac1*s10g + yFrac0*s11g
168 s11b = yFrac1*s10b + yFrac0*s11b
169 s11a = yFrac1*s10a + yFrac0*s11a
170 dstColorRGBA64.R = uint16(s11r)
171 dstColorRGBA64.G = uint16(s11g)
172 dstColorRGBA64.B = uint16(s11b)
173 dstColorRGBA64.A = uint16(s11a)
174 dst.Set(dp.X+int(dx), dp.Y+int(dy), dstColor)
175 }
176 }
177 }
178
179 // nicestScaler implements a general kernel-based image scaler.
180 type nicestScaler struct {
181 dw, dh, sw, sh int32
182 horizontal, vertical distrib
183 }
184
185 func (z *nicestScaler) Scale(dst Image, dp image.Point, src image.Image, sp imag e.Point) {
186 // TODO: is it worth having a sync.Pool for this temporary buffer?
187 tmp := make([][4]float64, z.dw*z.sh)
188 z.scaleX(tmp, src, sp)
189 z.scaleY(dst, dp, tmp)
190 }
191
192 // source is a range of contribs, their inverse total weight, and that ITW
193 // divided by 0xffff.
194 type source struct {
195 i, j int32
196 invTotalWeight float64
197 invTotalWeightFFFF float64
198 }
199
200 // contrib is the weight of a column or row.
201 type contrib struct {
202 coord int32
203 weight float64
204 }
205
206 // distrib measures how source pixels are distributed over destination pixels.
207 type distrib struct {
208 // sources are what contribs each column or row in the source image owns ,
209 // and the total weight of those contribs.
210 sources []source
211 // contribs are the contributions indexed by sources[s].i and sources[s] .j.
212 contribs []contrib
213 }
214
215 // newDistrib returns a distrib that distributes sw source columns (or rows)
216 // over dw destination columns (or rows).
217 func newDistrib(dw, sw int32) distrib {
218 scale := float64(sw) / float64(dw)
219 halfWidth, kernelArgScale := float64(catmullRomSupport), 1.0
220 if scale > 1 {
221 halfWidth *= scale
222 kernelArgScale = 1 / scale
223 }
224
225 // Make the sources slice, one source for each column or row, and tempor arily
226 // appropriate its elements' fields so that invTotalWeight is the scaled
227 // co-ordinate of the source column or row, and i and j are the lower an d
228 // upper bounds of the range of destination columns or rows affected by the
229 // source column or row.
230 n, sources := int32(0), make([]source, dw)
231 for x := range sources {
232 center := (float64(x)+0.5)*scale - 0.5
233 i := int32(math.Floor(center - halfWidth))
234 if i < 0 {
235 i = 0
236 }
237 j := int32(math.Ceil(center + halfWidth))
238 if j >= sw {
239 j = sw - 1
240 if j < i {
241 j = i
242 }
243 }
244 sources[x] = source{i: i, j: j, invTotalWeight: center}
245 n += j - i + 1
246 }
247
248 contribs := make([]contrib, 0, n)
249 for k, b := range sources {
250 totalWeight := 0.0
251 l := int32(len(contribs))
252 for coord := b.i; coord <= b.j; coord++ {
253 weight := catmullRom((b.invTotalWeight - float64(coord)) * kernelArgScale)
254 if weight == 0 {
255 continue
256 }
257 totalWeight += weight
258 contribs = append(contribs, contrib{coord, weight})
259 }
260 totalWeight = 1 / totalWeight
261 sources[k] = source{
262 i: l,
263 j: int32(len(contribs)),
264 invTotalWeight: totalWeight,
265 invTotalWeightFFFF: totalWeight / 0xffff,
266 }
267 }
268
269 return distrib{sources, contribs}
270 }
271
272 // scaleX distributes the source image's columns over the temporary image.
273 func (z *nicestScaler) scaleX(tmp [][4]float64, src image.Image, sp image.Point) {
274 t := 0
275 for y := int32(0); y < z.sh; y++ {
276 for _, s := range z.horizontal.sources {
277 var r, g, b, a float64
278 for _, c := range z.horizontal.contribs[s.i:s.j] {
279 rr, gg, bb, aa := src.At(sp.X+int(c.coord), sp.Y +int(y)).RGBA()
280 r += float64(rr) * c.weight
281 g += float64(gg) * c.weight
282 b += float64(bb) * c.weight
283 a += float64(aa) * c.weight
284 }
285 tmp[t] = [4]float64{
286 r * s.invTotalWeightFFFF,
287 g * s.invTotalWeightFFFF,
288 b * s.invTotalWeightFFFF,
289 a * s.invTotalWeightFFFF,
290 }
291 t++
292 }
293 }
294 }
295
296 // scaleY distributes the temporary image's rows over the destination image.
297 func (z *nicestScaler) scaleY(dst Image, dp image.Point, tmp [][4]float64) {
298 dstColorRGBA64 := &color.RGBA64{}
299 dstColor := color.Color(dstColorRGBA64)
300 for x := int32(0); x < z.dw; x++ {
301 for y, s := range z.vertical.sources {
302 var r, g, b, a float64
303 for _, c := range z.vertical.contribs[s.i:s.j] {
304 p := &tmp[c.coord*z.dw+x]
305 r += p[0] * c.weight
306 g += p[1] * c.weight
307 b += p[2] * c.weight
308 a += p[3] * c.weight
309 }
310 dstColorRGBA64.R = ftou(r * s.invTotalWeight)
311 dstColorRGBA64.G = ftou(g * s.invTotalWeight)
312 dstColorRGBA64.B = ftou(b * s.invTotalWeight)
313 dstColorRGBA64.A = ftou(a * s.invTotalWeight)
314 dst.Set(dp.X+int(x), dp.Y+y, dstColor)
315 }
316 }
317 }
318
319 // catmullRom is the Catmull-Rom kernel.
320 //
321 // It is an instance of the more general cubic BC-spline kernel with parameters
322 // B=0 and C=0.5. See Mitchell and Netravali, "Reconstruction Filters in
323 // Computer Graphics", Computer Graphics, Vol. 22, No. 4, pp. 221-228.
324 //
325 // TODO: use a Kaiser-Bessel kernel instead of a Catmull-Rom one?
326 // TODO: would a piece-wise linear interpolation instead of computing the
327 // exact value (whether cubic, sinc-based, etc.) be significantly faster?
328 func catmullRom(t float64) float64 {
329 if t < 0 {
330 t = -t
331 }
332 if t < 1 {
333 return (1.5*t-2.5)*t*t + 1
334 }
335 if t < 2 {
336 return ((-0.5*t+2.5)*t-4)*t + 2
337 }
338 return 0
339 }
340
341 const catmullRomSupport = 2
342
343 func ftou(f float64) uint16 {
344 i := int32(0xffff*f + 0.5)
345 if i > 0xffff {
346 return 0xffff
347 } else if i > 0 {
348 return uint16(i)
349 }
350 return 0
351 }
OLDNEW
« no previous file with comments | « draw/draw.go ('k') | draw/scale_test.go » ('j') | draw/scale_test.go » ('J')

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b