4
* This file is part of FreeRCT.
5
* FreeRCT is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6
* FreeRCT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FreeRCT. If not, see <http://www.gnu.org/licenses/>.
10
/** @file image.cpp %Image loading, cutting, and saving the sprites. */
12
#include "../../src/stdafx.h"
20
static const size_t HEADER_SIZE = 4; ///< Number of bytes to read to decide whether a provided file is indeed a PNG file.
21
static const int TRANSPARENT = 0; ///< Colour index of 'transparent' in the 8bpp image.
24
/** Information about available bit masks. */
25
struct MaskInformation {
26
int width; ///< Width of the mask.
27
int height; ///< Height of the mask.
28
const unsigned char *data; ///< Data of the mask.
29
const char *name; ///< Name of the mask.
32
/** List of bit masks. */
33
static const MaskInformation _masks[] = {
34
{mask64_width, mask64_height, mask64_bits, "voxel64"},
39
* Retrieve a bitmask by its name.
40
* @param name Name of the bitmask to retrieve.
41
* @return The bitmask and its meta-information (static reference, do not free).
43
static const MaskInformation *GetMask(const std::string &name)
45
const MaskInformation *msk = _masks;
46
while (msk->name != NULL) {
47
if (msk->name == name) return msk;
50
fprintf(stderr, "Error: Cannot find a bitmask named \"%s\"\n", name.c_str());
57
this->png_initialized = false;
62
if (this->png_initialized) {
63
png_destroy_read_struct(&this->png_ptr, &this->info_ptr, &this->end_info);
68
* Load a .png file from the disk.
69
* @param fname Name of the .png file to load.
70
* @param mask Bitmask to apply.
71
* @return An error message if loading failed, or \c NULL if loading succeeded.
73
const char *Image::LoadFile(const char *fname, BitMaskData *mask)
75
FILE *fp = fopen(fname, "rb");
76
if (fp == NULL) return "Input file does not exist";
78
uint8 header[HEADER_SIZE];
79
if (fread(header, 1, HEADER_SIZE, fp) != HEADER_SIZE) {
81
return "Failed to read the PNG header";
83
bool is_png = !png_sig_cmp(header, 0, HEADER_SIZE);
86
return "Header indicates it is not a PNG file";
89
this->png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
92
return "Failed to initialize the png data";
95
this-> info_ptr = png_create_info_struct(this->png_ptr);
96
if (!this->info_ptr) {
97
png_destroy_read_struct(&this->png_ptr, (png_infopp)NULL, (png_infopp)NULL);
99
return "Failed to setup a png info structure";
102
this->end_info = png_create_info_struct(this->png_ptr);
103
if (!this->end_info) {
104
png_destroy_read_struct(&this->png_ptr, &this->info_ptr, (png_infopp)NULL);
106
return "Failed to setup a png end info structure";
109
/* Setup callback in case of errors. */
110
if (setjmp(png_jmpbuf(this->png_ptr))) {
111
png_destroy_read_struct(&this->png_ptr, &this->info_ptr, &this->end_info);
113
return "Error detected while reading PNG file";
116
/* Initialize for file reading. */
117
png_init_io(this->png_ptr, fp);
118
png_set_sig_bytes(this->png_ptr, HEADER_SIZE);
120
png_read_png(this->png_ptr, this->info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
122
int bit_depth = png_get_bit_depth(this->png_ptr, this->info_ptr);
123
if (bit_depth != 8) {
124
png_destroy_read_struct(&this->png_ptr, &this->info_ptr, &this->end_info);
126
return "PNG file is not an 8bpp file";
130
this->mask = GetMask(mask->type);
131
this->mask_xpos = mask->x_pos;
132
this->mask_ypos = mask->y_pos;
139
this->png_initialized = true;
140
this->row_pointers = png_get_rows(this->png_ptr, this->info_ptr);
143
this->width = png_get_image_width(this->png_ptr, this->info_ptr);
144
this->height = png_get_image_height(this->png_ptr, this->info_ptr);
149
* Get the width of the image.
150
* @return Width of the loaded image, or \c -1.
152
int Image::GetWidth()
154
if (!this->png_initialized) return -1;
159
* Get the height of the image.
160
* @return Height of the loaded image, or \c -1.
162
int Image::GetHeight()
164
if (!this->png_initialized) return -1;
169
* Get a pixel from the image.
170
* @param x Horizontal position.
171
* @param y Vertical position.
172
* @return Value of the pixel (palette index, as it is an 8bpp indexed image).
174
uint8 Image::GetPixel(int x, int y)
176
assert(this->png_initialized);
177
assert(x >= 0 && x < this->width);
178
assert(y >= 0 && y < this->height);
180
if (this->mask != NULL) {
181
if (x >= this->mask_xpos && x < this->mask_xpos + this->mask->width &&
182
y >= this->mask_ypos && y < this->mask_ypos + this->mask->height) {
183
const unsigned char *p = this->mask->data;
184
int off = (this->mask_ypos - y) * ((this->mask->width + 7) / 8);
186
off = this->mask_xpos - x;
188
if ((*p & (1 << (off & 7))) == 0) return TRANSPARENT;
192
return this->row_pointers[y][x];
196
* Is the queried set of pixels empty?
197
* @param xpos Horizontal start position.
198
* @param ypos Vertical start position.
199
* @param dx Horizontal stepping size.
200
* @param dy Vertical stepping size.
201
* @param length Number of pixels to examine.
202
* @return All examined pixels are empty (have colour \c 0).
204
bool Image::IsEmpty(int xpos, int ypos, int dx, int dy, int length)
207
if (this->GetPixel(xpos, ypos) != TRANSPARENT) return false;
216
SpriteImage::SpriteImage()
219
this->row_sizes = NULL;
225
SpriteImage::~SpriteImage()
228
free(this->row_sizes);
232
* Copy a part of the image as a sprite.
233
* @param img %Image source.
234
* @param xoffset Horizontal offset of the origin to the top-left pixel of the sprite.
235
* @param yoffset Vertical offset of the origin to the top-left pixel of the sprite.
236
* @param xpos Left position of the sprite in the image.
237
* @param ypos Top position of the sprite in the image.
238
* @param xsize Width of the sprite in the image.
239
* @param ysize Height of the sprite in the image.
240
* @return Error message if the conversion failed, else \c NULL.
242
const char *SpriteImage::CopySprite(Image *img, int xoffset, int yoffset, int xpos, int ypos, int xsize, int ysize)
244
assert(img->png_initialized);
246
/* Remove any old data. */
248
free(this->row_sizes);
250
this->row_sizes = NULL;
254
int img_width = img->GetWidth();
255
int img_height = img->GetHeight();
256
if (xpos < 0 || ypos < 0) return "Negative starting position";
257
if (xpos >= img_width || ypos >= img_height) return "Starting position beyond image";
258
if (xsize < 0 || ysize < 0) return "Negative image size";
259
if (xpos + xsize > img_width) return "Sprite too wide";
260
if (ypos + ysize > img_height) return "Sprite too high";
262
/* Perform cropping. */
264
/* Crop left columns. */
265
while (xsize > 0 && img->IsEmpty(xpos, ypos, 0, 1, ysize)) {
272
while (ysize > 0 && img->IsEmpty(xpos, ypos, 1, 0, xsize)) {
278
/* Crop right columns. */
279
while (xsize > 0 && img->IsEmpty(xpos + xsize - 1, ypos, 0, 1, ysize)) {
283
/* Crop bottom rows. */
284
while (ysize > 0 && img->IsEmpty(xpos, ypos + ysize - 1, 1, 0, xsize)) {
288
if (xsize == 0 || ysize == 0) {
299
this->xoffset = xoffset;
300
this->yoffset = yoffset;
302
this->height = ysize;
304
this->row_sizes = (uint16 *)malloc(this->height * sizeof(uint16));
305
if (this->row_sizes == NULL) return "Cannot allocate row sizes";
307
/* Examine the sprite, and record length of data for each row. */
309
for (int y = 0; y < this->height; y++) {
311
int last_stored = 0; // Upto this position (exclusive), the row was counted.
312
for (int x = 0; x < xsize; x++) {
313
uint8 col = img->GetPixel(xpos + x, ypos + y);
314
if (col == TRANSPARENT) continue;
319
uint8 col = img->GetPixel(xpos + x, ypos + y);
320
if (col == TRANSPARENT) break;
323
/* from 'start' upto and excluding 'x' are pixels to draw. */
324
while (last_stored + 127 < start) {
325
length += 2; // 127 pixels gap, 0 pixels to draw.
328
while (x - start > 255) {
329
length += 2 + 255; // ((start-last_stored) gap, 255 pixels, and 255 colour indices.
333
length += 2 + x - start;
336
assert(length <= 0xffff);
337
this->row_sizes[y] = length;
338
this->data_size += length;
340
if (this->data_size == 0) { // No pixels -> no need to store any data.
345
/* Copy sprite pixels. */
346
this->data = (uint8 *)malloc(this->data_size);
347
if (this->data == NULL) return "Cannot allocate sprite pixel memory";
349
uint8 *ptr = this->data;
350
for (int y = 0; y < this->height; y++) {
351
if (this->row_sizes[y] == 0) continue;
353
uint8 *last_header = NULL;
354
int last_stored = 0; // Upto this position (exclusive), the row was counted.
355
for (int x = 0; x < xsize; x++) {
356
uint8 col = img->GetPixel(xpos + x, ypos + y);
357
if (col == TRANSPARENT) continue;
362
uint8 col = img->GetPixel(xpos + x, ypos + y);
363
if (col == TRANSPARENT) break;
366
/* from 'start' upto and excluding 'x' are pixels to draw. */
367
while (last_stored + 127 < start) {
368
*ptr++ = 127; // 127 pixels gap, 0 pixels to draw.
372
while (x - start > 255) {
373
*ptr++ = start - last_stored;
375
for (int i = 0; i < 255; i++) {
376
*ptr++ = img->GetPixel(xpos + start, ypos + y);
382
*ptr++ = start - last_stored;
385
*ptr++ = img->GetPixel(xpos + start, ypos + y);
390
assert(last_header != NULL);
391
*last_header |= 128; // This was the last sequence of pixels.
393
assert(ptr - this->data == this->data_size);