(svn r10143) -Add: store the filename of the grfs opened and allow easy access to the name

-Codechange: store the SpriteID in the spritecache too
-Add: add a PNG loader for graphical files
-Documentation: added a document to explain the PNG format
replace/41b28d7194a279bdc17475d4fbe2ea6ec885a466
truelight 17 years ago
parent 4185c4afcd
commit b7443d800b

@ -0,0 +1,36 @@
32bpp and OpenTTD
=================
OpenTTD has 32bpp support. This means: OpenTTD still is 8bpp, but it has the
posibility to override the graphics with 32bpp. This means that it isn't a
replacement of grf or newgrf, but simply an addition. If you want to use 32bpp
graphics of a newgrf, you do need the newgrf itself too (with 8bpp graphics).
The Format
----------
32bpp images are stored in PNG. They should go in:
data/sprites/<grfname>/<SpriteID>.png
For example, a grfname would be 'openttd' (without .grf, lowercase), and the
SpriteID 3, to override the 3rd sprite in openttd.grf with a 32bpp version.
The format of this PNG can be almost anything, but we advise to use RGBA
format. Alpha-channel is fully supported.
As the core of OpenTTD is 8bpp, and because you of course want company colours
in your images, you will need to add a mask for every sprite that needs colour
remapping. The name is simular as above, only you have to put a 'm' behind the
SpriteID. This image should be a 8bpp palette image, where the palette is the
OpenTTD palette. Upon load of the PNG, the mask is loaded too, and overrides
the RGB (not the Alpha) of the original PNG image, and replacing it with a
8bpp color remapped and converted to 32bpp.
An other thing that OpenTTD needs in your png, is 2 tEXt chunks: x_offs and
y_offs. This to define the x- and y-offset, of course. Use the tool we supply
to add this information. Sadly enough most graphical editors trashes those
chunks upon save, so you have to readd it every time you save your image.
Your images should be the same as the grf, in size.

@ -1007,6 +1007,12 @@
<File
RelativePath=".\..\src\spriteloader\grf.hpp">
</File>
<File
RelativePath=".\..\src\spriteloader\png.cpp">
</File>
<File
RelativePath=".\..\src\spriteloader\png.hpp">
</File>
<File
RelativePath=".\..\src\spriteloader\spriteloader.hpp">
</File>

@ -956,7 +956,7 @@
>
</File>
<File
RelativePath="..\src\newgrf_industrytiles.h"
RelativePath=".\..\src\newgrf_industrytiles.h"
>
</File>
<File
@ -1563,6 +1563,14 @@
RelativePath=".\..\src\spriteloader\grf.hpp"
>
</File>
<File
RelativePath=".\..\src\spriteloader\png.cpp"
>
</File>
<File
RelativePath=".\..\src\spriteloader\png.hpp"
>
</File>
<File
RelativePath=".\..\src\spriteloader\spriteloader.hpp"
>
@ -1636,7 +1644,7 @@
>
</File>
<File
RelativePath="..\src\newgrf_industrytiles.cpp"
RelativePath=".\..\src\newgrf_industrytiles.cpp"
>
</File>
<File

@ -307,6 +307,8 @@ blitter/blitter.hpp
# Sprite loaders
spriteloader/grf.cpp
spriteloader/grf.hpp
spriteloader/png.cpp
spriteloader/png.hpp
spriteloader/spriteloader.hpp
# Renderer

