image: add RGBA64Image interface

The new RGBA64At method is equivalent to the existing At method (and the
new SetRGBA64 method is equivalent to the existing Set method in the
image/draw package), but they can avoid allocations from converting
concrete color types to the color.Color interface type.

Also update api/go1.17.txt and doc/go1.17.html

Fixes #44808

Change-Id: I8671f3144512b1200fa373840ed6729a5d61bc35
Reviewed-on: https://go-review.googlesource.com/c/go/+/311129
Trust: Nigel Tao <nigeltao@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Rob Pike <r@golang.org>
This commit is contained in:
Nigel Tao 2021-04-18 13:38:34 +10:00
parent 9401172166
commit 86743e7d86
6 changed files with 338 additions and 1 deletions

View file

@ -28,6 +28,38 @@ pkg encoding/csv, method (*Reader) FieldPos(int) (int, int)
pkg go/build, type Context struct, ToolTags []string
pkg go/parser, const SkipObjectResolution = 64
pkg go/parser, const SkipObjectResolution Mode
pkg image, method (*Alpha) RGBA64At(int, int) color.RGBA64
pkg image, method (*Alpha) SetRGBA64(int, int, color.RGBA64)
pkg image, method (*Alpha16) RGBA64At(int, int) color.RGBA64
pkg image, method (*Alpha16) SetRGBA64(int, int, color.RGBA64)
pkg image, method (*CMYK) RGBA64At(int, int) color.RGBA64
pkg image, method (*CMYK) SetRGBA64(int, int, color.RGBA64)
pkg image, method (*Gray) RGBA64At(int, int) color.RGBA64
pkg image, method (*Gray) SetRGBA64(int, int, color.RGBA64)
pkg image, method (*Gray16) RGBA64At(int, int) color.RGBA64
pkg image, method (*Gray16) SetRGBA64(int, int, color.RGBA64)
pkg image, method (*NRGBA) RGBA64At(int, int) color.RGBA64
pkg image, method (*NRGBA) SetRGBA64(int, int, color.RGBA64)
pkg image, method (*NRGBA64) RGBA64At(int, int) color.RGBA64
pkg image, method (*NRGBA64) SetRGBA64(int, int, color.RGBA64)
pkg image, method (*NYCbCrA) RGBA64At(int, int) color.RGBA64
pkg image, method (*Paletted) RGBA64At(int, int) color.RGBA64
pkg image, method (*Paletted) SetRGBA64(int, int, color.RGBA64)
pkg image, method (*RGBA) RGBA64At(int, int) color.RGBA64
pkg image, method (*RGBA) SetRGBA64(int, int, color.RGBA64)
pkg image, method (*YCbCr) RGBA64At(int, int) color.RGBA64
pkg image, type RGBA64Image interface { At, Bounds, ColorModel, RGBA64At }
pkg image, type RGBA64Image interface, At(int, int) color.Color
pkg image, type RGBA64Image interface, Bounds() Rectangle
pkg image, type RGBA64Image interface, ColorModel() color.Model
pkg image, type RGBA64Image interface, RGBA64At(int, int) color.RGBA64
pkg image/draw, type RGBA64Image interface { At, Bounds, ColorModel, RGBA64At, Set, SetRGBA64 }
pkg image/draw, type RGBA64Image interface, At(int, int) color.Color
pkg image/draw, type RGBA64Image interface, Bounds() image.Rectangle
pkg image/draw, type RGBA64Image interface, ColorModel() color.Model
pkg image/draw, type RGBA64Image interface, RGBA64At(int, int) color.RGBA64
pkg image/draw, type RGBA64Image interface, Set(int, int, color.Color)
pkg image/draw, type RGBA64Image interface, SetRGBA64(int, int, color.RGBA64)
pkg io/fs, func FileInfoToDirEntry(FileInfo) DirEntry
pkg math, const MaxFloat64 = 1.79769e+308 // 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368
pkg math, const MaxInt = 9223372036854775807
@ -153,7 +185,7 @@ pkg time, const Layout = "01/02 03:04:05PM '06 -0700"
pkg time, const Layout ideal-string
pkg time, func UnixMicro(int64) Time
pkg time, func UnixMilli(int64) Time
pkg time, method (Time) IsDST() bool
pkg time, method (Time) GoString() string
pkg time, method (Time) IsDST() bool
pkg time, method (Time) UnixMicro() int64
pkg time, method (Time) UnixMilli() int64

