Add: SpriteEncoder source code, description, and a simple decoder.

This commit is contained in:
Alberth
2013-10-08 20:45:08 +02:00
committed by Alberth
parent 4d3576e7e6
commit 97572bd931
12 changed files with 1422 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
Copyright (c) 2013 Albert "Alberth" Hofkamp
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Pixel format of 32bpp images.
The pixel format supports image sizes of up to 65534x65534 pixels, allows
transparency, and up to 256 recolour layers.
The file starts with
- 4 bytes header "CTHG"
- 2 bytes version number (low endian), currently version 2
- zero or more sprites.
A sprite has
- 4 bytes length of this sprite (low endian), length excludes these 4 bytes.
- 2 bytes sprite number (low endian).
- 2 bytes width of the sprite (low endian).
- 2 bytes height of the sprite (low endian).
- (width * height) pixel data as a continuous stream.
The pixel data starts at the top-left pixel, and runs horizontally (and
continues at the left of the next line when reaching the end). This continues
until the last pixel at the bottom-right has been written.
Sequences of pixels with the same characteristics are taken together in blocks
of up to 64 pixels long, and encoded. There are four types of blocks:
1. Fixed fully opaque 32bpp pixels (all the coloured pixels that are always the
same).
2. Fixed partially transparent 32bpp pixels (glow effects, or 50% transparency).
3. Fixed fully transparent pixels (empty space around the displayed shape to
make it a rectangular image).
4. Recolour layer (pixels that are retrieved from an RGB table with 256 entries
that acts like a palette). The data stored in the sprite is just the index
to use for each pixel. Opacity is the same for the entire block.
Typically there are tables with ranges of colours, and the sprite contains
the intensity as the index.
Below is the encoding for each type of pixel block:
1. Fixed fully opaque 32bpp pixels (RGB).
- 1 byte length (values 0-63)
- N x 3 byte pixel colours (RGB).
2. Fixed partially transparent 32bpp pixels (RGB).
- 1 byte length (values 0-63) + 64
- 1 byte amount of opacity (0-255).
- N x 3 byte pixel colours (RGB).
3. Fixed fully transparent pixels.
- 1 byte length (values 0-63) + 128
4. Recolour layer.
- 1 byte length (values 0-63) + 64 + 128
- 1 byte layer to apply (0-255).
- 1 byte amount of opacity (0-255).
- N bytes table index.

267
SpriteEncoder/ast.cpp Normal file
View File

