tools.go raw

   1  package imaging
   2  
   3  import (
   4  	"bytes"
   5  	"image"
   6  	"image/color"
   7  	"math"
   8  )
   9  
  10  // New creates a new image with the specified width and height, and fills it with the specified color.
  11  func New(width, height int, fillColor color.Color) *image.NRGBA {
  12  	if width <= 0 || height <= 0 {
  13  		return &image.NRGBA{}
  14  	}
  15  
  16  	c := color.NRGBAModel.Convert(fillColor).(color.NRGBA)
  17  	if (c == color.NRGBA{0, 0, 0, 0}) {
  18  		return image.NewNRGBA(image.Rect(0, 0, width, height))
  19  	}
  20  
  21  	return &image.NRGBA{
  22  		Pix:    bytes.Repeat([]byte{c.R, c.G, c.B, c.A}, width*height),
  23  		Stride: 4 * width,
  24  		Rect:   image.Rect(0, 0, width, height),
  25  	}
  26  }
  27  
  28  // Clone returns a copy of the given image.
  29  func Clone(img image.Image) *image.NRGBA {
  30  	src := newScanner(img)
  31  	dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
  32  	size := src.w * 4
  33  	parallel(0, src.h, func(ys <-chan int) {
  34  		for y := range ys {
  35  			i := y * dst.Stride
  36  			src.scan(0, y, src.w, y+1, dst.Pix[i:i+size])
  37  		}
  38  	})
  39  	return dst
  40  }
  41  
  42  // Anchor is the anchor point for image alignment.
  43  type Anchor int
  44  
  45  // Anchor point positions.
  46  const (
  47  	Center Anchor = iota
  48  	TopLeft
  49  	Top
  50  	TopRight
  51  	Left
  52  	Right
  53  	BottomLeft
  54  	Bottom
  55  	BottomRight
  56  )
  57  
  58  func anchorPt(b image.Rectangle, w, h int, anchor Anchor) image.Point {
  59  	var x, y int
  60  	switch anchor {
  61  	case TopLeft:
  62  		x = b.Min.X
  63  		y = b.Min.Y
  64  	case Top:
  65  		x = b.Min.X + (b.Dx()-w)/2
  66  		y = b.Min.Y
  67  	case TopRight:
  68  		x = b.Max.X - w
  69  		y = b.Min.Y
  70  	case Left:
  71  		x = b.Min.X
  72  		y = b.Min.Y + (b.Dy()-h)/2
  73  	case Right:
  74  		x = b.Max.X - w
  75  		y = b.Min.Y + (b.Dy()-h)/2
  76  	case BottomLeft:
  77  		x = b.Min.X
  78  		y = b.Max.Y - h
  79  	case Bottom:
  80  		x = b.Min.X + (b.Dx()-w)/2
  81  		y = b.Max.Y - h
  82  	case BottomRight:
  83  		x = b.Max.X - w
  84  		y = b.Max.Y - h
  85  	default:
  86  		x = b.Min.X + (b.Dx()-w)/2
  87  		y = b.Min.Y + (b.Dy()-h)/2
  88  	}
  89  	return image.Pt(x, y)
  90  }
  91  
  92  // Crop cuts out a rectangular region with the specified bounds
  93  // from the image and returns the cropped image.
  94  func Crop(img image.Image, rect image.Rectangle) *image.NRGBA {
  95  	r := rect.Intersect(img.Bounds()).Sub(img.Bounds().Min)
  96  	if r.Empty() {
  97  		return &image.NRGBA{}
  98  	}
  99  	src := newScanner(img)
 100  	dst := image.NewNRGBA(image.Rect(0, 0, r.Dx(), r.Dy()))
 101  	rowSize := r.Dx() * 4
 102  	parallel(r.Min.Y, r.Max.Y, func(ys <-chan int) {
 103  		for y := range ys {
 104  			i := (y - r.Min.Y) * dst.Stride
 105  			src.scan(r.Min.X, y, r.Max.X, y+1, dst.Pix[i:i+rowSize])
 106  		}
 107  	})
 108  	return dst
 109  }
 110  
 111  // CropAnchor cuts out a rectangular region with the specified size
 112  // from the image using the specified anchor point and returns the cropped image.
 113  func CropAnchor(img image.Image, width, height int, anchor Anchor) *image.NRGBA {
 114  	srcBounds := img.Bounds()
 115  	pt := anchorPt(srcBounds, width, height, anchor)
 116  	r := image.Rect(0, 0, width, height).Add(pt)
 117  	b := srcBounds.Intersect(r)
 118  	return Crop(img, b)
 119  }
 120  
 121  // CropCenter cuts out a rectangular region with the specified size
 122  // from the center of the image and returns the cropped image.
 123  func CropCenter(img image.Image, width, height int) *image.NRGBA {
 124  	return CropAnchor(img, width, height, Center)
 125  }
 126  
 127  // Paste pastes the img image to the background image at the specified position and returns the combined image.
 128  func Paste(background, img image.Image, pos image.Point) *image.NRGBA {
 129  	dst := Clone(background)
 130  	pos = pos.Sub(background.Bounds().Min)
 131  	pasteRect := image.Rectangle{Min: pos, Max: pos.Add(img.Bounds().Size())}
 132  	interRect := pasteRect.Intersect(dst.Bounds())
 133  	if interRect.Empty() {
 134  		return dst
 135  	}
 136  	src := newScanner(img)
 137  	parallel(interRect.Min.Y, interRect.Max.Y, func(ys <-chan int) {
 138  		for y := range ys {
 139  			x1 := interRect.Min.X - pasteRect.Min.X
 140  			x2 := interRect.Max.X - pasteRect.Min.X
 141  			y1 := y - pasteRect.Min.Y
 142  			y2 := y1 + 1
 143  			i1 := y*dst.Stride + interRect.Min.X*4
 144  			i2 := i1 + interRect.Dx()*4
 145  			src.scan(x1, y1, x2, y2, dst.Pix[i1:i2])
 146  		}
 147  	})
 148  	return dst
 149  }
 150  
 151  // PasteCenter pastes the img image to the center of the background image and returns the combined image.
 152  func PasteCenter(background, img image.Image) *image.NRGBA {
 153  	bgBounds := background.Bounds()
 154  	bgW := bgBounds.Dx()
 155  	bgH := bgBounds.Dy()
 156  	bgMinX := bgBounds.Min.X
 157  	bgMinY := bgBounds.Min.Y
 158  
 159  	centerX := bgMinX + bgW/2
 160  	centerY := bgMinY + bgH/2
 161  
 162  	x0 := centerX - img.Bounds().Dx()/2
 163  	y0 := centerY - img.Bounds().Dy()/2
 164  
 165  	return Paste(background, img, image.Pt(x0, y0))
 166  }
 167  
 168  // Overlay draws the img image over the background image at given position
 169  // and returns the combined image. Opacity parameter is the opacity of the img
 170  // image layer, used to compose the images, it must be from 0.0 to 1.0.
 171  //
 172  // Examples:
 173  //
 174  //	// Draw spriteImage over backgroundImage at the given position (x=50, y=50).
 175  //	dstImage := imaging.Overlay(backgroundImage, spriteImage, image.Pt(50, 50), 1.0)
 176  //
 177  //	// Blend two opaque images of the same size.
 178  //	dstImage := imaging.Overlay(imageOne, imageTwo, image.Pt(0, 0), 0.5)
 179  //
 180  func Overlay(background, img image.Image, pos image.Point, opacity float64) *image.NRGBA {
 181  	opacity = math.Min(math.Max(opacity, 0.0), 1.0) // Ensure 0.0 <= opacity <= 1.0.
 182  	dst := Clone(background)
 183  	pos = pos.Sub(background.Bounds().Min)
 184  	pasteRect := image.Rectangle{Min: pos, Max: pos.Add(img.Bounds().Size())}
 185  	interRect := pasteRect.Intersect(dst.Bounds())
 186  	if interRect.Empty() {
 187  		return dst
 188  	}
 189  	src := newScanner(img)
 190  	parallel(interRect.Min.Y, interRect.Max.Y, func(ys <-chan int) {
 191  		scanLine := make([]uint8, interRect.Dx()*4)
 192  		for y := range ys {
 193  			x1 := interRect.Min.X - pasteRect.Min.X
 194  			x2 := interRect.Max.X - pasteRect.Min.X
 195  			y1 := y - pasteRect.Min.Y
 196  			y2 := y1 + 1
 197  			src.scan(x1, y1, x2, y2, scanLine)
 198  			i := y*dst.Stride + interRect.Min.X*4
 199  			j := 0
 200  			for x := interRect.Min.X; x < interRect.Max.X; x++ {
 201  				d := dst.Pix[i : i+4 : i+4]
 202  				r1 := float64(d[0])
 203  				g1 := float64(d[1])
 204  				b1 := float64(d[2])
 205  				a1 := float64(d[3])
 206  
 207  				s := scanLine[j : j+4 : j+4]
 208  				r2 := float64(s[0])
 209  				g2 := float64(s[1])
 210  				b2 := float64(s[2])
 211  				a2 := float64(s[3])
 212  
 213  				coef2 := opacity * a2 / 255
 214  				coef1 := (1 - coef2) * a1 / 255
 215  				coefSum := coef1 + coef2
 216  				coef1 /= coefSum
 217  				coef2 /= coefSum
 218  
 219  				d[0] = uint8(r1*coef1 + r2*coef2)
 220  				d[1] = uint8(g1*coef1 + g2*coef2)
 221  				d[2] = uint8(b1*coef1 + b2*coef2)
 222  				d[3] = uint8(math.Min(a1+a2*opacity*(255-a1)/255, 255))
 223  
 224  				i += 4
 225  				j += 4
 226  			}
 227  		}
 228  	})
 229  	return dst
 230  }
 231  
 232  // OverlayCenter overlays the img image to the center of the background image and
 233  // returns the combined image. Opacity parameter is the opacity of the img
 234  // image layer, used to compose the images, it must be from 0.0 to 1.0.
 235  func OverlayCenter(background, img image.Image, opacity float64) *image.NRGBA {
 236  	bgBounds := background.Bounds()
 237  	bgW := bgBounds.Dx()
 238  	bgH := bgBounds.Dy()
 239  	bgMinX := bgBounds.Min.X
 240  	bgMinY := bgBounds.Min.Y
 241  
 242  	centerX := bgMinX + bgW/2
 243  	centerY := bgMinY + bgH/2
 244  
 245  	x0 := centerX - img.Bounds().Dx()/2
 246  	y0 := centerY - img.Bounds().Dy()/2
 247  
 248  	return Overlay(background, img, image.Point{x0, y0}, opacity)
 249  }
 250