]> git.sur5r.net Git - cc65/blob - src/sp65/lynxsprite.c
abc83006f5752e81f77ffce0eb1ec9c508f08448
[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 ("Sprite is too large for the Lynx");
137             }
138             if (byte & 0x1) {
139                 OutBuffer[OutIndex++] = byte;
140                 if (!OutIndex) {
141                     Error ("Sprite is too large for the Lynx");
142                 }
143             }
144         }
145         return;
146     }
147     val <<= 8 - bits;
148
149     do {
150         byte <<= 1;
151
152         if (val & 0x80)
153             ++byte;
154
155         if (!(--bit_counter)) {
156             OutBuffer[OutIndex++] = byte;
157             if (!OutIndex) {
158                 Error ("Sprite is too large for the Lynx");
159             }
160             byte = 0;
161             bit_counter = 8;
162         }
163
164         val <<= 1;
165
166     } while (--bits);
167 }
168
169 static void AssembleByteLiteral(unsigned bits, char val)
170 {
171     static char bit_counter = 8, byte = 0;
172
173     /* initialize */
174     if (!bits) {
175         OutIndex = 0;
176         bit_counter = 8;
177         byte = 0;
178         return;
179     }
180     /* handle end of line */
181     if (bits == 8) {
182         if (bit_counter != 8) {
183             byte <<= bit_counter;
184             OutBuffer[OutIndex++] = byte;
185             if (!OutIndex) {
186                 Error ("Sprite is too large for the Lynx");
187             }
188         }
189         return;
190     }
191     val <<= 8 - bits;
192
193     do {
194         byte <<= 1;
195
196         if (val & 0x80)
197             ++byte;
198
199         if (!(--bit_counter)) {
200             OutBuffer[OutIndex++] = byte;
201             if (!OutIndex) {
202                 Error ("Sprite is too large for the Lynx");
203             }
204             byte = 0;
205             bit_counter = 8;
206         }
207
208         val <<= 1;
209
210     } while (--bits);
211 }
212
213 static unsigned char ChoosePackagingMode(signed len, signed index, char LineBuffer[512])
214 {
215     --len;
216     if (!len) {
217         return 0;
218     }
219     if (LineBuffer[index] != LineBuffer[index + 1]) {
220         return 0;
221     }
222     return 1;
223 }
224
225 static void WriteOutBuffer(StrBuf *D)
226 {
227     signed i;
228
229     /* Fix bug in Lynx where the count cannot be 1 */
230     if (OutIndex == 1) {
231         OutBuffer[OutIndex++] = 0;
232     }
233     /* Write the byte count to the end of the scanline */
234     if (OutIndex == 255) {
235         Error ("Sprite is too large for the Lynx");
236     }
237     SB_AppendChar (D, OutIndex+1);
238     /* Write scanline data */
239     for (i = 0; i < OutIndex; i++) {
240         SB_AppendChar (D, OutBuffer[i]);
241     }
242 }
243
244 static void encodeSprite(StrBuf *D, enum Mode M, char ColorBits, char ColorMask, char LineBuffer[512],
245     int len, int LastOpaquePixel) {
246 /*
247  * The data starts with a byte count. It tells the number of bytes on this
248  * line + 1.
249  * Special case is a count of 1. It will change to next quadrant.
250  * Other special case is 0. It will end the sprite.
251  *
252  * Ordinary data packet. These are bits in a stream.
253  * 1=literal 0=packed
254  * 4 bit count (+1)
255  * for literal you put "count" values
256  * for packed you repeat the value "count" times
257  * Never use packed mode for one pixel
258  * If the last bit on a line is 1 you need to add a byte of zeroes
259  * A sequence 00000 ends a scan line
260  *
261  * All data is high nybble first
262  */
263     unsigned char V = 0;
264     signed i;
265     signed count;
266     unsigned char differ[16];
267     unsigned char *d_ptr;
268
269     switch (M) {
270     case smAuto:
271     case smLiteral:
272         AssembleByteLiteral(0, 0);
273         for (i = 0; i < len; i++) {
274             /* Fetch next pixel index into pixel buffer */
275             AssembleByteLiteral(ColorBits, LineBuffer[i] & ColorMask);
276         }
277         AssembleByteLiteral(8, 0);
278         /* Write the buffer to file */
279         WriteOutBuffer(D);
280         break;
281     case smPacked:
282         AssembleByte(0, 0);
283         i = 0;
284         while (len) {
285             if (ChoosePackagingMode(len, i, LineBuffer)) {
286                 /* Make runlength packet */
287                 V = LineBuffer[i];
288                 ++i;
289                 --len;
290                 count = 0;
291                 do {
292                     ++count;
293                     ++i;
294                     --len;
295                 } while (V == LineBuffer[i] && len && count != 15);
296
297                 AssembleByte(5, count);
298                 AssembleByte(ColorBits, V);
299
300             } else {
301                 /* Make packed literal packet */
302                 d_ptr = differ;
303                 V = LineBuffer[i++];
304                 *d_ptr++ = V;
305                 --len;
306                 count = 0;
307                 while (ChoosePackagingMode(len, i, LineBuffer) == 0 && len && count != 15) {
308                     V = LineBuffer[i++];
309                     *d_ptr++ = V;
310                     ++count;
311                     --len;
312                 }
313
314                 AssembleByte(5, count | 0x10);
315                 d_ptr = differ;
316                 do {
317                     AssembleByte(ColorBits, *d_ptr++);
318                 } while (--count >= 0);
319
320             }
321         }
322         AssembleByte(8, 0);
323         /* Write the buffer to file */
324         WriteOutBuffer(D);
325         break;
326
327     case smShaped:
328         break;
329     }
330 }
331
332 StrBuf* GenLynxSprite (const Bitmap* B, const Collection* A)
333 /* Generate binary output in Lynx sprite format for the bitmap B. The output
334  * is stored in a string buffer (which is actually a dynamic char array) and
335  * returned.
336  *
337  * The Lynx will draw 4 quadrants:
338  * - Down right
339  * - Up right
340  * - Up left
341  * - Down left
342  *
343  * The sprite will end with a byte 0.
344  */
345 {
346     enum Mode M;
347     StrBuf* D;
348     signed X, Y;
349     unsigned OX, OY;
350     char ColorBits;
351     char ColorMask;
352
353     /* Action point of the sprite */
354     OX = GetActionPointX (A);
355     OY = GetActionPointY (A);
356     if (OX >= GetBitmapWidth (B)) {
357         Error ("Action point X cannot be larger than bitmap width");
358     }
359     if (OY >= GetBitmapHeight (B)) {
360         Error ("Action point Y cannot be larger than bitmap height");
361     }
362
363     /* Output the image properties */
364     Print (stdout, 1, "Image is %ux%u with %u colors%s\n",
365            GetBitmapWidth (B), GetBitmapHeight (B), GetBitmapColors (B),
366            BitmapIsIndexed (B)? " (indexed)" : "");
367
368     /* Get the sprite mode */
369     M = GetMode (A);
370
371     /* Now check if bitmap indexes are ok */
372     if (GetBitmapColors (B) > 16) {
373         Error ("Too many colors for a Lynx sprite");
374     }
375     ColorBits = 4;
376     ColorMask = 0x0f;
377     if (GetBitmapColors (B) < 9) {
378         ColorBits = 3;
379         ColorMask = 0x07;
380     }
381     if (GetBitmapColors (B) < 5) {
382         ColorBits = 2;
383         ColorMask = 0x03;
384     }
385     if (GetBitmapColors (B) < 3) {
386         ColorBits = 1;
387         ColorMask = 0x01;
388     }
389
390     /* Create the output buffer and resize it to the required size. */
391     D = NewStrBuf ();
392     SB_Realloc (D, 63);
393
394     /* Convert the image for quadrant bottom right */
395     for (Y = OY; Y < (signed)GetBitmapHeight (B); ++Y) {
396         signed i = 0;
397         signed LastOpaquePixel = -1;
398         char LineBuffer[512]; /* The maximum size is 508 pixels */
399
400         /* Fill the LineBuffer for easier optimisation */
401         for (X = OX; X < (signed)GetBitmapWidth (B); ++X) {
402
403             /* Fetch next bit into byte buffer */
404             LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
405
406             if (LineBuffer[i]) {
407                 LastOpaquePixel = i;
408             }
409             ++i;
410         }
411
412         encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
413     }
414
415     if ((OY == 0) && (OX == 0)) {
416         /* Trivial case only one quadrant */
417
418         /* Mark end of sprite */
419         SB_AppendChar (D, 0);
420
421         /* Return the converted bitmap */
422         return D;
423     }
424
425     /* Next quadrant */
426     SB_AppendChar (D, 1);
427
428     /* Convert the image for quadrant top right */
429     for (Y = OY - 1; Y >= 0; --Y) {
430         signed i = 0;
431         signed LastOpaquePixel = -1;
432         char LineBuffer[512]; /* The maximum size is 508 pixels */
433
434         /* Fill the LineBuffer for easier optimisation */
435         for (X = OX; X < (signed)GetBitmapWidth (B); ++X) {
436
437             /* Fetch next bit into byte buffer */
438             LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
439
440             if (LineBuffer[i]) {
441                 LastOpaquePixel = i;
442             }
443             ++i;
444         }
445
446         encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
447     }
448
449     if (OX == 0) {
450         /* Special case only two quadrants */
451
452         /* Mark end of sprite */
453         SB_AppendChar (D, 0);
454
455         /* Return the converted bitmap */
456         return D;
457     }
458
459     /* Next quadrant */
460     SB_AppendChar (D, 1);
461
462     /* Convert the image for quadrant top left */
463     for (Y = OY - 1; Y >= 0; --Y) {
464         signed i = 0;
465         signed LastOpaquePixel = -1;
466         char LineBuffer[512]; /* The maximum size is 508 pixels */
467
468         /* Fill the LineBuffer for easier optimisation */
469         for (X = OX - 1; X >= 0; --X) {
470
471             /* Fetch next bit into byte buffer */
472             LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
473
474             if (LineBuffer[i]) {
475                 LastOpaquePixel = i;
476             }
477             ++i;
478         }
479
480         encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
481     }
482
483     /* Next quadrant */
484     SB_AppendChar (D, 1);
485
486     /* Convert the image for quadrant bottom left */
487     for (Y = OY; Y < (signed)GetBitmapHeight (B); ++Y) {
488         signed i = 0;
489         signed LastOpaquePixel = -1;
490         char LineBuffer[512]; /* The maximum size is 508 pixels */
491
492         /* Fill the LineBuffer for easier optimisation */
493         for (X = OX - 1; X >= 0; --X) {
494
495             /* Fetch next bit into byte buffer */
496             LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
497
498             if (LineBuffer[i]) {
499                 LastOpaquePixel = i;
500             }
501             ++i;
502         }
503
504         encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
505     }
506
507     /* End sprite */
508     SB_AppendChar (D, 0);
509
510     /* Return the converted bitmap */
511     return D;
512 }
513
514
515