Draw three or nine part image

Mac OSX specific forum
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3942
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Draw three or nine part image

Post by wilbert »

Maybe someone can use this code. I was just curious how to do the asm for the x64 calling convention.
It uses Cocoa to draw a three or nine part image.
Images that are not on the corners are tiled, not stretched, to fill the required space.
All image parts need to be passed as ImageID.
The procedures can be used between StartDrawing() and StopDrawing() .

Code: Select all

Procedure DrawThreePartImage_addr()
  ProcedureReturn ?dtpi_start
  dtpi_start:
  !extern _NSDrawThreePartImage
  CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
    !sub rsp, 40
    !movq [rsp     ], xmm0
    !movq [rsp +  8], xmm1
    !movq [rsp + 16], xmm2
    !movq [rsp + 24], xmm3
    !mov r8, 2
    !mov eax, 1
    !cvtsi2sd xmm0, eax
    !xor r9, r9
    !call _NSDrawThreePartImage
    !add rsp, 40
  CompilerElse
    !lea edx, [esp + 4]
    !sub esp, 44
    !mov ecx, 28
    !dtpi_loop_x32:
    !mov eax, [edx + ecx]
    !mov [esp + ecx], eax
    !sub ecx, 4
    !jnc dtpi_loop_x32
    !mov dword [esp + 32], 2
    !mov dword [esp + 36], 0x3f800000
    !mov dword [esp + 40], 0
    !call _NSDrawThreePartImage
    !add esp, 44
  CompilerEndIf
  !ret
EndProcedure

Procedure DrawNinePartImage_addr()
  ProcedureReturn ?dnpi_start
  dnpi_start:
  !extern _NSDrawNinePartImage
  CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
    !sub rsp, 88
    !movq [rsp     ], xmm0
    !movq [rsp +  8], xmm1
    !movq [rsp + 16], xmm2
    !movq [rsp + 24], xmm3
    !mov rax, [rsp + 96] 
    !mov [rsp + 32], rax
    !mov rax, [rsp + 96 + 8] 
    !mov [rsp + 40], rax
    !mov rax, [rsp + 96 + 16] 
    !mov [rsp + 48], rax
    !mov rax, [rsp + 96 + 24] 
    !mov [rsp + 56], rax
    !mov rax, [rsp + 96 + 32] 
    !mov [rsp + 64], rax
    !mov eax, 1
    !cvtsi2sd xmm0, eax
    !call _NSDrawNinePartImage
    !add rsp, 88
  CompilerElse
    !lea edx, [esp + 4]
    !sub esp, 76
    !mov ecx, 48
    !dnpi_loop_x32:
    !mov eax, [edx + ecx]
    !mov [esp + ecx], eax
    !sub ecx, 4
    !jnc dnpi_loop_x32
    !mov dword [esp + 52], 2
    !mov dword [esp + 56], 0x3f800000
    !mov dword [esp + 60], 0
    !call _NSDrawNinePartImage
    !add esp, 76
  CompilerEndIf
  !ret
EndProcedure

CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
  Prototype DrawThreePartImage_proto(x.d, y.d, w.d, h.d, i1, i2, i3, vertical = #False)
  Prototype DrawNinePartImage_proto(x.d, y.d, w.d, h.d, i1, i2, i3, i4, i5, i6, i7, i8, i9)
CompilerElse
  PrototypeC DrawThreePartImage_proto(x.f, y.f, w.f, h.f, i1, i2, i3, vertical = #False)
  PrototypeC DrawNinePartImage_proto(x.f, y.f, w.f, h.f, i1, i2, i3, i4, i5, i6, i7, i8, i9)
CompilerEndIf

Global DrawThreePartImage.DrawThreePartImage_proto = DrawThreePartImage_addr()
Global DrawNinePartImage.DrawNinePartImage_proto = DrawNinePartImage_addr()
User avatar
grabiller
Enthusiast
Enthusiast
Posts: 309
Joined: Wed Jun 01, 2011 9:38 am
Location: France - 89220 Rogny-Les-Septs-Ecluses
Contact:

Re: Draw three or nine part image

Post by grabiller »

Thanks!

Is the fact that non-corners image being tiled is a limitation of the api or is it possible to switch that somehow ?
guy rabiller | radfac founder / ceo | raafal.org
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3942
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Draw three or nine part image

Post by wilbert »

grabiller wrote:Is the fact that non-corners image being tiled is a limitation of the api or is it possible to switch that somehow ?
I'm afraid it's a limitation if the api.

I looked at your source and saw how you are handling the drawing of a custom component skin.
Are you familiar with the actionscript language from the flash player ?
It uses a property named scale9Grid. What it does is divide an image into a grid with 9 regions based on the rectangle you specify.
Instead of creating really 9 images, you specify one source image and a rectangle that specifies the grid to use.
A procedure like that could be like

Procedure DrawImageWithScale9Grid(dstX, dstY, dstWidth, dstHeight, Image_ID, s9rect_x, s9rect_y, s9rect_width, s9rect_height)

Another option would be to place all arguments after dstHeight inside a structure since they usually don't change for a component.
By creating such a procedure, you wouldn't have to divide the image anymore.
When using PureBasic itself you probably would still have to do it inside the procedure.
When using cocoa, you can use the drawInRect:fromRect:operation:fraction: method from the NSImage class.
It allows you to specify a rectangle on the source image that needs to be used as a source, a destination rectangle, a compositing operation and opacity.
One drawback of cocoa however is that in a lot of times, it uses a flipped coordinate space where (0,0) at bottom left instead of top left.
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3942
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Draw three or nine part image

Post by wilbert »

Here's an example of the combo you posted in your other thread using cocoa.
It might not be the fastest or shortest way but it works fine and shows how a scaling grid could be used.

Code: Select all

Structure Scale9Image
  Image_ID.i
  Grid_TopLeftX.CGFloat
  Grid_TopLeftY.CGFloat
  Grid_BottomRightX.CGFloat
  Grid_BottomRightY.CGFloat
EndStructure


Procedure SetNSRectFlipped(*Rect.NSRect, x.CGFloat, y.CGFloat, width.CGFloat, height.CGFloat, ctxHeight.CGFloat)
  *Rect\origin\x = x
  *Rect\origin\y = ctxHeight - y - height
  *Rect\size\width = width
  *Rect\size\height = height
EndProcedure  


Procedure LoadImageEx(Image, Filename.s)
  Protected.i Result, Rep, Width, Height
  Protected Size.NSSize, Point.NSPoint
  CocoaMessage(@Rep, 0, "NSImageRep imageRepWithContentsOfFile:$", @Filename)
  If Rep
    CocoaMessage(@Width, Rep, "pixelsWide")
    CocoaMessage(@Height, Rep, "pixelsHigh")
    If Width And Height
      Size\width = Width
      Size\height = Height
      CocoaMessage(0, Rep, "setSize:@", @Size)
      Result = CreateImage(Image, Width, Height, 32 | #PB_Image_Transparent)
      If Result
        If Image = #PB_Any : Image = Result : EndIf
        If StartDrawing(ImageOutput(Image))
          CocoaMessage(0, Rep, "drawAtPoint:@", @Point)
          StopDrawing()
        EndIf
      EndIf
    EndIf  
  EndIf
  ProcedureReturn Result
EndProcedure


Procedure DrawScale9Image(*Image.Scale9Image, dstX.CGFloat, dstY.CGFloat, dstWidth.CGFloat, dstHeight.CGFloat)
  
  Protected imgSize.NSSize, src.NSRect, dst.NSRect, delta.CGFloat = 1.0
  CocoaMessage(@imgSize, *Image\Image_ID, "size")
  
  Protected fImg.CGFloat = imgSize\height
  Protected fOut.CGFloat = OutputHeight()
  
  Protected lw.CGFloat = *Image\Grid_TopLeftX
  Protected cw.CGFloat = *Image\Grid_BottomRightX - *Image\Grid_TopLeftX
  Protected rw.CGFloat = imgSize\width - *Image\Grid_BottomRightX
  Protected th.CGFloat = *Image\Grid_TopLeftY
  Protected ch.CGFloat = *Image\Grid_BottomRightY - *Image\Grid_TopLeftY
  Protected bh.CGFloat = imgSize\height - *Image\Grid_BottomRightY
  
  ; draw top left
  SetNSRectFlipped(@src, 0, 0, lw, th, fImg)
  SetNSRectFlipped(@dst, dstX, dstY, lw, th, fOut)
  CocoaMessage(0, *Image\Image_ID, "drawInRect:@", @dst, "fromRect:@", @src, "operation:", 2, "fraction:@", @delta)
  
  ; draw top center
  SetNSRectFlipped(@src, lw, 0, cw, th, fImg)
  SetNSRectFlipped(@dst, dstX + lw, dstY, dstWidth - lw - rw, th, fOut)
  CocoaMessage(0, *Image\Image_ID, "drawInRect:@", @dst, "fromRect:@", @src, "operation:", 2, "fraction:@", @delta)
  
  ; draw top right
  SetNSRectFlipped(@src, lw + cw, 0, rw, th, fImg)
  SetNSRectFlipped(@dst, dstX + dstWidth - lw - rw, dstY, rw, th, fOut)
  CocoaMessage(0, *Image\Image_ID, "drawInRect:@", @dst, "fromRect:@", @src, "operation:", 2, "fraction:@", @delta)
  
  ; draw center left
  SetNSRectFlipped(@src, 0, th, lw, ch, fImg)
  SetNSRectFlipped(@dst, dstX, dstY + th, lw, dstHeight - th - bh, fOut)
  CocoaMessage(0, *Image\Image_ID, "drawInRect:@", @dst, "fromRect:@", @src, "operation:", 2, "fraction:@", @delta)
  
  ; draw center center
  SetNSRectFlipped(@src, lw, th, cw, ch, fImg)
  SetNSRectFlipped(@dst, dstX + lw, dstY + th, dstWidth - lw - rw, dstHeight - th - bh, fOut)
  CocoaMessage(0, *Image\Image_ID, "drawInRect:@", @dst, "fromRect:@", @src, "operation:", 2, "fraction:@", @delta)
  
  ; draw center right
  SetNSRectFlipped(@src, lw + cw, th, rw, ch, fImg)
  SetNSRectFlipped(@dst, dstX + dstWidth - lw - rw, dstY + th, rw, dstHeight - th - bh, fOut)
  CocoaMessage(0, *Image\Image_ID, "drawInRect:@", @dst, "fromRect:@", @src, "operation:", 2, "fraction:@", @delta)
  
  ; draw bottom left
  SetNSRectFlipped(@src, 0, th + ch, lw, bh, fImg)
  SetNSRectFlipped(@dst, dstX, dstY + dstHeight - bh, lw, bh, fOut)
  CocoaMessage(0, *Image\Image_ID, "drawInRect:@", @dst, "fromRect:@", @src, "operation:", 2, "fraction:@", @delta)
  
  ; draw bottom center
  SetNSRectFlipped(@src, lw, th + ch, cw, bh, fImg)
  SetNSRectFlipped(@dst, dstX + lw, dstY + dstHeight - bh, dstWidth - lw - rw, bh, fOut)
  CocoaMessage(0, *Image\Image_ID, "drawInRect:@", @dst, "fromRect:@", @src, "operation:", 2, "fraction:@", @delta)
  
  ; draw bottom right
  SetNSRectFlipped(@src, lw + cw, th + ch, rw, bh, fImg)
  SetNSRectFlipped(@dst, dstX + dstWidth - lw - rw, dstY + dstHeight - bh, rw, bh, fOut)
  CocoaMessage(0, *Image\Image_ID, "drawInRect:@", @dst, "fromRect:@", @src, "operation:", 2, "fraction:@", @delta)
  
EndProcedure



; *** test code ***

Define Combo.Scale9Image

LoadImageEx(0, "dark.combo.up.normal.png")

Combo\Image_ID = ImageID(0)
Combo\Grid_TopLeftX = 6
Combo\Grid_TopLeftY = 0
Combo\Grid_BottomRightX = 152
Combo\Grid_BottomRightY = 21

Global xoff = 10
Global yoff = 20
Global xsiz = 180
Global ysiz = 100

OpenWindow(0, 0, 0, xsiz, ysiz, "Scale9Image",#PB_Window_SystemMenu|#PB_Window_ScreenCentered|#PB_Window_SizeGadget)
WindowBounds( mainWin, 100, 100, #PB_Ignore, #PB_Ignore )

CanvasGadget( 0, 0, 0, xsiz, ysiz )

Repeat
  
  Select WaitWindowEvent()
      
    Case #PB_Event_SizeWindow
      xsiz = WindowWidth (0)
      ysiz = WindowHeight(0)
      ResizeGadget(0, 0, 0, xsiz, ysiz)
      StartDrawing(CanvasOutput(0))
      Box(0, 0, xsiz, ysiz, RGB($48,$48,$48))
      DrawScale9Image(@Combo, xoff, yoff, xsiz - 45, 21)
      StopDrawing()
      
    Case #PB_Event_CloseWindow
      Break
      
  EndSelect
  
ForEver

End
The same can of course be done using native PureBasic commands if you use temporary images.
User avatar
grabiller
Enthusiast
Enthusiast
Posts: 309
Joined: Wed Jun 01, 2011 9:38 am
Location: France - 89220 Rogny-Les-Septs-Ecluses
Contact:

Re: Draw three or nine part image

Post by grabiller »

Thanks a lot wilbert, that's very informative.

I indeed split my images at initialization time because PB does not provide (afaik) a DrawImage version with which you can specify the source x/y/width/height like the StretchBlt function on Windows. If it was I would simply load the image and use it directly without creating 3 or 9 sub-images and use a grid system like you suggest.

In terms of speed I'm not sure it would make any difference though, but I'm trying to use native PB functions as much as possible and resort to platform API only if I don't have any other choice.

I'm trying to make my project crossplatform but I have to admit that right now I'm spending more time trying to make things work on the 3 platforms rather than coding features. So thanks again for making me better understand how things work on Mac OS/X.
guy rabiller | radfac founder / ceo | raafal.org
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3942
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Draw three or nine part image

Post by wilbert »

I understand the cross platform part.
Would be great if PureBasic itself would have a drawing function like that.
It would be a nice addition to work with the canvas gadget.
User avatar
grabiller
Enthusiast
Enthusiast
Posts: 309
Joined: Wed Jun 01, 2011 9:38 am
Location: France - 89220 Rogny-Les-Septs-Ecluses
Contact:

Re: Draw three or nine part image

Post by grabiller »

Yes indeed. I've just posted a feature request for this:
http://www.purebasic.fr/english/viewtop ... =3&t=51621
guy rabiller | radfac founder / ceo | raafal.org
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3942
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Draw three or nine part image

Post by wilbert »

I noticed the request :D

As for your earlier comment on speed difference, I was curious myself.
A CocoaMessage call itself takes much more time compared to a normal PureBasic function call because of the way things work internally.
Yet my speed tests indicate the CocoaMessage approach is about two to three times faster on my computer compared to the PureBasic drawing routines.
Maybe Cocoa uses SSE or the GPU for its drawing routines.
It's not a big deal because the PureBasic routines are still fast enough but I was surprised by the difference.
User avatar
grabiller
Enthusiast
Enthusiast
Posts: 309
Joined: Wed Jun 01, 2011 9:38 am
Location: France - 89220 Rogny-Les-Septs-Ecluses
Contact:

Re: Draw three or nine part image

Post by grabiller »

This is interresting, indeed. Is the Mac not supposed to used OpenGL for UI ?

Regarding the speed, so far I'm pretty happy with the PB speed.

For the ColorWheel I'm even using several filter callbacks and it's just ok. I wouldn't show the wheel fullscreen though, but for the max size I'm planning to show it (~400x400) it's fast enough.
guy rabiller | radfac founder / ceo | raafal.org
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3942
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Draw three or nine part image

Post by wilbert »

As far as I understand Cocoa uses "Quartz 2D" which is not as fast as OpenGL .
But like I said, speed is not really an issue.
Your combobox for example with size 435 x 21 pixels takes 0.44 milliseconds to draw using the PureBasic routines and 0.19 milliseconds using the Cocoa routines (3.06 Ghz Core2Duo).
So you probably won't notice the difference. :wink:
Post Reply