Skip to content

Commit 97572bd

Browse files
committed
Add: SpriteEncoder source code, description, and a simple decoder.
1 parent 4d3576e commit 97572bd

12 files changed

Lines changed: 1422 additions & 0 deletions

File tree

‎SpriteEncoder/32bpp_pixformat.txt‎

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
Copyright (c) 2013 Albert "Alberth" Hofkamp
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of
4+
this software and associated documentation files (the "Software"), to deal in
5+
the Software without restriction, including without limitation the rights to
6+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7+
of the Software, and to permit persons to whom the Software is furnished to do
8+
so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
SOFTWARE.
20+
21+
22+
23+
Pixel format of 32bpp images.
24+
25+
The pixel format supports image sizes of up to 65534x65534 pixels, allows
26+
transparency, and up to 256 recolour layers.
27+
28+
The file starts with
29+
- 4 bytes header "CTHG"
30+
- 2 bytes version number (low endian), currently version 2
31+
- zero or more sprites.
32+
33+
A sprite has
34+
- 4 bytes length of this sprite (low endian), length excludes these 4 bytes.
35+
- 2 bytes sprite number (low endian).
36+
- 2 bytes width of the sprite (low endian).
37+
- 2 bytes height of the sprite (low endian).
38+
- (width * height) pixel data as a continuous stream.
39+
40+
The pixel data starts at the top-left pixel, and runs horizontally (and
41+
continues at the left of the next line when reaching the end). This continues
42+
until the last pixel at the bottom-right has been written.
43+
44+
Sequences of pixels with the same characteristics are taken together in blocks
45+
of up to 64 pixels long, and encoded. There are four types of blocks:
46+
47+
1. Fixed fully opaque 32bpp pixels (all the coloured pixels that are always the
48+
same).
49+
2. Fixed partially transparent 32bpp pixels (glow effects, or 50% transparency).
50+
3. Fixed fully transparent pixels (empty space around the displayed shape to
51+
make it a rectangular image).
52+
4. Recolour layer (pixels that are retrieved from an RGB table with 256 entries
53+
that acts like a palette). The data stored in the sprite is just the index
54+
to use for each pixel. Opacity is the same for the entire block.
55+
56+
Typically there are tables with ranges of colours, and the sprite contains
57+
the intensity as the index.
58+
59+
60+
Below is the encoding for each type of pixel block:
61+
62+
1. Fixed fully opaque 32bpp pixels (RGB).
63+
- 1 byte length (values 0-63)
64+
- N x 3 byte pixel colours (RGB).
65+
66+
2. Fixed partially transparent 32bpp pixels (RGB).
67+
- 1 byte length (values 0-63) + 64
68+
- 1 byte amount of opacity (0-255).
69+
- N x 3 byte pixel colours (RGB).
70+
71+
3. Fixed fully transparent pixels.
72+
- 1 byte length (values 0-63) + 128
73+
74+
4. Recolour layer.
75+
- 1 byte length (values 0-63) + 64 + 128
76+
- 1 byte layer to apply (0-255).
77+
- 1 byte amount of opacity (0-255).
78+
- N bytes table index.

