github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/gift/resize.go (about) 1 package gift 2 3 import ( 4 "image" 5 "image/draw" 6 "math" 7 ) 8 9 // Resampling is an interpolation algorithm used for image resizing. 10 type Resampling interface { 11 Support() float32 12 Kernel(float32) float32 13 } 14 15 func bcspline(x, b, c float32) float32 { 16 if x < 0.0 { 17 x = -x 18 } 19 if x < 1.0 { 20 return ((12-9*b-6*c)*x*x*x + (-18+12*b+6*c)*x*x + (6 - 2*b)) / 6 21 } 22 if x < 2.0 { 23 return ((-b-6*c)*x*x*x + (6*b+30*c)*x*x + (-12*b-48*c)*x + (8*b + 24*c)) / 6 24 } 25 return 0 26 } 27 28 func sinc(x float32) float32 { 29 if x == 0 { 30 return 1.0 31 } 32 return float32(math.Sin(math.Pi*float64(x)) / (math.Pi * float64(x))) 33 } 34 35 type resamp struct { 36 name string 37 support float32 38 kernel func(float32) float32 39 } 40 41 func (r resamp) String() string { 42 return r.name 43 } 44 45 func (r resamp) Support() float32 { 46 return r.support 47 } 48 49 func (r resamp) Kernel(x float32) float32 { 50 return r.kernel(x) 51 } 52 53 // Nearest neighbor resampling filter. 54 var NearestNeighborResampling Resampling 55 56 // Box resampling filter. 57 var BoxResampling Resampling 58 59 // Linear resampling filter. 60 var LinearResampling Resampling 61 62 // Cubic resampling filter (Catmull-Rom). 63 var CubicResampling Resampling 64 65 // Lanczos resampling filter (3 lobes). 66 var LanczosResampling Resampling 67 68 func precomputeResamplingWeights(dstSize, srcSize int, resampling Resampling) [][]uweight { 69 du := float32(srcSize) / float32(dstSize) 70 scale := du 71 if scale < 1.0 { 72 scale = 1.0 73 } 74 ru := float32(math.Ceil(float64(scale * resampling.Support()))) 75 76 tmp := make([]float32, int(ru+2)*2) 77 result := make([][]uweight, dstSize) 78 79 for v := 0; v < dstSize; v++ { 80 fU := (float32(v)+0.5)*du - 0.5 81 82 startu := int(math.Ceil(float64(fU - ru))) 83 if startu < 0 { 84 startu = 0 85 } 86 endu := int(math.Floor(float64(fU + ru))) 87 if endu > srcSize-1 { 88 endu = srcSize - 1 89 } 90 91 sumf := float32(0.0) 92 for u := startu; u <= endu; u++ { 93 w := resampling.Kernel((float32(u) - fU) / scale) 94 sumf += w 95 tmp[u-startu] = w 96 } 97 for u := startu; u <= endu; u++ { 98 w := float32(tmp[u-startu] / sumf) 99 result[v] = append(result[v], uweight{u, w}) 100 } 101 } 102 103 return result 104 } 105 106 func resizeLine(dstBuf []pixel, srcBuf []pixel, weights [][]uweight) { 107 for dstu := 0; dstu < len(weights); dstu++ { 108 var r, g, b, a float32 109 for _, iw := range weights[dstu] { 110 c := srcBuf[iw.u] 111 wa := c.A * iw.weight 112 r += c.R * wa 113 g += c.G * wa 114 b += c.B * wa 115 a += wa 116 } 117 if a != 0 { 118 r /= a 119 g /= a 120 b /= a 121 } 122 dstBuf[dstu] = pixel{r, g, b, a} 123 } 124 } 125 126 func resizeHorizontal(dst draw.Image, src image.Image, w int, resampling Resampling, options *Options) { 127 srcb := src.Bounds() 128 dstb := dst.Bounds() 129 130 weights := precomputeResamplingWeights(w, srcb.Dx(), resampling) 131 132 pixGetter := newPixelGetter(src) 133 pixSetter := newPixelSetter(dst) 134 135 parallelize(options.Parallelization, srcb.Min.Y, srcb.Max.Y, func(pmin, pmax int) { 136 srcBuf := make([]pixel, srcb.Dx()) 137 dstBuf := make([]pixel, w) 138 for srcy := pmin; srcy < pmax; srcy++ { 139 pixGetter.getPixelRow(srcy, &srcBuf) 140 resizeLine(dstBuf, srcBuf, weights) 141 pixSetter.setPixelRow(dstb.Min.Y+srcy-srcb.Min.Y, dstBuf) 142 } 143 }) 144 } 145 146 func resizeVertical(dst draw.Image, src image.Image, h int, resampling Resampling, options *Options) { 147 srcb := src.Bounds() 148 dstb := dst.Bounds() 149 150 weights := precomputeResamplingWeights(h, srcb.Dy(), resampling) 151 152 pixGetter := newPixelGetter(src) 153 pixSetter := newPixelSetter(dst) 154 155 parallelize(options.Parallelization, srcb.Min.X, srcb.Max.X, func(pmin, pmax int) { 156 srcBuf := make([]pixel, srcb.Dy()) 157 dstBuf := make([]pixel, h) 158 for srcx := pmin; srcx < pmax; srcx++ { 159 pixGetter.getPixelColumn(srcx, &srcBuf) 160 resizeLine(dstBuf, srcBuf, weights) 161 pixSetter.setPixelColumn(dstb.Min.X+srcx-srcb.Min.X, dstBuf) 162 } 163 }) 164 } 165 166 func resizeNearest(dst draw.Image, src image.Image, w, h int, options *Options) { 167 srcb := src.Bounds() 168 dstb := dst.Bounds() 169 dx := float64(srcb.Dx()) / float64(w) 170 dy := float64(srcb.Dy()) / float64(h) 171 172 pixGetter := newPixelGetter(src) 173 pixSetter := newPixelSetter(dst) 174 175 parallelize(options.Parallelization, dstb.Min.Y, dstb.Min.Y+h, func(pmin, pmax int) { 176 for dsty := pmin; dsty < pmax; dsty++ { 177 for dstx := dstb.Min.X; dstx < dstb.Min.X+w; dstx++ { 178 fx := math.Floor((float64(dstx-dstb.Min.X) + 0.5) * dx) 179 fy := math.Floor((float64(dsty-dstb.Min.Y) + 0.5) * dy) 180 srcx := srcb.Min.X + int(fx) 181 srcy := srcb.Min.Y + int(fy) 182 px := pixGetter.getPixel(srcx, srcy) 183 pixSetter.setPixel(dstx, dsty, px) 184 } 185 } 186 }) 187 } 188 189 type resizeFilter struct { 190 width int 191 height int 192 resampling Resampling 193 } 194 195 func (p *resizeFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) { 196 w, h := p.width, p.height 197 srcw, srch := srcBounds.Dx(), srcBounds.Dy() 198 199 if (w == 0 && h == 0) || w < 0 || h < 0 || srcw <= 0 || srch <= 0 { 200 dstBounds = image.Rect(0, 0, 0, 0) 201 } else if w == 0 { 202 fw := float64(h) * float64(srcw) / float64(srch) 203 dstw := int(math.Max(1.0, math.Floor(fw+0.5))) 204 dstBounds = image.Rect(0, 0, dstw, h) 205 } else if h == 0 { 206 fh := float64(w) * float64(srch) / float64(srcw) 207 dsth := int(math.Max(1.0, math.Floor(fh+0.5))) 208 dstBounds = image.Rect(0, 0, w, dsth) 209 } else { 210 dstBounds = image.Rect(0, 0, w, h) 211 } 212 213 return 214 } 215 216 func (p *resizeFilter) Draw(dst draw.Image, src image.Image, options *Options) { 217 if options == nil { 218 options = &defaultOptions 219 } 220 221 b := p.Bounds(src.Bounds()) 222 w, h := b.Dx(), b.Dy() 223 224 if w <= 0 || h <= 0 { 225 return 226 } 227 228 if src.Bounds().Dx() == w && src.Bounds().Dy() == h { 229 copyimage(dst, src, options) 230 return 231 } 232 233 if p.resampling.Support() <= 0 { 234 resizeNearest(dst, src, w, h, options) 235 return 236 } 237 238 if src.Bounds().Dx() == w { 239 resizeVertical(dst, src, h, p.resampling, options) 240 return 241 } 242 243 if src.Bounds().Dy() == h { 244 resizeHorizontal(dst, src, w, p.resampling, options) 245 return 246 } 247 248 tmp := createTempImage(image.Rect(0, 0, w, src.Bounds().Dy())) 249 resizeHorizontal(tmp, src, w, p.resampling, options) 250 resizeVertical(dst, tmp, h, p.resampling, options) 251 return 252 } 253 254 // Resize creates a filter that resizes an image to the specified width and height using the specified resampling. 255 // If one of width or height is 0, the image aspect ratio is preserved. 256 // Supported resampling parameters: NearestNeighborResampling, BoxResampling, LinearResampling, CubicResampling, LanczosResampling. 257 // 258 // Example: 259 // 260 // // Resize the src image to width=300 preserving the aspect ratio. 261 // g := gift.New( 262 // gift.Resize(300, 0, gift.LanczosResampling), 263 // ) 264 // dst := image.NewRGBA(g.Bounds(src.Bounds())) 265 // g.Draw(dst, src) 266 // 267 func Resize(width, height int, resampling Resampling) Filter { 268 return &resizeFilter{ 269 width: width, 270 height: height, 271 resampling: resampling, 272 } 273 } 274 275 type resizeToFitFilter struct { 276 width int 277 height int 278 resampling Resampling 279 } 280 281 func (p *resizeToFitFilter) Bounds(srcBounds image.Rectangle) image.Rectangle { 282 w, h := p.width, p.height 283 srcw, srch := srcBounds.Dx(), srcBounds.Dy() 284 285 if w <= 0 || h <= 0 || srcw <= 0 || srch <= 0 { 286 return image.Rect(0, 0, 0, 0) 287 } 288 289 if srcw <= w && srch <= h { 290 return image.Rect(0, 0, srcw, srch) 291 } 292 293 wratio := float64(srcw) / float64(w) 294 hratio := float64(srch) / float64(h) 295 296 var dstw, dsth int 297 if wratio > hratio { 298 dstw = w 299 dsth = minint(int(float64(srch)/wratio+0.5), h) 300 } else { 301 dsth = h 302 dstw = minint(int(float64(srcw)/hratio+0.5), w) 303 } 304 305 return image.Rect(0, 0, dstw, dsth) 306 } 307 308 func (p *resizeToFitFilter) Draw(dst draw.Image, src image.Image, options *Options) { 309 b := p.Bounds(src.Bounds()) 310 Resize(b.Dx(), b.Dy(), p.resampling).Draw(dst, src, options) 311 return 312 } 313 314 // ResizeToFit creates a filter that resizes an image to fit within the specified dimensions while preserving the aspect ratio. 315 // Supported resampling parameters: NearestNeighborResampling, BoxResampling, LinearResampling, CubicResampling, LanczosResampling. 316 func ResizeToFit(width, height int, resampling Resampling) Filter { 317 return &resizeToFitFilter{ 318 width: width, 319 height: height, 320 resampling: resampling, 321 } 322 } 323 324 type resizeToFillFilter struct { 325 width int 326 height int 327 anchor Anchor 328 resampling Resampling 329 } 330 331 func (p *resizeToFillFilter) Bounds(srcBounds image.Rectangle) image.Rectangle { 332 w, h := p.width, p.height 333 srcw, srch := srcBounds.Dx(), srcBounds.Dy() 334 335 if w <= 0 || h <= 0 || srcw <= 0 || srch <= 0 { 336 return image.Rect(0, 0, 0, 0) 337 } 338 339 return image.Rect(0, 0, w, h) 340 } 341 342 func (p *resizeToFillFilter) Draw(dst draw.Image, src image.Image, options *Options) { 343 b := p.Bounds(src.Bounds()) 344 w, h := b.Dx(), b.Dy() 345 346 if w <= 0 || h <= 0 { 347 return 348 } 349 350 srcw, srch := src.Bounds().Dx(), src.Bounds().Dy() 351 352 wratio := float64(srcw) / float64(w) 353 hratio := float64(srch) / float64(h) 354 355 var tmpw, tmph int 356 if wratio < hratio { 357 tmpw = w 358 tmph = maxint(int(float64(srch)/wratio+0.5), h) 359 } else { 360 tmph = h 361 tmpw = maxint(int(float64(srcw)/hratio+0.5), w) 362 } 363 364 tmp := createTempImage(image.Rect(0, 0, tmpw, tmph)) 365 Resize(tmpw, tmph, p.resampling).Draw(tmp, src, options) 366 CropToSize(w, h, p.anchor).Draw(dst, tmp, options) 367 368 return 369 } 370 371 // ResizeToFill creates a filter that resizes an image to the smallest possible size that will cover the specified dimensions, 372 // then crops the resized image to the specified dimensions using the specified anchor point. 373 // Supported resampling parameters: NearestNeighborResampling, BoxResampling, LinearResampling, CubicResampling, LanczosResampling. 374 func ResizeToFill(width, height int, resampling Resampling, anchor Anchor) Filter { 375 return &resizeToFillFilter{ 376 width: width, 377 height: height, 378 anchor: anchor, 379 resampling: resampling, 380 } 381 } 382 383 func init() { 384 // Nearest neighbor resampling filter. 385 NearestNeighborResampling = resamp{ 386 name: "NearestNeighborResampling", 387 support: 0.0, 388 kernel: func(x float32) float32 { 389 return 0 390 }, 391 } 392 393 // Box resampling filter. 394 BoxResampling = resamp{ 395 name: "BoxResampling", 396 support: 0.5, 397 kernel: func(x float32) float32 { 398 if x < 0.0 { 399 x = -x 400 } 401 if x <= 0.5 { 402 return 1.0 403 } 404 return 0 405 }, 406 } 407 408 // Linear resampling filter. 409 LinearResampling = resamp{ 410 name: "LinearResampling", 411 support: 1.0, 412 kernel: func(x float32) float32 { 413 if x < 0.0 { 414 x = -x 415 } 416 if x < 1.0 { 417 return 1.0 - x 418 } 419 return 0 420 }, 421 } 422 423 // Cubic resampling filter (Catmull-Rom). 424 CubicResampling = resamp{ 425 name: "CubicResampling", 426 support: 2.0, 427 kernel: func(x float32) float32 { 428 if x < 0.0 { 429 x = -x 430 } 431 if x < 2.0 { 432 return bcspline(x, 0.0, 0.5) 433 } 434 return 0 435 }, 436 } 437 438 // Lanczos resampling filter (3 lobes). 439 LanczosResampling = resamp{ 440 name: "LanczosResampling", 441 support: 3.0, 442 kernel: func(x float32) float32 { 443 if x < 0.0 { 444 x = -x 445 } 446 if x < 3.0 { 447 return sinc(x) * sinc(x/3.0) 448 } 449 return 0 450 }, 451 } 452 }