Left: | ||
Right: |
LEFT | RIGHT |
---|---|
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 | |
1 package draw | 5 package draw |
chai2010
2014/07/04 10:50:32
copyright missing
nigeltao
2014/07/07 00:58:40
Done.
| |
2 | 6 |
3 import ( | 7 import ( |
4 "image" | 8 "image" |
5 "image/color" | 9 "image/color" |
6 "math" | 10 "math" |
7 ) | 11 ) |
8 | 12 |
9 // Quality is a desired resampling quality, ranging from the fastest | 13 // Quality is a desired resampling quality, ranging from the fastest |
10 // computation (lowest quality) to the slowest computation (highest quality). | 14 // computation (lowest quality) to the slowest computation (highest quality). |
11 type Quality int | 15 type Quality int |
12 | 16 |
13 const ( | 17 const ( |
14 FastestQuality Quality = -1 | 18 FastestQuality Quality = -1 |
15 DefaultQuality Quality = 0 | 19 DefaultQuality Quality = 0 |
16 NicestQuality Quality = +1 | 20 NicestQuality Quality = +1 |
r
2014/07/08 19:44:54
not sure i like these names. they don't convey rea
| |
17 ) | 21 ) |
18 | 22 |
19 // Scale scales the part of the source image defined by src and sr and writes | 23 // Scale scales the part of the source image defined by src and sr and writes |
20 // to the part of the destination image defined by dst and dr. | 24 // to the part of the destination image defined by dst and dr. |
21 func Scale(dst Image, dr image.Rectangle, src image.Image, sr image.Rectangle, q Quality) { | 25 func Scale(dst Image, dr image.Rectangle, src image.Image, sr image.Rectangle, q Quality) { |
22 NewScaler(dr.Size(), sr.Size(), q).Scale(dst, dr.Min, src, sr.Min) | 26 NewScaler(dr.Size(), sr.Size(), q).Scale(dst, dr.Min, src, sr.Min) |
23 } | 27 } |
24 | 28 |
25 // Scaler scales part of a source image, starting from sp, and writes to a | 29 // Scaler scales part of a source image, starting from sp, and writes to a |
26 // destination image, starting from dp. The destination and source width and | 30 // destination image, starting from dp. The destination and source width and |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
93 dst.Set(dp.X+int(dx), dp.Y+int(dy), dstColor) | 97 dst.Set(dp.X+int(dx), dp.Y+int(dy), dstColor) |
94 } | 98 } |
95 } | 99 } |
96 } | 100 } |
97 | 101 |
98 // defaultScaler implements a bi-linear image scaler. | 102 // defaultScaler implements a bi-linear image scaler. |
99 type defaultScaler struct { | 103 type defaultScaler struct { |
100 dw, dh, sw, sh int32 | 104 dw, dh, sw, sh int32 |
101 } | 105 } |
102 | 106 |
103 func (z *defaultScaler) Scale(dst Image, dp image.Point, src image.Image, sp ima ge.Point) { | 107 func (z *defaultScaler) Scale(dst Image, dp image.Point, src image.Image, sp ima ge.Point) { |
klauspost
2014/07/06 21:05:11
If I read this algorithm correctly, it will be bro
nigeltao
2014/07/07 00:58:40
I don't think you're reading it correctly. This lo
klauspost
2014/07/17 09:55:17
Ok, I am sorry to say that I am reading it correct
| |
104 yscale := float64(z.sh) / float64(z.dh) | 108 yscale := float64(z.sh) / float64(z.dh) |
105 xscale := float64(z.sw) / float64(z.dw) | 109 xscale := float64(z.sw) / float64(z.dw) |
106 dstColorRGBA64 := &color.RGBA64{} | 110 dstColorRGBA64 := &color.RGBA64{} |
107 dstColor := color.Color(dstColorRGBA64) | 111 dstColor := color.Color(dstColorRGBA64) |
108 for dy := int32(0); dy < z.dh; dy++ { | 112 for dy := int32(0); dy < z.dh; dy++ { |
109 sy := (float64(dy)+0.5)*yscale - 0.5 | 113 sy := (float64(dy)+0.5)*yscale - 0.5 |
110 sy0 := int32(sy) | 114 sy0 := int32(sy) |
111 yFrac0 := sy - float64(sy0) | 115 yFrac0 := sy - float64(sy0) |
112 yFrac1 := 1 - yFrac0 | 116 yFrac1 := 1 - yFrac0 |
113 sy1 := sy0 + 1 | 117 sy1 := sy0 + 1 |
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
178 horizontal, vertical distrib | 182 horizontal, vertical distrib |
179 } | 183 } |
180 | 184 |
181 func (z *nicestScaler) Scale(dst Image, dp image.Point, src image.Image, sp imag e.Point) { | 185 func (z *nicestScaler) Scale(dst Image, dp image.Point, src image.Image, sp imag e.Point) { |
182 // TODO: is it worth having a sync.Pool for this temporary buffer? | 186 // TODO: is it worth having a sync.Pool for this temporary buffer? |
183 tmp := make([][4]float64, z.dw*z.sh) | 187 tmp := make([][4]float64, z.dw*z.sh) |
184 z.scaleX(tmp, src, sp) | 188 z.scaleX(tmp, src, sp) |
185 z.scaleY(dst, dp, tmp) | 189 z.scaleY(dst, dp, tmp) |
186 } | 190 } |
187 | 191 |
188 // source is a range of contribs, and their total weight. | 192 // source is a range of contribs, their inverse total weight, and that ITW |
193 // divided by 0xffff. | |
189 type source struct { | 194 type source struct { |
190 » i, j int32 | 195 » i, j int32 |
191 » totalWeight float64 | 196 » invTotalWeight float64 |
197 » invTotalWeightFFFF float64 | |
192 } | 198 } |
193 | 199 |
194 // contrib is the weight of a column or row. | 200 // contrib is the weight of a column or row. |
195 type contrib struct { | 201 type contrib struct { |
196 coord int32 | 202 coord int32 |
197 weight float64 | 203 weight float64 |
198 } | 204 } |
199 | 205 |
200 // distrib measures how source pixels are distributed over destination pixels. | 206 // distrib measures how source pixels are distributed over destination pixels. |
201 type distrib struct { | 207 type distrib struct { |
202 // sources are what contribs each column or row in the source image owns , | 208 // sources are what contribs each column or row in the source image owns , |
203 // and the total weight of those contribs. | 209 // and the total weight of those contribs. |
204 sources []source | 210 sources []source |
205 // contribs are the contributions indexed by sources[s].i and sources[s] .j. | 211 // contribs are the contributions indexed by sources[s].i and sources[s] .j. |
206 contribs []contrib | 212 contribs []contrib |
207 } | 213 } |
208 | 214 |
209 // newDistrib returns a distrib that distributes sw source columns (or rows) | 215 // newDistrib returns a distrib that distributes sw source columns (or rows) |
210 // over dw destination columns (or rows). | 216 // over dw destination columns (or rows). |
211 func newDistrib(dw, sw int32) distrib { | 217 func newDistrib(dw, sw int32) distrib { |
212 scale := float64(sw) / float64(dw) | 218 scale := float64(sw) / float64(dw) |
213 halfWidth, kernelArgScale := float64(catmullRomSupport), 1.0 | 219 halfWidth, kernelArgScale := float64(catmullRomSupport), 1.0 |
214 if scale > 1 { | 220 if scale > 1 { |
215 halfWidth *= scale | 221 halfWidth *= scale |
216 kernelArgScale = 1 / scale | 222 kernelArgScale = 1 / scale |
217 } | 223 } |
218 | 224 |
219 // Make the sources slice, one source for each column or row, and tempor arily | 225 // Make the sources slice, one source for each column or row, and tempor arily |
220 » // appropriate its elements' fields so that totalWeight is the scaled | 226 » // appropriate its elements' fields so that invTotalWeight is the scaled |
221 // co-ordinate of the source column or row, and i and j are the lower an d | 227 // co-ordinate of the source column or row, and i and j are the lower an d |
222 // upper bounds of the range of destination columns or rows affected by the | 228 // upper bounds of the range of destination columns or rows affected by the |
223 // source column or row. | 229 // source column or row. |
224 n, sources := int32(0), make([]source, dw) | 230 n, sources := int32(0), make([]source, dw) |
225 for x := range sources { | 231 for x := range sources { |
226 center := (float64(x)+0.5)*scale - 0.5 | 232 center := (float64(x)+0.5)*scale - 0.5 |
227 i := int32(math.Floor(center - halfWidth)) | 233 i := int32(math.Floor(center - halfWidth)) |
228 if i < 0 { | 234 if i < 0 { |
229 i = 0 | 235 i = 0 |
230 } | 236 } |
231 j := int32(math.Ceil(center + halfWidth)) | 237 j := int32(math.Ceil(center + halfWidth)) |
232 if j >= sw { | 238 if j >= sw { |
233 j = sw - 1 | 239 j = sw - 1 |
234 if j < i { | 240 if j < i { |
235 j = i | 241 j = i |
236 } | 242 } |
237 } | 243 } |
238 » » sources[x] = source{i: i, j: j, totalWeight: center} | 244 » » sources[x] = source{i: i, j: j, invTotalWeight: center} |
239 n += j - i + 1 | 245 n += j - i + 1 |
240 } | 246 } |
241 | 247 |
242 contribs := make([]contrib, 0, n) | 248 contribs := make([]contrib, 0, n) |
243 for k, b := range sources { | 249 for k, b := range sources { |
244 totalWeight := 0.0 | 250 totalWeight := 0.0 |
245 l := int32(len(contribs)) | 251 l := int32(len(contribs)) |
246 for coord := b.i; coord <= b.j; coord++ { | 252 for coord := b.i; coord <= b.j; coord++ { |
247 » » » weight := catmullRom((b.totalWeight - float64(coord)) * kernelArgScale) | 253 » » » weight := catmullRom((b.invTotalWeight - float64(coord)) * kernelArgScale) |
248 if weight == 0 { | 254 if weight == 0 { |
249 continue | 255 continue |
250 } | 256 } |
251 totalWeight += weight | 257 totalWeight += weight |
252 contribs = append(contribs, contrib{coord, weight}) | 258 contribs = append(contribs, contrib{coord, weight}) |
253 } | 259 } |
260 totalWeight = 1 / totalWeight | |
254 sources[k] = source{ | 261 sources[k] = source{ |
255 » » » i: l, | 262 » » » i: l, |
256 » » » j: int32(len(contribs)), | 263 » » » j: int32(len(contribs)), |
257 » » » totalWeight: totalWeight, | 264 » » » invTotalWeight: totalWeight, |
klauspost
2014/07/06 21:05:11
Check if totalWeight is zero - otherwise you risk
nigeltao
2014/07/07 00:58:40
And if totalWeight is zero, what do you think shou
| |
265 » » » invTotalWeightFFFF: totalWeight / 0xffff, | |
258 } | 266 } |
259 } | 267 } |
260 | 268 |
261 return distrib{sources, contribs} | 269 return distrib{sources, contribs} |
262 } | 270 } |
263 | 271 |
264 // scaleX distributes the source image's columns over the temporary image. | 272 // scaleX distributes the source image's columns over the temporary image. |
265 func (z *nicestScaler) scaleX(tmp [][4]float64, src image.Image, sp image.Point) { | 273 func (z *nicestScaler) scaleX(tmp [][4]float64, src image.Image, sp image.Point) { |
266 t := 0 | 274 t := 0 |
267 for y := int32(0); y < z.sh; y++ { | 275 for y := int32(0); y < z.sh; y++ { |
268 for _, s := range z.horizontal.sources { | 276 for _, s := range z.horizontal.sources { |
269 var r, g, b, a float64 | 277 var r, g, b, a float64 |
270 for _, c := range z.horizontal.contribs[s.i:s.j] { | 278 for _, c := range z.horizontal.contribs[s.i:s.j] { |
271 rr, gg, bb, aa := src.At(sp.X+int(c.coord), sp.Y +int(y)).RGBA() | 279 rr, gg, bb, aa := src.At(sp.X+int(c.coord), sp.Y +int(y)).RGBA() |
272 r += float64(rr) * c.weight | 280 r += float64(rr) * c.weight |
273 g += float64(gg) * c.weight | 281 g += float64(gg) * c.weight |
274 b += float64(bb) * c.weight | 282 b += float64(bb) * c.weight |
275 a += float64(aa) * c.weight | 283 a += float64(aa) * c.weight |
276 } | 284 } |
277 » » » tw := s.totalWeight * 0xffff | 285 » » » tmp[t] = [4]float64{ |
klauspost
2014/07/06 21:05:11
Precompute this value and store the inverse. That
nigeltao
2014/07/07 00:58:40
Done.
| |
278 » » » tmp[t] = [4]float64{r / tw, g / tw, b / tw, a / tw} | 286 » » » » r * s.invTotalWeightFFFF, |
287 » » » » g * s.invTotalWeightFFFF, | |
288 » » » » b * s.invTotalWeightFFFF, | |
289 » » » » a * s.invTotalWeightFFFF, | |
290 » » » } | |
279 t++ | 291 t++ |
280 } | 292 } |
281 } | 293 } |
282 } | 294 } |
283 | 295 |
284 // scaleY distributes the temporary image's rows over the destination image. | 296 // scaleY distributes the temporary image's rows over the destination image. |
285 func (z *nicestScaler) scaleY(dst Image, dp image.Point, tmp [][4]float64) { | 297 func (z *nicestScaler) scaleY(dst Image, dp image.Point, tmp [][4]float64) { |
286 dstColorRGBA64 := &color.RGBA64{} | 298 dstColorRGBA64 := &color.RGBA64{} |
287 dstColor := color.Color(dstColorRGBA64) | 299 dstColor := color.Color(dstColorRGBA64) |
288 for x := int32(0); x < z.dw; x++ { | 300 for x := int32(0); x < z.dw; x++ { |
289 for y, s := range z.vertical.sources { | 301 for y, s := range z.vertical.sources { |
290 var r, g, b, a float64 | 302 var r, g, b, a float64 |
291 for _, c := range z.vertical.contribs[s.i:s.j] { | 303 for _, c := range z.vertical.contribs[s.i:s.j] { |
292 p := &tmp[c.coord*z.dw+x] | 304 p := &tmp[c.coord*z.dw+x] |
293 r += p[0] * c.weight | 305 r += p[0] * c.weight |
294 g += p[1] * c.weight | 306 g += p[1] * c.weight |
295 b += p[2] * c.weight | 307 b += p[2] * c.weight |
296 a += p[3] * c.weight | 308 a += p[3] * c.weight |
297 } | 309 } |
298 » » » dstColorRGBA64.R = ftou(r / s.totalWeight) | 310 » » » dstColorRGBA64.R = ftou(r * s.invTotalWeight) |
klauspost
2014/07/06 21:05:11
You should precompute 1.0/s.TotalWeight (and store
nigeltao
2014/07/07 00:58:40
Done.
| |
299 » » » dstColorRGBA64.G = ftou(g / s.totalWeight) | 311 » » » dstColorRGBA64.G = ftou(g * s.invTotalWeight) |
300 » » » dstColorRGBA64.B = ftou(b / s.totalWeight) | 312 » » » dstColorRGBA64.B = ftou(b * s.invTotalWeight) |
301 » » » dstColorRGBA64.A = ftou(a / s.totalWeight) | 313 » » » dstColorRGBA64.A = ftou(a * s.invTotalWeight) |
302 dst.Set(dp.X+int(x), dp.Y+y, dstColor) | 314 dst.Set(dp.X+int(x), dp.Y+y, dstColor) |
303 } | 315 } |
304 } | 316 } |
305 } | 317 } |
306 | 318 |
307 // catmullRom is the Catmull-Rom kernel. | 319 // catmullRom is the Catmull-Rom kernel. |
308 // | 320 // |
309 // It is an instance of the more general cubic BC-spline kernel with parameters | 321 // It is an instance of the more general cubic BC-spline kernel with parameters |
310 // B=0 and C=0.5. See Mitchell and Netravali, "Reconstruction Filters in | 322 // B=0 and C=0.5. See Mitchell and Netravali, "Reconstruction Filters in |
311 // Computer Graphics", Computer Graphics, Vol. 22, No. 4, pp. 221-228. | 323 // Computer Graphics", Computer Graphics, Vol. 22, No. 4, pp. 221-228. |
(...skipping 18 matching lines...) Expand all Loading... | |
330 | 342 |
331 func ftou(f float64) uint16 { | 343 func ftou(f float64) uint16 { |
332 i := int32(0xffff*f + 0.5) | 344 i := int32(0xffff*f + 0.5) |
333 if i > 0xffff { | 345 if i > 0xffff { |
334 return 0xffff | 346 return 0xffff |
335 } else if i > 0 { | 347 } else if i > 0 { |
336 return uint16(i) | 348 return uint16(i) |
337 } | 349 } |
338 return 0 | 350 return 0 |
339 } | 351 } |
LEFT | RIGHT |