Image loading and color reduction
Here is a small example of how to perform the following steps in a common graphics using program requiring efficient access to image data:
load images from a file (jpg/png/gif for instance) with the Series 60 image conversion framework
synchronize the process from outside so that you get a blocking method for image loading
perform simple color reduction to convert the image into an indexed bitmap with 256-color palette of 12bit colors
Note: this example uses nested scheduling (nested CActiveScheduler::Start(), CActiveScheduler::Stop()). This approach might be hazardous if misused, but if used carefully it is a simple approach and works very well.
Below are the source files for the two classes CImageLoader and CTexture; CTexture is basically just an image with data, palette and height/width, but since I use this code in my 3D texture mapper directly, it is called texture. The source is commented and should be pretty simple to understand. Also it is not intended for straight copy-pasteing, rather than to learn from. AND it is copyrighted.
Happy hacking!
texture.h:
#define __TEXTURE_H
#include <e32base.h>
/**
* Represents a texture used by the texture mapping routines.
*/
class CTexture : public CBase
{
public:
/*!
@function NewL
@discussion Constructs a CTexture object
@param aWidth texture width (must be power of 2 from 3 to 10, thus giving
width range of 8 to 1024)
@param aHeight texture height
@param aWidthShift equal to log2(aWidth)
@param aPalette palette information. this is copied into a local buffer
@param aData indexed texture data with indices pointing to the palette.
This is not copied to a local buffer, so the original buffer must
not be released before destroying this object.
*/
static CTexture * NewL(TInt aWidth, TInt aHeight, TInt aWidthShift,
TUint16 *aPalette, TUint8 *aData);
~CTexture();
TInt GetWidth() { return iWidth; }
TInt GetHeight() { return iHeight; }
TInt GetWidthShift() { return iWidthShift; }
TUint16 * GetPalette() { return iPalette; }
TUint8 * GetData() { return iData; }
private:
CTexture();
void ConstructL(TInt aWidth, TInt aHeight, TInt aWidthShift,
TUint16 *aPalette,
TUint8 *aData);
TInt iWidth;
TInt iHeight;
TInt iWidthShift;
TUint16 *iPalette;
TUint8 *iData;
};
#endif
texture.cpp:
CTexture * CTexture::NewL(TInt aWidth, TInt aHeight, TInt aWidthShift,
TUint16 *aPalette, TUint8 *aData)
{
CTexture *texture = new (ELeave) CTexture();
CleanupStack::PushL(texture);
texture->ConstructL(aWidth, aHeight, aWidthShift,
aPalette, aData);
CleanupStack::Pop(); // texture
return texture;
}
void CTexture::ConstructL(TInt aWidth, TInt aHeight, TInt aWidthShift,
TUint16 *aPalette, TUint8 *aData)
{
iWidth = aWidth;
iHeight = aHeight;
iWidthShift = aWidthShift;
iData = aData;
// construct & copy palette to a local pointer
iPalette = (TUint16 *)User::AllocL(256 * sizeof(TUint16));
Mem::Copy(iPalette, aPalette, 256 * sizeof(TUint16));
}
CTexture::CTexture()
{
}
CTexture::~CTexture()
{
User::Free(iData);
User::Free(iPalette);
}
imageloader.h:
#define __IMAGELOADER_H
#include <e32base.h>
#include <MdaImageConverter.h>
#include <fbs.h>
#include "texture.h"
/**
* Color frequency array item.
*/
struct FreqItem {
TUint16 freq;
TUint16 color;
};
/**
* Utility methods for loading images.
*/
class CImageLoader : public CActive, public MMdaImageUtilObserver
{
public:
/**
* Error codes for texture loading
*/
enum TTextureLoadingError { KBadImageWidth = 1666001 };
/*!
@function LoadTextureL
@discussion Loads a image file and converts it into a CTexture object
@param aFilename filename of the image file
*/
static CTexture * LoadTextureL(const TDesC &aFilename);
// from MMdaImageUtilObserver
virtual void MiuoOpenComplete(TInt aError);
virtual void MiuoConvertComplete(TInt aError);
virtual void MiuoCreateComplete(TInt aError);
// from CActive
void RunL();
void DoCancel();
private:
CImageLoader(const TDesC *aFilename);
~CImageLoader();
void ReadImageL();
void CreateTexture();
void SortFreqTable(TInt aLeft, TInt aRight);
TUint8 FindNearestColor(TUint16 aColor, TInt aPaletteSize);
TDesC *iFilename;
CMdaImageFileToBitmapUtility *iConverter;
RTimer *iTimer;
CTexture *iTexture;
CFbsBitmap *iBitmap;
TInt iErrorCode;
FreqItem *iFreqTable;
};
#endif
imageloader.cpp:
#include "imageloader.h"
#define IMAGEREAD_TIMEOUT 5 * 1000 * 1000
//////////////////////////////////////
// CImageLoader
//////////////////////////////////////
void CImageLoader::DoCancel()
{
// cancel the timer
iTimer->Cancel();
iTimer->Close();
// end blocking in ReadTextureL()
CActiveScheduler::Stop();
}
void CImageLoader::RunL()
{
// timer timeout - loading failed
Cancel();
iTimer->Close();
}
CTexture * CImageLoader::LoadTextureL(const TDesC &aFilename)
{
// construct new loader instance
CImageLoader *loader = new (ELeave) CImageLoader(&aFilename);
CleanupStack::PushL(loader);
// add the loader to active scheduler
CActiveScheduler::Add(loader);
// perform operations for reading and converting the image
loader->ReadImageL();
// start a nested scheduling; blocks until CActiveScheduler::Stop()
// is called in DoCancel()
CActiveScheduler::Start();
// if error, leave with correct error code
if( loader->iTexture == NULL ) {
// instance will be destroyed by the cleanupstack
User::Leave(loader->iErrorCode);
}
// get a local copy of the instance's created texture
// if error(s) in process, this will be NULL
CTexture *texture = loader->iTexture;
// deallocate the instance
CleanupStack::PopAndDestroy();
// return the created texture
return texture;
}
CImageLoader::CImageLoader(const TDesC *aFilename)
: CActive(CActive::EPriorityStandard)
{
// make a local copy of the filename
iFilename = aFilename->Alloc();
}
CImageLoader::~CImageLoader()
{
RDebug::Print(_L("CImageLoader::~CImageLoader()"));
// deallocate all data
delete iFilename;
delete iTimer;
delete iConverter;
delete iBitmap;
}
// performs the actual reading and conversion of the image
void CImageLoader::ReadImageL()
{
// reset the texture
iTexture = NULL;
// create + initialize operation timeout timer
iTimer = new RTimer();
iTimer->CreateLocal();
// set timeout for the image read + conversion process
iTimer->After(iStatus, IMAGEREAD_TIMEOUT);
SetActive();
// start loading the image
iConverter = CMdaImageFileToBitmapUtility::NewL(*this);
iConverter->OpenL(*iFilename);
}
// called when OpenL() finishes
void CImageLoader::MiuoOpenComplete(TInt aError)
{
if( aError != KErrNone ) {
iErrorCode = aError;
Cancel();
return;
}
TFrameInfo info;
iConverter->FrameInfo(0, info);
// create a bitmap to write into
iBitmap = new (ELeave) CFbsBitmap();
TInt rc = iBitmap->Create(info.iOverallSizeInPixels, EColor4K);
if( rc != KErrNone )
{
iErrorCode = rc;
Cancel();
return;
}
// convert the gif into a bitmap
TRAPD(error, iConverter->ConvertL(*iBitmap));
// handle the error
if( error != KErrNone) {
iErrorCode = error;
Cancel();
return;
}
}
// called when ConvertL() finishes
void CImageLoader::MiuoConvertComplete(TInt aError)
{
if( aError != KErrNone ) {
iErrorCode = aError;
Cancel();
return;
}
// create the iTexture out of the bitmap data
CreateTexture();
// cancel the timeout timer to end blocking in LoadTextureL()
Cancel();
}
// quicksorts the color frequency table
void CImageLoader::SortFreqTable(TInt aLeft, TInt aRight)
{
TInt qleft = aLeft;
TInt qright = aRight;
TInt qpivot = iFreqTable[(qleft + qright) >> 1].freq;
do {
while( (iFreqTable[qleft].freq > qpivot) && (qleft < aRight) ) {
qleft++;
}
while( (qpivot > iFreqTable[qright].freq) && (qright > aLeft) ) {
qright--;
}
if( qleft <= qright ) {
// swap elements
TUint16 tmp = iFreqTable[qleft].freq;
iFreqTable[qleft].freq = iFreqTable[qright].freq;
iFreqTable[qright].freq = tmp;
tmp = iFreqTable[qleft].color;
iFreqTable[qleft].color = iFreqTable[qright].color;
iFreqTable[qright].color = tmp;
qleft++;
qright--;
}
} while( qleft <= qright );
// recursively sort left side
if( aLeft < qright ) SortFreqTable(aLeft, qright);
// recursively sort right side
if( qleft < aRight ) SortFreqTable(qleft, aRight);
}
// find color from the first 256 entries with smallest cubic difference
// component wise for the given 12bit color
inline TUint8 CImageLoader::FindNearestColor(TUint16 aColor, TInt aPaletteSize)
{
TInt index = -1;
TUint difference = 0xffffffff;
// extract original color components
TUint red0 = (aColor >> 8) & 0xf;
TUint green0 = (aColor >> 4) & 0xf;
TUint blue0 = aColor & 0xf;
for( TInt i = 0; i < aPaletteSize; i++ ) {
TUint16 color = iFreqTable[i].color;
// extract components of palette color
TUint red = (color >> 8) & 0xf;
TUint green = (color >> 4) & 0xf;
TUint blue = color & 0xf;
// calculate cubic difference
TUint diff = ((red0 - red) * (red0 - red)) +
((green0 - green) * (green0 - green)) +
((blue0 - blue) * (blue0 - blue));
// try early-out if exact match
if( diff == 0 ) {
return (TUint8)i;
}
if( diff < difference ) {
difference = diff;
index = i;
}
}
return (TUint8)index;
}
void CImageLoader::CreateTexture()
{
// verify bitmap size; its width must be a power of 2 (8 to 1024)
TSize imagesize = iBitmap->SizeInPixels();
TInt widthshift = 0;
// find correct shift value by calculating log2(widht). since there is no
// log2() we'll use log2(x) = log10(x) / log10(2)
TReal res1, res2, val = (TReal)imagesize.iWidth, two = 2.0;
Math::Log(res1, val);
Math::Log(res2, two);
// must be an integer; check that result has fractional part of 0
res1 = res1 / res2;
Math::Frac(res2, res1);
if( (res2 == 0.0) && (res1 > 3.0) ) {
widthshift = (TInt)res1;
} else {
iErrorCode = KBadImageWidth;
return;
}
// allocate memory for the indexed texture data
TInt texture_data_size = imagesize.iWidth * imagesize.iHeight;
TUint8 *data = (TUint8 *)User::Alloc(texture_data_size);
if( data == NULL ) {
iErrorCode = KErrNoMemory;
return;
}
// allocate color frequency array
iFreqTable = (FreqItem *)User::Alloc(4096 * sizeof(FreqItem));
if( iFreqTable == NULL ) {
iErrorCode = KErrNoMemory;
User::Free(data);
return;
}
Mem::FillZ(iFreqTable, 4096 * sizeof(FreqItem));
// extract bitmap header
SEpocBitmapHeader hdr = iBitmap->Header();
// calculate start address of the actual bitmap data
TInt data_start = (TInt)iBitmap->DataAddress();
TInt datasize = hdr.iBitmapSize - hdr.iStructSize;
// calculate color frequencies
TUint16 *p = (TUint16 *)data_start;
datasize >>= 1;
for( TInt i = 0; i < datasize; i++ ) {
TUint16 color = *p++;
iFreqTable[color].color = color;
if( iFreqTable[color].freq < 0xffff ) {
iFreqTable[color].freq++;
}
}
//calculate num. distinct colors
TInt num_colors = 0;
for( i = 0; i < 4096; i++ ) {
if( iFreqTable[i].freq > 0 ) {
num_colors++;
}
}
// sort the color frequency array
SortFreqTable(0, 4095);
// find nearest match for each color in the bitmap from the first
// 256 entries of the palette
p = (TUint16 *)data_start;
for( i = 0; i < datasize; i++ ) {
TUint16 color = *p++;
data[i] = FindNearestColor(color, 256);
}
// copy the palette data
TUint16 palette[256];
for( i = 0; i < 256; i++ ) {
palette[i] = iFreqTable[i].color;
}
// deallocate frequency table
User::Free(iFreqTable);
iFreqTable = NULL;
// create the texture object
TRAPD(error, (iTexture = CTexture::NewL(imagesize.iWidth, imagesize.iHeight,
widthshift, palette, data)));
if( error != KErrNone ) {
User::Free(data);
iErrorCode = error;
return;
}
}
// not used
void CImageLoader::MiuoCreateComplete(TInt /*aError*/)
{
}






