Deprecated Canvas.Graphics Optimization & Paint Invalidate Areas()

I have found many useful links on the forum advising how to modify my old code that referenced the Canvas.Graphics object outside of the Paint event and have settled on creating a background buffer image, doing all the drawing to its graphics object and then calling Invalidate().

Now in the Paint event I have the following code which works well:

If bufferPic <> Nil then
g.DrawPicture(bufferPic, 0, 0)
End If

I also need to optimize drawing by only invalidating portions of the canvas and so call Invalidate() using coorindates and width/height parameters:

Invalidate(10, 10, 100, 100, False)

So, firstly, would the code below in the Paint event correctly update only the portion of the canvas I have stated in the Invalidate call?


Dim i, count, X, Y, aWidth, aHeight As Integer

count = Ubound(areas)

If count < 0 then

g.DrawPicture(bufferPic, 0, 0)

Else

For i = 0 to count

X = areas(i).X
Y = areas(i).Y
aWidth = areas(i).Width
aHeight = areas(i).Height

g.DrawPicture(bufferPic, X, Y, aWidth, aHeight, X, Y, aWidth, aHeight) 

Next

End If

Secondly, can anyone tell me the most efficient/optimized way of copying a picture object using pure Xojo code and also a third-party plugin if required?

I have Einhugar’s PictureEffects plugin but don’t think it has a copy feature (unless I am mistaken) and I know some people have mentioned MBS plugins for this kind of task. It is also important to be able to preserve the transparency/alpha channel during the copy.

Once I know the best solution to this I intend to add this method to a global module so I can frequently call it rather than hard-coding the same Graphics.DrawPicture() calls each time in multiple places.

Thanks.

I often wonder the same myself.
Like: under what circumstances would you EVER see more than one area as a parameter?

(maybe bringing the window forward from behind two partially covering windows?)

And is it faster to blit the whole thing than to iterate through the collection of areas?

There is something called premature optimization.

As a matter of principle, go first with a full refresh, and if, and only if it appears to be too slow, then you can start defining refresh areas.

Unfortunately rendering is slow with a full refresh so I am trying this method now.

The revised code below compiles but doesn’t seem to speed anything up:

        objX = REALbasic.Rect(areas(i)).Left
        objY = REALbasic.Rect(areas(i)).Top
        objWidth = REALbasic.Rect(areas(i)).Width
        objHeight = REALbasic.Rect(areas(i)).Height
        
        g.DrawPicture(bufferPic, objX, objY, objWidth, objHeight, objX, objY, objWidth, objHeight) 

My experience with a few graphics based apps which rely on people drawing with the mouse, or moving graphics about is:
Repainting the whole screen at once isn’t slow enough to be noticable. Even on my retina system.

But it can be made so.

Things that cause it to appear too slow:
Cascading refreshes… if your controls are too close together, painting one causes another to refresh, which causes…
To check this, try adding some logging in the Paint event… see how many times it is actually being called.
If all you do is move the mouse, nothing should be firing.

Drawing in the Paint event.
If you wait until the paint event to start the drawing on the buffer, then you draw every time the event fires.
Instead, have a ‘create picture’ method that assembles the buffer.
Call it only if you know the content is changing.
In the paint event, just display the buffer and trust that it is up to date… dont redraw all the detail again.

[code]For i = 0 to count

X = areas(i).X
Y = areas(i).Y
aWidth = areas(i).Width
aHeight = areas(i).Height

g.DrawPicture(bufferPic, X, Y, aWidth, aHeight, X, Y, aWidth, aHeight)

Next[/code]

You use a buffer picture but draw to it in the paint event?

The event that fires 60 times each second?

Shouldn’t that drawing be done in the background (eg a Draw method) to the buffer pic: to only paint the finished product.

After all that is the whole point of using a buffer pic.

P.S. Jeff beat me to it.

It shouldnt.
It should only fire if there is a need to paint the contents.
You should be able to leave a window open for minutes without seeing it fire once if no interaction occurs.

If it fires 60 times a second, then either code is calling Refresh() deliberately - bad idea: use Invalidate() …
or something is wrong.

[quote=308552:@Jeff Tullin]I often wonder the same myself.
Like: under what circumstances would you EVER see more than one area as a parameter?[/quote]

Suppose you have a canvas that you draw objects on and some action in your UI makes several of them change state (like maybe selecting a group of them in a listing) You might get several areas to redraw if you invalidate each of them when they are selected

Like in the navigator - if you select items in the navigator that are controls on the layout and each now needs to show selection handles

Thanks for the advice.

No I create and draw to the buffer in other methods and then draw the buffer to the screen in the paint event. And I’ve tested how often the paint event fires and it’s only when you call it to refresh or invalidate (in my specific scenario).

