Eto.Drawing.Graphics.Clear() bug

Hello @curtisw, @dale,

I need to generate a large amount of color swatches (bitmaps) to be used in a GridView. In order to create them, i tried to build the bitmap like this:

bitmap = Eto.Drawing.Bitmap(20, 20, Eto.Drawing.PixelFormat.Format32bppRgb)
color = Eto.Drawing.Colors.AliceBlue
graphics = Eto.Drawing.Graphics(bitmap)
graphics.Clear(color)

The process to create 6 empty bitmaps (20x20 pixels) like above, takes 6 seconds which cannot be right. So i tried to clear the graphics with a solid brush like this:

bitmap = Eto.Drawing.Bitmap(20, 20, Eto.Drawing.PixelFormat.Format32bppRgb)
color = Eto.Drawing.Colors.AliceBlue
brush = Eto.Drawing.SolidBrush(color)
graphics = Eto.Drawing.Graphics(bitmap)
graphics.Clear(brush)

without any change in speed. So i tried to draw a rectangle in my color into the graphics object which seems to work very fast:

bitmap = Eto.Drawing.Bitmap(20, 20, Eto.Drawing.PixelFormat.Format32bppRgb)
color = Eto.Drawing.Colors.Red
brush = Eto.Drawing.SolidBrush(color)
graphics = Eto.Drawing.Graphics(bitmap)
rectangle = Eto.Drawing.Rectangle(0, 0, 12, 12)
graphics.FillRectangle(brush, rectangle)

but the result is a black bitmap unless i add this at the end:

graphics.Dispose()

As soon as this line is added, the bitmap creation process runs as slow as before. Running out of ideas, i’ve used bitmap.SetPixel(x,y,color) in a double nested loop as reported here which seems to run slightly faster but with more than 100 swatches to generate, takes too long to be usable.

How can i generate these color swatches fast enough ?

thanks,
c.

I remember SetPixel to be an extremely slow feature and that there are many workarounds to directly draw to the bitmap. I also don‘t know about its Multiplatform specifics but from immediate googling about „SetPixel is too slow“ you find tons of neat and drastic performance improvements. They all directly access memory and leave the field of managed memory: But have a look at this thread:

Furthermore its always a good idea to cache objects like Brushes and Graphics.

1 Like

I suggest perusing the test application code wrt drawing to find something that fits your need. I believe you’ll want to use Drawable (example from test app here). Further here is the drawing section of test app.

1 Like

I had the same issue with Clear() speed for “Layer swatches” in this topic:
Eto.Drawing.Graphics Slow - Clear() or FillRectangle()

SetPixel() ended up being instant for me (with 30 - 40 swatches). Didn’t test it for larger number of swatches.

1 Like

Hi @TomTom, i am aware of using LockBits under Windows. Using SetPixel as a (bad) example was just provided above because it has been the solution in the linked post. To build the color swatch relatively fast with Eto can be accomplished by 3 lines of code and filling a single pixel of a 1x1 bitmap like this:

c = Eto.Drawing.Color.FromArgb(color.R, color.G, color.B)
b = Eto.Drawing.Bitmap(1, 1, Eto.Drawing.PixelFormat.Format32bppRgb)
b.SetPixel(0, 0, c)
self.ColorSwatch = Eto.Drawing.Bitmap.WithSize(b, 20, 20)

Compared to the System.Drawing.Bitmap namespace, SetPixel works really fast under Eto.Drawing.Bitmap and the resulting bitmap is scaled up as icon to the required size. Even filling with SetPixel in a double nested loop is faster than bitmap.Clear(color) under Eto which can’t be true as no graphics object is created. However this was not my point. I simply do not get why such a simple task like filling a 20x20 pixel image with a constant color requires any hack like LockBits or hundrets of lines of code at all.

If things need to run under Windows only i can build it using System.Drawing.Bitmap, bitmap.Clear(color) works without a problem and is super fast, the problem is that Eto does not like that bitmap to be used in an ImageViewCell. It does not even accept a System.Drawing.Color without conversion and gives messages like:

Message: expected Color, got Color

I do that when i can and never wanted to use brushes or graphics at all. But since i cannot know which colors are used all i can cache is the resulting color swatch image. The lockups for existing ones take the same time as building a new one when it’s done like above.

_
c.

1 Like

Hi @spineribjoint1, that is why i linked your post above. You can fill a single pixel instead and blow your swatch image up as icon to skip the nested loop. But i’ve then found, probably as you did too, that the color swatch requires that 1px black border to make eg. a white color swatch pretty looking on a white background :slight_smile:

To get that border and fill (fast), under Windows only i used this:

