From f6b3bdda21f10b8078217a7679c9b36f441581ea Mon Sep 17 00:00:00 2001 From: karri Date: Fri, 30 Dec 2022 12:36:49 +0200 Subject: [PATCH] Add fixed Lynx sprite generation --- src/sp65/lynxsprite.c | 785 ++++++++++++++++++++++++++++-------------- 1 file changed, 529 insertions(+), 256 deletions(-) diff --git a/src/sp65/lynxsprite.c b/src/sp65/lynxsprite.c index 4d7669faf..dca42b061 100644 --- a/src/sp65/lynxsprite.c +++ b/src/sp65/lynxsprite.c @@ -88,12 +88,18 @@ static enum Mode GetMode (const Collection* A) } -static unsigned GetActionPointX (const Collection* A) +static unsigned GetActionPointX (const Bitmap* B, const Collection* A) /* Return the sprite mode from the attribute collection A */ { /* Check for a action point x attribute */ const char* ActionPointX = GetAttrVal (A, "ax"); if (ActionPointX) { + if (strcmp (ActionPointX, "mid") == 0) { + return GetBitmapWidth (B) / 2; + } + if (strcmp (ActionPointX, "max") == 0) { + return GetBitmapWidth (B) - 1; + } return atoi(ActionPointX); } else { return 0; @@ -101,12 +107,18 @@ static unsigned GetActionPointX (const Collection* A) } -static unsigned GetActionPointY (const Collection* A) +static unsigned GetActionPointY (const Bitmap* B, const Collection* A) /* Return the sprite mode from the attribute collection A */ { /* Check for a action point y attribute */ const char* ActionPointY = GetAttrVal (A, "ay"); if (ActionPointY) { + if (strcmp (ActionPointY, "mid") == 0) { + return GetBitmapHeight (B) / 2; + } + if (strcmp (ActionPointY, "max") == 0) { + return GetBitmapHeight (B) - 1; + } return atoi(ActionPointY); } else { return 0; @@ -125,6 +137,124 @@ static unsigned GetEdgeIndex (const Collection* A) } } +static unsigned GetQuadrant (const Collection* A) +/* Return the sprite mode from the attribute collection A */ +{ + /* Get index for edge color in shaped mode */ + const char* Quadrant = GetAttrVal (A, "quadrant"); + if (Quadrant) { + return atoi(Quadrant); + } else { + return 0; + } +} + +static void OptimizePenpal (const Bitmap* B, char *PenPal) +/* Create an optimal Penpal */ +{ + char usage[16]; + unsigned I, J, Val; + + memset(usage, 0, sizeof(usage)); + for (J = 0; J < GetBitmapHeight (B); J++) { + for (I = 0; I < GetBitmapWidth (B); I++) { + Val = GetPixel (B, I, J).Index; + if (Val < 16) { + usage[Val] = 1; + } + } + } + J = 0; + for (I = 0; I < 16; I++) { + if (usage[I]) { + switch (I) { + case 0: + PenPal[J] = '0'; + break; + case 1: + PenPal[J] = '1'; + break; + case 2: + PenPal[J] = '2'; + break; + case 3: + PenPal[J] = '3'; + break; + case 4: + PenPal[J] = '4'; + break; + case 5: + PenPal[J] = '5'; + break; + case 6: + PenPal[J] = '6'; + break; + case 7: + PenPal[J] = '7'; + break; + case 8: + PenPal[J] = '8'; + break; + case 9: + PenPal[J] = '9'; + break; + case 10: + PenPal[J] = 'a'; + break; + case 11: + PenPal[J] = 'b'; + break; + case 12: + PenPal[J] = 'c'; + break; + case 13: + PenPal[J] = 'd'; + break; + case 14: + PenPal[J] = 'e'; + break; + case 15: + PenPal[J] = 'f'; + break; + } + J++; + } + } + while (J < 16) { + PenPal[J] = 0; + J++; + } + /* printf("Penpal %s\n", PenPal); */ +} + +static unsigned GetPenpal (const Bitmap* B, const Collection* A, char *PenPal) +/* Return the penpal from the attribute collection A */ +{ + const char* Pen = GetAttrVal (A, "pen"); + if (Pen) { + if (strcmp (Pen, "opt") == 0) { + /* So we need to optimize the penpal and colour depth */ + OptimizePenpal (B, PenPal); + } else { + strncpy(PenPal, Pen, 17); + } + return 1; + } + return 0; +} + +static unsigned GetBPP (const Collection* A) +/* Return the sprite depth from the attribute collection A */ +{ + /* Get index for edge color in shaped mode */ + const char* BPP = GetAttrVal (A, "bpp"); + if (BPP) { + return atoi(BPP); + } else { + return 0; + } +} + static char OutBuffer[512]; /* The maximum size is 508 pixels */ static unsigned char OutIndex; @@ -140,26 +270,16 @@ static void AssembleByte(unsigned bits, char val) return; } /* handle end of line */ - if (bits == 8) { + if (bits == 7) { if (bit_counter != 8) { byte <<= bit_counter; OutBuffer[OutIndex++] = byte; if (!OutIndex) { Error ("Sprite is too large for the Lynx"); } - if (byte & 0x1) { - OutBuffer[OutIndex++] = byte; - if (!OutIndex) { - Error ("Sprite is too large for the Lynx"); - } - } - } - return; - } - /* handle end of line for literal */ - if (bits == 7) { - if (bit_counter != 8) { - byte <<= bit_counter; + } else { + /* Add pad byte */ + byte = 0; OutBuffer[OutIndex++] = byte; if (!OutIndex) { Error ("Sprite is too large for the Lynx"); @@ -189,28 +309,78 @@ static void AssembleByte(unsigned bits, char val) } while (--bits); } -static unsigned char ChoosePackagingMode(signed len, signed index, char ColorBits, char LineBuffer[512]) +static unsigned char AnalyseNextChunks(signed *newlen, signed len, char data[32], char ColorBits) { - --len; - if (!len) { - return 0; + char longest = 1; + char prev = 255; + char count = 0; + char index = 0; + char lindex = 0; + int i; + int literal_cost; + int packed_cost; + + for (i = 0; i < len; i++) { + index = index + 1; + if (data[i] == prev) { + count = count + 1; + if (count >= longest) { + longest = count; + lindex = index - count; + } + } else { + prev = data[i]; + count = 1; + } } - if (LineBuffer[index] != LineBuffer[index + 1]) { - return 0; + if (longest == 1) { + if (len > 16) { + *newlen = 16; + } else { + *newlen = len; + } + return 'L'; } - if (ColorBits > 2) { - return 1; + if ((lindex > 0) && (lindex + longest > 15)) { + /* We cannot pack the stride in this packet */ + *newlen = lindex; + return 'A'; } - if (LineBuffer[index] != LineBuffer[index + 2]) { - return 0; + /* Cost till end of area */ + literal_cost = 5 + lindex * ColorBits + longest * ColorBits; + packed_cost = 5 + lindex * ColorBits + 5 + ColorBits; + if (packed_cost < literal_cost) { + if (lindex == 0) { + /* Use packed data */ + if (longest > 16) { + *newlen = 16; + } else { + *newlen = longest; + } + return 'P'; + } + /* We had a good find, but it was not at the start of the line */ + *newlen = lindex; + return 'A'; } - if (ColorBits > 1) { - return 1; + /* There is no point in packing - use literal */ + if (len > 16) { + *newlen = 16; + } else { + *newlen = len; } - if (LineBuffer[index] != LineBuffer[index + 3]) { - return 0; + return 'L'; +} + +static unsigned char GetNextChunk(signed *newlen, signed len, char data[32], char ColorBits) +{ + char oper = 'A'; + + while (oper == 'A') { + oper = AnalyseNextChunks(newlen, len, data, ColorBits); + len = *newlen; } - return 1; + return oper; /* The packet type is now P or L and the length is in newlen */ } static void WriteOutBuffer(StrBuf *D) @@ -235,27 +405,25 @@ static void WriteOutBuffer(StrBuf *D) static void encodeSprite(StrBuf *D, enum Mode M, char ColorBits, char ColorMask, char LineBuffer[512], int len, int LastOpaquePixel) { /* -** The data starts with a byte count. It tells the number of bytes on this -** line + 1. -** Special case is a count of 1. It will change to next quadrant. -** Other special case is 0. It will end the sprite. -** -** Ordinary data packet. These are bits in a stream. -** 1=literal 0=packed -** 4 bit count (+1) -** for literal you put "count" values -** for packed you repeat the value "count" times -** Never use packed mode for one pixel -** If the last bit on a line is 1 you need to add a byte of zeroes -** A sequence 00000 ends a scan line -** -** All data is high nybble first -*/ + * The data starts with a byte count. It tells the number of bytes on this + * line + 1. + * Special case is a count of 1. It will change to next quadrant. + * Other special case is 0. It will end the sprite. + * + * Ordinary data packet. These are bits in a stream. + * 1=literal 0=packed + * 4 bit count (+1) + * for literal you put "count" values + * for packed you repeat the value "count" times + * Never use packed mode for one pixel + * If the last bit on a line is in use you need to add a byte of zeroes + * A sequence 00000 ends a scan line + * + * All data is high nybble first + */ unsigned char V = 0; signed i; signed count; - unsigned char differ[16]; - unsigned char *d_ptr; AssembleByte(0, 0); switch (M) { @@ -270,100 +438,46 @@ static void encodeSprite(StrBuf *D, enum Mode M, char ColorBits, char ColorMask, WriteOutBuffer(D); break; case smPacked: + case smShaped: + if (M == smShaped) { + if (LastOpaquePixel > -1) { + if (LastOpaquePixel < len - 1) { + len = LastOpaquePixel + 1; + } + } else { + len = 0; + } + } i = 0; while (len) { - if (ChoosePackagingMode(len, i, ColorBits, LineBuffer)) { + signed analyselen; + analyselen = len; + if (analyselen > 32) { + analyselen = 32; + } + if (GetNextChunk(&count, analyselen, LineBuffer + i, ColorBits) == 'P') { /* Make runlength packet */ V = LineBuffer[i]; - ++i; - --len; - count = 0; - do { - ++count; - ++i; - --len; - } while (V == LineBuffer[i] && len && count != 15); - - AssembleByte(5, count); - AssembleByte(ColorBits, V); + i += count; + len -= count; + AssembleByte(5, count-1); + AssembleByte(ColorBits, V & ColorMask); } else { /* Make packed literal packet */ - d_ptr = differ; - V = LineBuffer[i++]; - *d_ptr++ = V; - --len; - count = 0; - while (ChoosePackagingMode(len, i, ColorBits, LineBuffer) == 0 && len && count != 15) { - V = LineBuffer[i++]; - *d_ptr++ = V; - ++count; - --len; - } - - AssembleByte(5, count | 0x10); - d_ptr = differ; + AssembleByte(5, (count-1) | 0x10); do { - AssembleByte(ColorBits, *d_ptr++); - } while (--count >= 0); - + AssembleByte(ColorBits, LineBuffer[i]); + i++; + len--; + } while (--count > 0); } } - AssembleByte(8, 0); + /* Force EOL for shaped? AssembleByte(5, 0); */ + AssembleByte(7, 0); /* Write the buffer to file */ WriteOutBuffer(D); break; - - case smShaped: - if (LastOpaquePixel > -1) { - if (LastOpaquePixel < len - 1) { - len = LastOpaquePixel + 1; - } - i = 0; - while (len) { - if (ChoosePackagingMode(len, i, ColorBits, LineBuffer)) { - /* Make runlength packet */ - V = LineBuffer[i]; - ++i; - --len; - count = 0; - do { - ++count; - ++i; - --len; - } while (V == LineBuffer[i] && len && count != 15); - - AssembleByte(5, count); - AssembleByte(ColorBits, V); - - } else { - /* Make packed literal packet */ - d_ptr = differ; - V = LineBuffer[i++]; - *d_ptr++ = V; - --len; - count = 0; - while (ChoosePackagingMode(len, i, ColorBits, LineBuffer) == 0 && len && count != 15) { - V = LineBuffer[i++]; - *d_ptr++ = V; - ++count; - --len; - } - - AssembleByte(5, count | 0x10); - d_ptr = differ; - do { - AssembleByte(ColorBits, *d_ptr++); - } while (--count >= 0); - - } - } - AssembleByte(5, 0); - AssembleByte(8, 0); - /* Write the buffer to file */ - WriteOutBuffer(D); - } - break; } } @@ -373,10 +487,10 @@ StrBuf* GenLynxSprite (const Bitmap* B, const Collection* A) ** returned. ** ** The Lynx will draw 4 quadrants: -** - Down right -** - Up right -** - Up left -** - Down left +** 0 - Down right +** 1 - Up right +** 2 - Up left +** 3 - Down left ** ** The sprite will end with a byte 0. */ @@ -388,13 +502,24 @@ StrBuf* GenLynxSprite (const Bitmap* B, const Collection* A) char ColorBits; char ColorMask; char EdgeIndex; + char Quadrant; + char quad; + char BPP; + /* The default mapping is 1:1 plus extra colours become 0 */ + char Map[17] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,0}; + signed PenColors; + char PenPal[18]; + signed Val; /* Get EdgeIndex */ EdgeIndex = GetEdgeIndex (A); + /* Get Quadrant for starting the draw process */ + Quadrant = GetQuadrant (A) & 3; + /* Action point of the sprite */ - OX = GetActionPointX (A); - OY = GetActionPointY (A); + OX = GetActionPointX (B, A); + OY = GetActionPointY (B, A); if (OX >= GetBitmapWidth (B)) { Error ("Action point X cannot be larger than bitmap width"); } @@ -410,145 +535,293 @@ StrBuf* GenLynxSprite (const Bitmap* B, const Collection* A) /* Get the sprite mode */ M = GetMode (A); - /* Now check if bitmap indexes are ok */ - if (GetBitmapColors (B) > 16) { - Error ("Too many colors for a Lynx sprite"); + /* Now check how to do the mapping */ + if (GetPenpal (B, A, &PenPal[0])) { + signed I; + + /* Modify the map by content of PenPal */ + PenColors = strlen(PenPal); + for (I = 0; I < PenColors; I++) { + switch (PenPal[I]) { + case '0': + Map[0] = I; + break; + case '1': + Map[1] = I; + break; + case '2': + Map[2] = I; + break; + case '3': + Map[3] = I; + break; + case '4': + Map[4] = I; + break; + case '5': + Map[5] = I; + break; + case '6': + Map[6] = I; + break; + case '7': + Map[7] = I; + break; + case '8': + Map[8] = I; + break; + case '9': + Map[9] = I; + break; + case 'a': + case 'A': + Map[10] = I; + break; + case 'b': + case 'B': + Map[11] = I; + break; + case 'c': + case 'C': + Map[12] = I; + break; + case 'd': + case 'D': + Map[13] = I; + break; + case 'e': + case 'E': + Map[14] = I; + break; + case 'f': + case 'F': + Map[15] = I; + break; + /* The X is reserved as transparency. This allows for shaped sprites */ + case 'x': + case 'X': + Map[16] = I; + break; + } + } + } else { + PenColors = GetBitmapColors (B); } ColorBits = 4; - ColorMask = 0x0f; - if (GetBitmapColors (B) < 9) { + if (PenColors < 9) { ColorBits = 3; - ColorMask = 0x07; } - if (GetBitmapColors (B) < 5) { + if (PenColors < 5) { ColorBits = 2; - ColorMask = 0x03; } - if (GetBitmapColors (B) < 3) { + if (PenColors < 3) { ColorBits = 1; - ColorMask = 0x01; } + BPP = GetBPP (A); + if (BPP > 0) { + ColorBits = BPP; + } + switch (ColorBits) { + case 1: + ColorMask = 0x01; + break; + case 2: + ColorMask = 0x03; + break; + case 3: + ColorMask = 0x07; + break; + default: + case 4: + ColorMask = 0x0f; + break; + } + /* B->BPP = ColorBits; */ /* Create the output buffer and resize it to the required size. */ D = NewStrBuf (); SB_Realloc (D, 63); - /* Convert the image for quadrant bottom right */ - for (Y = OY; Y < (signed)GetBitmapHeight (B); ++Y) { - signed i = 0; - signed LastOpaquePixel = -1; - char LineBuffer[512]; /* The maximum size is 508 pixels */ + for (quad = 0; quad < 4; quad++) { + switch ((Quadrant + quad) & 3) { + case 0: + /* Convert the image for quadrant bottom right */ + for (Y = OY; Y < (signed)GetBitmapHeight (B); ++Y) { + signed i = 0; + signed LastOpaquePixel = -1; + char LineBuffer[512]; /* The maximum size is 508 pixels */ - /* Fill the LineBuffer for easier optimisation */ - for (X = OX; X < (signed)GetBitmapWidth (B); ++X) { + /* Fill the LineBuffer for easier optimisation */ + for (X = OX; X < (signed)GetBitmapWidth (B); ++X) { + /* Fetch next bit into byte buffer */ + Val = GetPixel (B, X, Y).Index; + if (Val > 16) Val = 16; + LineBuffer[i] = Map[Val] & ColorMask; - /* Fetch next bit into byte buffer */ - LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask; + if (Val != EdgeIndex) { + LastOpaquePixel = i; + } + ++i; + } - if (LineBuffer[i] != EdgeIndex) { - LastOpaquePixel = i; - } - ++i; - } + encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel); + } + if ((OY == 0) && (OX == 0)) { + /* Trivial case only one quadrant */ - encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel); + /* Mark end of sprite */ + SB_AppendChar (D, 0); + + /* Return the converted bitmap */ + return D; + } + if ((quad == 1) && (OY == 0)) { + /* Special case only two quadrants */ + + /* Mark end of sprite */ + SB_AppendChar (D, 0); + + /* Return the converted bitmap */ + return D; + } + break; + case 1: + /* Convert the image for quadrant top right */ + for (Y = OY - 1; Y >= 0; --Y) { + signed i = 0; + signed LastOpaquePixel = -1; + char LineBuffer[512]; /* The maximum size is 508 pixels */ + + /* Fill the LineBuffer for easier optimisation */ + for (X = OX; X < (signed)GetBitmapWidth (B); ++X) { + /* Fetch next bit into byte buffer */ + Val = GetPixel (B, X, Y).Index; + if (Val > 16) Val = 16; + + LineBuffer[i] = Map[Val] & ColorMask; + + if (Val != EdgeIndex) { + LastOpaquePixel = i; + } + ++i; + } + + encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel); + } + + if ((OY == GetBitmapHeight (B) - 1) && (OX == 0)) { + /* Trivial case only one quadrant */ + + /* Mark end of sprite */ + SB_AppendChar (D, 0); + + /* Return the converted bitmap */ + return D; + } + if ((quad == 1) && (OX == 0)) { + /* Special case only two quadrants */ + + /* Mark end of sprite */ + SB_AppendChar (D, 0); + + /* Return the converted bitmap */ + return D; + } + break; + case 2: + /* Convert the image for quadrant top left */ + for (Y = OY - 1; Y >= 0; --Y) { + signed i = 0; + signed LastOpaquePixel = -1; + char LineBuffer[512]; /* The maximum size is 508 pixels */ + + /* Fill the LineBuffer for easier optimisation */ + for (X = OX - 1; X >= 0; --X) { + /* Fetch next bit into byte buffer */ + Val = GetPixel (B, X, Y).Index; + if (Val > 16) Val = 16; + + LineBuffer[i] = Map[Val] & ColorMask; + + if (Val != EdgeIndex) { + LastOpaquePixel = i; + } + ++i; + } + + encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel); + } + if ((OY == GetBitmapHeight (B) - 1) && (OX == GetBitmapWidth (B) - 1)) { + /* Trivial case only one quadrant */ + + /* Mark end of sprite */ + SB_AppendChar (D, 0); + + /* Return the converted bitmap */ + return D; + } + if ((quad == 1) && (OY == GetBitmapHeight (B) - 1)) { + /* Special case only two quadrants */ + + /* Mark end of sprite */ + SB_AppendChar (D, 0); + + /* Return the converted bitmap */ + return D; + } + break; + case 3: + /* Convert the image for quadrant bottom left */ + for (Y = OY; Y < (signed)GetBitmapHeight (B); ++Y) { + signed i = 0; + signed LastOpaquePixel = -1; + char LineBuffer[512]; /* The maximum size is 508 pixels */ + + /* Fill the LineBuffer for easier optimisation */ + for (X = OX - 1; X >= 0; --X) { + /* Fetch next bit into byte buffer */ + Val = GetPixel (B, X, Y).Index; + if (Val > 16) Val = 16; + + LineBuffer[i] = Map[Val] & ColorMask; + + if (Val != EdgeIndex) { + LastOpaquePixel = i; + } + ++i; + } + + encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel); + } + if ((OY == 0) && (OX == GetBitmapWidth (B) - 1)) { + /* Trivial case only one quadrant */ + + /* Mark end of sprite */ + SB_AppendChar (D, 0); + + /* Return the converted bitmap */ + return D; + } + if ((quad == 1) && (OX == GetBitmapWidth (B) - 1)) { + /* Special case only two quadrants */ + + /* Mark end of sprite */ + SB_AppendChar (D, 0); + + /* Return the converted bitmap */ + return D; + } + break; + } + if (quad < 3) { + /* Next quadrant */ + SB_AppendChar (D, 1); + } else { + /* End sprite */ + SB_AppendChar (D, 0); + } } - if ((OY == 0) && (OX == 0)) { - /* Trivial case only one quadrant */ - - /* Mark end of sprite */ - SB_AppendChar (D, 0); - - /* Return the converted bitmap */ - return D; - } - - /* Next quadrant */ - SB_AppendChar (D, 1); - - /* Convert the image for quadrant top right */ - for (Y = OY - 1; Y >= 0; --Y) { - signed i = 0; - signed LastOpaquePixel = -1; - char LineBuffer[512]; /* The maximum size is 508 pixels */ - - /* Fill the LineBuffer for easier optimisation */ - for (X = OX; X < (signed)GetBitmapWidth (B); ++X) { - - /* Fetch next bit into byte buffer */ - LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask; - - if (LineBuffer[i] != EdgeIndex) { - LastOpaquePixel = i; - } - ++i; - } - - encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel); - } - - if (OX == 0) { - /* Special case only two quadrants */ - - /* Mark end of sprite */ - SB_AppendChar (D, 0); - - /* Return the converted bitmap */ - return D; - } - - /* Next quadrant */ - SB_AppendChar (D, 1); - - /* Convert the image for quadrant top left */ - for (Y = OY - 1; Y >= 0; --Y) { - signed i = 0; - signed LastOpaquePixel = -1; - char LineBuffer[512]; /* The maximum size is 508 pixels */ - - /* Fill the LineBuffer for easier optimisation */ - for (X = OX - 1; X >= 0; --X) { - - /* Fetch next bit into byte buffer */ - LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask; - - if (LineBuffer[i] != EdgeIndex) { - LastOpaquePixel = i; - } - ++i; - } - - encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel); - } - - /* Next quadrant */ - SB_AppendChar (D, 1); - - /* Convert the image for quadrant bottom left */ - for (Y = OY; Y < (signed)GetBitmapHeight (B); ++Y) { - signed i = 0; - signed LastOpaquePixel = -1; - char LineBuffer[512]; /* The maximum size is 508 pixels */ - - /* Fill the LineBuffer for easier optimisation */ - for (X = OX - 1; X >= 0; --X) { - - /* Fetch next bit into byte buffer */ - LineBuffer[i] = GetPixel (B, X, Y).Index & ColorMask; - - if (LineBuffer[i] != EdgeIndex) { - LastOpaquePixel = i; - } - ++i; - } - - encodeSprite(D, M, ColorBits, ColorMask, LineBuffer, i, LastOpaquePixel); - } - - /* End sprite */ - SB_AppendChar (D, 0); - /* Return the converted bitmap */ return D; }