PokittoLib is the library needed for programming the Pokitto DIY game console (www.pokitto.com)

Revision:
23:f88837b8f914
Child:
45:bbfc6002118c
diff -r e826f80d8582 -r f88837b8f914 POKITTO_LIBS/ImageFormat/BmpImage.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/POKITTO_LIBS/ImageFormat/BmpImage.cpp	Fri Dec 29 05:17:10 2017 +0000
@@ -0,0 +1,482 @@
+/**************************************************************************/
+/*!
+    @file     BmpImage.cpp
+    @author   Hannu Viitala. Original BMP decoder code by Jonne Valola.
+
+    @section LICENSE
+
+    Software License Agreement (BSD License)
+
+    Copyright (c) 2016, Jonne Valola
+    All rights reserved.
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions are met:
+    1. Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in the
+    documentation and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holders nor the
+    names of its contributors may be used to endorse or promote products
+    derived from this software without specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+    DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
+    DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+    ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+/**************************************************************************/
+
+#if POKITTO_USE_WIN_SIMULATOR
+#include "defines_win_SIM.h"
+#endif
+
+#define PROJ_MBED 1
+
+#if PROJ_LINUX || PROJ_MBED
+#include "defines_linux_SIM.h"
+#endif
+
+#include "Pokitto.h"
+#include "PokittoDisk.h"
+#ifdef POK_SIM
+#include "FileIO.h"
+#endif
+
+#if POK_ENABLE_SD > 0
+
+
+#include "PokittoDisplay.h"
+#include "ImageFormat.h"
+
+Pokitto::Core _game;
+Pokitto::Display pokdisp;
+
+int openImageFileFromSD(char* filepath, uint16_t **palette_out, uint8_t **bitmap_out) {
+
+    // Reset the out pointers.
+    *palette_out = 0;
+    *bitmap_out = 0;
+
+    BITMAPFILEHEADER bf;
+    BITMAPINFO bmi;
+
+    uint32_t bytes_read=0, bmpsize=0;
+
+    /** numcol is the number of colors for output */
+    unsigned int numcol = 0;
+
+    if (POK_COLORDEPTH == 8) numcol=256;
+    else if (POK_COLORDEPTH == 4) numcol=16;
+    else if (POK_COLORDEPTH == 2) numcol=4;
+    else if (POK_COLORDEPTH == 1) numcol=1;
+
+    if (!isThisFileOpen(filepath)) {
+        fileClose(); // close any open files
+        fileOpen(filepath, FILE_MODE_READONLY | FILE_MODE_BINARY);
+    }
+    else
+        return -1;  // Already open, not good.
+
+    if (fileOK() && fileReadBytes((uint8_t*)&bf, sizeof(bf)) == sizeof(bf) ) {
+        bytes_read += sizeof(bf);
+    }
+	else
+	{
+		POK_TRACE("Error reading BMP header\n");
+		fileClose();
+		return(-1);
+	}
+
+    if (fileReadBytes((uint8_t*)&bmi,sizeof(bmi.bmiHeader)) != sizeof(bmi.bmiHeader)) {
+		POK_TRACE("Error reading BMP info\n");
+		fileClose();
+		return(-1);
+	}
+    bytes_read += sizeof(bmi.bmiHeader);
+
+    /** Check image validity */
+
+    if (bf.bfType != 0x4D42) {
+        POK_TRACE("Bitmap file has an unrecognized format (4D42 id missing from beginning).\n");
+        POK_TRACE("BMP2POK accepts .BMP files that have an indexed (1,-bit, 4-bit or 8-bit) color palette.\n");
+        fileClose();
+        return(-1);
+    }
+    if (bmi.bmiHeader.biBitCount != POK_COLORDEPTH ) {
+        POK_TRACE("ERROR!\nThe image color depth should be the same as screen color depth!\n");
+    }
+    if (bmi.bmiHeader.biWidth%32 && bmi.bmiHeader.biBitCount == 1) {
+        POK_TRACE("ERROR!\nPadding of 1-bit (monochrome) images is not yet supported\n");
+        POK_TRACE("1-bit images need to have width that is divisible by 32!\n");
+        POK_TRACE("Adjust size of source image.\n");
+        fileClose();
+        return(-1);
+    }
+	if (bmi.bmiHeader.biWidth%4) {
+        POK_TRACE("Width is not divisible by 4\n");
+        fileClose();
+        return(-1);
+	}
+	if (bmi.bmiHeader.biWidth%8 && bmi.bmiHeader.biBitCount==4) {
+        if (bmi.bmiHeader.biWidth%4) {
+            POK_TRACE("ERROR!\n4-bit source images have to have a width that is divisible by 4\n");
+            fileClose();
+            return(-1);
+        }
+	}
+    if (bmi.bmiHeader.biBitCount != 8 && bmi.bmiHeader.biBitCount != 4 && bmi.bmiHeader.biBitCount != 1)
+    {
+        POK_TRACE("Only 8bpp, 4bpp & 1bpp BMP files are supported\n");
+        fileClose();
+        return(-1);
+    }
+    if (bmi.bmiHeader.biCompression != 0 &&
+        !(bmi.bmiHeader.biCompression == BI_RLE4 && bmi.bmiHeader.biBitCount == 4))
+    {
+        POK_TRACE("Only RLE compression for bitmaps with 4 bpp is supported\n");
+        fileClose();
+        return(-1);
+    }
+
+    /* If the height is negative the bmp image is in the correct way.
+       If the heigh is positive the bmp image is vertically mirrored
+    */
+    int biAbsHeight = bmi.bmiHeader.biHeight;
+    if (bmi.bmiHeader.biHeight < 0 )
+        biAbsHeight = - bmi.bmiHeader.biHeight;
+
+    /** Read and copy palette **/
+
+    int c = bmi.bmiHeader.biClrUsed;
+    if (c==0) c = 1 << bmi.bmiHeader.biBitCount; // from MS BMP specs. 0 means 2^n colors
+    bmi.bmiHeader.biClrUsed = c;
+
+    /* Allocate memory for the output parameter */
+    if (numcol>bmi.bmiHeader.biClrUsed) numcol = bmi.bmiHeader.biClrUsed;
+    *palette_out = (uint16_t*) malloc(numcol*2);
+  	if (*palette_out == NULL)
+	{
+		POK_TRACE("Error allocating temporary palette buffer.\n");
+		free(*palette_out);
+		return(-1);
+	}
+
+    /* seek to the beginning of the color table - because of gimp */
+    fileSeekAbsolute(bf.bfOffBits-c*4); //gfx data star minus color table
+
+    for (unsigned int c=0;c<numcol;c++) {
+
+        RGBQUAD rgbValue;
+        fileReadBytes((uint8_t*)&rgbValue, sizeof(RGBQUAD));
+        bytes_read += sizeof(RGBQUAD);
+
+        unsigned int r,g,b,o;
+        r = rgbValue.rgbRed >> 3; // 5 bit
+        g = rgbValue.rgbGreen >> 2; // 6 bits
+        b = rgbValue.rgbBlue >> 3; // 5 bits
+        o = (r<<11)|(g<<5)|b;
+
+        (*palette_out)[c] = o;
+    }
+
+    /** Read and copy image data **/
+
+    /* Get image data size. If the biSizeImage is given (>0) for RLE image, use that to reduce memory usage. */
+    bmpsize = bmi.bmiHeader.biWidth * biAbsHeight*bmi.bmiHeader.biBitCount/8;
+	if (bmi.bmiHeader.biCompression == BI_RLE4)
+        bmpsize = (bmi.bmiHeader.biSizeImage > 0) ? bmi.bmiHeader.biSizeImage : bmpsize;
+
+    // SEEK to the beginning of the data
+	fileSeekAbsolute(bf.bfOffBits);
+
+   /* Allocate output data buffer */
+    *bitmap_out = (uint8_t *) malloc(bmpsize + 2);  // header takes 2 bytes
+    if (*bitmap_out == NULL)
+    {
+        POK_TRACE("Error allocating temporary data buffer, is image too big?\n");
+        free(*palette_out);
+        return(-1);
+    }
+
+    /* Store image size to the pokitto bitmap header */
+    uint32_t outindex = 0;
+    (*bitmap_out)[outindex++] = bmi.bmiHeader.biWidth;
+    (*bitmap_out)[outindex++] = biAbsHeight;
+
+ 	if (bmi.bmiHeader.biCompression == BI_RLE4) {
+        bool eofReached = false;
+        while (outindex < bmpsize && !eofReached ) {
+            /* Read byte from the file. */
+            unsigned char rleByte;
+            if (fileReadBytes(&rleByte, sizeof(rleByte)) != sizeof(rleByte))
+            {
+                /* End of file reached. Allocate a new bitmap which is of the exact size of the data */
+                eofReached = true;
+
+                /* Allocate output data buffer */
+                uint8_t* old_bitmap = *bitmap_out;
+                *bitmap_out = NULL;
+                *bitmap_out = (uint8_t *) malloc(outindex);  // header takes 2 bytes
+                if (*bitmap_out == NULL)
+                {
+                    POK_TRACE("Error allocating temporary data buffer, is image too big?\n");
+                    free(old_bitmap);
+                    free(*palette_out);
+                    return(-1);
+                }
+
+                /* Copy data */
+                for (int i=0; i<outindex;i++)
+                   (*bitmap_out)[i] = old_bitmap[i];
+
+                /* Free original bitmap */
+                free(old_bitmap);
+            }
+            else {
+                /* Store byte */
+                (*bitmap_out)[outindex++] = rleByte;
+            }
+        } // end while
+	}
+	else {
+        /* Do vertical mirroring only for uncompressed data.
+           Note the compressed RLE data above could not be mirrored.
+        */
+        int widthInBytes = bmi.bmiHeader.biWidth * bmi.bmiHeader.biBitCount/8;
+        int widthInBytesInc, incY, startY, endY;
+        if (bmi.bmiHeader.biHeight > 0 ) {
+            /* Mirror vertically */
+            widthInBytesInc = - widthInBytes;
+            incY = -1;
+            startY = biAbsHeight - 1;
+            endY = -1;
+        }
+        else {
+            /* Do not mirror */
+            widthInBytesInc = widthInBytes;
+            incY = 1;
+            startY = 0;
+            endY = biAbsHeight;
+        }
+
+        /* Copy all bytes to the output bitmap */
+        for ( int y = startY, beginLine=startY*widthInBytes; y != endY; y += incY, beginLine += widthInBytesInc) {
+
+            /* Go to the beginning of the previous or next line */
+            outindex = beginLine;
+
+            for ( int xbyte = 0; xbyte < widthInBytes; xbyte++) {
+
+                /* Read byte from the file. */
+                unsigned char byteOfPixels;
+                if (fileReadBytes(&byteOfPixels, sizeof(byteOfPixels)) != sizeof(byteOfPixels))
+                {
+                    POK_TRACE("Error reading BMP data\n");
+                    fileClose();
+                    free(*bitmap_out);
+                    free(*palette_out);
+                    return(-1);
+                }
+
+                /* Copy a byte from the file to the bitmap */
+                (*bitmap_out)[2 + outindex] = byteOfPixels;
+                outindex++;
+            } // end for
+        } // end for
+	} // end if
+
+	// Done with the file reading.
+	fileClose();
+
+    return 0;
+}
+
+int directDrawImageFileFromSD(int16_t sx, int16_t sy, char* filepath) {
+
+    return(directDrawImageFileFromSD(0, 0, 0/* full width */, 0/* full height */, sx, sy, filepath));
+}
+
+int directDrawImageFileFromSD(uint16_t ix, uint16_t iy, uint16_t iw, uint16_t ih, int16_t sx, int16_t sy, char* filepath) {
+
+    BITMAPFILEHEADER bf;
+    BITMAPINFO bmi;
+
+    uint32_t bytes_read=0;
+
+    if (!isThisFileOpen(filepath)) {
+        fileClose(); // close any open files
+        fileOpen(filepath, FILE_MODE_READONLY | FILE_MODE_BINARY);
+    }
+    else {
+        POK_TRACE("Error! Already open\n");
+        return -1;  // Already open, not good.
+    }
+
+    if (fileOK() && fileReadBytes((uint8_t*)&bf, sizeof(bf)) == sizeof(bf) ) {  //!HV why we have to check fileOK()?
+        bytes_read += sizeof(bf);
+    }
+	else
+	{
+		POK_TRACE("Error reading BMP header\n");
+		fileClose();
+		return(-1);
+	}
+
+    if (fileReadBytes((uint8_t*)&bmi,sizeof(bmi.bmiHeader)) != sizeof(bmi.bmiHeader)) {
+		POK_TRACE("Error reading BMP info\n");
+		fileClose();
+		return(-1);
+	}
+    bytes_read += sizeof(bmi.bmiHeader);
+
+    /** Check image validity */
+
+    if (bf.bfType != 0x4D42) {
+        POK_TRACE("Bitmap file has an unrecognized format (4D42 id missing from beginning).\n");
+        POK_TRACE("BMP2POK accepts .BMP files that have an indexed (1,-bit, 4-bit or 8-bit) color palette.\n");
+        fileClose();
+        return(-1);
+    }
+    if (bmi.bmiHeader.biBitCount != 24 ) {
+        POK_TRACE("ERROR!\nThe image color depth should be the same as screen color depth (%d)!\n");
+		fileClose();
+        return(-1);
+    }
+    if (bmi.bmiHeader.biCompression != 0 )
+    {
+        POK_TRACE("Compression is not supported.\n");
+        fileClose();
+        return(-1);
+    }
+
+    /* If the height is negative the bmp image is in the correct way.
+       If the heigh is positive the bmp image is vertically mirrored
+    */
+    int biAbsHeight = bmi.bmiHeader.biHeight;
+    if (bmi.bmiHeader.biHeight < 0 )
+        biAbsHeight = - bmi.bmiHeader.biHeight;
+
+    /** Zero size parameter means full image size */
+    if(iw==0) iw = bmi.bmiHeader.biWidth; // 0 means full image width
+    if(ih==0) ih = biAbsHeight; // 0 means full image width
+
+    /** Check parameters */
+    if( ix + iw > bmi.bmiHeader.biWidth ) {
+        POK_TRACE("Error! Invalid parameter\n");
+		fileClose();
+        return(-1);
+    }
+    if( iy + ih > biAbsHeight ) {
+        POK_TRACE("Error! Invalid parameter\n");
+ 		fileClose();
+        return(-1);
+    }
+    if( sx > pokdisp.getWidth()-1 || sx<-iw) {
+        POK_TRACE("Error! Invalid parameter\n");
+ 		fileClose();
+        return(-1);
+    }
+    if( sy > pokdisp.getHeight()-1  || sy<-ih) {
+        POK_TRACE("Error! Invalid parameter\n");
+		fileClose();
+        return(-1);
+    }
+
+    /** Zero size parameter means full image size */
+    if(iw==0) iw = bmi.bmiHeader.biWidth; // 0 means full image width
+    if(ih==0) ih = biAbsHeight; // 0 means full image width
+
+    /** Clip image to screen dimensions */
+
+    int16_t clipX1OnScreen = max( 0, sx);
+    int16_t clipX2OnScreen = min( pokdisp.getWidth()-1, sx+iw-1);
+    int16_t clipWidthOnScreen = clipX2OnScreen-clipX1OnScreen+1;
+    int16_t clipY1OnScreen = max( 0, sy);
+    int16_t clipY2OnScreen = min( pokdisp.getHeight()-1, sy+ih-1);
+    int16_t clipHeightOnScreen = clipY2OnScreen-clipY1OnScreen+1;
+
+    uint16_t skipImagePixelsAtLineStart = ix+(clipX1OnScreen-sx);
+    uint16_t skipImagePixelsAtLineStartAsBytes = skipImagePixelsAtLineStart*3; // 3 bytes per pixel
+    uint16_t skipImagePixelsAtLineEndAsBytes = (bmi.bmiHeader.biWidth-(skipImagePixelsAtLineStart+clipWidthOnScreen))*3; // 3 bytes per pixel
+
+    uint16_t skipImageRowsAtImageStart = iy+(clipY1OnScreen-sy);
+    uint16_t skipImageRowsAtImageEnd = biAbsHeight-(skipImageRowsAtImageStart+clipHeightOnScreen);
+
+	/* Vertical loop parameters */
+    int incY, startY, pastEndY;
+    uint32_t skipImageRowsAsBytes = 0;
+    if (bmi.bmiHeader.biHeight > 0 ) {
+        /* Mirror vertically */
+        incY = -1;
+        startY = clipY2OnScreen;
+        pastEndY = clipY1OnScreen-1;
+        skipImageRowsAsBytes = skipImageRowsAtImageEnd*bmi.bmiHeader.biWidth*3; // 3 bytes per pixel
+    }
+    else {
+        /* Do not mirror */
+        incY = 1;
+        startY = clipY1OnScreen ;
+        pastEndY = clipY2OnScreen+1;
+        skipImageRowsAsBytes = skipImageRowsAtImageStart*bmi.bmiHeader.biWidth*3; // 3 bytes per pixel
+    }
+
+    /** Read and copy image data directly to the screen **/
+
+    /* Seek to the beginning of the data */
+	fileSeekAbsolute(bf.bfOffBits);
+
+    /* Seek until the image rect starts */
+    if (skipImageRowsAsBytes>0) fileSeekRelative( skipImageRowsAsBytes );
+
+    /* Copy all bytes to the output bitmap */
+    for ( int y = startY; y != pastEndY; y += incY) {
+
+         /* Seek until the image rect starts */
+        if (skipImagePixelsAtLineStartAsBytes>0) fileSeekRelative( skipImagePixelsAtLineStartAsBytes );
+
+        for ( int x = clipX1OnScreen; x <= clipX2OnScreen; x++) {
+
+            /* Read RGB pixel from the file. For 24 bpp the pixel is stored to 3 bytes*/
+            uint32_t rgb24;
+            if (fileReadBytes((uint8_t*)&rgb24, 3) != 3)
+            {
+                POK_TRACE("Error reading BMP data\n");
+                fileClose();
+                return(-1);
+            }
+
+            /* Copy a pixel from the file directly to the screen */
+//            uint8_t r,g,b;
+//            r = (xrgb >> (3 + 16))  & 0x1f; // 5 bit
+//            g = (xrgb >> (2 + 8))   & 0x3f; // 6 bits
+//            b = (xrgb >> 3)         & 0x1f; // 5 bits
+//            uint16_t targetpixel = (r<<11) | (g<<5) | b;
+
+            uint16_t targetpixel =
+               ((rgb24 >> 8) & 0x0000F800) |  // red (bits 15-11)
+               ((rgb24 >> 5) & 0x000007E0) |  // green (bits 10-5)
+               ((rgb24 >> 3) & 0x0000001F);  // blue (bits 4-0)
+            _game.display.directPixel(x, y, targetpixel);
+        } // end for
+
+        /* Skip pixels at line end */
+        if (skipImagePixelsAtLineEndAsBytes>0) fileSeekRelative( skipImagePixelsAtLineEndAsBytes );
+
+    } // end for
+
+	// Done with the file reading.
+	fileClose();
+
+    return 0;
+}
+
+#endif
+