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  }