/*
* dib2sprt.c
* Copyright (C) 2000-2003 A.J. van Os; Released under GPL
*
* Description:
* Functions to translate dib pictures into sprites
*/

#include <stdio.h>
#include <string.h>
#include "DeskLib:Error.h"
#include "DeskLib:Sprite.h"
#include "antiword.h"

#if 0 /* defined(DEBUG) */
static int iPicCounter = 0;
#endif /* DEBUG */


/*
* iGetByteWidth - compute the number of bytes needed for a row of pixels
*/
static int
iGetByteWidth(const imagedata_type *pImg)
{
       switch (pImg->uiBitsPerComponent) {
       case  1:
               return (pImg->iWidth + 31) / 32 * sizeof(int);
       case  4:
               return (pImg->iWidth + 7) / 8 * sizeof(int);
       case  8:
       case 24:
               return (pImg->iWidth + 3) / 4 * sizeof(int);
       default:
               DBG_DEC(pImg->uiBitsPerComponent);
               return 0;
       }
} /* end of iGetByteWidth */

/*
* pCreateBlankSprite - Create a blank sprite.
*
* Create a blank sprite and add a palette if needed
*
* returns a pointer to the sprite when successful, otherwise NULL
*/
static sprite_areainfo *
pCreateBlankSprite(const imagedata_type *pImg, size_t *pSize)
{
       sprite_areainfo *pArea;
       UCHAR   *pucTmp;
       size_t  tSize;
       screen_modeval  uMode;
       int     iIndex, iPaletteEntries;

       TRACE_MSG("pCreateBlankSprite");

       fail(pImg == NULL);
       fail(pSize == NULL);

       switch (pImg->uiBitsPerComponent) {
       case  1:
               uMode.screen_mode = 18;
               iPaletteEntries = 2;
               break;
       case  4:
               uMode.screen_mode = 20;
               iPaletteEntries = 16;
               break;
       case  8:
       case 24:
               uMode.screen_mode = 21;
               iPaletteEntries = 0;
               break;
       default:
               DBG_DEC(pImg->uiBitsPerComponent);
               return NULL;
       }
       fail(iPaletteEntries < 0 || iPaletteEntries > 16);

       /* Get memory for the sprite */
       tSize = sizeof(sprite_areainfo) +
               Sprite_MemorySize(pImg->iWidth, pImg->iHeight, uMode,
               iPaletteEntries > 0 ? sprite_HASPAL : sprite_HASNOMASKPAL);
       DBG_DEC(tSize);
       pArea = xmalloc(tSize);

       /* Initialise sprite area */
       pArea->areasize = tSize;
       pArea->numsprites = 0;
       pArea->firstoffset = sizeof(sprite_areainfo);
       pArea->freeoffset = sizeof(sprite_areainfo);

       /* Create a blank sprite */
       Error_CheckFatal(Sprite_Create(pArea, "wordimage",
               iPaletteEntries > 0 ? 1 : 0,
               pImg->iWidth, pImg->iHeight, uMode));

       /* Add the palette */
       pucTmp = (UCHAR *)pArea + pArea->firstoffset + sizeof(sprite_header);
       for (iIndex = 0; iIndex < iPaletteEntries; iIndex++) {
               /* First color */
               *pucTmp++ = 0;
               *pucTmp++ = pImg->aucPalette[iIndex][0];
               *pucTmp++ = pImg->aucPalette[iIndex][1];
               *pucTmp++ = pImg->aucPalette[iIndex][2];
               /* Second color */
               *pucTmp++ = 0;
               *pucTmp++ = pImg->aucPalette[iIndex][0];
               *pucTmp++ = pImg->aucPalette[iIndex][1];
               *pucTmp++ = pImg->aucPalette[iIndex][2];
       }

       *pSize = tSize;
       return pArea;
} /* end of pCreateBlankSprite */

