diff --git a/tools/cmd/icat/main.go b/tools/cmd/icat/main.go index 2f075c607..b8255ecb7 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.GOMAXPROCS(0))) + num_workers := utils.Max(1, utils.Min(num_of_items, runtime.NumCPU())) 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 009d0db9e..f041af27a 100644 --- a/tools/cmd/icat/native.go +++ b/tools/cmd/icat/native.go @@ -27,7 +27,7 @@ func resize_frame(imgd *image_data, img image.Image) (image.Image, image.Rectang return img, image.Rect(newleft, newtop, newleft+new_width, newtop+new_height) } -func add_frame(imgd *image_data, img image.Image) *image_frame { +func add_frame(ctx *images.Context, imgd *image_data, img image.Image) *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 @@ -71,16 +71,16 @@ func add_frame(imgd *image_data, img image.Image) *image_frame { f.in_memory_bytes = rgba.Pix final_img = rgba } - images.PasteCenter(final_img, img, remove_alpha) + ctx.PasteCenter(final_img, img, remove_alpha) imgd.frames = append(imgd.frames, &f) if flip { - images.FlipPixelsV(bytes_per_pixel, f.width, f.height, f.in_memory_bytes) + ctx.FlipPixelsV(bytes_per_pixel, f.width, f.height, f.in_memory_bytes) if f.height < imgd.canvas_height { f.top = (2*imgd.canvas_height - f.height - f.top) % imgd.canvas_height } } if flop { - images.FlipPixelsH(bytes_per_pixel, f.width, f.height, f.in_memory_bytes) + ctx.FlipPixelsH(bytes_per_pixel, f.width, f.height, f.in_memory_bytes) if f.width < imgd.canvas_width { f.left = (2*imgd.canvas_width - f.width - f.left) % imgd.canvas_width } @@ -104,7 +104,7 @@ func scale_image(imgd *image_data) { } } -func load_one_frame_image(imgd *image_data, src *opened_input) (img image.Image, err error) { +func load_one_frame_image(ctx *images.Context, imgd *image_data, src *opened_input) (img image.Image, err error) { img, err = imaging.Decode(src.file, imaging.AutoOrientation(true)) src.Rewind() if err != nil { @@ -118,7 +118,7 @@ func load_one_frame_image(imgd *image_data, src *opened_input) (img image.Image, return } -func add_gif_frames(imgd *image_data, gf *gif.GIF) error { +func add_gif_frames(ctx *images.Context, imgd *image_data, gf *gif.GIF) error { // 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 @@ -133,7 +133,7 @@ func add_gif_frames(imgd *image_data, gf *gif.GIF) error { scale_image(imgd) anchor_frame := 1 for i, paletted_img := range gf.Image { - frame := add_frame(imgd, paletted_img) + frame := add_frame(ctx, imgd, paletted_img) frame.delay_ms = utils.Max(min_gap, gf.Delay[i]) * 10 if frame.delay_ms == 0 { frame.delay_ms = -1 @@ -155,6 +155,7 @@ func add_gif_frames(imgd *image_data, gf *gif.GIF) error { } 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) @@ -162,16 +163,16 @@ func render_image_with_go(imgd *image_data, src *opened_input) (err error) { if err != nil { return fmt.Errorf("Failed to decode GIF file with error: %w", err) } - err = add_gif_frames(imgd, gif_frames) + err = add_gif_frames(&ctx, imgd, gif_frames) if err != nil { return err } default: - img, err := load_one_frame_image(imgd, src) + img, err := load_one_frame_image(&ctx, imgd, src) if err != nil { return err } - add_frame(imgd, img) + add_frame(&ctx, imgd, img) } return nil } diff --git a/tools/utils/images/to_rgb.go b/tools/utils/images/to_rgb.go index 3a88542ce..b8f1e85f2 100644 --- a/tools/utils/images/to_rgb.go +++ b/tools/utils/images/to_rgb.go @@ -404,14 +404,14 @@ func (s *scanner_rgb) scan(x1, y1, x2, y2 int, dst []uint8) { } } -func paste_nrgb_onto_opaque(background *NRGB, img image.Image, pos image.Point, bgcol *NRGBColor) { +func (self *Context) paste_nrgb_onto_opaque(background *NRGB, img image.Image, pos image.Point, bgcol *NRGBColor) { bg := NRGBColor{} if bgcol != nil { bg = *bgcol } src := newScannerRGB(img, bg) - run_paste(src, background, pos, func(dst []byte) {}) + self.run_paste(src, background, pos, func(dst []byte) {}) } func NewNRGB(r image.Rectangle) *NRGB { diff --git a/tools/utils/images/to_rgba.go b/tools/utils/images/to_rgba.go index 42f825772..c075354cc 100644 --- a/tools/utils/images/to_rgba.go +++ b/tools/utils/images/to_rgba.go @@ -7,41 +7,10 @@ import ( "image" "image/color" "math" - "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 @@ -330,7 +299,7 @@ type Scanner interface { bounds() image.Rectangle } -func run_paste(src Scanner, background image.Image, pos image.Point, postprocess func([]byte)) { +func (self *Context) run_paste(src Scanner, background image.Image, pos image.Point, postprocess func([]byte)) { pos = pos.Sub(background.Bounds().Min) pasteRect := image.Rectangle{Min: pos, Max: pos.Add(src.bounds().Size())} interRect := pasteRect.Intersect(background.Bounds()) @@ -352,7 +321,7 @@ func run_paste(src Scanner, background image.Image, pos image.Point, postprocess default: panic(fmt.Sprintf("Unsupported image type: %v", v)) } - parallel(interRect.Min.Y, interRect.Max.Y, func(ys <-chan int) { + self.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 @@ -368,14 +337,14 @@ func run_paste(src Scanner, background image.Image, pos image.Point, postprocess } -func paste_nrgba_onto_opaque(background *image.NRGBA, img image.Image, pos image.Point, bgcol *NRGBColor) { +func (self *Context) paste_nrgba_onto_opaque(background *image.NRGBA, img image.Image, pos image.Point, bgcol *NRGBColor) { src := newScanner(img) if bgcol == nil { - run_paste(src, background, pos, func([]byte) {}) + self.run_paste(src, background, pos, func([]byte) {}) return } bg := [3]float64{float64(bgcol.R), float64(bgcol.G), float64(bgcol.B)} - run_paste(src, background, pos, func(dst []byte) { + self.run_paste(src, background, pos, func(dst []byte) { for len(dst) > 0 { a := float64(dst[3]) / 255.0 for i := range dst[:3] { @@ -390,19 +359,19 @@ func paste_nrgba_onto_opaque(background *image.NRGBA, img image.Image, pos image } // 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 *NRGBColor) { +func (self *Context) Paste(background image.Image, img image.Image, pos image.Point, opaque_bg *NRGBColor) { switch background.(type) { case *image.NRGBA: - paste_nrgba_onto_opaque(background.(*image.NRGBA), img, pos, opaque_bg) + self.paste_nrgba_onto_opaque(background.(*image.NRGBA), img, pos, opaque_bg) case *NRGB: - paste_nrgb_onto_opaque(background.(*NRGB), img, pos, opaque_bg) + self.paste_nrgb_onto_opaque(background.(*NRGB), 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 *NRGBColor) { +func (self *Context) PasteCenter(background image.Image, img image.Image, opaque_bg *NRGBColor) { bgBounds := background.Bounds() bgW := bgBounds.Dx() bgH := bgBounds.Dy() @@ -415,7 +384,7 @@ func PasteCenter(background image.Image, img image.Image, opaque_bg *NRGBColor) x0 := centerX - img.Bounds().Dx()/2 y0 := centerY - img.Bounds().Dy()/2 - Paste(background, img, image.Pt(x0, y0), opaque_bg) + self.Paste(background, img, image.Pt(x0, y0), opaque_bg) } func FitImage(width, height, pwidth, pheight int) (final_width int, final_height int) { diff --git a/tools/utils/images/transforms.go b/tools/utils/images/transforms.go index 3d64956f0..921f67a6a 100644 --- a/tools/utils/images/transforms.go +++ b/tools/utils/images/transforms.go @@ -25,9 +25,9 @@ func reverse_row(bytes_per_pixel int, pix []uint8) { } } -func FlipPixelsH(bytes_per_pixel, width, height int, pix []uint8) { +func (self *Context) FlipPixelsH(bytes_per_pixel, width, height int, pix []uint8) { stride := bytes_per_pixel * width - parallel(0, height, func(ys <-chan int) { + self.Parallel(0, height, func(ys <-chan int) { for y := range ys { i := y * stride reverse_row(bytes_per_pixel, pix[i:i+stride]) @@ -35,10 +35,10 @@ func FlipPixelsH(bytes_per_pixel, width, height int, pix []uint8) { }) } -func FlipPixelsV(bytes_per_pixel, width, height int, pix []uint8) { +func (self *Context) FlipPixelsV(bytes_per_pixel, width, height int, pix []uint8) { stride := bytes_per_pixel * width num := height / 2 - parallel(0, num, func(ys <-chan int) { + self.Parallel(0, num, func(ys <-chan int) { for y := range ys { upper := y lower := height - 1 - y diff --git a/tools/utils/images/utils.go b/tools/utils/images/utils.go new file mode 100644 index 000000000..6ab2631ad --- /dev/null +++ b/tools/utils/images/utils.go @@ -0,0 +1,56 @@ +// License: GPLv3 Copyright: 2023, Kovid Goyal, + +package images + +import ( + "fmt" + "runtime" + "sync" + "sync/atomic" +) + +var _ = fmt.Print + +type Context struct { + num_of_threads atomic.Int32 +} + +func (self *Context) SetNumberOfThreads(n int) { + self.num_of_threads.Store(int32(n)) +} + +func (self *Context) NumberOfThreads() int { + return int(self.num_of_threads.Load()) +} + +// parallel processes the data in separate goroutines. +func (self *Context) Parallel(start, stop int, fn func(<-chan int)) { + count := stop - start + if count < 1 { + return + } + + procs := self.NumberOfThreads() + if procs <= 0 { + procs = runtime.NumCPU() + } + 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() +}