2007-06-12 14:27:40 +02:00
/* $Id$ */
2009-08-21 22:21:05 +02:00
/*
* This file is part of OpenTTD .
* OpenTTD 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.
* OpenTTD 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 .
* See the GNU General Public License for more details . You should have received a copy of the GNU General Public License along with OpenTTD . If not , see < http : //www.gnu.org/licenses/>.
*/
2008-05-06 17:11:33 +02:00
/** @file grf.cpp Reading graphics data from (New)GRF files. */
2007-06-12 14:27:40 +02:00
2007-06-11 13:50:49 +02:00
# include "../stdafx.h"
2007-12-23 11:56:02 +01:00
# include "../gfx_func.h"
2008-08-31 12:50:05 +02:00
# include "../fileio_func.h"
2007-06-11 13:50:49 +02:00
# include "../debug.h"
2008-11-23 14:42:05 +01:00
# include "../strings_func.h"
# include "table/strings.h"
2011-12-10 14:54:10 +01:00
# include "../error.h"
2011-01-22 15:52:20 +01:00
# include "../core/math_func.hpp"
2012-02-04 14:28:40 +01:00
# include "../core/alloc_type.hpp"
2007-06-11 13:50:49 +02:00
# include "grf.hpp"
2011-05-04 19:12:37 +02:00
extern const byte _palmap_w2d [ ] ;
2012-02-04 14:29:04 +01:00
/** The different colour components a sprite can have. */
enum SpriteColourComponent {
SCC_RGB = 1 < < 0 , ///< Sprite has RGB.
SCC_ALPHA = 1 < < 1 , ///< Sprite has alpha.
SCC_PAL = 1 < < 2 , ///< Sprite has palette data.
SCC_MASK = SCC_RGB | SCC_ALPHA | SCC_PAL , ///< Mask of valid colour bits.
} ;
DECLARE_ENUM_AS_BIT_SET ( SpriteColourComponent )
2008-11-23 14:42:05 +01:00
/**
* We found a corrupted sprite . This means that the sprite itself
* contains invalid data or is too small for the given dimensions .
* @ param file_slot the file the errored sprite is in
* @ param file_pos the location in the file of the errored sprite
* @ param line the line where the error occurs .
* @ return always false ( to tell loading the sprite failed )
*/
static bool WarnCorruptSprite ( uint8 file_slot , size_t file_pos , int line )
{
static byte warning_level = 0 ;
if ( warning_level = = 0 ) {
SetDParamStr ( 0 , FioGetFilename ( file_slot ) ) ;
2010-02-24 15:46:15 +01:00
ShowErrorMessage ( STR_NEWGRF_ERROR_CORRUPT_SPRITE , INVALID_STRING_ID , WL_ERROR ) ;
2008-11-23 14:42:05 +01:00
}
DEBUG ( sprite , warning_level , " [%i] Loading corrupted sprite from %s at position %i " , line , FioGetFilename ( file_slot ) , ( int ) file_pos ) ;
warning_level = 6 ;
return false ;
}
2012-02-04 14:28:40 +01:00
/**
* Decode the image data of a single sprite .
* @ param [ in , out ] sprite Filled with the sprite image data .
* @ param file_slot File slot .
* @ param file_pos File position .
* @ param sprite_type Type of the sprite we ' re decoding .
* @ param num Size of the decompressed sprite .
* @ param type Type of the encoded sprite .
* @ return True if the sprite was successfully loaded .
*/
bool DecodeSingleSprite ( SpriteLoader : : Sprite * sprite , uint8 file_slot , size_t file_pos , SpriteType sprite_type , int64 num , byte type )
2007-06-11 13:50:49 +02:00
{
2012-02-04 14:28:40 +01:00
AutoFreePtr < byte > dest_orig ( MallocT < byte > ( num ) ) ;
2007-06-11 13:50:49 +02:00
byte * dest = dest_orig ;
2012-02-04 14:28:40 +01:00
const int64 dest_size = num ;
2007-06-11 13:50:49 +02:00
/* Read the file, which has some kind of compression */
while ( num > 0 ) {
int8 code = FioReadByte ( ) ;
if ( code > = 0 ) {
/* Plain bytes to read */
int size = ( code = = 0 ) ? 0x80 : code ;
num - = size ;
2008-11-23 14:42:05 +01:00
if ( num < 0 ) return WarnCorruptSprite ( file_slot , file_pos , __LINE__ ) ;
2007-06-11 13:50:49 +02:00
for ( ; size > 0 ; size - - ) {
* dest = FioReadByte ( ) ;
dest + + ;
}
} else {
/* Copy bytes from earlier in the sprite */
const uint data_offset = ( ( code & 7 ) < < 8 ) | FioReadByte ( ) ;
2008-11-23 14:42:05 +01:00
if ( dest - data_offset < dest_orig ) return WarnCorruptSprite ( file_slot , file_pos , __LINE__ ) ;
2007-06-11 13:50:49 +02:00
int size = - ( code > > 3 ) ;
num - = size ;
2008-11-23 14:42:05 +01:00
if ( num < 0 ) return WarnCorruptSprite ( file_slot , file_pos , __LINE__ ) ;
2007-06-11 13:50:49 +02:00
for ( ; size > 0 ; size - - ) {
* dest = * ( dest - data_offset ) ;
dest + + ;
}
}
}
2008-11-23 14:42:05 +01:00
if ( num ! = 0 ) return WarnCorruptSprite ( file_slot , file_pos , __LINE__ ) ;
2007-06-11 13:50:49 +02:00
2011-11-24 13:38:48 +01:00
sprite - > AllocateData ( sprite - > width * sprite - > height * ZOOM_LVL_BASE * ZOOM_LVL_BASE ) ;
2007-06-11 13:50:49 +02:00
2010-01-01 19:45:40 +01:00
/* When there are transparency pixels, this format has another trick.. decode it */
2007-06-11 13:50:49 +02:00
if ( type & 0x08 ) {
for ( int y = 0 ; y < sprite - > height ; y + + ) {
bool last_item = false ;
/* Look up in the header-table where the real data is stored for this row */
int offset = ( dest_orig [ y * 2 + 1 ] < < 8 ) | dest_orig [ y * 2 ] ;
2008-11-23 14:42:05 +01:00
2007-06-11 13:50:49 +02:00
/* Go to that row */
2008-11-23 14:42:05 +01:00
dest = dest_orig + offset ;
2007-06-11 13:50:49 +02:00
do {
2008-11-23 14:42:05 +01:00
if ( dest + 2 > dest_orig + dest_size ) {
return WarnCorruptSprite ( file_slot , file_pos , __LINE__ ) ;
}
2007-06-11 13:50:49 +02:00
SpriteLoader : : CommonPixel * data ;
/* Read the header:
* 0 . . 14 - length
* 15 - last_item
* 16 . . 31 - transparency bytes */
last_item = ( ( * dest ) & 0x80 ) ! = 0 ;
int length = ( * dest + + ) & 0x7F ;
int skip = * dest + + ;
data = & sprite - > data [ y * sprite - > width + skip ] ;
2008-11-23 14:42:05 +01:00
if ( skip + length > sprite - > width | | dest + length > dest_orig + dest_size ) {
return WarnCorruptSprite ( file_slot , file_pos , __LINE__ ) ;
}
2007-06-11 13:50:49 +02:00
for ( int x = 0 ; x < length ; x + + ) {
2009-12-03 16:27:33 +01:00
switch ( sprite_type ) {
2011-05-04 19:12:37 +02:00
case ST_NORMAL : data - > m = _palette_remap_grf [ file_slot ] ? _palmap_w2d [ * dest ] : * dest ; break ;
2009-12-03 16:27:33 +01:00
case ST_FONT : data - > m = min ( * dest , 2u ) ; break ;
default : data - > m = * dest ; break ;
}
2007-06-11 13:50:49 +02:00
dest + + ;
data + + ;
}
} while ( ! last_item ) ;
}
} else {
2008-11-23 14:42:05 +01:00
if ( dest_size < sprite - > width * sprite - > height ) {
return WarnCorruptSprite ( file_slot , file_pos , __LINE__ ) ;
}
if ( dest_size > sprite - > width * sprite - > height ) {
static byte warning_level = 0 ;
2012-02-04 14:28:40 +01:00
DEBUG ( sprite , warning_level , " Ignoring " OTTD_PRINTF64 " unused extra bytes from the sprite from %s at position %i " , dest_size - sprite - > width * sprite - > height , FioGetFilename ( file_slot ) , ( int ) file_pos ) ;
2008-11-23 14:42:05 +01:00
warning_level = 6 ;
}
2007-06-11 13:50:49 +02:00
dest = dest_orig ;
2008-09-02 20:45:15 +02:00
for ( int i = 0 ; i < sprite - > width * sprite - > height ; i + + ) {
2009-12-03 16:27:33 +01:00
switch ( sprite_type ) {
2011-05-04 19:12:37 +02:00
case ST_NORMAL : sprite - > data [ i ] . m = _palette_remap_grf [ file_slot ] ? _palmap_w2d [ dest [ i ] ] : dest [ i ] ; break ;
2009-12-03 16:27:33 +01:00
case ST_FONT : sprite - > data [ i ] . m = min ( dest [ i ] , 2u ) ; break ;
default : sprite - > data [ i ] . m = dest [ i ] ; break ;
}
2008-09-02 20:45:15 +02:00
}
2007-06-11 13:50:49 +02:00
}
2011-11-24 13:38:48 +01:00
if ( ZOOM_LVL_BASE ! = 1 & & sprite_type = = ST_NORMAL ) {
/* Simple scaling, back-to-front so that no intermediate buffers are needed. */
int width = sprite - > width * ZOOM_LVL_BASE ;
int height = sprite - > height * ZOOM_LVL_BASE ;
for ( int y = height - 1 ; y > = 0 ; y - - ) {
for ( int x = width - 1 ; x > = 0 ; x - - ) {
sprite - > data [ y * width + x ] = sprite - > data [ y / ZOOM_LVL_BASE * sprite - > width + x / ZOOM_LVL_BASE ] ;
}
}
sprite - > width * = ZOOM_LVL_BASE ;
sprite - > height * = ZOOM_LVL_BASE ;
sprite - > x_offs * = ZOOM_LVL_BASE ;
sprite - > y_offs * = ZOOM_LVL_BASE ;
}
2007-06-13 12:31:40 +02:00
/* Make sure to mark all transparent pixels transparent on the alpha channel too */
2010-07-24 12:14:39 +02:00
for ( int i = 0 ; i < sprite - > width * sprite - > height ; i + + ) {
2007-06-13 12:31:40 +02:00
if ( sprite - > data [ i ] . m ! = 0 ) sprite - > data [ i ] . a = 0xFF ;
2010-07-24 12:14:39 +02:00
}
2007-06-13 12:31:40 +02:00
2007-06-11 13:50:49 +02:00
return true ;
}
2012-02-04 14:28:40 +01:00
2012-02-04 14:29:04 +01:00
bool LoadSpriteV1 ( SpriteLoader : : Sprite * sprite , uint8 file_slot , size_t file_pos , SpriteType sprite_type )
2012-02-04 14:28:40 +01:00
{
/* Open the right file and go to the correct position */
FioSeekToFile ( file_slot , file_pos ) ;
/* Read the size and type */
int num = FioReadWord ( ) ;
byte type = FioReadByte ( ) ;
/* Type 0xFF indicates either a colourmap or some other non-sprite info; we do not handle them here */
if ( type = = 0xFF ) return false ;
sprite - > height = FioReadByte ( ) ;
sprite - > width = FioReadWord ( ) ;
sprite - > x_offs = FioReadWord ( ) ;
sprite - > y_offs = FioReadWord ( ) ;
/* 0x02 indicates it is a compressed sprite, so we can't rely on 'num' to be valid.
* In case it is uncompressed , the size is ' num ' - 8 ( header - size ) . */
num = ( type & 0x02 ) ? sprite - > width * sprite - > height : num - 8 ;
return DecodeSingleSprite ( sprite , file_slot , file_pos , sprite_type , num , type ) ;
}
2012-02-04 14:29:04 +01:00
bool LoadSpriteV2 ( SpriteLoader : : Sprite * sprite , uint8 file_slot , size_t file_pos , SpriteType sprite_type )
{
/* Is the sprite not present/stripped in the GRF? */
if ( file_pos = = SIZE_MAX ) return false ;
/* Open the right file and go to the correct position */
FioSeekToFile ( file_slot , file_pos ) ;
uint32 id = FioReadDword ( ) ;
do {
int64 num = FioReadDword ( ) ;
size_t start_pos = FioGetPos ( ) ;
byte type = FioReadByte ( ) ;
/* Type 0xFF indicates either a colourmap or some other non-sprite info; we do not handle them here. */
if ( type = = 0xFF ) return false ;
byte colour = type & SCC_MASK ;
byte zoom = FioReadByte ( ) ;
if ( colour = = SCC_PAL & & zoom = = 0 ) {
sprite - > height = FioReadWord ( ) ;
sprite - > width = FioReadWord ( ) ;
sprite - > x_offs = FioReadWord ( ) ;
sprite - > y_offs = FioReadWord ( ) ;
/* Mask out colour information. */
type = type & ~ SCC_MASK ;
/* For chunked encoding we store the decompressed size in the file,
* otherwise we can calculate it from the image dimensions . */
uint decomp_size = ( type & 0x08 ) ? FioReadDword ( ) : sprite - > width * sprite - > height ;
bool valid = DecodeSingleSprite ( sprite , file_slot , file_pos , sprite_type , decomp_size , type ) ;
if ( FioGetPos ( ) ! = start_pos + num ) return WarnCorruptSprite ( file_slot , file_pos , __LINE__ ) ;
return valid ;
} else {
/* Not the wanted zoom level or colour depth, continue searching. */
FioSkipBytes ( num - 2 ) ;
}
} while ( FioReadDword ( ) = = id ) ;
return false ;
}
bool SpriteLoaderGrf : : LoadSprite ( SpriteLoader : : Sprite * sprite , uint8 file_slot , size_t file_pos , SpriteType sprite_type )
{
if ( this - > container_ver > = 2 ) {
return LoadSpriteV2 ( sprite , file_slot , file_pos , sprite_type ) ;
} else {
return LoadSpriteV1 ( sprite , file_slot , file_pos , sprite_type ) ;
}
}