Files
opencloud/vendor/github.com/kovidgoyal/imaging/tools.go
dependabot[bot] 85361fca67 build(deps): bump github.com/kovidgoyal/imaging from 1.7.2 to 1.8.17
Bumps [github.com/kovidgoyal/imaging](https://github.com/kovidgoyal/imaging) from 1.7.2 to 1.8.17.
- [Release notes](https://github.com/kovidgoyal/imaging/releases)
- [Changelog](https://github.com/kovidgoyal/imaging/blob/master/.goreleaser.yaml)
- [Commits](https://github.com/kovidgoyal/imaging/compare/v1.7.2...v1.8.17)

---
updated-dependencies:
- dependency-name: github.com/kovidgoyal/imaging
  dependency-version: 1.8.17
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-26 14:46:30 +01:00

523 lines
13 KiB
Go

package imaging
import (
"bytes"
"fmt"
"image"
"image/color"
"image/draw"
"math"
"slices"
"github.com/kovidgoyal/imaging/nrgb"
"github.com/kovidgoyal/imaging/nrgba"
)
var _ = fmt.Println
// New creates a new image with the specified width and height, and fills it with the specified color.
func New(width, height int, fillColor color.Color) *image.NRGBA {
if width <= 0 || height <= 0 {
return &image.NRGBA{}
}
c := color.NRGBAModel.Convert(fillColor).(color.NRGBA)
if (c == color.NRGBA{0, 0, 0, 0}) {
return image.NewNRGBA(image.Rect(0, 0, width, height))
}
return &image.NRGBA{
Pix: bytes.Repeat([]byte{c.R, c.G, c.B, c.A}, width*height),
Stride: 4 * width,
Rect: image.Rect(0, 0, width, height),
}
}
// Clone returns a copy of the given image.
func Clone(img image.Image) *image.NRGBA {
w, h := img.Bounds().Dx(), img.Bounds().Dy()
src := nrgba.NewNRGBAScanner(img)
dst := image.NewNRGBA(image.Rect(0, 0, w, h))
size := w * 4
if err := run_in_parallel_over_range(0, func(start, limit int) {
for y := start; y < limit; y++ {
i := y * dst.Stride
src.Scan(0, y, w, y+1, dst.Pix[i:i+size])
}
}, 0, h); err != nil {
panic(err)
}
return dst
}
func ClonePreservingOrigin(img image.Image) *image.NRGBA {
w, h := img.Bounds().Dx(), img.Bounds().Dy()
src := nrgba.NewNRGBAScanner(img)
dst := image.NewNRGBA(img.Bounds())
size := w * 4
if err := run_in_parallel_over_range(0, func(start, limit int) {
for y := start; y < limit; y++ {
i := y * dst.Stride
src.Scan(0, y, w, y+1, dst.Pix[i:i+size])
}
}, 0, h); err != nil {
panic(err)
}
return dst
}
func AsNRGBA(src image.Image) *image.NRGBA {
if nrgba, ok := src.(*image.NRGBA); ok {
return nrgba
}
return ClonePreservingOrigin(src)
}
func AsNRGB(src image.Image) *NRGB {
if nrgb, ok := src.(*NRGB); ok {
return nrgb
}
sc := nrgb.NewNRGBScanner(src, nrgb.Color{})
dst := sc.NewImage(src.Bounds()).(*nrgb.Image)
w, h := src.Bounds().Dx(), src.Bounds().Dy()
if err := run_in_parallel_over_range(0, func(start, limit int) {
for y := start; y < limit; y++ {
sc.ScanRow(0, y, w, y+1, dst, y)
}
}, 0, h); err != nil {
panic(err)
}
return dst
}
// Clone an image preserving it's type for all known image types or returning an NRGBA64 image otherwise
func ClonePreservingType(src image.Image) image.Image {
switch src := src.(type) {
case *image.RGBA:
dst := *src
dst.Pix = slices.Clone(src.Pix)
return &dst
case *image.RGBA64:
dst := *src
dst.Pix = slices.Clone(src.Pix)
return &dst
case *image.NRGBA:
dst := *src
dst.Pix = slices.Clone(src.Pix)
return &dst
case *NRGB:
dst := *src
dst.Pix = slices.Clone(src.Pix)
return &dst
case *image.NRGBA64:
dst := *src
dst.Pix = slices.Clone(src.Pix)
return &dst
case *image.Gray:
dst := *src
dst.Pix = slices.Clone(src.Pix)
return &dst
case *image.Gray16:
dst := *src
dst.Pix = slices.Clone(src.Pix)
return &dst
case *image.Alpha:
dst := *src
dst.Pix = slices.Clone(src.Pix)
return &dst
case *image.Alpha16:
dst := *src
dst.Pix = slices.Clone(src.Pix)
return &dst
case *image.CMYK:
dst := *src
dst.Pix = slices.Clone(src.Pix)
return &dst
case *image.Paletted:
dst := *src
dst.Pix = slices.Clone(src.Pix)
dst.Palette = slices.Clone(src.Palette)
return &dst
case *image.YCbCr:
dst := *src
dst.Y = slices.Clone(src.Y)
dst.Cb = slices.Clone(src.Cb)
dst.Cr = slices.Clone(src.Cr)
return &dst
case *image.NYCbCrA:
dst := *src
dst.Y = slices.Clone(src.Y)
dst.Cb = slices.Clone(src.Cb)
dst.Cr = slices.Clone(src.Cr)
dst.A = slices.Clone(src.A)
return &dst
// For any other image type, fall back to a generic copy.
// This creates an NRGBA image, which may not be the original type,
// but ensures the image data is preserved.
default:
b := src.Bounds()
dst := image.NewNRGBA64(b)
draw.Draw(dst, b, src, b.Min, draw.Src)
return dst
}
}
// Ensure image has origin at (0, 0). Note that this destroys the original
// image and returns a new image with the same data, but origin shifted.
func NormalizeOrigin(src image.Image) image.Image {
r := src.Bounds()
if r.Min.X == 0 && r.Min.Y == 0 {
return src
}
r = image.Rect(0, 0, r.Dx(), r.Dy())
switch src := src.(type) {
case *image.RGBA:
dst := *src
*src = image.RGBA{}
dst.Rect = r
return &dst
case *image.RGBA64:
dst := *src
*src = image.RGBA64{}
dst.Rect = r
return &dst
case *image.NRGBA:
dst := *src
*src = image.NRGBA{}
dst.Rect = r
return &dst
case *NRGB:
dst := *src
*src = NRGB{}
dst.Rect = r
return &dst
case *image.NRGBA64:
dst := *src
*src = image.NRGBA64{}
dst.Rect = r
return &dst
case *image.Gray:
dst := *src
*src = image.Gray{}
dst.Rect = r
return &dst
case *image.Gray16:
dst := *src
*src = image.Gray16{}
dst.Rect = r
return &dst
case *image.Alpha:
dst := *src
*src = image.Alpha{}
dst.Rect = r
return &dst
case *image.Alpha16:
dst := *src
*src = image.Alpha16{}
dst.Rect = r
return &dst
case *image.CMYK:
dst := *src
*src = image.CMYK{}
dst.Rect = r
return &dst
case *image.Paletted:
dst := *src
*src = image.Paletted{}
dst.Rect = r
return &dst
case *image.YCbCr:
dst := *src
*src = image.YCbCr{}
dst.Rect = r
return &dst
case *image.NYCbCrA:
dst := *src
*src = image.NYCbCrA{}
dst.Rect = r
return &dst
// For any other image type, fall back to a generic copy.
// This creates an NRGBA image, which may not be the original type,
// but ensures the image data is preserved.
default:
b := src.Bounds()
dst := image.NewNRGBA64(b)
draw.Draw(dst, b, src, b.Min, draw.Src)
dst.Rect = r
return dst
}
}
// Anchor is the anchor point for image alignment.
type Anchor int
// Anchor point positions.
const (
Center Anchor = iota
TopLeft
Top
TopRight
Left
Right
BottomLeft
Bottom
BottomRight
)
func anchorPt(b image.Rectangle, w, h int, anchor Anchor) image.Point {
var x, y int
switch anchor {
case TopLeft:
x = b.Min.X
y = b.Min.Y
case Top:
x = b.Min.X + (b.Dx()-w)/2
y = b.Min.Y
case TopRight:
x = b.Max.X - w
y = b.Min.Y
case Left:
x = b.Min.X
y = b.Min.Y + (b.Dy()-h)/2
case Right:
x = b.Max.X - w
y = b.Min.Y + (b.Dy()-h)/2
case BottomLeft:
x = b.Min.X
y = b.Max.Y - h
case Bottom:
x = b.Min.X + (b.Dx()-w)/2
y = b.Max.Y - h
case BottomRight:
x = b.Max.X - w
y = b.Max.Y - h
default:
x = b.Min.X + (b.Dx()-w)/2
y = b.Min.Y + (b.Dy()-h)/2
}
return image.Pt(x, y)
}
// Crop cuts out a rectangular region with the specified bounds
// from the image and returns the cropped image.
func Crop(img image.Image, rect image.Rectangle) *image.NRGBA {
r := rect.Intersect(img.Bounds()).Sub(img.Bounds().Min)
if r.Empty() {
return &image.NRGBA{}
}
if r.Eq(img.Bounds().Sub(img.Bounds().Min)) {
return Clone(img)
}
src := nrgba.NewNRGBAScanner(img)
dst := image.NewNRGBA(image.Rect(0, 0, r.Dx(), r.Dy()))
rowSize := r.Dx() * 4
if err := run_in_parallel_over_range(0, func(start, limit int) {
for y := start; y < limit; y++ {
i := (y - r.Min.Y) * dst.Stride
src.Scan(r.Min.X, y, r.Max.X, y+1, dst.Pix[i:i+rowSize])
}
}, r.Min.Y, r.Max.Y); err != nil {
panic(err)
}
return dst
}
// CropAnchor cuts out a rectangular region with the specified size
// from the image using the specified anchor point and returns the cropped image.
func CropAnchor(img image.Image, width, height int, anchor Anchor) *image.NRGBA {
srcBounds := img.Bounds()
pt := anchorPt(srcBounds, width, height, anchor)
r := image.Rect(0, 0, width, height).Add(pt)
b := srcBounds.Intersect(r)
return Crop(img, b)
}
// CropCenter cuts out a rectangular region with the specified size
// from the center of the image and returns the cropped image.
func CropCenter(img image.Image, width, height int) *image.NRGBA {
return CropAnchor(img, width, height, Center)
}
// Paste pastes the img image to the background image at the specified position and returns the combined image.
func Paste(background, img image.Image, pos image.Point) *image.NRGBA {
dst := Clone(background)
pos = pos.Sub(background.Bounds().Min)
pasteRect := image.Rectangle{Min: pos, Max: pos.Add(img.Bounds().Size())}
interRect := pasteRect.Intersect(dst.Bounds())
if interRect.Empty() {
return dst
}
if interRect.Eq(dst.Bounds()) {
return Clone(img)
}
src := nrgba.NewNRGBAScanner(img)
if err := run_in_parallel_over_range(0, func(start, limit int) {
for y := start; y < limit; y++ {
x1 := interRect.Min.X - pasteRect.Min.X
x2 := interRect.Max.X - pasteRect.Min.X
y1 := y - pasteRect.Min.Y
y2 := y1 + 1
i1 := y*dst.Stride + interRect.Min.X*4
i2 := i1 + interRect.Dx()*4
src.Scan(x1, y1, x2, y2, dst.Pix[i1:i2])
}
}, interRect.Min.Y, interRect.Max.Y); err != nil {
panic(err)
}
return dst
}
// PasteCenter pastes the img image to the center of the background image and returns the combined image.
func PasteCenter(background, img image.Image) *image.NRGBA {
bgBounds := background.Bounds()
bgW := bgBounds.Dx()
bgH := bgBounds.Dy()
bgMinX := bgBounds.Min.X
bgMinY := bgBounds.Min.Y
centerX := bgMinX + bgW/2
centerY := bgMinY + bgH/2
x0 := centerX - img.Bounds().Dx()/2
y0 := centerY - img.Bounds().Dy()/2
return Paste(background, img, image.Pt(x0, y0))
}
// Overlay draws the img image over the background image at given position
// and returns the combined image. Opacity parameter is the opacity of the img
// image layer, used to compose the images, it must be from 0.0 to 1.0.
//
// Examples:
//
// // Draw spriteImage over backgroundImage at the given position (x=50, y=50).
// dstImage := imaging.Overlay(backgroundImage, spriteImage, image.Pt(50, 50), 1.0)
//
// // Blend two opaque images of the same size.
// dstImage := imaging.Overlay(imageOne, imageTwo, image.Pt(0, 0), 0.5)
func Overlay(background, img image.Image, pos image.Point, opacity float64) *image.NRGBA {
opacity = math.Min(math.Max(opacity, 0.0), 1.0) // Ensure 0.0 <= opacity <= 1.0.
dst := Clone(background)
pos = pos.Sub(background.Bounds().Min)
pasteRect := image.Rectangle{Min: pos, Max: pos.Add(img.Bounds().Size())}
interRect := pasteRect.Intersect(dst.Bounds())
if interRect.Empty() {
return dst
}
src := nrgba.NewNRGBAScanner(img)
if err := run_in_parallel_over_range(0, func(start, limit int) {
scanLine := make([]uint8, interRect.Dx()*4)
for y := start; y < limit; y++ {
x1 := interRect.Min.X - pasteRect.Min.X
x2 := interRect.Max.X - pasteRect.Min.X
y1 := y - pasteRect.Min.Y
y2 := y1 + 1
src.Scan(x1, y1, x2, y2, scanLine)
i := y*dst.Stride + interRect.Min.X*4
j := 0
for x := interRect.Min.X; x < interRect.Max.X; x++ {
d := dst.Pix[i : i+4 : i+4]
r1 := float64(d[0])
g1 := float64(d[1])
b1 := float64(d[2])
a1 := float64(d[3])
s := scanLine[j : j+4 : j+4]
r2 := float64(s[0])
g2 := float64(s[1])
b2 := float64(s[2])
a2 := float64(s[3])
coef2 := opacity * a2 / 255
coef1 := (1 - coef2) * a1 / 255
coefSum := coef1 + coef2
coef1 /= coefSum
coef2 /= coefSum
d[0] = uint8(r1*coef1 + r2*coef2)
d[1] = uint8(g1*coef1 + g2*coef2)
d[2] = uint8(b1*coef1 + b2*coef2)
d[3] = uint8(math.Min(a1+a2*opacity*(255-a1)/255, 255))
i += 4
j += 4
}
}
}, interRect.Min.Y, interRect.Max.Y); err != nil {
panic(err)
}
return dst
}
// OverlayCenter overlays the img image to the center of the background image and
// returns the combined image. Opacity parameter is the opacity of the img
// image layer, used to compose the images, it must be from 0.0 to 1.0.
func OverlayCenter(background, img image.Image, opacity float64) *image.NRGBA {
bgBounds := background.Bounds()
bgW := bgBounds.Dx()
bgH := bgBounds.Dy()
bgMinX := bgBounds.Min.X
bgMinY := bgBounds.Min.Y
centerX := bgMinX + bgW/2
centerY := bgMinY + bgH/2
x0 := centerX - img.Bounds().Dx()/2
y0 := centerY - img.Bounds().Dy()/2
return Overlay(background, img, image.Point{x0, y0}, opacity)
}
// Paste the image onto the specified background color.
func PasteOntoBackground(img image.Image, bg color.Color) image.Image {
if IsOpaque(img) {
return img
}
_, _, _, a := bg.RGBA()
bg_is_opaque := a == 0xffff
var base draw.Image
if bg_is_opaque {
// use premult as its faster and will be converted to NRGB anyway
base = image.NewRGBA(img.Bounds())
} else {
base = image.NewNRGBA(img.Bounds())
}
bgi := image.NewUniform(bg)
draw.Draw(base, base.Bounds(), bgi, image.Point{}, draw.Src)
draw.Draw(base, base.Bounds(), img, img.Bounds().Min, draw.Over)
if bg_is_opaque {
return AsNRGB(base)
}
return base
}
// Return contiguous non-premultiplied RGB pixel data for this image with 8 bits per channel
func AsRGBData8(img image.Image) (pix []uint8) {
b := img.Bounds()
n := AsNRGB(img)
if n.Stride == b.Dx()*3 {
return n.Pix
}
pix = make([]uint8, 0, b.Dx()*b.Dy()*3)
for y := range b.Dy() {
pix = append(pix, n.Pix[y*n.Stride:y*(n.Stride+1)]...)
}
return pix
}
// Return contiguous non-premultiplied RGBA pixel data for this image with 8 bits per channel
func AsRGBAData8(img image.Image) (pix []uint8) {
b := img.Bounds()
n := AsNRGBA(img)
if n.Stride == b.Dx()*4 {
return n.Pix
}
pix = make([]uint8, 0, b.Dx()*b.Dy()*4)
for y := range b.Dy() {
pix = append(pix, n.Pix[y*n.Stride:y*(n.Stride+1)]...)
}
return pix
}