Richard Parker / EALCD

widgets/EAImage.cpp

Committer:
richardparker
Date:
2010-05-06
Revision:
6:4fe6f365cbeb
Parent:
3:24fbf4dbd7e5

File content as of revision 6:4fe6f365cbeb:

// 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),
    _cached(false),
    _cache(NULL),
    _size(0L),
    _pos(0L)
{
    // Start from known state.
    unload();
}

EAImage::~EAImage()
{
    unload();
}

int EAImage::_feof(FILE* stream)
{
    if (isCached() == true)
    {
        return (_pos < _size) ? 0 : 1;
    } else {
        return feof(stream);
    }
}


int EAImage::_fseek(FILE* stream, long int offset, int origin)
{
    if (isCached() == true)
    {   
        switch (origin)
        {
            default:
            case SEEK_SET:
                _pos = offset;
            break;
            case SEEK_CUR:
                _pos = _pos + offset;
            break;
            case SEEK_END:
                _pos = (_size-1) + offset;
            break;
        }
        
        // Make sure position within range.
        if (_pos < 0)
        {
            _pos = 0;
            return 0;
        }
        
        if (_pos > (_size-1))
        {
            _pos = (_size-1);
            return 0;
        }
        
        // Error.
        return 1;
    } else {
        return fseek(stream, offset, origin);
    }
}

size_t EAImage::_fread(void* ptr, size_t size, size_t count, FILE* stream)
{
    if (isCached() == true)
    {
        // Truncate size to fit array if needed.
        if (_pos + (count*size) > _size)
        {
            count = (_size - _pos)/size;
        }
        
        if (count <= 0)
        {
            return 0;
        }
        
        // Now copy out of cache to array.
        memcpy(ptr, &_cache[_pos], count*size);
        
        // Update the file position.
        _pos = _pos + (count*size);
        
        // Return the number of bytes read.
        return count;
    } else {
        return fread(ptr, size, count, stream);
    }
}

bool EAImage::_loadCache(FILE* fp)
{
    // Can't do anything with a null pointer.
    if (fp == NULL)
    {
        return false;
    }
    
    // Clear any previously loaded cache.
    if (_cache != NULL)
    {
        delete[] _cache;
        _cache = NULL;
    }
    _size = 0L;
    _pos = 0L;

    if (isCached() == false)
    {
        return true;
    }

    // Get the size of the file.    
    fseek(fp, 0, SEEK_END);
    long int size = ftell(fp);
    
    if (size == -1)
    {
        // Something went wronmg with the command.
        return false;
    }

    // Now create a cache large enough to hold all of the image data.
    _cache = new char[size];
    
    if (_cache == NULL)
    {
        // Unable to allocate enough space for the font.
        return false;
    }

    // Now rewind file pointer back to the beginning.
    rewind(fp);
    
    const unsigned int CHUNK_SIZE = 100;
    long int sizeRead = 0;
    long int totalSizeRead = 0;
        
    // Load in small chunks at a time.
    while (!feof(fp))
    {
        // Read into the buffer.
        sizeRead = fread(&_cache[totalSizeRead], 1, CHUNK_SIZE, fp);
                        
        // Update total count of amount of data loaded.
        totalSizeRead += sizeRead;
    }
    
    // Check that the whole file loaded.
    if (totalSizeRead != size)
    {
        // Clear cache.        
        delete[] _cache;
        _cache = NULL;
        
        // Return error.
        return false;
    }
    
    // Record size and position.
    _size = size;
    _pos = 0L;
    
    return true;
}

bool EAImage::_loadPalette(FILE* fp)
{
    // Can't do anything with a null pointer unless cached.
    if ((fp == NULL) && (isCached() == false))
    {
        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);
        unload();
    
        // 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);        
        unload();
    
        // Header incorrect.
        return false;
    }
    
    // Set the values for later.
    _dataOffset = offset;
    setWidth(info().width);
    setHeight(info().height);
    
    // If the image is to be cached then load the cache.
    if (_loadCache(fp) == false)
    {
        // Clean up file handle.
        fclose(fp);        
        unload();
    
        // Header incorrect.
        return false;
    }
             
    // 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);
    
    // Empty the cache.
    if (_cache != NULL)
    {
        delete[] _cache;
        _cache = NULL;
    }    
    _size = 0L;
    _pos = 0L;
}

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 = NULL;
    if (isCached() == false)
    {
        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.
    if (_loadPalette(fp) == false)
    {
        if (isCached() == false)
        {
            fclose(fp);
        }
        
        return;
    }
    
    // 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;
    }
    
    if (isCached() == false)
    {
        // Close the file and release handle.
        fclose(fp);
    }
}