@@ -0,0 +1,267 @@
/*
Copyright (c) 2013 Albert "Alberth" Hofkamp
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <cstdio>
#include <cstdlib>
#include <cassert>
#include "ast.h"
#include "image.h"
#include "output.h"
enum Opacity
{
OP_TRANSPARENT = 0,
OP_OPAQUE = 255,
};
const std::string sUnset = "<unset>";
Sprite::Sprite()
{
m_iLine = -1;
m_iSprite = -1;
m_iLeft = -1;
m_iTop = -1;
m_iWidth = -1;
m_iHeight = -1;
m_sBaseImage = sUnset;
m_sRecolourImage = sUnset;
for (int i = 0; i < 256; i++)
{
m_aNumber[i] = i;
}
}
void Sprite::SetRecolour(const std::string &sFilename)
{
m_sRecolourImage = sFilename;
}
static void Report(int iNumber, int iLine, const char *pMsg)
{
if (iNumber < 0)
{
fprintf(stderr, "Sprite at line %d: Missing %s\n", iLine, pMsg);
exit(1);
}
}
void Sprite::Check() const
{
Report(m_iSprite, m_iLine, "sprite number");
Report(m_iLeft, m_iLine, "left edge coordinate");
Report(m_iTop, m_iLine, "top edge coordinate");
Report(m_iWidth, m_iLine, "sprite width");
Report(m_iHeight, m_iLine, "sprite height");
if (m_sBaseImage == sUnset)
{
fprintf(stderr, "Sprite at line %d: Missing image filename\n", m_iLine);
exit(1);
}
}
/**
* Look ahead in the recolour bitmaps to check when the next recoloured pixels will occur.
* @param iCount Current index in the image.
* @param iEndCount End of the image.
* @param pLayer Recolouring bitmap (if available).
* @return Number of pixels to go before the next recoloured pixel (limited to 63 look ahead).
*/
static int GetDistanceToNextRecolour(const uint32 iCount, const uint32 iEndCount, const Image8bpp *pLayer)
{
uint32 iLength = iEndCount - iCount;
if (iLength > 63) iLength = 63; // No need to look ahead further.
if (pLayer != NULL)
{
for (size_t i = 0; i < iLength; i++)
{
if (pLayer->Get(iCount + i) != 0) return i;
}
}
return iLength;
}
/**
* Get the recolour table to use, and the number of pixels to recolour.
* @param iCount Current index in the image.
* @param iEndCount End of the image.
* @param pLayer Recolouring bitmap.
* @param pLayerNumber [out] Number of the recolouring table to use.
* @return Number of pixels to recolour from the current position.
*/
static int GetRecolourInformation(const uint32 iCount, const uint32 iEndCount, const Image8bpp *pLayer, uint8 *pLayerNumber)
{
uint32 iLength = iEndCount - iCount;
if (iLength > 63) iLength = 63; // No need to look ahead further.
*pLayerNumber = pLayer->Get(iCount);
for (size_t i = 1; i < iLength; i++)
{
if (pLayer->Get(iCount + i) != *pLayerNumber) return i;
}
return iLength;
}
/**
* Look ahead in the base image to check how many pixels from the current position have the same opacity.
* @param iCount Current index in the image.
* @param iEndCount End of the image.
* @parswm oBase Base image.
* @return Number of pixels to go before the opacity of the current pixel changes (limited to 63 look ahead).
*/
static int GetDistanceToNextTransparency(const uint32 iCount, const uint32 iEndCount, const Image32bpp &oBase)
{
uint32 iLength = iEndCount - iCount;
if (iLength > 63) iLength = 63; // No need to look ahead further.
uint8 iOpacity = GetA(oBase.Get(iCount));
for (uint i = 1; i < iLength; i++)
{
if (iOpacity != GetA(oBase.Get(iCount + i))) return i;
}
return iLength;
}
/**
* Write the RGB colour for the next \a iLength pixels, starting from the \a iCount offset.
* @param oBase Base image to encode.
* @param iCount Current index in the image.
* @param iLength Number of pixels to process.
* @param pDest Destination to write to.
*/
static void WriteColour(const Image32bpp &oBase, uint32 iCount, int iLength, Output *pDest)
{
while (iLength > 0)
{
uint32 iColour = oBase.Get(iCount);
iCount++;
pDest->Uint8(GetR(iColour));
pDest->Uint8(GetG(iColour));
pDest->Uint8(GetB(iColour));
iLength--;
}
}
/**
* Write the table index for the next \a iLength pixels, starting from the \a iCount offset.
* @param oBase Base image to encode.
* @param iCount Current index in the image.
* @param iLength Number of pixels to process.
* @param pDest Destination to write to.
*/
static void WriteTableIndex(const Image32bpp &oBase, uint32 iCount, int iLength, Output *pDest)
{
while (iLength > 0)
{
uint32 iColour = oBase.Get(iCount);
iCount++;
uint8 biggest = GetR(iColour);
if (biggest < GetG(iColour)) biggest = GetG(iColour);
if (biggest < GetB(iColour)) biggest = GetB(iColour);
pDest->Uint8(biggest);
iLength--;
}
}
/**
* Encode a 32bpp image from the \a oBase image, and optionally the recolouring \a pLayer bitmap.
*/
static void Encode32bpp(int iWidth, int iHeight, const Image32bpp &oBase, const Image8bpp *pLayer, Output *pDest, const unsigned char *pNumber)
{
const uint32 iPixCount = iWidth * iHeight;
uint32 iCount = 0;
while (iCount < iPixCount)
{
int iLength = GetDistanceToNextRecolour(iCount, iPixCount, pLayer);
int length2 = GetDistanceToNextTransparency(iCount, iPixCount, oBase);
if (iLength > 63) iLength = 63;
if (iLength == 0) { // Recolour layer.
uint8 iTableNumber;
iLength = GetRecolourInformation(iCount, iPixCount, pLayer, &iTableNumber);
if (length2 < iLength) iLength = length2;
assert(iLength > 0);
pDest->Uint8(64 + 128 + iLength);
pDest->Uint8(pNumber[iTableNumber]);
pDest->Uint8(GetA(oBase.Get(iCount))); // Opacity.
WriteTableIndex(oBase, iCount, iLength, pDest);
iCount += iLength;
continue;
}
if (length2 < iLength) iLength = length2;
assert(iLength > 0);
uint8 iOpacity = GetA(oBase.Get(iCount));
if (iOpacity == OP_OPAQUE) { // Fixed non-transparent 32bpp pixels (RGB).
pDest->Uint8(iLength);
WriteColour(oBase, iCount, iLength, pDest);
iCount += iLength;
continue;
}
if (iOpacity == OP_TRANSPARENT) { // Fixed fully transparent pixels.
pDest->Uint8(128 + iLength);
iCount += iLength;
continue;
}
/* Partially transparent 32bpp pixels (RGB). */
pDest->Uint8(64 + iLength);
pDest->Uint8(iOpacity);
WriteColour(oBase, iCount, iLength, pDest);
iCount += iLength;
continue;
}
}
void Sprite::Write(Output *pOut) const
{
Image32bpp *pBase = Load32Bpp(m_sBaseImage, m_iLine, m_iLeft, m_iWidth, m_iTop, m_iHeight);
if (pBase == NULL)
{
fprintf(stderr, "Warning: Skipping sprite %d at line %d: Image load failed.\n", m_iSprite, m_iLine);
return;
}
Image8bpp *pLayer = NULL;
if (m_sRecolourImage != sUnset)
{
pLayer = Load8Bpp(m_sRecolourImage, m_iLine, m_iLeft, m_iWidth, m_iTop, m_iHeight);
}
int iAddress = pOut->Reserve(4);
pOut->Uint16(m_iSprite);
pOut->Uint16(m_iWidth);
pOut->Uint16(m_iHeight); // XXX Add length of this sprite?
Encode32bpp(m_iWidth, m_iHeight, *pBase, pLayer, pOut, m_aNumber);
int iLength = pOut->Reserve(0) - (iAddress + 4); // Start counting after the length.
pOut->Write(iAddress, iLength & 0xFF);
pOut->Write(iAddress + 1, (iLength >> 8) & 0xFF);
pOut->Write(iAddress + 2, (iLength >> 16) & 0xFF);
pOut->Write(iAddress + 3, (iLength >> 24) & 0xFF);
delete pLayer;
delete pBase;
}
// vim: et sw=4 ts=4 sts=4