View file

@ -693,6 +693,19 @@ func Foo() bool {
</dd>
</dl><!-- go/format -->
<dl id="image"><dt><a href="/pkg/image/">image</a></dt>
<dd>
<p><!-- CL 311129 -->
The concrete image types (<code>RGBA</code>, <code>Gray16</code> and so on)
now implement a new <a href="/pkg/image/#RGBA64Image"><code>RGBA64Image</code></a>
interface. Those concrete types, other than the chroma-subsampling
related <code>YCbCr</code> and <code>NYCbCrA</code>, also now implement
<a href="/pkg/image/draw/#RGBA64Image"><code>draw.RGBA64Image</code></a>, a
new interface in the <code>image/draw</code> package.
</p>
</dd>
</dl><!-- image -->
<dl id="io/fs"><dt><a href="/pkg/io/fs/">io/fs</a></dt>
<dd>
<p><!-- CL 293649 -->

View file

@ -23,6 +23,16 @@ type Image interface {
Set(x, y int, c color.Color)
}
// RGBA64Image extends both the Image and image.RGBA64Image interfaces with a
// SetRGBA64 method to change a single pixel. SetRGBA64 is equivalent to
// calling Set, but it can avoid allocations from converting concrete color
// types to the color.Color interface type.
type RGBA64Image interface {
image.RGBA64Image
Set(x, y int, c color.Color)
SetRGBA64(x, y int, c color.RGBA64)
}
// Quantizer produces a palette for an image.
type Quantizer interface {
// Quantize appends up to cap(p) - len(p) colors to p and returns the

View file

@ -45,6 +45,17 @@ type Image interface {
At(x, y int) color.Color
}
// RGBA64Image is an Image whose pixels can be converted directly to a
// color.RGBA64.
type RGBA64Image interface {
// RGBA64At returns the RGBA64 color of the pixel at (x, y). It is
// equivalent to calling At(x, y).RGBA() and converting the resulting
// 32-bit return values to a color.RGBA64, but it can avoid allocations
// from converting concrete color types to the color.Color interface type.
RGBA64At(x, y int) color.RGBA64
Image
}
// PalettedImage is an image whose colors may come from a limited palette.
// If m is a PalettedImage and m.ColorModel() returns a color.Palette p,
// then m.At(x, y) should be equivalent to p[m.ColorIndexAt(x, y)]. If m's
@ -90,6 +101,24 @@ func (p *RGBA) At(x, y int) color.Color {
return p.RGBAAt(x, y)
}
func (p *RGBA) RGBA64At(x, y int) color.RGBA64 {
if !(Point{x, y}.In(p.Rect)) {
return color.RGBA64{}
}
i := p.PixOffset(x, y)
s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
r := uint16(s[0])
g := uint16(s[1])
b := uint16(s[2])
a := uint16(s[3])
return color.RGBA64{
(r << 8) | r,
(g << 8) | g,
(b << 8) | b,
(a << 8) | a,
}
}
func (p *RGBA) RGBAAt(x, y int) color.RGBA {
if !(Point{x, y}.In(p.Rect)) {
return color.RGBA{}
@ -118,6 +147,18 @@ func (p *RGBA) Set(x, y int, c color.Color) {
s[3] = c1.A
}
func (p *RGBA) SetRGBA64(x, y int, c color.RGBA64) {
if !(Point{x, y}.In(p.Rect)) {
return
}
i := p.PixOffset(x, y)
s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
s[0] = uint8(c.R >> 8)
s[1] = uint8(c.G >> 8)
s[2] = uint8(c.B >> 8)
s[3] = uint8(c.A >> 8)
}
func (p *RGBA) SetRGBA(x, y int, c color.RGBA) {
if !(Point{x, y}.In(p.Rect)) {
return
@ -311,6 +352,11 @@ func (p *NRGBA) At(x, y int) color.Color {
return p.NRGBAAt(x, y)
}
func (p *NRGBA) RGBA64At(x, y int) color.RGBA64 {
r, g, b, a := p.NRGBAAt(x, y).RGBA()
return color.RGBA64{uint16(r), uint16(g), uint16(b), uint16(a)}
}
func (p *NRGBA) NRGBAAt(x, y int) color.NRGBA {
if !(Point{x, y}.In(p.Rect)) {
return color.NRGBA{}
@ -339,6 +385,24 @@ func (p *NRGBA) Set(x, y int, c color.Color) {
s[3] = c1.A
}
func (p *NRGBA) SetRGBA64(x, y int, c color.RGBA64) {
if !(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+4 : i+4] // 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)
s[3] = uint8(a >> 8)
}
func (p *NRGBA) SetNRGBA(x, y int, c color.NRGBA) {
if !(Point{x, y}.In(p.Rect)) {
return
@ -415,6 +479,11 @@ func (p *NRGBA64) At(x, y int) color.Color {
return p.NRGBA64At(x, y)
}
func (p *NRGBA64) RGBA64At(x, y int) color.RGBA64 {
r, g, b, a := p.NRGBA64At(x, y).RGBA()
return color.RGBA64{uint16(r), uint16(g), uint16(b), uint16(a)}
}
func (p *NRGBA64) NRGBA64At(x, y int) color.NRGBA64 {
if !(Point{x, y}.In(p.Rect)) {
return color.NRGBA64{}
@ -452,6 +521,28 @@ func (p *NRGBA64) Set(x, y int, c color.Color) {
s[7] = uint8(c1.A)
}
func (p *NRGBA64) SetRGBA64(x, y int, c color.RGBA64) {
if !(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+8 : i+8] // Small cap improves performance, see https://golang.org/issue/27857
s[0] = uint8(r >> 8)
s[1] = uint8(r)
s[2] = uint8(g >> 8)
s[3] = uint8(g)
s[4] = uint8(b >> 8)
s[5] = uint8(b)
s[6] = uint8(a >> 8)
s[7] = uint8(a)
}
func (p *NRGBA64) SetNRGBA64(x, y int, c color.NRGBA64) {
if !(Point{x, y}.In(p.Rect)) {
return
@ -532,6 +623,12 @@ func (p *Alpha) At(x, y int) color.Color {
return p.AlphaAt(x, y)
}
func (p *Alpha) RGBA64At(x, y int) color.RGBA64 {
a := uint16(p.AlphaAt(x, y).A)
a |= a << 8
return color.RGBA64{a, a, a, a}
}
func (p *Alpha) AlphaAt(x, y int) color.Alpha {
if !(Point{x, y}.In(p.Rect)) {
return color.Alpha{}
@ -554,6 +651,14 @@ func (p *Alpha) Set(x, y int, c color.Color) {
p.Pix[i] = color.AlphaModel.Convert(c).(color.Alpha).A
}
func (p *Alpha) SetRGBA64(x, y int, c color.RGBA64) {
if !(Point{x, y}.In(p.Rect)) {
return
}
i := p.PixOffset(x, y)
p.Pix[i] = uint8(c.A >> 8)
}
func (p *Alpha) SetAlpha(x, y int, c color.Alpha) {
if !(Point{x, y}.In(p.Rect)) {
return
@ -626,6 +731,11 @@ func (p *Alpha16) At(x, y int) color.Color {
return p.Alpha16At(x, y)
}
func (p *Alpha16) RGBA64At(x, y int) color.RGBA64 {
a := p.Alpha16At(x, y).A
return color.RGBA64{a, a, a, a}
}
func (p *Alpha16) Alpha16At(x, y int) color.Alpha16 {
if !(Point{x, y}.In(p.Rect)) {
return color.Alpha16{}
@ -650,6 +760,15 @@ func (p *Alpha16) Set(x, y int, c color.Color) {
p.Pix[i+1] = uint8(c1.A)
}
func (p *Alpha16) SetRGBA64(x, y int, c color.RGBA64) {
if !(Point{x, y}.In(p.Rect)) {
return
}
i := p.PixOffset(x, y)
p.Pix[i+0] = uint8(c.A >> 8)
p.Pix[i+1] = uint8(c.A)
}
func (p *Alpha16) SetAlpha16(x, y int, c color.Alpha16) {
if !(Point{x, y}.In(p.Rect)) {
return
@ -723,6 +842,12 @@ func (p *Gray) At(x, y int) color.Color {
return p.GrayAt(x, y)
}
func (p *Gray) RGBA64At(x, y int) color.RGBA64 {
gray := uint16(p.GrayAt(x, y).Y)
gray |= gray << 8
return color.RGBA64{gray, gray, gray, 0xffff}
}
func (p *Gray) GrayAt(x, y int) color.Gray {
if !(Point{x, y}.In(p.Rect)) {
return color.Gray{}
@ -745,6 +870,16 @@ func (p *Gray) Set(x, y int, c color.Color) {
p.Pix[i] = color.GrayModel.Convert(c).(color.Gray).Y
}
func (p *Gray) SetRGBA64(x, y int, c color.RGBA64) {
if !(Point{x, y}.In(p.Rect)) {
return
}
// This formula is the same as in color.grayModel.
gray := (19595*uint32(c.R) + 38470*uint32(c.G) + 7471*uint32(c.B) + 1<<15) >> 24
i := p.PixOffset(x, y)
p.Pix[i] = uint8(gray)
}
func (p *Gray) SetGray(x, y int, c color.Gray) {
if !(Point{x, y}.In(p.Rect)) {
return
@ -804,6 +939,11 @@ func (p *Gray16) At(x, y int) color.Color {
return p.Gray16At(x, y)
}
func (p *Gray16) RGBA64At(x, y int) color.RGBA64 {
gray := p.Gray16At(x, y).Y
return color.RGBA64{gray, gray, gray, 0xffff}
}
func (p *Gray16) Gray16At(x, y int) color.Gray16 {
if !(Point{x, y}.In(p.Rect)) {
return color.Gray16{}
@ -828,6 +968,17 @@ func (p *Gray16) Set(x, y int, c color.Color) {
p.Pix[i+1] = uint8(c1.Y)
}
func (p *Gray16) SetRGBA64(x, y int, c color.RGBA64) {
if !(Point{x, y}.In(p.Rect)) {
return
}
// This formula is the same as in color.gray16Model.
gray := (19595*uint32(c.R) + 38470*uint32(c.G) + 7471*uint32(c.B) + 1<<15) >> 16
i := p.PixOffset(x, y)
p.Pix[i+0] = uint8(gray >> 8)
p.Pix[i+1] = uint8(gray)
}
func (p *Gray16) SetGray16(x, y int, c color.Gray16) {
if !(Point{x, y}.In(p.Rect)) {
return
@ -888,6 +1039,11 @@ func (p *CMYK) At(x, y int) color.Color {
return p.CMYKAt(x, y)
}
func (p *CMYK) RGBA64At(x, y int) color.RGBA64 {
r, g, b, a := p.CMYKAt(x, y).RGBA()
return color.RGBA64{uint16(r), uint16(g), uint16(b), uint16(a)}
}
func (p *CMYK) CMYKAt(x, y int) color.CMYK {
if !(Point{x, y}.In(p.Rect)) {
return color.CMYK{}
@ -916,6 +1072,19 @@ func (p *CMYK) Set(x, y int, c color.Color) {
s[3] = c1.K
}
func (p *CMYK) SetRGBA64(x, y int, c color.RGBA64) {
if !(Point{x, y}.In(p.Rect)) {
return
}
cc, mm, yy, kk := color.RGBToCMYK(uint8(c.R>>8), uint8(c.G>>8), uint8(c.B>>8))
i := p.PixOffset(x, y)
s := p.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
s[0] = cc
s[1] = mm
s[2] = yy
s[3] = kk
}
func (p *CMYK) SetCMYK(x, y int, c color.CMYK) {
if !(Point{x, y}.In(p.Rect)) {
return
@ -988,6 +1157,26 @@ func (p *Paletted) At(x, y int) color.Color {
return p.Palette[p.Pix[i]]
}
func (p *Paletted) RGBA64At(x, y int) color.RGBA64 {
if len(p.Palette) == 0 {
return color.RGBA64{}
}
c := color.Color(nil)
if !(Point{x, y}.In(p.Rect)) {
c = p.Palette[0]
} else {
i := p.PixOffset(x, y)
c = p.Palette[p.Pix[i]]
}
r, g, b, a := c.RGBA()
return color.RGBA64{
uint16(r),
uint16(g),
uint16(b),
uint16(a),
}
}
// PixOffset returns the index of the first element of Pix that corresponds to
// the pixel at (x, y).
func (p *Paletted) PixOffset(x, y int) int {
@ -1002,6 +1191,14 @@ func (p *Paletted) Set(x, y int, c color.Color) {
p.Pix[i] = uint8(p.Palette.Index(c))
}
func (p *Paletted) SetRGBA64(x, y int, c color.RGBA64) {
if !(Point{x, y}.In(p.Rect)) {
return
}
i := p.PixOffset(x, y)
p.Pix[i] = uint8(p.Palette.Index(c))
}
func (p *Paletted) ColorIndexAt(x, y int) uint8 {
if !(Point{x, y}.In(p.Rect)) {
return 0

View file

@ -6,6 +6,7 @@ package image
import (
"image/color"
"image/color/palette"
"testing"
)
@ -191,6 +192,80 @@ func Test16BitsPerColorChannel(t *testing.T) {
}
}
func TestRGBA64Image(t *testing.T) {
// memset sets every element of s to v.
memset := func(s []byte, v byte) {
for i := range s {
s[i] = v
}
}
r := Rect(0, 0, 3, 2)
testCases := []Image{
NewAlpha(r),
NewAlpha16(r),
NewCMYK(r),
NewGray(r),
NewGray16(r),
NewNRGBA(r),
NewNRGBA64(r),
NewNYCbCrA(r, YCbCrSubsampleRatio444),
NewPaletted(r, palette.Plan9),
NewRGBA(r),
NewRGBA64(r),
NewYCbCr(r, YCbCrSubsampleRatio444),
}
for _, tc := range testCases {
switch tc := tc.(type) {
// Most of the concrete image types in the testCases implement the
// draw.RGBA64Image interface: they have a SetRGBA64 method. We use an
// interface literal here, instead of importing "image/draw", to avoid
// an import cycle.
//
// The YCbCr and NYCbCrA types are special-cased. Chroma subsampling
// means that setting one pixel can modify neighboring pixels. They
// don't have Set or SetRGBA64 methods because that side effect could
// be surprising. Here, we just memset the channel buffers instead.
case interface {
SetRGBA64(x, y int, c color.RGBA64)
}:
tc.SetRGBA64(1, 1, color.RGBA64{0x7FFF, 0x3FFF, 0x0000, 0x7FFF})
case *NYCbCrA:
memset(tc.YCbCr.Y, 0x77)
memset(tc.YCbCr.Cb, 0x88)
memset(tc.YCbCr.Cr, 0x99)
memset(tc.A, 0xAA)
case *YCbCr:
memset(tc.Y, 0x77)
memset(tc.Cb, 0x88)
memset(tc.Cr, 0x99)
default:
t.Errorf("could not initialize pixels for %T", tc)
continue
}
// Check that RGBA64At(x, y) is equivalent to At(x, y).RGBA().
rgba64Image, ok := tc.(RGBA64Image)
if !ok {
t.Errorf("%T is not an RGBA64Image", tc)
continue
}
got := rgba64Image.RGBA64At(1, 1)
wantR, wantG, wantB, wantA := tc.At(1, 1).RGBA()
if (uint32(got.R) != wantR) || (uint32(got.G) != wantG) ||
(uint32(got.B) != wantB) || (uint32(got.A) != wantA) {
t.Errorf("%T:\ngot (0x%04X, 0x%04X, 0x%04X, 0x%04X)\n"+
"want (0x%04X, 0x%04X, 0x%04X, 0x%04X)", tc,
got.R, got.G, got.B, got.A,
wantR, wantG, wantB, wantA)
continue
}
}
}
func BenchmarkAt(b *testing.B) {
for _, tc := range testImages {
b.Run(tc.name, func(b *testing.B) {

View file

@ -71,6 +71,11 @@ func (p *YCbCr) At(x, y int) color.Color {
return p.YCbCrAt(x, y)
}
func (p *YCbCr) RGBA64At(x, y int) color.RGBA64 {
r, g, b, a := p.YCbCrAt(x, y).RGBA()
return color.RGBA64{uint16(r), uint16(g), uint16(b), uint16(a)}
}
func (p *YCbCr) YCbCrAt(x, y int) color.YCbCr {
if !(Point{x, y}.In(p.Rect)) {
return color.YCbCr{}
@ -210,6 +215,11 @@ func (p *NYCbCrA) At(x, y int) color.Color {
return p.NYCbCrAAt(x, y)
}
func (p *NYCbCrA) RGBA64At(x, y int) color.RGBA64 {
r, g, b, a := p.NYCbCrAAt(x, y).RGBA()
return color.RGBA64{uint16(r), uint16(g), uint16(b), uint16(a)}
}
func (p *NYCbCrA) NYCbCrAAt(x, y int) color.NYCbCrA {
if !(Point{X: x, Y: y}.In(p.Rect)) {
return color.NYCbCrA{}