fjvallarino/monomer

[Question] How to render changeable picture efficiently enough?

Opened this issue · 3 comments

Lev135 commented

I want to show square grid with about 100x100 colored cells. The state of the grid (i. e. the colors of the cells) shouldn't change frequently. However, the simplest solution, which I've tried: to create a widget and draw the grid in render function works very poorly, since rerendering is called too often. Is there any way to make it better?

makeGrid :: ()
  => (Int, Int)
  -> Widget s e
makeGrid (w, h)
  = createSingle () def {
      singleGetSizeReq = getSizeReq
    , singleRender = render
    }
  where
    cellSize = 10
    coord n = fromIntegral n * cellSize

    getSizeReq _wenv _node = (fixedSize $ coord w, fixedSize $ coord h)

    render _wenv node renderer = do
      let vp = node ^. L.info . L.viewport
          origin = Point (vp ^. L.x) (vp ^. L.y)
      drawInTranslation renderer origin do
        for_ [0 .. w - 1] \i -> do
          for_ [0 .. h - 1] \j -> do
            let rect = Rect (coord i) (coord j) (coord 1) (coord 1)
                s = Just $ BorderSide 0.1 gray
                bd = Border s s s s
            drawRectBorder renderer rect bd Nothing

@Lev135 Hi! In color picker widget there is a pattern with alternating colors to indicate transparency which is implemented using imageMem widget:

patternImage :: WidgetEvent e => Int -> Int -> Color -> Color -> WidgetNode s e
patternImage steps blockW col1 col2 = newImg where
row1 = encodeRow steps blockW col1 col2
row2 = encodeRow steps blockW col2 col1
builder = mconcat (replicate steps (row1 <> row2))
imgData = BL.toStrict $ toLazyByteString builder
imgLen = fromIntegral (steps * blockW)
imgSize = Size imgLen imgLen
imgConfig = [fitFill, imageRepeatX, imageRepeatY]
newImg = imageMem_ "colorPickerAlphaBg" imgData imgSize imgConfig

Perhaps you can use imageMem to render your square grid.

Hi @Lev135!

The main problem is you're drawing quite a few items using a helper function that is not particularly efficient. This function is used to render widget borders in several scenarios, and because of that it's too general; I added a note for myself to review it for the most basic case.

Based on what I see from the code, is the objective only drawing a grid, or are you planning on drawing squares in the viewport?

If you are only going to draw the borders, the simplest/most efficient solution is just drawing ten lines horizontally and ten lines vertically.

If you need to draw individual blocks and want them to have their own borders (be it because there are spaces between blocks or the border colors are different), you could try using Renderer's renderRect, which is a lower level version of drawRect and drawBorderRect. In this case you'll have to call the begin/end functions manually:

beginPath renderer
setFillColor renderer rectColor
renderRect renderer rectBounds
fill renderer

Alternatively, if you only want to draw borders, you can call:

setStrokeColor renderer color
setStrokeWidth renderer width
Lev135 commented

In my case I have colored cells, so solution with drawing lines doesn't work. ReplacingdrawRect with renderRect doesn't seems to have a notable effect. For @Deltaspace0 suggestion to use imageMem: I can't see any drawing primitives for such images... Maybe I've missed something. To be honest, I haven't quite figured out how it works and how it should solve my problem: render function will use a ready picture? For ColorPicker there should be no efficiency difficulies at all (untill we haven't several thousands of them).