@ -28,11 +28,12 @@ struct Fio {
byte *buffer, *buffer_end; ///< position pointer in local buffer and last valid byte of buffer
uint32 pos; ///< current (system) position in file
FILE *cur_fh; ///< current file handle
const char *filename; ///< current filename
FILE *handles[MAX_HANDLES]; ///< array of file handles we can have open
byte buffer_start[FIO_BUFFER_SIZE]; ///< local buffer when read from file
const char *filenames[MAX_HANDLES]; ///< array of filenames we (should) have open
#if defined(LIMITED_FDS)
uint open_handles; ///< current amount of open handles
const char *filename[MAX_HANDLES]; ///< array of filenames we (should) have open
uint usage_count[MAX_HANDLES]; ///< count how many times this file has been opened
#endif /* LIMITED_FDS */
};
@ -45,6 +46,11 @@ uint32 FioGetPos()
return _fio.pos + (_fio.buffer - _fio.buffer_start) - FIO_BUFFER_SIZE;
}
const char *FioGetFilename()
{
return _fio.filename;
}
void FioSeekTo(uint32 pos, int mode)
{
if (mode == SEEK_CUR) pos += FioGetPos();
@ -76,6 +82,7 @@ void FioSeekToFile(uint32 pos)
f = _fio.handles[pos >> 24];
assert(f != NULL);
_fio.cur_fh = f;
_fio.filename = _fio.filenames[pos >> 24];
FioSeekTo(GB(pos, 0, 24), SEEK_SET);
}
@ -174,8 +181,8 @@ void FioOpenFile(int slot, const char *filename)
FioCloseFile(slot); // if file was opened before, close it
_fio.handles[slot] = f;
_fio.filenames[slot] = filename;
#if defined(LIMITED_FDS)
_fio.filename[slot] = filename;
_fio.usage_count[slot] = 0;
_fio.open_handles++;
#endif /* LIMITED_FDS */

@ -8,6 +8,7 @@
void FioSeekTo(uint32 pos, int mode);
void FioSeekToFile(uint32 pos);
uint32 FioGetPos();
const char *FioGetFilename();
byte FioReadByte();
uint16 FioReadWord();
uint32 FioReadDword();

@ -4,6 +4,8 @@
#include "stdafx.h"
#include "openttd.h"
#include "variables.h"
#include "string.h"
#include "debug.h"
#include "functions.h"
#include "macros.h"
@ -12,6 +14,7 @@
#include "fileio.h"
#include "helpers.hpp"
#include "spriteloader/grf.hpp"
#include "spriteloader/png.hpp"
#include "blitter/blitter.hpp"
/* Default of 4MB spritecache */
@ -22,6 +25,8 @@ struct SpriteCache {
void *ptr;
uint32 file_pos;
int16 lru;
uint32 id;
const char *grf_name;
};
@ -132,6 +137,20 @@ static void* ReadSprite(SpriteCache *sc, SpriteID id, bool real_sprite)
file_pos = GetSpriteCache(SPR_IMG_QUERY)->file_pos;
}
if (BlitterFactoryBase::GetCurrentBlitter()->GetScreenDepth() == 32) {
/* Try loading 32bpp graphics in case we are 32bpp output */
SpriteLoaderPNG sprite_loader;
SpriteLoader::Sprite sprite;
if (sprite_loader.LoadSprite(&sprite, sc->grf_name, sc->id)) {
sc->ptr = BlitterFactoryBase::GetCurrentBlitter()->Encode(&sprite, &AllocSprite);
free(sprite.data);
return sc->ptr;
}
/* If the PNG couldn't be loaded, fall back to 8bpp grfs */
}
FioSeekToFile(file_pos);
/* Read the size and type */
@ -203,7 +222,7 @@ static void* ReadSprite(SpriteCache *sc, SpriteID id, bool real_sprite)
SpriteLoaderGrf sprite_loader;
SpriteLoader::Sprite sprite;
if (!sprite_loader.LoadSprite(&sprite, file_pos)) return NULL;
if (!sprite_loader.LoadSprite(&sprite, sc->grf_name, file_pos)) return NULL;
if (id == 142) sprite.height = 10; // Compensate for a TTD bug
sc->ptr = BlitterFactoryBase::GetCurrentBlitter()->Encode(&sprite, &AllocSprite);
free(sprite.data);
@ -227,6 +246,17 @@ bool LoadNextSprite(int load_index, byte file_index)
sc->file_pos = file_pos;
sc->ptr = NULL;
sc->lru = 0;
sc->id = load_index;
char *grf_name = strrchr(FioGetFilename(), PATHSEPCHAR);
if (grf_name == NULL) grf_name = (char *)FioGetFilename();
/* Copy the string, make it lowercase, strip .grf */
grf_name = strdup(grf_name);
char *t = strrchr(grf_name, '.');
if (t != NULL) *t = '\0';
strtolower(grf_name);
free((char *)sc->grf_name);
sc->grf_name = grf_name;
return true;
}
@ -239,6 +269,8 @@ void DupSprite(SpriteID old_spr, SpriteID new_spr)
scnew->file_pos = scold->file_pos;
scnew->ptr = NULL;
scnew->id = scold->id;
scnew->grf_name = strdup(scold->grf_name);
}

