Update to the latest version of imaging

Gives us a bunch of new features and allows us to move a bunch of code
into imaging
This commit is contained in:
Kovid Goyal
2025-11-06 22:31:31 +05:30
parent 56b8db4bfc
commit 1e6d67b975
11 changed files with 77 additions and 228 deletions

View File

@@ -144,6 +144,8 @@ Detailed list of changes
- ssh kitten: Fix a bug where automatic login was not working (:iss:`9187`)
- icat kitten: Add support for APNG, netPBM, ICC color profiles and CCIP metadata to the builtin engine
0.44.0 [2025-11-03]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

6
go.mod
View File

@@ -12,9 +12,8 @@ require (
github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0
github.com/kovidgoyal/dbus v0.0.0-20250519011319-e811c41c0bc1
github.com/kovidgoyal/exiffix v0.0.0-20250919160812-dbef770c2032
github.com/kovidgoyal/go-parallel v1.0.1
github.com/kovidgoyal/imaging v1.7.2
github.com/kovidgoyal/go-parallel v1.1.1
github.com/kovidgoyal/imaging v1.8.2
github.com/seancfoley/ipaddress-go v1.7.1
github.com/shirou/gopsutil/v4 v4.25.10
github.com/zeebo/xxh3 v1.0.2
@@ -34,6 +33,7 @@ require (
require (
github.com/ebitengine/purego v0.9.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/kettek/apng v0.0.0-20250827064933-2bb5f5fcf253 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect

12
go.sum
View File

@@ -24,16 +24,16 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/kettek/apng v0.0.0-20250827064933-2bb5f5fcf253 h1:ar6YqPcuumkcWgAJHkmda6Q35V3OnpxeTej4iU/QFLA=
github.com/kettek/apng v0.0.0-20250827064933-2bb5f5fcf253/go.mod h1:x78/VRQYKuCftMWS0uK5e+F5RJ7S4gSlESRWI0Prl6Q=
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kovidgoyal/dbus v0.0.0-20250519011319-e811c41c0bc1 h1:rMY/hWfcVzBm6BLX6YLA+gLJEpuXBed/VP6YEkXt8R4=
github.com/kovidgoyal/dbus v0.0.0-20250519011319-e811c41c0bc1/go.mod h1:RbNG3Q1g6GUy1/WzWVx+S24m7VKyvl57vV+cr2hpt50=
github.com/kovidgoyal/exiffix v0.0.0-20250919160812-dbef770c2032 h1:TEV9lpo2a6fP1byiDsoJe2fXpvrj2itae41xMM+bEAg=
github.com/kovidgoyal/exiffix v0.0.0-20250919160812-dbef770c2032/go.mod h1:VU38Nlbvb0lbyS5YkopCZMS5HuJ5QLVJBxRWyzq79q4=
github.com/kovidgoyal/go-parallel v1.0.1 h1:nYUjN+EdpbmQjTg3N5eTUInuXTB3/1oD2vHdaMfuHoI=
github.com/kovidgoyal/go-parallel v1.0.1/go.mod h1:BJNIbe6+hxyFWv7n6oEDPj3PA5qSw5OCtf0hcVxWJiw=
github.com/kovidgoyal/imaging v1.7.2 h1:mmT6k6Az3mC6dbqdZ6Q9KQCdZFWTAQ+q97NyGZgJ/2c=
github.com/kovidgoyal/imaging v1.7.2/go.mod h1:GdkCORjfZMMGFY0Pb7TDmRhj7PDhxF/QShKukSCj0VU=
github.com/kovidgoyal/go-parallel v1.1.1 h1:1OzpNjtrUkBPq3UaqrnvOoB2F9RttSt811uiUXyI7ok=
github.com/kovidgoyal/go-parallel v1.1.1/go.mod h1:BJNIbe6+hxyFWv7n6oEDPj3PA5qSw5OCtf0hcVxWJiw=
github.com/kovidgoyal/imaging v1.8.2 h1:AYBaYSAI7W6p2CfQnAqVEB6AeiOVMJ0uUEuhuM+zuWo=
github.com/kovidgoyal/imaging v1.8.2/go.mod h1:fHutWjAiIZ3t7+YRVzuPkICFFzHTTkxgx2jSeQXKjE0=
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

View File

@@ -210,6 +210,9 @@ func (self *GraphicsHandler) transmit(lp *loop.Loop, img *images.ImageData, m *i
default:
gc.SetAction(graphics.GRT_action_frame)
gc.SetGap(int32(frame.Delay_ms))
if frame.Replace {
gc.SetCompositionMode(graphics.Overwrite)
}
if frame.Compose_onto > 0 {
gc.SetOverlaidFrame(uint64(frame.Compose_onto))
}

View File

@@ -5,16 +5,14 @@ package icat
import (
"fmt"
"image"
"image/gif"
"github.com/kovidgoyal/go-parallel"
"github.com/kovidgoyal/imaging/nrgb"
"github.com/kovidgoyal/kitty/tools/tty"
"github.com/kovidgoyal/kitty/tools/tui/graphics"
"github.com/kovidgoyal/kitty/tools/utils"
"github.com/kovidgoyal/kitty/tools/utils/images"
"github.com/kovidgoyal/kitty/tools/utils/shm"
"github.com/kovidgoyal/exiffix"
"github.com/kovidgoyal/imaging"
)
@@ -33,19 +31,19 @@ func resize_frame(imgd *image_data, img image.Image) (image.Image, image.Rectang
const shm_template = "kitty-icat-*"
func add_frame(ctx *images.Context, imgd *image_data, img image.Image) *image_frame {
func add_frame(ctx *images.Context, imgd *image_data, img image.Image, left, top int) *image_frame {
is_opaque := false
if imgd.format_uppercase == "JPEG" {
// special cased because EXIF orientation could have already changed this image to an NRGBA making IsOpaque() very slow
is_opaque = true
} else {
is_opaque = images.IsOpaque(img)
is_opaque = imaging.IsOpaque(img)
}
b := img.Bounds()
if imgd.scaled_frac.x != 0 {
img, b = resize_frame(imgd, img)
}
f := image_frame{width: b.Dx(), height: b.Dy(), number: len(imgd.frames) + 1, left: b.Min.X, top: b.Min.Y}
f := image_frame{width: b.Dx(), height: b.Dy(), number: len(imgd.frames) + 1, left: left, top: top}
dest_rect := image.Rect(0, 0, f.width, f.height)
var final_img image.Image
bytes_per_pixel := 4
@@ -55,7 +53,7 @@ func add_frame(ctx *images.Context, imgd *image_data, img image.Image) *image_fr
bytes_per_pixel = 3
m, err := shm.CreateTemp(shm_template, uint64(f.width*f.height*bytes_per_pixel))
if err != nil {
rgb = imaging.NewNRGB(dest_rect)
rgb = nrgb.NewNRGB(dest_rect)
} else {
rgb = &imaging.NRGB{Pix: m.Slice(), Stride: bytes_per_pixel * f.width, Rect: dest_rect}
f.shm = m
@@ -111,47 +109,21 @@ func scale_image(imgd *image_data) bool {
return false
}
func load_one_frame_image(imgd *image_data, src *opened_input) (img image.Image, err error) {
img, _, err = exiffix.Decode(src.file)
src.Rewind()
if err != nil {
return
}
// reset the sizes as we read EXIF tags here which could have rotated the image
imgd.canvas_width = img.Bounds().Dx()
imgd.canvas_height = img.Bounds().Dy()
set_basic_metadata(imgd)
scale_image(imgd)
return
}
var debugprintln = tty.DebugPrintln
var _ = debugprintln
func (frame *image_frame) set_disposal(anchor_frame int, disposal byte) int {
anchor_frame, frame.compose_onto = images.SetGIFFrameDisposal(frame.number, anchor_frame, disposal)
return anchor_frame
}
func (frame *image_frame) set_delay(gap, min_gap int) {
frame.delay_ms = utils.Max(min_gap, gap) * 10
if frame.delay_ms == 0 {
frame.delay_ms = -1
func add_frames(ctx *images.Context, imgd *image_data, gf *imaging.Image) {
for _, f := range gf.Frames {
frame := add_frame(ctx, imgd, f.Image, f.X, f.Y)
frame.number, frame.compose_onto = int(f.Number), int(f.ComposeOnto)
frame.replace = f.Replace
frame.delay_ms = int(f.Delay.Milliseconds())
if frame.delay_ms <= 0 {
frame.delay_ms = -1 // -1 is gapless in graphics protocol
}
}
}
func add_gif_frames(ctx *images.Context, imgd *image_data, gf *gif.GIF) error {
min_gap := images.CalcMinimumGIFGap(gf.Delay)
scale_image(imgd)
anchor_frame := 1
for i, paletted_img := range gf.Image {
frame := add_frame(ctx, imgd, paletted_img)
frame.set_delay(gf.Delay[i], min_gap)
anchor_frame = frame.set_disposal(anchor_frame, gf.Disposal[i])
}
return nil
}
func render_image_with_go(imgd *image_data, src *opened_input) (err error) {
defer func() {
if r := recover(); r != nil {
@@ -159,23 +131,19 @@ func render_image_with_go(imgd *image_data, src *opened_input) (err error) {
}
}()
ctx := images.Context{}
switch {
case imgd.format_uppercase == "GIF" && opts.Loop != 0:
gif_frames, err := gif.DecodeAll(src.file)
src.Rewind()
if err != nil {
return fmt.Errorf("Failed to decode GIF file with error: %w", err)
}
err = add_gif_frames(&ctx, imgd, gif_frames)
if err != nil {
return err
}
default:
img, err := load_one_frame_image(imgd, src)
if err != nil {
return err
}
add_frame(&ctx, imgd, img)
imgs, _, err := imaging.DecodeAll(src.file)
if err != nil {
return err
}
if imgs == nil {
return fmt.Errorf("unknown image format")
}
// Loading could auto orient and therefore change width/height, so
// re-calculate
imgd.canvas_width = int(imgs.Metadata.PixelWidth)
imgd.canvas_height = int(imgs.Metadata.PixelHeight)
set_basic_metadata(imgd)
scale_image(imgd)
add_frames(&ctx, imgd, imgs)
return nil
}

View File

@@ -6,7 +6,6 @@ import (
"bytes"
"fmt"
"image"
"image/color"
"io"
"io/fs"
"net/http"
@@ -171,8 +170,8 @@ type image_frame struct {
width, height, left, top int
transmission_format graphics.GRT_f
compose_onto int
replace bool
number int
disposal_background color.NRGBA
delay_ms int
}

View File

@@ -74,11 +74,9 @@ func gc_for_image(imgd *image_data, frame_num int, frame *image_frame) *graphics
} else {
gc.SetAction(graphics.GRT_action_frame)
gc.SetGap(int32(frame.delay_ms))
gc.SetCompositionMode(utils.IfElse(frame.replace, graphics.Overwrite, graphics.AlphaBlend))
if frame.compose_onto > 0 {
gc.SetOverlaidFrame(uint64(frame.compose_onto))
} else {
bg := (uint32(frame.disposal_background.R) << 24) | (uint32(frame.disposal_background.G) << 16) | (uint32(frame.disposal_background.B) << 8) | uint32(frame.disposal_background.A)
gc.SetBackgroundColor(bg)
}
gc.SetLeftEdge(uint64(frame.left)).SetTopEdge(uint64(frame.top))
}

View File

@@ -403,6 +403,9 @@ func (self *ImageCollection) transmit_rendering(lp *loop.Loop, r *rendering) {
if frame.Compose_onto > 0 {
gc.SetOverlaidFrame(uint64(frame.Compose_onto))
}
if frame.Replace {
gc.SetCompositionMode(Overwrite)
}
gc.SetLeftEdge(uint64(frame.Left)).SetTopEdge(uint64(frame.Top))
}
transmit(lp, r.image_id, self.temp_file_map, frame, gc)

View File

@@ -8,7 +8,6 @@ import (
"errors"
"fmt"
"image"
"image/color"
"image/gif"
"image/png"
"io"
@@ -20,10 +19,11 @@ import (
"strings"
"sync"
"github.com/kovidgoyal/imaging/nrgb"
"github.com/kovidgoyal/imaging/prism/meta/gifmeta"
"github.com/kovidgoyal/kitty/tools/utils"
"github.com/kovidgoyal/kitty/tools/utils/shm"
"github.com/kovidgoyal/exiffix"
"github.com/kovidgoyal/imaging"
)
@@ -50,15 +50,17 @@ type ImageFrame struct {
Number int // 1-based number
Compose_onto int // number of frame to compose onto
Delay_ms int32 // negative for gapless frame, zero ignored, positive is number of ms
Replace bool // do a replace rather than an alpha blend
Is_opaque bool
Img image.Image
}
type SerializableImageFrame struct {
Width, Height, Left, Top int
Number int // 1-based number
Compose_onto int // number of frame to compose onto
Delay_ms int // negative for gapless frame, zero ignored, positive is number of ms
Number int // 1-based number
Compose_onto int // number of frame to compose onto
Delay_ms int // negative for gapless frame, zero ignored, positive is number of ms
Replace bool // do a replace rather than an alpha blend
Is_opaque bool
Size int
}
@@ -71,7 +73,7 @@ func (s *ImageFrame) Serialize() SerializableImageFrame {
return SerializableImageFrame{
Width: s.Width, Height: s.Height, Left: s.Left, Top: s.Top,
Number: s.Number, Compose_onto: s.Compose_onto, Delay_ms: int(s.Delay_ms),
Is_opaque: s.Is_opaque,
Is_opaque: s.Is_opaque, Replace: s.Replace,
}
}
@@ -131,7 +133,7 @@ func (self *ImageFrame) Data() (ans []byte) {
var final_img image.Image
switch bytes_per_pixel {
case 3:
rgb := imaging.NewNRGB(dest_rect)
rgb := nrgb.NewNRGB(dest_rect)
final_img = rgb
ans = rgb.Pix
case 4:
@@ -148,14 +150,14 @@ func ImageFrameFromSerialized(s SerializableImageFrame, data []byte) (aa *ImageF
ans := ImageFrame{
Width: s.Width, Height: s.Height, Left: s.Left, Top: s.Top,
Number: s.Number, Compose_onto: s.Compose_onto, Delay_ms: int32(s.Delay_ms),
Is_opaque: s.Is_opaque,
Is_opaque: s.Is_opaque, Replace: s.Replace,
}
bytes_per_pixel := utils.IfElse(s.Is_opaque, 3, 4)
if expected := bytes_per_pixel * s.Width * s.Height; len(data) != expected {
return nil, fmt.Errorf("serialized image data has size: %d != %d", len(data), expected)
}
if s.Is_opaque {
ans.Img, err = imaging.NewNRGBWithContiguousRGBPixels(data, s.Left, s.Top, s.Width, s.Height)
ans.Img, err = nrgb.NewNRGBWithContiguousRGBPixels(data, s.Left, s.Top, s.Width, s.Height)
} else {
ans.Img, err = NewNRGBAWithContiguousRGBAPixels(data, s.Left, s.Top, s.Width, s.Height)
}
@@ -242,37 +244,6 @@ func (self *ImageData) Resize(x_frac, y_frac float64) *ImageData {
return &ans
}
func CalcMinimumGIFGap(gaps []int) int {
// Some broken GIF images have all zero gaps, browsers with their usual
// idiot ideas render these with a default 100ms gap https://bugzilla.mozilla.org/show_bug.cgi?id=125137
// Browsers actually force a 100ms gap at any zero gap frame, but that
// just means it is impossible to deliberately use zero gap frames for
// sophisticated blending, so we dont do that.
max_gap := utils.Max(0, gaps...)
min_gap := 0
if max_gap <= 0 {
min_gap = 10
}
return min_gap
}
func SetGIFFrameDisposal(number, anchor_frame int, disposal byte) (int, int) {
compose_onto := 0
if number > 1 {
switch disposal {
case gif.DisposalNone:
compose_onto = number - 1
anchor_frame = number
case gif.DisposalBackground:
// see https://github.com/golang/go/issues/20694
anchor_frame = number
case gif.DisposalPrevious:
compose_onto = anchor_frame
}
}
return anchor_frame, compose_onto
}
func MakeTempDir(template string) (ans string, err error) {
if template == "" {
template = "kitty-img-*"
@@ -287,51 +258,25 @@ func MakeTempDir(template string) (ans string, err error) {
}
// Native {{{
func (frame *ImageFrame) set_delay(min_gap, delay int) {
frame.Delay_ms = int32(max(min_gap, delay) * 10)
if frame.Delay_ms == 0 {
frame.Delay_ms = -1 // gapless frame in the graphics protocol
}
}
func open_native_gif(f io.Reader, ans *ImageData) error {
gif_frames, err := gif.DecodeAll(f)
if err != nil {
return err
}
min_gap := CalcMinimumGIFGap(gif_frames.Delay)
anchor_frame := 1
for i, paletted_img := range gif_frames.Image {
b := paletted_img.Bounds()
frame := ImageFrame{Img: paletted_img, Left: b.Min.X, Top: b.Min.Y, Width: b.Dx(), Height: b.Dy(), Number: len(ans.Frames) + 1, Is_opaque: paletted_img.Opaque()}
frame.set_delay(min_gap, gif_frames.Delay[i])
anchor_frame, frame.Compose_onto = SetGIFFrameDisposal(frame.Number, anchor_frame, gif_frames.Disposal[i])
ans.Frames = append(ans.Frames, &frame)
}
return nil
}
func OpenNativeImageFromReader(f io.ReadSeeker) (ans *ImageData, err error) {
c, fmt, err := image.DecodeConfig(f)
ic, _, err := imaging.DecodeAll(f)
if err != nil {
return nil, err
}
_, _ = f.Seek(0, io.SeekStart)
ans = &ImageData{Width: c.Width, Height: c.Height, Format_uppercase: strings.ToUpper(fmt)}
if ans.Format_uppercase == "GIF" {
err = open_native_gif(f, ans)
if err != nil {
return nil, err
ans = &ImageData{Width: int(ic.Metadata.PixelWidth), Height: int(ic.Metadata.PixelHeight), Format_uppercase: strings.ToUpper(ic.Metadata.Format.String())}
for _, f := range ic.Frames {
fr := ImageFrame{
Img: f.Image, Left: f.X, Top: f.Y, Width: f.Image.Bounds().Dx(), Height: f.Image.Bounds().Dy(),
Compose_onto: int(f.ComposeOnto), Number: int(f.Number), Delay_ms: int32(f.Delay.Milliseconds()),
Replace: f.Replace, Is_opaque: imaging.IsOpaque(f.Image),
}
} else {
img, _, err := exiffix.Decode(f)
if err != nil {
return nil, err
if fr.Delay_ms <= 0 {
fr.Delay_ms = -1 // -1 is gapless in graphics protocol
}
b := img.Bounds()
ans.Frames = []*ImageFrame{{Img: img, Left: b.Min.X, Top: b.Min.Y, Width: b.Dx(), Height: b.Dy()}}
ans.Frames[0].Is_opaque = c.ColorModel == color.YCbCrModel || c.ColorModel == color.GrayModel || c.ColorModel == color.Gray16Model || c.ColorModel == color.CMYKModel || ans.Format_uppercase == "JPEG" || ans.Format_uppercase == "JPG" || IsOpaque(img)
ans.Frames = append(ans.Frames, &fr)
}
return
}
@@ -601,7 +546,9 @@ func RenderWithMagick(path string, ro *RenderOptions, frames []IdentifyRecord) (
for i, frame := range frames {
gaps[i] = frame.Gap
}
min_gap := CalcMinimumGIFGap(gaps)
// although ImageMagick *might* be already taking care of this adjustment,
// I dont know for sure, so do it anyway.
min_gap := gifmeta.CalcMinimumGap(gaps)
for _, entry := range entries {
fname := entry.Name()
p, _, _ := strings.Cut(fname, ".")
@@ -653,7 +600,7 @@ func RenderWithMagick(path string, ro *RenderOptions, frames []IdentifyRecord) (
frame := ImageFrame{
Number: index + 1, Width: width, Height: height, Left: x, Top: y, Is_opaque: identify_data.Is_opaque,
}
frame.set_delay(min_gap, identify_data.Gap)
frame.Delay_ms = int32(max(min_gap, identify_data.Gap) * 10)
err = check_resize(&frame, df.Name())
if err != nil {
return
@@ -665,9 +612,10 @@ func RenderWithMagick(path string, ro *RenderOptions, frames []IdentifyRecord) (
return
}
slices.SortFunc(ans, func(a, b *ImageFrame) int { return a.Number - b.Number })
anchor_frame := 1
anchor_frame := uint(1)
for i, frame := range ans {
anchor_frame, frame.Compose_onto = SetGIFFrameDisposal(frame.Number, anchor_frame, byte(frames[i].Disposal))
af, co := gifmeta.SetGIFFrameDisposal(uint(frame.Number), anchor_frame, byte(frames[i].Disposal))
anchor_frame, frame.Compose_onto = af, int(co)
}
return
}

View File

@@ -1,73 +0,0 @@
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
package images
import (
"fmt"
"image"
"github.com/kovidgoyal/imaging"
)
var _ = fmt.Print
func paletted_is_opaque(p *image.Paletted) bool {
if len(p.Palette) > 256 {
return p.Opaque()
}
var is_alpha [256]bool
has_alpha := false
for i, c := range p.Palette {
_, _, _, a := c.RGBA()
if a != 0xffff {
is_alpha[i] = true
has_alpha = true
}
}
if !has_alpha {
return true
}
i0, i1 := 0, p.Rect.Dx()
for y := p.Rect.Min.Y; y < p.Rect.Max.Y; y++ {
for _, c := range p.Pix[i0:i1] {
if is_alpha[c] {
return false
}
}
i0 += p.Stride
i1 += p.Stride
}
return true
}
func IsOpaque(img image.Image) bool {
switch i := img.(type) {
case *image.RGBA:
return i.Opaque()
case *image.RGBA64:
return i.Opaque()
case *image.NRGBA:
return i.Opaque()
case *image.NRGBA64:
return i.Opaque()
case *image.Alpha:
return i.Opaque()
case *image.Alpha16:
return i.Opaque()
case *image.Gray:
return i.Opaque()
case *image.Gray16:
return i.Opaque()
case *image.CMYK:
return i.Opaque()
case *image.Paletted:
return paletted_is_opaque(i)
case *image.Uniform:
return i.Opaque()
case *image.YCbCr:
return i.Opaque()
case *imaging.NRGB:
return i.Opaque()
}
return false
}

View File

@@ -7,6 +7,7 @@ import (
"image"
"github.com/kovidgoyal/imaging"
"github.com/kovidgoyal/imaging/nrgb"
)
var _ = fmt.Print
@@ -17,6 +18,6 @@ func (self *Context) paste_nrgb_onto_opaque(background *imaging.NRGB, img image.
bg = *bgcol
}
src := imaging.NewNRGBScanner(img, bg)
src := nrgb.NewNRGBScanner(img, bg)
self.run_paste(src, background, pos, func(dst []byte) {})
}