/*
* iReduceColor - reduce from 24 bit to 8 bit color
*
* Reduce 24 bit true colors to RISC OS default 256 color palette
*
* returns the resulting color
*/
static int
iReduceColor(int iRed, int iGreen, int iBlue)
{
       int     iResult;

       iResult = (iBlue & 0x80) ? 0x80 : 0;
       iResult |= (iGreen & 0x80) ? 0x40 : 0;
       iResult |= (iGreen & 0x40) ? 0x20 : 0;
       iResult |= (iRed & 0x80) ? 0x10 : 0;
       iResult |= (iBlue & 0x40) ? 0x08 : 0;
       iResult |= (iRed & 0x40) ? 0x04 : 0;
       iResult |= ((iRed | iGreen | iBlue) & 0x20) ? 0x02 : 0;
       iResult |= ((iRed | iGreen | iBlue) & 0x10) ? 0x01 : 0;
       return iResult;
} /* end of iReduceColor */

/*
* vDecode1bpp - decode an uncompressed 1 bit per pixel image
*/
static void
vDecode1bpp(FILE *pFile, UCHAR *pucData, const imagedata_type *pImg)
{
       int     iX, iY, iByteWidth, iOffset, iTmp, iEighthWidth, iPadding;
       UCHAR   ucTmp;

       DBG_MSG("vDecode1bpp");

       fail(pFile == NULL);
       fail(pucData == NULL);
       fail(pImg == NULL);
       fail(pImg->iColorsUsed < 1 || pImg->iColorsUsed > 2);

       iByteWidth = iGetByteWidth(pImg);

       iEighthWidth = (pImg->iWidth + 7) / 8;
       iPadding = ROUND4(iEighthWidth) - iEighthWidth;

       for (iY = pImg->iHeight - 1; iY >= 0; iY--) {
               for (iX = 0; iX < iEighthWidth; iX++) {
                       iTmp = iNextByte(pFile);
                       if (iTmp == EOF) {
                               return;
                       }
                       /* Reverse the bit order */
                       ucTmp  = (iTmp & BIT(0)) ? (UCHAR)BIT(7) : 0;
                       ucTmp |= (iTmp & BIT(1)) ? (UCHAR)BIT(6) : 0;
                       ucTmp |= (iTmp & BIT(2)) ? (UCHAR)BIT(5) : 0;
                       ucTmp |= (iTmp & BIT(3)) ? (UCHAR)BIT(4) : 0;
                       ucTmp |= (iTmp & BIT(4)) ? (UCHAR)BIT(3) : 0;
                       ucTmp |= (iTmp & BIT(5)) ? (UCHAR)BIT(2) : 0;
                       ucTmp |= (iTmp & BIT(6)) ? (UCHAR)BIT(1) : 0;
                       ucTmp |= (iTmp & BIT(7)) ? (UCHAR)BIT(0) : 0;
                       iOffset = iY * iByteWidth + iX;
                       *(pucData + iOffset) = ucTmp;
               }
               (void)tSkipBytes(pFile, iPadding);
       }
} /* end of vDecode1bpp */

/*
* vDecode4bpp - decode an uncompressed 4 bits per pixel image
*/
static void
vDecode4bpp(FILE *pFile, UCHAR *pucData, const imagedata_type *pImg)
{
       int     iX, iY, iByteWidth, iOffset, iTmp, iHalfWidth, iPadding;
       UCHAR   ucTmp;

       DBG_MSG("vDecode4bpp");

       fail(pFile == NULL);
       fail(pucData == NULL);
       fail(pImg == NULL);
       fail(pImg->iColorsUsed < 1 || pImg->iColorsUsed > 16);

       iByteWidth = iGetByteWidth(pImg);

       iHalfWidth = (pImg->iWidth + 1) / 2;
       iPadding = ROUND4(iHalfWidth) - iHalfWidth;

       for (iY = pImg->iHeight - 1; iY >= 0; iY--) {
               for (iX = 0; iX < iHalfWidth; iX++) {
                       iTmp = iNextByte(pFile);
                       if (iTmp == EOF) {
                               return;
                       }
                       /* Reverse the nibble order */
                       ucTmp = (iTmp & 0xf0) >> 4;
                       ucTmp |= (iTmp & 0x0f) << 4;
                       iOffset = iY * iByteWidth + iX;
                       *(pucData + iOffset) = ucTmp;
               }
               (void)tSkipBytes(pFile, iPadding);
       }
} /* end of vDecode4bpp */

