github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/image/draw/scale.go (about) 1 // Copyright 2015 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 //go:generate go run gen.go 6 7 package draw 8 9 import ( 10 "image" 11 "image/color" 12 "math" 13 "sync" 14 15 "golang.org/x/image/math/f64" 16 ) 17 18 // Copy copies the part of the source image defined by src and sr and writes 19 // the result of a Porter-Duff composition to the part of the destination image 20 // defined by dst and the translation of sr so that sr.Min translates to dp. 21 func Copy(dst Image, dp image.Point, src image.Image, sr image.Rectangle, op Op, opts *Options) { 22 var o Options 23 if opts != nil { 24 o = *opts 25 } 26 dr := sr.Add(dp.Sub(sr.Min)) 27 if o.DstMask == nil { 28 DrawMask(dst, dr, src, sr.Min, o.SrcMask, o.SrcMaskP.Add(sr.Min), op) 29 } else { 30 NearestNeighbor.Scale(dst, dr, src, sr, op, opts) 31 } 32 } 33 34 // Scaler scales the part of the source image defined by src and sr and writes 35 // the result of a Porter-Duff composition to the part of the destination image 36 // defined by dst and dr. 37 // 38 // A Scaler is safe to use concurrently. 39 type Scaler interface { 40 Scale(dst Image, dr image.Rectangle, src image.Image, sr image.Rectangle, op Op, opts *Options) 41 } 42 43 // Transformer transforms the part of the source image defined by src and sr 44 // and writes the result of a Porter-Duff composition to the part of the 45 // destination image defined by dst and the affine transform m applied to sr. 46 // 47 // For example, if m is the matrix 48 // 49 // m00 m01 m02 50 // m10 m11 m12 51 // 52 // then the src-space point (sx, sy) maps to the dst-space point 53 // (m00*sx + m01*sy + m02, m10*sx + m11*sy + m12). 54 // 55 // A Transformer is safe to use concurrently. 56 type Transformer interface { 57 Transform(dst Image, m f64.Aff3, src image.Image, sr image.Rectangle, op Op, opts *Options) 58 } 59 60 // Options are optional parameters to Copy, Scale and Transform. 61 // 62 // A nil *Options means to use the default (zero) values of each field. 63 type Options struct { 64 // Masks limit what parts of the dst image are drawn to and what parts of 65 // the src image are drawn from. 66 // 67 // A dst or src mask image having a zero alpha (transparent) pixel value in 68 // the respective coordinate space means that that dst pixel is entirely 69 // unaffected or that src pixel is considered transparent black. A full 70 // alpha (opaque) value means that the dst pixel is maximally affected or 71 // the src pixel contributes maximally. The default values, nil, are 72 // equivalent to fully opaque, infinitely large mask images. 73 // 74 // The DstMask is otherwise known as a clip mask, and its pixels map 1:1 to 75 // the dst image's pixels. DstMaskP in DstMask space corresponds to 76 // image.Point{X:0, Y:0} in dst space. For example, when limiting 77 // repainting to a 'dirty rectangle', use that image.Rectangle and a zero 78 // image.Point as the DstMask and DstMaskP. 79 // 80 // The SrcMask's pixels map 1:1 to the src image's pixels. SrcMaskP in 81 // SrcMask space corresponds to image.Point{X:0, Y:0} in src space. For 82 // example, when drawing font glyphs in a uniform color, use an 83 // *image.Uniform as the src, and use the glyph atlas image and the 84 // per-glyph offset as SrcMask and SrcMaskP: 85 // Copy(dst, dp, image.NewUniform(color), image.Rect(0, 0, glyphWidth, glyphHeight), &Options{ 86 // SrcMask: glyphAtlas, 87 // SrcMaskP: glyphOffset, 88 // }) 89 DstMask image.Image 90 DstMaskP image.Point 91 SrcMask image.Image 92 SrcMaskP image.Point 93 94 // TODO: a smooth vs sharp edges option, for arbitrary rotations? 95 } 96 97 // Interpolator is an interpolation algorithm, when dst and src pixels don't 98 // have a 1:1 correspondence. 99 // 100 // Of the interpolators provided by this package: 101 // - NearestNeighbor is fast but usually looks worst. 102 // - CatmullRom is slow but usually looks best. 103 // - ApproxBiLinear has reasonable speed and quality. 104 // 105 // The time taken depends on the size of dr. For kernel interpolators, the 106 // speed also depends on the size of sr, and so are often slower than 107 // non-kernel interpolators, especially when scaling down. 108 type Interpolator interface { 109 Scaler 110 Transformer 111 } 112 113 // Kernel is an interpolator that blends source pixels weighted by a symmetric 114 // kernel function. 115 type Kernel struct { 116 // Support is the kernel support and must be >= 0. At(t) is assumed to be 117 // zero when t >= Support. 118 Support float64 119 // At is the kernel function. It will only be called with t in the 120 // range [0, Support). 121 At func(t float64) float64 122 } 123 124 // Scale implements the Scaler interface. 125 func (q *Kernel) Scale(dst Image, dr image.Rectangle, src image.Image, sr image.Rectangle, op Op, opts *Options) { 126 q.newScaler(dr.Dx(), dr.Dy(), sr.Dx(), sr.Dy(), false).Scale(dst, dr, src, sr, op, opts) 127 } 128 129 // NewScaler returns a Scaler that is optimized for scaling multiple times with 130 // the same fixed destination and source width and height. 131 func (q *Kernel) NewScaler(dw, dh, sw, sh int) Scaler { 132 return q.newScaler(dw, dh, sw, sh, true) 133 } 134 135 func (q *Kernel) newScaler(dw, dh, sw, sh int, usePool bool) Scaler { 136 z := &kernelScaler{ 137 kernel: q, 138 dw: int32(dw), 139 dh: int32(dh), 140 sw: int32(sw), 141 sh: int32(sh), 142 horizontal: newDistrib(q, int32(dw), int32(sw)), 143 vertical: newDistrib(q, int32(dh), int32(sh)), 144 } 145 if usePool { 146 z.pool.New = func() interface{} { 147 tmp := z.makeTmpBuf() 148 return &tmp 149 } 150 } 151 return z 152 } 153 154 var ( 155 // NearestNeighbor is the nearest neighbor interpolator. It is very fast, 156 // but usually gives very low quality results. When scaling up, the result 157 // will look 'blocky'. 158 NearestNeighbor = Interpolator(nnInterpolator{}) 159 160 // ApproxBiLinear is a mixture of the nearest neighbor and bi-linear 161 // interpolators. It is fast, but usually gives medium quality results. 162 // 163 // It implements bi-linear interpolation when upscaling and a bi-linear 164 // blend of the 4 nearest neighbor pixels when downscaling. This yields 165 // nicer quality than nearest neighbor interpolation when upscaling, but 166 // the time taken is independent of the number of source pixels, unlike the 167 // bi-linear interpolator. When downscaling a large image, the performance 168 // difference can be significant. 169 ApproxBiLinear = Interpolator(ablInterpolator{}) 170 171 // BiLinear is the tent kernel. It is slow, but usually gives high quality 172 // results. 173 BiLinear = &Kernel{1, func(t float64) float64 { 174 return 1 - t 175 }} 176 177 // CatmullRom is the Catmull-Rom kernel. It is very slow, but usually gives 178 // very high quality results. 179 // 180 // It is an instance of the more general cubic BC-spline kernel with parameters 181 // B=0 and C=0.5. See Mitchell and Netravali, "Reconstruction Filters in 182 // Computer Graphics", Computer Graphics, Vol. 22, No. 4, pp. 221-228. 183 CatmullRom = &Kernel{2, func(t float64) float64 { 184 if t < 1 { 185 return (1.5*t-2.5)*t*t + 1 186 } 187 return ((-0.5*t+2.5)*t-4)*t + 2 188 }} 189 190 // TODO: a Kaiser-Bessel kernel? 191 ) 192 193 type nnInterpolator struct{} 194 195 type ablInterpolator struct{} 196 197 type kernelScaler struct { 198 kernel *Kernel 199 dw, dh, sw, sh int32 200 horizontal, vertical distrib 201 pool sync.Pool 202 } 203 204 func (z *kernelScaler) makeTmpBuf() [][4]float64 { 205 return make([][4]float64, z.dw*z.sh) 206 } 207 208 // source is a range of contribs, their inverse total weight, and that ITW 209 // divided by 0xffff. 210 type source struct { 211 i, j int32 212 invTotalWeight float64 213 invTotalWeightFFFF float64 214 } 215 216 // contrib is the weight of a column or row. 217 type contrib struct { 218 coord int32 219 weight float64 220 } 221 222 // distrib measures how source pixels are distributed over destination pixels. 223 type distrib struct { 224 // sources are what contribs each column or row in the source image owns, 225 // and the total weight of those contribs. 226 sources []source 227 // contribs are the contributions indexed by sources[s].i and sources[s].j. 228 contribs []contrib 229 } 230 231 // newDistrib returns a distrib that distributes sw source columns (or rows) 232 // over dw destination columns (or rows). 233 func newDistrib(q *Kernel, dw, sw int32) distrib { 234 scale := float64(sw) / float64(dw) 235 halfWidth, kernelArgScale := q.Support, 1.0 236 // When shrinking, broaden the effective kernel support so that we still 237 // visit every source pixel. 238 if scale > 1 { 239 halfWidth *= scale 240 kernelArgScale = 1 / scale 241 } 242 243 // Make the sources slice, one source for each column or row, and temporarily 244 // appropriate its elements' fields so that invTotalWeight is the scaled 245 // coordinate of the source column or row, and i and j are the lower and 246 // upper bounds of the range of destination columns or rows affected by the 247 // source column or row. 248 n, sources := int32(0), make([]source, dw) 249 for x := range sources { 250 center := (float64(x)+0.5)*scale - 0.5 251 i := int32(math.Floor(center - halfWidth)) 252 if i < 0 { 253 i = 0 254 } 255 j := int32(math.Ceil(center + halfWidth)) 256 if j > sw { 257 j = sw 258 if j < i { 259 j = i 260 } 261 } 262 sources[x] = source{i: i, j: j, invTotalWeight: center} 263 n += j - i 264 } 265 266 contribs := make([]contrib, 0, n) 267 for k, b := range sources { 268 totalWeight := 0.0 269 l := int32(len(contribs)) 270 for coord := b.i; coord < b.j; coord++ { 271 t := abs((b.invTotalWeight - float64(coord)) * kernelArgScale) 272 if t >= q.Support { 273 continue 274 } 275 weight := q.At(t) 276 if weight == 0 { 277 continue 278 } 279 totalWeight += weight 280 contribs = append(contribs, contrib{coord, weight}) 281 } 282 totalWeight = 1 / totalWeight 283 sources[k] = source{ 284 i: l, 285 j: int32(len(contribs)), 286 invTotalWeight: totalWeight, 287 invTotalWeightFFFF: totalWeight / 0xffff, 288 } 289 } 290 291 return distrib{sources, contribs} 292 } 293 294 // abs is like math.Abs, but it doesn't care about negative zero, infinities or 295 // NaNs. 296 func abs(f float64) float64 { 297 if f < 0 { 298 f = -f 299 } 300 return f 301 } 302 303 // ftou converts the range [0.0, 1.0] to [0, 0xffff]. 304 func ftou(f float64) uint16 { 305 i := int32(0xffff*f + 0.5) 306 if i > 0xffff { 307 return 0xffff 308 } 309 if i > 0 { 310 return uint16(i) 311 } 312 return 0 313 } 314 315 // fffftou converts the range [0.0, 65535.0] to [0, 0xffff]. 316 func fffftou(f float64) uint16 { 317 i := int32(f + 0.5) 318 if i > 0xffff { 319 return 0xffff 320 } 321 if i > 0 { 322 return uint16(i) 323 } 324 return 0 325 } 326 327 // invert returns the inverse of m. 328 // 329 // TODO: move this into the f64 package, once we work out the convention for 330 // matrix methods in that package: do they modify the receiver, take a dst 331 // pointer argument, or return a new value? 332 func invert(m *f64.Aff3) f64.Aff3 { 333 m00 := +m[3*1+1] 334 m01 := -m[3*0+1] 335 m02 := +m[3*1+2]*m[3*0+1] - m[3*1+1]*m[3*0+2] 336 m10 := -m[3*1+0] 337 m11 := +m[3*0+0] 338 m12 := +m[3*1+0]*m[3*0+2] - m[3*1+2]*m[3*0+0] 339 340 det := m00*m11 - m10*m01 341 342 return f64.Aff3{ 343 m00 / det, 344 m01 / det, 345 m02 / det, 346 m10 / det, 347 m11 / det, 348 m12 / det, 349 } 350 } 351 352 func matMul(p, q *f64.Aff3) f64.Aff3 { 353 return f64.Aff3{ 354 p[3*0+0]*q[3*0+0] + p[3*0+1]*q[3*1+0], 355 p[3*0+0]*q[3*0+1] + p[3*0+1]*q[3*1+1], 356 p[3*0+0]*q[3*0+2] + p[3*0+1]*q[3*1+2] + p[3*0+2], 357 p[3*1+0]*q[3*0+0] + p[3*1+1]*q[3*1+0], 358 p[3*1+0]*q[3*0+1] + p[3*1+1]*q[3*1+1], 359 p[3*1+0]*q[3*0+2] + p[3*1+1]*q[3*1+2] + p[3*1+2], 360 } 361 } 362 363 // transformRect returns a rectangle dr that contains sr transformed by s2d. 364 func transformRect(s2d *f64.Aff3, sr *image.Rectangle) (dr image.Rectangle) { 365 ps := [...]image.Point{ 366 {sr.Min.X, sr.Min.Y}, 367 {sr.Max.X, sr.Min.Y}, 368 {sr.Min.X, sr.Max.Y}, 369 {sr.Max.X, sr.Max.Y}, 370 } 371 for i, p := range ps { 372 sxf := float64(p.X) 373 syf := float64(p.Y) 374 dx := int(math.Floor(s2d[0]*sxf + s2d[1]*syf + s2d[2])) 375 dy := int(math.Floor(s2d[3]*sxf + s2d[4]*syf + s2d[5])) 376 377 // The +1 adjustments below are because an image.Rectangle is inclusive 378 // on the low end but exclusive on the high end. 379 380 if i == 0 { 381 dr = image.Rectangle{ 382 Min: image.Point{dx + 0, dy + 0}, 383 Max: image.Point{dx + 1, dy + 1}, 384 } 385 continue 386 } 387 388 if dr.Min.X > dx { 389 dr.Min.X = dx 390 } 391 dx++ 392 if dr.Max.X < dx { 393 dr.Max.X = dx 394 } 395 396 if dr.Min.Y > dy { 397 dr.Min.Y = dy 398 } 399 dy++ 400 if dr.Max.Y < dy { 401 dr.Max.Y = dy 402 } 403 } 404 return dr 405 } 406 407 func clipAffectedDestRect(adr image.Rectangle, dstMask image.Image, dstMaskP image.Point) (image.Rectangle, image.Image) { 408 if dstMask == nil { 409 return adr, nil 410 } 411 // TODO: enable this fast path once Go 1.5 is released, where an 412 // image.Rectangle implements image.Image. 413 // if r, ok := dstMask.(image.Rectangle); ok { 414 // return adr.Intersect(r.Sub(dstMaskP)), nil 415 // } 416 // TODO: clip to dstMask.Bounds() if the color model implies that out-of-bounds means 0 alpha? 417 return adr, dstMask 418 } 419 420 func transform_Uniform(dst Image, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.Uniform, sr image.Rectangle, bias image.Point, op Op) { 421 switch op { 422 case Over: 423 switch dst := dst.(type) { 424 case *image.RGBA: 425 pr, pg, pb, pa := src.C.RGBA() 426 pa1 := (0xffff - pa) * 0x101 427 428 for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { 429 dyf := float64(dr.Min.Y+int(dy)) + 0.5 430 d := dst.PixOffset(dr.Min.X+adr.Min.X, dr.Min.Y+int(dy)) 431 for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { 432 dxf := float64(dr.Min.X+int(dx)) + 0.5 433 sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X 434 sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y 435 if !(image.Point{sx0, sy0}).In(sr) { 436 continue 437 } 438 dst.Pix[d+0] = uint8((uint32(dst.Pix[d+0])*pa1/0xffff + pr) >> 8) 439 dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*pa1/0xffff + pg) >> 8) 440 dst.Pix[d+2] = uint8((uint32(dst.Pix[d+2])*pa1/0xffff + pb) >> 8) 441 dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*pa1/0xffff + pa) >> 8) 442 } 443 } 444 445 default: 446 pr, pg, pb, pa := src.C.RGBA() 447 pa1 := 0xffff - pa 448 dstColorRGBA64 := &color.RGBA64{} 449 dstColor := color.Color(dstColorRGBA64) 450 451 for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { 452 dyf := float64(dr.Min.Y+int(dy)) + 0.5 453 for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { 454 dxf := float64(dr.Min.X+int(dx)) + 0.5 455 sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X 456 sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y 457 if !(image.Point{sx0, sy0}).In(sr) { 458 continue 459 } 460 qr, qg, qb, qa := dst.At(dr.Min.X+int(dx), dr.Min.Y+int(dy)).RGBA() 461 dstColorRGBA64.R = uint16(qr*pa1/0xffff + pr) 462 dstColorRGBA64.G = uint16(qg*pa1/0xffff + pg) 463 dstColorRGBA64.B = uint16(qb*pa1/0xffff + pb) 464 dstColorRGBA64.A = uint16(qa*pa1/0xffff + pa) 465 dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(dy), dstColor) 466 } 467 } 468 } 469 470 case Src: 471 switch dst := dst.(type) { 472 case *image.RGBA: 473 pr, pg, pb, pa := src.C.RGBA() 474 pr8 := uint8(pr >> 8) 475 pg8 := uint8(pg >> 8) 476 pb8 := uint8(pb >> 8) 477 pa8 := uint8(pa >> 8) 478 479 for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { 480 dyf := float64(dr.Min.Y+int(dy)) + 0.5 481 d := dst.PixOffset(dr.Min.X+adr.Min.X, dr.Min.Y+int(dy)) 482 for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 { 483 dxf := float64(dr.Min.X+int(dx)) + 0.5 484 sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X 485 sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y 486 if !(image.Point{sx0, sy0}).In(sr) { 487 continue 488 } 489 dst.Pix[d+0] = pr8 490 dst.Pix[d+1] = pg8 491 dst.Pix[d+2] = pb8 492 dst.Pix[d+3] = pa8 493 } 494 } 495 496 default: 497 pr, pg, pb, pa := src.C.RGBA() 498 dstColorRGBA64 := &color.RGBA64{ 499 uint16(pr), 500 uint16(pg), 501 uint16(pb), 502 uint16(pa), 503 } 504 dstColor := color.Color(dstColorRGBA64) 505 506 for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { 507 dyf := float64(dr.Min.Y+int(dy)) + 0.5 508 for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { 509 dxf := float64(dr.Min.X+int(dx)) + 0.5 510 sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X 511 sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y 512 if !(image.Point{sx0, sy0}).In(sr) { 513 continue 514 } 515 dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(dy), dstColor) 516 } 517 } 518 } 519 } 520 } 521 522 func opaque(m image.Image) bool { 523 o, ok := m.(interface { 524 Opaque() bool 525 }) 526 return ok && o.Opaque() 527 }