From 1e6d67b9751f9fc1c9699bbe38b8cf1ea2c788e0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 6 Nov 2025 22:31:31 +0530 Subject: [PATCH] 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 --- docs/changelog.rst | 2 + go.mod | 6 +- go.sum | 12 ++-- kittens/choose_files/graphics.go | 3 + kittens/icat/native.go | 86 ++++++++---------------- kittens/icat/process_images.go | 3 +- kittens/icat/transmit.go | 4 +- tools/tui/graphics/collection.go | 3 + tools/utils/images/loading.go | 110 ++++++++----------------------- tools/utils/images/opaque.go | 73 -------------------- tools/utils/images/to_rgb.go | 3 +- 11 files changed, 77 insertions(+), 228 deletions(-) delete mode 100644 tools/utils/images/opaque.go diff --git a/docs/changelog.rst b/docs/changelog.rst index f8fe020d0..295f4811a 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -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] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/go.mod b/go.mod index 3544fd590..7dd4009cb 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 2ac9e1f70..d73f4743a 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/kittens/choose_files/graphics.go b/kittens/choose_files/graphics.go index bf0abe5c7..69c5bbd26 100644 --- a/kittens/choose_files/graphics.go +++ b/kittens/choose_files/graphics.go @@ -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)) } diff --git a/kittens/icat/native.go b/kittens/icat/native.go index 7c8788512..4ff597227 100644 --- a/kittens/icat/native.go +++ b/kittens/icat/native.go @@ -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 } diff --git a/kittens/icat/process_images.go b/kittens/icat/process_images.go index e7fcbf9d2..c9957f279 100644 --- a/kittens/icat/process_images.go +++ b/kittens/icat/process_images.go @@ -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 } diff --git a/kittens/icat/transmit.go b/kittens/icat/transmit.go index e1a885ca8..a5c4a3003 100644 --- a/kittens/icat/transmit.go +++ b/kittens/icat/transmit.go @@ -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)) } diff --git a/tools/tui/graphics/collection.go b/tools/tui/graphics/collection.go index 6dbc86b45..0e77af35a 100644 --- a/tools/tui/graphics/collection.go +++ b/tools/tui/graphics/collection.go @@ -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) diff --git a/tools/utils/images/loading.go b/tools/utils/images/loading.go index 448306cc7..b8fb9f0d9 100644 --- a/tools/utils/images/loading.go +++ b/tools/utils/images/loading.go @@ -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 } diff --git a/tools/utils/images/opaque.go b/tools/utils/images/opaque.go deleted file mode 100644 index 6ba1f10d9..000000000 --- a/tools/utils/images/opaque.go +++ /dev/null @@ -1,73 +0,0 @@ -// License: GPLv3 Copyright: 2023, Kovid Goyal, - -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 -} diff --git a/tools/utils/images/to_rgb.go b/tools/utils/images/to_rgb.go index 675b4a734..5ed027762 100644 --- a/tools/utils/images/to_rgb.go +++ b/tools/utils/images/to_rgb.go @@ -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) {}) }