/*
* vDecode8bpp - decode an uncompressed 8 bits per pixel image
*/
static void
vDecode8bpp(FILE *pFile, UCHAR *pucData, const imagedata_type *pImg)
{
       int     iX, iY, iByteWidth, iOffset, iIndex, iPadding;

       DBG_MSG("vDecode8bpp");

       fail(pFile == NULL);
       fail(pucData == NULL);
       fail(pImg == NULL);
       fail(pImg->iColorsUsed < 1 || pImg->iColorsUsed > 256);

       iByteWidth = iGetByteWidth(pImg);

       iPadding = ROUND4(pImg->iWidth) - pImg->iWidth;

       for (iY = pImg->iHeight - 1; iY >= 0; iY--) {
               for (iX = 0; iX < pImg->iWidth; iX++) {
                       iIndex = iNextByte(pFile);
                       if (iIndex == EOF) {
                               return;
                       }
                       iOffset = iY * iByteWidth + iX;
                       *(pucData + iOffset) = iReduceColor(
                               pImg->aucPalette[iIndex][0],
                               pImg->aucPalette[iIndex][1],
                               pImg->aucPalette[iIndex][2]);
               }
               (void)tSkipBytes(pFile, iPadding);
       }
} /* end of vDecode8bpp */

/*
* vDecode24bpp - decode an uncompressed 24 bits per pixel image
*/
static void
vDecode24bpp(FILE *pFile, UCHAR *pucData, const imagedata_type *pImg)
{
       int     iX, iY, iTripleWidth, iByteWidth, iOffset, iPadding;
       int     iRed, iGreen, iBlue;

       DBG_MSG("vDecode24bpp");

       fail(pFile == NULL);
       fail(pucData == NULL);
       fail(pImg == NULL);

       iByteWidth = iGetByteWidth(pImg);

       iTripleWidth = pImg->iWidth * 3;
       iPadding = ROUND4(iTripleWidth) - iTripleWidth;

       for (iY = pImg->iHeight - 1; iY >= 0; iY--) {
               for (iX = 0; iX < pImg->iWidth; iX++) {
                       iBlue = iNextByte(pFile);
                       if (iBlue == EOF) {
                               return;
                       }
                       iGreen = iNextByte(pFile);
                       if (iGreen == EOF) {
                               return;
                       }
                       iRed = iNextByte(pFile);
                       if (iRed == EOF) {
                               return;
                       }
                       iOffset = iY * iByteWidth + iX;
                       *(pucData + iOffset) =
                                       iReduceColor(iRed, iGreen, iBlue);
               }
               (void)tSkipBytes(pFile, iPadding);
       }
} /* end of vDecode24bpp */

/*
* vDecodeRle4 - decode a RLE compressed 4 bits per pixel image
*/
static void
vDecodeRle4(FILE *pFile, UCHAR *pucData, const imagedata_type *pImg)
{
       int     iX, iY, iByteWidth, iOffset, iTmp, iHalfWidth;
       int     iRun, iRunLength, iHalfRun;
       BOOL    bEOL;
       UCHAR   ucTmp;

       DBG_MSG("vDecodeRle4");

       fail(pFile == NULL);
       fail(pucData == NULL);
       fail(pImg == NULL);
       fail(pImg->iColorsUsed < 1 || pImg->iColorsUsed > 16);

       DBG_DEC(pImg->iWidth);
       DBG_DEC(pImg->iHeight);

       iByteWidth = iGetByteWidth(pImg);
       iHalfWidth = (pImg->iWidth + 1) / 2;

       for (iY = pImg->iHeight - 1; iY >= 0; iY--) {
               bEOL = FALSE;
               iX = 0;
               while (!bEOL) {
                       iRunLength = iNextByte(pFile);
                       if (iRunLength == EOF) {
                               return;
                       }
                       if (iRunLength != 0) {
                               /*
                                * Encoded packet:
                                * RunLength pixels, all the "same" value
                                */
                               iTmp = iNextByte(pFile);
                               if (iTmp == EOF) {
                                       return;
                               }
                               /* Reverse the nibble order */
                               ucTmp = (iTmp & 0xf0) >> 4;
                               ucTmp |= (iTmp & 0x0f) << 4;
                               iHalfRun = (iRunLength + 1) / 2;
                               for (iRun = 0; iRun < iHalfRun; iRun++) {
                                       if (iX < iHalfWidth) {
                                               iOffset = iY * iByteWidth + iX;
                                               *(pucData + iOffset) = ucTmp;
                                       }
                                       iX++;
                               }
                               continue;
                       }
                       /* Literal or escape */
                       iRunLength = iNextByte(pFile);
                       if (iRunLength == EOF) {
                               return;
                       }
                       if (iRunLength == 0) {          /* End of line escape */
                               bEOL = TRUE;
                       } else if (iRunLength == 1) {   /* End of file escape */
                               return;
                       } else if (iRunLength == 2) {   /* Delta escape */
                               DBG_MSG("RLE4: encountered delta escape");
                               return;
                       } else {                        /* Literal packet */
                               iHalfRun = (iRunLength + 1) / 2;
                               for (iRun = 0; iRun < iHalfRun; iRun++) {
                                       iTmp = iNextByte(pFile);
                                       if (iTmp == EOF) {
                                               return;
                                       }
                                       /* Reverse the nibble order */
                                       ucTmp = (iTmp & 0xf0) >> 4;
                                       ucTmp |= (iTmp & 0x0f) << 4;
                                       if (iX < iHalfWidth) {
                                               iOffset = iY * iByteWidth + iX;
                                               *(pucData + iOffset) = ucTmp;
                                       }
                                       iX++;
                               }
                               /* Padding if the number of bytes is odd */
                               if (odd(iHalfRun)) {
                                       (void)tSkipBytes(pFile, 1);
                               }
                       }
               }
               DBG_DEC_C(iX != iHalfWidth, iX);
       }
} /* end of vDecodeRle4 */

