Page 3 of 5

Posted: Sun Aug 30, 2009 9:05 pm
by srod
Didn't work here Netmaestro, just made a copy of the original color image.

Vista x86, PB 4.4 beta 2.

**EDIT : my mistake! Doh! Works fine - thanks.

Posted: Sun Aug 30, 2009 9:14 pm
by netmaestro
I'm pulling the saved image from the disk into Photoshop and verifying 8bit indexed color, how are you testing to see if the image is truly 8 bits depth? If you do an ImageDepth() on the catched image, that won't work because PB pulls it in at the default 24bit depth.

Posted: Sun Aug 30, 2009 9:17 pm
by srod
Sorry, works fine! :)

Posted: Sun Aug 30, 2009 9:19 pm
by netmaestro
Damn you you scared me!! :shock:

Posted: Sun Aug 30, 2009 9:21 pm
by srod
netmaestro wrote:Damn you you scared me!! :shock:
Now you know how I feel everytime I wake up in the afternoon and see my face staring back at me in the mirror! :wink:

Posted: Sun Aug 30, 2009 11:42 pm
by LuCiFeR[SD]
haha, you guys crack me up... Just like Srod's Mirror :P

Re: Convert image to 8bit 256 colors (windows only)

Posted: Sun Oct 18, 2009 12:27 pm
by luis
Wow... nice code Netmaestro, I was looking in the forum for something to quantize color images and found this.

Your adaptive palette works a lot better than the ones generated using octrees, and with most images do a better job than a good optimized median cut too. And it's simple and fast to implement.

