diff --git a/app/internal/gpu/gpu.go b/app/internal/gpu/gpu.go index 028ef966..a73b6056 100644 --- a/app/internal/gpu/gpu.go +++ b/app/internal/gpu/gpu.go @@ -18,7 +18,6 @@ import ( "gioui.org/internal/ops" "gioui.org/op" "gioui.org/op/paint" - "golang.org/x/image/draw" ) type GPU struct { @@ -79,9 +78,9 @@ type drawState struct { rect bool z int - // Current ImageOp image and rect, if any. - img image.Image - imgRect image.Rectangle + // Current ImageOp image and size, if any. + img *image.RGBA + imgSize image.Point // Current ColorOp, if any. color color.RGBA } @@ -119,7 +118,7 @@ type material struct { uvOffset f32.Point } -// clipOp is the shadow of draw.ClipOp. +// clipOp is the shadow of paint.ClipOp. type clipOp struct { bounds f32.Rectangle } @@ -144,25 +143,16 @@ func (op *clipOp) decode(data []byte) { } } -func decodeImageOp(data []byte, refs []interface{}) paint.ImageOp { +func decodeImageOp(data []byte, refs []interface{}) (*image.RGBA, image.Point) { bo := binary.LittleEndian if opconst.OpType(data[0]) != opconst.TypeImage { panic("invalid op") } - sr := image.Rectangle{ - Min: image.Point{ - X: int(int32(bo.Uint32(data[1:]))), - Y: int(int32(bo.Uint32(data[5:]))), - }, - Max: image.Point{ - X: int(int32(bo.Uint32(data[9:]))), - Y: int(int32(bo.Uint32(data[13:]))), - }, - } - return paint.ImageOp{ - Src: refs[0].(image.Image), - Rect: sr, + sz := image.Point{ + X: int(int32(bo.Uint32(data[1:]))), + Y: int(int32(bo.Uint32(data[5:]))), } + return refs[0].(*image.RGBA), sz } func decodeColorOp(data []byte) paint.ColorOp { @@ -206,7 +196,7 @@ type resource interface { } type texture struct { - src image.Image + src *image.RGBA id gl.Texture } @@ -737,9 +727,7 @@ loop: state.img = nil state.color = op.Color case opconst.TypeImage: - op := decodeImageOp(encOp.Data, encOp.Refs) - state.img = op.Src - state.imgRect = op.Rect + state.img, state.imgSize = decodeImageOp(encOp.Data, encOp.Refs) case opconst.TypePaint: op := decodePaintOp(encOp.Data) off := state.t.Transform(f32.Point{}) @@ -801,14 +789,12 @@ func (d *drawState) materialFor(cache *resourceCache, rect f32.Rectangle, off f3 m.material = materialColor m.color = gamma(d.color.RGBA()) m.opaque = m.color[3] == 1.0 - } else if uniform, ok := d.img.(*image.Uniform); ok { - m.material = materialColor - m.color = gamma(uniform.RGBA()) - m.opaque = m.color[3] == 1.0 } else { m.material = materialTexture dr := boundRectF(rect.Add(off)) - sr := d.imgRect + sr := image.Rectangle{ + Max: d.imgSize, + } if dx := dr.Dx(); dx != 0 { // Don't clip 1 px width sources. if sdx := sr.Dx(); sdx > 1 { @@ -908,25 +894,18 @@ func (r *renderer) drawOps(ops []imageOp) { r.ctx.Disable(gl.DEPTH_TEST) } -func (r *renderer) uploadTexture(img image.Image) { +func (r *renderer) uploadTexture(img *image.RGBA) { var pixels []byte b := img.Bounds() w, h := b.Dx(), b.Dy() - switch img := img.(type) { - case *image.RGBA: - if img.Stride == w*4 { - start := (b.Min.X + b.Min.Y*w) * 4 - end := (b.Max.X + (b.Max.Y-1)*w) * 4 - pixels = img.Pix[start:end] - } else { - pixels = copyImage(img, b).Pix - } - default: - pixels = copyImage(img, b).Pix + if img.Stride != w*4 { + panic("unsupported stride") } + start := (b.Min.X + b.Min.Y*w) * 4 + end := (b.Max.X + (b.Max.Y-1)*w) * 4 + pixels = img.Pix[start:end] tt := r.ctx.caps.srgbaTriple r.ctx.TexImage2D(gl.TEXTURE_2D, 0, tt.internalFormat, w, h, tt.format, tt.typ, pixels) - } func gamma(r, g, b, a uint32) [4]float32 { @@ -1008,12 +987,6 @@ func createTexture(ctx *context) gl.Texture { return tex } -func copyImage(img image.Image, r image.Rectangle) *image.RGBA { - tmp := image.NewRGBA(r) - draw.Draw(tmp, r, img, r.Min, draw.Src) - return tmp -} - const blitVSrc = ` #version 100 diff --git a/op/paint/paint.go b/op/paint/paint.go index 81093233..07fe960f 100644 --- a/op/paint/paint.go +++ b/op/paint/paint.go @@ -6,6 +6,7 @@ import ( "encoding/binary" "image" "image/color" + "image/draw" "math" "gioui.org/f32" @@ -13,16 +14,12 @@ import ( "gioui.org/op" ) -// ImageOp sets the material to a section of an -// image. +// ImageOp sets the material to an image. type ImageOp struct { - // Src is the image. Note that once a particular image.Image has - // been used by an ImageOp, updating the image contents might not - // be reflected in the rendered image. Use a new image.Image - // instead. - Src image.Image - // Rect defines the section of Src to use. - Rect image.Rectangle + uniform bool + color color.RGBA + src *image.RGBA + size image.Point } // ColorOp sets the material to a constant color. @@ -36,15 +33,45 @@ type PaintOp struct { Rect f32.Rectangle } +func NewImageOp(src image.Image) ImageOp { + switch src := src.(type) { + case *image.Uniform: + col := color.RGBAModel.Convert(src.C).(color.RGBA) + return ImageOp{ + uniform: true, + color: col, + } + default: + sz := src.Bounds().Size() + // Copy the image into a GPU friendly format. + dst := image.NewRGBA(image.Rectangle{ + Max: sz, + }) + draw.Draw(dst, src.Bounds(), src, image.Point{}, draw.Src) + return ImageOp{ + src: dst, + size: sz, + } + } +} + +func (i ImageOp) Size() image.Point { + return i.size +} + func (i ImageOp) Add(o *op.Ops) { + if i.uniform { + ColorOp{ + Color: i.color, + }.Add(o) + return + } data := make([]byte, opconst.TypeImageLen) data[0] = byte(opconst.TypeImage) bo := binary.LittleEndian - bo.PutUint32(data[1:], uint32(i.Rect.Min.X)) - bo.PutUint32(data[5:], uint32(i.Rect.Min.Y)) - bo.PutUint32(data[9:], uint32(i.Rect.Max.X)) - bo.PutUint32(data[13:], uint32(i.Rect.Max.Y)) - o.Write(data, i.Src) + bo.PutUint32(data[1:], uint32(i.size.X)) + bo.PutUint32(data[5:], uint32(i.size.Y)) + o.Write(data, i.src) } func (c ColorOp) Add(o *op.Ops) { diff --git a/widget/material/button.go b/widget/material/button.go index 745ad1d4..cf77b7f8 100644 --- a/widget/material/button.go +++ b/widget/material/button.go @@ -41,7 +41,7 @@ type Icon struct { size unit.Value // Cached values. - img image.Image + op paint.ImageOp imgSize int } @@ -112,9 +112,11 @@ func (b IconButton) Layout(gtx *layout.Context, button *widget.Button) { layout.UniformInset(b.Padding).Layout(gtx, func() { size := gtx.Px(b.Size) - gtx.Px(b.Padding) ico := b.Icon.image(size) - paint.ImageOp{Src: ico, Rect: ico.Bounds()}.Add(gtx.Ops) + ico.Add(gtx.Ops) paint.PaintOp{ - Rect: toRectF(ico.Bounds()), + Rect: f32.Rectangle{ + Max: toPointF(ico.Size()), + }, }.Add(gtx.Ops) gtx.Dimensions = layout.Dimensions{ Size: image.Point{X: size, Y: size}, @@ -143,9 +145,9 @@ func (b IconButton) Layout(gtx *layout.Context, button *widget.Button) { st.Layout(gtx, bg, ico) } -func (ic *Icon) image(sz int) image.Image { +func (ic *Icon) image(sz int) paint.ImageOp { if sz == ic.imgSize { - return ic.img + return ic.op } m, _ := iconvg.DecodeMetadata(ic.src) dx, dy := m.ViewBox.AspectRatio() @@ -157,15 +159,19 @@ func (ic *Icon) image(sz int) image.Image { iconvg.Decode(&ico, ic.src, &iconvg.DecodeOptions{ Palette: &m.Palette, }) - ic.img = img + ic.op = paint.NewImageOp(img) ic.imgSize = sz - return img + return ic.op +} + +func toPointF(p image.Point) f32.Point { + return f32.Point{X: float32(p.X), Y: float32(p.Y)} } func toRectF(r image.Rectangle) f32.Rectangle { return f32.Rectangle{ - Min: f32.Point{X: float32(r.Min.X), Y: float32(r.Min.Y)}, - Max: f32.Point{X: float32(r.Max.X), Y: float32(r.Max.Y)}, + Min: toPointF(r.Min), + Max: toPointF(r.Max), } } diff --git a/widget/material/image.go b/widget/material/image.go index 90d3a4dc..8b5e40ce 100644 --- a/widget/material/image.go +++ b/widget/material/image.go @@ -15,8 +15,6 @@ import ( type Image struct { // Src is the image to display. Src image.Image - // Rect is the source rectangle. - Rect image.Rectangle // Scale is the ratio of image pixels to // dps. Scale float32 @@ -25,7 +23,6 @@ type Image struct { func (t *Theme) Image(img image.Image) Image { return Image{ Src: img, - Rect: img.Bounds(), Scale: 160 / 72, // About 72 DPI. } } @@ -47,7 +44,7 @@ func (im Image) Layout(gtx *layout.Context) { dr := f32.Rectangle{ Max: f32.Point{X: float32(d.X), Y: float32(d.Y)}, } - paint.ImageOp{Src: im.Src, Rect: im.Rect}.Add(gtx.Ops) + paint.NewImageOp(im.Src).Add(gtx.Ops) paint.PaintOp{Rect: dr}.Add(gtx.Ops) gtx.Dimensions = layout.Dimensions{Size: d, Baseline: d.Y} }