1 /*****************************************************************************/
5 /* Lynx sprite format backend for the sp65 sprite and bitmap utility */
9 /* (C) 2012, Ullrich von Bassewitz */
10 /* Roemerstrasse 52 */
11 /* D-70794 Filderstadt */
12 /* EMail: uz@cc65.org */
15 /* This software is provided 'as-is', without any expressed or implied */
16 /* warranty. In no event will the authors be held liable for any damages */
17 /* arising from the use of this software. */
19 /* Permission is granted to anyone to use this software for any purpose, */
20 /* including commercial applications, and to alter it and redistribute it */
21 /* freely, subject to the following restrictions: */
23 /* 1. The origin of this software must not be misrepresented; you must not */
24 /* claim that you wrote the original software. If you use this software */
25 /* in a product, an acknowledgment in the product documentation would be */
26 /* appreciated but is not required. */
27 /* 2. Altered source versions must be plainly marked as such, and must not */
28 /* be misrepresented as being the original software. */
29 /* 3. This notice may not be removed or altered from any source */
32 /*****************************************************************************/
45 #include "lynxsprite.h"
49 /*****************************************************************************/
51 /*****************************************************************************/
64 /*****************************************************************************/
66 /*****************************************************************************/
70 static enum Mode GetMode (const Collection* A)
71 /* Return the sprite mode from the attribute collection A */
73 /* Check for a mode attribute */
74 const char* Mode = GetAttrVal (A, "mode");
76 if (strcmp (Mode, "literal") == 0) {
78 } else if (strcmp (Mode, "packed") == 0) {
80 } else if (strcmp (Mode, "shaped") == 0) {
83 Error ("Invalid value for attribute `mode'");
91 static unsigned GetActionPointX (const Collection* A)
92 /* Return the sprite mode from the attribute collection A */
94 /* Check for a action point x attribute */
95 const char* ActionPointX = GetAttrVal (A, "ax");
97 return atoi(ActionPointX);
104 static unsigned GetActionPointY (const Collection* A)
105 /* Return the sprite mode from the attribute collection A */
107 /* Check for a action point y attribute */
108 const char* ActionPointY = GetAttrVal (A, "ay");
110 return atoi(ActionPointY);
116 static unsigned GetEdgeIndex (const Collection* A)
117 /* Return the sprite mode from the attribute collection A */
119 /* Get index for edge color in shaped mode */
120 const char* EdgeIndex = GetAttrVal (A, "edge");
122 return atoi(EdgeIndex);
128 static char OutBuffer[512]; /* The maximum size is 508 pixels */
129 static unsigned char OutIndex;
131 static void AssembleByte(unsigned bits, char val)
133 static char bit_counter = 8, byte = 0;
142 /* handle end of line */
144 if (bit_counter != 8) {
145 byte <<= bit_counter;
146 OutBuffer[OutIndex++] = byte;
148 Error ("Sprite is too large for the Lynx");
151 OutBuffer[OutIndex++] = byte;
153 Error ("Sprite is too large for the Lynx");
159 /* handle end of line for literal */
161 if (bit_counter != 8) {
162 byte <<= bit_counter;
163 OutBuffer[OutIndex++] = byte;
165 Error ("Sprite is too large for the Lynx");
178 if (!(--bit_counter)) {
179 OutBuffer[OutIndex++] = byte;
181 Error ("Sprite is too large for the Lynx");
192 static unsigned char ChoosePackagingMode(signed len, signed index, char ColorBits, char LineBuffer[512])
198 if (LineBuffer[index] != LineBuffer[index + 1]) {
204 if (LineBuffer[index] != LineBuffer[index + 2]) {
210 if (LineBuffer[index] != LineBuffer[index + 3]) {
216 static void WriteOutBuffer(StrBuf *D)
220 /* Fix bug in Lynx where the count cannot be 1 */
222 OutBuffer[OutIndex++] = 0;
224 /* Write the byte count to the end of the scanline */
225 if (OutIndex == 255) {
226 Error ("Sprite is too large for the Lynx");
228 SB_AppendChar (D, OutIndex+1);
229 /* Write scanline data */
230 for (i = 0; i < OutIndex; i++) {
231 SB_AppendChar (D, OutBuffer[i]);
235 static void encodeSprite(StrBuf *D, enum Mode M, char ColorBits, char ColorMask, char LineBuffer[512],
236 int len, int LastOpaquePixel) {
238 ** The data starts with a byte count. It tells the number of bytes on this
240 ** Special case is a count of 1. It will change to next quadrant.
241 ** Other special case is 0. It will end the sprite.
243 ** Ordinary data packet. These are bits in a stream.
244 ** 1=literal 0=packed
246 ** for literal you put "count" values
247 ** for packed you repeat the value "count" times
248 ** Never use packed mode for one pixel
249 ** If the last bit on a line is 1 you need to add a byte of zeroes
250 ** A sequence 00000 ends a scan line
252 ** All data is high nybble first
257 unsigned char differ[16];
258 unsigned char *d_ptr;
264 for (i = 0; i < len; i++) {
265 /* Fetch next pixel index into pixel buffer */
266 AssembleByte(ColorBits, LineBuffer[i] & ColorMask);
269 /* Write the buffer to file */
275 if (ChoosePackagingMode(len, i, ColorBits, LineBuffer)) {
276 /* Make runlength packet */
285 } while (V == LineBuffer[i] && len && count != 15);
287 AssembleByte(5, count);
288 AssembleByte(ColorBits, V);
291 /* Make packed literal packet */
297 while (ChoosePackagingMode(len, i, ColorBits, LineBuffer) == 0 && len && count != 15) {
304 AssembleByte(5, count | 0x10);
307 AssembleByte(ColorBits, *d_ptr++);
308 } while (--count >= 0);
313 /* Write the buffer to file */
318 if (LastOpaquePixel > -1) {
319 if (LastOpaquePixel < len - 1) {
320 len = LastOpaquePixel + 1;
324 if (ChoosePackagingMode(len, i, ColorBits, LineBuffer)) {
325 /* Make runlength packet */
334 } while (V == LineBuffer[i] && len && count != 15);
336 AssembleByte(5, count);
337 AssembleByte(ColorBits, V);
340 /* Make packed literal packet */
346 while (ChoosePackagingMode(len, i, ColorBits, LineBuffer) == 0 && len && count != 15) {
353 AssembleByte(5, count | 0x10);
356 AssembleByte(ColorBits, *d_ptr++);
357 } while (--count >= 0);
363 /* Write the buffer to file */
370 StrBuf* GenLynxSprite (const Bitmap* B, const Collection* A)
371 /* Generate binary output in Lynx sprite format for the bitmap B. The output
372 ** is stored in a string buffer (which is actually a dynamic char array) and
375 ** The Lynx will draw 4 quadrants:
381 ** The sprite will end with a byte 0.
393 EdgeIndex = GetEdgeIndex (A);
395 /* Action point of the sprite */
396 OX = GetActionPointX (A);
397 OY = GetActionPointY (A);
398 if (OX >= GetBitmapWidth (B)) {
399 Error ("Action point X cannot be larger than bitmap width");
401 if (OY >= GetBitmapHeight (B)) {
402 Error ("Action point Y cannot be larger than bitmap height");
405 /* Output the image properties */
406 Print (stdout, 1, "Image is %ux%u with %u colors%s\n",
407 GetBitmapWidth (B), GetBitmapHeight (B), GetBitmapColors (B),
408 BitmapIsIndexed (B)? " (indexed)" : "");
410 /* Get the sprite mode */
413 /* Now check if bitmap indexes are ok */
414 if (GetBitmapColors (B) > 16) {
415 Error ("Too many colors for a Lynx sprite");
419 if (GetBitmapColors (B) < 9) {
423 if (GetBitmapColors (B) < 5) {
427 if (GetBitmapColors (B) < 3) {
432 /* Create the output buffer and resize it to the required size. */
436 /* Convert the image for quadrant bottom right */
437 for (Y = OY; Y < (signed)GetBitmapHeight (B); ++Y) {
439 signed LastOpaquePixel = -1;
440 char LineBuffer[512]; /* The maximum size is 508 pixels */
442 /* Fill the LineBuffer for easier optimisation */
443 for (X = OX; X < (signed)GetBitmapWidth (B); ++X) {
445 /* Fetch next bit into byte buffer */
446 LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
448 if (LineBuffer[i] != EdgeIndex) {
454 encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
457 if ((OY == 0) && (OX == 0)) {
458 /* Trivial case only one quadrant */
460 /* Mark end of sprite */
461 SB_AppendChar (D, 0);
463 /* Return the converted bitmap */
468 SB_AppendChar (D, 1);
470 /* Convert the image for quadrant top right */
471 for (Y = OY - 1; Y >= 0; --Y) {
473 signed LastOpaquePixel = -1;
474 char LineBuffer[512]; /* The maximum size is 508 pixels */
476 /* Fill the LineBuffer for easier optimisation */
477 for (X = OX; X < (signed)GetBitmapWidth (B); ++X) {
479 /* Fetch next bit into byte buffer */
480 LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
482 if (LineBuffer[i] != EdgeIndex) {
488 encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
492 /* Special case only two quadrants */
494 /* Mark end of sprite */
495 SB_AppendChar (D, 0);
497 /* Return the converted bitmap */
502 SB_AppendChar (D, 1);
504 /* Convert the image for quadrant top left */
505 for (Y = OY - 1; Y >= 0; --Y) {
507 signed LastOpaquePixel = -1;
508 char LineBuffer[512]; /* The maximum size is 508 pixels */
510 /* Fill the LineBuffer for easier optimisation */
511 for (X = OX - 1; X >= 0; --X) {
513 /* Fetch next bit into byte buffer */
514 LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
516 if (LineBuffer[i] != EdgeIndex) {
522 encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
526 SB_AppendChar (D, 1);
528 /* Convert the image for quadrant bottom left */
529 for (Y = OY; Y < (signed)GetBitmapHeight (B); ++Y) {
531 signed LastOpaquePixel = -1;
532 char LineBuffer[512]; /* The maximum size is 508 pixels */
534 /* Fill the LineBuffer for easier optimisation */
535 for (X = OX - 1; X >= 0; --X) {
537 /* Fetch next bit into byte buffer */
538 LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
540 if (LineBuffer[i] != EdgeIndex) {
546 encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
550 SB_AppendChar (D, 0);
552 /* Return the converted bitmap */