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}