From 7c1a21ce5677e15db79cc78aeaea7a2a183f74e7 Mon Sep 17 00:00:00 2001 From: Egon Elbre Date: Wed, 11 Mar 2020 13:01:42 +0200 Subject: [PATCH] add f32color.RGBA Signed-off-by: Egon Elbre --- app/headless/backend_test.go | 55 ++++---------------- app/internal/d3d11/backend_windows.go | 5 +- app/internal/d3d11/d3d11_windows.go | 7 +-- gpu/gpu.go | 35 ++++--------- gpu/path.go | 5 +- internal/f32color/rgba.go | 75 +++++++++++++++++++++++++++ 6 files changed, 105 insertions(+), 77 deletions(-) create mode 100644 internal/f32color/rgba.go diff --git a/app/headless/backend_test.go b/app/headless/backend_test.go index ec3965da..c1e97f24 100644 --- a/app/headless/backend_test.go +++ b/app/headless/backend_test.go @@ -9,11 +9,11 @@ import ( "image/color" "image/png" "io/ioutil" - "math" "runtime" "testing" "gioui.org/gpu/backend" + "gioui.org/internal/f32color" "gioui.org/internal/unsafe" ) @@ -48,8 +48,8 @@ func TestSimpleShader(t *testing.T) { } // Just off the center to catch inverted triangles. cx, cy := 300, 400 - shaderCol := [4]float32{.25, .55, .75, 1.0} - if got, exp := img.RGBAAt(cx, cy), tosRGB(shaderCol); got != exp { + shaderCol := f32color.RGBA{R: .25, G: .55, B: .75, A: 1.0} + if got, exp := img.RGBAAt(cx, cy), shaderCol.SRGB(); got != exp { t.Errorf("got color %v, expected %v", got, exp) } } @@ -94,8 +94,8 @@ func TestInputShader(t *testing.T) { t.Errorf("got color %v, expected %v", got, clearCol) } cx, cy := 300, 400 - shaderCol := [4]float32{.25, .55, .75, 1.0} - if got, exp := img.RGBAAt(cx, cy), tosRGB(shaderCol); got != exp { + shaderCol := f32color.RGBA{R: .25, G: .55, B: .75, A: 1.0} + if got, exp := img.RGBAAt(cx, cy), shaderCol.SRGB(); got != exp { t.Errorf("got color %v, expected %v", got, exp) } } @@ -109,11 +109,11 @@ func TestFramebuffers(t *testing.T) { col1 = color.RGBA{R: 0xad, G: 0xbe, B: 0xef, A: 0xde} col2 = color.RGBA{R: 0xfe, G: 0xba, B: 0xbe, A: 0xca} ) - fcol1, fcol2 := fromsRGB(col1), fromsRGB(col2) - b.ClearColor(fcol1[0], fcol1[1], fcol1[2], fcol1[3]) + fcol1, fcol2 := f32color.RGBAFromSRGB(col1), f32color.RGBAFromSRGB(col2) + b.ClearColor(fcol1.Float32()) b.BindFramebuffer(fbo1) b.Clear(backend.BufferAttachmentColor) - b.ClearColor(fcol2[0], fcol2[1], fcol2[2], fcol2[3]) + b.ClearColor(fcol2.Float32()) b.BindFramebuffer(fbo2) b.Clear(backend.BufferAttachmentColor) img := screenshot(t, fbo1, sz) @@ -174,8 +174,8 @@ func newBackend(t *testing.T) backend.Device { b.BeginFrame() // ClearColor accepts linear RGBA colors, while 8-bit colors // are in the sRGB color space. - col := fromsRGB(clearCol) - b.ClearColor(col[0], col[1], col[2], col[3]) + col := f32color.RGBAFromSRGB(clearCol) + b.ClearColor(col.Float32()) t.Cleanup(func() { b.EndFrame() ctx.ReleaseCurrent() @@ -209,38 +209,3 @@ func saveImage(file string, img image.Image) error { } return ioutil.WriteFile(file, buf.Bytes(), 0666) } - -func tosRGB(col [4]float32) color.RGBA { - for i := 0; i <= 2; i++ { - c := col[i] - // Use the formula from EXT_sRGB. - switch { - case c <= 0: - c = 0 - case 0 < c && c < 0.0031308: - c = 12.92 * c - case 0.0031308 <= c && c < 1: - c = 1.055*float32(math.Pow(float64(c), 0.41666)) - 0.055 - case c >= 1: - c = 1 - } - col[i] = c - } - return color.RGBA{R: uint8(col[0]*255 + .5), G: uint8(col[1]*255 + .5), B: uint8(col[2]*255 + .5), A: uint8(col[3]*255 + .5)} -} - -func fromsRGB(col color.Color) [4]float32 { - r, g, b, a := col.RGBA() - color := [4]float32{float32(r) / 0xffff, float32(g) / 0xffff, float32(b) / 0xffff, float32(a) / 0xffff} - for i := 0; i <= 2; i++ { - c := color[i] - // Use the formula from EXT_sRGB. - if c <= 0.04045 { - c = c / 12.92 - } else { - c = float32(math.Pow(float64((c+0.055)/1.055), 2.4)) - } - color[i] = c - } - return color -} diff --git a/app/internal/d3d11/backend_windows.go b/app/internal/d3d11/backend_windows.go index 1b1210ae..049f275f 100644 --- a/app/internal/d3d11/backend_windows.go +++ b/app/internal/d3d11/backend_windows.go @@ -10,6 +10,7 @@ import ( "unsafe" "gioui.org/gpu/backend" + "gioui.org/internal/f32color" gunsafe "gioui.org/internal/unsafe" "golang.org/x/sys/windows" ) @@ -26,7 +27,7 @@ type Device struct { } type Backend struct { - clearColor [4]float32 + clearColor f32color.RGBA clearDepth float32 viewport _D3D11_VIEWPORT depthState depthState @@ -539,7 +540,7 @@ func (b *Backend) NewProgram(vertexShader, fragmentShader backend.ShaderSources) } func (b *Backend) ClearColor(colr, colg, colb, cola float32) { - b.clearColor = [...]float32{colr, colg, colb, cola} + b.clearColor = f32color.RGBA{R: colr, G: colg, B: colb, A: cola} } func (b *Backend) Clear(buffers backend.BufferAttachments) { diff --git a/app/internal/d3d11/d3d11_windows.go b/app/internal/d3d11/d3d11_windows.go index 940dd68c..6c65adf5 100644 --- a/app/internal/d3d11/d3d11_windows.go +++ b/app/internal/d3d11/d3d11_windows.go @@ -5,9 +5,10 @@ package d3d11 import ( "fmt" "math" + "syscall" "unsafe" - "syscall" + "gioui.org/internal/f32color" "golang.org/x/sys/windows" ) @@ -1033,7 +1034,7 @@ func (c *_ID3D11DeviceContext) ClearDepthStencilView(target *_ID3D11DepthStencil ) } -func (c *_ID3D11DeviceContext) ClearRenderTargetView(target *_ID3D11RenderTargetView, color *[4]float32) { +func (c *_ID3D11DeviceContext) ClearRenderTargetView(target *_ID3D11RenderTargetView, color *f32color.RGBA) { syscall.Syscall( c.vtbl.ClearRenderTargetView, 3, @@ -1243,7 +1244,7 @@ func (c *_ID3D11DeviceContext) DrawIndexed(count, start uint32, base int32) { ) } -func (c *_ID3D11DeviceContext) OMSetBlendState(state *_ID3D11BlendState, factor *[4]float32, sampleMask uint32) { +func (c *_ID3D11DeviceContext) OMSetBlendState(state *_ID3D11BlendState, factor *f32color.RGBA, sampleMask uint32) { syscall.Syscall6( c.vtbl.OMSetBlendState, 4, diff --git a/gpu/gpu.go b/gpu/gpu.go index cb37d941..ef4c782b 100644 --- a/gpu/gpu.go +++ b/gpu/gpu.go @@ -19,6 +19,7 @@ import ( "gioui.org/f32" "gioui.org/gpu/backend" + "gioui.org/internal/f32color" "gioui.org/internal/opconst" "gioui.org/internal/ops" "gioui.org/internal/path" @@ -54,7 +55,7 @@ type drawOps struct { reader ops.Reader cache *resourceCache viewport image.Point - clearColor [3]float32 + clearColor f32color.RGBA imageOps []imageOp // zimageOps are the rectangle clipped opaque images // that can use fast front-to-back rendering with z-test @@ -104,7 +105,7 @@ type material struct { material materialType opaque bool // For materialTypeColor. - color [4]float32 + color f32color.RGBA // For materialTypeTexture. texture *texture uvScale f32.Point @@ -256,7 +257,7 @@ type blitUniforms struct { } type colorUniforms struct { - color [4]float32 + color f32color.RGBA } type materialType uint8 @@ -334,7 +335,7 @@ func (g *GPU) BeginFrame() { } g.ctx.BindFramebuffer(g.defFBO) g.ctx.DepthFunc(backend.DepthFuncGreater) - g.ctx.ClearColor(g.drawOps.clearColor[0], g.drawOps.clearColor[1], g.drawOps.clearColor[2], 1.0) + g.ctx.ClearColor(g.drawOps.clearColor.Float32()) g.ctx.ClearDepth(0.0) g.ctx.Clear(backend.BufferAttachmentColor | backend.BufferAttachmentDepth) g.ctx.Viewport(0, 0, viewport.X, viewport.Y) @@ -650,7 +651,7 @@ func floor(v float32) int { func (d *drawOps) reset(cache *resourceCache, viewport image.Point) { d.profile = false - d.clearColor = [3]float32{1.0, 1.0, 1.0} + d.clearColor = f32color.RGBA{R: 1.0, G: 1.0, B: 1.0, A: 1.0} d.cache = cache d.viewport = viewport d.imageOps = d.imageOps[:0] @@ -744,7 +745,7 @@ loop: d.zimageOps = d.zimageOps[:0] d.imageOps = d.imageOps[:0] state.z = 0 - copy(d.clearColor[:], mat.color[:3]) + d.clearColor = mat.color.Opaque() continue } state.z++ @@ -789,8 +790,8 @@ func (d *drawState) materialFor(cache *resourceCache, rect f32.Rectangle, off f3 switch d.matType { case materialColor: m.material = materialColor - m.color = gamma(d.color.RGBA()) - m.opaque = m.color[3] == 1.0 + m.color = f32color.RGBAFromSRGB(d.color) + m.opaque = m.color.A == 1.0 case materialTexture: m.material = materialTexture dr := boundRectF(rect.Add(off)) @@ -883,23 +884,7 @@ func (r *renderer) drawOps(ops []imageOp) { r.ctx.SetDepthTest(false) } -func gamma(r, g, b, a uint32) [4]float32 { - color := [4]float32{float32(r) / 0xffff, float32(g) / 0xffff, float32(b) / 0xffff, float32(a) / 0xffff} - // Assume that image.Uniform colors are in sRGB space. Linearize. - for i := 0; i <= 2; i++ { - c := color[i] - // Use the formula from EXT_sRGB. - if c <= 0.04045 { - c = c / 12.92 - } else { - c = float32(math.Pow(float64((c+0.055)/1.055), 2.4)) - } - color[i] = c - } - return color -} - -func (b *blitter) blit(z float32, mat materialType, col [4]float32, scale, off, uvScale, uvOff f32.Point) { +func (b *blitter) blit(z float32, mat materialType, col f32color.RGBA, scale, off, uvScale, uvOff f32.Point) { p := b.prog[mat] b.ctx.BindProgram(p.prog) var uniforms *blitUniforms diff --git a/gpu/path.go b/gpu/path.go index 691ea0a8..9c028c88 100644 --- a/gpu/path.go +++ b/gpu/path.go @@ -11,6 +11,7 @@ import ( "gioui.org/f32" "gioui.org/gpu/backend" + "gioui.org/internal/f32color" "gioui.org/internal/path" gunsafe "gioui.org/internal/unsafe" ) @@ -337,11 +338,11 @@ func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv ima } } -func (p *pather) cover(z float32, mat materialType, col [4]float32, scale, off, uvScale, uvOff, coverScale, coverOff f32.Point) { +func (p *pather) cover(z float32, mat materialType, col f32color.RGBA, scale, off, uvScale, uvOff, coverScale, coverOff f32.Point) { p.coverer.cover(z, mat, col, scale, off, uvScale, uvOff, coverScale, coverOff) } -func (c *coverer) cover(z float32, mat materialType, col [4]float32, scale, off, uvScale, uvOff, coverScale, coverOff f32.Point) { +func (c *coverer) cover(z float32, mat materialType, col f32color.RGBA, scale, off, uvScale, uvOff, coverScale, coverOff f32.Point) { p := c.prog[mat] c.ctx.BindProgram(p.prog) var uniforms *coverUniforms diff --git a/internal/f32color/rgba.go b/internal/f32color/rgba.go new file mode 100644 index 00000000..03377aca --- /dev/null +++ b/internal/f32color/rgba.go @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +package f32color + +import ( + "image/color" + "math" +) + +// RGBA is a 32 bit floating point linear space color. +type RGBA struct { + R, G, B, A float32 +} + +// Array returns rgba values in a [4]float32 array. +func (rgba RGBA) Array() [4]float32 { + return [4]float32{rgba.R, rgba.G, rgba.B, rgba.A} +} + +// Float32 returns r, g, b, a values. +func (col RGBA) Float32() (r, g, b, a float32) { + return col.R, col.G, col.B, col.A +} + +// SRGBA converts from linear to sRGB color space. +func (col RGBA) SRGB() color.RGBA { + return color.RGBA{ + R: uint8(linearTosRGB(col.R)*255 + .5), + G: uint8(linearTosRGB(col.G)*255 + .5), + B: uint8(linearTosRGB(col.B)*255 + .5), + A: uint8(col.A*255 + .5), + } +} + +// Opaque returns the color without alpha component. +func (col RGBA) Opaque() RGBA { + col.A = 1.0 + return col +} + +// RGBAFromSRGB converts color.Color to RGBA. +func RGBAFromSRGB(col color.Color) RGBA { + r, g, b, a := col.RGBA() + return RGBA{ + R: sRGBToLinear(float32(r) / 0xffff), + G: sRGBToLinear(float32(g) / 0xffff), + B: sRGBToLinear(float32(b) / 0xffff), + A: float32(a) / 0xFFFF, + } +} + +// linearTosRGB transforms color value from linear to sRGB. +func linearTosRGB(c float32) float32 { + // Formula from EXT_sRGB. + switch { + case c <= 0: + return 0 + case 0 < c && c < 0.0031308: + return 12.92 * c + case 0.0031308 <= c && c < 1: + return 1.055*float32(math.Pow(float64(c), 0.41666)) - 0.055 + } + + return 1 +} + +// sRGBToLinear transforms color value from sRGB to linear. +func sRGBToLinear(c float32) float32 { + // Formula from EXT_sRGB. + if c <= 0.04045 { + return c / 12.92 + } else { + return float32(math.Pow(float64((c+0.055)/1.055), 2.4)) + } +}