gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

resize.go (14148B)


      1 package imaging
      2 
      3 import (
      4 	"image"
      5 	"math"
      6 )
      7 
      8 type indexWeight struct {
      9 	index  int
     10 	weight float64
     11 }
     12 
     13 func precomputeWeights(dstSize, srcSize int, filter ResampleFilter) [][]indexWeight {
     14 	du := float64(srcSize) / float64(dstSize)
     15 	scale := du
     16 	if scale < 1.0 {
     17 		scale = 1.0
     18 	}
     19 	ru := math.Ceil(scale * filter.Support)
     20 
     21 	out := make([][]indexWeight, dstSize)
     22 	tmp := make([]indexWeight, 0, dstSize*int(ru+2)*2)
     23 
     24 	for v := 0; v < dstSize; v++ {
     25 		fu := (float64(v)+0.5)*du - 0.5
     26 
     27 		begin := int(math.Ceil(fu - ru))
     28 		if begin < 0 {
     29 			begin = 0
     30 		}
     31 		end := int(math.Floor(fu + ru))
     32 		if end > srcSize-1 {
     33 			end = srcSize - 1
     34 		}
     35 
     36 		var sum float64
     37 		for u := begin; u <= end; u++ {
     38 			w := filter.Kernel((float64(u) - fu) / scale)
     39 			if w != 0 {
     40 				sum += w
     41 				tmp = append(tmp, indexWeight{index: u, weight: w})
     42 			}
     43 		}
     44 		if sum != 0 {
     45 			for i := range tmp {
     46 				tmp[i].weight /= sum
     47 			}
     48 		}
     49 
     50 		out[v] = tmp
     51 		tmp = tmp[len(tmp):]
     52 	}
     53 
     54 	return out
     55 }
     56 
     57 // Resize resizes the image to the specified width and height using the specified resampling
     58 // filter and returns the transformed image. If one of width or height is 0, the image aspect
     59 // ratio is preserved.
     60 //
     61 // Example:
     62 //
     63 //	dstImage := imaging.Resize(srcImage, 800, 600, imaging.Lanczos)
     64 //
     65 func Resize(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA {
     66 	dstW, dstH := width, height
     67 	if dstW < 0 || dstH < 0 {
     68 		return &image.NRGBA{}
     69 	}
     70 	if dstW == 0 && dstH == 0 {
     71 		return &image.NRGBA{}
     72 	}
     73 
     74 	srcW := img.Bounds().Dx()
     75 	srcH := img.Bounds().Dy()
     76 	if srcW <= 0 || srcH <= 0 {
     77 		return &image.NRGBA{}
     78 	}
     79 
     80 	// If new width or height is 0 then preserve aspect ratio, minimum 1px.
     81 	if dstW == 0 {
     82 		tmpW := float64(dstH) * float64(srcW) / float64(srcH)
     83 		dstW = int(math.Max(1.0, math.Floor(tmpW+0.5)))
     84 	}
     85 	if dstH == 0 {
     86 		tmpH := float64(dstW) * float64(srcH) / float64(srcW)
     87 		dstH = int(math.Max(1.0, math.Floor(tmpH+0.5)))
     88 	}
     89 
     90 	if filter.Support <= 0 {
     91 		// Nearest-neighbor special case.
     92 		return resizeNearest(img, dstW, dstH)
     93 	}
     94 
     95 	if srcW != dstW && srcH != dstH {
     96 		return resizeVertical(resizeHorizontal(img, dstW, filter), dstH, filter)
     97 	}
     98 	if srcW != dstW {
     99 		return resizeHorizontal(img, dstW, filter)
    100 	}
    101 	if srcH != dstH {
    102 		return resizeVertical(img, dstH, filter)
    103 	}
    104 	return Clone(img)
    105 }
    106 
    107 func resizeHorizontal(img image.Image, width int, filter ResampleFilter) *image.NRGBA {
    108 	src := newScanner(img)
    109 	dst := image.NewNRGBA(image.Rect(0, 0, width, src.h))
    110 	weights := precomputeWeights(width, src.w, filter)
    111 	parallel(0, src.h, func(ys <-chan int) {
    112 		scanLine := make([]uint8, src.w*4)
    113 		for y := range ys {
    114 			src.scan(0, y, src.w, y+1, scanLine)
    115 			j0 := y * dst.Stride
    116 			for x := range weights {
    117 				var r, g, b, a float64
    118 				for _, w := range weights[x] {
    119 					i := w.index * 4
    120 					s := scanLine[i : i+4 : i+4]
    121 					aw := float64(s[3]) * w.weight
    122 					r += float64(s[0]) * aw
    123 					g += float64(s[1]) * aw
    124 					b += float64(s[2]) * aw
    125 					a += aw
    126 				}
    127 				if a != 0 {
    128 					aInv := 1 / a
    129 					j := j0 + x*4
    130 					d := dst.Pix[j : j+4 : j+4]
    131 					d[0] = clamp(r * aInv)
    132 					d[1] = clamp(g * aInv)
    133 					d[2] = clamp(b * aInv)
    134 					d[3] = clamp(a)
    135 				}
    136 			}
    137 		}
    138 	})
    139 	return dst
    140 }
    141 
    142 func resizeVertical(img image.Image, height int, filter ResampleFilter) *image.NRGBA {
    143 	src := newScanner(img)
    144 	dst := image.NewNRGBA(image.Rect(0, 0, src.w, height))
    145 	weights := precomputeWeights(height, src.h, filter)
    146 	parallel(0, src.w, func(xs <-chan int) {
    147 		scanLine := make([]uint8, src.h*4)
    148 		for x := range xs {
    149 			src.scan(x, 0, x+1, src.h, scanLine)
    150 			for y := range weights {
    151 				var r, g, b, a float64
    152 				for _, w := range weights[y] {
    153 					i := w.index * 4
    154 					s := scanLine[i : i+4 : i+4]
    155 					aw := float64(s[3]) * w.weight
    156 					r += float64(s[0]) * aw
    157 					g += float64(s[1]) * aw
    158 					b += float64(s[2]) * aw
    159 					a += aw
    160 				}
    161 				if a != 0 {
    162 					aInv := 1 / a
    163 					j := y*dst.Stride + x*4
    164 					d := dst.Pix[j : j+4 : j+4]
    165 					d[0] = clamp(r * aInv)
    166 					d[1] = clamp(g * aInv)
    167 					d[2] = clamp(b * aInv)
    168 					d[3] = clamp(a)
    169 				}
    170 			}
    171 		}
    172 	})
    173 	return dst
    174 }
    175 
    176 // resizeNearest is a fast nearest-neighbor resize, no filtering.
    177 func resizeNearest(img image.Image, width, height int) *image.NRGBA {
    178 	dst := image.NewNRGBA(image.Rect(0, 0, width, height))
    179 	dx := float64(img.Bounds().Dx()) / float64(width)
    180 	dy := float64(img.Bounds().Dy()) / float64(height)
    181 
    182 	if dx > 1 && dy > 1 {
    183 		src := newScanner(img)
    184 		parallel(0, height, func(ys <-chan int) {
    185 			for y := range ys {
    186 				srcY := int((float64(y) + 0.5) * dy)
    187 				dstOff := y * dst.Stride
    188 				for x := 0; x < width; x++ {
    189 					srcX := int((float64(x) + 0.5) * dx)
    190 					src.scan(srcX, srcY, srcX+1, srcY+1, dst.Pix[dstOff:dstOff+4])
    191 					dstOff += 4
    192 				}
    193 			}
    194 		})
    195 	} else {
    196 		src := toNRGBA(img)
    197 		parallel(0, height, func(ys <-chan int) {
    198 			for y := range ys {
    199 				srcY := int((float64(y) + 0.5) * dy)
    200 				srcOff0 := srcY * src.Stride
    201 				dstOff := y * dst.Stride
    202 				for x := 0; x < width; x++ {
    203 					srcX := int((float64(x) + 0.5) * dx)
    204 					srcOff := srcOff0 + srcX*4
    205 					copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
    206 					dstOff += 4
    207 				}
    208 			}
    209 		})
    210 	}
    211 
    212 	return dst
    213 }
    214 
    215 // Fit scales down the image using the specified resample filter to fit the specified
    216 // maximum width and height and returns the transformed image.
    217 //
    218 // Example:
    219 //
    220 //	dstImage := imaging.Fit(srcImage, 800, 600, imaging.Lanczos)
    221 //
    222 func Fit(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA {
    223 	maxW, maxH := width, height
    224 
    225 	if maxW <= 0 || maxH <= 0 {
    226 		return &image.NRGBA{}
    227 	}
    228 
    229 	srcBounds := img.Bounds()
    230 	srcW := srcBounds.Dx()
    231 	srcH := srcBounds.Dy()
    232 
    233 	if srcW <= 0 || srcH <= 0 {
    234 		return &image.NRGBA{}
    235 	}
    236 
    237 	if srcW <= maxW && srcH <= maxH {
    238 		return Clone(img)
    239 	}
    240 
    241 	srcAspectRatio := float64(srcW) / float64(srcH)
    242 	maxAspectRatio := float64(maxW) / float64(maxH)
    243 
    244 	var newW, newH int
    245 	if srcAspectRatio > maxAspectRatio {
    246 		newW = maxW
    247 		newH = int(float64(newW) / srcAspectRatio)
    248 	} else {
    249 		newH = maxH
    250 		newW = int(float64(newH) * srcAspectRatio)
    251 	}
    252 
    253 	return Resize(img, newW, newH, filter)
    254 }
    255 
    256 // Fill creates an image with the specified dimensions and fills it with the scaled source image.
    257 // To achieve the correct aspect ratio without stretching, the source image will be cropped.
    258 //
    259 // Example:
    260 //
    261 //	dstImage := imaging.Fill(srcImage, 800, 600, imaging.Center, imaging.Lanczos)
    262 //
    263 func Fill(img image.Image, width, height int, anchor Anchor, filter ResampleFilter) *image.NRGBA {
    264 	dstW, dstH := width, height
    265 
    266 	if dstW <= 0 || dstH <= 0 {
    267 		return &image.NRGBA{}
    268 	}
    269 
    270 	srcBounds := img.Bounds()
    271 	srcW := srcBounds.Dx()
    272 	srcH := srcBounds.Dy()
    273 
    274 	if srcW <= 0 || srcH <= 0 {
    275 		return &image.NRGBA{}
    276 	}
    277 
    278 	if srcW == dstW && srcH == dstH {
    279 		return Clone(img)
    280 	}
    281 
    282 	if srcW >= 100 && srcH >= 100 {
    283 		return cropAndResize(img, dstW, dstH, anchor, filter)
    284 	}
    285 	return resizeAndCrop(img, dstW, dstH, anchor, filter)
    286 }
    287 
    288 // cropAndResize crops the image to the smallest possible size that has the required aspect ratio using
    289 // the given anchor point, then scales it to the specified dimensions and returns the transformed image.
    290 //
    291 // This is generally faster than resizing first, but may result in inaccuracies when used on small source images.
    292 func cropAndResize(img image.Image, width, height int, anchor Anchor, filter ResampleFilter) *image.NRGBA {
    293 	dstW, dstH := width, height
    294 
    295 	srcBounds := img.Bounds()
    296 	srcW := srcBounds.Dx()
    297 	srcH := srcBounds.Dy()
    298 	srcAspectRatio := float64(srcW) / float64(srcH)
    299 	dstAspectRatio := float64(dstW) / float64(dstH)
    300 
    301 	var tmp *image.NRGBA
    302 	if srcAspectRatio < dstAspectRatio {
    303 		cropH := float64(srcW) * float64(dstH) / float64(dstW)
    304 		tmp = CropAnchor(img, srcW, int(math.Max(1, cropH)+0.5), anchor)
    305 	} else {
    306 		cropW := float64(srcH) * float64(dstW) / float64(dstH)
    307 		tmp = CropAnchor(img, int(math.Max(1, cropW)+0.5), srcH, anchor)
    308 	}
    309 
    310 	return Resize(tmp, dstW, dstH, filter)
    311 }
    312 
    313 // resizeAndCrop resizes the image to the smallest possible size that will cover the specified dimensions,
    314 // crops the resized image to the specified dimensions using the given anchor point and returns
    315 // the transformed image.
    316 func resizeAndCrop(img image.Image, width, height int, anchor Anchor, filter ResampleFilter) *image.NRGBA {
    317 	dstW, dstH := width, height
    318 
    319 	srcBounds := img.Bounds()
    320 	srcW := srcBounds.Dx()
    321 	srcH := srcBounds.Dy()
    322 	srcAspectRatio := float64(srcW) / float64(srcH)
    323 	dstAspectRatio := float64(dstW) / float64(dstH)
    324 
    325 	var tmp *image.NRGBA
    326 	if srcAspectRatio < dstAspectRatio {
    327 		tmp = Resize(img, dstW, 0, filter)
    328 	} else {
    329 		tmp = Resize(img, 0, dstH, filter)
    330 	}
    331 
    332 	return CropAnchor(tmp, dstW, dstH, anchor)
    333 }
    334 
    335 // Thumbnail scales the image up or down using the specified resample filter, crops it
    336 // to the specified width and hight and returns the transformed image.
    337 //
    338 // Example:
    339 //
    340 //	dstImage := imaging.Thumbnail(srcImage, 100, 100, imaging.Lanczos)
    341 //
    342 func Thumbnail(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA {
    343 	return Fill(img, width, height, Center, filter)
    344 }
    345 
    346 // ResampleFilter specifies a resampling filter to be used for image resizing.
    347 //
    348 //	General filter recommendations:
    349 //
    350 //	- Lanczos
    351 //		A high-quality resampling filter for photographic images yielding sharp results.
    352 //
    353 //	- CatmullRom
    354 //		A sharp cubic filter that is faster than Lanczos filter while providing similar results.
    355 //
    356 //	- MitchellNetravali
    357 //		A cubic filter that produces smoother results with less ringing artifacts than CatmullRom.
    358 //
    359 //	- Linear
    360 //		Bilinear resampling filter, produces a smooth output. Faster than cubic filters.
    361 //
    362 //	- Box
    363 //		Simple and fast averaging filter appropriate for downscaling.
    364 //		When upscaling it's similar to NearestNeighbor.
    365 //
    366 //	- NearestNeighbor
    367 //		Fastest resampling filter, no antialiasing.
    368 //
    369 type ResampleFilter struct {
    370 	Support float64
    371 	Kernel  func(float64) float64
    372 }
    373 
    374 // NearestNeighbor is a nearest-neighbor filter (no anti-aliasing).
    375 var NearestNeighbor ResampleFilter
    376 
    377 // Box filter (averaging pixels).
    378 var Box ResampleFilter
    379 
    380 // Linear filter.
    381 var Linear ResampleFilter
    382 
    383 // Hermite cubic spline filter (BC-spline; B=0; C=0).
    384 var Hermite ResampleFilter
    385 
    386 // MitchellNetravali is Mitchell-Netravali cubic filter (BC-spline; B=1/3; C=1/3).
    387 var MitchellNetravali ResampleFilter
    388 
    389 // CatmullRom is a Catmull-Rom - sharp cubic filter (BC-spline; B=0; C=0.5).
    390 var CatmullRom ResampleFilter
    391 
    392 // BSpline is a smooth cubic filter (BC-spline; B=1; C=0).
    393 var BSpline ResampleFilter
    394 
    395 // Gaussian is a Gaussian blurring filter.
    396 var Gaussian ResampleFilter
    397 
    398 // Bartlett is a Bartlett-windowed sinc filter (3 lobes).
    399 var Bartlett ResampleFilter
    400 
    401 // Lanczos filter (3 lobes).
    402 var Lanczos ResampleFilter
    403 
    404 // Hann is a Hann-windowed sinc filter (3 lobes).
    405 var Hann ResampleFilter
    406 
    407 // Hamming is a Hamming-windowed sinc filter (3 lobes).
    408 var Hamming ResampleFilter
    409 
    410 // Blackman is a Blackman-windowed sinc filter (3 lobes).
    411 var Blackman ResampleFilter
    412 
    413 // Welch is a Welch-windowed sinc filter (parabolic window, 3 lobes).
    414 var Welch ResampleFilter
    415 
    416 // Cosine is a Cosine-windowed sinc filter (3 lobes).
    417 var Cosine ResampleFilter
    418 
    419 func bcspline(x, b, c float64) float64 {
    420 	var y float64
    421 	x = math.Abs(x)
    422 	if x < 1.0 {
    423 		y = ((12-9*b-6*c)*x*x*x + (-18+12*b+6*c)*x*x + (6 - 2*b)) / 6
    424 	} else if x < 2.0 {
    425 		y = ((-b-6*c)*x*x*x + (6*b+30*c)*x*x + (-12*b-48*c)*x + (8*b + 24*c)) / 6
    426 	}
    427 	return y
    428 }
    429 
    430 func sinc(x float64) float64 {
    431 	if x == 0 {
    432 		return 1
    433 	}
    434 	return math.Sin(math.Pi*x) / (math.Pi * x)
    435 }
    436 
    437 func init() {
    438 	NearestNeighbor = ResampleFilter{
    439 		Support: 0.0, // special case - not applying the filter
    440 	}
    441 
    442 	Box = ResampleFilter{
    443 		Support: 0.5,
    444 		Kernel: func(x float64) float64 {
    445 			x = math.Abs(x)
    446 			if x <= 0.5 {
    447 				return 1.0
    448 			}
    449 			return 0
    450 		},
    451 	}
    452 
    453 	Linear = ResampleFilter{
    454 		Support: 1.0,
    455 		Kernel: func(x float64) float64 {
    456 			x = math.Abs(x)
    457 			if x < 1.0 {
    458 				return 1.0 - x
    459 			}
    460 			return 0
    461 		},
    462 	}
    463 
    464 	Hermite = ResampleFilter{
    465 		Support: 1.0,
    466 		Kernel: func(x float64) float64 {
    467 			x = math.Abs(x)
    468 			if x < 1.0 {
    469 				return bcspline(x, 0.0, 0.0)
    470 			}
    471 			return 0
    472 		},
    473 	}
    474 
    475 	MitchellNetravali = ResampleFilter{
    476 		Support: 2.0,
    477 		Kernel: func(x float64) float64 {
    478 			x = math.Abs(x)
    479 			if x < 2.0 {
    480 				return bcspline(x, 1.0/3.0, 1.0/3.0)
    481 			}
    482 			return 0
    483 		},
    484 	}
    485 
    486 	CatmullRom = ResampleFilter{
    487 		Support: 2.0,
    488 		Kernel: func(x float64) float64 {
    489 			x = math.Abs(x)
    490 			if x < 2.0 {
    491 				return bcspline(x, 0.0, 0.5)
    492 			}
    493 			return 0
    494 		},
    495 	}
    496 
    497 	BSpline = ResampleFilter{
    498 		Support: 2.0,
    499 		Kernel: func(x float64) float64 {
    500 			x = math.Abs(x)
    501 			if x < 2.0 {
    502 				return bcspline(x, 1.0, 0.0)
    503 			}
    504 			return 0
    505 		},
    506 	}
    507 
    508 	Gaussian = ResampleFilter{
    509 		Support: 2.0,
    510 		Kernel: func(x float64) float64 {
    511 			x = math.Abs(x)
    512 			if x < 2.0 {
    513 				return math.Exp(-2 * x * x)
    514 			}
    515 			return 0
    516 		},
    517 	}
    518 
    519 	Bartlett = ResampleFilter{
    520 		Support: 3.0,
    521 		Kernel: func(x float64) float64 {
    522 			x = math.Abs(x)
    523 			if x < 3.0 {
    524 				return sinc(x) * (3.0 - x) / 3.0
    525 			}
    526 			return 0
    527 		},
    528 	}
    529 
    530 	Lanczos = ResampleFilter{
    531 		Support: 3.0,
    532 		Kernel: func(x float64) float64 {
    533 			x = math.Abs(x)
    534 			if x < 3.0 {
    535 				return sinc(x) * sinc(x/3.0)
    536 			}
    537 			return 0
    538 		},
    539 	}
    540 
    541 	Hann = ResampleFilter{
    542 		Support: 3.0,
    543 		Kernel: func(x float64) float64 {
    544 			x = math.Abs(x)
    545 			if x < 3.0 {
    546 				return sinc(x) * (0.5 + 0.5*math.Cos(math.Pi*x/3.0))
    547 			}
    548 			return 0
    549 		},
    550 	}
    551 
    552 	Hamming = ResampleFilter{
    553 		Support: 3.0,
    554 		Kernel: func(x float64) float64 {
    555 			x = math.Abs(x)
    556 			if x < 3.0 {
    557 				return sinc(x) * (0.54 + 0.46*math.Cos(math.Pi*x/3.0))
    558 			}
    559 			return 0
    560 		},
    561 	}
    562 
    563 	Blackman = ResampleFilter{
    564 		Support: 3.0,
    565 		Kernel: func(x float64) float64 {
    566 			x = math.Abs(x)
    567 			if x < 3.0 {
    568 				return sinc(x) * (0.42 - 0.5*math.Cos(math.Pi*x/3.0+math.Pi) + 0.08*math.Cos(2.0*math.Pi*x/3.0))
    569 			}
    570 			return 0
    571 		},
    572 	}
    573 
    574 	Welch = ResampleFilter{
    575 		Support: 3.0,
    576 		Kernel: func(x float64) float64 {
    577 			x = math.Abs(x)
    578 			if x < 3.0 {
    579 				return sinc(x) * (1.0 - (x * x / 9.0))
    580 			}
    581 			return 0
    582 		},
    583 	}
    584 
    585 	Cosine = ResampleFilter{
    586 		Support: 3.0,
    587 		Kernel: func(x float64) float64 {
    588 			x = math.Abs(x)
    589 			if x < 3.0 {
    590 				return sinc(x) * math.Cos((math.Pi/2.0)*(x/3.0))
    591 			}
    592 			return 0
    593 		},
    594 	}
    595 }