From 143fd6e4ddfb1df5e201fab61ad4d88fa6172c6e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 2 Jan 2023 10:57:55 +0530 Subject: [PATCH] Start work on transmitting RGB images --- tools/utils/images/to_rgb.go | 141 +++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 tools/utils/images/to_rgb.go diff --git a/tools/utils/images/to_rgb.go b/tools/utils/images/to_rgb.go new file mode 100644 index 000000000..a305d500e --- /dev/null +++ b/tools/utils/images/to_rgb.go @@ -0,0 +1,141 @@ +// License: GPLv3 Copyright: 2023, Kovid Goyal, + +package images + +import ( + "fmt" + "image" + "image/color" +) + +var _ = fmt.Print + +type NRGBColor struct { + R, G, B uint8 +} + +func (c NRGBColor) RGBA() (r, g, b, a uint32) { + r = uint32(c.R) + r |= r << 8 + g = uint32(c.G) + g |= g << 8 + b = uint32(c.B) + b |= b << 8 + a = 65280 // ( 255 << 8 ) + return +} + +// NRGB is an in-memory image whose At method returns NRGBColor values. +type NRGB struct { + // Pix holds the image's pixels, in R, G, B, A order. The pixel at + // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*4]. + Pix []uint8 + // Stride is the Pix stride (in bytes) between vertically adjacent pixels. + Stride int + // Rect is the image's bounds. + Rect image.Rectangle +} + +func nrgbModel(c color.Color) color.Color { + if _, ok := c.(NRGBColor); ok { + return c + } + r, g, b, a := c.RGBA() + if a == 0xffff { + return NRGBColor{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8)} + } + if a == 0 { + return NRGBColor{0, 0, 0} + } + // Since Color.RGBA returns an alpha-premultiplied color, we should have r <= a && g <= a && b <= a. + r = (r * 0xffff) / a + g = (g * 0xffff) / a + b = (b * 0xffff) / a + return NRGBColor{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8)} +} + +var NRGBModel color.Model = color.ModelFunc(nrgbModel) + +func (p *NRGB) ColorModel() color.Model { return NRGBModel } + +func (p *NRGB) Bounds() image.Rectangle { return p.Rect } + +func (p *NRGB) At(x, y int) color.Color { + return p.NRGBAt(x, y) +} + +func (p *NRGB) NRGBAt(x, y int) NRGBColor { + if !(image.Point{x, y}.In(p.Rect)) { + return NRGBColor{} + } + i := p.PixOffset(x, y) + s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857 + return NRGBColor{s[0], s[1], s[2]} +} + +// PixOffset returns the index of the first element of Pix that corresponds to +// the pixel at (x, y). +func (p *NRGB) PixOffset(x, y int) int { + return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4 +} + +func (p *NRGB) Set(x, y int, c color.Color) { + if !(image.Point{x, y}.In(p.Rect)) { + return + } + i := p.PixOffset(x, y) + c1 := NRGBModel.Convert(c).(NRGBColor) + s := p.Pix[i : i+3 : i+3] // Small cap improves performance, see https://golang.org/issue/27857 + s[0] = c1.R + s[1] = c1.G + s[2] = c1.B +} + +func (p *NRGB) SetRGBA64(x, y int, c color.RGBA64) { + if !(image.Point{x, y}.In(p.Rect)) { + return + } + r, g, b, a := uint32(c.R), uint32(c.G), uint32(c.B), uint32(c.A) + if (a != 0) && (a != 0xffff) { + r = (r * 0xffff) / a + g = (g * 0xffff) / a + b = (b * 0xffff) / a + } + i := p.PixOffset(x, y) + s := p.Pix[i : i+3 : i+3] // Small cap improves performance, see https://golang.org/issue/27857 + s[0] = uint8(r >> 8) + s[1] = uint8(g >> 8) + s[2] = uint8(b >> 8) +} + +func (p *NRGB) SetNRGBA(x, y int, c color.NRGBA) { + if !(image.Point{x, y}.In(p.Rect)) { + return + } + i := p.PixOffset(x, y) + s := p.Pix[i : i+3 : i+3] // Small cap improves performance, see https://golang.org/issue/27857 + s[0] = c.R + s[1] = c.G + s[2] = c.B +} + +// SubImage returns an image representing the portion of the image p visible +// through r. The returned value shares pixels with the original image. +func (p *NRGB) SubImage(r image.Rectangle) image.Image { + r = r.Intersect(p.Rect) + // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside + // either r1 or r2 if the intersection is empty. Without explicitly checking for + // this, the Pix[i:] expression below can panic. + if r.Empty() { + return &NRGB{} + } + i := p.PixOffset(r.Min.X, r.Min.Y) + return &NRGB{ + Pix: p.Pix[i:], + Stride: p.Stride, + Rect: r, + } +} + +// Opaque scans the entire image and reports whether it is fully opaque. +func (p *NRGB) Opaque() bool { return true }