]> git.sur5r.net Git - cc65/blob - src/sp65/lynxsprite.c
Fix shaped bugs
[cc65] / src / sp65 / lynxsprite.c
1 /*****************************************************************************/
2 /*                                                                           */
3 /*                               lynxsprite.c                                */
4 /*                                                                           */
5 /*    Lynx sprite format backend for the sp65 sprite and bitmap utility      */
6 /*                                                                           */
7 /*                                                                           */
8 /*                                                                           */
9 /* (C) 2012,      Ullrich von Bassewitz                                      */
10 /*                Roemerstrasse 52                                           */
11 /*                D-70794 Filderstadt                                        */
12 /* EMail:         uz@cc65.org                                                */
13 /*                                                                           */
14 /*                                                                           */
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.                                    */
18 /*                                                                           */
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:                            */
22 /*                                                                           */
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              */
30 /*    distribution.                                                          */
31 /*                                                                           */
32 /*****************************************************************************/
33
34
35
36 #include <stdlib.h>
37
38 /* common */
39 #include "attrib.h"
40 #include "print.h"
41
42 /* sp65 */
43 #include "attr.h"
44 #include "error.h"
45 #include "lynxsprite.h"
46
47
48
49 /*****************************************************************************/
50 /*                                   Data                                    */
51 /*****************************************************************************/
52
53
54
55 /* Sprite mode */
56 enum Mode {
57     smAuto,
58     smLiteral,
59     smPacked,
60     smShaped
61 };
62
63
64 /*****************************************************************************/
65 /*                                   Code                                    */
66 /*****************************************************************************/
67
68
69
70 static enum Mode GetMode (const Collection* A)
71 /* Return the sprite mode from the attribute collection A */
72 {
73     /* Check for a mode attribute */
74     const char* Mode = GetAttrVal (A, "mode");
75     if (Mode) {
76         if (strcmp (Mode, "literal") == 0) {
77             return smLiteral;
78         } else if (strcmp (Mode, "packed") == 0) {
79             return smPacked;
80         } else if (strcmp (Mode, "shaped") == 0) {
81             return smShaped;
82         } else {
83             Error ("Invalid value for attribute `mode'");
84         }
85     }
86
87     return smAuto;
88 }
89
90
91 static unsigned GetActionPointX (const Collection* A)
92 /* Return the sprite mode from the attribute collection A */
93 {
94     /* Check for a action point x attribute */
95     const char* ActionPointX = GetAttrVal (A, "ax");
96     if (ActionPointX) {
97         return atoi(ActionPointX);
98     } else {
99         return 0;
100     }
101 }
102
103
104 static unsigned GetActionPointY (const Collection* A)
105 /* Return the sprite mode from the attribute collection A */
106 {
107     /* Check for a action point y attribute */
108     const char* ActionPointY = GetAttrVal (A, "ay");
109     if (ActionPointY) {
110         return atoi(ActionPointY);
111     } else {
112         return 0;
113     }
114 }
115
116 static char OutBuffer[512]; /* The maximum size is 508 pixels */
117 static unsigned char OutIndex;
118
119 static void AssembleByte(unsigned bits, char val)
120 {
121     static char bit_counter = 8, byte = 0;
122
123     /* initialize */
124     if (!bits) {
125         OutIndex = 0;
126         bit_counter = 8;
127         byte = 0;
128         return;
129     }
130     /* handle end of line */
131     if (bits == 8) {
132         if (bit_counter != 8) {
133             byte <<= bit_counter;
134             OutBuffer[OutIndex++] = byte;
135             if (!OutIndex) {
136                 Error ("ASprite is too large for the Lynx");
137             }
138             if (byte & 0x1) {
139                 OutBuffer[OutIndex++] = byte;
140                 if (!OutIndex) {
141                     Error ("BSprite is too large for the Lynx");
142                 }
143             }
144         }
145         return;
146     }
147     /* handle end of line for literal */
148     if (bits == 7) {
149         if (bit_counter != 8) {
150             byte <<= bit_counter;
151             OutBuffer[OutIndex++] = byte;
152             if (!OutIndex) {
153                 Error ("ASprite is too large for the Lynx");
154             }
155         }
156         return;
157     }
158     val <<= 8 - bits;
159
160     do {
161         byte <<= 1;
162
163         if (val & 0x80)
164             ++byte;
165
166         if (!(--bit_counter)) {
167             OutBuffer[OutIndex++] = byte;
168             if (!OutIndex) {
169                 Error ("Sprite is too large for the Lynx");
170             }
171             byte = 0;
172             bit_counter = 8;
173         }
174
175         val <<= 1;
176
177     } while (--bits);
178 }
179
180 static unsigned char ChoosePackagingMode(signed len, signed index, char LineBuffer[512])
181 {
182     --len;
183     if (!len) {
184         return 0;
185     }
186     if (LineBuffer[index] != LineBuffer[index + 1]) {
187         return 0;
188     }
189     return 1;
190 }
191
192 static void WriteOutBuffer(StrBuf *D)
193 {
194     signed i;
195
196     /* Fix bug in Lynx where the count cannot be 1 */
197     if (OutIndex == 1) {
198         OutBuffer[OutIndex++] = 0;
199     }
200     /* Write the byte count to the end of the scanline */
201     if (OutIndex == 255) {
202         Error ("Sprite is too large for the Lynx");
203     }
204     SB_AppendChar (D, OutIndex+1);
205     /* Write scanline data */
206     for (i = 0; i < OutIndex; i++) {
207         SB_AppendChar (D, OutBuffer[i]);
208     }
209 }
210
211 static void encodeSprite(StrBuf *D, enum Mode M, char ColorBits, char ColorMask, char LineBuffer[512],
212     int len, int LastOpaquePixel) {
213 /*
214  * The data starts with a byte count. It tells the number of bytes on this
215  * line + 1.
216  * Special case is a count of 1. It will change to next quadrant.
217  * Other special case is 0. It will end the sprite.
218  *
219  * Ordinary data packet. These are bits in a stream.
220  * 1=literal 0=packed
221  * 4 bit count (+1)
222  * for literal you put "count" values
223  * for packed you repeat the value "count" times
224  * Never use packed mode for one pixel
225  * If the last bit on a line is 1 you need to add a byte of zeroes
226  * A sequence 00000 ends a scan line
227  *
228  * All data is high nybble first
229  */
230     unsigned char V = 0;
231     signed i;
232     signed count;
233     unsigned char differ[16];
234     unsigned char *d_ptr;
235
236     AssembleByte(0, 0);
237     switch (M) {
238     case smAuto:
239     case smLiteral:
240         for (i = 0; i < len; i++) {
241             /* Fetch next pixel index into pixel buffer */
242             AssembleByte(ColorBits, LineBuffer[i] & ColorMask);
243         }
244         AssembleByte(7, 0);
245         /* Write the buffer to file */
246         WriteOutBuffer(D);
247         break;
248     case smPacked:
249         i = 0;
250         while (len) {
251             if (ChoosePackagingMode(len, i, LineBuffer)) {
252                 /* Make runlength packet */
253                 V = LineBuffer[i];
254                 ++i;
255                 --len;
256                 count = 0;
257                 do {
258                     ++count;
259                     ++i;
260                     --len;
261                 } while (V == LineBuffer[i] && len && count != 15);
262
263                 AssembleByte(5, count);
264                 AssembleByte(ColorBits, V);
265
266             } else {
267                 /* Make packed literal packet */
268                 d_ptr = differ;
269                 V = LineBuffer[i++];
270                 *d_ptr++ = V;
271                 --len;
272                 count = 0;
273                 while (ChoosePackagingMode(len, i, LineBuffer) == 0 && len && count != 15) {
274                     V = LineBuffer[i++];
275                     *d_ptr++ = V;
276                     ++count;
277                     --len;
278                 }
279
280                 AssembleByte(5, count | 0x10);
281                 d_ptr = differ;
282                 do {
283                     AssembleByte(ColorBits, *d_ptr++);
284                 } while (--count >= 0);
285
286             }
287         }
288         AssembleByte(8, 0);
289         /* Write the buffer to file */
290         WriteOutBuffer(D);
291         break;
292
293     case smShaped:
294         if (LastOpaquePixel > -1) {
295             if (LastOpaquePixel < len - 1) {
296                 len = LastOpaquePixel + 1;
297             }
298             i = 0;
299             while (len) {
300                 if (ChoosePackagingMode(len, i, LineBuffer)) {
301                     /* Make runlength packet */
302                     V = LineBuffer[i];
303                     ++i;
304                     --len;
305                     count = 0;
306                     do {
307                         ++count;
308                         ++i;
309                         --len;
310                     } while (V == LineBuffer[i] && len && count != 15);
311
312                     AssembleByte(5, count);
313                     AssembleByte(ColorBits, V);
314
315                 } else {
316                     /* Make packed literal packet */
317                     d_ptr = differ;
318                     V = LineBuffer[i++];
319                     *d_ptr++ = V;
320                     --len;
321                     count = 0;
322                     while (ChoosePackagingMode(len, i, LineBuffer) == 0 && len && count != 15) {
323                         V = LineBuffer[i++];
324                         *d_ptr++ = V;
325                         ++count;
326                         --len;
327                     }
328
329                     AssembleByte(5, count | 0x10);
330                     d_ptr = differ;
331                     do {
332                         AssembleByte(ColorBits, *d_ptr++);
333                     } while (--count >= 0);
334
335                 }
336             }
337             AssembleByte(5, 0);
338             AssembleByte(8, 0);
339             /* Write the buffer to file */
340             WriteOutBuffer(D);
341         }
342         break;
343     }
344 }
345
346 StrBuf* GenLynxSprite (const Bitmap* B, const Collection* A)
347 /* Generate binary output in Lynx sprite format for the bitmap B. The output
348  * is stored in a string buffer (which is actually a dynamic char array) and
349  * returned.
350  *
351  * The Lynx will draw 4 quadrants:
352  * - Down right
353  * - Up right
354  * - Up left
355  * - Down left
356  *
357  * The sprite will end with a byte 0.
358  */
359 {
360     enum Mode M;
361     StrBuf* D;
362     signed X, Y;
363     unsigned OX, OY;
364     char ColorBits;
365     char ColorMask;
366
367     /* Action point of the sprite */
368     OX = GetActionPointX (A);
369     OY = GetActionPointY (A);
370     if (OX >= GetBitmapWidth (B)) {
371         Error ("Action point X cannot be larger than bitmap width");
372     }
373     if (OY >= GetBitmapHeight (B)) {
374         Error ("Action point Y cannot be larger than bitmap height");
375     }
376
377     /* Output the image properties */
378     Print (stdout, 1, "Image is %ux%u with %u colors%s\n",
379            GetBitmapWidth (B), GetBitmapHeight (B), GetBitmapColors (B),
380            BitmapIsIndexed (B)? " (indexed)" : "");
381
382     /* Get the sprite mode */
383     M = GetMode (A);
384
385     /* Now check if bitmap indexes are ok */
386     if (GetBitmapColors (B) > 16) {
387         Error ("Too many colors for a Lynx sprite");
388     }
389     ColorBits = 4;
390     ColorMask = 0x0f;
391     if (GetBitmapColors (B) < 9) {
392         ColorBits = 3;
393         ColorMask = 0x07;
394     }
395     if (GetBitmapColors (B) < 5) {
396         ColorBits = 2;
397         ColorMask = 0x03;
398     }
399     if (GetBitmapColors (B) < 3) {
400         ColorBits = 1;
401         ColorMask = 0x01;
402     }
403
404     /* Create the output buffer and resize it to the required size. */
405     D = NewStrBuf ();
406     SB_Realloc (D, 63);
407
408     /* Convert the image for quadrant bottom right */
409     for (Y = OY; Y < (signed)GetBitmapHeight (B); ++Y) {
410         signed i = 0;
411         signed LastOpaquePixel = -1;
412         char LineBuffer[512]; /* The maximum size is 508 pixels */
413
414         /* Fill the LineBuffer for easier optimisation */
415         for (X = OX; X < (signed)GetBitmapWidth (B); ++X) {
416
417             /* Fetch next bit into byte buffer */
418             LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
419
420             if (LineBuffer[i]) {
421                 LastOpaquePixel = i;
422             }
423             ++i;
424         }
425
426         encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
427     }
428
429     if ((OY == 0) && (OX == 0)) {
430         /* Trivial case only one quadrant */
431
432         /* Mark end of sprite */
433         SB_AppendChar (D, 0);
434
435         /* Return the converted bitmap */
436         return D;
437     }
438
439     /* Next quadrant */
440     SB_AppendChar (D, 1);
441
442     /* Convert the image for quadrant top right */
443     for (Y = OY - 1; Y >= 0; --Y) {
444         signed i = 0;
445         signed LastOpaquePixel = -1;
446         char LineBuffer[512]; /* The maximum size is 508 pixels */
447
448         /* Fill the LineBuffer for easier optimisation */
449         for (X = OX; X < (signed)GetBitmapWidth (B); ++X) {
450
451             /* Fetch next bit into byte buffer */
452             LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
453
454             if (LineBuffer[i]) {
455                 LastOpaquePixel = i;
456             }
457             ++i;
458         }
459
460         encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
461     }
462
463     if (OX == 0) {
464         /* Special case only two quadrants */
465
466         /* Mark end of sprite */
467         SB_AppendChar (D, 0);
468
469         /* Return the converted bitmap */
470         return D;
471     }
472
473     /* Next quadrant */
474     SB_AppendChar (D, 1);
475
476     /* Convert the image for quadrant top left */
477     for (Y = OY - 1; Y >= 0; --Y) {
478         signed i = 0;
479         signed LastOpaquePixel = -1;
480         char LineBuffer[512]; /* The maximum size is 508 pixels */
481
482         /* Fill the LineBuffer for easier optimisation */
483         for (X = OX - 1; X >= 0; --X) {
484
485             /* Fetch next bit into byte buffer */
486             LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
487
488             if (LineBuffer[i]) {
489                 LastOpaquePixel = i;
490             }
491             ++i;
492         }
493
494         encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
495     }
496
497     /* Next quadrant */
498     SB_AppendChar (D, 1);
499
500     /* Convert the image for quadrant bottom left */
501     for (Y = OY; Y < (signed)GetBitmapHeight (B); ++Y) {
502         signed i = 0;
503         signed LastOpaquePixel = -1;
504         char LineBuffer[512]; /* The maximum size is 508 pixels */
505
506         /* Fill the LineBuffer for easier optimisation */
507         for (X = OX - 1; X >= 0; --X) {
508
509             /* Fetch next bit into byte buffer */
510             LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
511
512             if (LineBuffer[i]) {
513                 LastOpaquePixel = i;
514             }
515             ++i;
516         }
517
518         encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
519     }
520
521     /* End sprite */
522     SB_AppendChar (D, 0);
523
524     /* Return the converted bitmap */
525     return D;
526 }
527
528
529