From 64971549fd63d131f136a16913deaec3bdbcf2f3 Mon Sep 17 00:00:00 2001 From: Andrew Hannam Date: Wed, 3 Apr 2013 13:51:43 +1000 Subject: [PATCH] New GDISP image handling with demo Images currently support Native and BMP (except RLE4,8 and 16 bit - due to bugs) Supports reading from Memory, BaseFileStream or real files (only on the Win32 simulator). Move gdisp_pictures demo to better refect its purpose. Bug fixes for BMP RLE4,8 & 16 bit to come very soon GIF support very soon. --- .../fruits1.h | 0 .../fruits1.jpg | Bin .../gfxconf.h | 86 +- .../main.c | 52 +- .../matterhorn.jpg | Bin .../matterhorn1.h | 0 .../matterhorn2.h | 0 .../matterhorn2.jpg | Bin demos/modules/gdisp/gdisp_images/gfxconf.h | 56 ++ demos/modules/gdisp/gdisp_images/main.c | 52 + .../gdisp/gdisp_images/results_650x493.png | Bin 0 -> 32345 bytes .../modules/gdisp/gdisp_images/test-pal8.bmp | Bin 0 -> 9254 bytes gfxconf.example.h | 28 +- include/gdisp/gdisp.h | 4 + include/gdisp/image.h | 355 +++++++ include/gdisp/options.h | 49 + src/gdisp/gdisp.mk | 11 +- src/gdisp/image.c | 210 ++++ src/gdisp/image_bmp.c | 912 ++++++++++++++++++ src/gdisp/image_gif.c | 47 + src/gdisp/image_jpg.c | 34 + src/gdisp/image_native.c | 157 +++ src/gdisp/image_png.c | 34 + 23 files changed, 2012 insertions(+), 75 deletions(-) rename demos/modules/gdisp/{gdisp_pictures => gdisp_compiled_pictures}/fruits1.h (100%) rename demos/modules/gdisp/{gdisp_pictures => gdisp_compiled_pictures}/fruits1.jpg (100%) rename demos/modules/gdisp/{gdisp_pictures => gdisp_compiled_pictures}/gfxconf.h (96%) rename demos/modules/gdisp/{gdisp_pictures => gdisp_compiled_pictures}/main.c (96%) rename demos/modules/gdisp/{gdisp_pictures => gdisp_compiled_pictures}/matterhorn.jpg (100%) rename demos/modules/gdisp/{gdisp_pictures => gdisp_compiled_pictures}/matterhorn1.h (100%) rename demos/modules/gdisp/{gdisp_pictures => gdisp_compiled_pictures}/matterhorn2.h (100%) rename demos/modules/gdisp/{gdisp_pictures => gdisp_compiled_pictures}/matterhorn2.jpg (100%) create mode 100644 demos/modules/gdisp/gdisp_images/gfxconf.h create mode 100644 demos/modules/gdisp/gdisp_images/main.c create mode 100644 demos/modules/gdisp/gdisp_images/results_650x493.png create mode 100644 demos/modules/gdisp/gdisp_images/test-pal8.bmp create mode 100644 include/gdisp/image.h create mode 100644 src/gdisp/image.c create mode 100644 src/gdisp/image_bmp.c create mode 100644 src/gdisp/image_gif.c create mode 100644 src/gdisp/image_jpg.c create mode 100644 src/gdisp/image_native.c create mode 100644 src/gdisp/image_png.c diff --git a/demos/modules/gdisp/gdisp_pictures/fruits1.h b/demos/modules/gdisp/gdisp_compiled_pictures/fruits1.h similarity index 100% rename from demos/modules/gdisp/gdisp_pictures/fruits1.h rename to demos/modules/gdisp/gdisp_compiled_pictures/fruits1.h diff --git a/demos/modules/gdisp/gdisp_pictures/fruits1.jpg b/demos/modules/gdisp/gdisp_compiled_pictures/fruits1.jpg similarity index 100% rename from demos/modules/gdisp/gdisp_pictures/fruits1.jpg rename to demos/modules/gdisp/gdisp_compiled_pictures/fruits1.jpg diff --git a/demos/modules/gdisp/gdisp_pictures/gfxconf.h b/demos/modules/gdisp/gdisp_compiled_pictures/gfxconf.h similarity index 96% rename from demos/modules/gdisp/gdisp_pictures/gfxconf.h rename to demos/modules/gdisp/gdisp_compiled_pictures/gfxconf.h index b60fd1cb..464c2675 100644 --- a/demos/modules/gdisp/gdisp_pictures/gfxconf.h +++ b/demos/modules/gdisp/gdisp_compiled_pictures/gfxconf.h @@ -1,43 +1,43 @@ -/** - * This file has a different license to the rest of the GFX system. - * You can copy, modify and distribute this file as you see fit. - * You do not need to publish your source modifications to this file. - * The only thing you are not permitted to do is to relicense it - * under a different license. - */ - -#ifndef _GFXCONF_H -#define _GFXCONF_H - -/* GFX sub-systems to turn on */ -#define GFX_USE_GDISP TRUE -#define GFX_USE_GWIN FALSE -#define GFX_USE_GEVENT FALSE -#define GFX_USE_GTIMER FALSE -#define GFX_USE_GINPUT FALSE - -/* Features for the GDISP sub-system. */ -#define GDISP_NEED_VALIDATION TRUE -#define GDISP_NEED_CLIP TRUE -#define GDISP_NEED_TEXT TRUE -#define GDISP_NEED_CIRCLE TRUE -#define GDISP_NEED_ELLIPSE FALSE -#define GDISP_NEED_ARC FALSE -#define GDISP_NEED_SCROLL FALSE -#define GDISP_NEED_PIXELREAD FALSE -#define GDISP_NEED_CONTROL TRUE -#define GDISP_NEED_MULTITHREAD TRUE -#define GDISP_NEED_ASYNC FALSE -#define GDISP_NEED_MSGAPI FALSE - -/* Builtin Fonts */ -#define GDISP_INCLUDE_FONT_SMALL FALSE -#define GDISP_INCLUDE_FONT_LARGER FALSE -#define GDISP_INCLUDE_FONT_UI1 FALSE -#define GDISP_INCLUDE_FONT_UI2 TRUE -#define GDISP_INCLUDE_FONT_LARGENUMBERS TRUE - -/* Features for the GINPUT sub-system. */ -#define GINPUT_NEED_MOUSE FALSE - -#endif /* _GFXCONF_H */ +/** + * This file has a different license to the rest of the GFX system. + * You can copy, modify and distribute this file as you see fit. + * You do not need to publish your source modifications to this file. + * The only thing you are not permitted to do is to relicense it + * under a different license. + */ + +#ifndef _GFXCONF_H +#define _GFXCONF_H + +/* GFX sub-systems to turn on */ +#define GFX_USE_GDISP TRUE +#define GFX_USE_GWIN FALSE +#define GFX_USE_GEVENT FALSE +#define GFX_USE_GTIMER FALSE +#define GFX_USE_GINPUT FALSE + +/* Features for the GDISP sub-system. */ +#define GDISP_NEED_VALIDATION TRUE +#define GDISP_NEED_CLIP TRUE +#define GDISP_NEED_TEXT TRUE +#define GDISP_NEED_CIRCLE TRUE +#define GDISP_NEED_ELLIPSE FALSE +#define GDISP_NEED_ARC FALSE +#define GDISP_NEED_SCROLL FALSE +#define GDISP_NEED_PIXELREAD FALSE +#define GDISP_NEED_CONTROL TRUE +#define GDISP_NEED_MULTITHREAD TRUE +#define GDISP_NEED_ASYNC FALSE +#define GDISP_NEED_MSGAPI FALSE + +/* Builtin Fonts */ +#define GDISP_INCLUDE_FONT_SMALL FALSE +#define GDISP_INCLUDE_FONT_LARGER FALSE +#define GDISP_INCLUDE_FONT_UI1 FALSE +#define GDISP_INCLUDE_FONT_UI2 TRUE +#define GDISP_INCLUDE_FONT_LARGENUMBERS TRUE + +/* Features for the GINPUT sub-system. */ +#define GINPUT_NEED_MOUSE FALSE + +#endif /* _GFXCONF_H */ diff --git a/demos/modules/gdisp/gdisp_pictures/main.c b/demos/modules/gdisp/gdisp_compiled_pictures/main.c similarity index 96% rename from demos/modules/gdisp/gdisp_pictures/main.c rename to demos/modules/gdisp/gdisp_compiled_pictures/main.c index 9eae3319..b437619e 100644 --- a/demos/modules/gdisp/gdisp_pictures/main.c +++ b/demos/modules/gdisp/gdisp_compiled_pictures/main.c @@ -1,26 +1,26 @@ -#include "ch.h" -#include "hal.h" -#include "gfx.h" -#include "matterhorn1.h" -#include "matterhorn2.h" -#include "fruits1.h" - -int main(void) { - halInit(); - chSysInit(); - - gdispInit(); - gdispSetOrientation(GDISP_ROTATE_90); - - while(1) { - gdispBlitArea(0, 0, fruits1.width, fruits1.height, (const pixel_t*)fruits1.pixel_data); - chThdSleepMilliseconds(3000); - - gdispBlitArea(0, 0, matterhorn1.width, matterhorn1.height, (const pixel_t*)matterhorn1.pixel_data); - chThdSleepMilliseconds(3000); - - gdispBlitArea(0, 0, matterhorn2.width, matterhorn2.height, (const pixel_t*)matterhorn2.pixel_data); - chThdSleepMilliseconds(3000); - } -} - +#include "ch.h" +#include "hal.h" +#include "gfx.h" +#include "matterhorn1.h" +#include "matterhorn2.h" +#include "fruits1.h" + +int main(void) { + halInit(); + chSysInit(); + + gdispInit(); + gdispSetOrientation(GDISP_ROTATE_90); + + while(1) { + gdispBlitArea(0, 0, fruits1.width, fruits1.height, (const pixel_t*)fruits1.pixel_data); + chThdSleepMilliseconds(3000); + + gdispBlitArea(0, 0, matterhorn1.width, matterhorn1.height, (const pixel_t*)matterhorn1.pixel_data); + chThdSleepMilliseconds(3000); + + gdispBlitArea(0, 0, matterhorn2.width, matterhorn2.height, (const pixel_t*)matterhorn2.pixel_data); + chThdSleepMilliseconds(3000); + } +} + diff --git a/demos/modules/gdisp/gdisp_pictures/matterhorn.jpg b/demos/modules/gdisp/gdisp_compiled_pictures/matterhorn.jpg similarity index 100% rename from demos/modules/gdisp/gdisp_pictures/matterhorn.jpg rename to demos/modules/gdisp/gdisp_compiled_pictures/matterhorn.jpg diff --git a/demos/modules/gdisp/gdisp_pictures/matterhorn1.h b/demos/modules/gdisp/gdisp_compiled_pictures/matterhorn1.h similarity index 100% rename from demos/modules/gdisp/gdisp_pictures/matterhorn1.h rename to demos/modules/gdisp/gdisp_compiled_pictures/matterhorn1.h diff --git a/demos/modules/gdisp/gdisp_pictures/matterhorn2.h b/demos/modules/gdisp/gdisp_compiled_pictures/matterhorn2.h similarity index 100% rename from demos/modules/gdisp/gdisp_pictures/matterhorn2.h rename to demos/modules/gdisp/gdisp_compiled_pictures/matterhorn2.h diff --git a/demos/modules/gdisp/gdisp_pictures/matterhorn2.jpg b/demos/modules/gdisp/gdisp_compiled_pictures/matterhorn2.jpg similarity index 100% rename from demos/modules/gdisp/gdisp_pictures/matterhorn2.jpg rename to demos/modules/gdisp/gdisp_compiled_pictures/matterhorn2.jpg diff --git a/demos/modules/gdisp/gdisp_images/gfxconf.h b/demos/modules/gdisp/gdisp_images/gfxconf.h new file mode 100644 index 00000000..77e70f36 --- /dev/null +++ b/demos/modules/gdisp/gdisp_images/gfxconf.h @@ -0,0 +1,56 @@ +/** + * This file has a different license to the rest of the GFX system. + * You can copy, modify and distribute this file as you see fit. + * You do not need to publish your source modifications to this file. + * The only thing you are not permitted to do is to relicense it + * under a different license. + */ + +#ifndef _GFXCONF_H +#define _GFXCONF_H + +/* GFX sub-systems to turn on */ +#define GFX_USE_GDISP TRUE +#define GFX_USE_GWIN FALSE +#define GFX_USE_GEVENT FALSE +#define GFX_USE_GTIMER FALSE +#define GFX_USE_GINPUT FALSE + +/* Features for the GDISP sub-system. */ +#define GDISP_NEED_VALIDATION TRUE +#define GDISP_NEED_CLIP TRUE +#define GDISP_NEED_TEXT FALSE +#define GDISP_NEED_CIRCLE FALSE +#define GDISP_NEED_ELLIPSE FALSE +#define GDISP_NEED_ARC FALSE +#define GDISP_NEED_CONVEX_POLYGON FALSE +#define GDISP_NEED_SCROLL FALSE +#define GDISP_NEED_PIXELREAD FALSE +#define GDISP_NEED_CONTROL FALSE +#define GDISP_NEED_IMAGE TRUE +#define GDISP_NEED_MULTITHREAD FALSE +#define GDISP_NEED_ASYNC FALSE +#define GDISP_NEED_MSGAPI FALSE + +/* Builtin Fonts */ +#define GDISP_INCLUDE_FONT_SMALL FALSE +#define GDISP_INCLUDE_FONT_LARGER FALSE +#define GDISP_INCLUDE_FONT_UI1 FALSE +#define GDISP_INCLUDE_FONT_UI2 FALSE +#define GDISP_INCLUDE_FONT_LARGENUMBERS FALSE + +/* GDISP image decoders */ +#define GDISP_NEED_IMAGE_NATIVE FALSE +#define GDISP_NEED_IMAGE_GIF FALSE +#define GDISP_NEED_IMAGE_BMP TRUE +#define GDISP_NEED_IMAGE_JPG FALSE +#define GDISP_NEED_IMAGE_PNG FALSE + +/* Features for the GWIN sub-system. */ +#define GWIN_NEED_BUTTON FALSE +#define GWIN_NEED_CONSOLE FALSE + +/* Features for the GINPUT sub-system. */ +#define GINPUT_NEED_MOUSE FALSE + +#endif /* _GFXCONF_H */ diff --git a/demos/modules/gdisp/gdisp_images/main.c b/demos/modules/gdisp/gdisp_images/main.c new file mode 100644 index 00000000..6ca5f640 --- /dev/null +++ b/demos/modules/gdisp/gdisp_images/main.c @@ -0,0 +1,52 @@ +/* + ChibiOS/GFX - Copyright (C) 2012, 2013 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + ChibiOS/GFX is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "ch.h" +#include "hal.h" +#include "gfx.h" + +static gdispImage myImage; + +int main(void) { + coord_t swidth, sheight; + + halInit(); // Initialise the Hardware + chSysInit(); // Initialize the OS + gdispInit(); // Initialize the display + + gdispClear(Black); + + // Get the display dimensions + swidth = gdispGetWidth(); + sheight = gdispGetHeight(); + + // Set up IO for our image + gdispImageSetSimulFileReader(&myImage, "test-pal8.bmp"); + + gdispImageOpen(&myImage); + gdispImageDraw(&myImage, 0, 0, swidth, sheight, 0, 0); + gdispImageClose(&myImage); + + while(1) { + chThdSleepMilliseconds(1000); + } + + return 0; +} diff --git a/demos/modules/gdisp/gdisp_images/results_650x493.png b/demos/modules/gdisp/gdisp_images/results_650x493.png new file mode 100644 index 0000000000000000000000000000000000000000..66dcd6e30fef92f1bbe834dd9e6db034db6567d3 GIT binary patch literal 32345 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 LsmT_=?)(1_N!uob literal 0 HcmV?d00001 diff --git a/demos/modules/gdisp/gdisp_images/test-pal8.bmp b/demos/modules/gdisp/gdisp_images/test-pal8.bmp new file mode 100644 index 0000000000000000000000000000000000000000..96b2f86680b70ca101fa80b3f305f70d0d930005 GIT binary patch literal 9254 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 V`32>dRe1m;d%gHxfN|ck{sX?6Ab0=( literal 0 HcmV?d00001 diff --git a/gfxconf.example.h b/gfxconf.example.h index 9db7d1b5..c9a91f2d 100644 --- a/gfxconf.example.h +++ b/gfxconf.example.h @@ -38,16 +38,36 @@ #define GDISP_NEED_PIXELREAD FALSE #define GDISP_NEED_CONTROL FALSE #define GDISP_NEED_QUERY FALSE +#define GDISP_NEED_IMAGE FALSE #define GDISP_NEED_MULTITHREAD FALSE #define GDISP_NEED_ASYNC FALSE #define GDISP_NEED_MSGAPI FALSE /* GDISP - builtin fonts */ -#define GDISP_INCLUDE_FONT_SMALL TRUE -#define GDISP_INCLUDE_FONT_LARGER TRUE -#define GDISP_INCLUDE_FONT_UI1 TRUE +#define GDISP_INCLUDE_FONT_SMALL FALSE +#define GDISP_INCLUDE_FONT_LARGER FALSE +#define GDISP_INCLUDE_FONT_UI1 FALSE #define GDISP_INCLUDE_FONT_UI2 TRUE -#define GDISP_INCLUDE_FONT_LARGENUMBERS TRUE +#define GDISP_INCLUDE_FONT_LARGENUMBERS FALSE + +/* GDISP image decoders */ +#define GDISP_NEED_IMAGE_NATIVE FALSE +#define GDISP_NEED_IMAGE_GIF FALSE +#define GDISP_NEED_IMAGE_BMP FALSE +#define GDISP_NEED_IMAGE_JPG FALSE +#define GDISP_NEED_IMAGE_PNG FALSE + +/* Optional image support that can be turned off */ +/* + #define GDISP_NEED_IMAGE_BMP_1 TRUE + #define GDISP_NEED_IMAGE_BMP_4 TRUE + #define GDISP_NEED_IMAGE_BMP_4_RLE TRUE + #define GDISP_NEED_IMAGE_BMP_8 TRUE + #define GDISP_NEED_IMAGE_BMP_8_RLE TRUE + #define GDISP_NEED_IMAGE_BMP_16 TRUE + #define GDISP_NEED_IMAGE_BMP_24 TRUE + #define GDISP_NEED_IMAGE_BMP_32 TRUE +*/ /* Features for the TDISP subsystem. */ #define TDISP_NEED_MULTITHREAD FALSE diff --git a/include/gdisp/gdisp.h b/include/gdisp/gdisp.h index 9e408578..85f9b4ea 100644 --- a/include/gdisp/gdisp.h +++ b/include/gdisp/gdisp.h @@ -966,6 +966,10 @@ void gdispDrawBox(coord_t x, coord_t y, coord_t cx, coord_t cy, color_t color); } #endif +#if GDISP_NEED_IMAGE || defined(__DOXYGEN__) + #include "gdisp/image.h" +#endif + #endif /* GFX_USE_GDISP */ #endif /* _GDISP_H */ diff --git a/include/gdisp/image.h b/include/gdisp/image.h new file mode 100644 index 00000000..8e71437b --- /dev/null +++ b/include/gdisp/image.h @@ -0,0 +1,355 @@ +/* + ChibiOS/GFX - Copyright (C) 2012, 2013 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + ChibiOS/GFX is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/** + * @file include/gdisp/image.h + * @brief GDISP image header file. + * + * @addtogroup GDISP + * @{ + */ + +#ifndef _GDISP_IMAGE_H +#define _GDISP_IMAGE_H +#if (GFX_USE_GDISP && GDISP_NEED_IMAGE) || defined(__DOXYGEN__) + +/** + * @brief The type of image + */ +typedef uint16_t gdispImageType; + #define GDISP_IMAGE_TYPE_UNKNOWN 0 + #define GDISP_IMAGE_TYPE_NATIVE 1 + #define GDISP_IMAGE_TYPE_GIF 2 + #define GDISP_IMAGE_TYPE_BMP 3 + #define GDISP_IMAGE_TYPE_JPG 4 + #define GDISP_IMAGE_TYPE_PNG 5 + +/** + * @brief An image error code + */ +typedef uint16_t gdispImageError; + #define GDISP_IMAGE_ERR_OK 0 + #define GDISP_IMAGE_ERR_UNRECOVERABLE 0x8000 + #define GDISP_IMAGE_ERR_BADFORMAT (GDISP_IMAGE_ERR_UNRECOVERABLE+1) + #define GDISP_IMAGE_ERR_BADDATA (GDISP_IMAGE_ERR_UNRECOVERABLE+2) + #define GDISP_IMAGE_ERR_UNSUPPORTED (GDISP_IMAGE_ERR_UNRECOVERABLE+3) + #define GDISP_IMAGE_ERR_UNSUPPORTED_OK 3 + #define GDISP_IMAGE_ERR_NOMEMORY (GDISP_IMAGE_ERR_UNRECOVERABLE+4) + +/** + * @brief Image flags + */ +typedef uint16_t gdispImageFlags; + #define GDISP_IMAGE_FLG_TRANSPARENT 0x0001 /* The image has transparency */ + #define GDISP_IMAGE_FLG_ANIMATED 0x0002 /* The image has animation */ + #define GDISP_IMAGE_FLG_MULTIPAGE 0x0004 /* The image has multiple pages */ + +struct gdispImageIO; + +/** + * @brief An image IO close function + * + * @param[in] pio Pointer to the io structure + * @param[in] desc The descriptor. A filename or an image structure pointer. + * + */ +typedef void (*gdispImageIOCloseFn)(struct gdispImageIO *pio); + +/** + * @brief An image IO read function + * @returns The number of bytes actually read or 0 on error + * + * @param[in] pio Pointer to the io structure + * @param[in] buf Where the results should be placed + * @param[in] len The number of bytes to read + * + */ +typedef size_t (*gdispImageIOReadFn)(struct gdispImageIO *pio, void *buf, size_t len); + +/** + * @brief An image IO seek function + * + * @param[in] pio Pointer to the io structure + * @param[in] pos Which byte to seek to relative to the start of the "file". + * + */ +typedef void (*gdispImageIOSeekFn)(struct gdispImageIO *pio, size_t pos); + +typedef struct gdispImageIOFunctions { + gdispImageIOReadFn read; /* @< The function to read input */ + gdispImageIOSeekFn seek; /* @< The function to seek input */ + gdispImageIOCloseFn close; /* @< The function to close input */ + } gdispImageIOFunctions; + +/** + * @brief The structure defining the IO routines for image handling + */ +typedef struct gdispImageIO { + const void * fd; /* @< The "file" descriptor */ + size_t pos; /* @< The current "file" position */ + const gdispImageIOFunctions *fns; /* @< The current "file" functions */ +} gdispImageIO; + +/** + * @brief The structure for an image + */ +typedef struct gdispImage { + gdispImageType type; /* @< The image type */ + gdispImageFlags flags; /* @< The image flags */ + coord_t width, height; /* @< The image dimensions */ + gdispImageIO io; /* @< The image IO functions */ + uint32_t membytes; /* @< How much RAM has been allocated */ + const struct gdispImageHandlers * fns; /* @< Don't mess with this! */ + struct gdispImagePrivate * priv; /* @< Don't mess with this! */ +} gdispImage; + +#ifdef __cplusplus +extern "C" { +#endif + + /** + * @brief Sets the io fields in the image structure to routines + * that support reading from an image stored in RAM or Flash. + * + * @return TRUE if the IO open function succeeds + * + * @param[in] img The image structure + * @param[in] memimage A pointer to the image in RAM or Flash + * + * @note Always returns TRUE for a Memory Reader + */ + bool_t gdispImageSetMemoryReader(gdispImage *img, const char *memimage); + + /** + * @brief Sets the io fields in the image structure to routines + * that support reading from an image stored on a BaseFileStream (eg SDCard). + * + * @return TRUE if the IO open function succeeds + * + * @param[in] img The image structure + * @param[in] BaseFileStreamPtr A pointer to the (open) BaseFileStream object. + * + */ + bool_t gdispImageSetBaseFileStreamReader(gdispImage *img, void *BaseFileStreamPtr); + + #if defined(WIN32) || defined(__DOXYGEN__) + /** + * @brief Sets the io fields in the image structure to routines + * that support reading from an image stored in Win32 simulators native + * file system. + * @pre Only available on the Win32 simulator + * + * @return TRUE if the IO open function succeeds + * + * @param[in] img The image structure + * @param[in] filename The filename to open + * + */ + bool_t gdispImageSetSimulFileReader(gdispImage *img, const char *filename); + #endif + + /** + * @brief Open an image ready for drawing + * @details Determine the image format and get ready to decode the first image frame + * @return GDISP_IMAGE_ERR_OK (0) on success or an error code. + * + * @param[in] img The image structure + * + * @pre The io fields should be filled in before calling gdispImageOpen() + * + * @note This determines which decoder to use and then initialises all other fields + * in the gdispImage structure. + * @note There are three types of return - everything OK, partial success and unrecoverable + * failures. For everything OK it returns GDISP_IMAGE_ERR_OK. A partial success can + * be distinguished from a unrecoverable failure by testing the GDISP_IMAGE_ERR_UNRECOVERABLE + * bit in the error code. + * A partial success return code means an image can still be drawn but perhaps with + * reduced functionality eg only the first page of a multi-page image. + * @note @p gdispImageClose() can be called even after a failure to open the image to ensure + * that the IO close routine gets called. + */ + gdispImageError gdispImageOpen(gdispImage *img); + + /** + * @brief Close an image and release any dynamicly allocated working storage. + * + * @param[in] img The image structure + * + * @pre gdispImageOpen() must have returned successfully. + * + * @note Also calls the IO close function (if it hasn't already been called). + */ + void gdispImageClose(gdispImage *img); + + /** + * @brief Cache the image + * @details Decodes and caches the current frame into RAM. + * @return GDISP_IMAGE_ERR_OK (0) on success or an error code. + * + * @param[in] img The image structure + * + * @pre gdispImageOpen() must have returned successfully. + * + * @note This can use a LOT of RAM! + * @note The decoder may choose to ignore the request for caching. If it does so it will + * return GDISP_IMAGE_ERR_UNSUPPORTED_OK. + * @note A fatal error here does not necessarily mean that drawing the image will fail. For + * example, a GDISP_IMAGE_ERR_NOMEMORY error simply means there isn't enough RAM to + * cache the image. + */ + gdispImageError gdispImageCache(gdispImage *img); + + /** + * @brief Draw the image + * @return GDISP_IMAGE_ERR_OK (0) on success or an error code. + * + * @param[in] img The image structure + * @param[in] x,y The screen location to draw the image + * @param[in] cx,cy The area on the screen to draw + * @param[in] sx,sy The image position to start drawing at + * + * @pre gdispImageOpen() must have returned successfully. + * + * @note If sx,sy + cx,cy is outside the image boundaries the area outside the image + * is simply not drawn. + * @note If @p gdispImageCache() has been called first for this frame, this routine will draw using a + * fast blit from the cached frame. If not, it reads the input and decodes it as it + * is drawing. This may be significantly slower than if the image has been cached (but + * uses a lot less RAM) + */ + gdispImageError gdispImageDraw(gdispImage *img, coord_t x, coord_t y, coord_t cx, coord_t cy, coord_t sx, coord_t sy); + + /** + * @brief Prepare for the next frame/page in the image file. + * @return A time in milliseconds to keep displaying the current frame before trying to draw + * the next frame. Watch out for the special values TIME_IMMEDIATE and TIME_INFINITE. + * + * @param[in] img The image structure + * + * @pre gdispImageOpen() must have returned successfully. + * + * @note It will return TIME_IMMEDIATE if the first frame/page hasn't been drawn or if the next frame + * should be drawn immediately. + * @note It will return TIME_INFINITE if another image frame doesn't exist or an error has occurred. + * @note Images that support multiple pages (eg TIFF files) will return TIME_IMMEDIATE between pages + * and then TIME_INFINITE when there are no more pages. + * @note An image that displays a looped animation will never return TIME_INFINITE unless it + * gets an error. + * @note Calling gdispImageDraw() after getting a TIME_INFINITE will go back to drawing the first + * frame/page. + */ + systime_t gdispImageNext(gdispImage *img); + + #if GDISP_NEED_IMAGE_NATIVE + /** + * @brief The image drawing routines for a NATIVE format image. + * + * @note Only use these functions if you absolutely know the format + * of the image you are decoding. Generally you should use the + * generic functions and it will auto-detect the format. + * @note A NATIVE format image is defined as an 8 byte header described below, immediately + * followed by the bitmap data. The bitmap data is stored in the native format for + * the display controller. If the pixel format specified in the header does not + * match the controller native format then the image is rejected. + * @note The 8 byte header: + * { 'N', 'I', width.hi, width.lo, height.hi, height.lo, format.hi, format.lo } + * The format word = GDISP_PIXELFORMAT + * @{ + */ + gdispImageError gdispImageOpen_NATIVE(gdispImage *img); + void gdispImageClose_NATIVE(gdispImage *img); + gdispImageError gdispImageCache_NATIVE(gdispImage *img); + gdispImageError gdispImageDraw_NATIVE(gdispImage *img, coord_t x, coord_t y, coord_t cx, coord_t cy, coord_t sx, coord_t sy); + systime_t gdispImageNext_NATIVE(gdispImage *img); + /* @} */ + #endif + + #if GDISP_NEED_IMAGE_GIF + /** + * @brief The image drawing routines for a GIF image. + * @note Only use these functions if you absolutely know the format + * of the image you are decoding. Generally you should use the + * generic functions and it will auto-detect the format. + * @{ + */ + gdispImageError gdispImageOpen_GIF(gdispImage *img); + void gdispImageClose_GIF(gdispImage *img); + gdispImageError gdispImageCache_GIF(gdispImage *img); + gdispImageError gdispImageDraw_GIF(gdispImage *img, coord_t x, coord_t y, coord_t cx, coord_t cy, coord_t sx, coord_t sy); + systime_t gdispImageNext_GIF(gdispImage *img); + /* @} */ + #endif + + #if GDISP_NEED_IMAGE_BMP + /** + * @brief The image drawing routines for a BMP image. + * @note Only use these functions if you absolutely know the format + * of the image you are decoding. Generally you should use the + * generic functions and it will auto-detect the format. + * @{ + */ + gdispImageError gdispImageOpen_BMP(gdispImage *img); + void gdispImageClose_BMP(gdispImage *img); + gdispImageError gdispImageCache_BMP(gdispImage *img); + gdispImageError gdispImageDraw_BMP(gdispImage *img, coord_t x, coord_t y, coord_t cx, coord_t cy, coord_t sx, coord_t sy); + systime_t gdispImageNext_BMP(gdispImage *img); + /* @} */ + #endif + + #if GDISP_NEED_IMAGE_JPG + /** + * @brief The image drawing routines for a JPG image. + * @note Only use these functions if you absolutely know the format + * of the image you are decoding. Generally you should use the + * generic functions and it will auto-detect the format. + * @{ + */ + gdispImageError gdispImageOpen_JPG(gdispImage *img); + void gdispImageClose_JPG(gdispImage *img); + gdispImageError gdispImageCache_JPG(gdispImage *img); + gdispImageError gdispImageDraw_JPG(gdispImage *img, coord_t x, coord_t y, coord_t cx, coord_t cy, coord_t sx, coord_t sy); + systime_t gdispImageNext_JPG(gdispImage *img); + /* @} */ + #endif + + #if GDISP_NEED_IMAGE_PNG + /** + * @brief The image drawing routines for a PNG image. + * @note Only use these functions if you absolutely know the format + * of the image you are decoding. Generally you should use the + * generic functions and it will auto-detect the format. + * @{ + */ + gdispImageError gdispImageOpen_PNG(gdispImage *img); + void gdispImageClose_PNG(gdispImage *img); + gdispImageError gdispImageCache_PNG(gdispImage *img); + gdispImageError gdispImageDraw_PNG(gdispImage *img, coord_t x, coord_t y, coord_t cx, coord_t cy, coord_t sx, coord_t sy); + systime_t gdispImageNext_PNG(gdispImage *img); + /* @} */ + #endif + +#ifdef __cplusplus +} +#endif + +#endif /* GFX_USE_GDISP && GDISP_NEED_IMAGE */ +#endif /* _GDISP_IMAGE_H */ +/** @} */ + diff --git a/include/gdisp/options.h b/include/gdisp/options.h index 78e48a0a..deacc036 100644 --- a/include/gdisp/options.h +++ b/include/gdisp/options.h @@ -130,6 +130,13 @@ #ifndef GDISP_NEED_QUERY #define GDISP_NEED_QUERY FALSE #endif + /** + * @brief Is the image interface required. + * @details Defaults to FALSE + */ + #ifndef GDISP_NEED_IMAGE + #define GDISP_NEED_IMAGE FALSE + #endif /** * @brief Is the messaging api interface required. * @details Defaults to FALSE @@ -137,6 +144,48 @@ #ifndef GDISP_NEED_MSGAPI #define GDISP_NEED_MSGAPI FALSE #endif +/** + * @} + * + * @name GDISP Image Options + * @pre GDISP_NEED_IMAGE must be TRUE + * @{ + */ + /** + * @brief Is native image decoding required. + * @details Defaults to FALSE + */ + #ifndef GDISP_NEED_IMAGE_NATIVE + #define GDISP_NEED_IMAGE_NATIVE FALSE + #endif + /** + * @brief Is GIF image decoding required. + * @details Defaults to FALSE + */ + #ifndef GDISP_NEED_IMAGE_GIF + #define GDISP_NEED_IMAGE_GIF FALSE + #endif + /** + * @brief Is BMP image decoding required. + * @details Defaults to FALSE + */ + #ifndef GDISP_NEED_IMAGE_BMP + #define GDISP_NEED_IMAGE_BMP FALSE + #endif + /** + * @brief Is JPG image decoding required. + * @details Defaults to FALSE + */ + #ifndef GDISP_NEED_IMAGE_JPG + #define GDISP_NEED_IMAGE_JPG FALSE + #endif + /** + * @brief Is PNG image decoding required. + * @details Defaults to FALSE + */ + #ifndef GDISP_NEED_IMAGE_PNG + #define GDISP_NEED_IMAGE_PNG FALSE + #endif /** * @} * diff --git a/src/gdisp/gdisp.mk b/src/gdisp/gdisp.mk index 426e9e2c..84570e9e 100644 --- a/src/gdisp/gdisp.mk +++ b/src/gdisp/gdisp.mk @@ -1,2 +1,9 @@ -GFXSRC += $(GFXLIB)/src/gdisp/gdisp.c \ - $(GFXLIB)/src/gdisp/fonts.c +GFXSRC += $(GFXLIB)/src/gdisp/gdisp.c \ + $(GFXLIB)/src/gdisp/fonts.c \ + $(GFXLIB)/src/gdisp/image.c \ + $(GFXLIB)/src/gdisp/image_native.c \ + $(GFXLIB)/src/gdisp/image_gif.c \ + $(GFXLIB)/src/gdisp/image_bmp.c \ + $(GFXLIB)/src/gdisp/image_jpg.c \ + $(GFXLIB)/src/gdisp/image_png.c + \ No newline at end of file diff --git a/src/gdisp/image.c b/src/gdisp/image.c new file mode 100644 index 00000000..616aab11 --- /dev/null +++ b/src/gdisp/image.c @@ -0,0 +1,210 @@ +/* + ChibiOS/GFX - Copyright (C) 2012, 2013 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + ChibiOS/GFX is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/** + * @file src/gdisp/image.c + * @brief GDISP generic image code. + */ +#include "ch.h" +#include "hal.h" +#include "gfx.h" + +#if GFX_USE_GDISP && GDISP_NEED_IMAGE + +/* The structure defining the routines for image drawing */ +typedef struct gdispImageHandlers { + gdispImageError (*open)(gdispImage *img); /* The open function */ + void (*close)(gdispImage *img); /* The close function */ + gdispImageError (*cache)(gdispImage *img); /* The cache function */ + gdispImageError (*draw)(gdispImage *img, + coord_t x, coord_t y, + coord_t cx, coord_t cy, + coord_t sx, coord_t sy); /* The draw function */ + systime_t (*next)(gdispImage *img); /* The next frame function */ +} gdispImageHandlers; + +static gdispImageHandlers ImageHandlers[] = { + #if GDISP_NEED_IMAGE_NATIVE + { gdispImageOpen_NATIVE, gdispImageClose_NATIVE, + gdispImageCache_NATIVE, gdispImageDraw_NATIVE, gdispImageNext_NATIVE, + }, + #endif + #if GDISP_NEED_IMAGE_GIF + { gdispImageOpen_GIF, gdispImageClose_GIF, + gdispImageCache_GIF, gdispImageDraw_GIF, gdispImageNext_GIF, + }, + #endif + #if GDISP_NEED_IMAGE_BMP + { gdispImageOpen_BMP, gdispImageClose_BMP, + gdispImageCache_BMP, gdispImageDraw_BMP, gdispImageNext_BMP, + }, + #endif + #if GDISP_NEED_IMAGE_JPG + { gdispImageOpen_JPG, gdispImageClose_JPG, + gdispImageCache_JPG, gdispImageDraw_JPG, gdispImageNext_JPG, + }, + #endif + #if GDISP_NEED_IMAGE_PNG + { gdispImageOpen_PNG, gdispImageClose_PNG, + gdispImageCache_PNG, gdispImageDraw_PNG, gdispImageNext_PNG, + }, + #endif +}; + +static size_t ImageMemoryRead(struct gdispImageIO *pio, void *buf, size_t len) { + if (pio->fd == (void *)-1) return 0; + memcpy(buf, ((const char *)pio->fd)+pio->pos, len); + pio->pos += len; + return len; +} + +static void ImageMemorySeek(struct gdispImageIO *pio, size_t pos) { + if (pio->fd == (void *)-1) return; + pio->pos = pos; +} + +static void ImageMemoryClose(struct gdispImageIO *pio) { + pio->fd = (void *)-1; + pio->pos = 0; +} + +static const gdispImageIOFunctions ImageMemoryFunctions = + { ImageMemoryRead, ImageMemorySeek, ImageMemoryClose }; + +bool_t gdispImageSetMemoryReader(gdispImage *img, const char *memimage) { + img->io.fns = &ImageMemoryFunctions; + img->io.pos = 0; + img->io.fd = (void *)memimage; + return TRUE; +} + +static size_t ImageBaseFileStreamRead(struct gdispImageIO *pio, void *buf, size_t len) { + if (pio->fd == (void *)-1) return 0; + len = chSequentialStreamRead(((BaseFileStream *)pio->fd), (uint8_t *)buf, len); + pio->pos += len; + return len; +} + +static void ImageBaseFileStreamSeek(struct gdispImageIO *pio, size_t pos) { + if (pio->fd == (void *)-1) return; + if (pio->pos != pos) { + chFileStreamSeek(((BaseFileStream *)pio->fd), pos); + pio->pos = pos; + } +} + +static void ImageBaseFileStreamClose(struct gdispImageIO *pio) { + if (pio->fd == (void *)-1) return; + chFileStreamClose(((BaseFileStream *)pio->fd)); + pio->fd = (void *)-1; + pio->pos = 0; +} + +static const gdispImageIOFunctions ImageBaseFileStreamFunctions = + { ImageBaseFileStreamRead, ImageBaseFileStreamSeek, ImageBaseFileStreamClose }; + +bool_t gdispImageSetBaseFileStreamReader(gdispImage *img, void *BaseFileStreamPtr) { + img->io.fns = &ImageBaseFileStreamFunctions; + img->io.pos = 0; + img->io.fd = BaseFileStreamPtr; + return TRUE; +} + +#if defined(WIN32) + #include + + static size_t ImageSimulFileRead(struct gdispImageIO *pio, void *buf, size_t len) { + if (pio->fd == (void *)-1) return 0; + len = read((int)pio->fd, buf, len); + if ((int)len < 0) len = 0; + pio->pos += len; + return len; + } + + static void ImageSimulFileSeek(struct gdispImageIO *pio, size_t pos) { + if (pio->fd == (void *)-1) return; + if (pio->pos != pos) { + lseek((int)pio->fd, pos, SEEK_SET); + pio->pos = pos; + } + } + + static void ImageSimulFileClose(struct gdispImageIO *pio) { + if (pio->fd == (void *)-1) return; + close((int)pio->fd); + pio->fd = (void *)-1; + pio->pos = 0; + } + + static const gdispImageIOFunctions ImageSimulFileFunctions = + { ImageSimulFileRead, ImageSimulFileSeek, ImageSimulFileClose }; + + bool_t gdispImageSetSimulFileReader(gdispImage *img, const char *filename) { + img->io.fns = &ImageSimulFileFunctions; + img->io.pos = 0; + img->io.fd = (void *)open(filename, O_RDONLY|O_BINARY); + return img->io.fd != (void *)-1; + } +#endif + +gdispImageError gdispImageOpen(gdispImage *img) { + gdispImageError err; + + for(img->fns = ImageHandlers; img->fns < ImageHandlers+sizeof(ImageHandlers)/sizeof(ImageHandlers[0]); img->fns++) { + err = img->fns->open(img); + if (err != GDISP_IMAGE_ERR_BADFORMAT) { + if ((err & GDISP_IMAGE_ERR_UNRECOVERABLE)) + img->fns = 0; + return err; + } + img->io.fns->seek(&img->io, 0); + } + img->type = GDISP_IMAGE_TYPE_UNKNOWN; + img->flags = 0; + img->fns = 0; + img->priv = 0; + return GDISP_IMAGE_ERR_BADFORMAT; +} + +void gdispImageClose(gdispImage *img) { + if (img->fns) + img->fns->close(img); + else + img->io.fns->close(&img->io); +} + +gdispImageError gdispImageCache(gdispImage *img) { + if (!img->fns) return GDISP_IMAGE_ERR_BADFORMAT; + return img->fns->cache(img); +} + +gdispImageError gdispImageDraw(gdispImage *img, coord_t x, coord_t y, coord_t cx, coord_t cy, coord_t sx, coord_t sy) { + if (!img->fns) return GDISP_IMAGE_ERR_BADFORMAT; + return img->fns->draw(img, x, y, cx, cy, sx, sy); +} + +systime_t gdispImageNext(gdispImage *img) { + if (!img->fns) return GDISP_IMAGE_ERR_BADFORMAT; + return img->fns->next(img); +} + + +#endif /* GFX_USE_GDISP && GDISP_NEED_IMAGE */ +/** @} */ diff --git a/src/gdisp/image_bmp.c b/src/gdisp/image_bmp.c new file mode 100644 index 00000000..f286003e --- /dev/null +++ b/src/gdisp/image_bmp.c @@ -0,0 +1,912 @@ +/* + ChibiOS/GFX - Copyright (C) 2012, 2013 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + ChibiOS/GFX is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/** + * @file src/gdisp/image_bmp.c + * @brief GDISP native image code. + */ +#include "ch.h" +#include "hal.h" +#include "gfx.h" + +#if GFX_USE_GDISP && GDISP_NEED_IMAGE && GDISP_NEED_IMAGE_BMP + +#ifndef GDISP_NEED_IMAGE_BMP_1 + #define GDISP_NEED_IMAGE_BMP_1 TRUE +#endif +#ifndef GDISP_NEED_IMAGE_BMP_4 + #define GDISP_NEED_IMAGE_BMP_4 TRUE +#endif +#ifndef GDISP_NEED_IMAGE_BMP_4_RLE + #define GDISP_NEED_IMAGE_BMP_4_RLE FALSE // Currently Broken +#endif +#ifndef GDISP_NEED_IMAGE_BMP_8 + #define GDISP_NEED_IMAGE_BMP_8 TRUE +#endif +#ifndef GDISP_NEED_IMAGE_BMP_8_RLE + #define GDISP_NEED_IMAGE_BMP_8_RLE FALSE // Currently Broken +#endif +#ifndef GDISP_NEED_IMAGE_BMP_16 + #define GDISP_NEED_IMAGE_BMP_16 FALSE // Currently Broken +#endif +#ifndef GDISP_NEED_IMAGE_BMP_24 + #define GDISP_NEED_IMAGE_BMP_24 TRUE +#endif +#ifndef GDISP_NEED_IMAGE_BMP_32 + #define GDISP_NEED_IMAGE_BMP_32 TRUE +#endif + +/** + * How big a pixel array to allocate for blitting (in pixels) + * Bigger is faster but uses more RAM. + * This must be greater than 40 bytes and 32 pixels as we read our headers into this space as well + */ +#define BLIT_BUFFER_SIZE 32 + +/* + * Determining endianness as at compile time is not guaranteed or compiler portable. + * We use the best test we can. If we can't guarantee little endianness we do things the + * hard way. + */ +#define GUARANTEED_LITTLE_ENDIAN (!defined(SAFE_ENDIAN) && !defined(SAFE_ALIGNMENT) && (\ + (defined(__BYTE_ORDER__)&&(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) \ + || defined(__LITTLE_ENDIAN__) \ + || defined(__LITTLE_ENDIAN) \ + || defined(_LITTLE_ENDIAN) \ +/* || (1 == *(unsigned char *)&(const int){1})*/ \ + )) + + +/* This is a runtime test */ +static const uint8_t dwordOrder[4] = { 1, 2, 3, 4 }; + +#define isWordLittleEndian() (*(uint16_t *)&dwordOrder == 0x0201) +#define isDWordLittleEndian() (*(uint32_t *)&dwordOrder == 0x04030201) + +#if GUARANTEED_LITTLE_ENDIAN + /* These are fast routines for guaranteed little endian machines */ + #define CONVERT_FROM_WORD_LE(w) + #define CONVERT_FROM_DWORD_LE(dw) +#else + /* These are slower routines for when little endianness cannot be guaranteed at compile time */ + #define CONVERT_FROM_WORD_LE(w) { if (!isWordLittleEndian()) w = ((((uint16_t)(w))>>8)|(((uint16_t)(w))<<8)); } + #define CONVERT_FROM_DWORD_LE(dw) { if (!isDWordLittleEndian()) dw = (((uint32_t)(((const uint8_t *)(&dw))[0]))|(((uint32_t)(((const uint8_t *)(&dw))[1]))<<8)|(((uint32_t)(((const uint8_t *)(&dw))[2]))<<16)|(((uint32_t)(((const uint8_t *)(&dw))[3]))<<24)); } +#endif +#define CONVERT_FROM_WORD_BE(w) { if (isWordLittleEndian()) w = ((((uint16_t)(w))>>8)|(((uint16_t)(w))<<8)); } + +typedef struct gdispImagePrivate { + uint8_t bmpflags; + #define BMP_V2 0x01 // Version 2 (old) header format + #define BMP_V4 0x02 // Version 4 (alpha support) header format + #define BMP_PALETTE 0x04 // Uses a palette + #define BMP_COMP_RLE 0x08 // Uses RLE compression + #define BMP_COMP_MASK 0x10 // Uses mask & shift decoding + #define BMP_RLE_ENC 0x20 // Currently in RLE encoded run + #define BMP_RLE_ABS 0x40 // Currently in RLE absolute run + #define BMP_TOP_TO_BOTTOM 0x80 // Decodes bottom to top line + uint8_t bitsperpixel; +#if GDISP_NEED_IMAGE_BMP_1 || GDISP_NEED_IMAGE_BMP_4 || GDISP_NEED_IMAGE_BMP_4_RLE || GDISP_NEED_IMAGE_BMP_8 || GDISP_NEED_IMAGE_BMP_8_RLE + uint16_t palsize; + pixel_t *palette; +#endif +#if GDISP_NEED_IMAGE_BMP_4_RLE || GDISP_NEED_IMAGE_BMP_8_RLE + uint16_t rlerun; + uint8_t rlecode; +#endif +#if GDISP_NEED_IMAGE_BMP_16 || GDISP_NEED_IMAGE_BMP_32 + int8_t shiftred; + int8_t shiftgreen; + int8_t shiftblue; + int8_t shiftalpha; + uint32_t maskred; + uint32_t maskgreen; + uint32_t maskblue; + uint32_t maskalpha; +#endif + size_t frame0pos; + pixel_t *frame0cache; + pixel_t buf[BLIT_BUFFER_SIZE]; + } gdispImagePrivate; + +gdispImageError gdispImageOpen_BMP(gdispImage *img) { + gdispImagePrivate *priv; + uint8_t hdr[2]; + uint16_t aword; + uint32_t adword; + uint32_t offsetColorTable; + + /* Read the file identifier */ + if (img->io.fns->read(&img->io, hdr, 2) != 2) + return GDISP_IMAGE_ERR_BADFORMAT; // It can't be us + + /* Process the BITMAPFILEHEADER structure */ + + /** + * We only accept Windows V2+ bitmaps. + * - we don't support OS/2 bitmaps, icons, pointers, or Windows V1 bitmaps. + */ + if (hdr[0] != 'B' || hdr[1] != 'M') + return GDISP_IMAGE_ERR_BADFORMAT; // It can't be us + + /* We know we are a BMP format image */ + img->flags = 0; + + /* Allocate our private area */ + if (!(img->priv = (gdispImagePrivate *)chHeapAlloc(NULL, sizeof(gdispImagePrivate)))) + return GDISP_IMAGE_ERR_NOMEMORY; + img->membytes = sizeof(gdispImagePrivate); + + /* Initialise the essential bits in the private area */ + priv = img->priv; + priv->frame0cache = 0; + priv->bmpflags = 0; +#if GDISP_NEED_IMAGE_BMP_1 || GDISP_NEED_IMAGE_BMP_4 || GDISP_NEED_IMAGE_BMP_4_RLE || GDISP_NEED_IMAGE_BMP_8 || GDISP_NEED_IMAGE_BMP_8_RLE + priv->palette = 0; +#endif + + /* Skip the size field and the 2 reserved fields */ + if (img->io.fns->read(&img->io, priv->buf, 8) != 8) + goto baddatacleanup; + + /* Get the offset to the bitmap data */ + if (img->io.fns->read(&img->io, &priv->frame0pos, 4) != 4) + goto baddatacleanup; + CONVERT_FROM_DWORD_LE(priv->frame0pos); + + /* Process the BITMAPCOREHEADER structure */ + + /* Get the offset to the colour data */ + if (img->io.fns->read(&img->io, &offsetColorTable, 4) != 4) + goto baddatacleanup; + CONVERT_FROM_DWORD_LE(offsetColorTable); + offsetColorTable += 14; // Add the size of the BITMAPFILEHEADER + + // Detect our bitmap version + if (offsetColorTable == 12+14) { + img->priv->bmpflags |= BMP_V2; + + // Read the header + if (img->io.fns->read(&img->io, priv->buf, 12-4) != 12-4) + goto baddatacleanup; + // Get the width + img->width = *(uint16_t *)(((uint8_t *)priv->buf)+0); + CONVERT_FROM_WORD_LE(img->width); + // Get the height + img->height = *(uint16_t *)(((uint8_t *)priv->buf)+2); + CONVERT_FROM_WORD_LE(img->height); + if (img->height < 0) { + img->priv->bmpflags |= BMP_TOP_TO_BOTTOM; + img->height = -img->height; + } + // Get the planes + aword = *(uint16_t *)(((uint8_t *)priv->buf)+4); + CONVERT_FROM_WORD_LE(aword); + if (aword != 1) + goto unsupportedcleanup; + // Get the bits per pixel + aword = *(uint16_t *)(((uint8_t *)priv->buf)+6); + CONVERT_FROM_WORD_LE(aword); + switch(aword) { +#if GDISP_NEED_IMAGE_BMP_1 + case 1: +#endif +#if GDISP_NEED_IMAGE_BMP_4 + case 4: +#endif +#if GDISP_NEED_IMAGE_BMP_8 + case 8: +#endif +#if GDISP_NEED_IMAGE_BMP_1 || GDISP_NEED_IMAGE_BMP_4 || GDISP_NEED_IMAGE_BMP_8 + priv->bmpflags |= BMP_PALETTE; + priv->palsize = 1<bitsperpixel = aword; + + } else if (offsetColorTable >= 40+14) { + if (offsetColorTable > 40+14) + priv->bmpflags |= BMP_V4; + + // Read the header + if (img->io.fns->read(&img->io, priv->buf, 40-4) != 40-4) + goto baddatacleanup; + // Get the width + adword = *(uint32_t *)(((uint8_t *)priv->buf)+0); + CONVERT_FROM_DWORD_LE(adword); + if (adword > 32768) // This also picks up negative values + goto unsupportedcleanup; + img->width = adword; + // Get the height + adword = *(uint32_t *)(((uint8_t *)priv->buf)+4); + CONVERT_FROM_DWORD_LE(adword); + if ((int32_t)adword < 0) { // Negative test + priv->bmpflags |= BMP_TOP_TO_BOTTOM; + adword = -adword; + } + if (adword > 32768) + goto unsupportedcleanup; + img->height = adword; + // Get the planes + aword = *(uint16_t *)(((uint8_t *)priv->buf)+8); + CONVERT_FROM_WORD_LE(aword); + if (aword != 1) + goto unsupportedcleanup; + // Get the bits per pixel + aword = *(uint16_t *)(((uint8_t *)priv->buf)+10); + CONVERT_FROM_WORD_LE(aword); + switch(aword) { +#if GDISP_NEED_IMAGE_BMP_1 + case 1: +#endif +#if GDISP_NEED_IMAGE_BMP_4 || GDISP_NEED_IMAGE_BMP_4_RLE + case 4: +#endif +#if GDISP_NEED_IMAGE_BMP_8 || GDISP_NEED_IMAGE_BMP_8_RLE + case 8: +#endif +#if GDISP_NEED_IMAGE_BMP_1 || GDISP_NEED_IMAGE_BMP_4 || GDISP_NEED_IMAGE_BMP_4_RLE || GDISP_NEED_IMAGE_BMP_8 || GDISP_NEED_IMAGE_BMP_8_RLE + priv->bmpflags |= BMP_PALETTE; + priv->palsize = 1<bitsperpixel = aword; + // Get the compression + adword = *(uint32_t *)(((uint8_t *)priv->buf)+12); + CONVERT_FROM_DWORD_LE(adword); + switch(adword) { + case 0: // BI_RGB - uncompressed + break; +#if GDISP_NEED_IMAGE_BMP_8_RLE + case 1: // BI_RLE8 compression + if (priv->bitsperpixel != 8) + goto unsupportedcleanup; + priv->bmpflags |= BMP_COMP_RLE; + break; +#endif +#if GDISP_NEED_IMAGE_BMP_4_RLE + case 2: // BI_RLE4 compression + if (priv->bitsperpixel != 4) + goto unsupportedcleanup; + priv->bmpflags |= BMP_COMP_RLE; + break; +#endif +#if GDISP_NEED_IMAGE_BMP_16 || GDISP_NEED_IMAGE_BMP_32 + case 3: // BI_BITFIELDS decoding + if (priv->bitsperpixel < 16 || priv->bitsperpixel == 24) + goto unsupportedcleanup; + priv->bmpflags |= BMP_COMP_MASK; + if (priv->bmpflags & BMP_V4) // V4 stored the masks in the header + offsetColorTable = 40+14; + break; +#endif + default: + goto unsupportedcleanup; + } + priv->bitsperpixel = aword; +#if GDISP_NEED_IMAGE_BMP_1 || GDISP_NEED_IMAGE_BMP_4 || GDISP_NEED_IMAGE_BMP_4_RLE || GDISP_NEED_IMAGE_BMP_8 || GDISP_NEED_IMAGE_BMP_8_RLE + // Get the actual colors used + adword = *(uint32_t *)(((uint8_t *)priv->buf)+28); + CONVERT_FROM_DWORD_LE(adword); + if (adword && adword < priv->palsize) + priv->palsize = adword; +#endif + } else + goto baddatacleanup; + +#if GDISP_NEED_IMAGE_BMP_1 || GDISP_NEED_IMAGE_BMP_4 || GDISP_NEED_IMAGE_BMP_4_RLE || GDISP_NEED_IMAGE_BMP_8 || GDISP_NEED_IMAGE_BMP_8_RLE + /* Load the palette tables */ + if (priv->bmpflags & BMP_PALETTE) { + img->io.fns->seek(&img->io, offsetColorTable); + + if (!(priv->palette = (color_t *)chHeapAlloc(NULL, priv->palsize*sizeof(color_t)))) + return GDISP_IMAGE_ERR_NOMEMORY; + img->membytes += priv->palsize * sizeof(color_t); + if (priv->bmpflags & BMP_V2) { + for(aword = 0; aword < priv->palsize; aword++) { + if (img->io.fns->read(&img->io, &priv->buf, 3) != 3) goto baddatacleanup; + priv->palette[aword] = RGB2COLOR(((uint8_t *)priv->buf)[2], ((uint8_t *)priv->buf)[1], ((uint8_t *)priv->buf)[0]); + } + } else { + for(aword = 0; aword < priv->palsize; aword++) { + if (img->io.fns->read(&img->io, &priv->buf, 4) != 4) goto baddatacleanup; + priv->palette[aword] = RGB2COLOR(((uint8_t *)priv->buf)[2], ((uint8_t *)priv->buf)[1], ((uint8_t *)priv->buf)[0]); + } + } + + } +#endif + +#if GDISP_NEED_IMAGE_BMP_16 || GDISP_NEED_IMAGE_BMP_32 + /* Load the bit masks */ + if (priv->bmpflags & BMP_COMP_MASK) { + img->io.fns->seek(&img->io, offsetColorTable); + if (img->io.fns->read(&img->io, &priv->maskred, 4) != 4) goto baddatacleanup; + CONVERT_FROM_DWORD_LE(priv->maskred); + if (img->io.fns->read(&img->io, &priv->maskgreen, 4) != 4) goto baddatacleanup; + CONVERT_FROM_DWORD_LE(priv->maskgreen); + if (img->io.fns->read(&img->io, &priv->maskblue, 4) != 4) goto baddatacleanup; + CONVERT_FROM_DWORD_LE(priv->maskblue); + if (priv->bmpflags & BMP_V4) { + if (img->io.fns->read(&img->io, &priv->maskalpha, 4) != 4) goto baddatacleanup; + CONVERT_FROM_DWORD_LE(priv->maskalpha); + } else + priv->maskalpha = 0; + } else if (priv->bitsperpixel == 16) { + priv->bmpflags |= BMP_COMP_MASK; + priv->maskred = 0x001F0000; + priv->maskgreen = 0x07E00000; + priv->maskblue = 0xF8000000; + priv->maskalpha = 0; + } else if (priv->bitsperpixel == 32) { + priv->bmpflags |= BMP_COMP_MASK; + priv->maskred = 0x00FF0000; + priv->maskgreen = 0x0000FF00; + priv->maskblue = 0x000000FF; + priv->maskalpha = 0; + } + + /* We need to adjust the masks and calculate the shift values so the result scales 0 -> 255 */ + if (priv->bmpflags & BMP_COMP_MASK) { + priv->shiftred = 0; + priv->shiftgreen = 0; + priv->shiftblue = 0; + if (priv->bitsperpixel == 16) { + priv->maskred >>= 16; + priv->maskgreen >>= 16; + priv->maskblue >>= 16; + } + if (priv->maskred) { + if (priv->maskred < 256) + for(adword = priv->maskred; adword < 128; priv->shiftred--, adword <<= 1); + else + for(adword = priv->maskred; adword > 255; priv->shiftred++, adword >>= 1); + } + if (priv->maskgreen) { + if (priv->maskgreen < 256) + for(adword = priv->maskgreen; adword < 128; priv->shiftgreen--, adword <<= 1); + else + for(adword = priv->maskgreen; adword > 255; priv->shiftgreen++, adword >>= 1); + } + if (priv->maskblue) { + if (priv->maskblue < 256) + for(adword = priv->maskblue; adword < 128; priv->shiftblue--, adword <<= 1); + else + for(adword = priv->maskblue; adword > 255; priv->shiftblue++, adword >>= 1); + } + if (priv->maskalpha) { + if (priv->maskalpha < 256) + for(adword = priv->maskalpha; adword < 128; priv->shiftalpha--, adword <<= 1); + else + for(adword = priv->maskalpha; adword > 255; priv->shiftalpha++, adword >>= 1); + } + } +#endif + + return GDISP_IMAGE_ERR_OK; + +baddatacleanup: + gdispImageClose_BMP(img); // Clean up the private data area + return GDISP_IMAGE_ERR_BADDATA; // Oops - something wrong + +unsupportedcleanup: + gdispImageClose_BMP(img); // Clean up the private data area + return GDISP_IMAGE_ERR_UNSUPPORTED; // Not supported +} + +void gdispImageClose_BMP(gdispImage *img) { + if (img->priv) { +#if GDISP_NEED_IMAGE_BMP_1 || GDISP_NEED_IMAGE_BMP_4 || GDISP_NEED_IMAGE_BMP_4_RLE || GDISP_NEED_IMAGE_BMP_8 || GDISP_NEED_IMAGE_BMP_8_RLE + if (img->priv->palette) + chHeapFree((void *)img->priv->palette); +#endif + if (img->priv->frame0cache) + chHeapFree((void *)img->priv->frame0cache); + chHeapFree((void *)img->priv); + img->priv = 0; + } + img->membytes = 0; + img->io.fns->close(&img->io); +} + +static coord_t getPixels(gdispImage *img, coord_t x) { + gdispImagePrivate * priv; + color_t * pc; + coord_t len; + + priv = img->priv; + pc = priv->buf; + len = 0; + + switch(priv->bitsperpixel) { +#if GDISP_NEED_IMAGE_BMP_1 + case 1: + { + uint8_t b[4]; + uint8_t m; + + priv = img->priv; + pc = priv->buf; + len = 0; + + while(x < img->width && len <= BLIT_BUFFER_SIZE-32) { + if (img->io.fns->read(&img->io, &b, 4) != 4) + return 0; + + for(m=0x80; m; m >>= 1, pc++) + pc[0] = priv->palette[(m&b[0]) ? 1 : 0]; + for(m=0x80; m; m >>= 1, pc++) + pc[0] = priv->palette[(m&b[1]) ? 1 : 0]; + for(m=0x80; m; m >>= 1, pc++) + pc[0] = priv->palette[(m&b[2]) ? 1 : 0]; + for(m=0x80; m; m >>= 1, pc++) + pc[0] = priv->palette[(m&b[3]) ? 1 : 0]; + len += 32; + x += 32; + } + } + return len; +#endif + +#if GDISP_NEED_IMAGE_BMP_4 || GDISP_NEED_IMAGE_BMP_4_RLE + case 4: + #if GDISP_NEED_IMAGE_BMP_4_RLE + #if GDISP_NEED_IMAGE_BMP_4 + if (priv->bmpflags & BMP_COMP_RLE) + #endif + { + uint8_t b[4]; + + while(x < img->width) { + if (priv->bmpflags & BMP_RLE_ENC) { + while (priv->rlerun && len <= BLIT_BUFFER_SIZE-2 && x < img->width) { + *pc++ = priv->palette[priv->rlecode >> 4]; + priv->rlerun--; + len++; + x++; + if (priv->rlerun) { + *pc++ = priv->palette[priv->rlecode & 0x0F]; + priv->rlerun--; + len++; + x++; + } + } + if (priv->rlerun) // Return if we have more run to do + return len; + } else if (priv->bmpflags & BMP_RLE_ABS) { + while (priv->rlerun && len <= BLIT_BUFFER_SIZE-2 && x < img->width) { + if (img->io.fns->read(&img->io, &b, 1) != 1) + return 0; + *pc++ = priv->palette[b[0] >> 4]; + priv->rlerun--; + len++; + x++; + if (priv->rlerun) { + *pc++ = priv->palette[b[0] & 0x0F]; + priv->rlerun--; + len++; + x++; + } + } + if (priv->rlerun) // Return if we have more run to do + return len; + } + + // We have finished the current run - read a new run + priv->bmpflags &= ~(BMP_RLE_ENC|BMP_RLE_ABS); + + // There are always at least 2 bytes in an RLE code + if (img->io.fns->read(&img->io, &b, 2) != 2) + return 0; + + if (b[0]) { // Encoded mode + priv->rlerun = b[0]; + priv->rlecode = b[1]; + priv->bmpflags |= BMP_RLE_ENC; + } else if (b[1] == 0) { // End of line + if (x < img->width) { + priv->rlerun = img->width - x; + priv->rlecode = 0; // Who knows what color this should really be + priv->bmpflags |= BMP_RLE_ENC; + } + } else if (b[1] == 1) { // End of file + return len; + } else if (b[1] == 2) { // Delta x, y + // There are always at least 2 bytes in an RLE code + if (img->io.fns->read(&img->io, &b, 2) != 2) + return 0; + priv->rlerun = b[0] + (uint16_t)b[1] * img->width; + priv->rlecode = 0; // Who knows what color this should really be + priv->bmpflags |= BMP_RLE_ENC; + } else { // Absolute mode + priv->rlerun = b[1]; + priv->bmpflags |= BMP_RLE_ABS; + } + } + return len; + } + #endif + #if GDISP_NEED_IMAGE_BMP_4 + { + uint8_t b[4]; + + while(x < img->width && len <= BLIT_BUFFER_SIZE-8) { + if (img->io.fns->read(&img->io, &b, 4) != 4) + return 0; + + *pc++ = priv->palette[b[0] >> 4]; + *pc++ = priv->palette[b[0] & 0x0F]; + *pc++ = priv->palette[b[1] >> 4]; + *pc++ = priv->palette[b[1] & 0x0F]; + *pc++ = priv->palette[b[2] >> 4]; + *pc++ = priv->palette[b[2] & 0x0F]; + *pc++ = priv->palette[b[3] >> 4]; + *pc++ = priv->palette[b[3] & 0x0F]; + len += 8; + x += 8; + } + return len; + } + #endif +#endif + +#if GDISP_NEED_IMAGE_BMP_8 || GDISP_NEED_IMAGE_BMP_8_RLE + case 8: + #if GDISP_NEED_IMAGE_BMP_8_RLE + #if GDISP_NEED_IMAGE_BMP_8 + if (priv->bmpflags & BMP_COMP_RLE) + #endif + { + uint8_t b[4]; + + while(x < img->width) { + if (priv->bmpflags & BMP_RLE_ENC) { + while (priv->rlerun && len < BLIT_BUFFER_SIZE && x < img->width) { + *pc++ = priv->palette[priv->rlecode]; + priv->rlerun--; + len++; + x++; + } + if (priv->rlerun) // Return if we have more run to do + return len; + } else if (priv->bmpflags & BMP_RLE_ABS) { + while (priv->rlerun && len < BLIT_BUFFER_SIZE && x < img->width) { + if (img->io.fns->read(&img->io, &b, 1) != 1) + return 0; + *pc++ = priv->palette[b[0]]; + priv->rlerun--; + len++; + x++; + } + if (priv->rlerun) // Return if we have more run to do + return len; + } + + // We have finished the current run - read a new run + priv->bmpflags &= ~(BMP_RLE_ENC|BMP_RLE_ABS); + + // There are always at least 2 bytes in an RLE code + if (img->io.fns->read(&img->io, &b, 2) != 2) + return 0; + + if (b[0]) { // Encoded mode + priv->rlerun = b[0]; + priv->rlecode = b[1]; + priv->bmpflags |= BMP_RLE_ENC; + } else if (b[1] == 0) { // End of line + if (x < img->width) { + priv->rlerun = img->width - x; + priv->rlecode = 0; // Who knows what color this should really be + priv->bmpflags |= BMP_RLE_ENC; + } + } else if (b[1] == 1) { // End of file + return len; + } else if (b[1] == 2) { // Delta x, y + // There are always at least 2 bytes in an RLE code + if (img->io.fns->read(&img->io, &b, 2) != 2) + return GDISP_IMAGE_ERR_BADDATA; + priv->rlerun = b[0] + (uint16_t)b[1] * img->width; + priv->rlecode = 0; // Who knows what color this should really be + priv->bmpflags |= BMP_RLE_ENC; + } else { // Absolute mode + priv->rlerun = b[1]; + priv->bmpflags |= BMP_RLE_ABS; + } + } + return len; + } + #endif + #if GDISP_NEED_IMAGE_BMP_8 + { + uint8_t b[4]; + + while(x < img->width && len <= BLIT_BUFFER_SIZE-4) { + if (img->io.fns->read(&img->io, &b, 4) != 4) + return 0; + + *pc++ = priv->palette[b[0]]; + *pc++ = priv->palette[b[1]]; + *pc++ = priv->palette[b[2]]; + *pc++ = priv->palette[b[3]]; + len += 4; + x += 4; + } + return len; + } + #endif +#endif + +#if GDISP_NEED_IMAGE_BMP_16 + case 16: + { + uint16_t w[2]; + color_t r, g, b; + + while(x < img->width && len <= BLIT_BUFFER_SIZE-2) { + if (img->io.fns->read(&img->io, &w, 4) != 4) + return 0; + CONVERT_FROM_WORD_BE(w[0]); + CONVERT_FROM_WORD_BE(w[1]); + if (priv->shiftred < 0) + r = (color_t)((w[0] & priv->maskred) << -priv->shiftred); + else + r = (color_t)((w[0] & priv->maskred) >> priv->shiftred); + if (priv->shiftgreen < 0) + g = (color_t)((w[0] & priv->maskgreen) << -priv->shiftgreen); + else + g = (color_t)((w[0] & priv->maskgreen) >> priv->shiftgreen); + if (priv->shiftblue < 0) + b = (color_t)((w[0] & priv->maskblue) << -priv->shiftblue); + else + b = (color_t)((w[0] & priv->maskblue) >> priv->shiftblue); + /* We don't support alpha yet */ + *pc++ = RGB2COLOR(r, g, b); + if (priv->shiftred < 0) + r = (color_t)((w[1] & priv->maskred) << -priv->shiftred); + else + r = (color_t)((w[1] & priv->maskred) >> priv->shiftred); + if (priv->shiftgreen < 0) + g = (color_t)((w[1] & priv->maskgreen) << -priv->shiftgreen); + else + g = (color_t)((w[1] & priv->maskgreen) >> priv->shiftgreen); + if (priv->shiftblue < 0) + b = (color_t)((w[1] & priv->maskblue) << -priv->shiftblue); + else + b = (uint8_t)((w[1] & priv->maskblue) >> priv->shiftblue); + /* We don't support alpha yet */ + *pc++ = RGB2COLOR(r, g, b); + x += 2; + len += 2; + } + } + return len; +#endif + +#if GDISP_NEED_IMAGE_BMP_24 + case 24: + { + uint8_t b[3]; + + while(x < img->width && len < BLIT_BUFFER_SIZE) { + if (img->io.fns->read(&img->io, &b, 3) != 3) + return 0; + *pc++ = RGB2COLOR(b[2], b[1], b[0]); + x++; + len++; + } + + if (x >= img->width) { + // Make sure we have read a multiple of 4 bytes for the line + if ((x & 3) && img->io.fns->read(&img->io, &b, x & 3) != (x & 3)) + return 0; + } + } + return len; +#endif + +#if GDISP_NEED_IMAGE_BMP_32 + case 32: + { + uint32_t dw; + color_t r, g, b; + + while(x < img->width && len < BLIT_BUFFER_SIZE) { + if (img->io.fns->read(&img->io, &dw, 4) != 4) + return 0; + CONVERT_FROM_DWORD_LE(dw); + if (priv->shiftred < 0) + r = (color_t)((dw & priv->maskred) << -priv->shiftred); + else + r = (color_t)((dw & priv->maskred) >> priv->shiftred); + if (priv->shiftgreen < 0) + g = (color_t)((dw & priv->maskgreen) << -priv->shiftgreen); + else + g = (color_t)((dw & priv->maskgreen) >> priv->shiftgreen); + if (priv->shiftblue < 0) + b = (color_t)((dw & priv->maskblue) << -priv->shiftblue); + else + b = (color_t)((dw & priv->maskblue) >> priv->shiftblue); + /* We don't support alpha yet */ + *pc++ = RGB2COLOR(r, g, b); + x++; + len++; + } + } + return len; +#endif + + default: + return len; + } +} + +gdispImageError gdispImageCache_BMP(gdispImage *img) { + gdispImagePrivate * priv; + color_t * pcs; + color_t * pcd; + coord_t pos, x, y; + size_t len; + + /* If we are already cached - just return OK */ + priv = img->priv; + if (priv->frame0cache) + return GDISP_IMAGE_ERR_OK; + + /* We need to allocate the cache */ + len = img->width * img->height * sizeof(pixel_t); + priv->frame0cache = (pixel_t *)chHeapAlloc(NULL, len); + if (!priv->frame0cache) + return GDISP_IMAGE_ERR_NOMEMORY; + img->membytes += len; + + /* Read the entire bitmap into cache */ + img->io.fns->seek(&img->io, priv->frame0pos); +#if GDISP_NEED_IMAGE_BMP_4_RLE || GDISP_NEED_IMAGE_BMP_8_RLE + priv->rlerun = 0; + priv->rlecode = 0; +#endif + + if (priv->bmpflags & BMP_TOP_TO_BOTTOM) { + for(y = 0, pcd = priv->frame0cache; y < img->height; y++) { + x = 0; pos = 0; + while(x < img->width) { + if (!pos) { + if (!(pos = getPixels(img, x))) + return GDISP_IMAGE_ERR_BADDATA; + pcs = priv->buf; + } + *pcd++ = *pcs++; + x++; pos--; + } + } + } else { + for(y = img->height-1, pcd = priv->frame0cache + img->width*(img->height-1); y >= 0; y--, pcd -= 2*img->width) { + x = 0; pos = 0; + while(x < img->width) { + if (!pos) { + if (!(pos = getPixels(img, x))) + return GDISP_IMAGE_ERR_BADDATA; + pcs = priv->buf; + } + *pcd++ = *pcs++; + x++; pos--; + } + } + } + + return GDISP_IMAGE_ERR_OK; +} + +gdispImageError gdispImageDraw_BMP(gdispImage *img, coord_t x, coord_t y, coord_t cx, coord_t cy, coord_t sx, coord_t sy) { + gdispImagePrivate * priv; + coord_t mx, my; + coord_t pos, len, st; + + priv = img->priv; + + /* Check some reasonableness */ + if (sx >= img->width || sy >= img->height) return GDISP_IMAGE_ERR_OK; + if (sx + cx > img->width) cx = img->width - sx; + if (sy + cy > img->height) cy = img->height - sy; + + /* Draw from the image cache - if it exists */ + if (priv->frame0cache) { + gdispBlitAreaEx(x, y, cx, cy, sx, sy, img->width, priv->frame0cache); + return GDISP_IMAGE_ERR_OK; + } + + /* Start decoding from the beginning */ + img->io.fns->seek(&img->io, priv->frame0pos); +#if GDISP_NEED_IMAGE_BMP_4_RLE || GDISP_NEED_IMAGE_BMP_8_RLE + priv->rlerun = 0; + priv->rlecode = 0; +#endif + + if (priv->bmpflags & BMP_TOP_TO_BOTTOM) { + for(my = 0; my < img->height; my++) { + mx = 0; + while(mx < img->width) { + if (!(pos = getPixels(img, mx))) + return GDISP_IMAGE_ERR_BADDATA; + if (my >= sy && my < sy+cy && mx < sx+cx && mx+pos >= sx) { + st = mx < sx ? sx - mx : 0; + len = pos-st; + if (mx+st+len > sx+cx) len = sx+cx-mx-st; + if (len == 1) + gdispDrawPixel(x+mx+st-sx, y+my-sy, priv->buf[st]); + else + gdispBlitAreaEx(x+mx+st-sx, y+my-sy, len, 1, st, 0, pos, priv->buf); + } + mx += pos; + } + } + } else { + for(my = img->height-1; my >= 0; my--) { + mx = 0; + while(mx < img->width) { + if (!(pos = getPixels(img, mx))) + return GDISP_IMAGE_ERR_BADDATA; + if (my >= sy && my < sy+cy && mx < sx+cx && mx+pos >= sx) { + st = mx < sx ? sx - mx : 0; + len = pos-st; + if (mx+st+len > sx+cx) len = sx+cx-mx-st; + if (len == 1) + gdispDrawPixel(x+mx+st-sx, y+my-sy, priv->buf[st]); + else + gdispBlitAreaEx(x+mx+st-sx, y+my-sy, len, 1, st, 0, pos, priv->buf); + } + mx += pos; + } + } + } + + return GDISP_IMAGE_ERR_OK; +} + +systime_t gdispImageNext_BMP(gdispImage *img) { + (void) img; + + /* No more frames/pages */ + return TIME_INFINITE; +} + +#endif /* GFX_USE_GDISP && GDISP_NEED_IMAGE && GDISP_NEED_IMAGE_BMP */ +/** @} */ diff --git a/src/gdisp/image_gif.c b/src/gdisp/image_gif.c new file mode 100644 index 00000000..b7a940ad --- /dev/null +++ b/src/gdisp/image_gif.c @@ -0,0 +1,47 @@ +/* + ChibiOS/GFX - Copyright (C) 2012, 2013 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + ChibiOS/GFX is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/** + * @file src/gdisp/image_gif.c + * @brief GDISP native image code. + */ +#include "ch.h" +#include "hal.h" +#include "gfx.h" + +#if GFX_USE_GDISP && GDISP_NEED_IMAGE && GDISP_NEED_IMAGE_GIF + +#error "GIF support not implemented yet" + +/* A pallete structure */ +typedef struct gdispImagePallete { + uint8_t flags; + #define GDISP_IMAGE_FLG_INT_TRANSPARENT 0x01 + uint8_t idxtrans; /* The transparent idx */ + uint8_t maxidx; /* The maximum index (0..255) */ + uint8_t repidx; /* The index to use if the image data > maxidx */ + color_t pal[256]; /* The pallete entries - not all may actually be allocated */ +} gdispImagePallete; + +/* Draw a single palletized line (or partial line) */ +static void gdispDrawPalleteLine(const gdispImagePallete *pal, const uint8_t *line, coord_t x, coord_t y, coord_t cx); + +#endif /* GFX_USE_GDISP && GDISP_NEED_IMAGE && GDISP_NEED_IMAGE_GIF */ +/** @} */ diff --git a/src/gdisp/image_jpg.c b/src/gdisp/image_jpg.c new file mode 100644 index 00000000..e8b5eeeb --- /dev/null +++ b/src/gdisp/image_jpg.c @@ -0,0 +1,34 @@ +/* + ChibiOS/GFX - Copyright (C) 2012, 2013 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + ChibiOS/GFX is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/** + * @file src/gdisp/image_jpg.c + * @brief GDISP native image code. + */ +#include "ch.h" +#include "hal.h" +#include "gfx.h" + +#if GFX_USE_GDISP && GDISP_NEED_IMAGE && GDISP_NEED_IMAGE_JPG + +#error "JPG support not implemented yet" + +#endif /* GFX_USE_GDISP && GDISP_NEED_IMAGE && GDISP_NEED_IMAGE_JPG */ +/** @} */ diff --git a/src/gdisp/image_native.c b/src/gdisp/image_native.c new file mode 100644 index 00000000..4a41e9c8 --- /dev/null +++ b/src/gdisp/image_native.c @@ -0,0 +1,157 @@ +/* + ChibiOS/GFX - Copyright (C) 2012, 2013 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + ChibiOS/GFX is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/** + * @file src/gdisp/image_native.c + * @brief GDISP native image code. + */ +#include "ch.h" +#include "hal.h" +#include "gfx.h" + +#if GFX_USE_GDISP && GDISP_NEED_IMAGE && GDISP_NEED_IMAGE_NATIVE + +/** + * How big a pixel array to allocate for blitting + * Bigger is faster but uses more RAM. + */ +#define BLIT_BUFFER_SIZE 32 + +#define HEADER_SIZE 8 +#define FRAME0POS (HEADER_SIZE) + +typedef struct gdispImagePrivate { + pixel_t *frame0cache; + pixel_t buf[BLIT_BUFFER_SIZE]; + } gdispImagePrivate; + +gdispImageError gdispImageOpen_NATIVE(gdispImage *img) { + uint8_t hdr[HEADER_SIZE]; + + /* Read the 8 byte header */ + if (img->io.fns->read(&img->io, hdr, 8) != 8) + return GDISP_IMAGE_ERR_BADFORMAT; // It can't be us + + if (hdr[0] != 'N' || hdr[1] != 'I') + return GDISP_IMAGE_ERR_BADFORMAT; // It can't be us + + if (hdr[6] != GDISP_PIXELFORMAT/256 || hdr[7] != (GDISP_PIXELFORMAT & 0xFF)) + return GDISP_IMAGE_ERR_UNSUPPORTED; // Unsupported pixel format + + /* We know we are a native format image */ + img->flags = 0; + img->width = (((uint16_t)hdr[2])<<8) | (hdr[3]); + img->height = (((uint16_t)hdr[4])<<8) | (hdr[5]); + if (img->width < 1 || img->height < 1) + return GDISP_IMAGE_ERR_BADDATA; + if (!(img->priv = (gdispImagePrivate *)chHeapAlloc(NULL, sizeof(gdispImagePrivate)))) + return GDISP_IMAGE_ERR_NOMEMORY; + img->membytes = sizeof(gdispImagePrivate); + img->priv->frame0cache = 0; + + return GDISP_IMAGE_ERR_OK; +} + +void gdispImageClose_NATIVE(gdispImage *img) { + if (img->priv) { + if (img->priv->frame0cache) + chHeapFree((void *)img->priv->frame0cache); + chHeapFree((void *)img->priv); + img->priv = 0; + } + img->membytes = 0; + img->io.fns->close(&img->io); +} + +gdispImageError gdispImageCache_NATIVE(gdispImage *img) { + size_t len; + + /* If we are already cached - just return OK */ + if (img->priv->frame0cache) + return GDISP_IMAGE_ERR_OK; + + /* We need to allocate the cache */ + len = img->width * img->height * sizeof(pixel_t); + img->priv->frame0cache = (pixel_t *)chHeapAlloc(NULL, len); + if (!img->priv->frame0cache) + return GDISP_IMAGE_ERR_NOMEMORY; + img->membytes += len; + + /* Read the entire bitmap into cache */ + img->io.fns->seek(&img->io, FRAME0POS); + if (img->io.fns->read(&img->io, img->priv->frame0cache, len) != len) + return GDISP_IMAGE_ERR_BADDATA; + + return GDISP_IMAGE_ERR_OK; +} + +gdispImageError gdispImageDraw_NATIVE(gdispImage *img, coord_t x, coord_t y, coord_t cx, coord_t cy, coord_t sx, coord_t sy) { + coord_t mx, mcx; + size_t pos, len; + + /* Check some reasonableness */ + if (sx >= img->width || sy >= img->height) return GDISP_IMAGE_ERR_OK; + if (sx + cx > img->width) cx = img->width - sx; + if (sy + cy > img->height) cy = img->height - sy; + + /* Draw from the image cache - if it exists */ + if (img->priv->frame0cache) { + gdispBlitAreaEx(x, y, cx, cy, sx, sy, img->width, img->priv->frame0cache); + return GDISP_IMAGE_ERR_OK; + } + + /* For this image decoder we cheat and just seek straight to the region we want to display */ + pos = FRAME0POS + (img->width * sy + cx) * sizeof(pixel_t); + + /* Cycle through the lines */ + for(;cy;cy--, y++) { + /* Move to the start of the line */ + img->io.fns->seek(&img->io, pos); + + /* Draw the line in chunks using BitBlt */ + for(mx = x, mcx = cx; mcx > 0; mcx -= len, mx += len) { + // Read the data + len = img->io.fns->read(&img->io, + img->priv->buf, + mx > BLIT_BUFFER_SIZE ? (BLIT_BUFFER_SIZE*sizeof(pixel_t)) : (mx * sizeof(pixel_t))) + / sizeof(pixel_t); + if (!len) + return GDISP_IMAGE_ERR_BADDATA; + + /* Blit the chunk of data */ + gdispBlitAreaEx(mx, y, len, 1, 0, 0, len, img->priv->buf); + } + + /* Get the position for the start of the next line */ + pos += img->width*sizeof(pixel_t); + } + + return GDISP_IMAGE_ERR_OK; +} + +systime_t gdispImageNext_NATIVE(gdispImage *img) { + (void) img; + + /* No more frames/pages */ + return TIME_INFINITE; +} + +#endif /* GFX_USE_GDISP && GDISP_NEED_IMAGE && GDISP_NEED_IMAGE_NATIVE */ +/** @} */ diff --git a/src/gdisp/image_png.c b/src/gdisp/image_png.c new file mode 100644 index 00000000..3dca3645 --- /dev/null +++ b/src/gdisp/image_png.c @@ -0,0 +1,34 @@ +/* + ChibiOS/GFX - Copyright (C) 2012, 2013 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + ChibiOS/GFX is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/** + * @file src/gdisp/image_png.c + * @brief GDISP native image code. + */ +#include "ch.h" +#include "hal.h" +#include "gfx.h" + +#if GFX_USE_GDISP && GDISP_NEED_IMAGE && GDISP_NEED_IMAGE_PNG + +#error "PNG support not implemented yet" + +#endif /* GFX_USE_GDISP && GDISP_NEED_IMAGE && GDISP_NEED_IMAGE_PNG */ +/** @} */