# heightmap.cpp

Go to the documentation of this file.

    00001 /* $Id: heightmap.cpp 11834 2008-01-13 14:37:30Z rubidium $ */
    00002
    00005 #include "stdafx.h"
    00006 #include "openttd.h"
    00007 #include "variables.h"
    00008 #include "heightmap.h"
    00009 #include "clear_map.h"
    00010 #include "void_map.h"
    00011 #include "debug.h"
    00012 #include "gui.h"
    00013 #include "saveload.h"
    00014 #include "bmp.h"
    00015 #include "gfx_func.h"
    00016 #include "core/alloc_func.hpp"
    00017 #include "fios.h"
    00018 #include "settings_type.h"
    00019
    00020 #include "table/strings.h"
    00021
    00027 static inline byte RGBToGrayscale(byte red, byte green, byte blue)
    00028 {
    00029   /* To avoid doubles and stuff, multiple it with a total of 65536 (16bits), then
    00030    *  divide by it to normalize the value to a byte again. */
    00031   return ((red * 19595) + (green * 38470) + (blue * 7471)) / 65536;
    00032 }
    00033
    00034
    00035 #ifdef WITH_PNG
    00036
    00037 #include <png.h>
    00038
    00042 static void ReadHeightmapPNGImageData(byte *map, png_structp png_ptr, png_infop info_ptr)
    00043 {
    00044   uint x, y;
    00045   byte gray_palette[256];
    00046   png_bytep *row_pointers = NULL;
    00047
    00048   /* Get palette and convert it to grayscale */
    00049   if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) {
    00050     int i;
    00051     int palette_size;
    00052     png_color *palette;
    00053     bool all_gray = true;
    00054
    00055     png_get_PLTE(png_ptr, info_ptr, &palette, &palette_size);
    00056     for (i = 0; i < palette_size && (palette_size != 16 || all_gray); i++) {
    00057       all_gray &= palette[i].red == palette[i].green && palette[i].red == palette[i].blue;
    00058       gray_palette[i] = RGBToGrayscale(palette[i].red, palette[i].green, palette[i].blue);
    00059     }
    00060
    00067     if (palette_size == 16 && !all_gray) {
    00068       for (i = 0; i < palette_size; i++) {
    00069         gray_palette[i] = 256 * i / palette_size;
    00070       }
    00071     }
    00072   }
    00073
    00074   row_pointers = png_get_rows(png_ptr, info_ptr);
    00075
    00076   /* Read the raw image data and convert in 8-bit grayscale */
    00077   for (x = 0; x < info_ptr->width; x++) {
    00078     for (y = 0; y < info_ptr->height; y++) {
    00079       byte *pixel = &map[y * info_ptr->width + x];
    00080       uint x_offset = x * info_ptr->channels;
    00081
    00082       if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) {
    00083         *pixel = gray_palette[row_pointers[y][x_offset]];
    00084       } else if (info_ptr->channels == 3) {
    00085         *pixel = RGBToGrayscale(row_pointers[y][x_offset + 0],
    00086             row_pointers[y][x_offset + 1], row_pointers[y][x_offset + 2]);
    00087       } else {
    00088         *pixel = row_pointers[y][x_offset];
    00089       }
    00090     }
    00091   }
    00092 }
    00093
    00099 static bool ReadHeightmapPNG(char *filename, uint *x, uint *y, byte **map)
    00100 {
    00101   FILE *fp;
    00102   png_structp png_ptr = NULL;
    00103   png_infop info_ptr  = NULL;
    00104
    00105   fp = fopen(filename, "rb");
    00106   if (fp == NULL) {
    00107     ShowErrorMessage(STR_PNGMAP_ERR_FILE_NOT_FOUND, STR_PNGMAP_ERROR, 0, 0);
    00108     return false;
    00109   }
    00110
    00111   png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    00112   if (png_ptr == NULL) {
    00113     ShowErrorMessage(STR_PNGMAP_ERR_MISC, STR_PNGMAP_ERROR, 0, 0);
    00114     fclose(fp);
    00115     return false;
    00116   }
    00117
    00118   info_ptr = png_create_info_struct(png_ptr);
    00119   if (info_ptr == NULL || setjmp(png_jmpbuf(png_ptr))) {
    00120     ShowErrorMessage(STR_PNGMAP_ERR_MISC, STR_PNGMAP_ERROR, 0, 0);
    00121     fclose(fp);
    00122     png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
    00123     return false;
    00124   }
    00125
    00126   png_init_io(png_ptr, fp);
    00127
    00128   /* Allocate memory and read image, without alpha or 16-bit samples
    00129    * (result is either 8-bit indexed/grayscale or 24-bit RGB) */
    00130   png_set_packing(png_ptr);
    00131   png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_PACKING | PNG_TRANSFORM_STRIP_ALPHA | PNG_TRANSFORM_STRIP_16, NULL);
    00132
    00133   /* Maps of wrong color-depth are not used.
    00134    * (this should have been taken care of by stripping alpha and 16-bit samples on load) */
    00135   if ((info_ptr->channels != 1) && (info_ptr->channels != 3) && (info_ptr->bit_depth != 8)) {
    00136     ShowErrorMessage(STR_PNGMAP_ERR_IMAGE_TYPE, STR_PNGMAP_ERROR, 0, 0);
    00137     fclose(fp);
    00138     png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
    00139     return false;
    00140   }
    00141
    00142   if (map != NULL) {
    00143     *map = MallocT<byte>(info_ptr->width * info_ptr->height);
    00144
    00145     if (*map == NULL) {
    00146       ShowErrorMessage(STR_PNGMAP_ERR_MISC, STR_PNGMAP_ERROR, 0, 0);
    00147       fclose(fp);
    00148       png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
    00149       return false;
    00150     }
    00151
    00152     ReadHeightmapPNGImageData(*map, png_ptr, info_ptr);
    00153   }
    00154
    00155   *x = info_ptr->width;
    00156   *y = info_ptr->height;
    00157
    00158   fclose(fp);
    00159   png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
    00160   return true;
    00161 }
    00162
    00163 #endif /* WITH_PNG */
    00164
    00165
    00169 static void ReadHeightmapBMPImageData(byte *map, BmpInfo *info, BmpData *data)
    00170 {
    00171   uint x, y;
    00172   byte gray_palette[256];
    00173
    00174   if (data->palette != NULL) {
    00175     uint i;
    00176     bool all_gray = true;
    00177
    00178     if (info->palette_size != 2) {
    00179       for (i = 0; i < info->palette_size && (info->palette_size != 16 || all_gray); i++) {
    00180         all_gray &= data->palette[i].r == data->palette[i].g && data->palette[i].r == data->palette[i].b;
    00181         gray_palette[i] = RGBToGrayscale(data->palette[i].r, data->palette[i].g, data->palette[i].b);
    00182       }
    00183
    00190       if (info->palette_size == 16 && !all_gray) {
    00191         for (i = 0; i < info->palette_size; i++) {
    00192           gray_palette[i] = 256 * i / info->palette_size;
    00193         }
    00194       }
    00195     } else {
    00200       gray_palette[0] = 0;
    00201       gray_palette[1] = 16;
    00202     }
    00203   }
    00204
    00205   /* Read the raw image data and convert in 8-bit grayscale */
    00206   for (y = 0; y < info->height; y++) {
    00207     byte *pixel = &map[y * info->width];
    00208     byte *bitmap = &data->bitmap[y * info->width * (info->bpp == 24 ? 3 : 1)];
    00209
    00210     for (x = 0; x < info->width; x++) {
    00211       if (info->bpp != 24) {
    00212         *pixel++ = gray_palette[*bitmap++];
    00213       } else {
    00214         *pixel++ = RGBToGrayscale(*bitmap, *(bitmap + 1), *(bitmap + 2));
    00215         bitmap += 3;
    00216       }
    00217     }
    00218   }
    00219 }
    00220
    00226 static bool ReadHeightmapBMP(char *filename, uint *x, uint *y, byte **map)
    00227 {
    00228   FILE *f;
    00229   BmpInfo info;
    00230   BmpData data;
    00231   BmpBuffer buffer;
    00232
    00233   /* Init BmpData */
    00234   memset(&data, 0, sizeof(data));
    00235
    00236   f = fopen(filename, "rb");
    00237   if (f == NULL) {
    00238     ShowErrorMessage(STR_PNGMAP_ERR_FILE_NOT_FOUND, STR_BMPMAP_ERROR, 0, 0);
    00239     return false;
    00240   }
    00241
    00242   BmpInitializeBuffer(&buffer, f);
    00243
    00244   if (!BmpReadHeader(&buffer, &info, &data)) {
    00245     ShowErrorMessage(STR_BMPMAP_ERR_IMAGE_TYPE, STR_BMPMAP_ERROR, 0, 0);
    00246     fclose(f);
    00247     BmpDestroyData(&data);
    00248     return false;
    00249   }
    00250
    00251   if (map != NULL) {
    00252     if (!BmpReadBitmap(&buffer, &info, &data)) {
    00253       ShowErrorMessage(STR_BMPMAP_ERR_IMAGE_TYPE, STR_BMPMAP_ERROR, 0, 0);
    00254       fclose(f);
    00255       BmpDestroyData(&data);
    00256       return false;
    00257     }
    00258
    00259     *map = MallocT<byte>(info.width * info.height);
    00260     if (*map == NULL) {
    00261       ShowErrorMessage(STR_PNGMAP_ERR_MISC, STR_BMPMAP_ERROR, 0, 0);
    00262       fclose(f);
    00263       BmpDestroyData(&data);
    00264       return false;
    00265     }
    00266
    00267     ReadHeightmapBMPImageData(*map, &info, &data);
    00268
    00269   }
    00270
    00271   BmpDestroyData(&data);
    00272
    00273   *x = info.width;
    00274   *y = info.height;
    00275
    00276   fclose(f);
    00277   return true;
    00278 }
    00279
    00287 static void GrayscaleToMapHeights(uint img_width, uint img_height, byte *map)
    00288 {
    00289   /* Defines the detail of the aspect ratio (to avoid doubles) */
    00290   const uint num_div = 16384;
    00291
    00292   uint width, height;
    00293   uint row, col;
    00294   uint row_pad = 0, col_pad = 0;
    00295   uint img_scale;
    00296   uint img_row, img_col;
    00297   TileIndex tile;
    00298
    00299   /* Get map size and calculate scale and padding values */
    00300   switch (_patches.heightmap_rotation) {
    00301     default: NOT_REACHED();
    00302     case HM_COUNTER_CLOCKWISE:
    00303       width   = MapSizeX();
    00304       height  = MapSizeY();
    00305       break;
    00306     case HM_CLOCKWISE:
    00307       width   = MapSizeY();
    00308       height  = MapSizeX();
    00309       break;
    00310   }
    00311
    00312   if ((img_width * num_div) / img_height > ((width * num_div) / height)) {
    00313     /* Image is wider than map - center vertically */
    00314     img_scale = (width * num_div) / img_width;
    00315     row_pad = (1 + height - ((img_height * img_scale) / num_div)) / 2;
    00316   } else {
    00317     /* Image is taller than map - center horizontally */
    00318     img_scale = (height * num_div) / img_height;
    00319     col_pad = (1 + width - ((img_width * img_scale) / num_div)) / 2;
    00320   }
    00321
    00322   /* Form the landscape */
    00323   for (row = 0; row < height - 1; row++) {
    00324     for (col = 0; col < width - 1; col++) {
    00325       switch (_patches.heightmap_rotation) {
    00326         default: NOT_REACHED();
    00327         case HM_COUNTER_CLOCKWISE: tile = TileXY(col, row); break;
    00328         case HM_CLOCKWISE:         tile = TileXY(row, col); break;
    00329       }
    00330
    00331       /* Check if current tile is within the 1-pixel map edge or padding regions */
    00332       if ((DistanceFromEdge(tile) <= 1) ||
    00333           (row < row_pad) || (row >= (height - row_pad - 1)) ||
    00334           (col < col_pad) || (col >= (width  - col_pad - 1))) {
    00335         SetTileHeight(tile, 0);
    00336       } else {
    00337         /* Use nearest neighbor resizing to scale map data.
    00338          *  We rotate the map 45 degrees (counter)clockwise */
    00339         img_row = (((row - row_pad) * num_div) / img_scale);
    00340         switch (_patches.heightmap_rotation) {
    00341           default: NOT_REACHED();
    00342           case HM_COUNTER_CLOCKWISE:
    00343             img_col = (((width - 1 - col - col_pad) * num_div) / img_scale);
    00344             break;
    00345           case HM_CLOCKWISE:
    00346             img_col = (((col - col_pad) * num_div) / img_scale);
    00347             break;
    00348         }
    00349
    00350         assert(img_row < img_height);
    00351         assert(img_col < img_width);
    00352
    00353         /* Color scales from 0 to 255, OpenTTD height scales from 0 to 15 */
    00354         SetTileHeight(tile, map[img_row * img_width + img_col] / 16);
    00355       }
    00356       MakeClear(tile, CLEAR_GRASS, 3);
    00357     }
    00358   }
    00359 }
    00360
    00365 static void FixSlopes()
    00366 {
    00367   uint width, height;
    00368   uint row, col;
    00369   byte current_tile;
    00370
    00371   /* Adjust height difference to maximum one horizontal/vertical change. */
    00372   width   = MapSizeX();
    00373   height  = MapSizeY();
    00374
    00375   /* Top and left edge */
    00376   for (row = 1; row < height - 2; row++) {
    00377     for (col = 1; col < width - 2; col++) {
    00378       /* Find lowest tile; either the top or left one */
    00379       current_tile = TileHeight(TileXY(col - 1, row)); // top edge
    00380       if (TileHeight(TileXY(col, row - 1)) < current_tile) {
    00381         current_tile = TileHeight(TileXY(col, row - 1)); // left edge
    00382       }
    00383
    00384       /* Does the height differ more than one? */
    00385       if (TileHeight(TileXY(col, row)) >= (uint)current_tile + 2) {
    00386         /* Then change the height to be no more than one */
    00387         SetTileHeight(TileXY(col, row), current_tile + 1);
    00388       }
    00389     }
    00390   }
    00391
    00392   /* Bottom and right edge */
    00393   for (row = height - 2; row > 0; row--) {
    00394     for (col = width - 2; col > 0; col--) {
    00395       /* Find lowest tile; either the bottom and right one */
    00396       current_tile = TileHeight(TileXY(col + 1, row)); // bottom edge
    00397       if (TileHeight(TileXY(col, row + 1)) < current_tile) {
    00398         current_tile = TileHeight(TileXY(col, row + 1)); // right edge
    00399       }
    00400
    00401       /* Does the height differ more than one? */
    00402       if (TileHeight(TileXY(col, row)) >= (uint)current_tile + 2) {
    00403         /* Then change the height to be no more than one */
    00404         SetTileHeight(TileXY(col, row), current_tile + 1);
    00405       }
    00406     }
    00407   }
    00408 }
    00409
    00413 static bool ReadHeightMap(char *filename, uint *x, uint *y, byte **map)
    00414 {
    00415   switch (_file_to_saveload.mode) {
    00416     default: NOT_REACHED();
    00417 #ifdef WITH_PNG
    00418     case SL_PNG:
    00419       return ReadHeightmapPNG(filename, x, y, map);
    00420 #endif /* WITH_PNG */
    00421     case SL_BMP:
    00422       return ReadHeightmapBMP(filename, x, y, map);
    00423   }
    00424 }
    00425
    00426 bool GetHeightmapDimensions(char *filename, uint *x, uint *y)
    00427 {
    00428   return ReadHeightMap(filename, x, y, NULL);
    00429 }
    00430
    00431 void LoadHeightmap(char *filename)
    00432 {
    00433   uint x, y;
    00434   byte *map = NULL;
    00435
    00436   if (!ReadHeightMap(filename, &x, &y, &map)) {
    00437     free(map);
    00438     return;
    00439   }
    00440
    00441   GrayscaleToMapHeights(x, y, map);
    00442   free(map);
    00443
    00444   FixSlopes();
    00445   MarkWholeScreenDirty();
    00446 }
    00447
    00448 void FlatEmptyWorld(byte tile_height)
    00449 {
    00450   uint width, height;
    00451   uint row, col;
    00452
    00453   width  = MapSizeX();
    00454   height = MapSizeY();
    00455
    00456   for (row = 2; row < height - 2; row++) {
    00457     for (col = 2; col < width - 2; col++) {
    00458       SetTileHeight(TileXY(col, row), tile_height);
    00459     }
    00460   }
    00461
    00462   FixSlopes();
    00463   MarkWholeScreenDirty();
    00464 }