> Image loading and color reduction
> Image loading and color reduction
It already loads into a CFbsBitmap... just after the call to CActiveScheduler::Start() returns in LoadTextureL(), the loader instance should have a valid iBitmap data member that you can access/copy/play with. Notice though that a few lines down this gets popped off the stack and deleted, though, so grab it and do something with it here. Also, if you just want the CFbsBitmap, you can delete the code that converts it into a 256-color paletted texmap.
Sam
> Image loading and color reduction
Color reduction - comparison to CFbsColor256BitmapUtil
> Image loading and color reduction
TInt rc = iBitmap->Create(info.iOverallSizeInPixels, EColor4K);
This ligne return error 0xFFFFFFDE ! why ? The size is correct, I'm tried with different format :(
But my app is an EXE application, I have include this code :(http://discussion.forum.nokia.com/forum/showthread.php?s=96d4807d3d54fcef0f69f34d2f0b87e7&threadid=13390&highlight=%2AGUI+in+EXE%2A)
thank you for your help
how to create skins for an application
> Image loading and color reduction
How many bpp does it convert from to 12bit? For example, does it convert 24bits per pixel to 12bit per pixel? I am using PCX files with index palette, however, I think the palette is made up of 8bit red, 8bit green and 8bit blue color components. will the code handle something like this? PS: I will simply get color from the palette instead of directly from the data.
Thanks, Erica
> Image loading and color reduction
[PLEASE IGNORE ABOVE QUESTION]
How many bpp does it convert from to 12bit? For example, does it convert 24bits per pixel to 12bit per pixel?
Does ConvertL() take a 24bit image convert it to 16bit color of 256 colors and stuff it into CFbsBitmap? If ConvertL() converts it to a CFbsBitmap why must it be color reduced? what is happening in ConvertL I know that it converts a gif or png or jgp to CFbsBitmap but that doesn't tell me what's really happening?
Inside of FindNearestColor() the code TUint red0 = (aColor >> 8) & 0xf; TUint green0 = (aColor >> 4) & 0xf; TUint blue0 = aColor & 0xf; Assumes that CFbsBitmap so some kind of color reduction has already taken place!
The code assumes that the original image has a palette 256 distinct colors what happens if it has > 256 distinct colors?
PS: I compress my .gif files with gzip untility is it possible to pass a gzip'ed .gif file to ConvertL() ?
Thanks, Erica
> Image loading and color reduction
In the example, a number is tested for being a power of two. I saw that there is a better way to do that. See this link.
http://www.parashift.com/c++-faq-lite/intrinsic-types.html#faq-26.12
> Image loading and color reduction