resample.go (2911B)
1 // Package resample scales images for the icon formats in this module. 2 package resample 3 4 import ( 5 "image" 6 "math" 7 8 "golang.org/x/image/draw" 9 ) 10 11 // Function is the algorithm used to resize an image. 12 type Function int 13 14 // Function constants, ordered from fastest to highest quality. 15 const ( 16 // Nearest-neighbor interpolation 17 NearestNeighbor Function = iota 18 // Bilinear interpolation 19 Bilinear 20 // Bicubic interpolation (with cubic hermite spline) 21 Bicubic 22 // Mitchell-Netravali interpolation 23 MitchellNetravali 24 // Lanczos interpolation (a=2) 25 Lanczos2 26 // Lanczos interpolation (a=3) 27 Lanczos3 28 ) 29 30 // scaler returns the resampler implementing f. Values outside the 31 // enumeration fall back to MitchellNetravali, the default. 32 func (f Function) scaler() draw.Interpolator { 33 switch f { 34 case NearestNeighbor: 35 return draw.NearestNeighbor 36 case Bilinear: 37 return draw.BiLinear 38 case Bicubic: 39 // Catmull-Rom is the cubic hermite spline interpolant. 40 return draw.CatmullRom 41 case Lanczos2: 42 return lanczos2 43 case Lanczos3: 44 return lanczos3 45 default: 46 return mitchellNetravali 47 } 48 } 49 50 // BiggestSide returns the larger of img's two dimensions. 51 func BiggestSide(img image.Image) uint { 52 b := img.Bounds() 53 return uint(max(b.Dx(), b.Dy(), 0)) 54 } 55 56 // Square scales img into a size by size square, ignoring the source aspect 57 // ratio. An image already that size is returned as it is. 58 // 59 // Scaling happens in alpha-premultiplied space, so colour does not bleed out 60 // of fully transparent pixels into the icon's edges. 61 func Square(img image.Image, size uint, f Function) image.Image { 62 bounds := img.Bounds() 63 if bounds.Dx() == int(size) && bounds.Dy() == int(size) { 64 return img 65 } 66 dst := image.NewRGBA(image.Rect(0, 0, int(size), int(size))) 67 f.scaler().Scale(dst, dst.Bounds(), img, bounds, draw.Src, nil) 68 return dst 69 } 70 71 // Kernels receive the distance from the sample as a non-negative value, and 72 // return the weight to give the pixel at that distance. 73 74 // mitchellNetravali is the Mitchell-Netravali filter with B = C = 1/3, the 75 // parameters its authors found to be the best compromise between blurring 76 // and ringing. 77 var mitchellNetravali = &draw.Kernel{Support: 2, At: func(t float64) float64 { 78 const b, c = 1.0 / 3.0, 1.0 / 3.0 79 switch { 80 case t < 1: 81 return ((12-9*b-6*c)*t*t*t + (-18+12*b+6*c)*t*t + (6 - 2*b)) / 6 82 case t < 2: 83 return ((-b-6*c)*t*t*t + (6*b+30*c)*t*t + (-12*b-48*c)*t + (8*b + 24*c)) / 6 84 default: 85 return 0 86 } 87 }} 88 89 var ( 90 lanczos2 = lanczos(2) 91 lanczos3 = lanczos(3) 92 ) 93 94 // lanczos returns the Lanczos filter of order a: a sinc windowed by another 95 // sinc stretched across the kernel's whole support. 96 func lanczos(a float64) *draw.Kernel { 97 return &draw.Kernel{Support: a, At: func(t float64) float64 { 98 switch { 99 case t == 0: 100 return 1 101 case t < a: 102 return a * math.Sin(math.Pi*t) * math.Sin(math.Pi*t/a) / (math.Pi * math.Pi * t * t) 103 default: 104 return 0 105 } 106 }} 107 }