From 1fa1a478d9f6b9394b90dd49ababfadd471eaf21 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 2 Jan 2023 07:35:14 +0530 Subject: [PATCH] Get --background working --- tools/cmd/icat/main.go | 2 +- tools/cmd/icat/native.go | 19 +- tools/utils/images/to_rgba.go | 392 ++++++++++++++++++++++++++++++++++ 3 files changed, 398 insertions(+), 15 deletions(-) create mode 100644 tools/utils/images/to_rgba.go diff --git a/tools/cmd/icat/main.go b/tools/cmd/icat/main.go index 50b0272c9..60f8d2aad 100644 --- a/tools/cmd/icat/main.go +++ b/tools/cmd/icat/main.go @@ -172,7 +172,7 @@ func on_initialize() (string, error) { keep_going.Store(true) screen_size = sz if !opts.DetectSupport && num_of_items > 0 { - num_workers := utils.Max(1, utils.Min(num_of_items, runtime.NumCPU())) + num_workers := utils.Max(1, utils.Min(num_of_items, runtime.GOMAXPROCS(0))) for i := 0; i < num_workers; i++ { go run_worker() } diff --git a/tools/cmd/icat/native.go b/tools/cmd/icat/native.go index 433149e08..2fa7fb7a0 100644 --- a/tools/cmd/icat/native.go +++ b/tools/cmd/icat/native.go @@ -5,7 +5,7 @@ package icat import ( "fmt" "image" - "kitty/tools/utils" + "kitty/tools/utils/images" "kitty/tools/utils/shm" "github.com/disintegration/imaging" @@ -22,29 +22,20 @@ func add_frame(imgd *image_data, img image.Image) { } b := img.Bounds() f := image_frame{width: b.Dx(), height: b.Dy()} - has_non_black_background := remove_alpha != nil && (remove_alpha.R != 0 || remove_alpha.G != 0 || remove_alpha.B != 0) + dest_rect := image.Rect(0, 0, f.width, f.height) var rgba *image.NRGBA m, err := shm.CreateTemp("icat-*", uint64(f.width*f.height*4)) if err != nil { - if has_non_black_background { - rgba = imaging.New(b.Dx(), b.Dy(), remove_alpha) - } else { - rgba = image.NewNRGBA(image.Rect(0, 0, f.width, f.height)) - } + rgba = image.NewNRGBA(dest_rect) } else { rgba = &image.NRGBA{ Pix: m.Slice(), Stride: 4 * f.width, - Rect: image.Rect(0, 0, f.width, f.height), + Rect: dest_rect, } f.shm = m - if has_non_black_background { - utils.Memset(m.Slice(), remove_alpha.R, remove_alpha.G, remove_alpha.B, remove_alpha.A) - } else { - utils.Memset(m.Slice()) - } } - imaging.PasteCenter(rgba, img) + images.PasteCenter(rgba, img, remove_alpha) imgd.format_uppercase = "RGBA" f.in_memory_bytes = rgba.Pix imgd.frames = append(imgd.frames, &f) diff --git a/tools/utils/images/to_rgba.go b/tools/utils/images/to_rgba.go new file mode 100644 index 000000000..0b1c0f185 --- /dev/null +++ b/tools/utils/images/to_rgba.go @@ -0,0 +1,392 @@ +// License: GPLv3 Copyright: 2023, Kovid Goyal, + +package images + +import ( + "fmt" + "image" + "image/color" + "runtime" + "sync" +) + +var _ = fmt.Print + +// parallel processes the data in separate goroutines. +func parallel(start, stop int, fn func(<-chan int)) { + count := stop - start + if count < 1 { + return + } + + procs := runtime.GOMAXPROCS(0) + if procs > count { + procs = count + } + + c := make(chan int, count) + for i := start; i < stop; i++ { + c <- i + } + close(c) + + var wg sync.WaitGroup + for i := 0; i < procs; i++ { + wg.Add(1) + go func() { + defer wg.Done() + fn(c) + }() + } + wg.Wait() +} + +type scanner struct { + image image.Image + w, h int + palette []color.NRGBA +} + +func newScanner(img image.Image) *scanner { + s := &scanner{ + image: img, + w: img.Bounds().Dx(), + h: img.Bounds().Dy(), + } + if img, ok := img.(*image.Paletted); ok { + s.palette = make([]color.NRGBA, len(img.Palette)) + for i := 0; i < len(img.Palette); i++ { + s.palette[i] = color.NRGBAModel.Convert(img.Palette[i]).(color.NRGBA) + } + } + return s +} + +// scan scans the given rectangular region of the image into dst. +func (s *scanner) scan(x1, y1, x2, y2 int, dst []uint8) { + switch img := s.image.(type) { + case *image.NRGBA: + size := (x2 - x1) * 4 + j := 0 + i := y1*img.Stride + x1*4 + if size == 4 { + for y := y1; y < y2; y++ { + d := dst[j : j+4 : j+4] + s := img.Pix[i : i+4 : i+4] + d[0] = s[0] + d[1] = s[1] + d[2] = s[2] + d[3] = s[3] + j += size + i += img.Stride + } + } else { + for y := y1; y < y2; y++ { + copy(dst[j:j+size], img.Pix[i:i+size]) + j += size + i += img.Stride + } + } + + case *image.NRGBA64: + j := 0 + for y := y1; y < y2; y++ { + i := y*img.Stride + x1*8 + for x := x1; x < x2; x++ { + s := img.Pix[i : i+8 : i+8] + d := dst[j : j+4 : j+4] + d[0] = s[0] + d[1] = s[2] + d[2] = s[4] + d[3] = s[6] + j += 4 + i += 8 + } + } + + case *image.RGBA: + j := 0 + for y := y1; y < y2; y++ { + i := y*img.Stride + x1*4 + for x := x1; x < x2; x++ { + d := dst[j : j+4 : j+4] + a := img.Pix[i+3] + switch a { + case 0: + d[0] = 0 + d[1] = 0 + d[2] = 0 + d[3] = a + case 0xff: + s := img.Pix[i : i+4 : i+4] + d[0] = s[0] + d[1] = s[1] + d[2] = s[2] + d[3] = a + default: + s := img.Pix[i : i+4 : i+4] + r16 := uint16(s[0]) + g16 := uint16(s[1]) + b16 := uint16(s[2]) + a16 := uint16(a) + d[0] = uint8(r16 * 0xff / a16) + d[1] = uint8(g16 * 0xff / a16) + d[2] = uint8(b16 * 0xff / a16) + d[3] = a + } + j += 4 + i += 4 + } + } + + case *image.RGBA64: + j := 0 + for y := y1; y < y2; y++ { + i := y*img.Stride + x1*8 + for x := x1; x < x2; x++ { + s := img.Pix[i : i+8 : i+8] + d := dst[j : j+4 : j+4] + a := s[6] + switch a { + case 0: + d[0] = 0 + d[1] = 0 + d[2] = 0 + case 0xff: + d[0] = s[0] + d[1] = s[2] + d[2] = s[4] + default: + r32 := uint32(s[0])<<8 | uint32(s[1]) + g32 := uint32(s[2])<<8 | uint32(s[3]) + b32 := uint32(s[4])<<8 | uint32(s[5]) + a32 := uint32(s[6])<<8 | uint32(s[7]) + d[0] = uint8((r32 * 0xffff / a32) >> 8) + d[1] = uint8((g32 * 0xffff / a32) >> 8) + d[2] = uint8((b32 * 0xffff / a32) >> 8) + } + d[3] = a + j += 4 + i += 8 + } + } + + case *image.Gray: + j := 0 + for y := y1; y < y2; y++ { + i := y*img.Stride + x1 + for x := x1; x < x2; x++ { + c := img.Pix[i] + d := dst[j : j+4 : j+4] + d[0] = c + d[1] = c + d[2] = c + d[3] = 0xff + j += 4 + i++ + } + } + + case *image.Gray16: + j := 0 + for y := y1; y < y2; y++ { + i := y*img.Stride + x1*2 + for x := x1; x < x2; x++ { + c := img.Pix[i] + d := dst[j : j+4 : j+4] + d[0] = c + d[1] = c + d[2] = c + d[3] = 0xff + j += 4 + i += 2 + } + } + + case *image.YCbCr: + j := 0 + x1 += img.Rect.Min.X + x2 += img.Rect.Min.X + y1 += img.Rect.Min.Y + y2 += img.Rect.Min.Y + + hy := img.Rect.Min.Y / 2 + hx := img.Rect.Min.X / 2 + for y := y1; y < y2; y++ { + iy := (y-img.Rect.Min.Y)*img.YStride + (x1 - img.Rect.Min.X) + + var yBase int + switch img.SubsampleRatio { + case image.YCbCrSubsampleRatio444, image.YCbCrSubsampleRatio422: + yBase = (y - img.Rect.Min.Y) * img.CStride + case image.YCbCrSubsampleRatio420, image.YCbCrSubsampleRatio440: + yBase = (y/2 - hy) * img.CStride + } + + for x := x1; x < x2; x++ { + var ic int + switch img.SubsampleRatio { + case image.YCbCrSubsampleRatio444, image.YCbCrSubsampleRatio440: + ic = yBase + (x - img.Rect.Min.X) + case image.YCbCrSubsampleRatio422, image.YCbCrSubsampleRatio420: + ic = yBase + (x/2 - hx) + default: + ic = img.COffset(x, y) + } + + yy1 := int32(img.Y[iy]) * 0x10101 + cb1 := int32(img.Cb[ic]) - 128 + cr1 := int32(img.Cr[ic]) - 128 + + r := yy1 + 91881*cr1 + if uint32(r)&0xff000000 == 0 { + r >>= 16 + } else { + r = ^(r >> 31) + } + + g := yy1 - 22554*cb1 - 46802*cr1 + if uint32(g)&0xff000000 == 0 { + g >>= 16 + } else { + g = ^(g >> 31) + } + + b := yy1 + 116130*cb1 + if uint32(b)&0xff000000 == 0 { + b >>= 16 + } else { + b = ^(b >> 31) + } + + d := dst[j : j+4 : j+4] + d[0] = uint8(r) + d[1] = uint8(g) + d[2] = uint8(b) + d[3] = 0xff + + iy++ + j += 4 + } + } + + case *image.Paletted: + j := 0 + for y := y1; y < y2; y++ { + i := y*img.Stride + x1 + for x := x1; x < x2; x++ { + c := s.palette[img.Pix[i]] + d := dst[j : j+4 : j+4] + d[0] = c.R + d[1] = c.G + d[2] = c.B + d[3] = c.A + j += 4 + i++ + } + } + + default: + j := 0 + b := s.image.Bounds() + x1 += b.Min.X + x2 += b.Min.X + y1 += b.Min.Y + y2 += b.Min.Y + for y := y1; y < y2; y++ { + for x := x1; x < x2; x++ { + r16, g16, b16, a16 := s.image.At(x, y).RGBA() + d := dst[j : j+4 : j+4] + switch a16 { + case 0xffff: + d[0] = uint8(r16 >> 8) + d[1] = uint8(g16 >> 8) + d[2] = uint8(b16 >> 8) + d[3] = 0xff + case 0: + d[0] = 0 + d[1] = 0 + d[2] = 0 + d[3] = 0 + default: + d[0] = uint8(((r16 * 0xffff) / a16) >> 8) + d[1] = uint8(((g16 * 0xffff) / a16) >> 8) + d[2] = uint8(((b16 * 0xffff) / a16) >> 8) + d[3] = uint8(a16 >> 8) + } + j += 4 + } + } + } +} + +func run_paste(bytes_per_pixel int, background *image.NRGBA, img image.Image, pos image.Point, postprocess func([]byte)) { + pos = pos.Sub(background.Bounds().Min) + pasteRect := image.Rectangle{Min: pos, Max: pos.Add(img.Bounds().Size())} + interRect := pasteRect.Intersect(background.Bounds()) + if interRect.Empty() { + return + } + src := newScanner(img) + parallel(interRect.Min.Y, interRect.Max.Y, func(ys <-chan int) { + for y := range ys { + x1 := interRect.Min.X - pasteRect.Min.X + x2 := interRect.Max.X - pasteRect.Min.X + y1 := y - pasteRect.Min.Y + y2 := y1 + 1 + i1 := y*background.Stride + interRect.Min.X*bytes_per_pixel + i2 := i1 + interRect.Dx()*bytes_per_pixel + dst := background.Pix[i1:i2] + src.scan(x1, y1, x2, y2, dst) + postprocess(dst) + } + }) + +} + +func paste_nrgba_onto_opaque(background *image.NRGBA, img image.Image, pos image.Point, bgcol *color.NRGBA) { + if bgcol == nil { + run_paste(4, background, img, pos, func([]byte) {}) + return + } + bg := [3]float64{float64(bgcol.R), float64(bgcol.G), float64(bgcol.B)} + run_paste(4, background, img, pos, func(dst []byte) { + for len(dst) > 0 { + a := float64(dst[3]) / 255.0 + for i := range dst[:3] { + // uint8() automatically converts floats greater than 255 but less than 256 to 255 + dst[i] = uint8(float64(dst[i])*a + bg[i]*(1-a)) + } + dst[3] = 255 + dst = dst[4:] + } + + }) +} + +// Paste pastes the img image to the background image at the specified position. Optionally composing onto the specified opaque color. +func Paste(background image.Image, img image.Image, pos image.Point, opaque_bg *color.NRGBA) { + switch background.(type) { + case *image.NRGBA: + paste_nrgba_onto_opaque(background.(*image.NRGBA), img, pos, opaque_bg) + default: + panic("Unsupported background image type") + } +} + +// PasteCenter pastes the img image to the center of the background image. Optionally composing onto the specified opaque color. +func PasteCenter(background image.Image, img image.Image, opaque_bg *color.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 + + Paste(background, img, image.Pt(x0, y0), opaque_bg) +}