‎SpriteEncoder/ast.cpp‎

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
/*
2+
Copyright (c) 2013 Albert "Alberth" Hofkamp
3+
4+
Permission is hereby granted, free of charge, to any person obtaining a copy of
5+
this software and associated documentation files (the "Software"), to deal in
6+
the Software without restriction, including without limitation the rights to
7+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8+
of the Software, and to permit persons to whom the Software is furnished to do
9+
so, subject to the following conditions:
10+
11+
The above copyright notice and this permission notice shall be included in all
12+
copies or substantial portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
SOFTWARE.
21+
*/
22+
#include <cstdio>
23+
#include <cstdlib>
24+
#include <cassert>
25+
#include "ast.h"
26+
#include "image.h"
27+
#include "output.h"
28+
29+
enum Opacity
30+
{
31+
OP_TRANSPARENT = 0,
32+
OP_OPAQUE = 255,
33+
};
34+
35+
const std::string sUnset = "<unset>";
36+
37+
Sprite::Sprite()
38+
{
39+
m_iLine = -1;
40+
m_iSprite = -1;
41+
m_iLeft = -1;
42+
m_iTop = -1;
43+
m_iWidth = -1;
44+
m_iHeight = -1;
45+
m_sBaseImage = sUnset;
46+
m_sRecolourImage = sUnset;
47+
for (int i = 0; i < 256; i++)
48+
{
49+
m_aNumber[i] = i;
50+
}
51+
}
52+
53+
void Sprite::SetRecolour(const std::string &sFilename)
54+
{
55+
m_sRecolourImage = sFilename;
56+
}
57+
58+
static void Report(int iNumber, int iLine, const char *pMsg)
59+
{
60+
if (iNumber < 0)
61+
{
62+
fprintf(stderr, "Sprite at line %d: Missing %s\n", iLine, pMsg);
63+
exit(1);
64+
}
65+
}
66+
67+
void Sprite::Check() const
68+
{
69+
Report(m_iSprite, m_iLine, "sprite number");
70+
Report(m_iLeft, m_iLine, "left edge coordinate");
71+
Report(m_iTop, m_iLine, "top edge coordinate");
72+
Report(m_iWidth, m_iLine, "sprite width");
73+
Report(m_iHeight, m_iLine, "sprite height");
74+
75+
if (m_sBaseImage == sUnset)
76+
{
77+
fprintf(stderr, "Sprite at line %d: Missing image filename\n", m_iLine);
78+
exit(1);
79+
}
80+
}
81+
82+
/**
83+
* Look ahead in the recolour bitmaps to check when the next recoloured pixels will occur.
84+
* @param iCount Current index in the image.
85+
* @param iEndCount End of the image.
86+
* @param pLayer Recolouring bitmap (if available).
87+
* @return Number of pixels to go before the next recoloured pixel (limited to 63 look ahead).
88+
*/
89+
static int GetDistanceToNextRecolour(const uint32 iCount, const uint32 iEndCount, const Image8bpp *pLayer)
90+
{
91+
uint32 iLength = iEndCount - iCount;
92+
if (iLength > 63) iLength = 63; // No need to look ahead further.
93+
94+
if (pLayer != NULL)
95+
{
96+
for (size_t i = 0; i < iLength; i++)
97+
{
98+
if (pLayer->Get(iCount + i) != 0) return i;
99+
}
100+
}
101+
return iLength;
102+
}
103+
104+
/**
105+
* Get the recolour table to use, and the number of pixels to recolour.
106+
* @param iCount Current index in the image.
107+
* @param iEndCount End of the image.
108+
* @param pLayer Recolouring bitmap.
109+
* @param pLayerNumber [out] Number of the recolouring table to use.
110+
* @return Number of pixels to recolour from the current position.
111+
*/
112+
static int GetRecolourInformation(const uint32 iCount, const uint32 iEndCount, const Image8bpp *pLayer, uint8 *pLayerNumber)
113+
{
114+
uint32 iLength = iEndCount - iCount;
115+
if (iLength > 63) iLength = 63; // No need to look ahead further.
116+
117+
*pLayerNumber = pLayer->Get(iCount);
118+
for (size_t i = 1; i < iLength; i++)
119+
{
120+
if (pLayer->Get(iCount + i) != *pLayerNumber) return i;
121+
}
122+
return iLength;
123+
}
124+
125+
/**
126+
* Look ahead in the base image to check how many pixels from the current position have the same opacity.
127+
* @param iCount Current index in the image.
128+
* @param iEndCount End of the image.
129+
* @parswm oBase Base image.
130+
* @return Number of pixels to go before the opacity of the current pixel changes (limited to 63 look ahead).
131+
*/
132+
static int GetDistanceToNextTransparency(const uint32 iCount, const uint32 iEndCount, const Image32bpp &oBase)
133+
{
134+
uint32 iLength = iEndCount - iCount;
135+
if (iLength > 63) iLength = 63; // No need to look ahead further.
136+
137+
uint8 iOpacity = GetA(oBase.Get(iCount));
138+
for (uint i = 1; i < iLength; i++)
139+
{
140+
if (iOpacity != GetA(oBase.Get(iCount + i))) return i;
141+
}
142+
return iLength;
143+
}
144+
145+
/**
146+
* Write the RGB colour for the next \a iLength pixels, starting from the \a iCount offset.
147+
* @param oBase Base image to encode.
148+
* @param iCount Current index in the image.
149+
* @param iLength Number of pixels to process.
150+
* @param pDest Destination to write to.
151+
*/
152+
static void WriteColour(const Image32bpp &oBase, uint32 iCount, int iLength, Output *pDest)
153+
{
154+
while (iLength > 0)
155+
{
156+
uint32 iColour = oBase.Get(iCount);
157+
iCount++;
158+
pDest->Uint8(GetR(iColour));
159+
pDest->Uint8(GetG(iColour));
160+
pDest->Uint8(GetB(iColour));
161+
iLength--;
162+
}
163+
}
164+
165+
/**
166+
* Write the table index for the next \a iLength pixels, starting from the \a iCount offset.
167+
* @param oBase Base image to encode.
168+
* @param iCount Current index in the image.
169+
* @param iLength Number of pixels to process.
170+
* @param pDest Destination to write to.
171+
*/
172+
static void WriteTableIndex(const Image32bpp &oBase, uint32 iCount, int iLength, Output *pDest)
173+
{
174+
while (iLength > 0)
175+
{
176+
uint32 iColour = oBase.Get(iCount);
177+
iCount++;
178+
uint8 biggest = GetR(iColour);
179+
if (biggest < GetG(iColour)) biggest = GetG(iColour);
180+
if (biggest < GetB(iColour)) biggest = GetB(iColour);
181+
pDest->Uint8(biggest);
182+
iLength--;
183+
}
184+
}
185+
186+
/**
187+
* Encode a 32bpp image from the \a oBase image, and optionally the recolouring \a pLayer bitmap.
188+
*/
189+
static void Encode32bpp(int iWidth, int iHeight, const Image32bpp &oBase, const Image8bpp *pLayer, Output *pDest, const unsigned char *pNumber)
190+
{
191+
const uint32 iPixCount = iWidth * iHeight;
192+
uint32 iCount = 0;
193+
while (iCount < iPixCount)
194+
{
195+
int iLength = GetDistanceToNextRecolour(iCount, iPixCount, pLayer);
196+
int length2 = GetDistanceToNextTransparency(iCount, iPixCount, oBase);
197+
198+
if (iLength > 63) iLength = 63;
199+
if (iLength == 0) { // Recolour layer.
200+
uint8 iTableNumber;
201+
iLength = GetRecolourInformation(iCount, iPixCount, pLayer, &iTableNumber);
202+
if (length2 < iLength) iLength = length2;
203+
assert(iLength > 0);
204+
205+
pDest->Uint8(64 + 128 + iLength);
206+
pDest->Uint8(pNumber[iTableNumber]);
207+
pDest->Uint8(GetA(oBase.Get(iCount))); // Opacity.
208+
WriteTableIndex(oBase, iCount, iLength, pDest);
209+
iCount += iLength;
210+
continue;
211+
}
212+
if (length2 < iLength) iLength = length2;
213+
assert(iLength > 0);
214+
215+
uint8 iOpacity = GetA(oBase.Get(iCount));
216+
if (iOpacity == OP_OPAQUE) { // Fixed non-transparent 32bpp pixels (RGB).
217+
pDest->Uint8(iLength);
218+
WriteColour(oBase, iCount, iLength, pDest);
219+
iCount += iLength;
220+
continue;
221+
}
222+
if (iOpacity == OP_TRANSPARENT) { // Fixed fully transparent pixels.
223+
pDest->Uint8(128 + iLength);
224+
iCount += iLength;
225+
continue;
226+
}
227+
/* Partially transparent 32bpp pixels (RGB). */
228+
pDest->Uint8(64 + iLength);
229+
pDest->Uint8(iOpacity);
230+
WriteColour(oBase, iCount, iLength, pDest);
231+
iCount += iLength;
232+
continue;
233+
}
234+
}
235+
236+
void Sprite::Write(Output *pOut) const
237+
{
238+
Image32bpp *pBase = Load32Bpp(m_sBaseImage, m_iLine, m_iLeft, m_iWidth, m_iTop, m_iHeight);
239+
if (pBase == NULL)
240+
{
241+
fprintf(stderr, "Warning: Skipping sprite %d at line %d: Image load failed.\n", m_iSprite, m_iLine);
242+
return;
243+
}
244+
245+
Image8bpp *pLayer = NULL;
246+
if (m_sRecolourImage != sUnset)
247+
{
248+
pLayer = Load8Bpp(m_sRecolourImage, m_iLine, m_iLeft, m_iWidth, m_iTop, m_iHeight);
249+
}
250+
251+
int iAddress = pOut->Reserve(4);
252+
pOut->Uint16(m_iSprite);
253+
pOut->Uint16(m_iWidth);
254+
pOut->Uint16(m_iHeight); // XXX Add length of this sprite?
255+
Encode32bpp(m_iWidth, m_iHeight, *pBase, pLayer, pOut, m_aNumber);
256+
257+
int iLength = pOut->Reserve(0) - (iAddress + 4); // Start counting after the length.
258+
pOut->Write(iAddress, iLength & 0xFF);
259+
pOut->Write(iAddress + 1, (iLength >> 8) & 0xFF);
260+
pOut->Write(iAddress + 2, (iLength >> 16) & 0xFF);
261+
pOut->Write(iAddress + 3, (iLength >> 24) & 0xFF);
262+
263+
delete pLayer;
264+
delete pBase;
265+
}
266+
267+
// vim: et sw=4 ts=4 sts=4

0 commit comments

Comments
 (0)