I just looked into a way to get the Dockitem.Graphics object into a Picture.Graphics, and all I could do was to use Pixel in a couple loops, which works, but is awfully slow.
Why is it not possible to have something like
graphics.Drawgraphics(othergraphics) ?
dim p as new picture(app.DockItem.Graphics.width, app.DockItem.Graphics.height)
for x as integer = 0 to app.DockItem.Graphics.width
for y as integer = 0 to app.DockItem.Graphics.height
p.RGBSurface.Pixel(x, y) = app.DockItem.Graphics.Pixel(x,y)
next
next
dim gin as graphics = app.DockItem.Graphics
dim p as new picture(gin.width, gin.height)
dim sout as RGBsurface = p.RGBsurface
for x as integer = 0 to gin.width
for y as integer = 0 to gin.height
sout.Pixel(x, y) = app.DockItem.Graphics.Pixel(x,y)
next
next
It may also help to do similar stuff to app.docitem.graphics and to copy gin.width and gin.height to local integers so that everything used in the loop is as resolved and fast as possible.
[quote=201911:@Michel Bujardet] dim p as new picture(app.DockItem.Graphics.width, app.DockItem.Graphics.height)
for x as integer = 0 to app.DockItem.Graphics.width
for y as integer = 0 to app.DockItem.Graphics.height
p.RGBSurface.Pixel(x, y) = app.DockItem.Graphics.Pixel(x,y)
next
next[/quote]
It used to be that swapping x and y loops would speed things up due to the way the data is laid out in memory. I have no idea if that is still true.
Unfortunately Graphics.Pixel is the bottleneck here. I was however able to get a 7x speedup using some standard xojo optimization techniques:
dim t0 as double = Microseconds
// first, do it the slow way
dim p as new picture(app.DockItem.Graphics.width, app.DockItem.Graphics.height)
for x as integer = 0 to app.DockItem.Graphics.width
for y as integer = 0 to app.DockItem.Graphics.height
p.RGBSurface.Pixel(x, y) = app.DockItem.Graphics.Pixel(x,y)
next
next
MsgBox "Slow way took " + format((Microseconds - t0)/ 1000, "0") + " msec"
// medium fast way
t0 = Microseconds
p = new picture(app.DockItem.Graphics.width, app.DockItem.Graphics.height)
for x as integer = 0 to app.DockItem.Graphics.width
for y as integer = 0 to app.DockItem.Graphics.height
p.RGBSurface.Pixel(x, y) = app.DockItem.Graphics.Pixel(x,y)
next
next
MsgBox "Medium way took " + format((Microseconds - t0)/ 1000, "0") + " msec"
// now, the fastest way, using extensive caching and optimization techniques
#Pragma DisableBackgroundTasks
t0 = Microseconds
dim g as Graphics = app.dockItem.Graphics
p = new picture(g.width,g.height)
dim ux as integer = g.width -1
dim uy as integer = g.height -1
dim r as RGBSurface = p.RGBSurface
for x as integer = 0 to ux
for y as integer = 0 to uy
r.Pixel(x, y) = g.Pixel(x,y)
next
next
MsgBox "Fast way took " + format((Microseconds - t0)/ 1000, "0") + " msec"
[quote=201941:@Michael Diehr]Unfortunately Graphics.Pixel is the bottleneck here. I was however able to get a 7x speedup using some standard xojo optimization techniques:
dim t0 as double = Microseconds
// first, do it the slow way
dim p as new picture(app.DockItem.Graphics.width, app.DockItem.Graphics.height)
for x as integer = 0 to app.DockItem.Graphics.width
for y as integer = 0 to app.DockItem.Graphics.height
p.RGBSurface.Pixel(x, y) = app.DockItem.Graphics.Pixel(x,y)
next
next
MsgBox "Slow way took " + format((Microseconds - t0)/ 1000, "0") + " msec"
// medium fast way
t0 = Microseconds
p = new picture(app.DockItem.Graphics.width, app.DockItem.Graphics.height)
for x as integer = 0 to app.DockItem.Graphics.width
for y as integer = 0 to app.DockItem.Graphics.height
p.RGBSurface.Pixel(x, y) = app.DockItem.Graphics.Pixel(x,y)
next
next
MsgBox "Medium way took " + format((Microseconds - t0)/ 1000, "0") + " msec"
// now, the fastest way, using extensive caching and optimization techniques
#Pragma DisableBackgroundTasks
t0 = Microseconds
dim g as Graphics = app.dockItem.Graphics
p = new picture(g.width,g.height)
dim ux as integer = g.width -1
dim uy as integer = g.height -1
dim r as RGBSurface = p.RGBSurface
for x as integer = 0 to ux
for y as integer = 0 to uy
r.Pixel(x, y) = g.Pixel(x,y)
next
next
MsgBox "Fast way took " + format((Microseconds - t0)/ 1000, "0") + " msec"
About 2x faster (so almost 20x faster than the first method, taking about 80 msec ). It’s completely un-tested so it may blow up your machine, but you can probably add error checking as needed.
Protected Function GetDockImageFast() As Picture
const CocoaLib = "Cocoa"
// Get the graphics of the picture as CGContext
dim g as graphics = app.dockItem.Graphics
dim pic as new picture(g.width,g.height)
dim w,h as integer = 1024
// get the CGContext from the dockItem
dim cntx as Ptr = ptr(g.Handle(Graphics.HandleTypeCGContextRef))
// get a pointer to the image data
declare function CGBitmapContextGetData lib CocoaLib (obj_id as Ptr) as Ptr
dim p as ptr = CGBitmapContextGetData(cntx)
declare function CGBitmapContextGetBytesPerRow lib CocoaLib (obj_id as Ptr) as Integer
dim rowBytes as integer = CGBitmapContextGetBytesPerRow(cntx)
#Pragma DisableBackgroundTasks
// now convert raw data into picture
dim ux as integer = g.width -1
dim uy as integer = g.height -1
dim r as RGBSurface = pic.RGBSurface
dim indx as integer //. index into pointer
for y as integer = 0 to uy
for x as integer = 0 to ux
// this is what we are doing algorithmically :
'dim red,green,blue,alpha as uint8
'alpha = p.uint8(indx)
'indx=indx+1
'alpha = 255-alpha // invert alpha properly for Xojo
'red = p.uint8(indx)
'indx=indx+1
'green = p.uint8(indx)
'indx=indx+1
'blue = p.uint8(indx)
'indx=indx+1
'dim c as color = RGB(red,green,blue,alpha)
'r.Pixel(x, y) = c
// this is a super fast way of doing it that's not easy to understand
dim ci as uint32 = ( (255 - p.Uint8(indx+0) ) *256*256*256 ) + ( p.Uint8(indx+1) * 256*256) + (p.Uint8(indx+2) * 256) + ( p.Uint8(indx+3) * 1)
indx=indx+4
r.pixel(x,y) = color(ci)
next
next
Return pic
End Function
[quote=201954:@Chris Carter]Would this be a bit faster if you put the allocaton of y outside of the loops so it is not allocated/deallocated every x loop ?
[/quote]
Indeed it might, although in this case I don’t think it matters much since the real bottleneck is the function call to .Pixel()
[quote=201941:@Michael Diehr] // first, do it the slow way
dim p as new picture(app.DockItem.Graphics.width, app.DockItem.Graphics.height)
for x as integer = 0 to app.DockItem.Graphics.width
for y as integer = 0 to app.DockItem.Graphics.height
p.RGBSurface.Pixel(x, y) = app.DockItem.Graphics.Pixel(x,y)
next
next
MsgBox “Slow way took " + format((Microseconds - t0)/ 1000, “0”) + " msec”
// medium fast way
t0 = Microseconds
p = new picture(app.DockItem.Graphics.width, app.DockItem.Graphics.height)
for x as integer = 0 to app.DockItem.Graphics.width
for y as integer = 0 to app.DockItem.Graphics.height
p.RGBSurface.Pixel(x, y) = app.DockItem.Graphics.Pixel(x,y)
next
next[/quote]
Where am I missing it? These two bits of code look the same…
Declare Function CGBitmapContextCreateImage Lib "CoreGraphics" (context as integer) As Ptr
Will give you a CGImageRef, which you can then draw into a Xojo picture’s graphics context (via CGContextDrawImage).
Declare Function CGBitmapContextCreateImage Lib "CoreGraphics" (context as integer) As Ptr
Will give you a CGImageRef, which you can then draw into a Xojo picture’s graphics context (via CGContextDrawImage).[/quote]
This only works for bitmap contexts, which isn’t what Graphics.Handle always returns.
Sam, thanks - it turns out in this case, it does work.
So here’s the latest version, which runs in 6 msec (which is about 230x as fast as the original code which took 1400+ msec):
Usual caveats apply : this is untested and may blow up your system, etc.
Protected Function GetDockImageFast4() As Picture
// Fastest way yet to convert App.DockItem.Graphics into a Xojo Picture
// as discussed https://forum.xojo.com/24237-why-no-simple-way-to-copy-a-graphics-object-into-another
// This version of code inspired by https://forum.xojo.com/9403-scale-quality-of-canvas-control/p5#p195272
//
// Note: needs a NSRect (or CGRect) structure (they are identical) which is defined as
// struct NSRect { x as single, y as single, width as single, height as single }
// Get the graphics of the picture as CGContext
dim g as graphics = app.dockItem.Graphics
dim w,h as integer
w = g.width
h = g.height
// make a new empty picture
dim pic as new Picture(w,h)
// get the CGContext from the dockItem
dim cntx as Ptr = ptr(g.Handle(Graphics.HandleTypeCGContextRef))
// sometimes you can get an image from a CGContext
Declare Function CGBitmapContextCreateImage Lib "CoreGraphics" (context as Ptr) As Ptr
Declare sub CGContextDrawImage lib "Cocoa" (cntxt As Ptr, r As NSRect, img As Ptr)
dim pSrc as ptr = CGBitmapContextCreateImage(cntx)
dim pDst as ptr = Ptr( pic.graphics.handle(Graphics.HandleTypeCGContextRef) )
// draw source into dest
dim r as NSRect
r.width = w
r.height = h
CGContextDrawImage(pDst,r,pSrc)
Return pic
End Function