Why no simple way to copy a graphics object into another ?

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) ?

Use graphics.clip?

Very, when it comes to a 1024 x 1024 picture…

Show us your code, we can probably speed it up?

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=201853:@Michel Bujardet]Why is it not possible to have something like
graphics.Drawgraphics(othergraphics) ?[/quote]

A Graphics object isn’t always something you can read from. For example, there’s no reading back what you wrote to a printer.

Maybe a Picture.Clone method is what you want if it existed (which it does not)?

Something along these lines may help (untested):-

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.

It wouldn’t make a difference for this since it’s not bound by memory accesses into a buffer.

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"

Results are:

  • 1472 msec for Slow
  • 1438 msec for Medium
  • 164 msec for Fast (almost 9x faster)

[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"

Results are:

  • 1472 msec for Slow
  • 1438 msec for Medium
  • 164 msec for Fast (almost 9x faster)[/quote]

Thank you.

Even Faster:

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

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 ?

dim  y as integer
for x as integer = 0 to ux
    for y = 0 to uy
      r.Pixel(x, y) = g.Pixel(x,y)
    next
  next

[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…

What did you do different?

[quote=201968:@Jon Ogden]Where am I missing it? These two bits of code look the same…
What did you do different?[/quote]

Oops - I had meant for the ‘medium’ way to cache the RGBSurface as per Michel’s suggestion. I neglected to do so.

With that change, I get about 1100msec (down from 1500) so a 25% speedup for that one change alone, which reinforces the idea that code like this:

  for i = 1 to foobar
     object.objectA.objectB.methodC = i

is very pretty and easy to read, it’s going to be much slower than caching the object, e.g.:

  dim o as ObjectB = object.objectA.objectB
  for i = 1 to foobar
    o.methodC = i

@Michael Diehr

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=202003:@Sam Rowlands]@Michael Diehr

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