diff options
author | Nigel Tao <nigeltao@golang.org> | 2021-04-18 13:38:34 +1000 |
---|---|---|
committer | Nigel Tao <nigeltao@golang.org> | 2021-06-18 23:57:09 +0000 |
commit | 86743e7d8652c316b5f77a84ffc83244ee10a41b (patch) | |
tree | 95eb6875dcced583cc0bf98bfb036e363e126df3 | |
parent | 9401172166ee6ac64a5a74b4a8f2aa6d3f936ea1 (diff) | |
download | go-86743e7d8652c316b5f77a84ffc83244ee10a41b.tar.gz go-86743e7d8652c316b5f77a84ffc83244ee10a41b.zip |
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>
-rw-r--r-- | api/go1.17.txt | 34 | ||||
-rw-r--r-- | doc/go1.17.html | 13 | ||||
-rw-r--r-- | src/image/draw/draw.go | 10 | ||||
-rw-r--r-- | src/image/image.go | 197 | ||||
-rw-r--r-- | src/image/image_test.go | 75 | ||||
-rw-r--r-- | src/image/ycbcr.go | 10 |
6 files changed, 338 insertions, 1 deletions
diff --git a/api/go1.17.txt b/api/go1.17.txt index 257ca271d3..c5eb381708 100644 --- a/api/go1.17.txt +++ b/api/go1.17.txt @@ -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 diff --git a/doc/go1.17.html b/doc/go1.17.html index 6424709453..9452651295 100644 --- a/doc/go1.17.html +++ b/doc/go1.17.html @@ -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 --> diff --git a/src/image/draw/draw.go b/src/image/draw/draw.go index 8f96aa2d18..13f6668293 100644 --- a/src/image/draw/draw.go +++ b/src/image/draw/draw.go @@ -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 diff --git a/src/image/image.go b/src/image/image.go index 8adba96ab6..930d9ac6c7 100644 --- a/src/image/image.go +++ b/src/image/image.go @@ -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 diff --git a/src/image/image_test.go b/src/image/image_test.go index b9b9bfaa28..c64b6107b7 100644 --- a/src/image/image_test.go +++ b/src/image/image_test.go @@ -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) { diff --git a/src/image/ycbcr.go b/src/image/ycbcr.go index fbdffe1bd1..328b90d152 100644 --- a/src/image/ycbcr.go +++ b/src/image/ycbcr.go @@ -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{} |