1/*Package clone provides image cloning function.*/
2package clone
3
4import (
5	"image"
6	"image/draw"
7
8	"github.com/anthonynsimon/bild/parallel"
9)
10
11// PadMethod is the method used to fill padded pixels.
12type PadMethod uint8
13
14const (
15	// NoFill leaves the padded pixels empty.
16	NoFill = iota
17	// EdgeExtend extends the closest edge pixel.
18	EdgeExtend
19	// EdgeWrap wraps around the pixels of an image.
20	EdgeWrap
21)
22
23// AsRGBA returns an RGBA copy of the supplied image.
24func AsRGBA(src image.Image) *image.RGBA {
25	bounds := src.Bounds()
26	img := image.NewRGBA(bounds)
27	draw.Draw(img, bounds, src, bounds.Min, draw.Src)
28	return img
29}
30
31// AsShallowRGBA tries to cast to image.RGBA to get reference. Otherwise makes a copy
32func AsShallowRGBA(src image.Image) *image.RGBA {
33	if rgba, ok := src.(*image.RGBA); ok {
34		return rgba
35	}
36	return AsRGBA(src)
37}
38
39// Pad returns an RGBA copy of the src image parameter with its edges padded
40// using the supplied PadMethod.
41// Parameter padX and padY correspond to the amount of padding to be applied
42// on each side.
43// Parameter m is the PadMethod to fill the new pixels.
44//
45// Usage example:
46//
47//		result := Pad(img, 5,5, EdgeExtend)
48//
49func Pad(src image.Image, padX, padY int, m PadMethod) *image.RGBA {
50	var result *image.RGBA
51
52	switch m {
53	case EdgeExtend:
54		result = extend(src, padX, padY)
55	case NoFill:
56		result = noFill(src, padX, padY)
57	case EdgeWrap:
58		result = wrap(src, padX, padY)
59	default:
60		result = extend(src, padX, padY)
61	}
62
63	return result
64}
65
66func noFill(img image.Image, padX, padY int) *image.RGBA {
67	srcBounds := img.Bounds()
68	paddedW, paddedH := srcBounds.Dx()+2*padX, srcBounds.Dy()+2*padY
69	newBounds := image.Rect(0, 0, paddedW, paddedH)
70	fillBounds := image.Rect(padX, padY, padX+srcBounds.Dx(), padY+srcBounds.Dy())
71
72	dst := image.NewRGBA(newBounds)
73	draw.Draw(dst, fillBounds, img, srcBounds.Min, draw.Src)
74
75	return dst
76}
77
78func extend(img image.Image, padX, padY int) *image.RGBA {
79	dst := noFill(img, padX, padY)
80	paddedW, paddedH := dst.Bounds().Dx(), dst.Bounds().Dy()
81
82	parallel.Line(paddedH, func(start, end int) {
83		for y := start; y < end; y++ {
84			iy := y
85			if iy < padY {
86				iy = padY
87			} else if iy >= paddedH-padY {
88				iy = paddedH - padY - 1
89			}
90
91			for x := 0; x < paddedW; x++ {
92				ix := x
93				if ix < padX {
94					ix = padX
95				} else if x >= paddedW-padX {
96					ix = paddedW - padX - 1
97				} else if iy == y {
98					// This only enters if we are not in a y-padded area or
99					// x-padded area, so nothing to extend here.
100					// So simply jump to the next padded-x index.
101					x = paddedW - padX - 1
102					continue
103				}
104
105				dstPos := y*dst.Stride + x*4
106				edgePos := iy*dst.Stride + ix*4
107
108				dst.Pix[dstPos+0] = dst.Pix[edgePos+0]
109				dst.Pix[dstPos+1] = dst.Pix[edgePos+1]
110				dst.Pix[dstPos+2] = dst.Pix[edgePos+2]
111				dst.Pix[dstPos+3] = dst.Pix[edgePos+3]
112			}
113		}
114	})
115
116	return dst
117}
118
119func wrap(img image.Image, padX, padY int) *image.RGBA {
120	dst := noFill(img, padX, padY)
121	paddedW, paddedH := dst.Bounds().Dx(), dst.Bounds().Dy()
122
123	parallel.Line(paddedH, func(start, end int) {
124		for y := start; y < end; y++ {
125			iy := y
126			if iy < padY {
127				iy = (paddedH - padY) - ((padY - y) % (paddedH - padY*2))
128			} else if iy >= paddedH-padY {
129				iy = padY - ((padY - y) % (paddedH - padY*2))
130			}
131
132			for x := 0; x < paddedW; x++ {
133				ix := x
134				if ix < padX {
135					ix = (paddedW - padX) - ((padX - x) % (paddedW - padX*2))
136				} else if ix >= paddedW-padX {
137					ix = padX - ((padX - x) % (paddedW - padX*2))
138				} else if iy == y {
139					// This only enters if we are not in a y-padded area or
140					// x-padded area, so nothing to extend here.
141					// So simply jump to the next padded-x index.
142					x = paddedW - padX - 1
143					continue
144				}
145
146				dstPos := y*dst.Stride + x*4
147				edgePos := iy*dst.Stride + ix*4
148
149				dst.Pix[dstPos+0] = dst.Pix[edgePos+0]
150				dst.Pix[dstPos+1] = dst.Pix[edgePos+1]
151				dst.Pix[dstPos+2] = dst.Pix[edgePos+2]
152				dst.Pix[dstPos+3] = dst.Pix[edgePos+3]
153			}
154		}
155	})
156
157	return dst
158}
159