The above code compiles but I do not know whether this would actually speed anything up if you had a large array of area objects in comparision to the speed of a single draw of the full buffer. Does anyone have any experience with this and any stats?

[quote=308496:@Denise Adams]
Secondly, can anyone tell me the most efficient/optimized way of copying a picture object?[/quote]

I have Einhugar’s PictureEffects plugin but don’t think it has a copy feature (unless I am mistaken) and I know some people have mentioned MBS plugins for this kind of task. It is also important to be able to preserve the transparency/alpha channel during the copy.

Once I know the best solution to this I intend to add this method to a global module so I can frequently call it rather than hard-coding the same Graphics.DrawPicture() calls each time in multiple places.

Thanks.

I sincerely doubt your Paint event is causing slowness. I suggest you use the profiler to find the real culprit.

I just ran a quick test.
Macbook 13in 2.9Ghz
Retina

Full window copy in the paint event
Copy done 1000 times during the paint event takes between 180 and 240 ticks
Thats 3…4 seconds to copy the buffer 1000 times.
Or put another way, in 1/24 second (animation speed), I could fit in between 9 to 13 full copy actions.
Imperceptable

[quote=308832:@Jeff Tullin]Or put another way, in 1/24 second (animation speed), I could fit in between 9 to 13 full copy actions.
Imperceptable[/quote]

Thanks, Jeff. That’s very helpful.

So to be clear, I typically use the code example below to make a copy of a picture object:

Dim p As New Picture(bufferPic.Width, bufferPic.Height, Screen(0).Depth)

p.Graphics.DrawPicture(bufferPic, 0, 0)

Is that the fastest/only way to do that? Can any external plugins can speed it up?

Use Profile ; you will see that it probably takes less than a microsecond.

Ok thanks Michel and Tim. I have not used Profiler before so will check it out.

You can use the following code to time a function.

Dim methodTimer as double = microseconds // ---- Do Thang system.DebugLog currentmethodName + " took: " + format( methodTimer / 1000000, "###,###,###,###.00" ) + " seconds"
I would time how long it took for your drawing to the picture buffer. You may find that it’s actually quicker to not use a buffer.

The other thing you can do, is use the area rects to copy out portions of your buffer, so it’s not redrawing the entire image when it doesn’t need to do so.

With declares you could do one or more of the following.

If this were 5 years ago and you’re developing a Mac application, I’d say use a CGLayer, these are GPU bound and stupidly fast. However Apple has decried that these are deprecated and haven’t worked properly for 3 years, all my bug reports go ignored.

You can gain a minor speed improvement, by making sure that your image is in the exact same pixel format as the display. This would take a lot initially, but it will save the the OS from having to convert pixel & color data.

[quote=308832:@Jeff Tullin]I just ran a quick test.
Macbook 13in 2.9Ghz
Retina[/quote]
Thats about 300 fps… I don’t mean to poo poo, but I don’t think it’s a fair test. Let me explain.

The macOS does employ caching, to what extent and how I don’t know, it could be going as far as saying, well your drawing the exact same image in the exact same location as last time, the pixel data hasn’t changed so I won’t bother.

I would imagine a more fair test would be to draw the picture once in the paint event. But also increment a frame count.
Then use a timer to refresh the canvas (don’t use invalidate as that would coalesce to the screen refresh rate) and see how many refreshes it could perform in say 5 seconds and then divide the total by 5. This still may not be a fair test as again we don’t know exactly how the OS caches the images and exactly under what circumstance would cause it to redraw.

I would imagine that a more fair test would alter the picture data before it calls refresh, then it times exactly how long a refresh takes, after 5 seconds it then averages that time to give you an indication of how long it takes to draw fresh pixel data.

Then my next step would be take my drawing code and to use a similar test but drawing directly to the canvas. I know you’ll get better results as you’re only drawing the data once, but you could at least tell by how much, maybe caching the drawing to a picture becomes irrelevant.

Very interesting.

Not actually important to me, since I only draw my buffer once in the Paint.
(The times are about the same if I am not drawing to a visible graphics context, tho.)

To me the difference is whether any ‘thinking’ happens in the Paint.
For example if you have 2000 things to draw in a loop and maybe some maths to work out where,
then taking that processing time out - by drawing the buffer elsewhere and having it ready to go - must be better than doing the loop code during the paint.

Back to the original question, I’d suggest you cannot dispense with the buffer IF you want to make use of the areas() method of painting just the changed parts.
For example, a fillrect or a drawn line that overlapped one of the area() would need to determine if any part of it did overlap…

Thanks all for your help. Greatly appreciated.