From c28c50a22ee3f010d2cba857a961a3f78bf02395 Mon Sep 17 00:00:00 2001 From: Thomas Friedel Date: Wed, 20 Feb 2019 16:51:26 +0100 Subject: [PATCH] added a blurred fill style in backend, used it for blurred shadow drawing --- backend.go | 1 - backend/backendbase/base.go | 8 +- backend/gogl/fill.go | 177 ++++++++++++++++++++++-------------- backend/gogl/gogl.go | 51 +++++++++++ canvas.go | 9 -- shadows.go | 7 +- 6 files changed, 165 insertions(+), 88 deletions(-) diff --git a/backend.go b/backend.go index 65f595c..e3639a8 100644 --- a/backend.go +++ b/backend.go @@ -6,5 +6,4 @@ type Backend interface { ClearRect(x, y, w, h int) Clear(pts [4][2]float64) Fill(style *backendbase.Style, pts [][2]float64) - // BlurredShadow(shadow *backendbase.Shadow, pts [][2]float64) } diff --git a/backend/backendbase/base.go b/backend/backendbase/base.go index fc09224..cf47793 100644 --- a/backend/backendbase/base.go +++ b/backend/backendbase/base.go @@ -5,14 +5,8 @@ import "image/color" type Style struct { Color color.RGBA GlobalAlpha float64 + Blur float64 // radialGradient *RadialGradient // linearGradient *LinearGradient // image *Image } - -type Shadow struct { - Color color.RGBA - OffsetX float64 - OffsetY float64 - Blur float64 -} diff --git a/backend/gogl/fill.go b/backend/gogl/fill.go index 6378812..5ab3e8a 100644 --- a/backend/gogl/fill.go +++ b/backend/gogl/fill.go @@ -1,56 +1,13 @@ package goglbackend import ( + "math" "unsafe" "github.com/go-gl/gl/v3.2-core/gl" "github.com/tfriedel6/canvas/backend/backendbase" ) -/* -// FillRect fills a rectangle with the active fill style -func (b *GoGLBackend) FillRect(x, y, w, h float64) { - cv.activate() - - p0 := cv.tf(vec{x, y}) - p1 := cv.tf(vec{x, y + h}) - p2 := cv.tf(vec{x + w, y + h}) - p3 := cv.tf(vec{x + w, y}) - - if cv.state.shadowColor.a != 0 { - tris := [24]float32{ - 0, 0, - float32(cv.fw), 0, - float32(cv.fw), float32(cv.fh), - 0, 0, - float32(cv.fw), float32(cv.fh), - 0, float32(cv.fh), - float32(p0[0]), float32(p0[1]), - float32(p3[0]), float32(p3[1]), - float32(p2[0]), float32(p2[1]), - float32(p0[0]), float32(p0[1]), - float32(p2[0]), float32(p2[1]), - float32(p1[0]), float32(p1[1]), - } - cv.drawShadow(tris[:]) - } - - gl.BindBuffer(gl.ARRAY_BUFFER, buf) - data := [8]float32{float32(p0[0]), float32(p0[1]), float32(p1[0]), float32(p1[1]), float32(p2[0]), float32(p2[1]), float32(p3[0]), float32(p3[1])} - gl.BufferData(gl.ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl.STREAM_DRAW) - - gl.StencilFunc(gl.EQUAL, 0, 0xFF) - - vertex := cv.useShader(&cv.state.fill) - gl.VertexAttribPointer(vertex, 2, gl.FLOAT, false, 0, 0) - gl.EnableVertexAttribArray(vertex) - gl.DrawArrays(gl.TRIANGLE_FAN, 0, 4) - gl.DisableVertexAttribArray(vertex) - - gl.StencilFunc(gl.ALWAYS, 0, 0xFF) -} -*/ - // ClearRect sets the color of the rectangle to transparent black func (b *GoGLBackend) ClearRect(x, y, w, h int) { gl.Scissor(int32(x), int32(b.h-y-h), int32(w), int32(h)) @@ -88,29 +45,14 @@ func (b *GoGLBackend) Clear(pts [4][2]float64) { gl.Enable(gl.BLEND) } -/* -func (b *GoGLBackend) Fill(style *backendbase.Style, pts [4][2]float64) { - gl.BindBuffer(gl.ARRAY_BUFFER, b.buf) - data := [8]float32{ - float32(pts[0][0]), float32(pts[0][1]), - float32(pts[1][0]), float32(pts[1][1]), - float32(pts[2][0]), float32(pts[2][1]), - float32(pts[3][0]), float32(pts[3][1])} - gl.BufferData(gl.ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl.STREAM_DRAW) - - gl.StencilFunc(gl.EQUAL, 0, 0xFF) - - vertex := b.useShader(style) - gl.VertexAttribPointer(vertex, 2, gl.FLOAT, false, 0, nil) - gl.EnableVertexAttribArray(vertex) - gl.DrawArrays(gl.TRIANGLE_FAN, 0, 4) - gl.DisableVertexAttribArray(vertex) - - gl.StencilFunc(gl.ALWAYS, 0, 0xFF) -} -*/ - func (b *GoGLBackend) Fill(style *backendbase.Style, pts [][2]float64) { + if style.Blur > 0 { + b.offscr1.alpha = true + b.enableTextureRenderTarget(&b.offscr1) + gl.ClearColor(0, 0, 0, 0) + gl.Clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT) + } + b.ptsBuf = b.ptsBuf[:0] b.ptsBuf = append(b.ptsBuf, 0, 0, @@ -169,4 +111,107 @@ func (b *GoGLBackend) Fill(style *backendbase.Style, pts [][2]float64) { gl.Clear(gl.STENCIL_BUFFER_BIT) gl.StencilMask(0xFF) } + + if style.Blur > 0 { + b.drawBlurred(style.Blur) + } +} + +func (b *GoGLBackend) drawBlurred(blur float64) { + gl.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) + + var kernel []float32 + var kernelBuf [255]float32 + var gs *gaussianShader + if blur < 3 { + gs = &b.gauss15r + kernel = kernelBuf[:15] + } else if blur < 12 { + gs = &b.gauss63r + kernel = kernelBuf[:63] + } else { + gs = &b.gauss127r + kernel = kernelBuf[:127] + } + + gaussianKernel(blur, kernel) + + b.offscr2.alpha = true + b.enableTextureRenderTarget(&b.offscr2) + gl.ClearColor(0, 0, 0, 0) + gl.Clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT) + + gl.StencilFunc(gl.EQUAL, 0, 0xFF) + + gl.BindBuffer(gl.ARRAY_BUFFER, b.shadowBuf) + data := [16]float32{0, 0, 0, float32(b.h), float32(b.w), float32(b.h), float32(b.w), 0, 0, 0, 0, 1, 1, 1, 1, 0} + gl.BufferData(gl.ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl.STREAM_DRAW) + + gl.ActiveTexture(gl.TEXTURE0) + gl.BindTexture(gl.TEXTURE_2D, b.offscr1.tex) + + gl.UseProgram(gs.ID) + gl.Uniform1i(gs.Image, 0) + gl.Uniform2f(gs.CanvasSize, float32(b.fw), float32(b.fh)) + gl.Uniform2f(gs.KernelScale, 1.0/float32(b.fw), 0.0) + gl.Uniform1fv(gs.Kernel, int32(len(kernel)), &kernel[0]) + gl.VertexAttribPointer(gs.Vertex, 2, gl.FLOAT, false, 0, nil) + gl.VertexAttribPointer(gs.TexCoord, 2, gl.FLOAT, false, 0, gl.PtrOffset(8*4)) + gl.EnableVertexAttribArray(gs.Vertex) + gl.EnableVertexAttribArray(gs.TexCoord) + gl.DrawArrays(gl.TRIANGLE_FAN, 0, 4) + gl.DisableVertexAttribArray(gs.Vertex) + gl.DisableVertexAttribArray(gs.TexCoord) + + gl.StencilFunc(gl.ALWAYS, 0, 0xFF) + + b.disableTextureRenderTarget() + + gl.StencilFunc(gl.EQUAL, 0, 0xFF) + + gl.BindBuffer(gl.ARRAY_BUFFER, b.shadowBuf) + data = [16]float32{0, 0, 0, float32(b.h), float32(b.w), float32(b.h), float32(b.w), 0, 0, 0, 0, 1, 1, 1, 1, 0} + gl.BufferData(gl.ARRAY_BUFFER, len(data)*4, unsafe.Pointer(&data[0]), gl.STREAM_DRAW) + + gl.ActiveTexture(gl.TEXTURE0) + gl.BindTexture(gl.TEXTURE_2D, b.offscr2.tex) + + gl.UseProgram(gs.ID) + gl.Uniform1i(gs.Image, 0) + gl.Uniform2f(gs.CanvasSize, float32(b.fw), float32(b.fh)) + gl.Uniform2f(gs.KernelScale, 0.0, 1.0/float32(b.fh)) + gl.Uniform1fv(gs.Kernel, int32(len(kernel)), &kernel[0]) + gl.VertexAttribPointer(gs.Vertex, 2, gl.FLOAT, false, 0, nil) + gl.VertexAttribPointer(gs.TexCoord, 2, gl.FLOAT, false, 0, gl.PtrOffset(8*4)) + gl.EnableVertexAttribArray(gs.Vertex) + gl.EnableVertexAttribArray(gs.TexCoord) + gl.DrawArrays(gl.TRIANGLE_FAN, 0, 4) + gl.DisableVertexAttribArray(gs.Vertex) + gl.DisableVertexAttribArray(gs.TexCoord) + + gl.StencilFunc(gl.ALWAYS, 0, 0xFF) + + gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) +} + +func gaussianKernel(stddev float64, target []float32) { + stddevSqr := stddev * stddev + center := float64(len(target) / 2) + factor := 1.0 / math.Sqrt(2*math.Pi*stddevSqr) + for i := range target { + x := float64(i) - center + target[i] = float32(factor * math.Pow(math.E, -x*x/(2*stddevSqr))) + } + // normalizeKernel(target) +} + +func normalizeKernel(kernel []float32) { + var sum float32 + for _, v := range kernel { + sum += v + } + factor := 1.0 / sum + for i := range kernel { + kernel[i] *= factor + } } diff --git a/backend/gogl/gogl.go b/backend/gogl/gogl.go index 6450559..4dd6453 100644 --- a/backend/gogl/gogl.go +++ b/backend/gogl/gogl.go @@ -365,3 +365,54 @@ func (b *GoGLBackend) useAlphaShader(style *backendbase.Style, alphaTexSlot int3 gl.Uniform1f(b.sar.GlobalAlpha, float32(style.GlobalAlpha)) return b.sar.Vertex, b.sar.AlphaTexCoord } + +func (b *GoGLBackend) enableTextureRenderTarget(offscr *offscreenBuffer) { + if offscr.w != b.w || offscr.h != b.h { + if offscr.w != 0 && offscr.h != 0 { + gl.DeleteTextures(1, &offscr.tex) + gl.DeleteFramebuffers(1, &offscr.frameBuf) + gl.DeleteRenderbuffers(1, &offscr.renderStencilBuf) + } + offscr.w = b.w + offscr.h = b.h + + gl.ActiveTexture(gl.TEXTURE0) + gl.GenTextures(1, &offscr.tex) + gl.BindTexture(gl.TEXTURE_2D, offscr.tex) + // todo do non-power-of-two textures work everywhere? + if offscr.alpha { + gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, int32(b.w), int32(b.h), 0, gl.RGBA, gl.UNSIGNED_BYTE, nil) + } else { + gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGB, int32(b.w), int32(b.h), 0, gl.RGB, gl.UNSIGNED_BYTE, nil) + } + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) + + gl.GenFramebuffers(1, &offscr.frameBuf) + gl.BindFramebuffer(gl.FRAMEBUFFER, offscr.frameBuf) + + gl.GenRenderbuffers(1, &offscr.renderStencilBuf) + gl.BindRenderbuffer(gl.RENDERBUFFER, offscr.renderStencilBuf) + gl.RenderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, int32(b.w), int32(b.h)) + gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, offscr.renderStencilBuf) + + gl.FramebufferTexture(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, offscr.tex, 0) + + if err := gl.CheckFramebufferStatus(gl.FRAMEBUFFER); err != gl.FRAMEBUFFER_COMPLETE { + // todo this should maybe not panic + panic(fmt.Sprintf("Failed to set up framebuffer for offscreen texture: %x", err)) + } + + gl.Clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT) + } else { + gl.BindFramebuffer(gl.FRAMEBUFFER, offscr.frameBuf) + } +} + +func (b *GoGLBackend) disableTextureRenderTarget() { + // if b.offscreen { + // b.enableTextureRenderTarget(&b.offscrBuf) + // } else { + gl.BindFramebuffer(gl.FRAMEBUFFER, 0) + // } +} diff --git a/canvas.go b/canvas.go index 2c71662..2fc0e7c 100644 --- a/canvas.go +++ b/canvas.go @@ -502,15 +502,6 @@ func (cv *Canvas) backendStyle(s *drawStyle, alpha float64) backendbase.Style { } } -func (cv *Canvas) backendShadow() backendbase.Shadow { - return backendbase.Shadow{ - Color: cv.state.shadowColor, - OffsetX: cv.state.shadowOffsetX, - OffsetY: cv.state.shadowOffsetY, - Blur: cv.state.shadowBlur, - } -} - func (cv *Canvas) useShader(style *drawStyle) (vertexLoc uint32) { if lg := style.linearGradient; lg != nil { lg.load() diff --git a/shadows.go b/shadows.go index 1ea30c7..1fe5b8c 100644 --- a/shadows.go +++ b/shadows.go @@ -28,11 +28,8 @@ func (cv *Canvas) drawShadow2(pts [][2]float64) { }) } - shadow := cv.backendShadow() - if cv.state.shadowBlur == 0 { - style := backendbase.Style{Color: shadow.Color, GlobalAlpha: 1} - cv.b.Fill(&style, cv.shadowBuf) - } + style := backendbase.Style{Color: cv.state.shadowColor, GlobalAlpha: 1, Blur: cv.state.shadowBlur} + cv.b.Fill(&style, cv.shadowBuf) } func (cv *Canvas) drawShadow(tris []float32) {