git.lucas.co / go_mono
git clone https://git.lucas.co/go_mono.git

draw/scale.go (15.8K)

  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 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 float64((float64(1.5*t)-2.5)*t*t) + 1
186 		}
187 		return float64((float64(float64(float64(-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((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(float64(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 := +float64(m[3*1+2]*m[3*0+1]) - float64(m[3*1+1]*m[3*0+2])
336 	m10 := -m[3*1+0]
337 	m11 := +m[3*0+0]
338 	m12 := +float64(m[3*1+0]*m[3*0+2]) - float64(m[3*1+2]*m[3*0+0])
339 
340 	det := float64(m00*m11) - float64(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 		float64(p[3*0+0]*q[3*0+0]) + float64(p[3*0+1]*q[3*1+0]),
355 		float64(p[3*0+0]*q[3*0+1]) + float64(p[3*0+1]*q[3*1+1]),
356 		float64(p[3*0+0]*q[3*0+2]) + float64(p[3*0+1]*q[3*1+2]) + p[3*0+2],
357 		float64(p[3*1+0]*q[3*0+0]) + float64(p[3*1+1]*q[3*1+0]),
358 		float64(p[3*1+0]*q[3*0+1]) + float64(p[3*1+1]*q[3*1+1]),
359 		float64(p[3*1+0]*q[3*0+2]) + float64(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(float64(s2d[0]*sxf) + float64(s2d[1]*syf) + s2d[2]))
375 		dy := int(math.Floor(float64(s2d[3]*sxf) + float64(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 	if r, ok := dstMask.(image.Rectangle); ok {
412 		return adr.Intersect(r.Sub(dstMaskP)), nil
413 	}
414 	// TODO: clip to dstMask.Bounds() if the color model implies that out-of-bounds means 0 alpha?
415 	return adr, dstMask
416 }
417 
418 func transform_Uniform(dst Image, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.Uniform, sr image.Rectangle, bias image.Point, op Op) {
419 	switch op {
420 	case Over:
421 		switch dst := dst.(type) {
422 		case *image.RGBA:
423 			pr, pg, pb, pa := src.C.RGBA()
424 			pa1 := (0xffff - pa) * 0x101
425 
426 			for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
427 				dyf := float64(dr.Min.Y+int(dy)) + 0.5
428 				d := dst.PixOffset(dr.Min.X+adr.Min.X, dr.Min.Y+int(dy))
429 				for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 {
430 					dxf := float64(dr.Min.X+int(dx)) + 0.5
431 					sx0 := int(float64(d2s[0]*dxf)+float64(d2s[1]*dyf)+d2s[2]) + bias.X
432 					sy0 := int(float64(d2s[3]*dxf)+float64(d2s[4]*dyf)+d2s[5]) + bias.Y
433 					if !(image.Point{sx0, sy0}).In(sr) {
434 						continue
435 					}
436 					dst.Pix[d+0] = uint8((uint32(dst.Pix[d+0])*pa1/0xffff + pr) >> 8)
437 					dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*pa1/0xffff + pg) >> 8)
438 					dst.Pix[d+2] = uint8((uint32(dst.Pix[d+2])*pa1/0xffff + pb) >> 8)
439 					dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*pa1/0xffff + pa) >> 8)
440 				}
441 			}
442 
443 		default:
444 			pr, pg, pb, pa := src.C.RGBA()
445 			pa1 := 0xffff - pa
446 			dstColorRGBA64 := &color.RGBA64{}
447 			dstColor := color.Color(dstColorRGBA64)
448 
449 			for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
450 				dyf := float64(dr.Min.Y+int(dy)) + 0.5
451 				for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ {
452 					dxf := float64(dr.Min.X+int(dx)) + 0.5
453 					sx0 := int(float64(d2s[0]*dxf)+float64(d2s[1]*dyf)+d2s[2]) + bias.X
454 					sy0 := int(float64(d2s[3]*dxf)+float64(d2s[4]*dyf)+d2s[5]) + bias.Y
455 					if !(image.Point{sx0, sy0}).In(sr) {
456 						continue
457 					}
458 					qr, qg, qb, qa := dst.At(dr.Min.X+int(dx), dr.Min.Y+int(dy)).RGBA()
459 					dstColorRGBA64.R = uint16(qr*pa1/0xffff + pr)
460 					dstColorRGBA64.G = uint16(qg*pa1/0xffff + pg)
461 					dstColorRGBA64.B = uint16(qb*pa1/0xffff + pb)
462 					dstColorRGBA64.A = uint16(qa*pa1/0xffff + pa)
463 					dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(dy), dstColor)
464 				}
465 			}
466 		}
467 
468 	case Src:
469 		switch dst := dst.(type) {
470 		case *image.RGBA:
471 			pr, pg, pb, pa := src.C.RGBA()
472 			pr8 := uint8(pr >> 8)
473 			pg8 := uint8(pg >> 8)
474 			pb8 := uint8(pb >> 8)
475 			pa8 := uint8(pa >> 8)
476 
477 			for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
478 				dyf := float64(dr.Min.Y+int(dy)) + 0.5
479 				d := dst.PixOffset(dr.Min.X+adr.Min.X, dr.Min.Y+int(dy))
480 				for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 {
481 					dxf := float64(dr.Min.X+int(dx)) + 0.5
482 					sx0 := int(float64(d2s[0]*dxf)+float64(d2s[1]*dyf)+d2s[2]) + bias.X
483 					sy0 := int(float64(d2s[3]*dxf)+float64(d2s[4]*dyf)+d2s[5]) + bias.Y
484 					if !(image.Point{sx0, sy0}).In(sr) {
485 						continue
486 					}
487 					dst.Pix[d+0] = pr8
488 					dst.Pix[d+1] = pg8
489 					dst.Pix[d+2] = pb8
490 					dst.Pix[d+3] = pa8
491 				}
492 			}
493 
494 		default:
495 			pr, pg, pb, pa := src.C.RGBA()
496 			dstColorRGBA64 := &color.RGBA64{
497 				uint16(pr),
498 				uint16(pg),
499 				uint16(pb),
500 				uint16(pa),
501 			}
502 			dstColor := color.Color(dstColorRGBA64)
503 
504 			for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
505 				dyf := float64(dr.Min.Y+int(dy)) + 0.5
506 				for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ {
507 					dxf := float64(dr.Min.X+int(dx)) + 0.5
508 					sx0 := int(float64(d2s[0]*dxf)+float64(d2s[1]*dyf)+d2s[2]) + bias.X
509 					sy0 := int(float64(d2s[3]*dxf)+float64(d2s[4]*dyf)+d2s[5]) + bias.Y
510 					if !(image.Point{sx0, sy0}).In(sr) {
511 						continue
512 					}
513 					dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(dy), dstColor)
514 				}
515 			}
516 		}
517 	}
518 }
519 
520 func opaque(m image.Image) bool {
521 	o, ok := m.(interface {
522 		Opaque() bool
523 	})
524 	return ok && o.Opaque()
525 }