/*
* vDecodeRle8 - decode a RLE compressed 8 bits per pixel image
*/
static void
vDecodeRle8(FILE *pFile, UCHAR *pucData, const imagedata_type *pImg)
{
       int     iX, iY, iRun, iRunLength, iOffset, iIndex, iByteWidth;
       BOOL    bEOL;

       DBG_MSG("vDecodeRle8");

       fail(pFile == NULL);
       fail(pucData == NULL);
       fail(pImg == NULL);
       fail(pImg->iColorsUsed < 1 || pImg->iColorsUsed > 256);

       DBG_DEC(pImg->iWidth);
       DBG_DEC(pImg->iHeight);

       iByteWidth = iGetByteWidth(pImg);

       for (iY = pImg->iHeight - 1; iY >= 0; iY--) {
               bEOL = FALSE;
               iX = 0;
               while (!bEOL) {
                       iRunLength = iNextByte(pFile);
                       if (iRunLength == EOF) {
                               return;
                       }
                       if (iRunLength != 0) {
                               /*
                                * Encoded packet:
                                * RunLength pixels, all the same value
                                */
                               iIndex = iNextByte(pFile);
                               if (iIndex == EOF) {
                                       return;
                               }
                               for (iRun = 0; iRun < iRunLength; iRun++) {
                                       if (iX < pImg->iWidth) {
                                               iOffset = iY * iByteWidth + iX;
                                               *(pucData + iOffset) =
                                                       iReduceColor(
                                                       pImg->aucPalette[iIndex][0],
                                                       pImg->aucPalette[iIndex][1],
                                                       pImg->aucPalette[iIndex][2]);
                                       }
                                       iX++;
                               }
                               continue;
                       }
                       /* Literal or escape */
                       iRunLength = iNextByte(pFile);
                       if (iRunLength == EOF) {
                               return;
                       }
                       if (iRunLength == 0) {          /* End of line escape */
                               bEOL = TRUE;
                       } else if (iRunLength == 1) {   /* End of file escape */
                               return;
                       } else if (iRunLength == 2) {   /* Delta escape */
                               DBG_MSG("RLE8: encountered delta escape");
                               return;
                       } else {                        /* Literal packet */
                               for (iRun = 0; iRun < iRunLength; iRun++) {
                                       iIndex = iNextByte(pFile);
                                       if (iIndex == EOF) {
                                               return;
                                       }
                                       if (iX < pImg->iWidth) {
                                               iOffset = iY * iByteWidth + iX;
                                               *(pucData + iOffset) =
                                                       iReduceColor(
                                                       pImg->aucPalette[iIndex][0],
                                                       pImg->aucPalette[iIndex][1],
                                                       pImg->aucPalette[iIndex][2]);
                                       }
                                       iX++;
                               }
                               /* Padding if the number of bytes is odd */
                               if (odd(iRunLength)) {
                                       (void)tSkipBytes(pFile, 1);
                               }
                       }
               }
               DBG_DEC_C(iX != pImg->iWidth, iX);
       }
} /* end of vDecodeRle8 */

