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 */
56enum Mode {
57 smAuto,
58 smLiteral,
59 smPacked,
60 smShaped
61};
62
63
64/*****************************************************************************/
65/* Code */
66/*****************************************************************************/
67
68
69
70static 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
91static 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
104static 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
116static 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
128static char OutBuffer[512]; /* The maximum size is 508 pixels */
129static unsigned char OutIndex;
130
131static 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
192static 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
216static 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
235static 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
370StrBuf* 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}
555