1package imaging
  2
  3import (
  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.
 11func 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.
 29func 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.
 43type Anchor int
 44
 45// Anchor point positions.
 46const (
 47	Center Anchor = iota
 48	TopLeft
 49	Top
 50	TopRight
 51	Left
 52	Right
 53	BottomLeft
 54	Bottom
 55	BottomRight
 56)
 57
 58func 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.
 94func 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.
113func 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.
123func 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.
128func 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.
152func 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//
180func 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.
235func 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}