/*****************************************************************************/ /* */ /* lynxsprite.c */ /* */ /* Lynx sprite format backend for the sp65 sprite and bitmap utility */ /* */ /* */ /* */ /* (C) 2012, Ullrich von Bassewitz */ /* Roemerstrasse 52 */ /* D-70794 Filderstadt */ /* EMail: uz@cc65.org */ /* */ /* */ /* This software is provided 'as-is', without any expressed or implied */ /* warranty. In no event will the authors be held liable for any damages */ /* arising from the use of this software. */ /* */ /* Permission is granted to anyone to use this software for any purpose, */ /* including commercial applications, and to alter it and redistribute it */ /* freely, subject to the following restrictions: */ /* */ /* 1. The origin of this software must not be misrepresented; you must not */ /* claim that you wrote the original software. If you use this software */ /* in a product, an acknowledgment in the product documentation would be */ /* appreciated but is not required. */ /* 2. Altered source versions must be plainly marked as such, and must not */ /* be misrepresented as being the original software. */ /* 3. This notice may not be removed or altered from any source */ /* distribution. */ /* */ /*****************************************************************************/ #include /* common */ #include "attrib.h" #include "print.h" /* sp65 */ #include "attr.h" #include "error.h" #include "lynxsprite.h" /*****************************************************************************/ /* Data */ /*****************************************************************************/ /* Sprite mode */ enum Mode { smAuto, smLiteral, smPacked, smShaped }; /*****************************************************************************/ /* Code */ /*****************************************************************************/ static enum Mode GetMode (const Collection* A) /* Return the sprite mode from the attribute collection A */ { /* Check for a mode attribute */ const char* Mode = GetAttrVal (A, "mode"); if (Mode) { if (strcmp (Mode, "literal") == 0) { return smLiteral; } else if (strcmp (Mode, "packed") == 0) { return smPacked; } else if (strcmp (Mode, "shaped") == 0) { return smShaped; } else { Error ("Invalid value for attribute `mode'"); } } return smAuto; } static unsigned GetActionPointX (const Collection* A) /* Return the sprite mode from the attribute collection A */ { /* Check for a action point x attribute */ const char* ActionPointX = GetAttrVal (A, "ax"); if (ActionPointX) { return atoi(ActionPointX); } else { return 0; } } static unsigned GetActionPointY (const Collection* A) /* Return the sprite mode from the attribute collection A */ { /* Check for a action point y attribute */ const char* ActionPointY = GetAttrVal (A, "ay"); if (ActionPointY) { return atoi(ActionPointY); } else { return 0; } } static char OutBuffer[512]; /* The maximum size is 508 pixels */ static unsigned char OutIndex; static void AssembleByte(unsigned bits, char val) { static char bit_counter = 8, byte = 0; /* initialize */ if (!bits) { OutIndex = 0; bit_counter = 8; byte = 0; return; } /* handle end of line */ if (bits == 8) { if (bit_counter != 8) { byte <<= bit_counter; OutBuffer[OutIndex++] = byte; if (!OutIndex) { Error ("Sprite is too large for the Lynx"); } if (byte & 0x1) { OutBuffer[OutIndex++] = byte; if (!OutIndex) { Error ("Sprite is too large for the Lynx"); } } } return; } val <<= 8 - bits; do { byte <<= 1; if (val & 0x80) ++byte; if (!(--bit_counter)) { OutBuffer[OutIndex++] = byte; if (!OutIndex) { Error ("Sprite is too large for the Lynx"); } byte = 0; bit_counter = 8; } val <<= 1; } while (--bits); } static void AssembleByteLiteral(unsigned bits, char val) { static char bit_counter = 8, byte = 0; /* initialize */ if (!bits) { OutIndex = 0; bit_counter = 8; byte = 0; return; } /* handle end of line */ if (bits == 8) { if (bit_counter != 8) { byte <<= bit_counter; OutBuffer[OutIndex++] = byte; if (!OutIndex) { Error ("Sprite is too large for the Lynx"); } } return; } val <<= 8 - bits; do { byte <<= 1; if (val & 0x80) ++byte; if (!(--bit_counter)) { OutBuffer[OutIndex++] = byte; if (!OutIndex) { Error ("Sprite is too large for the Lynx"); } byte = 0; bit_counter = 8; } val <<= 1; } while (--bits); } static unsigned char ChoosePackagingMode(signed len, signed index, char LineBuffer[512]) { --len; if (!len) { return 0; } if (LineBuffer[index] != LineBuffer[index + 1]) { return 0; } return 1; } static void WriteOutBuffer(StrBuf *D) { signed i; /* Fix bug in Lynx where the count cannot be 1 */ if (OutIndex == 1) { OutBuffer[OutIndex++] = 0; } /* Write the byte count to the end of the scanline */ if (OutIndex == 255) { Error ("Sprite is too large for the Lynx"); } SB_AppendChar (D, OutIndex+1); /* Write scanline data */ for (i = 0; i < OutIndex; i++) { SB_AppendChar (D, OutBuffer[i]); } } static void encodeSprite(StrBuf *D, enum Mode M, char ColorBits, char ColorMask, char LineBuffer[512], int len, int LastOpaquePixel) { /* * The data starts with a byte count. It tells the number of bytes on this * line + 1. * Special case is a count of 1. It will change to next quadrant. * Other special case is 0. It will end the sprite. * * Ordinary data packet. These are bits in a stream. * 1=literal 0=packed * 4 bit count (+1) * for literal you put "count" values * for packed you repeat the value "count" times * Never use packed mode for one pixel * If the last bit on a line is 1 you need to add a byte of zeroes * A sequence 00000 ends a scan line * * All data is high nybble first */ unsigned char V = 0; signed i; signed count; unsigned char differ[16]; unsigned char *d_ptr; switch (M) { case smAuto: case smLiteral: AssembleByteLiteral(0, 0); for (i = 0; i < len; i++) { /* Fetch next pixel index into pixel buffer */ AssembleByteLiteral(ColorBits, LineBuffer[i] & ColorMask); } AssembleByteLiteral(8, 0); /* Write the buffer to file */ WriteOutBuffer(D); break; case smPacked: AssembleByte(0, 0); i = 0; while (len) { if (ChoosePackagingMode(len, i, LineBuffer)) { /* Make runlength packet */ V = LineBuffer[i]; ++i; --len; count = 0; do { ++count; ++i; --len; } while (V == LineBuffer[i] && len && count != 15); AssembleByte(5, count); AssembleByte(ColorBits, V); } else { /* Make packed literal packet */ d_ptr = differ; V = LineBuffer[i++]; *d_ptr++ = V; --len; count = 0; while (ChoosePackagingMode(len, i, LineBuffer) == 0 && len && count != 15) { V = LineBuffer[i++]; *d_ptr++ = V; ++count; --len; } AssembleByte(5, count | 0x10); d_ptr = differ; do { AssembleByte(ColorBits, *d_ptr++); } while (--count >= 0); } } AssembleByte(8, 0); /* Write the buffer to file */ WriteOutBuffer(D); break; case smShaped: break; } } StrBuf* GenLynxSprite (const Bitmap* B, const Collection* A) /* Generate binary output in Lynx sprite format for the bitmap B. The output * is stored in a string buffer (which is actually a dynamic char array) and * returned. * * The Lynx will draw 4 quadrants: * - Down right * - Up right * - Up left * - Down left * * The sprite will end with a byte 0. */ { enum Mode M; StrBuf* D; signed X, Y; unsigned OX, OY; char ColorBits; char ColorMask; /* Action point of the sprite */ OX = GetActionPointX (A); OY = GetActionPointY (A); if (OX >= GetBitmapWidth (B)) { Error ("Action point X cannot be larger than bitmap width"); } if (OY >= GetBitmapHeight (B)) { Error ("Action point Y cannot be larger than bitmap height"); } /* Output the image properties */ Print (stdout, 1, "Image is %ux%u with %u colors%s\n", GetBitmapWidth (B), GetBitmapHeight (B), GetBitmapColors (B), BitmapIsIndexed (B)? " (indexed)" : ""); /* Get the sprite mode */ M = GetMode (A); /* Now check if bitmap indexes are ok */ if (GetBitmapColors (B) > 16) { Error ("Too many colors for a Lynx sprite"); } ColorBits = 4; ColorMask = 0x0f; if (GetBitmapColors (B) < 9) { ColorBits = 3; ColorMask = 0x07; } if (GetBitmapColors (B) < 5) { ColorBits = 2; ColorMask = 0x03; } if (GetBitmapColors (B) < 3) { ColorBits = 1; ColorMask = 0x01; } /* Create the output buffer and resize it to the required size. */ D = NewStrBuf (); SB_Realloc (D, 63); /* Convert the image for quadrant bottom right */ for (Y = OY; Y < (signed)GetBitmapHeight (B); ++Y) { signed i = 0; signed LastOpaquePixel = -1; char LineBuffer[512]; /* The maximum size is 508 pixels */ /* Fill the LineBuffer for easier optimisation */ for (X = OX; X < (signed)GetBitmapWidth (B); ++X) { /* Fetch next bit into byte buffer */ LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask; if (LineBuffer[i]) { LastOpaquePixel = i; } ++i; } encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel); } if ((OY == 0) && (OX == 0)) { /* Trivial case only one quadrant */ /* Mark end of sprite */ SB_AppendChar (D, 0); /* Return the converted bitmap */ return D; } /* Next quadrant */ SB_AppendChar (D, 1); /* Convert the image for quadrant top right */ for (Y = OY - 1; Y >= 0; --Y) { signed i = 0; signed LastOpaquePixel = -1; char LineBuffer[512]; /* The maximum size is 508 pixels */ /* Fill the LineBuffer for easier optimisation */ for (X = OX; X < (signed)GetBitmapWidth (B); ++X) { /* Fetch next bit into byte buffer */ LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask; if (LineBuffer[i]) { LastOpaquePixel = i; } ++i; } encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel); } if (OX == 0) { /* Special case only two quadrants */ /* Mark end of sprite */ SB_AppendChar (D, 0); /* Return the converted bitmap */ return D; } /* Next quadrant */ SB_AppendChar (D, 1); /* Convert the image for quadrant top left */ for (Y = OY - 1; Y >= 0; --Y) { signed i = 0; signed LastOpaquePixel = -1; char LineBuffer[512]; /* The maximum size is 508 pixels */ /* Fill the LineBuffer for easier optimisation */ for (X = OX - 1; X >= 0; --X) { /* Fetch next bit into byte buffer */ LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask; if (LineBuffer[i]) { LastOpaquePixel = i; } ++i; } encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel); } /* Next quadrant */ SB_AppendChar (D, 1); /* Convert the image for quadrant bottom left */ for (Y = OY; Y < (signed)GetBitmapHeight (B); ++Y) { signed i = 0; signed LastOpaquePixel = -1; char LineBuffer[512]; /* The maximum size is 508 pixels */ /* Fill the LineBuffer for easier optimisation */ for (X = OX - 1; X >= 0; --X) { /* Fetch next bit into byte buffer */ LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask; if (LineBuffer[i]) { LastOpaquePixel = i; } ++i; } encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel); } /* End sprite */ SB_AppendChar (D, 0); /* Return the converted bitmap */ return D; }