github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/gift/transform.go (about) 1 package gift 2 3 import ( 4 "image" 5 "image/color" 6 "image/draw" 7 ) 8 9 type transformType int 10 11 const ( 12 ttRotate90 transformType = iota 13 ttRotate180 14 ttRotate270 15 ttFlipHorizontal 16 ttFlipVertical 17 ttTranspose 18 ttTransverse 19 ) 20 21 type transformFilter struct { 22 tt transformType 23 } 24 25 func (p *transformFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) { 26 if p.tt == ttRotate90 || p.tt == ttRotate270 || p.tt == ttTranspose || p.tt == ttTransverse { 27 dstBounds = image.Rect(0, 0, srcBounds.Dy(), srcBounds.Dx()) 28 } else { 29 dstBounds = image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy()) 30 } 31 return 32 } 33 34 func (p *transformFilter) Draw(dst draw.Image, src image.Image, options *Options) { 35 if options == nil { 36 options = &defaultOptions 37 } 38 39 srcb := src.Bounds() 40 dstb := dst.Bounds() 41 42 pixGetter := newPixelGetter(src) 43 pixSetter := newPixelSetter(dst) 44 45 parallelize(options.Parallelization, srcb.Min.Y, srcb.Max.Y, func(pmin, pmax int) { 46 for srcy := pmin; srcy < pmax; srcy++ { 47 for srcx := srcb.Min.X; srcx < srcb.Max.X; srcx++ { 48 var dstx, dsty int 49 switch p.tt { 50 case ttRotate90: 51 dstx = dstb.Min.X + srcy - srcb.Min.Y 52 dsty = dstb.Min.Y + srcb.Max.X - srcx - 1 53 case ttRotate180: 54 dstx = dstb.Min.X + srcb.Max.X - srcx - 1 55 dsty = dstb.Min.Y + srcb.Max.Y - srcy - 1 56 case ttRotate270: 57 dstx = dstb.Min.X + srcb.Max.Y - srcy - 1 58 dsty = dstb.Min.Y + srcx - srcb.Min.X 59 case ttFlipHorizontal: 60 dstx = dstb.Min.X + srcb.Max.X - srcx - 1 61 dsty = dstb.Min.Y + srcy - srcb.Min.Y 62 case ttFlipVertical: 63 dstx = dstb.Min.X + srcx - srcb.Min.X 64 dsty = dstb.Min.Y + srcb.Max.Y - srcy - 1 65 case ttTranspose: 66 dstx = dstb.Min.X + srcy - srcb.Min.Y 67 dsty = dstb.Min.Y + srcx - srcb.Min.X 68 case ttTransverse: 69 dstx = dstb.Min.Y + srcb.Max.Y - srcy - 1 70 dsty = dstb.Min.X + srcb.Max.X - srcx - 1 71 } 72 pixSetter.setPixel(dstx, dsty, pixGetter.getPixel(srcx, srcy)) 73 } 74 } 75 }) 76 } 77 78 // Rotate90 creates a filter that rotates an image 90 degrees counter-clockwise. 79 func Rotate90() Filter { 80 return &transformFilter{ 81 tt: ttRotate90, 82 } 83 } 84 85 // Rotate180 creates a filter that rotates an image 180 degrees counter-clockwise. 86 func Rotate180() Filter { 87 return &transformFilter{ 88 tt: ttRotate180, 89 } 90 } 91 92 // Rotate270 creates a filter that rotates an image 270 degrees counter-clockwise. 93 func Rotate270() Filter { 94 return &transformFilter{ 95 tt: ttRotate270, 96 } 97 } 98 99 // FlipHorizontal creates a filter that flips an image horizontally. 100 func FlipHorizontal() Filter { 101 return &transformFilter{ 102 tt: ttFlipHorizontal, 103 } 104 } 105 106 // FlipVertical creates a filter that flips an image vertically. 107 func FlipVertical() Filter { 108 return &transformFilter{ 109 tt: ttFlipVertical, 110 } 111 } 112 113 // Transpose creates a filter that flips an image horizontally and rotates 90 degrees counter-clockwise. 114 func Transpose() Filter { 115 return &transformFilter{ 116 tt: ttTranspose, 117 } 118 } 119 120 // Transverse creates a filter that flips an image vertically and rotates 90 degrees counter-clockwise. 121 func Transverse() Filter { 122 return &transformFilter{ 123 tt: ttTransverse, 124 } 125 } 126 127 // Interpolation is an interpolation algorithm used for image transformation. 128 type Interpolation int 129 130 const ( 131 // Nearest Neighbor interpolation algorithm 132 NearestNeighborInterpolation Interpolation = iota 133 // Linear interpolation algorithm 134 LinearInterpolation 135 // Cubic interpolation algorithm 136 CubicInterpolation 137 ) 138 139 func rotatePoint(x, y, asin, acos float32) (float32, float32) { 140 newx := x*acos - y*asin 141 newy := x*asin + y*acos 142 return newx, newy 143 } 144 145 func calcRotatedSize(w, h int, angle float32) (int, int) { 146 if w <= 0 || h <= 0 { 147 return 0, 0 148 } 149 150 xoff := float32(w)/2 - 0.5 151 yoff := float32(h)/2 - 0.5 152 153 asin, acos := sincosf32(angle) 154 x1, y1 := rotatePoint(0-xoff, 0-yoff, asin, acos) 155 x2, y2 := rotatePoint(float32(w-1)-xoff, 0-yoff, asin, acos) 156 x3, y3 := rotatePoint(float32(w-1)-xoff, float32(h-1)-yoff, asin, acos) 157 x4, y4 := rotatePoint(0-xoff, float32(h-1)-yoff, asin, acos) 158 159 minx := minf32(x1, minf32(x2, minf32(x3, x4))) 160 maxx := maxf32(x1, maxf32(x2, maxf32(x3, x4))) 161 miny := minf32(y1, minf32(y2, minf32(y3, y4))) 162 maxy := maxf32(y1, maxf32(y2, maxf32(y3, y4))) 163 164 neww := maxx - minx + 1 165 if neww-floorf32(neww) > 0.01 { 166 neww += 2 167 } 168 newh := maxy - miny + 1 169 if newh-floorf32(newh) > 0.01 { 170 newh += 2 171 } 172 return int(neww), int(newh) 173 } 174 175 type rotateFilter struct { 176 angle float32 177 bgcolor color.Color 178 interpolation Interpolation 179 } 180 181 func (p *rotateFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) { 182 w, h := calcRotatedSize(srcBounds.Dx(), srcBounds.Dy(), p.angle) 183 dstBounds = image.Rect(0, 0, w, h) 184 return 185 } 186 187 func (p *rotateFilter) Draw(dst draw.Image, src image.Image, options *Options) { 188 if options == nil { 189 options = &defaultOptions 190 } 191 192 srcb := src.Bounds() 193 dstb := dst.Bounds() 194 195 w, h := calcRotatedSize(srcb.Dx(), srcb.Dy(), p.angle) 196 if w <= 0 || h <= 0 { 197 return 198 } 199 200 srcxoff := float32(srcb.Dx())/2 - 0.5 201 srcyoff := float32(srcb.Dy())/2 - 0.5 202 dstxoff := float32(w)/2 - 0.5 203 dstyoff := float32(h)/2 - 0.5 204 205 bgpx := pixelclr(p.bgcolor) 206 asin, acos := sincosf32(p.angle) 207 208 pixGetter := newPixelGetter(src) 209 pixSetter := newPixelSetter(dst) 210 211 parallelize(options.Parallelization, 0, h, func(pmin, pmax int) { 212 for y := pmin; y < pmax; y++ { 213 for x := 0; x < w; x++ { 214 215 xf, yf := rotatePoint(float32(x)-dstxoff, float32(y)-dstyoff, asin, acos) 216 xf, yf = float32(srcb.Min.X)+xf+srcxoff, float32(srcb.Min.Y)+yf+srcyoff 217 218 switch p.interpolation { 219 case CubicInterpolation: 220 var calc bool 221 var pxs [16]pixel 222 var cfs [16]float32 223 var px pixel 224 225 x0, y0 := int(floorf32(xf)), int(floorf32(yf)) 226 xq, yq := xf-float32(x0), yf-float32(y0) 227 228 for i := 0; i < 4; i++ { 229 for j := 0; j < 4; j++ { 230 pt := image.Pt(x0+j-1, y0+i-1) 231 if pt.In(srcb) { 232 pxs[i*4+j] = pixGetter.getPixel(pt.X, pt.Y) 233 calc = true 234 } else { 235 pxs[i*4+j] = bgpx 236 } 237 } 238 } 239 240 if !calc { 241 pixSetter.setPixel(dstb.Min.X+x, dstb.Min.Y+y, bgpx) 242 continue 243 } 244 245 cfs[0] = (1.0 / 36.0) * xq * yq * (xq - 1) * (xq - 2) * (yq - 1) * (yq - 2) 246 cfs[1] = -(1.0 / 12.0) * yq * (xq - 1) * (xq - 2) * (xq + 1) * (yq - 1) * (yq - 2) 247 cfs[2] = (1.0 / 12.0) * xq * yq * (xq + 1) * (xq - 2) * (yq - 1) * (yq - 2) 248 cfs[3] = -(1.0 / 36.0) * xq * yq * (xq - 1) * (xq + 1) * (yq - 1) * (yq - 2) 249 cfs[4] = -(1.0 / 12.0) * xq * (xq - 1) * (xq - 2) * (yq - 1) * (yq - 2) * (yq + 1) 250 cfs[5] = 0.25 * (xq - 1) * (xq - 2) * (xq + 1) * (yq - 1) * (yq - 2) * (yq + 1) 251 cfs[6] = -0.25 * xq * (xq + 1) * (xq - 2) * (yq - 1) * (yq - 2) * (yq + 1) 252 cfs[7] = (1.0 / 12.0) * xq * (xq - 1) * (xq + 1) * (yq - 1) * (yq - 2) * (yq + 1) 253 cfs[8] = (1.0 / 12.0) * xq * yq * (xq - 1) * (xq - 2) * (yq + 1) * (yq - 2) 254 cfs[9] = -0.25 * yq * (xq - 1) * (xq - 2) * (xq + 1) * (yq + 1) * (yq - 2) 255 cfs[10] = 0.25 * xq * yq * (xq + 1) * (xq - 2) * (yq + 1) * (yq - 2) 256 cfs[11] = -(1.0 / 12.0) * xq * yq * (xq - 1) * (xq + 1) * (yq + 1) * (yq - 2) 257 cfs[12] = -(1.0 / 36.0) * xq * yq * (xq - 1) * (xq - 2) * (yq - 1) * (yq + 1) 258 cfs[13] = (1.0 / 12.0) * yq * (xq - 1) * (xq - 2) * (xq + 1) * (yq - 1) * (yq + 1) 259 cfs[14] = -(1.0 / 12.0) * xq * yq * (xq + 1) * (xq - 2) * (yq - 1) * (yq + 1) 260 cfs[15] = (1.0 / 36.0) * xq * yq * (xq - 1) * (xq + 1) * (yq - 1) * (yq + 1) 261 262 for i := range pxs { 263 wa := pxs[i].A * cfs[i] 264 px.R += pxs[i].R * wa 265 px.G += pxs[i].G * wa 266 px.B += pxs[i].B * wa 267 px.A += wa 268 } 269 270 if px.A != 0.0 { 271 px.R /= px.A 272 px.G /= px.A 273 px.B /= px.A 274 } 275 276 pixSetter.setPixel(dstb.Min.X+x, dstb.Min.Y+y, px) 277 278 case LinearInterpolation: 279 var calc bool 280 var pxs [4]pixel 281 var cfs [4]float32 282 var px pixel 283 284 x0, y0 := int(floorf32(xf)), int(floorf32(yf)) 285 xq, yq := xf-float32(x0), yf-float32(y0) 286 287 for i := 0; i < 2; i++ { 288 for j := 0; j < 2; j++ { 289 pt := image.Pt(x0+j, y0+i) 290 if pt.In(srcb) { 291 pxs[i*2+j] = pixGetter.getPixel(pt.X, pt.Y) 292 calc = true 293 } else { 294 pxs[i*2+j] = bgpx 295 } 296 } 297 } 298 299 if !calc { 300 pixSetter.setPixel(dstb.Min.X+x, dstb.Min.Y+y, bgpx) 301 continue 302 } 303 304 cfs[0] = (1 - xq) * (1 - yq) 305 cfs[1] = xq * (1 - yq) 306 cfs[2] = (1 - xq) * yq 307 cfs[3] = xq * yq 308 309 for i := range pxs { 310 wa := pxs[i].A * cfs[i] 311 px.R += pxs[i].R * wa 312 px.G += pxs[i].G * wa 313 px.B += pxs[i].B * wa 314 px.A += wa 315 } 316 317 if px.A != 0.0 { 318 px.R /= px.A 319 px.G /= px.A 320 px.B /= px.A 321 } 322 323 pixSetter.setPixel(dstb.Min.X+x, dstb.Min.Y+y, px) 324 325 default: 326 var px pixel 327 x0, y0 := int(floorf32(xf+0.5)), int(floorf32(yf+0.5)) 328 if image.Pt(x0, y0).In(srcb) { 329 px = pixGetter.getPixel(x0, y0) 330 } else { 331 px = bgpx 332 } 333 pixSetter.setPixel(dstb.Min.X+x, dstb.Min.Y+y, px) 334 } 335 } 336 } 337 }) 338 339 return 340 } 341 342 // Rotate creates a filter that rotates an image by the given angle counter-clockwise. 343 // The angle parameter is the rotation angle in degrees. 344 // The backgroundColor parameter specifies the color of the uncovered zone after the rotation. 345 // The interpolation parameter specifies the interpolation method. 346 // Supported interpolation methods: NearestNeighborInterpolation, LinearInterpolation, CubicInterpolation. 347 // 348 // Example: 349 // 350 // g := gift.New( 351 // gift.Rotate(45, color.Black, gift.LinearInterpolation), 352 // ) 353 // dst := image.NewRGBA(g.Bounds(src.Bounds())) 354 // g.Draw(dst, src) 355 // 356 func Rotate(angle float32, backgroundColor color.Color, interpolation Interpolation) Filter { 357 return &rotateFilter{ 358 angle: angle, 359 bgcolor: backgroundColor, 360 interpolation: interpolation, 361 } 362 } 363 364 type cropFilter struct { 365 rect image.Rectangle 366 } 367 368 func (p *cropFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) { 369 b := srcBounds.Intersect(p.rect) 370 return b.Sub(b.Min) 371 } 372 373 func (p *cropFilter) Draw(dst draw.Image, src image.Image, options *Options) { 374 if options == nil { 375 options = &defaultOptions 376 } 377 378 srcb := src.Bounds().Intersect(p.rect) 379 dstb := dst.Bounds() 380 pixGetter := newPixelGetter(src) 381 pixSetter := newPixelSetter(dst) 382 383 parallelize(options.Parallelization, srcb.Min.Y, srcb.Max.Y, func(pmin, pmax int) { 384 for srcy := pmin; srcy < pmax; srcy++ { 385 for srcx := srcb.Min.X; srcx < srcb.Max.X; srcx++ { 386 dstx := dstb.Min.X + srcx - srcb.Min.X 387 dsty := dstb.Min.Y + srcy - srcb.Min.Y 388 pixSetter.setPixel(dstx, dsty, pixGetter.getPixel(srcx, srcy)) 389 } 390 } 391 }) 392 } 393 394 // Crop creates a filter that crops the specified rectangular region from an image. 395 // 396 // Example: 397 // 398 // g := gift.New( 399 // gift.Crop(image.Rect(100, 100, 200, 200)), 400 // ) 401 // dst := image.NewRGBA(g.Bounds(src.Bounds())) 402 // g.Draw(dst, src) 403 // 404 func Crop(rect image.Rectangle) Filter { 405 return &cropFilter{ 406 rect: rect, 407 } 408 } 409 410 // Anchor is the anchor point for image cropping. 411 type Anchor int 412 413 const ( 414 CenterAnchor Anchor = iota 415 TopLeftAnchor 416 TopAnchor 417 TopRightAnchor 418 LeftAnchor 419 RightAnchor 420 BottomLeftAnchor 421 BottomAnchor 422 BottomRightAnchor 423 ) 424 425 func anchorPt(b image.Rectangle, w, h int, anchor Anchor) image.Point { 426 var x, y int 427 switch anchor { 428 case TopLeftAnchor: 429 x = b.Min.X 430 y = b.Min.Y 431 case TopAnchor: 432 x = b.Min.X + (b.Dx()-w)/2 433 y = b.Min.Y 434 case TopRightAnchor: 435 x = b.Max.X - w 436 y = b.Min.Y 437 case LeftAnchor: 438 x = b.Min.X 439 y = b.Min.Y + (b.Dy()-h)/2 440 case RightAnchor: 441 x = b.Max.X - w 442 y = b.Min.Y + (b.Dy()-h)/2 443 case BottomLeftAnchor: 444 x = b.Min.X 445 y = b.Max.Y - h 446 case BottomAnchor: 447 x = b.Min.X + (b.Dx()-w)/2 448 y = b.Max.Y - h 449 case BottomRightAnchor: 450 x = b.Max.X - w 451 y = b.Max.Y - h 452 default: 453 x = b.Min.X + (b.Dx()-w)/2 454 y = b.Min.Y + (b.Dy()-h)/2 455 } 456 return image.Pt(x, y) 457 } 458 459 type cropToSizeFilter struct { 460 w, h int 461 anchor Anchor 462 } 463 464 func (p *cropToSizeFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) { 465 if p.w <= 0 || p.h <= 0 { 466 return image.Rect(0, 0, 0, 0) 467 } 468 pt := anchorPt(srcBounds, p.w, p.h, p.anchor) 469 r := image.Rect(0, 0, p.w, p.h).Add(pt) 470 b := srcBounds.Intersect(r) 471 return b.Sub(b.Min) 472 } 473 474 func (p *cropToSizeFilter) Draw(dst draw.Image, src image.Image, options *Options) { 475 if p.w <= 0 || p.h <= 0 { 476 return 477 } 478 pt := anchorPt(src.Bounds(), p.w, p.h, p.anchor) 479 r := image.Rect(0, 0, p.w, p.h).Add(pt) 480 b := src.Bounds().Intersect(r) 481 Crop(b).Draw(dst, src, options) 482 } 483 484 // CropToSize creates a filter that crops an image to the specified size using the specified anchor point. 485 func CropToSize(width, height int, anchor Anchor) Filter { 486 return &cropToSizeFilter{ 487 w: width, 488 h: height, 489 anchor: anchor, 490 } 491 }