142 lines
3.6 KiB
Go
142 lines
3.6 KiB
Go
// License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
|
|
|
|
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 }
|