]> git.sur5r.net Git - cc65/blob - src/sp65/lynxsprite.c
Add shaped mode
[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     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         if (LastOpaquePixel >= 0 && LastOpaquePixel < len) {
329             len = LastOpaquePixel;
330         }
331         AssembleByte(0, 0);
332         i = 0;
333         while (len) {
334             if (ChoosePackagingMode(len, i, LineBuffer)) {
335                 /* Make runlength packet */
336                 V = LineBuffer[i];
337                 ++i;
338                 --len;
339                 count = 0;
340                 do {
341                     ++count;
342                     ++i;
343                     --len;
344                 } while (V == LineBuffer[i] && len && count != 15);
345
346                 AssembleByte(5, count);
347                 AssembleByte(ColorBits, V);
348
349             } else {
350                 /* Make packed literal packet */
351                 d_ptr = differ;
352                 V = LineBuffer[i++];
353                 *d_ptr++ = V;
354                 --len;
355                 count = 0;
356                 while (ChoosePackagingMode(len, i, LineBuffer) == 0 && len && count != 15) {
357                     V = LineBuffer[i++];
358                     *d_ptr++ = V;
359                     ++count;
360                     --len;
361                 }
362
363                 AssembleByte(5, count | 0x10);
364                 d_ptr = differ;
365                 do {
366                     AssembleByte(ColorBits, *d_ptr++);
367                 } while (--count >= 0);
368
369             }
370         }
371         AssembleByte(5, 0);
372         AssembleByte(8, 0);
373         /* Write the buffer to file */
374         WriteOutBuffer(D);
375         break;
376     }
377 }
378
379 StrBuf* GenLynxSprite (const Bitmap* B, const Collection* A)
380 /* Generate binary output in Lynx sprite format for the bitmap B. The output
381  * is stored in a string buffer (which is actually a dynamic char array) and
382  * returned.
383  *
384  * The Lynx will draw 4 quadrants:
385  * - Down right
386  * - Up right
387  * - Up left
388  * - Down left
389  *
390  * The sprite will end with a byte 0.
391  */
392 {
393     enum Mode M;
394     StrBuf* D;
395     signed X, Y;
396     unsigned OX, OY;
397     char ColorBits;
398     char ColorMask;
399
400     /* Action point of the sprite */
401     OX = GetActionPointX (A);
402     OY = GetActionPointY (A);
403     if (OX >= GetBitmapWidth (B)) {
404         Error ("Action point X cannot be larger than bitmap width");
405     }
406     if (OY >= GetBitmapHeight (B)) {
407         Error ("Action point Y cannot be larger than bitmap height");
408     }
409
410     /* Output the image properties */
411     Print (stdout, 1, "Image is %ux%u with %u colors%s\n",
412            GetBitmapWidth (B), GetBitmapHeight (B), GetBitmapColors (B),
413            BitmapIsIndexed (B)? " (indexed)" : "");
414
415     /* Get the sprite mode */
416     M = GetMode (A);
417
418     /* Now check if bitmap indexes are ok */
419     if (GetBitmapColors (B) > 16) {
420         Error ("Too many colors for a Lynx sprite");
421     }
422     ColorBits = 4;
423     ColorMask = 0x0f;
424     if (GetBitmapColors (B) < 9) {
425         ColorBits = 3;
426         ColorMask = 0x07;
427     }
428     if (GetBitmapColors (B) < 5) {
429         ColorBits = 2;
430         ColorMask = 0x03;
431     }
432     if (GetBitmapColors (B) < 3) {
433         ColorBits = 1;
434         ColorMask = 0x01;
435     }
436
437     /* Create the output buffer and resize it to the required size. */
438     D = NewStrBuf ();
439     SB_Realloc (D, 63);
440
441     /* Convert the image for quadrant bottom right */
442     for (Y = OY; Y < (signed)GetBitmapHeight (B); ++Y) {
443         signed i = 0;
444         signed LastOpaquePixel = -1;
445         char LineBuffer[512]; /* The maximum size is 508 pixels */
446
447         /* Fill the LineBuffer for easier optimisation */
448         for (X = OX; X < (signed)GetBitmapWidth (B); ++X) {
449
450             /* Fetch next bit into byte buffer */
451             LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
452
453             if (LineBuffer[i]) {
454                 LastOpaquePixel = i;
455             }
456             ++i;
457         }
458
459         encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
460     }
461
462     if ((OY == 0) && (OX == 0)) {
463         /* Trivial case only one quadrant */
464
465         /* Mark end of sprite */
466         SB_AppendChar (D, 0);
467
468         /* Return the converted bitmap */
469         return D;
470     }
471
472     /* Next quadrant */
473     SB_AppendChar (D, 1);
474
475     /* Convert the image for quadrant top right */
476     for (Y = OY - 1; Y >= 0; --Y) {
477         signed i = 0;
478         signed LastOpaquePixel = -1;
479         char LineBuffer[512]; /* The maximum size is 508 pixels */
480
481         /* Fill the LineBuffer for easier optimisation */
482         for (X = OX; X < (signed)GetBitmapWidth (B); ++X) {
483
484             /* Fetch next bit into byte buffer */
485             LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
486
487             if (LineBuffer[i]) {
488                 LastOpaquePixel = i;
489             }
490             ++i;
491         }
492
493         encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
494     }
495
496     if (OX == 0) {
497         /* Special case only two quadrants */
498
499         /* Mark end of sprite */
500         SB_AppendChar (D, 0);
501
502         /* Return the converted bitmap */
503         return D;
504     }
505
506     /* Next quadrant */
507     SB_AppendChar (D, 1);
508
509     /* Convert the image for quadrant top left */
510     for (Y = OY - 1; Y >= 0; --Y) {
511         signed i = 0;
512         signed LastOpaquePixel = -1;
513         char LineBuffer[512]; /* The maximum size is 508 pixels */
514
515         /* Fill the LineBuffer for easier optimisation */
516         for (X = OX - 1; X >= 0; --X) {
517
518             /* Fetch next bit into byte buffer */
519             LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
520
521             if (LineBuffer[i]) {
522                 LastOpaquePixel = i;
523             }
524             ++i;
525         }
526
527         encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
528     }
529
530     /* Next quadrant */
531     SB_AppendChar (D, 1);
532
533     /* Convert the image for quadrant bottom left */
534     for (Y = OY; Y < (signed)GetBitmapHeight (B); ++Y) {
535         signed i = 0;
536         signed LastOpaquePixel = -1;
537         char LineBuffer[512]; /* The maximum size is 508 pixels */
538
539         /* Fill the LineBuffer for easier optimisation */
540         for (X = OX - 1; X >= 0; --X) {
541
542             /* Fetch next bit into byte buffer */
543             LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
544
545             if (LineBuffer[i]) {
546                 LastOpaquePixel = i;
547             }
548             ++i;
549         }
550
551         encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
552     }
553
554     /* End sprite */
555     SB_AppendChar (D, 0);
556
557     /* Return the converted bitmap */
558     return D;
559 }
560
561
562