Page 1 of 1

Skinning window

Posted: Mon Apr 03, 2006 5:47 am
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.

Posted: Mon Apr 03, 2006 6:45 am
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.

Posted: Mon Apr 03, 2006 9:53 am
by Dare2
Hi netmaestro,

Thanks, and thanks for the info on SmartWindowRefresh.

Posted: Fri Apr 28, 2006 11:57 am
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.

Posted: Fri Apr 28, 2006 2:11 pm
by Inf0Byt3
Check this out, maybe you can combine it with the other methods :
http://purebasic.myftp.org/?filename=fi ... n/Test.zip

Posted: Fri Apr 28, 2006 10:13 pm
by mskuma
Inf0Byt3, thanks very much - it's great! Just what I was looking for.

Thanks again to Dare2 for the original post.

Posted: Sat Apr 29, 2006 12:17 am
by Inf0Byt3
No problem ;)

Posted: Sat Apr 29, 2006 12:25 am
by Inf0Byt3
Now let's combine Dare2's code with mine and see what happenns :D.

Posted: Sat Apr 29, 2006 4:56 am
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!

Posted: Sat Apr 29, 2006 5:07 am
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. :)

Posted: Thu May 04, 2006 12:36 am
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.

Posted: Thu May 04, 2006 2:28 am
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.

Posted: Fri Jun 02, 2006 10:04 pm
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.