75
SpriteEncoder/ast.h Normal file
View File

@@ -0,0 +1,75 @@
/*
Copyright (c) 2013 Albert "Alberth" Hofkamp
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef AST_H
#define AST_H
#include <set>
#include <deque>
#include <string>
class Output;
class Sprite
{
public:
Sprite();
void SetRecolour(const std::string &filename);
void Check() const;
void Write(Output *out) const;
int m_iLine; // Line number of the sprite.
int m_iSprite; // Sprite number.
int m_iLeft; // Left coordinate in the images.
int m_iTop; // Top coordinate in the images.
int m_iWidth; // Width of the images.
int m_iHeight; // Height of the images.
std::string m_sBaseImage; // Full-colour RGBA base image.
std::string m_sRecolourImage; // Name of overlay image (if set).
unsigned char m_aNumber[256]; // Layer number of the recolouring.
};
inline static bool operator<(const Sprite &s1, const Sprite &s2)
{
return s1.m_iSprite < s2.m_iSprite;
}
struct ScannerData
{
int line;
int number;
std::string text;
Sprite *m_pSprite;
};
#define YYSTYPE ScannerData
extern YYSTYPE yylval;
int yylex();
int yyparse();
void yyerror(const char *msg);
void SetupScanner(const char *fname, FILE *new_file);
extern std::set<Sprite> g_oSprites;
#endif

161
SpriteEncoder/decode.py Normal file
View File

@@ -0,0 +1,161 @@
#!/usr/bin/env python3
"""
Program to decode the first sprite of a CTHG 2 file.
Mainly intended as a test for the checking the encoder, but also a demonstration of how to decode.
"""
_license = """
Copyright (c) 2013 Alberth "Alberth" Hofkamp
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
from PIL import Image
class Infile:
def __init__(self, fname):
self.fname = fname
self.handle = open(self.fname, "rb")
# Read header
for h in [ord('C'), ord('T'), ord('H'), ord('G'), 2, 0]:
v = self.getByte()
assert v == h
def getByte(self):
v = self.handle.read(1)[0]
return v
def getWord(self):
b = self.getByte()
return b | (self.getByte() << 8)
def getLong(self):
w = self.getWord()
return w | (self.getWord() << 16)
def getData(self, size):
data = []
for i in range(size):
data.append(self.getByte())
return data
def decode_xy(pix_idx, w, h):
y = pix_idx // w
x = pix_idx - w * y
assert x >= 0 and x < w
assert y >= 0 and y < h
return x, y
def get_colour(table, idx):
if table == 0:
return (0, 0, 0, 255)
if table == 1:
return (idx, 0, 0, 255)
if table == 2:
return (0, idx, 0, 255)
if table == 3:
return (0, 0, idx, 255)
if table == 4:
return (0, idx, idx, 255)
if table == 5:
return (idx, 0, idx, 255)
assert False
class Sprite:
def __init__(self, infile):
size = infile.getLong() - 2 - 2 - 2
self.number = infile.getWord()
self.width = infile.getWord()
self.height = infile.getWord()
self.data = infile.getData(size)
print("Sprite number {}".format(self.number))
print("Width {}".format(self.width))
print("Height {}".format(self.height))
print("Size {}".format(size))
print("Data size {}".format(len(self.data)))
def get_data(self, idx):
return self.data[idx], idx + 1
def save(self):
im = Image.new("RGBA", (self.width, self.height), (0,0,0,0))
pix = im.load()
idx = 0
pix_idx = 0
while idx < len(self.data):
length, idx = self.get_data(idx)
if length <= 63: # Fixed non-transparent 32bpp pixels (RGB)
length = length & 63
x, y = decode_xy(pix_idx, self.width, self.height)
for i in range(length):
d = (self.data[idx], self.data[idx+1], self.data[idx+2], 255)
pix[x,y] = d
idx = idx + 3
pix_idx = pix_idx + 1
x = x + 1
if x == self.width:
x = 0
y = y + 1
continue
elif length <= 64+63: # Partially transparent 32bpp pixels (RGB)
length = length & 63
opacity, idx = self.get_data(idx)
x, y = decode_xy(pix_idx, self.width, self.height)
for i in range(length):
d = (self.data[idx], self.data[idx+1], self.data[idx+2], opacity)
pix[x,y] = d
idx = idx + 3
pix_idx = pix_idx + 1
x = x + 1
if x == self.width:
x = 0
y = y + 1
continue
elif length <= 128+63: # Fixed fully transparent pixels
length = length & 63
pix_idx = pix_idx + length
continue
else: # Recolour layer.
length = length & 63
table, idx = self.get_data(idx)
opacity, idx = self.get_data(idx)
x, y = decode_xy(pix_idx, self.width, self.height)
for i in range(length):
col, idx = self.get_data(idx)
pix[x, y] = get_colour(table, col)
pix_idx = pix_idx + 1
x = x + 1
if x == self.width:
x = 0
y = y + 1
continue
im.save("sprite_" + str(self.number) + ".png")
inf = Infile("x.out")
spr = Sprite(inf)
spr.save()

89
SpriteEncoder/encode.cpp Normal file
View File

@@ -0,0 +1,89 @@
/*
Copyright (c) 2013 Albert "Alberth" Hofkamp
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "ast.h"
#include "output.h"
int main(int iArgc, char *pArgv[])
{
FILE *pInfile = NULL;
const char *pOutfname = NULL;
if (iArgc == 1)
{
SetupScanner(NULL, NULL);
}
else if (strcmp(pArgv[1], "-h") == 0 || strcmp(pArgv[1], "--help") == 0)
{
printf("Usage: encode <sprite-file> [<output-file>]\n");
exit(0);
}
else if (iArgc == 2)
{
pInfile = fopen(pArgv[1], "r");
SetupScanner(pArgv[1], pInfile);
}
else if (iArgc == 3)
{
pInfile = fopen(pArgv[1], "r");
SetupScanner(pArgv[1], pInfile);
pOutfname = pArgv[2];
}
else
{
fprintf(stderr, "Too many arguments, try 'encode -h'\n");
exit(1);
}
int iRet = yyparse();
if (pInfile != NULL) fclose(pInfile);
if (iRet != 0)
{
exit(iRet);
}
for (std::set<Sprite>::iterator iter = g_oSprites.begin(); iter != g_oSprites.end(); iter++)
{
iter->Check();
}
Output out;
out.Uint8('C'); out.Uint8('T'); out.Uint8('H'); out.Uint8('G');
out.Uint8(2); out.Uint8(0);
for (std::set<Sprite>::iterator iter = g_oSprites.begin(); iter != g_oSprites.end(); iter++)
{
iter->Write(&out);
}
if (pOutfname != NULL)
{
out.Write(pOutfname);
}
exit(0);
}
// vim: et sw=4 ts=4 sts=4

263
SpriteEncoder/image.cpp Normal file
View File

@@ -0,0 +1,263 @@
/*
Copyright (c) 2013 Albert "Alberth" Hofkamp
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <cstdlib>
#include <cassert>
#include <string>
#include <png.h>
#include "image.h"
uint32 MakeRGBA(uint8 r, uint8 g, uint8 b, uint8 a)
{
assert(sizeof(uint32) == 4); // Not really the good place, but it has to be checked somewhere!
uint32 ret;
uint32 x;
x = r; ret = x;
x = g; ret |= (x << 8);
x = b; ret |= (x << 16);
x = a; ret |= (x << 24);
return ret;
}
uint8 GetR(uint32 rgba) { return rgba & 0xFF; }
uint8 GetG(uint32 rgba) { return (rgba >> 8) & 0xFF; }
uint8 GetB(uint32 rgba) { return (rgba >> 16) & 0xFF; }
uint8 GetA(uint32 rgba) { return (rgba >> 24) & 0xFF; }
Image32bpp::Image32bpp(int iWidth, int iHeight)
{
this->iWidth = iWidth;
this->iHeight = iHeight;
pData = (uint32 *)malloc(4 * iWidth * iHeight);
}
Image32bpp::~Image32bpp()
{
free(pData);
}
uint32 Image32bpp::Get(int offset) const
{
int x, y;
y = offset / iWidth;
x = offset - y * iWidth;
assert(x >= 0 && x < iWidth && y >= 0 && y < iHeight);
return pData[offset];
}
Image8bpp::Image8bpp(int iWidth, int iHeight)
{
this->iWidth = iWidth;
this->iHeight = iHeight;
pData = (uint8 *)malloc(iWidth * iHeight);
}
Image8bpp::~Image8bpp()
{
free(pData);
}
unsigned char Image8bpp::Get(int offset) const
{
int x, y;
y = offset / iWidth;
x = offset - y * iWidth;
assert(x >= 0 && x < iWidth && y >= 0 && y < iHeight);
return pData[offset];
}
static void OpenFile(const std::string &sFilename, png_structp *pngPtr, png_infop *infoPtr, png_infop *endInfo, uint8 ***pRows)
{
FILE *pFile = fopen(sFilename.c_str(), "rb");
if(pFile == NULL)
{
fprintf(stderr, "PNG file \"%s\" could not be opened.\n", sFilename.c_str());
exit(1);
}
unsigned char header[4];
if(fread(header, 1, 4, pFile) != 4)
{
fprintf(stderr, "Could not read header of \"%s\".\n", sFilename.c_str());
fclose(pFile);
exit(1);
}
bool bIsPng = !png_sig_cmp(header, 0, 4);
if(!bIsPng)
{
fprintf(stderr, "Header of \"%s\" indicates it is not a PNG file.\n", sFilename.c_str());
fclose(pFile);
exit(1);
}
*pngPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!*pngPtr)
{
fprintf(stderr, "Could not initialize PNG data.\n");
fclose(pFile);
exit(1);
}
*infoPtr = png_create_info_struct(*pngPtr);
if(!*infoPtr)
{
fprintf(stderr, "Could not initialize PNG info data.\n");
png_destroy_read_struct(pngPtr, (png_infopp)NULL, (png_infopp)NULL);
fclose(pFile);
exit(1);
}
*endInfo = png_create_info_struct(*pngPtr);
if(!*endInfo)
{
fprintf(stderr, "Could not initialize PNG end data.\n");
png_destroy_read_struct(pngPtr, infoPtr, (png_infopp)NULL);
fclose(pFile);
exit(1);
}
/* Setup callback in case of errors. */
if(setjmp(png_jmpbuf(*pngPtr))) {
fprintf(stderr, "Error detected while reading PNG file.\n");
png_destroy_read_struct(pngPtr, infoPtr, endInfo);
fclose(pFile);
exit(1);
}
/* Initialize for file reading. */
png_init_io(*pngPtr, pFile);
png_set_sig_bytes(*pngPtr, 4);
png_read_png(*pngPtr, *infoPtr, PNG_TRANSFORM_IDENTITY, NULL);
*pRows = png_get_rows(*pngPtr, *infoPtr);
fclose(pFile);
}
Image32bpp *Load32Bpp(const std::string &sFilename, int line, int left, int width, int top, int height)
{
png_structp pngPtr;
png_infop infoPtr;
png_infop endInfo;
uint8 **pRows;
OpenFile(sFilename, &pngPtr, &infoPtr, &endInfo, &pRows);
int iWidth = png_get_image_width(pngPtr, infoPtr);
int iHeight = png_get_image_height(pngPtr, infoPtr);
int iBitDepth = png_get_bit_depth(pngPtr, infoPtr);
int iColorType = png_get_color_type(pngPtr, infoPtr);
if (iWidth < left + width)
{
fprintf(stderr, "Sprite at line %d: Sprite is not wide enough, require %d columns (%d + %d) while only %d columns are available.\n", line, left + width, left, width, iWidth);
exit(1);
}
if (iHeight < top + height)
{
fprintf(stderr, "Sprite at line %d: Sprite is not high enough, require %d rows (%d + %d) while only %d rows are available.\n", line, top + height, top, height, iHeight);
exit(1);
}
if (iBitDepth != 8)
{
fprintf(stderr, "Sprite at line %d: \"%s\" is not an 32bpp file (channels are not 8 bit wide)\n", line, sFilename.c_str());
png_destroy_read_struct(&pngPtr, &infoPtr, &endInfo);
exit(1);
}
if (iColorType != PNG_COLOR_TYPE_RGB_ALPHA)
{
fprintf(stderr, "Sprite at line %d: \"%s\" is not an RGBA file\n", line, sFilename.c_str());
png_destroy_read_struct(&pngPtr, &infoPtr, &endInfo);
exit(1);
}
Image32bpp *img = new Image32bpp(width, height);
uint32 *pData = img->pData;
for (int i = 0; i < height; i++)
{
uint8 *pRow = pRows[top + i] + left;
for (int j = 0; j < width; j++)
{
*pData++ = MakeRGBA(pRow[0], pRow[1], pRow[2], pRow[3]);
pRow += 4;
}
}
png_destroy_read_struct(&pngPtr, &infoPtr, &endInfo);
return img;
}
Image8bpp *Load8Bpp(const std::string &sFilename, int line, int left, int width, int top, int height)
{
png_structp pngPtr;
png_infop infoPtr;
png_infop endInfo;
uint8 **pRows;
OpenFile(sFilename, &pngPtr, &infoPtr, &endInfo, &pRows);
int iWidth = png_get_image_width(pngPtr, infoPtr);
int iHeight = png_get_image_height(pngPtr, infoPtr);
int iBitDepth = png_get_bit_depth(pngPtr, infoPtr);
int iColorType = png_get_color_type(pngPtr, infoPtr);
if (iWidth < left + width)
{
fprintf(stderr, "Sprite at line %d: Sprite is not wide enough, require %d columns (%d + %d) while only %d columns are available.\n", line, left + width, left, width, iWidth);
exit(1);
}
if (iHeight < top + height)
{
fprintf(stderr, "Sprite at line %d: Sprite is not high enough, require %d rows (%d + %d) while only %d rows are available.\n", line, top + height, top, height, iHeight);
exit(1);
}
if (iBitDepth != 8)
{
fprintf(stderr, "Sprite at line %d: \"%s\" is not an 8bpp file (the channel is not 8 bit wide\n", line, sFilename.c_str());
png_destroy_read_struct(&pngPtr, &infoPtr, &endInfo);
exit(1);
}
if (iColorType != PNG_COLOR_TYPE_PALETTE)
{
fprintf(stderr, "Sprite at line %d: \"%s\" is not a palleted image file\n", line, sFilename.c_str());
png_destroy_read_struct(&pngPtr, &infoPtr, &endInfo);
exit(1);
}
Image8bpp *img = new Image8bpp(width, height);
uint8 *pData = img->pData;
for (int i = 0; i < height; i++)
{
int y = top + i;
for (int j = 0; j < width; j++)
{
int x = left + j;
uint8 v = pRows[y][x];
*pData = v;
pData++;
}
}
return img;
}
// vim: et sw=4 ts=4 sts=4

