diff --git dataobj/height_map_loader.cc dataobj/height_map_loader.cc index 526b27a3a..6ccd2ea56 100644 --- dataobj/height_map_loader.cc +++ dataobj/height_map_loader.cc @@ -1,4 +1,7 @@ -/* code for loading heightmaps */ +/* + * This file is part of the Simutrans project under the artistic licence. + * (see licence.txt) + */ #include #include @@ -10,234 +13,343 @@ #include "../simsys.h" -height_map_loader_t::height_map_loader_t(bool new_format): - height_map_conversion_version_new(new_format) - { +height_map_loader_t::height_map_loader_t(bool new_format) : + height_map_conversion_version_new(new_format) +{ } // read height data from bmp or ppm files bool height_map_loader_t::get_height_data_from_file( const char *filename, sint8 groundwater, sint8 *&hfield, sint16 &ww, sint16 &hh, bool update_only_values ) { - if (FILE* const file = dr_fopen(filename, "rb")) { - char id[3]; - // parsing the header of this mixed file format is nottrivial ... - id[0] = fgetc(file); - id[1] = fgetc(file); - id[2] = 0; - if(strcmp(id, "P6")) { - if(strcmp(id, "BM")) { - fclose(file); - dbg->error("height_map_loader_t::load_heightfield()","Heightfield has wrong image type %s instead P6/BM", id); - return false; + hfield = NULL; + + FILE *const file = dr_fopen(filename, "rb"); + if( !file ) { + dbg->error("height_map_loader_t::get_height_data_from_file()", "Cannot open heightmap file"); + return false; + } + + char id[3] = {0, 0, 0}; + // parsing the header of this mixed file format is non-trivial ... + if( std::fgets(id, 3, file) == NULL ) { + std::fclose(file); + dbg->error("height_map_loader_t::get_height_data_from_file()", "Cannot read magic number"); + return false; + } + + const bool is_bmp = std::strcmp(id, "BM") == 0; + const bool is_ppm = std::strcmp(id, "P6") == 0; + + if( !is_bmp && !is_ppm ) { + std::fclose(file); + dbg->error("height_map_loader_t::get_height_data_from_file()", "Unsupported height map format (must be bmp or ppm)"); + return false; + } + + const char *err = NULL; + if( is_bmp ) { + err = read_bmp(file, groundwater, hfield, ww, hh, update_only_values); + } + else { + err = read_ppm(file, groundwater, hfield, ww, hh, update_only_values); + } + + // success ... (or not) + std::fclose(file); + + if( err ) { + dbg->error("height_map_loader_t::get_height_data_from_file()", + "Error while reading %s file: %s", is_bmp ? "bmp" : "ppm", err); + + if( hfield ) { + delete[] hfield; + hfield = NULL; + } + } + + return err == NULL; +} + + +const char *height_map_loader_t::read_bmp( FILE *file, sint8 groundwater, sint8 *&hfield, sint16 &ww, sint16 &hh, bool update_only_values ) +{ + uint32 l; + uint16 s; + + if( std::fseek( file, 10, SEEK_SET ) != 0 || std::fread(&l, sizeof(uint32), 1, file) != 1 ) { + return "Malformed bmp file"; + } + const uint32 data_offset = endian(l); + + if( std::fseek( file, 18, SEEK_SET ) != 0 || std::fread(&l, sizeof(sint32), 1, file) != 1 ) { + return "Malformed bmp file"; + } + sint32 w = endian(l); + + if( std::fseek( file, 22, SEEK_SET ) != 0 || std::fread(&l, sizeof(sint32), 1, file) != 1 ) { + return "Malformed bmp file"; + } + sint32 h = endian(l); + + if( std::fseek( file, 28, SEEK_SET ) != 0 || std::fread(&s, sizeof(sint16), 1, file) != 1 ) { + return "Malformed bmp file"; + } + const sint16 bit_depth = endian(s); + + if( std::fseek( file, 30, SEEK_SET ) != 0 || std::fread(&l, sizeof(sint32), 1, file) != 1 ) { + return "Malformed bmp file"; + } + const sint32 format = endian(l); + + if( std::fseek( file, 46, SEEK_SET ) != 0 || std::fread(&l, sizeof(sint32), 1, file) != 1 ) { + return "Malformed bmp file"; + } + sint32 table = endian(l); + + if( (bit_depth!=8 && bit_depth!=24) || format>1 ) { + return "Can only use 8 bit (RLE or normal) or 24 bit bitmaps!"; + } + + // skip parsing body + if( update_only_values ) { + ww = w; + hh = std::abs(h); + return NULL; + } + + // now read the data and convert them on the fly + try { + hfield = new sint8[w*h]; + } + catch (const std::bad_alloc &) { + hfield = NULL; + return "Not enough memory"; + } + + std::memset( hfield, groundwater, w*h ); + + if( bit_depth==8 ) { + // convert color tables to height levels + if( table==0 ) { + table = 256; + } + + sint8 h_table[256]; + if( std::fseek( file, 54, SEEK_SET ) != 0 ) { + return "Malformed bmp file"; + } + + for( int i=0; i1) { - if(!update_only_values) { - dbg->fatal("height_map_loader_t::get_height_data_from_file()","Can only use 8Bit (RLE or normal) or 24 bit bitmaps!"); + + h_table[i] = height_map_loader_t::rgb_to_height(R, G, B); + } + + // now read the data + if( std::fseek( file, data_offset, SEEK_SET ) != 0 ) { + return "Malformed bmp file"; + } + + if( format==0 ) { + // uncompressed (usually mirrored, if h<0) + const bool mirror = (h<0); + h = std::abs(h); + for( sint32 y=0; y0 ) { + for( sint32 k=0; k0) - bool mirror = (h<0); - h = abs(h); - for( sint32 y=0; y 0) { - for( sint32 k = 0; k < Count; k++, x++ ) { - hfield[x+(y*w)] = h_table[ColorIndex]; - } - } else if (Count == 0) { - sint32 Flag = ColorIndex; - if (Flag == 0) { - // goto next line - x = 0; - y--; - } - else if (Flag == 1) { - // end of bitmap - break; - } - else if (Flag == 2) { - // skip with cursor - x += (uint8)fgetc(file); - y -= (uint8)fgetc(file); - } - else { - // uncompressed run - Count = Flag; - for( sint32 k = 0; k < Count; k++, x++ ) { - hfield[x+y*w] = h_table[(uint8)fgetc(file)]; - } - if (ftell(file) & 1) { // alway even offset in file - fseek(file, 1, SEEK_CUR); - } + else { + // uncompressed run + Count = Flag; + for( sint32 k=0; k='0' && *c<='9') { - c++; + } + else { + // uncompressed 24 bits + const bool mirror = (h<0); + h = std::abs(h); + + for( sint32 y=0; yfatal("height_map_loader_t::load_heightfield()","Heightfield has wrong color depth %d", param[2] ); - } - return false; + + // skip superfluous bytes at the end of each scanline + if( std::fseek( file, (4-((w*3)&3))&3, SEEK_CUR ) != 0 ) { + return "Malformed bmp file"; } + } + } + + ww = w; + hh = h; + return NULL; +} + - // report only values - if(update_only_values) { - fclose(file); - ww = w; - hh = h; - return true; +const char *height_map_loader_t::read_ppm( FILE *file, sint8 groundwater, sint8 *&hfield, sint16 &ww, sint16 &hh, bool update_only_values ) +{ + // ppm format + char buf[255]; + const char *c = ""; + sint32 param[3] = {0, 0, 0}; + + for( int index=0; index<3; ) { + // the format is "P6[whitespace]width[whitespace]height[[whitespace bitdepth]]newline] + // however, Photoshop is the first program, that uses space for the first whitespace ... + // so we cater for Photoshop too + while( *c!=0 && *c<=32 ) { + c++; + } + + // usually, after P6 there comes a comment with the maker + // but comments can be anywhere + if( *c==0 ) { + if( read_line(buf, sizeof(buf), file) == NULL ) { + return "Malformed ppm file"; } - // ok, now read them in - hfield = new sint8[w*h]; - memset( hfield, groundwater, w*h ); + c = buf; + continue; + } - for(sint16 y=0; y='0' && *c<='9' ) { + c++; + } + } + + // now the data + const sint32 w = param[0]; + const sint32 h = param[1]; + + if( param[2]!=255 ) { + return "Heightfield has wrong color depth (must be 255)"; + } + + // report only values + if( update_only_values ) { + ww = w; + hh = h; + return NULL; + } + + // ok, now read them in + try { + hfield = new sint8[w*h]; + } + catch (const std::bad_alloc &) { + hfield = NULL; + return "Not enough memory"; + } + + std::memset( hfield, groundwater, w*h ); + + for( sint16 y=0; yget_groundwater(), h_field, w, h, true )) { @@ -67,12 +67,12 @@ const char *load_relief_frame_t::get_info(const char *fullpath) bool load_relief_frame_t::check_file( const char *fullpath, const char * ) { sint16 w, h; - sint8 *h_field; + sint8 *h_field = NULL; height_map_loader_t hml(new_format.pressed); if(hml.get_height_data_from_file(fullpath, (sint8)sets->get_groundwater(), h_field, w, h, true )) { - return w>0 && h>0; env_t::new_height_map_conversion = new_format.pressed; + return w>0 && h>0; } return false; } diff --git gui/welt.cc gui/welt.cc index 777fc3c24..bc922505e 100644 --- gui/welt.cc +++ gui/welt.cc @@ -287,7 +287,7 @@ bool welt_gui_t::update_from_heightfield(const char *filename) sint16 w, h; sint8 *h_field=NULL; - height_map_loader_t hml(sets); + height_map_loader_t hml(sets != NULL); if(hml.get_height_data_from_file(filename, (sint8)sets->get_groundwater(), h_field, w, h, false )) { sets->set_size_x(w); sets->set_size_y(h); @@ -306,6 +306,7 @@ bool welt_gui_t::update_from_heightfield(const char *filename) } } map_preview.set_map_data(&map); + delete[] h_field; return true; } return false; diff --git simworld.cc simworld.cc index 7885ad108..0d487ee97 100644 --- simworld.cc +++ simworld.cc @@ -6062,8 +6062,8 @@ uint8 karte_t::sp2num(player_t *player) void karte_t::load_heightfield(settings_t* const sets) { sint16 w, h; - sint8 *h_field; - height_map_loader_t hml(sets); + sint8 *h_field = NULL; + height_map_loader_t hml(sets != NULL); if(hml.get_height_data_from_file(sets->heightfield.c_str(), (sint8)(sets->get_groundwater()), h_field, w, h, false )) { sets->set_size(w,h); // create map