I believe I wil start from your code and try to see if I can enhance it more... I just read a paper on neural networks (Kohonen's to be precise) used exactly for this same purpose...

I'll let you know.

I really like the PB's forum :)

Re: Convert image to 8bit 256 colors (windows only)

Posted: Sat Oct 24, 2009 3:49 pm
by Booger
Very impressive. Thanks alot. Thanks for sharing.

Re: Convert image to 8bit 256 colors (windows only)

Posted: Sun Aug 01, 2010 4:39 pm
by Mistrel
Does not work on Windows XP 64-bit.

I tried both this and luis's method in PureBasic 4.31 and 4.40.

For your code "i" from ImageTo8bit() always returns 0.

Re: Convert image to 8bit 256 colors (windows only)

Posted: Mon Aug 02, 2010 7:18 am
by Mistrel
It works slightly better when compiling with 4.40 x64. Greyscale works fine. Screen8 works but looks wildly different, and adaptive crashes with an #Image not initialized at line 85 in the .pbi.

Re: Convert image to 8bit 256 colors (windows only)

Posted: Sun Jun 05, 2011 1:37 pm
by Inf0Byt3
Sorry to dig old topics out, but I'm wondering if it would be hard to add support for icon files?

Re: Convert image to 8bit 256 colors (windows only)

Posted: Sun Jun 05, 2011 2:05 pm
by netmaestro
I have written code for saving icons to disk and this code includes a procedure to minimize the filesize of the saved icon by counting the colors actually used in the original and creating the output icon using the minimum necessary. For example, if an icon is 5 kb in size because of the format it was saved in, but it only uses 16 or less actual colors, my routine will save it at 1 k.

Hopefully this is more or less what you are looking for, and here is the code:

Code: Select all

;=======================================================================
;  Library:               IconLib
;  Author:                Lloyd Gallant (netmaestro)
;  Date:                  October 15, 2009
;  Target OS:             Microsoft Windows All
;  Target Compiler:       Requires PureBasic 4.40
;  
;  Functions:             WriteIconFile
;                         Writes an Array of 4bit-32bit hIcons 
;                         to an .ico file on disk
;                          
;                         WriteCursorFile
;                         writes a 1bit-32bit hIcon or 1bit hCursor
;                         to a .cur file on disk
;=======================================================================

Structure ICONDIR
  idReserved.w                  ; // Reserved (must be 0)
  idType.w                      ; // Resource type (1 for icons)
  idCount.w                     ; // How many images?
  idEntries.ICONHEADER[0]       ; // The entries for each image
EndStructure

Structure ICONARRAY
  icons.l[0]
EndStructure

Procedure CountColorsUsed(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 = CreateCompatibleDC_(#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) 
  
  Global mapsize = MemorySize(*pPixels)/2
  Global NewMap Colors.RGBQUAD(mapsize)
  *p.RGBQUAD = *pPixels
  For i=1 To MemorySize(*pPixels)/SizeOf(RGBQUAD)
    With Colors(Hex(PeekL(*p)))
      \rgbBlue      = *p\rgbBlue
      \rgbGreen     = *p\rgbGreen
      \rgbRed       = *p\rgbRed
      \rgbReserved  = *p\rgbReserved
    EndWith
    *p+SizeOf(RGBQUAD)
  Next
  
  result = MapSize(colors())
  
  If result <= 16
    *colortable = AllocateMemory(16*SizeOf(RGBQUAD))
  ElseIf result <= 256
    *colortable= AllocateMemory(256*SizeOf(RGBQUAD))
  Else
    *colortable = 0
  EndIf
  
  If *colortable
    *writeptr.RGBQUAD = *colortable
    ForEach colors()
      With *writeptr
        \rgbBlue     = colors()\rgbBlue
        \rgbGreen    = colors()\rgbGreen
        \rgbRed      = colors()\rgbRed
        \rgbReserved = 0
      EndWith
      *writeptr+SizeOf(RGBQUAD)
    Next
  EndIf
  
  DeleteDC_(hdc)
  ClearMap(colors())
  FreeMemory(*pPixels)
  FreeMemory(*bmi)
  
  ProcedureReturn *colortable
EndProcedure

Procedure MinimizeColors(hBitmap)
  
  ; Count actual colors used: 
  ;  if     <= 16  -----> convert To 4bit
  ;  elseif <= 256 -----> convert to 8bit
  ;  else          -----> return original bitmap
  
  *colortable = CountColorsUsed(hBitmap)
  hImageOut = 0
  
  If *colortable
    Select MemorySize(*colortable)
      Case 64
        newdepth = 4
      Case 1024
        newdepth = 8
    EndSelect
    
    GetObject_(hBitmap,SizeOf(BITMAP),bmp.BITMAP) 
    w = bmp\bmWidth 
    h = bmp\bmHeight 
    
    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, hBitmap, 0, h, #Null, @bmi, #DIB_RGB_COLORS) 
    
    *colorbits = AllocateMemory(bmi\bmiHeader\biSizeImage) 
    
    GetDIBits_(hdcSrc, hBitmap, 0, h, *colorbits, @bmi, #DIB_RGB_COLORS) 
    
    With bmiReduced.BITMAPINFO 
      \bmiHeader\biSize     = SizeOf(BITMAPINFOHEADER) 
      \bmiHeader\biWidth    = w 
      \bmiHeader\biHeight   = h 
      \bmiHeader\biPlanes   = 1 
      \bmiHeader\biBitCount = newdepth
    EndWith
    
    hdcDest = CreateCompatibleDC_(0)
    
    hImageOut = CreateDIBSection_(hdcDest, @bmiReduced, #DIB_PAL_COLORS, @*bitsReduced, 0, 0) 
    
    SelectObject_(hdcDest, hImageOut) 
    SetDIBColorTable_(hdcDest, 0, MemorySize(*colortable)/SizeOf(RGBQUAD), *colortable)
    
    GdiFlush_()
    SetDIBits_(hdcSrc, hImageOut, 0, h, *colorbits, @bmi, #DIB_PAL_COLORS) 
    
    DeleteDC_(hdcSrc)
    DeleteDC_(hdcDest)
    
    FreeMemory(*colortable)
    FreeMemory(*colorbits)
    
  EndIf
  
  If GetObjectType_(hImageOut) = #OBJ_BITMAP
    ProcedureReturn hImageOut   
  Else
    ProcedureReturn hBitmap
  EndIf
  
EndProcedure


;=======================================================================
;  Library Function:      WriteIconFile
;  Author:                Lloyd Gallant (netmaestro)
;  Date:                  October 15, 2009
;  Target OS:             Microsoft Windows All
;  Target Compiler:       PureBasic 4.40
;  
;  Function:              Writes an array of 4bit-32bit hIcons 
;                         to an .ico file on disk
;=======================================================================

ProcedureDLL.l WriteIconFile( *hIcon_array.ICONARRAY, num_hIcons, filename$)
  
  ; netmaestro October 2009 based on http://msdn.microsoft.com/en-us/library/ms997538.aspx
  ; contributors: srod
  
  Protected hdc, iinf.ICONINFO, mask, color, bmpColor.BITMAP, bmpMask.BITMAP, *bi_color.BITMAPINFO, *bi_mask.BITMAPINFO
  Protected *maskbits, *colorbits, *file, *direntryptr,*imagedataptr, *id.ICONDIR, *ih.ICONHEADER, *ii.ICONIMAGE
  Protected i, testicon, thisfile, sz_color, sz_mask, result=0, writesuccess, icons_processed=0
  
  NewList Valid_hIcon.i() ; Valid_hIcon list gets filled with validated hIcons only
  
  For i=0 To num_hIcons-1
    testicon = *hIcon_array\icons[i]
    FillMemory(@iinf, SizeOf(ICONINFO), 0)
    GetIconInfo_(testicon, @iinf)
    If iinf\hbmMask And iinf\hbmColor And iinf\fIcon=1 ; 1-bit icons are not supported, valid depths are 4,8,16,32
      AddElement(Valid_hIcon())
      Valid_hIcon()=testicon
    EndIf
  Next
  
  hdc = CreateCompatibleDC_(#Null)
  
  *file = AllocateMemory(1024*1024*4) ; 4 megabytes should be more than enough
  *direntryptr = *file
  
  ; File header
  *id.ICONDIR = *direntryptr
  With *id
    \idType  = 1
    \idCount = ListSize(Valid_hIcon())
  EndWith
  *direntryptr+SizeOf(ICONDIR) ; ICONHEADER entries start here
  
  *imagedataptr = *direntryptr + ListSize(Valid_hIcon())*SizeOf(ICONHEADER) ; Leave room for <listsize> ICONHEADER entries
  ; Image data entries start here
  ForEach Valid_hIcon()
    GetIconInfo_(Valid_hIcon(), @iinf.ICONINFO)
    usecolortable = #False
    num_colors    = 0
    sz_colortable = 0
    
    mask  = iinf\hbmMask
    
    color = MinimizeColors(iinf\hbmColor)
    
    ;===================================================
    ;          Get this entry's color bits
    ;===================================================
    
    GetObject_(color, SizeOf(BITMAP), bmpColor.BITMAP)
    
    If bmpColor\bmBitsPixel <= 8
      usecolortable = #True
      num_colors = Int(Pow(2,bmpColor\bmBitsPixel))
      sz_colortable = num_colors*SizeOf(RGBQUAD)
      If *colortable : FreeMemory(*colortable) : EndIf
      *colortable = AllocateMemory(sz_colortable)
      old = SelectObject_(hdc, color)
      GetDIBColorTable_(hdc, 0, num_colors, *colortable)    
      SelectObject_(hdc, old)
    EndIf
    
    If *bi_color : FreeMemory(*bi_color) : EndIf
    *bi_color = AllocateMemory(SizeOf(BITMAPINFOHEADER)+256*SizeOf(RGBQUAD))
    *bi_color\bmiHeader\biSize = SizeOf(BITMAPINFOHEADER)
    
    GetDIBits_(hdc, color, 0, bmpColor\bmHeight, 0, *bi_color, #DIB_RGB_COLORS ) 
    sz_color = *bi_color\bmiHeader\biSizeImage
    *bi_color\bmiHeader\biBitCount = bmpColor\bmBitsPixel
    If *colorbits : FreeMemory(*colorbits) : EndIf
    *colorbits = AllocateMemory(sz_color) 
    GetDIBits_(hdc, color, 0, bmpColor\bmHeight, *colorbits, *bi_color, #DIB_RGB_COLORS )     
    
    ;===================================================
    ;          Get this entry's mask bits
    ;===================================================
    
    GetObject_(mask, SizeOf(BITMAP), bmpMask.BITMAP)
    
    If *bi_mask : FreeMemory(*bi_mask) : EndIf 
    *bi_mask = AllocateMemory(SizeOf(BITMAPINFOHEADER)+256*SizeOf(RGBQUAD))
    *bi_mask\bmiHeader\biSize = SizeOf(BITMAPINFOHEADER)
    
    GetDIBits_(hdc, mask, 0, bmpMask\bmHeight, 0, *bi_mask, #DIB_RGB_COLORS ) 
    sz_mask = *bi_mask\bmiHeader\biSizeImage
    *bi_mask\bmiHeader\biBitCount = bmpMask\bmBitsPixel
    If *maskbits : FreeMemory(*maskbits) : EndIf
    *maskbits = AllocateMemory( sz_mask ) 
    GetDIBits_(hdc, mask, 0, bmpMask\bmHeight, *maskbits, *bi_mask, #DIB_RGB_COLORS )     
    
    ;===================================================
    ;           Write this entry to the file
    ;===================================================
    
    ; Icon header first, in the headers section
    *ih.ICONHEADER = *direntryptr 
    With *ih
      \bWidth        = bmpcolor\bmWidth
      \bHeight       = bmpcolor\bmHeight 
      \wPlanes       = 1
      \wBitCount     = bmpcolor\bmBitsPixel 
      \dwBytesinRes  = sz_colortable + sz_color + sz_mask + SizeOf(ICONIMAGE) 
      \dwBytesOffset = *imagedataptr - *file
    EndWith
    *direntryptr+SizeOf(ICONHEADER) 
    
    ; ICONIMAGE structure next, in the imagedata section
    *ii.ICONIMAGE = *imagedataptr 
    With *ii\icHeader
      \biSize      = SizeOf(BITMAPINFOHEADER)
      \biWidth     = bmpColor\bmWidth
      \biHeight    = bmpColor\bmHeight * 2
      \biPlanes    = 1
      \biBitCount  = bmpcolor\bmBitsPixel 
      \biSizeImage = sz_colortable + sz_color + sz_mask + SizeOf(ICONIMAGE)
    EndWith
    *imagedataptr+SizeOf(BITMAPINFOHEADER) 
    
    ; Lastly, colortable (if present), color and mask bits
    If usecolortable : CopyMemory( *colortable, *imagedataptr, sz_colortable ) : *imagedataptr + sz_colortable : EndIf
    CopyMemory( *colorbits, *imagedataptr, sz_color ) : *imagedataptr + sz_color
    CopyMemory( *maskbits,  *imagedataptr, sz_mask  ) : *imagedataptr + sz_mask ; Pointer is ready for the next entry
    
    icons_processed + 1
    
    If color:DeleteObject_(color):EndIf
    DeleteObject_(mask)
    
  Next  
  
  DeleteDC_(hdc)
  
  ;===========================================================
  ;   All icons are processed, file can be written to disk
  ;===========================================================
  
  If icons_processed
    thisfile = CreateFile(#PB_Any, filename$)
    If thisfile
      writesuccess = *imagedataptr-*file 
      result       = WriteData( thisfile, *file, writesuccess )
      CloseFile( thisfile )
    Else
      MessageRequester("Info:", "Function WriteIconFile in library IconLib failed with error: unable to create file "+filename$+"  ", #MB_ICONERROR)
    EndIf
    FreeMemory(*bi_color)
    FreeMemory(*bi_mask)
    If *colortable : FreeMemory(*colortable) : EndIf    
    FreeMemory(*colorbits)
    FreeMemory(*maskbits)
  EndIf
  
  FreeMemory(*file)
  
  If result = writesuccess And result <> 0
    ProcedureReturn icons_processed
  Else
    ProcedureReturn 0
  EndIf
  
EndProcedure

;=======================================================================
;  Library Function:      WriteCursorFile
;  Author:                Lloyd Gallant (netmaestro)
;  Date:                  October 16, 2009
;  Target OS:             Microsoft Windows All
;  Target Compiler:       PureBasic 4.40
;  
;  Function:              Writes a 16bit-32bit or 1bit hCursor
;                         to a .cur file on disk
;=======================================================================

ProcedureDLL.l WriteCursorFile(hCursor, xHotspot, yHotspot, filename$)
  
  GetIconInfo_(hCursor, @ii.ICONINFO)
  
  If Not (ii\hbmMask)
    ProcedureReturn 0
  EndIf
  
  mask  = ii\hbmMask
  color = ii\hbmColor
  
  usecolortable = #False
  num_colors    = 0
  sz_colortable = 0
  
  hdc = CreateCompatibleDC_(#Null)
  
  ;===================================================
  ;                Get color bits
  ;===================================================
  
  If color
    GetObject_(color, SizeOf(BITMAP), bmpColor.BITMAP)
    
    If bmpColor\bmBitsPixel <= 8
      usecolortable = #True
      num_colors = Int(Pow(2,bmpColor\bmBitsPixel))
      sz_colortable = num_colors*SizeOf(RGBQUAD)
      If *colortable : FreeMemory(*colortable) : EndIf
      *colortable = AllocateMemory(sz_colortable)
      old = SelectObject_(hdc, color)
      GetDIBColorTable_(hdc, 0, num_colors, *colortable)    
      SelectObject_(hdc, old)
    EndIf
    
    *bi_color.BITMAPINFO = AllocateMemory(SizeOf(BITMAPINFOHEADER)+255*SizeOf(RGBQUAD))
    
    *bi_color\bmiHeader\biSize = SizeOf(BITMAPINFOHEADER)
    
    GetDIBits_(hdc, color, 0, bmpColor\bmHeight, 0, *bi_color, #DIB_RGB_COLORS ) 
    sz_color = *bi_color\bmiHeader\biSizeImage
    *bi_color\bmiHeader\biBitCount = bmpColor\bmBitsPixel
    *colorbits = AllocateMemory(sz_color) 
    GetDIBits_(hdc, color, 0, bmpColor\bmHeight, *colorbits, *bi_color, #DIB_RGB_COLORS )    
  Else ; 1-bit 
    sz_color = 0 
    sz_colortable = 2*SizeOf(RGBQUAD)
  EndIf
  
  ;===================================================
  ;                  Get mask bits
  ;===================================================
  
  GetObject_(mask, SizeOf(BITMAP), bmpMask.BITMAP)
  
  *bi_mask.BITMAPINFO = AllocateMemory(SizeOf(BITMAPINFOHEADER)+255*SizeOf(RGBQUAD))
  
  With *bi_mask\bmiHeader
    \biSize        = SizeOf(BITMAPINFOHEADER)
    \biWidth       = bmpMask\bmWidth 
    \biHeight      = bmpMask\bmHeight
    \biPlanes      = bmpMask\bmPlanes
    \biBitCount    = 0
  EndWith
  
  GetDIBits_(hdc, mask, 0, bmpMask\bmHeight, 0, *bi_mask, #DIB_PAL_COLORS ) 
  sz_mask = *bi_mask\bmiHeader\biSizeImage
  *bi_mask\bmiHeader\biBitCount = bmpMask\bmBitsPixel
  *maskbits = AllocateMemory( sz_mask ) 
  GetDIBits_(hdc, mask, 0, bmpMask\bmHeight, *maskbits, *bi_mask, #DIB_PAL_COLORS )     
  
  ;===================================================
  ;              Start writing the file
  ;===================================================
  
  *file = AllocateMemory(1024*1024)
  *writeptr = *file
  
  *id.ICONDIR = *writeptr
  With *id
    \idType  = 2
    \idCount = 1
  EndWith
  *writeptr+SizeOf(ICONDIR) 
  
  *ih.ICONHEADER = *writeptr 
  With *ih
    If color : \bHeight = bmpColor\bmHeight : Else : \bHeight = bmpMask\bmHeight/2 : EndIf
    \bWidth        = bmpMask\bmWidth   
    \wPlanes       = xHotspot
    \wBitCount     = yHotspot
    \dwBytesinRes  = sz_colortable + sz_color + sz_mask + SizeOf(ICONIMAGE) 
    \dwBytesOffset = SizeOf(ICONDIR)+SizeOf(ICONHEADER)
  EndWith
  *writeptr+SizeOf(ICONHEADER) 
  
  *ii.ICONIMAGE = *writeptr 
  With *ii\icHeader
    \biSize   = SizeOf(BITMAPINFOHEADER)
    \biWidth  = bmpMask\bmWidth 
    If color : \biHeight = bmpColor\bmHeight * 2 : Else : \biHeight = bmpMask\bmHeight : EndIf
    \biPlanes = 1
    If color : \biBitCount  = bmpColor\bmBitsPixel : Else : \biBitCount = 1 : EndIf
    \biSizeImage = sz_colortable + sz_color + sz_mask + SizeOf(ICONIMAGE) 
  EndWith
  *writeptr+SizeOf(BITMAPINFOHEADER) 
  
  If Not color ; 1-bit
    PokeL( *writeptr, #Black ) : *writeptr + SizeOf(LONG)
    PokeL( *writeptr, #White ) : *writeptr + SizeOf(LONG)
  Else
    If usecolortable : CopyMemory( *colortable, *writeptr, sz_colortable ) : *writeptr + sz_colortable : EndIf
  EndIf
  
  If color: CopyMemory( *colorbits, *writeptr, sz_color ) : *writeptr + sz_color : EndIf 
  CopyMemory( *maskbits,  *writeptr, sz_mask  ) : *writeptr + sz_mask
  
  If color : DeleteObject_(color) : EndIf
  If mask  : DeleteObject_(mask)  : EndIf
  
  If *bi_color   : FreeMemory(*bi_color)   : EndIf
  If *bi_mask    : FreeMemory(*bi_mask)    : EndIf
  If *colorbits  : FreeMemory(*colorbits)  : EndIf
  If *colortable : FreeMemory(*colortable) : EndIf
  If *maskbits   : FreeMemory(*maskbits)   : EndIf
  
  thisfile = CreateFile(#PB_Any, filename$)
  If thisfile
    writesuccess = *writeptr-*file 
    result       = WriteData( thisfile, *file, writesuccess )
    CloseFile( thisfile )
  Else
    MessageRequester("Info:", "Function WriteCursorFile in library IconLib failed with error: unable to create file "+filename$+"  ", #MB_ICONERROR)
  EndIf
  
  FreeMemory(*file)
  DeleteDC_(hdc)
  
  If result = writesuccess And result <> 0
    ProcedureReturn 1
  Else
    ProcedureReturn 0
  EndIf
  
EndProcedure 


Dim icos(1)
icos(0) = LoadImage(0,#PB_Compiler_Home+"Examples\sources\data\cdplayer.ico")
WriteIconFile(icos(),1,"c:\cdplayer.test.ico")

Re: Convert image to 8bit 256 colors (windows only)

Posted: Sun Jun 05, 2011 3:39 pm
by MachineCode
Can the original code save as PNG instead of BMP? Because wouldn't that reduce the file size even more? PNG supports 256 color palettes, so it should work?

Re: Convert image to 8bit 256 colors (windows only)

Posted: Sun Jun 05, 2011 3:50 pm
by netmaestro
Can the original code save as PNG instead of BMP?
You have the code, go ahead and add it if you like. I won't have time for it anytime soon unfortunately.

Re: Convert image to 8bit 256 colors (windows only)

Posted: Sun Jun 05, 2011 3:52 pm
by MachineCode
I tried it already but it didn't work. That's why I was asking the original source. ;)

All I did was use "SaveImage(i, filename$, #PB_ImagePlugin_PNG)" instead of the Save8bitImage() procedure, but I get an error of "The specified #Image is not initialized." Not sure how to get around it.