OLD | NEW |
(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 |
| 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 } |
OLD | NEW |