@ -8,7 +8,7 @@
#include "../debug.h"
#include "grf.hpp"
bool SpriteLoaderGrf::LoadSprite(SpriteLoader::Sprite *sprite, uint32 file_pos)
bool SpriteLoaderGrf::LoadSprite(SpriteLoader::Sprite *sprite, const char *filename, uint32 file_pos)
{
/* Open the right file and go to the correct position */
FioSeekToFile(file_pos);

@ -12,7 +12,7 @@ public:
/**
* Load a sprite from the disk and return a sprite struct which is the same for all loaders.
*/
bool LoadSprite(SpriteLoader::Sprite *sprite, uint32 file_pos);
bool LoadSprite(SpriteLoader::Sprite *sprite, const char *filename, uint32 file_pos);
};
#endif /* SPRITELOADER_GRF_HPP */

@ -0,0 +1,180 @@
/* $Id$ */
/** @file grf.cpp */
#include "../stdafx.h"
#include "../gfx.h"
#include "../fileio.h"
#include "../variables.h"
#include "../debug.h"
#include "png.hpp"
#include <png.h>
#define PNG_SLOT 62
static void PNGAPI png_my_read(png_structp png_ptr, png_bytep data, png_size_t length)
{
FioReadBlock(data, length);
}
static void PNGAPI png_my_error(png_structp png_ptr, png_const_charp message)
{
DEBUG(sprite, 0, "ERROR (libpng): %s - %s", message, (char *)png_get_error_ptr(png_ptr));
longjmp(png_ptr->jmpbuf, 1);
}
static void PNGAPI png_my_warning(png_structp png_ptr, png_const_charp message)
{
DEBUG(sprite, 0, "WARNING (libpng): %s - %s", message, (char *)png_get_error_ptr(png_ptr));
}
static bool OpenPNGFile(const char *filename, uint32 id, bool mask)
{
char png_file[MAX_PATH];
snprintf(png_file, sizeof(png_file), "sprites" PATHSEP "%s" PATHSEP "%d%s.png", filename, id, mask ? "m" : "");
if (FioCheckFileExists(png_file)) {
FioOpenFile(PNG_SLOT, png_file);
return true;
}
/* TODO -- Add TAR support */
return false;
}
static bool LoadPNG(SpriteLoader::Sprite *sprite, const char *filename, uint32 id, bool mask)
{
png_byte header[8];
png_structp png_ptr;
png_infop info_ptr, end_info;
uint bit_depth, color_type;
uint i, pixelsize;
png_bytep row_pointer;
SpriteLoader::CommonPixel *dst;
if (!OpenPNGFile(filename, id, mask)) return mask; // If mask is true, and file not found, continue true anyway, as it isn't a show-stopper
/* Check the header */
FioReadBlock(header, 8);
if (png_sig_cmp(header, 0, 8) != 0) return false;
/* Create the reader */
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)NULL, png_my_error, png_my_warning);
if (png_ptr == NULL) return false;
/* Create initial stuff */
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL) {
png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
return false;
}
end_info = png_create_info_struct(png_ptr);
if (end_info == NULL) {
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
return false;
}
/* Make sure that upon error, we can clean up graceful */
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
return false;
}
/* Read the file */
png_set_read_fn(png_ptr, NULL, png_my_read);
png_set_sig_bytes(png_ptr, 8);
png_read_info(png_ptr, info_ptr);
if (!mask) {
/* Read the text chunks */
png_textp text_ptr;
int num_text = 0;
png_get_text(png_ptr, info_ptr, &text_ptr, &num_text);
if (num_text == 0) DEBUG(misc, 0, "Warning: PNG Sprite '%s/%d.png' doesn't have x_offs and y_offs; expect graphical problems", filename, id);
for (int i = 0; i < num_text; i++) {
/* x_offs and y_offs are in the text-chunk of PNG */
if (strcmp("x_offs", text_ptr[i].key) == 0) sprite->x_offs = strtol(text_ptr[i].text, NULL, 0);
if (strcmp("y_offs", text_ptr[i].key) == 0) sprite->y_offs = strtol(text_ptr[i].text, NULL, 0);
}
sprite->height = info_ptr->height;
sprite->width = info_ptr->width;
sprite->data = CallocT<SpriteLoader::CommonPixel>(sprite->width * sprite->height);
}
bit_depth = png_get_bit_depth(png_ptr, info_ptr);
color_type = png_get_color_type(png_ptr, info_ptr);
if (mask && (bit_depth != 8 || color_type != PNG_COLOR_TYPE_PALETTE)) {
DEBUG(misc, 0, "Ignoring mask for SpriteID %d as it isn't a 8 bit palette image", id);
return true;
}
if (!mask) {
if (bit_depth == 16) png_set_strip_16(png_ptr);
if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png_ptr);
/* TODO 32bpp: Convert grayscale to rgb */
#ifdef TTD_LITTLE_ENDIAN
png_set_bgr(png_ptr);
#else
if (color_type == PNG_COLOR_TYPE_RGB_ALPHA) png_set_swap_alpha(png_ptr);
#endif
if (color_type == PNG_COLOR_TYPE_RGB) {
#ifdef TTD_LITTLE_ENDIAN
png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
#else
png_set_filler(png_ptr, 0xff, PNG_FILLER_BEFORE);
#endif
}
pixelsize = sizeof(uint32);
} else {
pixelsize = sizeof(uint8);
}
row_pointer = (png_byte *)malloc(info_ptr->width * pixelsize);
if (row_pointer == NULL) {
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
return false;
}
for (i = 0; i < info_ptr->height; i++) {
png_read_row(png_ptr, row_pointer, NULL);
dst = sprite->data + i * info_ptr->width;
for (uint x = 0; x < info_ptr->width; x++) {
if (mask) {
if (row_pointer[x * sizeof(uint8)] != 0) {
dst[x].b = 0;
dst[x].g = 0;
dst[x].r = 0;
/* Alpha channel is used from the original image (to allow transparency in remap colors) */
dst[x].m = row_pointer[x * sizeof(uint8)];
}
} else {
dst[x].b = row_pointer[x * sizeof(uint32) + 0];
dst[x].g = row_pointer[x * sizeof(uint32) + 1];
dst[x].r = row_pointer[x * sizeof(uint32) + 2];
dst[x].a = row_pointer[x * sizeof(uint32) + 3];
dst[x].m = 0;
}
}
}
free(row_pointer);
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
return true;
}
bool SpriteLoaderPNG::LoadSprite(SpriteLoader::Sprite *sprite, const char *filename, uint32 file_pos)
{
if (!LoadPNG(sprite, filename, file_pos, false)) return false;
if (!LoadPNG(sprite, filename, file_pos, true)) return false;
return true;
}

@ -0,0 +1,18 @@
/* $Id$ */
/** @file png.hpp */
#ifndef SPRITELOADER_PNG_HPP
#define SPRITELOADER_PNG_HPP
#include "spriteloader.hpp"
class SpriteLoaderPNG : public SpriteLoader {
public:
/**
* Load a sprite from the disk and return a sprite struct which is the same for all loaders.
*/
bool LoadSprite(SpriteLoader::Sprite *sprite, const char *filename, uint32 file_pos);
};
#endif /* SPRITELOADER_PNG_HPP */

@ -26,7 +26,7 @@ public:
/**
* Load a sprite from the disk and return a sprite struct which is the same for all loaders.
*/
virtual bool LoadSprite(SpriteLoader::Sprite *sprite, uint32 file_pos) = 0;
virtual bool LoadSprite(SpriteLoader::Sprite *sprite, const char *filename, uint32 file_pos) = 0;
virtual ~SpriteLoader() { }
};

Loading…
Cancel
Save