Page 3 of 3

Re: You could write 2D games on the CanvasGadget

Posted: Sun Sep 23, 2012 12:04 pm
by grabiller
Demivec wrote:../.. How can you tell if the default image used for the CanvasGadget is 24bit or 32bit? I know the ImageID of the image can be retrieved but that is as far as I can get without API.
I also would like to know this information. It seems CanvasGadget has 24bit depth surface as doing Alpha(Point(..)) after drawing some alpha value with DrawingMode( #PB_2DDrawing_AlphaChannel ) always returns 0.

Is there a way to set the CanvasGadget surface to be 32bit depth ?

(I'm not looking at creating transparent CanvasGadget but to encode data in the Alpha channel for picking)

Re: You could write 2D games on the CanvasGadget

Posted: Sun Sep 23, 2012 4:49 pm
by netmaestro
My solution for this problem would be to mirror the canvas image in memory using a 32bit transparent image, then you can receive x and y coordinates from the canvas and test their alpha values from the mirror. An imagemap, basically. Here is an example of what I mean:

Code: Select all

CreateImage(0, 256,256,32|#PB_Image_Transparent)
StartDrawing(ImageOutput(0))
  DrawingMode(#PB_2DDrawing_AlphaBlend|#PB_2DDrawing_Gradient)
  FrontColor(RGBA(0,0,0,0))
  BackColor(RGBA(0,0,255,255))
  CircularGradient(127, 127, 127)    
  Circle(127, 127, 127)
StopDrawing()

OpenWindow(0,0,0,640,480,"",#PB_Window_ScreenCentered|#PB_Window_SystemMenu)
CanvasGadget(0,120,20,256,256)
ProgressBarGadget(1,90,20,20,256,0,255,#PB_ProgressBar_Smooth|#PB_ProgressBar_Vertical)
StartDrawing(CanvasOutput(0))
  DrawAlphaImage(ImageID(0),0,0)
StopDrawing()

Repeat
  EventID = WaitWindowEvent()
  Select EventID
    Case #PB_Event_Gadget
      Select EventGadget()
        Case 0
          Select EventType()
            Case #PB_EventType_MouseMove
              x=GetGadgetAttribute(0,#PB_Canvas_MouseX)
              y=GetGadgetAttribute(0,#PB_Canvas_MouseY)
              StartDrawing(ImageOutput(0))
                DrawingMode(#PB_2DDrawing_AlphaBlend)
                SetGadgetState(1, Alpha(Point(x,y)))
              StopDrawing()
          EndSelect
      EndSelect
  EndSelect
  
Until EventID = #PB_Event_CloseWindow

Re: You could write 2D games on the CanvasGadget

Posted: Sun Sep 23, 2012 4:57 pm
by grabiller
That's exactly what I'm doing right now as a workaround except I'm not using a 32bit image, no need (it is never displayed), just a 24bit and I just encode the data in the Red Channel.

To bad we can't create 8bit images. It's a waste of memory as 2 channels are then not used (Green & Blue).

Of course, ideally CanvasGadget should support 32bit surface.

Re: You could write 2D games on the CanvasGadget

Posted: Sun Sep 23, 2012 10:35 pm
by netmaestro
Of course, ideally CanvasGadget should support 32bit surface.
The canvas gadget will probably never support 32bit depth, for one very good reason: It would kill performance for animations dead. In order to support 32bit depths, each refresh of the gadget would have to go through all the alphablending operations needed to render the output properly and that is just way too slow for anything animated or really anything that needs updated a lot. This change was made for the Image Gadget a few years ago and everyone found out very quickly that if you need animation or a lot of fast updates you'd better have a 24bit image in there or you're going to be one sad sack indeed.
To bad we can't create 8bit images. It's a waste of memory as 2 channels are then not used (Green & Blue).
8 bit images contain a 256-entry color table of RGBQUAD values and an array of bytes each of which contains the 0-255 index location of the color to represent. There is no green or blue channel to remain unused. However, if an 8 bit image can be of use to you, and if you're using Windows, I've done some work in that area which you are welcome to use. It will convert any image to 8bit depth and/or save as BMP or GIF:

Code: Select all

;===================================================================================== 
; Library commands:       ConvertTo8bit(), Save8bitImage(), SaveGIF()
; Author:                 Lloyd Gallant (netmaestro), luis
; Date:                   December 12, 2008 
; Target OS:              Microsoft Windows All 
; Target Compiler:        PureBasic 4.40 and later 
; License:                Free, unrestricted, no warranty 
;            
; Usage: ConvertTo8bit(hImageIn, dither) 
; 
;        hImageIn: Is the 16,24 or 32bit image handle to reduce to 8bit depth 
;        dither:   boolean telling the function whether or not to 
;                  apply Floyd-Steinberg dithering to the image
; 
; Usage: Save8bitImage(image, filename$ [,memory]) 
; 
;        image:     Is an 8bit image handle to save to disk or memory 
;        filename$: Is the name to save it to. 
;        memory:    Is a boolean which if true, will cause the procedure to return 
;                   a memory block containing the complete bitmap file. You may 
;                   compress this and send it over a network or catch the image 
;                   from the returned pointer as desired. You must free the pointer 
;                   when you're finished to avoid a memory leak. 
;
; Usage: SaveGIF(hImagein, outpath$)
;         
;        hImageIn: Is the handle of the image to reduce to 8bit depth and save as GIF
;        outpath$  Is the pathname/filename to save it to.
;
;===================================================================================== 


;/* NeuQuant Neural-Net Quantization Algorithm 
; * Copyright (c) 1994 Anthony Dekker
; *
; * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994.
; * See "Kohonen neural networks for optimal colour quantization"
; * in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367.
; * for a discussion of the algorithm.
; * See also  http://members.ozemail.com.au/~dekker/NEUQUANT.HTML
; *
; * Any party obtaining a copy of these files from the author, directly or
; * indirectly, is granted, free of charge, a full and unrestricted irrevocable,
; * world-wide, paid up, royalty-free, nonexclusive right and license to deal
; * in this software and documentation files (the "Software"), including without
; * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
; * and/or sell copies of the Software, and to permit persons who receive
; * copies from any such party to do so, with the only requirement being
; * that this copyright notice remain intact.
; */

; Ported to PureBasic 4.31-4.40 by Luis on Oct 2009 
; http://luis.no-ip.net
; The code in this include *should* be cross / platform. Tested on Windows 32/64 bit.
; Pfew! A lot or pointers' arithmetic to convert !

; Pseudo code usage example for PB:
;
; [ obtain a pointer to the color data bitmap in memory ]
;
; // please note the color data must be a sequence of BGRA quadruplets (32 bit/pixel) and that the A channel is ignored
; // allocated space would be = (4 * ImageWidth * ImageHeight) 
;
; NQ_InitNetwork() ; initialize network
; NQ_Learn (*ImageBuf, MemorySize(*ImageBuf), [1..30]) ; learning 
; NQ_UnbiasNetwork() ; network unbias
;
; // at this point the optimized palette has been calculated and it's in g_NQ_Network() 
; // g_NQ_Network(0..255)\iBGRC[0] ; BLUE 
; // g_NQ_Network(0..255)\iBGRC[1] ; GREEN 
; // g_NQ_Network(0..255)\iBGRC[2] ; RED 
; 
; NQ_SortNetwork() ; optimize the palette order for the lookup phase
;
; // now you loop through every pixel in the original 24/32 bit image and retrieve the palette index to be used in the 8 bit image 
; 
; [read B, G, R data from source image ]
; iIndex = NQ_LookupPalette(B, G, R) 
; [ write iIndex to the destination image ]


Structure T_RGBTRIPLE 
 rgbtBlue.b
 rgbtGreen.b
 rgbtRed.b
EndStructure

Structure T_PIXEL
 iBGRC.i[4]
EndStructure

Declare.i 	_NQ_Contest (b, g, r)
Declare 	_NQ_AlterSingle (alpha, i, b, g, r)
Declare 	_NQ_AlterNeigh (rad, i, b, g, r)

Declare 	NQ_InitNetwork ()
Declare 	NQ_UnbiasNetwork()
Declare 	NQ_SortNetwork()
Declare.i 	NQ_LookupPalette (b,g,r)
Declare.i 	NQ_Learn (*ImageBuffer.T_RGBTRIPLE, iBufferLen, iSample)

#NQ_netsize = 256  ; number of colours used

; four primes near 500 - assume no image has a length so large
; that it is divisible by all four primes 
#NQ_prime1  = 499
#NQ_prime2  = 491
#NQ_prime3	= 487
#NQ_prime4	= 503

#NQ_minpicturebytes     = 3 * #NQ_prime4 ; minimum size for input image 

; Network Definitions   
#NQ_maxnetpos           = #NQ_netsize - 1
#NQ_netbiasshift        = 4	  ; bias for colour values 
#NQ_ncycles	            = 100 ; no. of learning cycles 

; Defs for freq and bias
#NQ_intbiasshift        = 16 ; bias for fractions 
#NQ_intbias	            = 1 << #NQ_intbiasshift
#NQ_gammashift          = 10 ; gamma = 1024 
#NQ_gamma   	        = 1 << #NQ_gammashift
#NQ_betashift           = 10
#NQ_beta		        = #NQ_intbias >> #NQ_betashift ; beta = 1/1024 
#NQ_betagamma	        = #NQ_intbias << (#NQ_gammashift - #NQ_betashift)

; defs for decreasing radius factor
#NQ_initrad	            = #NQ_netsize >> 3  ; for 256 cols, radius starts 
#NQ_radiusbiasshift     = 6 ; at 32.0 biased by 6 bits
#NQ_radiusbias	        = 1 << #NQ_radiusbiasshift
#NQ_initradius	        = #NQ_initrad * #NQ_radiusbias 
#NQ_radiusdec	        = 15 ; and decreases by a factor of 1/#NQ_radiusdec each cycle (originally was 30)

; defs for decreasing alpha factor 
#NQ_alphabiasshift	    = 10 ; alpha starts at 1.0 
#NQ_initalpha	        = 1 << #NQ_alphabiasshift

; radbias and alpharadbias used for radpower calculation 
#NQ_radbiasshift	    = 8
#NQ_radbias		        = 1 << #NQ_radbiasshift
#NQ_alpharadbshift      = #NQ_alphabiasshift + #NQ_radbiasshift
#NQ_alpharadbias        = 1 << #NQ_alpharadbshift


Global Dim g_NQ_Network.T_PIXEL (#NQ_netsize - 1)  ; the network itself 
Global Dim g_NQ_NetIndex(256 - 1)                  ; for network lookup - really 256 
Global Dim g_NQ_Bias (#NQ_netsize - 1)             ; bias and freq arrays for learning 
Global Dim g_NQ_Freq (#NQ_netsize - 1)             ; bias and freq arrays for learning 
Global Dim g_NQ_RadPower (#NQ_initrad - 1)         ; radpower for precomputation 


Procedure.i _NQ_Contest (b, g, r)
 Protected i, dist, a, biasdist, betafreq
 Protected bestpos, bestbiaspos, bestd, bestbiasd
 Protected *p.Integer, *f.Integer, *n.T_PIXEL

 ; finds closest neuron (min dist) and updates freq 
 ; finds best neuron (min dist-bias) and returns position 
 ; for frequently chosen neurons, g_NQ_Freq(i) is high and g_NQ_Bias(i) is negative
 ; g_NQ_Bias(i) = gamma*((1/#NQ_netsize)-g_NQ_Freq(i)) 

 bestd = #MAXLONG
 bestbiasd = bestd
 bestpos = -1
 bestbiaspos = bestpos
 *p = @g_NQ_Bias()
 *f = @g_NQ_Freq()
 
 For i = 0 To #NQ_netsize - 1
    *n = @g_NQ_Network(i)
	dist = *n\iBGRC[0] - b : If dist < 0 : dist = -dist : EndIf
    a = *n\iBGRC[1] - g :  If a < 0 : a = -a : EndIf
    dist + a
    a = *n\iBGRC[2] - r : If a < 0 : a = -a : EndIf  
	dist + a
    If dist < bestd : bestd = dist : bestpos = i : EndIf
	biasdist = dist - (*p\i >> (#NQ_intbiasshift - #NQ_netbiasshift))
	If biasdist < bestbiasd : bestbiasd = biasdist: bestbiaspos = i : EndIf
	betafreq = *f\i >> #NQ_betashift
    *f\i - betafreq : *f + SizeOf(Integer)
    *p\i + (betafreq << #NQ_gammashift) : *p + SizeOf(Integer)
 Next   
 
 g_NQ_Freq(bestpos) + #NQ_beta
 g_NQ_Bias(bestpos) - #NQ_betagamma
	
 ProcedureReturn bestbiaspos	
EndProcedure

; Move neuron i towards biased (b,g,r) by factor alpha
Procedure _NQ_AlterSingle (alpha, i, b, g, r)
 Protected *n.T_PIXEL = @g_NQ_Network(i) ; alter hit neuron 
 
 *n\iBGRC[0] - (alpha * (*n\iBGRC[0] - b)) / #NQ_initalpha 
 *n\iBGRC[1] - (alpha * (*n\iBGRC[1] - g)) / #NQ_initalpha
 *n\iBGRC[2] - (alpha * (*n\iBGRC[2] - r)) / #NQ_initalpha
EndProcedure

; Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in g_NQ_RadPower[|i-j|]
Procedure _NQ_AlterNeigh (rad, i, b, g, r)
 Protected j, k, lo, hi, a
 Protected *p.T_PIXEL, *q.Integer 

 lo = i - rad : If lo < -1 : lo = -1 : EndIf
 hi = i + rad : If hi > #NQ_netsize : hi = #NQ_netsize : EndIf
 
 j = i + 1 
 k = i - 1
 *q = @g_NQ_RadPower()
 
 While (j < hi) Or (k > lo) 
    *q + SizeOf(Integer) : a = *q\i
    
    If j < hi
        *p = @g_NQ_Network(j)
        *p\iBGRC[0] - (a * (*p\iBGRC[0] - b)) / #NQ_alpharadbias
        *p\iBGRC[1] - (a * (*p\iBGRC[1] - g)) / #NQ_alpharadbias
        *p\iBGRC[2] - (a * (*p\iBGRC[2] - r)) / #NQ_alpharadbias
        j + 1
    EndIf
    If k > lo
        *p = @g_NQ_Network(k)
        *p\iBGRC[0] - (a * (*p\iBGRC[0] - b)) / #NQ_alpharadbias
        *p\iBGRC[1] - (a * (*p\iBGRC[1] - g)) / #NQ_alpharadbias
        *p\iBGRC[2] - (a * (*p\iBGRC[2] - r)) / #NQ_alpharadbias
        k - 1        
    EndIf
 Wend
EndProcedure

; Initialise network in range (0,0,0) To (255,255,255) and set parameters
Procedure NQ_InitNetwork ()	
 Protected i, *p.T_PIXEL
  
 For i = 0 To #NQ_netsize - 1
    *p = @g_NQ_Network(i)
    *p\iBGRC[0] = (i << (#NQ_netbiasshift + 8)) / #NQ_netsize
    *p\iBGRC[1] = *p\iBGRC[0]
    *p\iBGRC[2] = *p\iBGRC[0]
    g_NQ_Freq(i) = #NQ_intbias/#NQ_netsize ; 1/netsize 
    g_NQ_Bias(i) = 0
 Next
EndProcedure   

; Unbias network to give byte values 0..255 and record position i to prepare for sort
Procedure NQ_UnbiasNetwork()
 Protected i, j, t
 
 For i = 0 To #NQ_netsize - 1
    For j = 0 To 2
        t = (g_NQ_Network(i)\iBGRC[j] + (1 << (#NQ_netbiasshift - 1))) >> #NQ_netbiasshift
        If (t > 255) : t = 255 : EndIf
        g_NQ_Network(i)\iBGRC[j] = t
    Next
    g_NQ_Network(i)\iBGRC[3]  = i ; record colour no
 Next
EndProcedure

; Insertion sort of network and building of g_NQ_NetIndex(0..255) (to do after unbias)
Procedure NQ_SortNetwork()
 
 Protected i, j, smallpos, smallval 
 Protected *p.T_PIXEL, *q.T_PIXEL
 Protected previouscol, startpos
 
 previouscol = 0
 startpos = 0
 
 For i = 0 To #NQ_netsize - 1
 
  *p = @g_NQ_Network(i)
    
  smallpos = i   
	smallval = *p\iBGRC[1]  ; index on g 
	
	; find smallest in i..#NQ_netsize - 1 
   For j = i+1 To #NQ_netsize - 1
        *q = @g_NQ_Network(j)
        If *q\iBGRC[1] < smallval ; index on g 
		      smallpos = j
			    smallval = *q\iBGRC[1] ; index on g
        EndIf
   Next
    
    *q = @g_NQ_Network(smallpos)
    
    ; swap p (i) and q (smallpos) entries 
	If i <> smallpos 		
		Swap *q\iBGRC[0], *p\iBGRC[0]
		Swap *q\iBGRC[1], *p\iBGRC[1]
		Swap *q\iBGRC[2], *p\iBGRC[2]
		Swap *q\iBGRC[3], *p\iBGRC[3]		
	EndIf
	
	; smallval entry is now in position i 
    If smallval <> previouscol
        g_NQ_NetIndex(previouscol) = (startpos+i) >> 1

        For j = previouscol + 1 To smallval - 1
            g_NQ_NetIndex(j) = i
        Next
        
        previouscol = smallval        
        startpos = i        
    EndIf        
 Next
 
 g_NQ_NetIndex(previouscol) = (startpos + #NQ_maxnetpos) >> 1
 
 For j = previouscol + 1 To 255
    g_NQ_NetIndex(j) = #NQ_maxnetpos ; really 256
 Next

EndProcedure

; Search for BGR values 0..255 (after net is unbiased) and return colour index
Procedure.i NQ_LookupPalette (b, g, r)
 Protected i, j, dist, a, best, bestd
 Protected *p.T_PIXEL
 
 bestd = 1000 ; biggest possible dist is 256*3
 best = -1
 
 i = g_NQ_NetIndex(g) ; index on g 
 j = i - 1            ; start at g_NQ_NetIndex(g) and work outwards 

 While i < #NQ_netsize Or j >= 0
 
    If i < #NQ_netsize    
        *p = @g_NQ_Network(i)
        
        dist = *p\iBGRC[1] - g ; inx key 
        
        If dist >= bestd
            i = #NQ_netsize ; stop iter
        Else
            i + 1
            If dist < 0: dist = -dist : EndIf
            a = *p\iBGRC[0] - b : If a < 0 : a = -a : EndIf
            dist + a
            If dist < bestd
                a = *p\iBGRC[2] - r : If a < 0 : a = -a : EndIf
                dist + a
                If dist < bestd : bestd = dist : best = *p\iBGRC[3] : EndIf
            EndIf    
        EndIf
    EndIf
        
    If j >= 0    
        *p = @g_NQ_Network(j)
        
        dist = g - *p\iBGRC[1] ; inx key - reverse dif
        
        If dist >= bestd 
            j = -1 ; stop iter
        Else
            j - 1
            If dist < 0 : dist = -dist : EndIf
            a = *p\iBGRC[0] - b : If a < 0 : a = -a : EndIf
            dist + a
            If dist < bestd 
                a = *p\iBGRC[2] - r : If  a < 0 : a = -a : EndIf
                dist + a
                If dist < bestd : bestd = dist : best = *p\iBGRC[3] : EndIf
            EndIf
        EndIf
    EndIf
    
 Wend
 
 ProcedureReturn best
EndProcedure

; Main Learning Loop
Procedure.i NQ_Learn (*ImageBuffer.T_RGBTRIPLE, iBufferLen, iSample)

 Protected i, j, b, g, r
 Protected radius, rad, alpha, iStep, delta, samplepixels, alphadec 
 Protected *lim
 
 If iBufferLen < #NQ_minpicturebytes
    ProcedureReturn 0
 EndIf
 
 alphadec = 30 + ((iSample - 1) / 3)
 
 *lim = *ImageBuffer + iBufferLen
 samplepixels = iBufferLen / (4 * iSample)
 delta = samplepixels / #NQ_ncycles
 alpha = #NQ_initalpha
 radius = #NQ_initradius
 
 If samplepixels = 0 Or delta = 0 
    ProcedureReturn 0
 EndIf
 
 rad = radius >> #NQ_radiusbiasshift
 
 If rad <= 1 : rad = 0 : EndIf
 
 For i = 0 To rad - 1
    g_NQ_RadPower(i) = alpha * (((rad * rad - i * i) * #NQ_radbias) / (rad*rad))
 Next
 
 If ((iBufferLen % #NQ_prime1) <> 0) 
    iStep = 4 * #NQ_prime1 
 ElseIf ((iBufferLen % #NQ_prime2) <> 0) 
    iStep = 4 * #NQ_prime2
 ElseIf ((iBufferLen % #NQ_prime3) <> 0)
    iStep = 4 * #NQ_prime3
 Else 
    iStep = 4 * #NQ_prime4
 EndIf

 i = 0
 
 While (i < samplepixels) 
    b = (*ImageBuffer\rgbtBlue &$FF) << #NQ_netbiasshift
    g = (*ImageBuffer\rgbtGreen &$FF) << #NQ_netbiasshift
    r = (*ImageBuffer\rgbtRed &$FF) << #NQ_netbiasshift
	j = _NQ_Contest(b,g,r)

	_NQ_AlterSingle (alpha,j,b,g,r)
    
    If (rad) : _NQ_AlterNeigh(rad,j,b,g,r) : EndIf ; alter neighbours
    
    *ImageBuffer + iStep
    
    If *ImageBuffer >= *lim : *ImageBuffer - iBufferLen : EndIf
    
    i + 1
    
    If i % delta = 0
        alpha - (alpha / alphadec)
		radius - (radius / #NQ_radiusdec)
		rad = radius >> #NQ_radiusbiasshift
		
		If rad <= 1 : rad = 0 : EndIf
		
		For j = 0 To rad - 1
		    g_NQ_RadPower(j) = alpha * (((rad * rad - j * j) * #NQ_radbias) / (rad*rad))
		Next        
    EndIf	
 Wend
 
 ProcedureReturn 1

EndProcedure

;=============================================================
;{            FLOYD-STEINBERG DITHERING RESIDENTS
;=============================================================

; /* Copyright (c) 2009 the authors listed at the following URL, and/or
; the authors of referenced articles or incorporated external code:
; http://en.literateprograms.org/Floyd-Steinberg_dithering_(C)?action=history&offset=20080916082812

; Permission is hereby granted, free of charge, to any person obtaining
; a copy of this software and associated documentation files (the
; "Software"), to deal in the Software without restriction, including
; without limitation the rights to use, copy, modify, merge, publish,
; distribute, sublicense, and/or sell copies of the Software, and to
; permit persons to whom the Software is furnished to do so, subject to
; the following conditions:

; The above copyright notice and this permission notice shall be
; included in all copies or substantial portions of the Software.

; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
; IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
; CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
; TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
; SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

; Retrieved from: http://en.literateprograms.org/Floyd-Steinberg_dithering_(C)?oldid=14630
;
; Converted to PureBasic code by Lloyd Gallant (netmaestro) on October 1, 2009

Structure RGBQUAD_U                ; Unsigned char RGBQUAD
  rgbBlue.a
  rgbGreen.a
  rgbRed.a
  rgbReserved.a
EndStructure

Structure PALETTE                  ; Used to index the colortable
  index.RGBQUAD_U[256]
EndStructure

Structure COLORBITS_RGBQUAD        ; Used to index the source colorbits 
  pixels.RGBQUAD_U[0]
EndStructure

Structure COLORBITS_1BYTE_PERPIXEL ; Used to index the target colorbits
  pixels.a[0]
EndStructure

Macro plus_truncate_uchar(a, b) 
    If a+b<0 : a=0 : ElseIf  a+b>255 : a=255 : Else : a+b : EndIf
EndMacro

Macro compute_disperse(channel)
  error = (*colorbits\pixels[x + y*w]\channel - *palette\index[index]\channel ) >> 1 ; shift to soften
  If (x + 1) < w : plus_truncate_uchar(*colorbits\pixels[(x+1) + (y+0)*w]\channel, (error*7) >> 4) : EndIf
  If (y + 1) < h 
    If (x - 1) > 0
      plus_truncate_uchar(*colorbits\pixels[(x-1) + (y+1)*w]\channel, (error*3) >> 4)
      plus_truncate_uchar(*colorbits\pixels[(x+0) + (y+1)*w]\channel, (error*5) >> 4)
      If (x + 1 < w) : plus_truncate_uchar(*colorbits\pixels[(x+1)+(y+1)*w]\channel,(error*1) >> 4) : EndIf
    EndIf
  EndIf
EndMacro
;}

ProcedureDLL Save8bitImage(image, filename$, memory=0) 
  
  If GetObject_(image, SizeOf(BITMAP), Bmp.BITMAP) 
    With BmiInfo.BITMAPINFOHEADER 
      \biSize         = SizeOf(BITMAPINFOHEADER) 
      \biWidth        = Bmp\bmWidth 
      \biHeight       = Bmp\bmHeight 
      \biPlanes       = 1 
      \biBitCount     = 8 
    EndWith 
  Else 
    ProcedureReturn 0 
  EndIf 
  
  sz_colorbits = Bmp\bmWidthBytes*Bmp\bmHeight 
  *colortable = AllocateMemory(256*SizeOf(RGBQUAD)) 
  dc = CreateDC_("DISPLAY",0,0,0)
  hdc = CreateCompatibleDC_(dc)
    SelectObject_(hdc, image)
    NumColors = GetDIBColorTable_(hdc, 0, 256, *colortable) 
  DeleteDC_(dc)
  DeleteDC_(hdc)
  sz_image = SizeOf(BITMAPFILEHEADER) + SizeOf(BITMAPINFOHEADER) + NumColors*SizeOf(RGBQUAD) + sz_colorbits 
  *rawimage = AllocateMemory(sz_image) 
  *fileheader.BITMAPFILEHEADER = *rawimage 
  *header = *rawimage + SizeOf(BITMAPFILEHEADER) 
  With *fileheader 
    \bfType = PeekW(@"BM") 
    \bfSize = sz_image 
    \bfOffBits = SizeOf(BITMAPFILEHEADER) + SizeOf(BITMAPINFOHEADER) + NumColors*SizeOf(RGBQUAD) 
  EndWith 
  CopyMemory(BmiInfo, *header, SizeOf(BITMAPINFOHEADER)) 
  CopyMemory(*colortable, *rawimage + SizeOf(BITMAPFILEHEADER) + SizeOf(BITMAPINFOHEADER), NumColors*SizeOf(RGBQUAD)) 
  CopyMemory(Bmp\bmBits, *rawimage + SizeOf(BITMAPFILEHEADER) + SizeOf(BITMAPINFOHEADER) + NumColors*SizeOf(RGBQUAD), sz_colorbits) 
  
  FreeMemory(*colortable) 
  
  If Not memory 
    file = CreateFile(#PB_Any, filename$) 
    If file 
      WriteData(file,*rawimage,MemorySize(*rawimage)) 
      CloseFile(file) 
    EndIf 
    FreeMemory(*rawimage) 
    ProcedureReturn 1 
  Else 
    ProcedureReturn *rawimage 
  EndIf 
  
EndProcedure 

Procedure Get32BitColors(pBitmap) 

  GetObject_(pBitmap, SizeOf(BITMAP), @bmp.BITMAP) 
  *bmi.BITMAPINFO = AllocateMemory(SizeOf(BITMAPINFO)+SizeOf(RGBQUAD)*255)
  With *bmi\bmiHeader
    \biSize         = SizeOf(BITMAPINFOHEADER) 
    \biWidth        = bmp\bmWidth 
    \biHeight       = bmp\bmHeight 
    \biPlanes       = 1 
    \biBitCount     = 32 
  EndWith 
  hDC = GetWindowDC_(#Null) 
  GetDIBits_(hDC, pBitmap, 0, bmp\bmHeight, #Null, *bmi, #DIB_RGB_COLORS)
  *pPixels = AllocateMemory(*bmi\bmiHeader\biSizeImage)
  iRes = GetDIBits_(hDC, pBitmap, 0, bmp\bmHeight, *pPixels, *bmi, #DIB_RGB_COLORS) 
  ReleaseDC_(#Null, hDC) 
  FreeMemory(*bmi)
  ProcedureReturn *pPixels 
  
EndProcedure 


Procedure.i CreatePalette_NeuQuant (hImage, iQuality=10)  
 ; by luis
 ; return a *ptr to memory containing 256 RGBQUAD structures
 ; allocated memory must be freed by the caller
 
 Protected *ImageBuf, *Palette, k
 Protected Dim tPaletteTable.RGBQUAD(256)

 *ImageBuf = Get32BitColors(hImage)
 
 If *ImageBuf
   NQ_InitNetwork()
    
   If NQ_Learn(*ImageBuf, MemorySize(*ImageBuf), iQuality)
        
     NQ_UnbiasNetwork()
     
     FreeMemory(*ImageBuf)
      
     For k = 0 To 255
       With tPaletteTable(k)
         \rgbBlue  = g_NQ_Network(k)\iBGRC[0] 
         \rgbGreen = g_NQ_Network(k)\iBGRC[1] 
         \rgbRed   = g_NQ_Network(k)\iBGRC[2] 
         \rgbReserved = 0
       EndWith       
     Next
     
     *Palette = AllocateMemory (256 * SizeOf(RGBQUAD))
     
     If *Palette 
       CopyMemory(@tPaletteTable(), *Palette, MemorySize(*Palette))
     EndIf
   Else
     FreeMemory(*ImageBuf)
   EndIf
 EndIf
 
 ProcedureReturn *Palette 
EndProcedure

ProcedureDLL ConvertTo8bit(hImageIn, dither) 

  If GetObjectType_(hImageIn) <> #OBJ_BITMAP
    ProcedureReturn 0 ; not a bitmap
  EndIf

  GetObject_(hImageIn,SizeOf(BITMAP),bmp.BITMAP) 
  w = bmp\bmWidth 
  h = bmp\bmHeight 
  d = bmp\bmBitsPixel 

  If d < 24
    ProcedureReturn 0 ; depth not 24 or 32
  EndIf

  If w*h<1600
    If w<h
      tmpw=64:tmph=Round((64.0/w)*h,#PB_Round_Nearest)
    ElseIf h<w
      tmph=64:tmpw = Round((64.0/h)*w,#PB_Round_Nearest)
    Else
      tmpw=64 : tmph=64
    EndIf
    tmpimage = CreateImage(#PB_Any, w,h,24)
    StartDrawing(ImageOutput(tmpimage))
      DrawImage(hImageIn,0,0)
    StopDrawing()
    ResizeImage(tmpimage, tmpw,tmph, #PB_Image_Raw)
    hImagetmp = ImageID(tmpimage)
    *palette.PALETTE = CreatePalette_NeuQuant(hImagetmp) 
    FreeImage(tmpimage)
  Else
    *palette.PALETTE = CreatePalette_NeuQuant(hImageIn) 
  EndIf

  hdcSrc = CreateCompatibleDC_(0)

  With bmi.BITMAPINFO 
    \bmiHeader\biSize     = SizeOf(BITMAPINFOHEADER) 
    \bmiHeader\biWidth    = w 
    \bmiHeader\biHeight   = h 
    \bmiHeader\biPlanes   = 1 
    \bmiHeader\biBitCount = 32
  EndWith  
  
  GetDIBits_(hdcSrc, hImageIn, 0, 0, #Null, @bmi, #DIB_RGB_COLORS) 
    
  *colorbits.COLORBITS_RGBQUAD = AllocateMemory(bmi\bmiHeader\biSizeImage) 
 
  GetDIBits_(hdcSrc, hImageIn, 0, h, *colorbits, @bmi, #DIB_RGB_COLORS) 
  
  With bmi8.BITMAPINFO 
     \bmiHeader\biSize     = SizeOf(BITMAPINFOHEADER) 
     \bmiHeader\biWidth    = w 
     \bmiHeader\biHeight   = h 
     \bmiHeader\biPlanes   = 1 
     \bmiHeader\biBitCount = 8 
  EndWith
  
  hdcDest = CreateCompatibleDC_(0)

  hImageOut = CreateDIBSection_(hdcDest, @bmi8, #DIB_PAL_COLORS, @*bits8.COLORBITS_1BYTE_PERPIXEL, 0, 0) 
  GetObject_(hImageout, SizeOf(bitmap), bmpout.bitmap)
  widthbytes=bmpout\bmwidthbytes 
 
  SelectObject_(hdcDest, hImageOut)  
  SetDIBColorTable_(hdcDest,0,256,*palette) 
  szImage = widthbytes*h
  
  NQ_SortNetwork() ; for NQ_LookupPalette to work properly

  ; Set and optionally dither the colorbits
  For y = 0 To h-1
    For x = 0 To w-1
      index.a = NQ_LookupPalette (*colorbits\pixels[x + y*w]\rgbBlue, *colorbits\pixels[x + y*w]\rgbGreen, *colorbits\pixels[x + y*w]\rgbRed)
      *bits8\pixels[x + y*widthbytes] = index
      If dither
        compute_disperse(rgbRed)
        compute_disperse(rgbGreen)
        compute_disperse(rgbBlue)
      EndIf
    Next
  Next
  
  DeleteDC_(hdcSrc)
  DeleteDC_(hdcDest)
  
  FreeMemory(*colorbits) 
  FreeMemory(*palette) 
  
  ProcedureReturn hImageOut 

EndProcedure 

;=============================================================
;{            Save as GIF Section
;=============================================================

Global *token, *image, lib

Prototype GdiplusStartup( *token, *input, mode ) 
Prototype GdipCreateBitmapFromHBITMAP(*hBitmap, *palette, *image)
Prototype GdipSetImagePalette(*image, *palette)
Prototype GdipSaveImageToFile(*image, *path, *clsid, *enc)
Prototype GdipDisposeImage( *image ) 
Prototype GdiplusShutdown( *token ) 

Global Startup.GdiplusStartup                                     
Global CreateBitmapFromHBITMAP.GdipCreateBitmapFromHBITMAP
Global SetImagePalette.GdipSetImagePalette                  
Global SaveImageToFile.GdipSaveImageToFile                  
Global DisposeImage.GdipDisposeImage                              
Global Shutdown.GdiplusShutdown                            

CompilerIf Defined(GdiplusStartupInput, #PB_Structure) = 0
  Structure GdiplusStartupInput 
    GdiPlusVersion.l 
    *DebugEventCallback.Debug_Event
    SuppressBackgroundThread.l 
    SuppressExternalCodecs.l 
  EndStructure 
CompilerEndIf  

ProcedureDLL SaveGIF(hImageIn, outpath$)

  lib = OpenLibrary(#PB_Any, "gdiplus.dll")
  If Not lib
    MessageRequester("Error","Required component gdiplus.dll is not found. Please install it and retry    ", #MB_ICONERROR)
    ProcedureReturn
  EndIf
  
  input.GdiplusStartupInput
  input\GdiPlusVersion = 1
  
  Startup.GdiplusStartup                              = GetFunction( lib, "GdiplusStartup" )          
  CreateBitmapFromHBITMAP.GdipCreateBitmapFromHBITMAP = GetFunction( lib, "GdipCreateBitmapFromHBITMAP" ) 
  SetImagePalette.GdipSetImagePalette                 = GetFunction( lib, "GdipSetImagePalette" )
  SaveImageToFile.GdipSaveImageToFile                 = GetFunction( lib, "GdipSaveImageToFile" )
  DisposeImage.GdipDisposeImage                       = GetFunction( lib, "GdipDisposeImage" )        
  Shutdown.GdiplusShutdown                            = GetFunction( lib, "GdiplusShutdown" )    
  
  Startup( @*token, @input, #Null)    
  Unicode$=Space(Len(outpath$)*2+2)
  PokeS(@Unicode$, outpath$, -1, #PB_Unicode) 
  *outpath = SysAllocString_(@Unicode$) 
  
  image8 = ConvertTo8bit(hImageIn, 1)
  CreateBitmapFromHBITMAP(image8, 0, @*gdip_image_object)
   
  If SaveImageToFile( *gdip_image_object, *outpath, ?clsid_gif, 0) = #S_OK 
    result = #True 
  Else
    result = #False
  EndIf
  
  DisposeImage(*gdip_image_object)
  Shutdown(*token)
  CloseLibrary(lib)

  ProcedureReturn result
  
EndProcedure

DataSection 
  clsid_gif: ; clsid for gif image format 
  Data.l $557cf402 
  Data.w $1a04 
  Data.w $11d3 
  Data.b $9a,$73,$00,$00,$f8,$1e,$f3,$2e 
EndDataSection 

;}


Re: You could write 2D games on the CanvasGadget

Posted: Sun Sep 23, 2012 10:52 pm
by grabiller
For the CanvasGadget, having an option to have it 24bit or 32bit would solve the issue. Peoples afraid of 32bit performances would use the 24bit version. No big deal.

For 8bit images, thanks for your code but I'm working on a cross-platform project. If only CreateImage could support 8 aside 24 and 32 :wink: