Richard Parker / EALCD

widgets/EAImage.cpp

Committer:
richardparker
Date:
2010-03-31
Revision:
3:24fbf4dbd7e5
Parent:
1:f04bcaea1d60
Child:
6:4fe6f365cbeb

File content as of revision 3:24fbf4dbd7e5:

// Copyright 2010 Richard Parker

#include "mbed.h"

#include "EAImage.h"

#include "../graphics/EAPen.h"
#include "../graphics/EAColor.h"
#include "../screen/EALCD.h"

EAImage::EAImage()
:   _path(NULL),
    _palette(NULL),
    _dataOffset(0),
    _mask(false)
{
    // Start from known state.
    unload();
}

EAImage::~EAImage()
{
    if (_path != NULL)
    {
        delete[] _path;
        _path = NULL;
    }
    
    if (_palette != NULL)
    {
        delete[] _palette;
        _palette = NULL;
    }
}

bool EAImage::_loadPalette(FILE* fp)
{
    // Can't do anything with a null pointer.
    if (fp == NULL)
    {
        return false;
    }
    
    // Clear any previously loaded palette.
    if (_palette != NULL)
    {
        delete[] _palette;
        _palette = NULL;
    }
    
    // If the number of bits is not less than 16 then return after having cleared the 
    // palette. There is no palette required.
    if (info().bitsPerPixel >= 16)
    {
        return true;
    }
    
    // First create a palette of the required size.
    unsigned int noColors = info().noColors;
    
    // When 0 the number of colors is equal to 2^n where n is the bits per pixel.
    if (noColors == 0)
    {
        noColors = pow((double)2, info().bitsPerPixel);
    }
    
    // Create the palette and assign the memory.
    _palette = new EAColor[noColors];
    
    // Now parse the palette. First seek to the start of the palette. This must be the 
    // start of the bitmap data minus the number of colour entries multiplied by 4 (the
    // colours are stored as 4 byte long entries, the last byte can be ignored).
    unsigned int offset = _dataOffset - (noColors*4);
    
    // Seek to the start of the table.
    fseek(fp, offset, SEEK_SET);
    unsigned char buffer[4];
    
    // Iterate through the table filling the palette as needed.
    for (int i = 0; i < noColors; i++)
    {
        int sizeRead = fread(&buffer, 4, 1, fp);
        
        if (sizeRead == 1)
        {
            _palette[i].setRgb(buffer[2], buffer[1], buffer[0]);
        }
    }
    
    return true;
}

bool EAImage::_loadHeader(const char* path)
{
    if (path == NULL)
    {
        // Invalid path passed in.
        return false;
    }
    
    // Try and open the file, check type and load width and height.
    FILE* fp = fopen(path, "r");
    if (fp == NULL)
    {
        return false;
    }

    unsigned int bufint = 0;
    unsigned int offset = 0;

    // Read the magic numbers at start.
    fread(&bufint, 1, 2, fp);   
    if (bufint != 0x4d42)
    {
        // Clean up file handle.
        fclose(fp);
    
        // Header incorrect.
        return false;
    }
       
    // Now read the bmp file size.
    fread(&bufint, 1, 4, fp);
      
    // Now read the two creator values and throw away.
    fread(&bufint, 1, 2, fp);
    fread(&bufint, 1, 2, fp);
    
    // Now read the offset for the bitmap data.
    fread(&bufint, 1, 4, fp);
    offset = bufint;
         
    // Retrieve the header.
    fread(&_header, sizeof(EABMPHeader), 1, fp);
          
    // Make sure the compression type is a value that can be dealt with. 
    if ((info().compressionType != 3) && (info().compressionType != 0))
    {      
        // Clean up file handle.
        fclose(fp);
    
        // Header incorrect.
        return false;
    }
    
    // Set the values for later.
    _dataOffset = offset;
    setWidth(info().width);
    setHeight(info().height);
             
    // Close the file.
    fclose(fp);
    
    return true;
}

int EAImage::_wordsInRow()
{
    // If there were no padding to 32 bit boundaries this would be the number of bits per row 
    // in the file.
    int bitsPerWidth = width() * info().bitsPerPixel;
       
    // This is the row size with padding on the end.
    int remainder = (bitsPerWidth % 32);
    int bitsPerRow = (remainder == 0) ? bitsPerWidth : bitsPerWidth + (32 - remainder);
    
    // Return the size in number of words.
    return (bitsPerRow / 32);
}

int EAImage::_wordForX(unsigned int x)
{
    int bitForX = x * info().bitsPerPixel;
    
    return (bitForX / 32);
}

int EAImage::_xWordOffset(unsigned int x)
{
    int bitForX = x * info().bitsPerPixel;
    
    return (bitForX % 32);
}

unsigned short EAImage::_getColourAtOffset(unsigned int word, int offset)
{
    // Sort bytes for endianness.
    unsigned char* cptr;
    unsigned short result = 0x0000;

    // Now need to decide how to cast the value to a colour.
    switch (info().bitsPerPixel)
    {
        // Swap the bytes around.
        case 32:
        case 4:
        case 8:
        case 1:
            unsigned char tmp;
            cptr = (unsigned char*)&word;
            tmp = cptr[0];
            cptr[0] = cptr[3];
            cptr[3] = tmp;
            tmp = cptr[1];
            cptr[1] = cptr[2];
            cptr[2] = tmp;
        break;

        default:
        // Swap the 16bits around.
        case 16:
            unsigned char tmpa;
            unsigned char tmpb;
            cptr = (unsigned char*)&word;
            tmpa = cptr[1];
            tmpb = cptr[0];
            cptr[1] = cptr[3];
            cptr[0] = cptr[2];
            cptr[3] = tmpa;
            cptr[2] = tmpb;
        break;
    }

    // First shift off the pixels above.
    word = word << offset;
    
    // Now shift down so that pixel is in lsb.
    word = word >> (32 - info().bitsPerPixel);

    EAColor c;
            
    // Now need to decide how to cast the value to a colour.
    switch (info().bitsPerPixel)
    {
        case 8:
        case 4:
        case 1:
            if (_palette != NULL)
            {
                result = _palette[word].rawValue();
            } else {
                result = 0x0000;
            }
        break;

        // By default just cast to unsigned short and return.        
        default:
        case 16:
            result = (unsigned short)(word);
        break;
        
        case 24:                        
        case 32:         
            unsigned char b = ((word << 0) >> 24);
            unsigned char g = ((word << 8) >> 24);
            unsigned char r = ((word << 16) >> 24);
                       
            c.setRgb(r, g, b);
            result = c.rawValue();
        break;
    }
    
    return result;
}

bool EAImage::load(const char* path)
{
    if (path == NULL)
    {
        // Reset all of the state.
        unload();

        // Invalid path passed in.
        return false;
    }

    if (_loadHeader(path) == false)
    {
        // Reset all of the state.
        unload();

        return false;
    }

    int pathLen = strlen(path);
    
    // If already loaded an image then clear to load the new one.
    if (_path != NULL)
    {
        delete[] _path;
        _path = NULL;
    }
    
    // Now allocate enough space to hold path. Note +1 for null character.
    _path = new char[pathLen+1];
    
    // Now copy over passed in path to path variable.
    strcpy(_path, path);
    
    // Image loaded successfully.
    return true;
}

void EAImage::unload()
{
    // Empty the header struct.
    _header.headerSize = 0;
    _header.width = 0;
    _header.height = 0;
    _header.noColorPlanes = 0;
    _header.bitsPerPixel = 0;
    _header.compressionType = 0;
    _header.bmpSize = 0;
    _header.horizontalRes = 0;
    _header.verticalRes = 0;
    _header.noColors = 0;
    _header.noImportantColors = 0;

    // Clear the path.
    if (_path != NULL)
    {
        delete[] _path;
        _path = NULL;
    }
    
    // Clear the palette.
    if (_palette != NULL)
    {
        delete[] _palette;
        _palette = NULL;
    }

    // Clear the data offset.
    _dataOffset = 0;
    
    // Set the size to 0.
    setWidth(0);
    setHeight(0);
    
    // Set as not a mask.
    _mask = false;
}

void EAImage::paint(EALCD& lcd)
{
    paint(lcd, 0, 0, width(), height());
}

void EAImage::paint(EALCD& lcd, unsigned int x, unsigned int y, unsigned int w, unsigned int h)
{
    if (isValid() == false)
    {
        return;
    }
    
    // Don't allow draw out of range.
    if (x + w > width())
    {
        return;
    }

    if (y + h > height())
    {
        return;
    }
    
    // Buffer to hold load one line at a time this must be large enough to hold all of the data used 
    // for a line in the file. Note that the line in the file is padded so that it always ends on a 
    // 32 bit boundary.
    int wordsInRow = _wordsInRow();
    unsigned int buffer[wordsInRow];
    int xD = 0;
    int yD = 0;
    int wordOffset = 0;
    int bitsPerPixel = info().bitsPerPixel;
    unsigned short data = 0;
    
    // Try and open the file, skip straight to the data.
    FILE* fp = fopen(_path, "r");
    if (fp == NULL)
    {
        return;
    }
    
    // If the bits per pixel are less than 16 then the bitmap is using a colour 
    // palette which should be loaded first.
    _loadPalette(fp);   
    
    // Skip the header and size.
    fseek(fp, _dataOffset, SEEK_SET);
    
    // Prepare the lcd for drawing.
    lcd._window(this->x(), this->y(), w, h);
    lcd._moveTo(this->x(), this->y()); 
    
    // Move in the file to the first pixel in the window.
    fseek(fp, (y*wordsInRow*4)+(_wordForX(x)*4), SEEK_CUR); 
                            
    // Now read the data.
    while (!feof(fp))
    {    
        wordOffset = _xWordOffset(x);
               
        int sizeRead = fread(&buffer, 4, wordsInRow, fp);
        
        for (int i = 0; i < sizeRead; i++)
        {        
            while (wordOffset < 32)
            {           
                // Convert the colour to a 16 bit colour value that can be written directly to the screen.                
                data = _getColourAtOffset(buffer[i], wordOffset);

                if (isMask() == true)
                {
                    // When a mask the 0x0000 value is transparent anything else is drawn with the pen color.
                    if (data == 0x0000)
                    {
                        lcd.noop();
                    } else {
                        lcd._writeToDisplay(lcd.pen().color().rawValue());
                    }
                } else {
                    // Not a mask so just use colour that have loaded.
                    lcd._writeToDisplay(data);
                }
                
                // Got to next pixel in the word.
                wordOffset += bitsPerPixel;

                // Keep count of current x value.
                xD++;
                if (xD == w)
                {
                    break;
                }            
            }
            wordOffset = 0;
            
            // When written all required pixels exit.
            if (xD == w)
            {
                xD = 0;
                break;
            }            
        }

        // Keep count of curernt y value.
        yD++;
        if (yD == h)
        {
            break;
        }            
    }
    
    // Clear the palette.
    if (_palette != NULL)
    {
        delete[] _palette;
        _palette = NULL;
    }
    
    // Close the file and release handle.
    fclose(fp);
}