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  }