diff --git a/prototypes/xgb-draw.go b/prototypes/xgb-draw.go index 7a1bdfc..b893f6f 100644 --- a/prototypes/xgb-draw.go +++ b/prototypes/xgb-draw.go @@ -1,9 +1,6 @@ // Network-friendly drawing application based on XRender. // -// TODO -// - use double buffering to remove flicker -// (more pronounced over X11 forwarding) -// - maybe keep the pixmap as large as the window +// TODO: Maybe keep the pixmap as large as the window. package main import ( @@ -26,6 +23,23 @@ func findPictureFormat(formats []render.Pictforminfo, return 0 } +func createNewPicture(X *xgb.Conn, depth byte, drawable xproto.Drawable, + width uint16, height uint16, format render.Pictformat) render.Picture { + pixmapid, err := xproto.NewPixmapId(X) + if err != nil { + log.Fatalln(err) + } + _ = xproto.CreatePixmap(X, depth, pixmapid, drawable, width, height) + + pictid, err := render.NewPictureId(X) + if err != nil { + log.Fatalln(err) + } + _ = render.CreatePicture(X, pictid, xproto.Drawable(pixmapid), format, + 0, []uint32{}) + return pictid +} + func main() { X, err := xgb.NewConn() if err != nil { @@ -125,53 +139,44 @@ func main() { log.Fatalln(err) } _ = render.CreateSolidFill(X, colorid, render.Color{ - Red: 0xeeee, + Red: 0x4444, Green: 0x8888, - Blue: 0x4444, + Blue: 0xffff, Alpha: 0xffff, }) - // Pixmaps. + // Various pixmaps. const ( pixWidth = 1000 pixHeight = 1000 ) - pixid, err := xproto.NewPixmapId(X) - if err != nil { - log.Fatalln(err) - } - _ = xproto.CreatePixmap(X, 24, pixid, xproto.Drawable(screen.Root), - pixWidth, pixHeight) + canvasid := createNewPicture(X, 24, + xproto.Drawable(screen.Root), pixWidth, pixHeight, pformatRGB) + bufferid := createNewPicture(X, 24, + xproto.Drawable(screen.Root), pixWidth, pixHeight, pformatRGB) + maskid := createNewPicture(X, 8, + xproto.Drawable(screen.Root), pixWidth, pixHeight, pformatAlpha) - pixpictid, err := render.NewPictureId(X) - if err != nil { - log.Fatalln(err) - } - render.CreatePicture(X, pixpictid, xproto.Drawable(pixid), - pformatRGB, 0, []uint32{}) - - pixmaskid, err := xproto.NewPixmapId(X) - if err != nil { - log.Fatalln(err) - } - _ = xproto.CreatePixmap(X, 8, pixmaskid, xproto.Drawable(screen.Root), - pixWidth, pixHeight) - - pixmaskpictid, err := render.NewPictureId(X) - if err != nil { - log.Fatalln(err) - } - render.CreatePicture(X, pixmaskpictid, xproto.Drawable(pixmaskid), - pformatAlpha, 0, []uint32{}) + // Smoothing by way of blur, apparently a misguided idea. + /* + _ = render.SetPictureFilter(X, maskid, + uint16(len("convolution")), "convolution", + []render.Fixed{F64ToFixed(3), F64ToFixed(3), + F64ToFixed(0), F64ToFixed(0.15), F64ToFixed(0), + F64ToFixed(0.15), F64ToFixed(0.40), F64ToFixed(0.15), + F64ToFixed(0), F64ToFixed(0.15), F64ToFixed(0)}) + */ // Pixmaps come uninitialized. - render.FillRectangles(X, - render.PictOpSrc, pixpictid, render.Color{ + _ = render.FillRectangles(X, + render.PictOpSrc, canvasid, render.Color{ Red: 0xffff, Green: 0xffff, Blue: 0xffff, Alpha: 0xffff, }, []xproto.Rectangle{{Width: pixWidth, Height: pixHeight}}) - // This is the only method we can use to render proper brush strokes. + // This is the only method we can use to render brush strokes without + // alpha accumulation due to stamping. Though this also seems to be + // misguided. Keeping it here for educational purposes. // // ConjointOver is defined as: A = Aa * 1 + Ab * max(1-Aa/Ab,0) // which basically resolves to: A = max(Aa, Ab) @@ -184,20 +189,28 @@ func main() { // - https://keithp.com/~keithp/talks/cairo2003.pdf drawPointAt := func(x, y int16) { _ = render.Composite(X, render.PictOpConjointOver, - brushid, render.PictureNone, pixmaskpictid, + brushid, render.PictureNone, maskid, 0, 0, 0, 0, x-brushRadius, y-brushRadius, brushRadius*2, brushRadius*2) - _ = render.SetPictureClipRectangles(X, pid, + _ = render.SetPictureClipRectangles(X, bufferid, x-brushRadius, y-brushRadius, []xproto.Rectangle{ {Width: brushRadius * 2, Height: brushRadius * 2}}) - _ = render.Composite(X, render.PictOpSrc, - pixpictid, render.PictureNone, pid, + canvasid, render.PictureNone, bufferid, 0, 0, 0, 0, 0 /* dst-x */, 0, /* dst-y */ pixWidth, pixHeight) _ = render.Composite(X, render.PictOpOver, - colorid, pixmaskpictid, pid, + colorid, maskid, bufferid, + 0, 0, 0, 0, 0 /* dst-x */, 0, /* dst-y */ + pixWidth, pixHeight) + + // Composited, now blit to window without flicker. + _ = render.SetPictureClipRectangles(X, pid, + x-brushRadius, y-brushRadius, []xproto.Rectangle{ + {Width: brushRadius * 2, Height: brushRadius * 2}}) + _ = render.Composite(X, render.PictOpSrc, + bufferid, render.PictureNone, pid, 0, 0, 0, 0, 0 /* dst-x */, 0, /* dst-y */ pixWidth, pixHeight) } @@ -271,14 +284,16 @@ func main() { _ = render.SetPictureClipRectangles(X, pid, int16(e.X), int16(e.Y), []xproto.Rectangle{{Width: e.Width, Height: e.Height}}) + // Not bothering to deflicker here using the buffer pixmap, + // with compositing this event is rare enough. _ = render.Composite(X, render.PictOpSrc, - pixpictid, render.PictureNone, pid, + canvasid, render.PictureNone, pid, 0, 0, 0, 0, 0 /* dst-x */, 0, /* dst-y */ pixWidth, pixHeight) if drawing { _ = render.Composite(X, render.PictOpOver, - colorid, pixmaskpictid, pid, + colorid, maskid, pid, 0, 0, 0, 0, 0 /* dst-x */, 0, /* dst-y */ pixWidth, pixHeight) } @@ -286,7 +301,7 @@ func main() { case xproto.ButtonPressEvent: if e.Detail == xproto.ButtonIndex1 { render.FillRectangles(X, - render.PictOpSrc, pixmaskpictid, render.Color{}, + render.PictOpSrc, maskid, render.Color{}, []xproto.Rectangle{{Width: pixWidth, Height: pixHeight}}) drawing = true @@ -303,7 +318,7 @@ func main() { case xproto.ButtonReleaseEvent: if e.Detail == xproto.ButtonIndex1 { _ = render.Composite(X, render.PictOpOver, - colorid, pixmaskpictid, pixpictid, + colorid, maskid, canvasid, 0, 0, 0, 0, 0 /* dst-x */, 0, /* dst-y */ pixWidth, pixHeight)