]> git.sur5r.net Git - cc65/blob - src/sp65/lynxsprite.c
Merge remote-tracking branch 'upstream/master' into a5200
[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 unsigned GetEdgeIndex (const Collection* A)
117 /* Return the sprite mode from the attribute collection A */
118 {
119     /* Get index for edge color in shaped mode */
120     const char* EdgeIndex = GetAttrVal (A, "edge");
121     if (EdgeIndex) {
122         return atoi(EdgeIndex);
123     } else {
124         return 0;
125     }
126 }
127
128 static char OutBuffer[512]; /* The maximum size is 508 pixels */
129 static unsigned char OutIndex;
130
131 static void AssembleByte(unsigned bits, char val)
132 {
133     static char bit_counter = 8, byte = 0;
134
135     /* initialize */
136     if (!bits) {
137         OutIndex = 0;
138         bit_counter = 8;
139         byte = 0;
140         return;
141     }
142     /* handle end of line */
143     if (bits == 8) {
144         if (bit_counter != 8) {
145             byte <<= bit_counter;
146             OutBuffer[OutIndex++] = byte;
147             if (!OutIndex) {
148                 Error ("Sprite is too large for the Lynx");
149             }
150             if (byte & 0x1) {
151                 OutBuffer[OutIndex++] = byte;
152                 if (!OutIndex) {
153                     Error ("Sprite is too large for the Lynx");
154                 }
155             }
156         }
157         return;
158     }
159     /* handle end of line for literal */
160     if (bits == 7) {
161         if (bit_counter != 8) {
162             byte <<= bit_counter;
163             OutBuffer[OutIndex++] = byte;
164             if (!OutIndex) {
165                 Error ("Sprite is too large for the Lynx");
166             }
167         }
168         return;
169     }
170     val <<= 8 - bits;
171
172     do {
173         byte <<= 1;
174
175         if (val & 0x80)
176             ++byte;
177
178         if (!(--bit_counter)) {
179             OutBuffer[OutIndex++] = byte;
180             if (!OutIndex) {
181                 Error ("Sprite is too large for the Lynx");
182             }
183             byte = 0;
184             bit_counter = 8;
185         }
186
187         val <<= 1;
188
189     } while (--bits);
190 }
191
192 static unsigned char ChoosePackagingMode(signed len, signed index, char ColorBits, char LineBuffer[512])
193 {
194     --len;
195     if (!len) {
196         return 0;
197     }
198     if (LineBuffer[index] != LineBuffer[index + 1]) {
199         return 0;
200     }
201     if (ColorBits > 2) {
202         return 1;
203     }
204     if (LineBuffer[index] != LineBuffer[index + 2]) {
205         return 0;
206     }
207     if (ColorBits > 1) {
208         return 1;
209     }
210     if (LineBuffer[index] != LineBuffer[index + 3]) {
211         return 0;
212     }
213     return 1;
214 }
215
216 static void WriteOutBuffer(StrBuf *D)
217 {
218     signed i;
219
220     /* Fix bug in Lynx where the count cannot be 1 */
221     if (OutIndex == 1) {
222         OutBuffer[OutIndex++] = 0;
223     }
224     /* Write the byte count to the end of the scanline */
225     if (OutIndex == 255) {
226         Error ("Sprite is too large for the Lynx");
227     }
228     SB_AppendChar (D, OutIndex+1);
229     /* Write scanline data */
230     for (i = 0; i < OutIndex; i++) {
231         SB_AppendChar (D, OutBuffer[i]);
232     }
233 }
234
235 static void encodeSprite(StrBuf *D, enum Mode M, char ColorBits, char ColorMask, char LineBuffer[512],
236     int len, int LastOpaquePixel) {
237 /*
238  * The data starts with a byte count. It tells the number of bytes on this
239  * line + 1.
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.
242  *
243  * Ordinary data packet. These are bits in a stream.
244  * 1=literal 0=packed
245  * 4 bit count (+1)
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
251  *
252  * All data is high nybble first
253  */
254     unsigned char V = 0;
255     signed i;
256     signed count;
257     unsigned char differ[16];
258     unsigned char *d_ptr;
259
260     AssembleByte(0, 0);
261     switch (M) {
262     case smAuto:
263     case smLiteral:
264         for (i = 0; i < len; i++) {
265             /* Fetch next pixel index into pixel buffer */
266             AssembleByte(ColorBits, LineBuffer[i] & ColorMask);
267         }
268         AssembleByte(7, 0);
269         /* Write the buffer to file */
270         WriteOutBuffer(D);
271         break;
272     case smPacked:
273         i = 0;
274         while (len) {
275             if (ChoosePackagingMode(len, i, ColorBits, LineBuffer)) {
276                 /* Make runlength packet */
277                 V = LineBuffer[i];
278                 ++i;
279                 --len;
280                 count = 0;
281                 do {
282                     ++count;
283                     ++i;
284                     --len;
285                 } while (V == LineBuffer[i] && len && count != 15);
286
287                 AssembleByte(5, count);
288                 AssembleByte(ColorBits, V);
289
290             } else {
291                 /* Make packed literal packet */
292                 d_ptr = differ;
293                 V = LineBuffer[i++];
294                 *d_ptr++ = V;
295                 --len;
296                 count = 0;
297                 while (ChoosePackagingMode(len, i, ColorBits, LineBuffer) == 0 && len && count != 15) {
298                     V = LineBuffer[i++];
299                     *d_ptr++ = V;
300                     ++count;
301                     --len;
302                 }
303
304                 AssembleByte(5, count | 0x10);
305                 d_ptr = differ;
306                 do {
307                     AssembleByte(ColorBits, *d_ptr++);
308                 } while (--count >= 0);
309
310             }
311         }
312         AssembleByte(8, 0);
313         /* Write the buffer to file */
314         WriteOutBuffer(D);
315         break;
316
317     case smShaped:
318         if (LastOpaquePixel > -1) {
319             if (LastOpaquePixel < len - 1) {
320                 len = LastOpaquePixel + 1;
321             }
322             i = 0;
323             while (len) {
324                 if (ChoosePackagingMode(len, i, ColorBits, LineBuffer)) {
325                     /* Make runlength packet */
326                     V = LineBuffer[i];
327                     ++i;
328                     --len;
329                     count = 0;
330                     do {
331                         ++count;
332                         ++i;
333                         --len;
334                     } while (V == LineBuffer[i] && len && count != 15);
335
336                     AssembleByte(5, count);
337                     AssembleByte(ColorBits, V);
338
339                 } else {
340                     /* Make packed literal packet */
341                     d_ptr = differ;
342                     V = LineBuffer[i++];
343                     *d_ptr++ = V;
344                     --len;
345                     count = 0;
346                     while (ChoosePackagingMode(len, i, ColorBits, LineBuffer) == 0 && len && count != 15) {
347                         V = LineBuffer[i++];
348                         *d_ptr++ = V;
349                         ++count;
350                         --len;
351                     }
352
353                     AssembleByte(5, count | 0x10);
354                     d_ptr = differ;
355                     do {
356                         AssembleByte(ColorBits, *d_ptr++);
357                     } while (--count >= 0);
358
359                 }
360             }
361             AssembleByte(5, 0);
362             AssembleByte(8, 0);
363             /* Write the buffer to file */
364             WriteOutBuffer(D);
365         }
366         break;
367     }
368 }
369
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
373  * returned.
374  *
375  * The Lynx will draw 4 quadrants:
376  * - Down right
377  * - Up right
378  * - Up left
379  * - Down left
380  *
381  * The sprite will end with a byte 0.
382  */
383 {
384     enum Mode M;
385     StrBuf* D;
386     signed X, Y;
387     unsigned OX, OY;
388     char ColorBits;
389     char ColorMask;
390     char EdgeIndex;
391
392     /* Get EdgeIndex */
393     EdgeIndex = GetEdgeIndex (A);
394
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");
400     }
401     if (OY >= GetBitmapHeight (B)) {
402         Error ("Action point Y cannot be larger than bitmap height");
403     }
404
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)" : "");
409
410     /* Get the sprite mode */
411     M = GetMode (A);
412
413     /* Now check if bitmap indexes are ok */
414     if (GetBitmapColors (B) > 16) {
415         Error ("Too many colors for a Lynx sprite");
416     }
417     ColorBits = 4;
418     ColorMask = 0x0f;
419     if (GetBitmapColors (B) < 9) {
420         ColorBits = 3;
421         ColorMask = 0x07;
422     }
423     if (GetBitmapColors (B) < 5) {
424         ColorBits = 2;
425         ColorMask = 0x03;
426     }
427     if (GetBitmapColors (B) < 3) {
428         ColorBits = 1;
429         ColorMask = 0x01;
430     }
431
432     /* Create the output buffer and resize it to the required size. */
433     D = NewStrBuf ();
434     SB_Realloc (D, 63);
435
436     /* Convert the image for quadrant bottom right */
437     for (Y = OY; Y < (signed)GetBitmapHeight (B); ++Y) {
438         signed i = 0;
439         signed LastOpaquePixel = -1;
440         char LineBuffer[512]; /* The maximum size is 508 pixels */
441
442         /* Fill the LineBuffer for easier optimisation */
443         for (X = OX; X < (signed)GetBitmapWidth (B); ++X) {
444
445             /* Fetch next bit into byte buffer */
446             LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
447
448             if (LineBuffer[i] != EdgeIndex) {
449                 LastOpaquePixel = i;
450             }
451             ++i;
452         }
453
454         encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
455     }
456
457     if ((OY == 0) && (OX == 0)) {
458         /* Trivial case only one quadrant */
459
460         /* Mark end of sprite */
461         SB_AppendChar (D, 0);
462
463         /* Return the converted bitmap */
464         return D;
465     }
466
467     /* Next quadrant */
468     SB_AppendChar (D, 1);
469
470     /* Convert the image for quadrant top right */
471     for (Y = OY - 1; Y >= 0; --Y) {
472         signed i = 0;
473         signed LastOpaquePixel = -1;
474         char LineBuffer[512]; /* The maximum size is 508 pixels */
475
476         /* Fill the LineBuffer for easier optimisation */
477         for (X = OX; X < (signed)GetBitmapWidth (B); ++X) {
478
479             /* Fetch next bit into byte buffer */
480             LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
481
482             if (LineBuffer[i] != EdgeIndex) {
483                 LastOpaquePixel = i;
484             }
485             ++i;
486         }
487
488         encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
489     }
490
491     if (OX == 0) {
492         /* Special case only two quadrants */
493
494         /* Mark end of sprite */
495         SB_AppendChar (D, 0);
496
497         /* Return the converted bitmap */
498         return D;
499     }
500
501     /* Next quadrant */
502     SB_AppendChar (D, 1);
503
504     /* Convert the image for quadrant top left */
505     for (Y = OY - 1; Y >= 0; --Y) {
506         signed i = 0;
507         signed LastOpaquePixel = -1;
508         char LineBuffer[512]; /* The maximum size is 508 pixels */
509
510         /* Fill the LineBuffer for easier optimisation */
511         for (X = OX - 1; X >= 0; --X) {
512
513             /* Fetch next bit into byte buffer */
514             LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
515
516             if (LineBuffer[i] != EdgeIndex) {
517                 LastOpaquePixel = i;
518             }
519             ++i;
520         }
521
522         encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
523     }
524
525     /* Next quadrant */
526     SB_AppendChar (D, 1);
527
528     /* Convert the image for quadrant bottom left */
529     for (Y = OY; Y < (signed)GetBitmapHeight (B); ++Y) {
530         signed i = 0;
531         signed LastOpaquePixel = -1;
532         char LineBuffer[512]; /* The maximum size is 508 pixels */
533
534         /* Fill the LineBuffer for easier optimisation */
535         for (X = OX - 1; X >= 0; --X) {
536
537             /* Fetch next bit into byte buffer */
538             LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
539
540             if (LineBuffer[i] != EdgeIndex) {
541                 LastOpaquePixel = i;
542             }
543             ++i;
544         }
545
546         encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
547     }
548
549     /* End sprite */
550     SB_AppendChar (D, 0);
551
552     /* Return the converted bitmap */
553     return D;
554 }