Skinning window

Everything else that doesn't fall into one of the other PB categories.
Dare2
Moderator
Moderator
Posts: 3321
Joined: Sat Dec 27, 2003 3:55 am
Location: Great Southern Land

Skinning window

Post by Dare2 »

With all the bad press skins get and given that excellent alternatives exist, I am not sure it this is of interest to anyone.

Creates a skin for a window, and uses it.

Bare bones stuff (for example, no error checking, files not compressed, etc, etc).

Creating a "kwikskin":

Code: Select all

; Dare2
; CreateKwikSkin
;  PureBasic 4.00
;
; I looked at some code in the archive at PureArea.net
; This borrows from code by: Mischa.
; Many thanks to Mischa.
;
; Note: Anything crap was introduced by me.
;

#imgNum  = 1
#fileNum = 1

Structure UDT_ks_info                            ; Basic info for skin
  imgWide.l                                      ; Width of image
  imgHigh.l                                      ; Height of image
  maskSize.l                                     ; Size of mask (bytes)
  picSize.l                                      ; Size of image (bytes)
EndStructure

UsePNGImageEncoder()                             ; So we can use BMP and PNG
UsePNGImageDecoder()                             ; jpg is not recommended, but your call

; srcFile   is the image to use as the skin
; destFile  is the file create and containing skin information
; transCol  is the RGB(r,g,b) of the transparent colour

