git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/imaging/tools.go (about) 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 if r.Eq(img.Bounds().Sub(img.Bounds().Min)) { 100 return Clone(img) 101 } 102 103 src := newScanner(img) 104 dst := image.NewNRGBA(image.Rect(0, 0, r.Dx(), r.Dy())) 105 rowSize := r.Dx() * 4 106 parallel(r.Min.Y, r.Max.Y, func(ys <-chan int) { 107 for y := range ys { 108 i := (y - r.Min.Y) * dst.Stride 109 src.scan(r.Min.X, y, r.Max.X, y+1, dst.Pix[i:i+rowSize]) 110 } 111 }) 112 return dst 113 } 114 115 // CropAnchor cuts out a rectangular region with the specified size 116 // from the image using the specified anchor point and returns the cropped image. 117 func CropAnchor(img image.Image, width, height int, anchor Anchor) *image.NRGBA { 118 srcBounds := img.Bounds() 119 pt := anchorPt(srcBounds, width, height, anchor) 120 r := image.Rect(0, 0, width, height).Add(pt) 121 b := srcBounds.Intersect(r) 122 return Crop(img, b) 123 } 124 125 // CropCenter cuts out a rectangular region with the specified size 126 // from the center of the image and returns the cropped image. 127 func CropCenter(img image.Image, width, height int) *image.NRGBA { 128 return CropAnchor(img, width, height, Center) 129 } 130 131 // Paste pastes the img image to the background image at the specified position and returns the combined image. 132 func Paste(background, img image.Image, pos image.Point) *image.NRGBA { 133 dst := Clone(background) 134 pos = pos.Sub(background.Bounds().Min) 135 pasteRect := image.Rectangle{Min: pos, Max: pos.Add(img.Bounds().Size())} 136 interRect := pasteRect.Intersect(dst.Bounds()) 137 if interRect.Empty() { 138 return dst 139 } 140 if interRect.Eq(dst.Bounds()) { 141 return Clone(img) 142 } 143 144 src := newScanner(img) 145 parallel(interRect.Min.Y, interRect.Max.Y, func(ys <-chan int) { 146 for y := range ys { 147 x1 := interRect.Min.X - pasteRect.Min.X 148 x2 := interRect.Max.X - pasteRect.Min.X 149 y1 := y - pasteRect.Min.Y 150 y2 := y1 + 1 151 i1 := y*dst.Stride + interRect.Min.X*4 152 i2 := i1 + interRect.Dx()*4 153 src.scan(x1, y1, x2, y2, dst.Pix[i1:i2]) 154 } 155 }) 156 return dst 157 } 158 159 // PasteCenter pastes the img image to the center of the background image and returns the combined image. 160 func PasteCenter(background, img image.Image) *image.NRGBA { 161 bgBounds := background.Bounds() 162 bgW := bgBounds.Dx() 163 bgH := bgBounds.Dy() 164 bgMinX := bgBounds.Min.X 165 bgMinY := bgBounds.Min.Y 166 167 centerX := bgMinX + bgW/2 168 centerY := bgMinY + bgH/2 169 170 x0 := centerX - img.Bounds().Dx()/2 171 y0 := centerY - img.Bounds().Dy()/2 172 173 return Paste(background, img, image.Pt(x0, y0)) 174 } 175 176 // Overlay draws the img image over the background image at given position 177 // and returns the combined image. Opacity parameter is the opacity of the img 178 // image layer, used to compose the images, it must be from 0.0 to 1.0. 179 // 180 // Examples: 181 // 182 // // Draw spriteImage over backgroundImage at the given position (x=50, y=50). 183 // dstImage := imaging.Overlay(backgroundImage, spriteImage, image.Pt(50, 50), 1.0) 184 // 185 // // Blend two opaque images of the same size. 186 // dstImage := imaging.Overlay(imageOne, imageTwo, image.Pt(0, 0), 0.5) 187 func Overlay(background, img image.Image, pos image.Point, opacity float64) *image.NRGBA { 188 opacity = math.Min(math.Max(opacity, 0.0), 1.0) // Ensure 0.0 <= opacity <= 1.0. 189 dst := Clone(background) 190 pos = pos.Sub(background.Bounds().Min) 191 pasteRect := image.Rectangle{Min: pos, Max: pos.Add(img.Bounds().Size())} 192 interRect := pasteRect.Intersect(dst.Bounds()) 193 if interRect.Empty() { 194 return dst 195 } 196 src := newScanner(img) 197 parallel(interRect.Min.Y, interRect.Max.Y, func(ys <-chan int) { 198 scanLine := make([]uint8, interRect.Dx()*4) 199 for y := range ys { 200 x1 := interRect.Min.X - pasteRect.Min.X 201 x2 := interRect.Max.X - pasteRect.Min.X 202 y1 := y - pasteRect.Min.Y 203 y2 := y1 + 1 204 src.scan(x1, y1, x2, y2, scanLine) 205 i := y*dst.Stride + interRect.Min.X*4 206 j := 0 207 for x := interRect.Min.X; x < interRect.Max.X; x++ { 208 d := dst.Pix[i : i+4 : i+4] 209 r1 := float64(d[0]) 210 g1 := float64(d[1]) 211 b1 := float64(d[2]) 212 a1 := float64(d[3]) 213 214 s := scanLine[j : j+4 : j+4] 215 r2 := float64(s[0]) 216 g2 := float64(s[1]) 217 b2 := float64(s[2]) 218 a2 := float64(s[3]) 219 220 coef2 := opacity * a2 / 255 221 coef1 := (1 - coef2) * a1 / 255 222 coefSum := coef1 + coef2 223 coef1 /= coefSum 224 coef2 /= coefSum 225 226 d[0] = uint8(r1*coef1 + r2*coef2) 227 d[1] = uint8(g1*coef1 + g2*coef2) 228 d[2] = uint8(b1*coef1 + b2*coef2) 229 d[3] = uint8(math.Min(a1+a2*opacity*(255-a1)/255, 255)) 230 231 i += 4 232 j += 4 233 } 234 } 235 }) 236 return dst 237 } 238 239 // OverlayCenter overlays the img image to the center of the background image and 240 // returns the combined image. Opacity parameter is the opacity of the img 241 // image layer, used to compose the images, it must be from 0.0 to 1.0. 242 func OverlayCenter(background, img image.Image, opacity float64) *image.NRGBA { 243 bgBounds := background.Bounds() 244 bgW := bgBounds.Dx() 245 bgH := bgBounds.Dy() 246 bgMinX := bgBounds.Min.X 247 bgMinY := bgBounds.Min.Y 248 249 centerX := bgMinX + bgW/2 250 centerY := bgMinY + bgH/2 251 252 x0 := centerX - img.Bounds().Dx()/2 253 y0 := centerY - img.Bounds().Dy()/2 254 255 return Overlay(background, img, image.Point{x0, y0}, opacity) 256 }