Convert image to 8bit 256 colors (windows only)
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.
Vista x86, PB 4.4 beta 2.
**EDIT : my mistake! Doh! Works fine - thanks.
Last edited by srod on Sun Aug 30, 2009 9:16 pm, edited 1 time in total.
I may look like a mule, but I'm not a complete ass.
- netmaestro
- PureBasic Bullfrog
- Posts: 8451
- Joined: Wed Jul 06, 2005 5:42 am
- Location: Fort Nelson, BC, Canada
- netmaestro
- PureBasic Bullfrog
- Posts: 8451
- Joined: Wed Jul 06, 2005 5:42 am
- Location: Fort Nelson, BC, Canada
-
- 666
- Posts: 1033
- Joined: Mon Sep 01, 2003 2:33 pm
Re: Convert image to 8bit 256 colors (windows only)
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
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

"Have you tried turning it off and on again ?"
A little PureBasic review
A little PureBasic review
Re: Convert image to 8bit 256 colors (windows only)
Very impressive. Thanks alot. Thanks for sharing.
Re: Convert image to 8bit 256 colors (windows only)
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.
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)
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)
Sorry to dig old topics out, but I'm wondering if it would be hard to add support for icon files?
None are more hopelessly enslaved than those who falsely believe they are free. (Goethe)
- netmaestro
- PureBasic Bullfrog
- Posts: 8451
- Joined: Wed Jul 06, 2005 5:42 am
- Location: Fort Nelson, BC, Canada
Re: Convert image to 8bit 256 colors (windows only)
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:
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")
BERESHEIT
-
- Addict
- Posts: 1482
- Joined: Tue Feb 22, 2011 1:16 pm
Re: Convert image to 8bit 256 colors (windows only)
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?
Microsoft Visual Basic only lasted 7 short years: 1991 to 1998.
PureBasic: Born in 1998 and still going strong to this very day!
PureBasic: Born in 1998 and still going strong to this very day!
- netmaestro
- PureBasic Bullfrog
- Posts: 8451
- Joined: Wed Jul 06, 2005 5:42 am
- Location: Fort Nelson, BC, Canada
Re: Convert image to 8bit 256 colors (windows only)
You have the code, go ahead and add it if you like. I won't have time for it anytime soon unfortunately.Can the original code save as PNG instead of BMP?
BERESHEIT
-
- Addict
- Posts: 1482
- Joined: Tue Feb 22, 2011 1:16 pm
Re: Convert image to 8bit 256 colors (windows only)
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.

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.
Microsoft Visual Basic only lasted 7 short years: 1991 to 1998.
PureBasic: Born in 1998 and still going strong to this very day!
PureBasic: Born in 1998 and still going strong to this very day!