]> git.sur5r.net Git - cc65/blob - src/sp65/lynxsprite.c
Add support for 4 quadrants
[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 /* common */
37 #include "attrib.h"
38 #include "print.h"
39
40 /* sp65 */
41 #include "attr.h"
42 #include "error.h"
43 #include "lynxsprite.h"
44
45
46
47 /*****************************************************************************/
48 /*                                   Data                                    */
49 /*****************************************************************************/
50
51
52
53 /* Sprite mode */
54 enum Mode {
55     smAuto,
56     smLiteral,
57     smPacked,
58     smPackedTransparent
59 };
60
61
62 /*****************************************************************************/
63 /*                                   Code                                    */
64 /*****************************************************************************/
65
66
67
68 static enum Mode GetMode (const Collection* A)
69 /* Return the sprite mode from the attribute collection A */
70 {
71     /* Check for a mode attribute */
72     const char* Mode = GetAttrVal (A, "mode");
73     if (Mode) {
74         if (strcmp (Mode, "literal") == 0) {
75             return smLiteral;
76         } else if (strcmp (Mode, "packed") == 0) {
77             return smPacked;
78         } else if (strcmp (Mode, "transparent") == 0) {
79             return smPackedTransparent;
80         } else {
81             Error ("Invalid value for attribute `mode'");
82         }
83     } else {
84         return smAuto;
85     }
86 }
87
88
89 static void encodeSprite(StrBuf *D, enum Mode M, char ColorBits, char ColorMask, char LineBuffer[512],
90     int i, int LastOpaquePixel) {
91 /*
92  * The data starts with a byte count. It tells the number of bytes on this
93  * line + 1.
94  * Special case is a count of 1. It will change to next quadrant.
95  * Other special case is 0. It will end the sprite.
96  *
97  * Ordinary data packet. These are bits in a stream.
98  * 1=literal 0=packed
99  * 4 bit count (+1)
100  * for literal you put "count" values
101  * for packed you repeat the value "count" times
102  * Never use packed mode for one pixel
103  * If the last bit on a line is 1 you need to add a byte of zeroes
104  * A sequence 00000 ends a scan line
105  *
106  * All data is high nybble first
107  */
108     char OutBuffer[512]; /* The maximum size is 508 pixels */
109     unsigned char OutIndex = 0;
110     unsigned char V = 0;
111     unsigned W = 0;
112     signed j;
113     signed k;
114
115     switch (M) {
116     case smAuto:
117     case smLiteral:
118         OutIndex = 0;
119         k = 0;
120         for (j = 0; j < i; j++) {
121             /* Fetch next pixel index into pixel buffer */
122             W = (W << ColorBits) | (LineBuffer[j] & ColorMask);
123             k += ColorBits;
124             if (k > 7) {
125                 /* The byte is ready */
126                 k -= 8;
127                 V = (W >> k) & 0xFF;
128                 OutBuffer[OutIndex++] = V;
129                 if (!OutIndex) {
130                     Error ("Sprite is too large for the Lynx");
131                 }
132             }
133         }
134         /* Output last bits */
135         if (k != 0) {
136             W = (W << (8-k));
137             k = 0;
138             V = W & 0xFF;
139             OutBuffer[OutIndex++] = V;
140             if (!OutIndex) {
141                 Error ("Sprite is too large for the Lynx");
142             }
143         }
144         /* Fix bug in Lynx where the last bit on a line is 1 */
145         if (V & 1) {
146             OutBuffer[OutIndex++] = 0;
147             if (!OutIndex) {
148                 Error ("Sprite is too large for the Lynx");
149             }
150         }
151         /* Fix bug in Lynx where the count cannot be 1 */
152         if (OutIndex == 1) {
153             OutBuffer[OutIndex++] = 0;
154         }
155         /* Write the byte count to the end of the scanline */
156         if (OutIndex == 255) {
157             Error ("Sprite is too large for the Lynx");
158         }
159         SB_AppendChar (D, OutIndex+1);
160         /* Write scanline data */
161         for (j = 0; j < OutIndex; j++) {
162             SB_AppendChar (D, OutBuffer[j]);
163         }
164         break;
165     case smPacked:
166         /* Bug workaround: If last bit is 1 and it is in bit0 add a zero byte */
167         /* Note: These extra pixels will be painted also. There is no workaround for this */
168         if (LineBuffer[i - 1] & 0x01) {
169             LineBuffer[i++] = 0;
170         }
171         /* Logical problem workaround: The count can not be 1 so add an extra byte */
172         if (i == 1) {
173             LineBuffer[i++] = 0;
174         }
175         /* Write the byte count for this partial scanline */
176         SB_AppendChar (D, i);
177         for (i = 0; i < LineBuffer[0]; i++) {
178             SB_AppendChar (D, LineBuffer[i]);
179         }
180         break;
181     case smPackedTransparent:
182         break;
183     }
184 }
185
186 StrBuf* GenLynxSprite (const Bitmap* B, const Collection* A)
187 /* Generate binary output in Lynx sprite format for the bitmap B. The output
188  * is stored in a string buffer (which is actually a dynamic char array) and
189  * returned.
190  *
191  * The Lynx will draw 4 quadrants:
192  * - Down right
193  * - Up right
194  * - Up left
195  * - Down left
196  *
197  * The data starts with a byte count. It tells the number of bytes on this
198  * line + 1.
199  * Special case is a count of 1. It will change to next quadrant.
200  * Other special case is 0. It will end the sprite.
201  *
202  * Ordinary data packet. These are bits in a stream.
203  * 1=literal 0=packed
204  * 4 bit count (+1)
205  * for literal you put "count" values
206  * for packed you repeat the value "count" times
207  * Never use packed mode for one pixel
208  * If the last bit on a line is 1 you need to add a byte of zeroes
209  * A sequence 00000 ends a scan line
210  *
211  * All data is high nybble first
212  */
213 {
214     enum Mode M;
215     StrBuf* D;
216     signed X, Y;
217     unsigned OX, OY;
218     char ColorBits;
219     char ColorMask;
220
221     /* Anchor point of the sprite */
222     OX = 0;
223     OY = 0;
224
225     /* Output the image properties */
226     Print (stdout, 1, "Image is %ux%u with %u colors%s\n",
227            GetBitmapWidth (B), GetBitmapHeight (B), GetBitmapColors (B),
228            BitmapIsIndexed (B)? " (indexed)" : "");
229
230     /* Get the sprite mode */
231     M = GetMode (A);
232
233     /* Now check if bitmap indexes are ok */
234     if (GetBitmapColors (B) > 16) {
235         Error ("Too many colors for a Lynx sprite");
236     }
237     ColorBits = 4;
238     ColorMask = 0x0f;
239     if (GetBitmapColors (B) < 9) {
240         ColorBits = 3;
241         ColorMask = 0x07;
242     }
243     if (GetBitmapColors (B) < 5) {
244         ColorBits = 2;
245         ColorMask = 0x03;
246     }
247     if (GetBitmapColors (B) < 3) {
248         ColorBits = 1;
249         ColorMask = 0x01;
250     }
251
252     /* Create the output buffer and resize it to the required size. */
253     D = NewStrBuf ();
254     SB_Realloc (D, 63);
255
256     /* Convert the image for quadrant bottom right */
257     for (Y = OY; Y < (signed)GetBitmapHeight (B); ++Y) {
258         signed i = 0;
259         signed LastOpaquePixel = -1;
260         char LineBuffer[512]; /* The maximum size is 508 pixels */
261
262         /* Fill the LineBuffer for easier optimisation */
263         for (X = OX; X < (signed)GetBitmapWidth (B); ++X) {
264
265             /* Fetch next bit into byte buffer */
266             LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
267
268             if (LineBuffer[i]) {
269                 LastOpaquePixel = i;
270             }
271             ++i;
272         }
273
274         encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
275     }
276
277     if ((OY == 0) && (OX == 0)) {
278         /* Trivial case only one quadrant */
279
280         /* Mark end of sprite */
281         SB_AppendChar (D, 0);
282
283         /* Return the converted bitmap */
284         return D;
285     }
286
287     /* Next quadrant */
288     SB_AppendChar (D, 1);
289
290     /* Convert the image for quadrant top right */
291     for (Y = OY - 1; Y >= 0; --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     /* Next quadrant */
312     SB_AppendChar (D, 1);
313
314     /* Convert the image for quadrant top left */
315     for (Y = OY - 1; Y >= 0; --Y) {
316         signed i = 0;
317         signed LastOpaquePixel = -1;
318         char LineBuffer[512]; /* The maximum size is 508 pixels */
319
320         /* Fill the LineBuffer for easier optimisation */
321         for (X = OX - 1; X >= 0; --X) {
322
323             /* Fetch next bit into byte buffer */
324             LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
325
326             if (LineBuffer[i]) {
327                 LastOpaquePixel = i;
328             }
329             ++i;
330         }
331
332         encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
333     }
334
335     /* Next quadrant */
336     SB_AppendChar (D, 1);
337
338     /* Convert the image for quadrant bottom left */
339     for (Y = OY; Y < (signed)GetBitmapHeight (B); ++Y) {
340         signed i = 0;
341         signed LastOpaquePixel = -1;
342         char LineBuffer[512]; /* The maximum size is 508 pixels */
343
344         /* Fill the LineBuffer for easier optimisation */
345         for (X = OX - 1; X >= 0; --X) {
346
347             /* Fetch next bit into byte buffer */
348             LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask;
349
350             if (LineBuffer[i]) {
351                 LastOpaquePixel = i;
352             }
353             ++i;
354         }
355
356         encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel);
357     }
358
359     /* End sprite */
360     SB_AppendChar (D, 0);
361
362     /* Return the converted bitmap */
363     return D;
364 }
365
366
367