format = System.Drawing.Imaging.PixelFormat.Format32bppRgb
bitmap = System.Drawing.Bitmap(12, 12, format)
with System.Drawing.Graphics.FromImage(bitmap) as graphics:
    graphics.FillRectangle(System.Drawing.SolidBrush(self.Layer.Color), 1, 1, 10, 10)
        
# put image into memory stream
memoryStream = System.IO.MemoryStream()
format = System.Drawing.Imaging.ImageFormat.Bmp
System.Drawing.Bitmap.Save(bitmap, memoryStream, format)

self.ColorSwatch = Eto.Drawing.Bitmap(memoryStream)

I have not found a simpler way yet to make Eto accept that System.Drawing.Bitmap.

btw. your layer dialog example was very inspiring. Did you finish that project ?
_
c.

Hi @clement, sorry I didn’t notice you already linked the post. Layer dialog is still work in progress. I still didn’t resolve the keyboard focus issue. Let me know if you get any ideas.

Alternative approach for swatches in Eto is to use forms.TextBox(). I found it relatively fast for a custom color select dialog. You can get the sense of speed in the video. Not sure this is applicable in your case but maybe you get some ideas.

Hi @spineribjoint1, cool idea! So you just update background colors of the TextBox ? I need to try if i just can use a TextBoxCell with the same technique.

_
c.

Hi @nathanletwory, thanks, i see there is a Eto.Forms.DrawableCell but it does not allow to provide the index to bind to when an indexed-based data item is used. I understand that you suggest to use the OnPaint event to set the cell background color right ?

_
c.

@clement I don’t know the details, but I do know that @maxsoder is using drawable to create the materials panel to be able to open files and scroll fairly smoothly when there are thousands of materials in the file.

I’m using Drawable to make headers for collapsible panels, all attribute labels, and various custom buttons, it seems to work very nice & quick. But I am not messing with a grid, just regular content UI, so grain of salt I guess.

Yes. I used TextBox because of some additional functionality it provides (e.g. ShowBorder). You can achieve similar effect with other controls. And yes, it is a bit hacky. It’s easy to add a black border around it if needed.

# Swatch creation function
def Swatch(h=0, s=1, l=1):

    def store(sender, e):
        self.selected = sender.BackgroundColor
        self.Close()

    def enterm(sender, e):
        swatch.ShowBorder = True
        self.Format(self.new, sender.BackgroundColor, 'New')

    def exitm(sender, e):
        swatch.ShowBorder = False

    swatch = forms.TextBox()
    swatch.Cursor = forms.Cursor(forms.CursorType.Arrow)
    swatch.ShowBorder = False; swatch.ReadOnly = True
    swatch.BackgroundColor = drawing.ColorHSL(h,s,l)
    swatch.Size = drawing.Size(20,20)
    swatch.MouseEnter += enterm; swatch.MouseLeave += exitm; swatch.MouseUp += store
    return swatch
1 Like

Hey @clement,

Yes it looks like there is a general performance issue using a Graphics object on a Bitmap under WPF/Windows currently. I’ve logged that as RH-58749 to look at fixing the issue. Also, to just point out that you must call Graphics.Dispose() after you are done with it.

As for the issue at hand, there’s a few ways to go about creating images on the fly for a GridView cell, which will probably always yield faster and leaner results for this particular use case anyway.

  1. You can use Bitmap.Lock in Eto, which is extremely fast and with BitmapData.SetPixel() it is much faster than calling Bitmap.SetPixel that many times. For example:

    bitmap = new Eto.Drawing.Bitmap(20, 20, Eto.Drawing.PixelFormat.Format32bppRgb)
    color = Eto.Drawing.Colors.AliceBlue
    bitmapData = bitmap.Lock()
    for y in range(0, 20):
        for x in range(0, 20):
            bitmapData.SetPixel(x, y, color)
    bitmapData.Dispose()
    
  2. You can use a CustomCell with any particular control, e.g. maybe a Panel and set its BackgroundColor.

  3. You can use the DrawableCell and use CellPaintEventEventArgs.Item in its Paint event to get the item you are painting for. This is a good option if you want to do something more than just a simple box of color (e.g. draw borders, draw it as a circle, etc). This is my recommendation for this scenario.

Here is an example that demonstrates options 2 and 3.

SampleGridViewDrawing.py (1.4 KB)

Hope this helps!

2 Likes

Hi @curtisw, thanks a lot for your help an this example code!

_
c.

As a follow up, as of v6.29.20176.15551 and v7.0.20176.16016 there should no longer be a huge performance hit using the Graphics object on a Bitmap on Windows. Thanks again for reporting the issue!

1 Like