#if 0 /* defined(DEBUG) */
static void
vCopy2File(UCHAR *pucSprite, size_t tSpriteSize)
{
       FILE    *pOutFile;
       int     iIndex;
       char    szFilename[30];

       sprintf(szFilename, "<Wimp$ScrapDir>.sprt%04d", ++iPicCounter);
       pOutFile = fopen(szFilename, "wb");
       if (pOutFile == NULL) {
               return;
       }
       DBG_MSG(szFilename);
       for (iIndex = 4; iIndex < (int)tSpriteSize; iIndex++) {
               if (putc(pucSprite[iIndex], pOutFile) == EOF) {
                       break;
               }
       }
       (void)fclose(pOutFile);
       vSetFiletype(szFilename, FILETYPE_SPRITE);
} /* end of vCopy2File */
#endif /* DEBUG */

/*
* vDecodeDIB - decode a dib picture
*/
static void
vDecodeDIB(diagram_type *pDiag, FILE *pFile, const imagedata_type *pImg)
{
       sprite_areainfo *pSprite;
       UCHAR   *pucPalette, *pucData;
       size_t  tSpriteSize;
       int     iHeaderSize;

       /* Skip the bitmap info header */
       iHeaderSize = (int)ulNextLong(pFile);
       (void)tSkipBytes(pFile, iHeaderSize - 4);
       /* Skip the colortable */
       if (pImg->uiBitsPerComponent <= 8) {
               (void)tSkipBytes(pFile,
                       pImg->iColorsUsed * ((iHeaderSize > 12) ? 4 : 3));
       }

       /* Create an blank sprite */
       pSprite = pCreateBlankSprite(pImg, &tSpriteSize);
       pucPalette = (UCHAR *)pSprite +
                       pSprite->firstoffset + sizeof(sprite_header);

       /* Add the pixel information */
       switch (pImg->uiBitsPerComponent) {
       case  1:
               fail(pImg->eCompression != compression_none);
               pucData = pucPalette + 2 * 8;
               vDecode1bpp(pFile, pucData, pImg);
               break;
       case  4:
               fail(pImg->eCompression != compression_none &&
                               pImg->eCompression != compression_rle4);
               pucData = pucPalette + 16 * 8;
               if (pImg->eCompression == compression_rle4) {
                       vDecodeRle4(pFile, pucData, pImg);
               } else {
                       vDecode4bpp(pFile, pucData, pImg);
               }
               break;
       case  8:
               fail(pImg->eCompression != compression_none &&
                               pImg->eCompression != compression_rle8);
               pucData = pucPalette + 0 * 8;
               if (pImg->eCompression == compression_rle8) {
                       vDecodeRle8(pFile, pucData, pImg);
               } else {
                       vDecode8bpp(pFile, pucData, pImg);
               }
               break;
       case 24:
               fail(pImg->eCompression != compression_none);
               pucData = pucPalette + 0 * 8;
               vDecode24bpp(pFile, pucData, pImg);
               break;
       default:
               DBG_DEC(pImg->uiBitsPerComponent);
               break;
       }

#if 0 /* defined(DEBUG) */
       vCopy2File((UCHAR *)pSprite, tSpriteSize);
#endif /* DEBUG */

       /* Add the sprite to the Draw file */
       vImage2Diagram(pDiag, pImg,
               (UCHAR *)pSprite + pSprite->firstoffset,
               tSpriteSize - pSprite->firstoffset);

       /* Clean up before you leave */
       pSprite = xfree(pSprite);
} /* end of vDecodeDIB */

/*
* bTranslateDIB - translate a DIB picture
*
* This function translates a picture from dib to sprite
*
* return TRUE when sucessful, otherwise FALSE
*/
BOOL
bTranslateDIB(diagram_type *pDiag, FILE *pFile,
       ULONG ulFileOffset, const imagedata_type *pImg)
{
       /* Seek to start position of DIB data */
       if (!bSetDataOffset(pFile, ulFileOffset)) {
               return FALSE;
       }

       vDecodeDIB(pDiag, pFile, pImg);

       return TRUE;
} /* end of bTranslateDIB */