65
SpriteEncoder/image.h Normal file
View File

@@ -0,0 +1,65 @@
/*
Copyright (c) 2013 Albert "Alberth" Hofkamp
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef IMAGE_H
#define IMAGE_H
typedef unsigned char uint8;
typedef unsigned int uint32;
class Image32bpp
{
public:
Image32bpp(int iWidth, int iHeight);
~Image32bpp();
uint32 Get(int offset) const;
int iWidth;
int iHeight;
uint32 *pData;
};
class Image8bpp
{
public:
Image8bpp(int iWidth, int iHeight);
~Image8bpp();
unsigned char Get(int offset) const;
int iWidth;
int iHeight;
uint8 *pData;
};
uint8 GetR(uint32 rgba);
uint8 GetG(uint32 rgba);
uint8 GetB(uint32 rgba);
uint8 GetA(uint32 rgba);
Image32bpp *Load32Bpp(const std::string &sFilename, int line, int left, int width, int top, int height);
Image8bpp *Load8Bpp(const std::string &sFilename, int line, int left, int width, int top, int height);
#endif
// vim: et sw=4 ts=4 sts=4

39
SpriteEncoder/mk Executable file
View File

@@ -0,0 +1,39 @@
#!/bin/sh
# Copyright (c) 2013 Albert "Alberth" Hofkamp
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
# of the Software, and to permit persons to whom the Software is furnished to do
# so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
set -e -u -x
CCFLAGS="-Wall -g"
FLEXFLAGS=
bison --defines=tokens.h --output=parser.cpp parser.y
flex $FLEXFLAGS --outfile=scanner.cpp scanner.l
g++ $CCFLAGS -c -o parser.o parser.cpp
g++ $CCFLAGS -c -o scanner.o scanner.cpp
g++ $CCFLAGS -c -o encode.o encode.cpp
g++ $CCFLAGS -c -o ast.o ast.cpp
g++ $CCFLAGS -c -o image.o image.cpp
g++ $CCFLAGS -c -o output.o output.cpp
g++ $CCFLAGS -o encoder parser.o scanner.o encode.o ast.o image.o output.o -lpng

141
SpriteEncoder/output.cpp Normal file
View File

@@ -0,0 +1,141 @@
/*
Copyright (c) 2013 Albert "Alberth" Hofkamp
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <cstdio>
#include <cstdlib>
#include <cassert>
#include "output.h"
DataBlock::DataBlock()
{
m_iUsed = 0;
m_pNext = NULL;
}
bool DataBlock::Full()
{
return m_iUsed == BUF_SIZE;
}
void DataBlock::Add(unsigned char byte)
{
assert(m_iUsed < BUF_SIZE);
buffer[m_iUsed++] = byte;
}
void DataBlock::Write(FILE *handle)
{
if (fwrite(buffer, 1, m_iUsed, handle) != (size_t)m_iUsed)
{
fprintf(stderr, "Writing output failed!\n");
exit(1);
}
}
Output::Output()
{
m_pFirst = NULL;
m_pLast = NULL;
}
Output::~Output()
{
DataBlock *pBlk = m_pFirst;
while (pBlk != NULL)
{
DataBlock *pBlk2 = pBlk->m_pNext;
delete pBlk;
pBlk = pBlk2;
}
}
void Output::Uint16(int iValue)
{
Uint8(iValue & 0xFF);
Uint8((iValue >> 8) & 0xFF);
}
void Output::Uint8(unsigned char byte)
{
if (m_pLast == NULL || m_pLast->Full())
{
DataBlock *pBlk = new DataBlock();
if (m_pLast == NULL)
{
m_pFirst = pBlk;
m_pLast = pBlk;
}
else
{
m_pLast->m_pNext = pBlk;
m_pLast = pBlk;
}
}
m_pLast->Add(byte);
}
int Output::Reserve(int iSize)
{
// Count current size.
int iLength = 0;
DataBlock *pBlk = m_pFirst;
while (pBlk != NULL)
{
iLength += pBlk->m_iUsed;
pBlk = pBlk->m_pNext;
}
// Add 'size' bytes as reserved space.
for (int i = 0; i < iSize; i++)
{
Uint8(0);
}
return iLength;
}
void Output::Write(int iAddress, unsigned char iValue)
{
int iOffset = 0;
DataBlock *pBlk = m_pFirst;
while (pBlk != NULL && iOffset + pBlk->m_iUsed < iAddress)
{
iOffset += pBlk->m_iUsed;
pBlk = pBlk->m_pNext;
}
assert(pBlk != NULL);
iAddress -= iOffset;
assert(iAddress >= 0 && iAddress < BUF_SIZE);
pBlk->buffer[iAddress] = iValue;
}
void Output::Write(const char *fname)
{
FILE *handle = fopen(fname, "wb");
DataBlock *pBlk = m_pFirst;
while (pBlk != NULL)
{
pBlk->Write(handle);
pBlk = pBlk->m_pNext;
}
fclose(handle);
}
// vim: et sw=4 ts=4 sts=4

61
SpriteEncoder/output.h Normal file
View File

@@ -0,0 +1,61 @@
/*
Copyright (c) 2013 Albert "Alberth" Hofkamp
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef OUTPUT_H
#define OUTPUT_H
#define BUF_SIZE 100000
class DataBlock
{
public:
DataBlock();
bool Full();
void Add(unsigned char byte);
void Write(FILE *handle);
unsigned char buffer[BUF_SIZE];
int m_iUsed;
DataBlock *m_pNext;
};
class Output
{
public:
Output();
~Output();
void Write(const char *fname);
void Uint8(unsigned char byte);
void Uint16(int val);
void Write(int address, unsigned char byte);
int Reserve(int size);
DataBlock *m_pFirst;
DataBlock *m_pLast;
};
#endif
// vim: et sw=4 ts=4 sts=4

79
SpriteEncoder/parser.y Normal file
View File

@@ -0,0 +1,79 @@
/*
Copyright (c) 2013 Albert "Alberth" Hofkamp
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
%{
#include <list>
#include "ast.h"
#include <cstdio>
std::set<Sprite> g_oSprites;
%}
%token CURLY_OPEN CURLY_CLOSE EQUAL SEMICOL
%token LEFTKW TOPKW WIDTHKW HEIGHTKW BASEIMGKW RECOLOURKW SPRITEKW LAYERKW
%token<number> NUMBER
%token<text> STRING
%type<m_pSprite> SpriteSettings Sprite
%start Program
%%
Program : /* empty */
{ g_oSprites.clear(); }
| Program Sprite
{ g_oSprites.insert(*$2); delete $2; }
;
Sprite : SPRITEKW NUMBER CURLY_OPEN SpriteSettings CURLY_CLOSE
{ $$ = $4; $$->m_iSprite = $2; }
;
SpriteSettings : /* empty */
{ $$ = new Sprite; }
| SpriteSettings LEFTKW EQUAL NUMBER SEMICOL
{ $$ = $1; $$->m_iLeft = $4; }
| SpriteSettings TOPKW EQUAL NUMBER SEMICOL
{ $$ = $1; $$->m_iTop = $4; }
| SpriteSettings WIDTHKW EQUAL NUMBER SEMICOL
{ $$ = $1; $$->m_iWidth = $4; }
| SpriteSettings HEIGHTKW EQUAL NUMBER SEMICOL
{ $$ = $1; $$->m_iHeight = $4; }
| SpriteSettings BASEIMGKW EQUAL STRING SEMICOL
{ $$ = $1; $$->m_sBaseImage = $4; }
| SpriteSettings RECOLOURKW EQUAL STRING SEMICOL
{ $$ = $1; $$->SetRecolour($4); }
| SpriteSettings LAYERKW NUMBER EQUAL NUMBER SEMICOL
{ $$ = $1; $$->m_aNumber[($3) & 0xFF] = ($5) & 0xFF; }
;
%%
void yyerror(const char *msg)
{
fprintf(stderr, "Parse error at line %d: %s\n", yylval.line, msg);
exit(1);
}