Procedure CreateMask(srcFile.s,destFile.s,transCol)
  Define skin.UDT_ks_info
  Define bmpInfo.BITMAPINFOHEADER

  image=LoadImage(#imgNum,srcFile)               ; Get the image
  wide = ImageWidth(#imgNum)                     ; Get width and height
  high = ImageHeight(#imgNum)
  mem = AllocateMemory(wide*high*4)              ; Allocate some space

  hDC = StartDrawing(ImageOutput(#imgNum))       ; Need a DC, get via StartDrawing

  hMask  = CreateRectRgn_(0,0,0,0)               ; Create an empty mask

  bmpInfo\biSize = SizeOf(BITMAPINFOHEADER)      ; Set up the bmp header info
  bmpInfo\biWidth  = wide 
  bmpInfo\biHeight = high 
  bmpInfo\biPlanes = 1 
  bmpInfo\biBitCount = 32 
  bmpInfo\biCompression = #BI_RGB
                                                 ; Make a DIB from the bitmap
  GetDIBits_(hDC,ImageID(#imgNum), 1, high-1, mem, bmpInfo, #DIB_RGB_COLORS)

  ptr=mem                                        ; Scan through the image ignoring the
  For vert = 0 To high-1                         ;   transCol pixels and create tiny
    For horz = 0 To wide-1                       ;   regions then ORing these with the
      If PeekL(ptr) <> transCol                  ;   initial mask region
        tmpRgn = CreateRectRgn_(horz, high-vert-1, horz+1, high-vert-2)
        CombineRgn_(hMask, hMask, tmpRgn, #RGN_OR)
        DeleteObject_(tmpRgn)                    ; Get rid of the temporary region
      EndIf 
      ptr+4                                      ; Next pixel, please.
    Next
  Next 
  StopDrawing()                                  ; Done.

  skin\imgWide = wide                            ; Store our basic information (should have done
  skin\imgHigh = high                            ;   this directly rather than via wide/high vars)
  skin\maskSize = GetRegionData_(hMask,0,0)
  skin\picSize = FileSize(srcFile)

  pic = AllocateMemory(skin\picSize)             ; Load the image file 
  OpenFile(#fileNum, srcFile)
  ReadData(#fileNum, pic,skin\picSize)
  CloseFile(#fileNum)

  skn=AllocateMemory(skin\maskSize)              ; Copy the mask information
  GetRegionData_(hMask, skin\maskSize, skn)

  CreateFile(#fileNum, destFile)                 ; Make our (uncompressed) skin file
  WriteData(#fileNum, @skin, SizeOf(UDT_ks_info))
  WriteData(#fileNum, skn, skin\maskSize)
  WriteData(#fileNum, pic, skin\picSize)
  CloseFile(#fileNum)

  FreeMemory(skn)                                ; Clean up
  FreeMemory(pic)
  DeleteObject_(hMask)
EndProcedure 


; Note - depending on the size and complexity of your file, this can have a noticable pause.

srcFile.s = "C:\path\to\your\bmp\or\png\image"
wk.s = GetExtensionPart(srcFile)
destFile.s = Left(srcFile,Len(srcFile)-Len(wk)) + "skn"

CreateMask(srcFile, destFile, RGB(255,0,255))  ; Source,Destination and the transparent color
End
Using a "kwikskin":

Code: Select all

; Dare2.
; KwikSkin
;
; PureBasic 4
;
; Two parts:
; First part is a simple window skinner.
; Second part is an example of skinning.
;
; No error checking, etc, just some basic stuff and a loose end.
;
; You need your own image and to have created the skin file.
;
; Right click on the window to exit.
;
; All my own work, I can't blame anyone else  :(
; Any similarity to any other code is purely co-incidental
; however for those who demand credit for perceived similarities:
;      --> Put your name here <--
;

Structure UDT_ks_info                            ; Basic skin info (stored in file)
  imgWide.l                                      ; How wide
  imgHigh.l                                      ; How high
  maskSize.l                                     ; Bytes used for mask
  picSize.l                                      ; Bytes used for image 
EndStructure

Structure UDT_ks_plus                            ; Extended skin info, used in app
  kwik.UDT_ks_info                               ; The basic info
  maskAddress.l                                  ; Memory address of mask
  picAddress.l                                   ; Memory address of image
  maskHandle.l                                   ; handle of mask
  winPBnumber.l                                  ; Window number (pb number)
  imgHandle.l                                    ; Image handle
EndStructure

UsePNGImageEncoder()                             ; So we can use PNG
UsePNGImageDecoder() 

; Draws the image onto the window.
; Example of what should be called whenever window needs refreshing/repainting.
; The parameter passed in is the value retrieved from ks_LoadSkin

Procedure ks_DrawWindow(*kwikSkin.UDT_ks_plus)
  StartDrawing(WindowOutput(*kwikSkin\winPBnumber)) 
    DrawImage(ImageID(*kwikSkin\imgHandle), 0, 0) 
  StopDrawing() 
EndProcedure

; Loads a kwikskin file and prepares for use.
; Must call ks_ReleaseSkin for each skin loaded.

Procedure ks_LoadSkin(srcFile.s)
  Define *kwikSkin.UDT_ks_plus
  Define kwikMem.l
  Define hFile.l

  kwikMem=AllocateMemory(SizeOf(UDT_ks_info))
  *kwikSkin=kwikMem
  hFile=OpenFile(#PB_Any,srcFile)
  ReadData(hFile,kwikMem,SizeOf(UDT_ks_info))
  *kwikSkin\maskAddress=AllocateMemory(*kwikSkin\kwik\maskSize)
  *kwikSkin\picAddress=AllocateMemory(*kwikSkin\kwik\picSize)

  ReadData(hFile,*kwikSkin\maskAddress,*kwikSkin\kwik\maskSize)
  ReadData(hFile,*kwikSkin\picAddress,*kwikSkin\kwik\picSize)
  CloseFile(hFile)
  ProcedureReturn kwikMem
EndProcedure

; Releases a skin and associated resources
; The parameter passed in is the value retrieved from ks_LoadSkin

Procedure ks_ReleaseSkin(*kwikSkin.UDT_ks_plus)
  DeleteObject_(*kwikSkin\maskHandle) 
  FreeMemory(*kwikSkin\maskAddress)
  FreeMemory(*kwikSkin\picAddress)
EndProcedure

; Skins the window
; The winNumber parameter is the PureBasic number (not the internal or OS value) of the window.
; The skinPtr parameter passed in is the value retrieved from ks_LoadSkin
; (For some reason it will not accept *kwikSkin.UDT_ks_plus as an arg instead of skinPtr)

Procedure ks_SkinWindow(winNumber,skinPtr)
  Define *kwikSkin.UDT_ks_plus
  *kwikSkin=skinPtr
  *kwikSkin\maskHandle=ExtCreateRegion_(0,*kwikSkin\kwik\maskSize,*kwikSkin\maskAddress)
  SetWindowRgn_(WindowID(winNumber),*kwikSkin\maskHandle,#True)
  *kwikSkin\winPBnumber=winNumber
  *kwikSkin\imgHandle = CatchImage(#PB_Any,*kwikSkin\picAddress)
  Debug *kwikSkin\picAddress
  Debug *kwikSkin\kwik\picSize
  Debug *kwikSkin\imgHandle
  ks_DrawWindow(skinPtr)
EndProcedure

; Retrieves the width of the skin
; The parameter passed in is the value retrieved from ks_LoadSkin

Procedure ks_GetSkinWidth(*kwikSkin.UDT_ks_plus)
  ProcedureReturn *kwikSkin\kwik\imgWide
EndProcedure

; Retrieves the height of the skin
; The parameter passed in is the value retrieved from ks_LoadSkin

Procedure ks_GetSkinHeight(*kwikSkin.UDT_ks_plus)
  ProcedureReturn *kwikSkin\kwik\imgHigh
EndProcedure

;-----------------------------------
; The above being the include file
;-----------------------------------
; The below being an example of use
;-----------------------------------

Global kSkin.l                    ; This is a loose end. Needed as global for the callback proc.


; Callback proc

Procedure WindowCallBack(WindowId, Message, lParam, wParam) 
  If Message = #WM_PAINT 
    ks_DrawWindow(kSkin)
  EndIf 
  ProcedureReturn #PB_ProcessPureBasicEvents 
EndProcedure 


file.s="C:\path\to\your\skin\file"

kSkin = ks_LoadSkin(file)                  ; Load the skin
w = ks_GetSkinWidth(kSkin)                 ; Retrieve width (just because we can) :)
h = ks_GetSkinHeight(kSkin)                ; Retrieve height.

; Our window must be borderless otherwise strange things happen!

hWnd = OpenWindow(0, 10,10, w,h, "Test", #PB_Window_Invisible|#PB_Window_BorderLess) 

SetWindowCallback(@WindowCallback())       ; Set our callback
SmartWindowRefresh(0,#True)                ; Not sure if this helps but it doesn't seem to hurt.
ks_SkinWindow(0,kSkin)                     ; Apply our loaded skin

HideWindow(0,#False)                       ; Unhide the window

Repeat                                     ; Hang around until the RIGHT mouse button is clicked.
  Select WaitWindowEvent() 
    Case #WM_LBUTTONDOWN 
      SendMessage_(hwnd,#WM_NCLBUTTONDOWN, #HTCAPTION,0) 
    Case #WM_RBUTTONDOWN 
      Quit=1 
  EndSelect 
Until Quit=1 

ks_ReleaseSkin(kSkin)                      ; Release the skin
End
Any advice on improvements, any pointers to traps I have fallen into, are appreciated. If you happen to find it useful, it is yours to use.
@}--`--,-- A rose by any other name ..
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8452
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

Nice work, Dare2, looks very good, and should interest many users. Thanks for sharing it. A tiny observation based on:
SmartWindowRefresh() is only useful when you resize a lot of gadget (so only on resizable window), it try to avoid unneeded painting -fred
...is that the SmartWindowRefresh is probably redundant and just taking up space in the exe.
BERESHEIT
Dare2
Moderator
Moderator
Posts: 3321
Joined: Sat Dec 27, 2003 3:55 am
Location: Great Southern Land

Post by Dare2 »

Hi netmaestro,

Thanks, and thanks for the info on SmartWindowRefresh.
@}--`--,-- A rose by any other name ..
mskuma
Enthusiast
Enthusiast
Posts: 573
Joined: Sat Dec 03, 2005 1:31 am
Location: Australia

Post by mskuma »

Dare2, thanks alot for taking the time to present this - this area is interesting to me as a learning exercise.

This reminded me of a similar code suggestion by netmaestro in this post (link here). So I compared these two methods (visually). The outcome seems the same but I noticed some minor visual differences - this is really esoteric I know, but maybe this can lead to some more learning.. so I mention FWIW. On my machine I noticed the following visual differences between the two methods:

Dare2's method: dragging around the window caused a trailing white area on the screen that eventually got filled by the OS.

Netmaestro's method: No such dragging/trailing artefact, however upon initial window display, a (grey solid?) rectangle very briefly flashes before it is replaced by the skin. There is no rectangle flash in Dare2's example.

Any improvement clues are welcome.
Inf0Byt3
PureBasic Fanatic
PureBasic Fanatic
Posts: 2236
Joined: Fri Dec 09, 2005 12:15 pm
Location: Elbonia

Post by Inf0Byt3 »

Check this out, maybe you can combine it with the other methods :
http://purebasic.myftp.org/?filename=fi ... n/Test.zip
None are more hopelessly enslaved than those who falsely believe they are free. (Goethe)
mskuma
Enthusiast
Enthusiast
Posts: 573
Joined: Sat Dec 03, 2005 1:31 am
Location: Australia

Post by mskuma »

Inf0Byt3, thanks very much - it's great! Just what I was looking for.

Thanks again to Dare2 for the original post.
Inf0Byt3
PureBasic Fanatic
PureBasic Fanatic
Posts: 2236
Joined: Fri Dec 09, 2005 12:15 pm
Location: Elbonia

Post by Inf0Byt3 »

No problem ;)
None are more hopelessly enslaved than those who falsely believe they are free. (Goethe)
Inf0Byt3
PureBasic Fanatic
PureBasic Fanatic
Posts: 2236
Joined: Fri Dec 09, 2005 12:15 pm
Location: Elbonia

Post by Inf0Byt3 »

Now let's combine Dare2's code with mine and see what happenns :D.
None are more hopelessly enslaved than those who falsely believe they are free. (Goethe)
Dare2
Moderator
Moderator
Posts: 3321
Joined: Sat Dec 27, 2003 3:55 am
Location: Great Southern Land

Post by Dare2 »

Hi mskuma,

Thanks for the thanks, mate. I also did this for learning, which is probably pretty obvious. :) If you know C (and were not aware of this) there is Danilo's skinwinfast sources in PBOSL. It may be of interest to you. It was all C to me. :)

If you get a neat skinner going without flicker, trailing artifacts, etc, would you give some clues on how it was managed?

Thanks!
@}--`--,-- A rose by any other name ..
mskuma
Enthusiast
Enthusiast
Posts: 573
Joined: Sat Dec 03, 2005 1:31 am
Location: Australia

Post by mskuma »

Hi Dare2
Dare2 wrote:If you get a neat skinner going without flicker, trailing artifacts, etc, would you give some clues on how it was managed?
I've only had a quick look so far, but Inf0Byt3's code was flicker-free. It also seems to be short, sweet & elegant. I recommend you check it out if not already done so. :)
dagcrack
Addict
Addict
Posts: 1868
Joined: Sun Mar 07, 2004 8:47 am
Location: Argentina
Contact:

Post by dagcrack »

It's not really elegant since it doesnt support dynamic windows, etc.

A real skinning library would skin windows gadgets. So you plug it in your existant application and it will be skinned. That is the ideal thing.

For an all-custom gadget system, you'll sure have less "limitations" with the widgets, etc... But it will be just a PITA to implement if you didnt design it well. Also, what about if you don't want the application to be skinned?. Hence, you need a system that performs real skinning so you can either enable/disable it on demand.
! Black holes are where God divided by zero !
My little blog!
(Not for the faint hearted!)
mskuma
Enthusiast
Enthusiast
Posts: 573
Joined: Sat Dec 03, 2005 1:31 am
Location: Australia

Post by mskuma »

dagcrack - I agree with you. I was just thinking before about buttons in particular, e.g. if you wanted to have different sized buttons, it would cause some pain since it would be necessary to build a resizing procedure based on the left, central & right part of the bitmap. This would enable buttons of any width. This example alone highlights the benefit of a comprehensive skinning solution (like some of the ones available).

I also very much agree about giving the user choice (to turn it off or even change it themselves if other skins are available). I think for some special application for vertical market or novice users, it may be possible to use the above kindly-offered solutions as a single look-and-feel approach, but maybe for a more general population, a more flexible solution is nicer - ideally one that doesn't require a DLL to facilitate it! (but can't have everything :wink: ) Thanks.
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

Anyone else get an invalid memory access error when closing the program -at line 77?

Code: Select all

FreeMemory(*kwikSkin\maskAddress)
Everything seems okay, so I can't figure out why it's crashing.
I may look like a mule, but I'm not a complete ass.
Post Reply