Permalink
Browse files

Make ImageData implement draw.Image interface

According to HTML standard, ImageData is defined to contain
non-alpha-premultiplied pixels [1][2]:

>	Pixel values must not be premultiplied by alpha.

As a result, we change ImageData to use NRGBA rather than RGBA.

ImageData, in nature, is similar to image.NRGBA.

We change its API slightly to be more like image.NRGBA. This allows it
to implement image.Image and draw.Image interfaces. That makes ImageData
more useful, since it can be passed directly to drawing algorithms that
work with image.Image or draw.Image. Skipping a temporary image.NRGBA
can improve performance significantly.

Also implement bounds checking in At and Set methods to be consistent
with image package. (It's still possible to access the underlying data
directly, if desired, via Data field.)

A downside of this change is the need to import image package, which
can increase binary size. The increase is small enough, and the
benefits of making ImageData more useful for idiomatic usage are deemed
to justify the cost.

References:

1.	https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation
2.	https://stackoverflow.com/questions/23497925/how-can-i-stop-the-alpha-premultiplication-with-canvas-imagedata
  • Loading branch information...
dmitshur committed Jan 9, 2018
1 parent 2366b08 commit 339f7527838b20d05cc85ebd1f88bec7571ed22a
Showing with 52 additions and 13 deletions.
  1. +45 −13 dom.go
  2. +7 −0 dom_test.go
View
58 dom.go
@@ -113,6 +113,7 @@
package dom // import "honnef.co/go/js/dom"
import (
"image"
"image/color"
"strings"
"time"
@@ -1950,22 +1951,53 @@ type ImageData struct {
Data *js.Object `js:"data"`
}
func (imd *ImageData) At(x, y int) *color.RGBA {
var index = 4 * (x + y*imd.Width)
return &color.RGBA{
R: uint8(imd.Data.Index(index).Int()),
G: uint8(imd.Data.Index(index + 1).Int()),
B: uint8(imd.Data.Index(index + 2).Int()),
A: uint8(imd.Data.Index(index + 3).Int()),
func (m *ImageData) ColorModel() color.Model { return color.NRGBAModel }
func (m *ImageData) Bounds() image.Rectangle {
return image.Rect(0, 0, m.Width, m.Height)
}
func (m *ImageData) At(x, y int) color.Color {
return m.NRGBAAt(x, y)
}
func (m *ImageData) NRGBAAt(x, y int) color.NRGBA {
if x < 0 || x >= m.Width ||
y < 0 || y >= m.Height {
return color.NRGBA{}
}
i := (y*m.Width + x) * 4
return color.NRGBA{
R: uint8(m.Data.Index(i + 0).Int()),
G: uint8(m.Data.Index(i + 1).Int()),
B: uint8(m.Data.Index(i + 2).Int()),
A: uint8(m.Data.Index(i + 3).Int()),
}
}
func (imd *ImageData) Set(x, y int, c color.RGBA) {
var index = 4 * (x + y*imd.Width)
imd.Data.SetIndex(index, c.R)
imd.Data.SetIndex(index+1, c.G)
imd.Data.SetIndex(index+2, c.B)
imd.Data.SetIndex(index+3, c.A)
func (m *ImageData) Set(x, y int, c color.Color) {
if x < 0 || x >= m.Width ||
y < 0 || y >= m.Height {
return
}
c1 := color.NRGBAModel.Convert(c).(color.NRGBA)
i := (y*m.Width + x) * 4
m.Data.SetIndex(i+0, c1.R)
m.Data.SetIndex(i+1, c1.G)
m.Data.SetIndex(i+2, c1.B)
m.Data.SetIndex(i+3, c1.A)
}
func (m *ImageData) SetNRGBA(x, y int, c color.NRGBA) {
if x < 0 || x >= m.Width ||
y < 0 || y >= m.Height {
return
}
i := (y*m.Width + x) * 4
m.Data.SetIndex(i+0, c.R)
m.Data.SetIndex(i+1, c.G)
m.Data.SetIndex(i+2, c.B)
m.Data.SetIndex(i+3, c.A)
}
// CanvasGradient represents an opaque object describing a gradient.
View
@@ -1,8 +1,15 @@
package dom
import (
"image"
"image/draw"
)
var _ Node = &BasicNode{}
var _ HTMLElement = &BasicHTMLElement{}
var _ Element = &BasicElement{}
var _ Document = &document{}
var _ Window = &window{}
var _ HTMLDocument = &htmlDocument{}
var _ image.Image = &ImageData{}
var _ draw.Image = &ImageData{}

0 comments on commit 339f752

Please sign in to comment.