]> git.sur5r.net Git - cc65/blob - src/sp65/lynxsprite.c
Add anchor point support
[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     smPackedTransparent
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, "transparent") == 0) {
81             return smPackedTransparent;
82         } else {
83             Error ("Invalid value for attribute `mode'");
84         }
85     } else {
86         return smAuto;
87     }
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
117 static void encodeSprite(StrBuf *D, enum Mode M, char ColorBits, char ColorMask, char LineBuffer[512],
118     int i, int LastOpaquePixel) {
119 /*
120  * The data starts with a byte count. It tells the number of bytes on this
121  * line + 1.
122  * Special case is a count of 1. It will change to next quadrant.
123  * Other special case is 0. It will end the sprite.
124  *
125  * Ordinary data packet. These are bits in a stream.
126  * 1=literal 0=packed
127  * 4 bit count (+1)
128  * for literal you put "count" values
129  * for packed you repeat the value "count" times
130  * Never use packed mode for one pixel
131  * If the last bit on a line is 1 you need to add a byte of zeroes
132  * A sequence 00000 ends a scan line
133  *
134  * All data is high nybble first
135  */
136     char OutBuffer[512]; /* The maximum size is 508 pixels */
137     unsigned char OutIndex = 0;
138     unsigned char V = 0;
139     unsigned W = 0;
140     signed j;
141     signed k;
142
143     switch (M) {
144     case smAuto:
145     case smLiteral:
146         OutIndex = 0;
147         k = 0;
148         for (j = 0; j < i; j++) {
149             /* Fetch next pixel index into pixel buffer */
150             W = (W << ColorBits) | (LineBuffer[j] & ColorMask);
151             k += ColorBits;
152             if (k > 7) {
153                 /* The byte is ready */
154                 k -= 8;
155                 V = (W >> k) & 0xFF;
156                 OutBuffer[OutIndex++] = V;
157                 if (!OutIndex) {
158                     Error ("Sprite is too large for the Lynx");
159                 }
160             }
161         }
162         /* Output last bits */
163         if (k != 0) {
164             W = (W << (8-k));
165             k = 0;
166             V = W & 0xFF;
167             OutBuffer[OutIndex++] = V;
168             if (!OutIndex) {
169                 Error ("Sprite is too large for the Lynx");
170             }
171         }
172         /* Fix bug in Lynx where the last bit on a line is 1 */
173         if (V & 1) {
174             OutBuffer[OutIndex++] = 0;
175             if (!OutIndex) {
176                 Error ("Sprite is too large for the Lynx");
177             }
178         }
179         /* Fix bug in Lynx where the count cannot be 1 */
180         if (OutIndex == 1) {
181             OutBuffer[OutIndex++] = 0;
182         }
183         /* Write the byte count to the end of the scanline */
184         if (OutIndex == 255) {
185             Error ("Sprite is too large for the Lynx");
186         }
187         SB_AppendChar (D, OutIndex+1);
188         /* Write scanline data */
189         for (j = 0; j < OutIndex; j++) {
190             SB_AppendChar (D, OutBuffer[j]);
191         }
192         break;
193     case smPacked:
194         /* Bug workaround: If last bit is 1 and it is in bit0 add a zero byte */
195         /* Note: These extra pixels will be painted also. There is no workaround for this */
196         if (LineBuffer[i - 1] & 0x01) {
197             LineBuffer[i++] = 0;
198         }
199         /* Logical problem workaround: The count can not be 1 so add an extra byte */
200         if (i == 1) {
201             LineBuffer[i++] = 0;
202         }
203         /* Write the byte count for this partial scanline */
204         SB_AppendChar (D, i);
205         for (i = 0; i < LineBuffer[0]; i++) {
206             SB_AppendChar (D, LineBuffer[i]);
207         }
208         break;
209     case smPackedTransparent:
210         break;
211     }
212 }
213
214 StrBuf* GenLynxSprite (const Bitmap* B, const Collection* A)
215 /* Generate binary output in Lynx sprite format for the bitmap B. The output
216  * is stored in a string buffer (which is actually a dynamic char array) and
217  * returned.
218  *
219  * The Lynx will draw 4 quadrants:
220  * - Down right
221  * - Up right
222  * - Up left
223  * - Down left
224  *
225  * The data starts with a byte count. It tells the number of bytes on this
226  * line + 1.
227  * Special case is a count of 1. It will change to next quadrant.
228  * Other special case is 0. It will end the sprite.
229  *
230  * Ordinary data packet. These are bits in a stream.
231  * 1=literal 0=packed
232  * 4 bit count (+1)
233  * for literal you put "count" values
234  * for packed you repeat the value "count" times
235  * Never use packed mode for one pixel
236  * If the last bit on a line is 1 you need to add a byte of zeroes
237  * A sequence 00000 ends a scan line
238  *
239  * All data is high nybble first
240  */
241 {
242     enum Mode M;
243     StrBuf* D;
244     signed X, Y;
245     unsigned OX, OY;
246     char ColorBits;
247     char ColorMask;
248
249     /* Action point of the sprite */
250     OX = GetActionPointX (A);
251     OY = GetActionPointY (A);
252     if (OX >= GetBitmapWidth (B)) {
253         Error ("Action point X cannot be larger than bitmap width");
254     }
255     if (OY >= GetBitmapHeight (B)) {
256         Error ("Action point Y cannot be larger than bitmap height");
257     }
258
259     /* Output the image properties */
260     Print (stdout, 1, "Image is %ux%u with %u colors%s\n",
261            GetBitmapWidth (B), GetBitmapHeight (B), GetBitmapColors (B),
262            BitmapIsIndexed (B)? " (indexed)" : "");
263
264     /* Get the sprite mode */
265     M = GetMode (A);
266
267     /* Now check if bitmap indexes are ok */
268     if (GetBitmapColors (B) > 16) {
269         Error ("Too many colors for a Lynx sprite");
270     }
271     ColorBits = 4;
272     ColorMask = 0x0f;
273     if (GetBitmapColors (B) < 9) {
274         ColorBits = 3;
275         ColorMask = 0x07;
276     }
277     if (GetBitmapColors (B) < 5) {
278         ColorBits = 2;
279         ColorMask = 0x03;
280     }
281     if (GetBitmapColors (B) < 3) {
282         ColorBits = 1;
283         ColorMask = 0x01;
284     }
285
286     /* Create the output buffer and resize it to the required size. */
287     D = NewStrBuf ();
288     SB_Realloc (D, 63);
289
290     /* Convert the image for quadrant bottom right */
291     for (Y = OY; Y < (signed)GetBitmapHeight (B); ++Y) {
292         signed i = 0;
293         signed LastOpaquePixel = -1;
294         char LineBuffer[512]; /* The maximum size is 508 pixels */
295
296         /* Fill the LineBuffer for easier optimisation */
297         for (X = OX; X < (signed)GetBitmapWidth (B); ++X) {
298
299             /* Fetch next bit into byte buffer */
300             LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
301
302             if (LineBuffer[i]) {
303                 LastOpaquePixel = i;
304             }
305             ++i;
306         }
307
308         encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
309     }
310
311     if ((OY == 0) && (OX == 0)) {
312         /* Trivial case only one quadrant */
313
314         /* Mark end of sprite */
315         SB_AppendChar (D, 0);
316
317         /* Return the converted bitmap */
318         return D;
319     }
320
321     /* Next quadrant */
322     SB_AppendChar (D, 1);
323
324     /* Convert the image for quadrant top right */
325     for (Y = OY - 1; Y >= 0; --Y) {
326         signed i = 0;
327         signed LastOpaquePixel = -1;
328         char LineBuffer[512]; /* The maximum size is 508 pixels */
329
330         /* Fill the LineBuffer for easier optimisation */
331         for (X = OX; X < (signed)GetBitmapWidth (B); ++X) {
332
333             /* Fetch next bit into byte buffer */
334             LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
335
336             if (LineBuffer[i]) {
337                 LastOpaquePixel = i;
338             }
339             ++i;
340         }
341
342         encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
343     }
344
345     /* Next quadrant */
346     SB_AppendChar (D, 1);
347
348     /* Convert the image for quadrant top left */
349     for (Y = OY - 1; Y >= 0; --Y) {
350         signed i = 0;
351         signed LastOpaquePixel = -1;
352         char LineBuffer[512]; /* The maximum size is 508 pixels */
353
354         /* Fill the LineBuffer for easier optimisation */
355         for (X = OX - 1; X >= 0; --X) {
356
357             /* Fetch next bit into byte buffer */
358             LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
359
360             if (LineBuffer[i]) {
361                 LastOpaquePixel = i;
362             }
363             ++i;
364         }
365
366         encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
367     }
368
369     /* Next quadrant */
370     SB_AppendChar (D, 1);
371
372     /* Convert the image for quadrant bottom left */
373     for (Y = OY; Y < (signed)GetBitmapHeight (B); ++Y) {
374         signed i = 0;
375         signed LastOpaquePixel = -1;
376         char LineBuffer[512]; /* The maximum size is 508 pixels */
377
378         /* Fill the LineBuffer for easier optimisation */
379         for (X = OX - 1; X >= 0; --X) {
380
381             /* Fetch next bit into byte buffer */
382             LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
383
384             if (LineBuffer[i]) {
385                 LastOpaquePixel = i;
386             }
387             ++i;
388         }
389
390         encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
391     }
392
393     /* End sprite */
394     SB_AppendChar (D, 0);
395
396     /* Return the converted bitmap */
397     return D;
398 }
399
400
401