Permalink
Browse files

Make ImageData implement draw.Image interface (#50)

Make ImageData implement draw.Image interface

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.

Using ImageData directly and avoiding a temporary image.NRGBA can
improve performance significantly, since that way, there's no need
to copy all of the pixel values from one to the other.

The reason we make the change from using RGBA to using NRGBA is because
ImageData is defined to contain non-alpha-premultiplied pixels,
according to HTML standard [1] and additional sources [2]:

>	Pixel values must not be premultiplied by alpha.

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 6a8015da13438f4de2c715e781271ab856b4b24c
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 6a8015d

Please sign in to comment.