104
SpriteEncoder/scanner.l Normal file
View File

@@ -0,0 +1,104 @@
/*
Copyright (c) 2013 Albert "Alberth" Hofkamp
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
%{
#include <cstdio>
#include <string>
#include "ast.h"
#include "tokens.h"
int line = 1;
std::string filename;
%}
%x IN_STRING
%x IN_COMMENT
%option noyywrap
%%
"{" { yylval.line = line; return CURLY_OPEN; }
"}" { yylval.line = line; return CURLY_CLOSE; }
"=" { yylval.line = line; return EQUAL; }
";" { yylval.line = line; return SEMICOL; }
"sprite" { yylval.line = line; return SPRITEKW; }
"left" { yylval.line = line; return LEFTKW; }
"top" { yylval.line = line; return TOPKW; }
"width" { yylval.line = line; return WIDTHKW; }
"height" { yylval.line = line; return HEIGHTKW; }
"base" { yylval.line = line; return BASEIMGKW; }
"recolour" { yylval.line = line; return RECOLOURKW; }
"layer" { yylval.line = line; return LAYERKW; }
"0" { yylval.line = line;
yylval.number = 0;
return NUMBER; }
[1-9][0-9]* { yylval.line = line;
yylval.number = atoi(yytext);
return NUMBER; }
\" { yylval.text = "";
yylval.line = line;
BEGIN(IN_STRING); }
<IN_STRING>\" { BEGIN(INITIAL);
return STRING; }
<IN_STRING>. { yylval.text += yytext; }
"//" { BEGIN(IN_COMMENT); }
<IN_COMMENT>\n { BEGIN(INITIAL);
line++; }
<IN_COMMENT>. { }
" " { }
\t { }
\n { line++; }
. { fprintf(stderr, "Unrecognized character encountered at line %d\n", line);
exit(1); }
<IN_COMMENT><<EOF>> { BEGIN(INITIAL); }
<IN_STRING><<EOF>> { BEGIN(INITIAL); }
%%
void SetupScanner(const char *fname, FILE *new_file)
{
if (new_file == NULL || fname == NULL)
{
new_file = stdin;
}
yyrestart(new_file);
BEGIN(INITIAL);
filename = (fname == NULL) ? "<stdin>" : fname;
line = 1;
}