OpenTTD Source  1.11.0-beta2
heightmap.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of OpenTTD.
3  * 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.
4  * 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.
5  * 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/>.
6  */
7 
10 #include "stdafx.h"
11 #include "heightmap.h"
12 #include "clear_map.h"
13 #include "void_map.h"
14 #include "error.h"
15 #include "saveload/saveload.h"
16 #include "bmp.h"
17 #include "gfx_func.h"
18 #include "fios.h"
19 #include "fileio_func.h"
20 
21 #include "table/strings.h"
22 
23 #include "safeguards.h"
24 
29 static inline byte RGBToGrayscale(byte red, byte green, byte blue)
30 {
31  /* To avoid doubles and stuff, multiply it with a total of 65536 (16bits), then
32  * divide by it to normalize the value to a byte again. */
33  return ((red * 19595) + (green * 38470) + (blue * 7471)) / 65536;
34 }
35 
36 
37 #ifdef WITH_PNG
38 
39 #include <png.h>
40 
44 static void ReadHeightmapPNGImageData(byte *map, png_structp png_ptr, png_infop info_ptr)
45 {
46  uint x, y;
47  byte gray_palette[256];
48  png_bytep *row_pointers = nullptr;
49  bool has_palette = png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_PALETTE;
50  uint channels = png_get_channels(png_ptr, info_ptr);
51 
52  /* Get palette and convert it to grayscale */
53  if (has_palette) {
54  int i;
55  int palette_size;
56  png_color *palette;
57  bool all_gray = true;
58 
59  png_get_PLTE(png_ptr, info_ptr, &palette, &palette_size);
60  for (i = 0; i < palette_size && (palette_size != 16 || all_gray); i++) {
61  all_gray &= palette[i].red == palette[i].green && palette[i].red == palette[i].blue;
62  gray_palette[i] = RGBToGrayscale(palette[i].red, palette[i].green, palette[i].blue);
63  }
64 
71  if (palette_size == 16 && !all_gray) {
72  for (i = 0; i < palette_size; i++) {
73  gray_palette[i] = 256 * i / palette_size;
74  }
75  }
76  }
77 
78  row_pointers = png_get_rows(png_ptr, info_ptr);
79 
80  /* Read the raw image data and convert in 8-bit grayscale */
81  for (x = 0; x < png_get_image_width(png_ptr, info_ptr); x++) {
82  for (y = 0; y < png_get_image_height(png_ptr, info_ptr); y++) {
83  byte *pixel = &map[y * png_get_image_width(png_ptr, info_ptr) + x];
84  uint x_offset = x * channels;
85 
86  if (has_palette) {
87  *pixel = gray_palette[row_pointers[y][x_offset]];
88  } else if (channels == 3) {
89  *pixel = RGBToGrayscale(row_pointers[y][x_offset + 0],
90  row_pointers[y][x_offset + 1], row_pointers[y][x_offset + 2]);
91  } else {
92  *pixel = row_pointers[y][x_offset];
93  }
94  }
95  }
96 }
97 
103 static bool ReadHeightmapPNG(const char *filename, uint *x, uint *y, byte **map)
104 {
105  FILE *fp;
106  png_structp png_ptr = nullptr;
107  png_infop info_ptr = nullptr;
108 
109  fp = FioFOpenFile(filename, "rb", HEIGHTMAP_DIR);
110  if (fp == nullptr) {
111  ShowErrorMessage(STR_ERROR_PNGMAP, STR_ERROR_PNGMAP_FILE_NOT_FOUND, WL_ERROR);
112  return false;
113  }
114 
115  png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
116  if (png_ptr == nullptr) {
117  ShowErrorMessage(STR_ERROR_PNGMAP, STR_ERROR_PNGMAP_MISC, WL_ERROR);
118  fclose(fp);
119  return false;
120  }
121 
122  info_ptr = png_create_info_struct(png_ptr);
123  if (info_ptr == nullptr || setjmp(png_jmpbuf(png_ptr))) {
124  ShowErrorMessage(STR_ERROR_PNGMAP, STR_ERROR_PNGMAP_MISC, WL_ERROR);
125  fclose(fp);
126  png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
127  return false;
128  }
129 
130  png_init_io(png_ptr, fp);
131 
132  /* Allocate memory and read image, without alpha or 16-bit samples
133  * (result is either 8-bit indexed/grayscale or 24-bit RGB) */
134  png_set_packing(png_ptr);
135  png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_PACKING | PNG_TRANSFORM_STRIP_ALPHA | PNG_TRANSFORM_STRIP_16, nullptr);
136 
137  /* Maps of wrong colour-depth are not used.
138  * (this should have been taken care of by stripping alpha and 16-bit samples on load) */
139  if ((png_get_channels(png_ptr, info_ptr) != 1) && (png_get_channels(png_ptr, info_ptr) != 3) && (png_get_bit_depth(png_ptr, info_ptr) != 8)) {
140  ShowErrorMessage(STR_ERROR_PNGMAP, STR_ERROR_PNGMAP_IMAGE_TYPE, WL_ERROR);
141  fclose(fp);
142  png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
143  return false;
144  }
145 
146  uint width = png_get_image_width(png_ptr, info_ptr);
147  uint height = png_get_image_height(png_ptr, info_ptr);
148 
149  /* Check if image dimensions don't overflow a size_t to avoid memory corruption. */
150  if ((uint64)width * height >= (size_t)-1) {
151  ShowErrorMessage(STR_ERROR_PNGMAP, STR_ERROR_HEIGHTMAP_TOO_LARGE, WL_ERROR);
152  fclose(fp);
153  png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
154  return false;
155  }
156 
157  if (map != nullptr) {
158  *map = MallocT<byte>(width * height);
159  ReadHeightmapPNGImageData(*map, png_ptr, info_ptr);
160  }
161 
162  *x = width;
163  *y = height;
164 
165  fclose(fp);
166  png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
167  return true;
168 }
169 
170 #endif /* WITH_PNG */
171 
172 
176 static void ReadHeightmapBMPImageData(byte *map, BmpInfo *info, BmpData *data)
177 {
178  uint x, y;
179  byte gray_palette[256];
180 
181  if (data->palette != nullptr) {
182  uint i;
183  bool all_gray = true;
184 
185  if (info->palette_size != 2) {
186  for (i = 0; i < info->palette_size && (info->palette_size != 16 || all_gray); i++) {
187  all_gray &= data->palette[i].r == data->palette[i].g && data->palette[i].r == data->palette[i].b;
188  gray_palette[i] = RGBToGrayscale(data->palette[i].r, data->palette[i].g, data->palette[i].b);
189  }
190 
197  if (info->palette_size == 16 && !all_gray) {
198  for (i = 0; i < info->palette_size; i++) {
199  gray_palette[i] = 256 * i / info->palette_size;
200  }
201  }
202  } else {
207  gray_palette[0] = 0;
208  gray_palette[1] = 16;
209  }
210  }
211 
212  /* Read the raw image data and convert in 8-bit grayscale */
213  for (y = 0; y < info->height; y++) {
214  byte *pixel = &map[y * info->width];
215  byte *bitmap = &data->bitmap[y * info->width * (info->bpp == 24 ? 3 : 1)];
216 
217  for (x = 0; x < info->width; x++) {
218  if (info->bpp != 24) {
219  *pixel++ = gray_palette[*bitmap++];
220  } else {
221  *pixel++ = RGBToGrayscale(*bitmap, *(bitmap + 1), *(bitmap + 2));
222  bitmap += 3;
223  }
224  }
225  }
226 }
227 
233 static bool ReadHeightmapBMP(const char *filename, uint *x, uint *y, byte **map)
234 {
235  FILE *f;
236  BmpInfo info;
237  BmpData data;
238  BmpBuffer buffer;
239 
240  /* Init BmpData */
241  memset(&data, 0, sizeof(data));
242 
243  f = FioFOpenFile(filename, "rb", HEIGHTMAP_DIR);
244  if (f == nullptr) {
245  ShowErrorMessage(STR_ERROR_BMPMAP, STR_ERROR_PNGMAP_FILE_NOT_FOUND, WL_ERROR);
246  return false;
247  }
248 
249  BmpInitializeBuffer(&buffer, f);
250 
251  if (!BmpReadHeader(&buffer, &info, &data)) {
252  ShowErrorMessage(STR_ERROR_BMPMAP, STR_ERROR_BMPMAP_IMAGE_TYPE, WL_ERROR);
253  fclose(f);
254  BmpDestroyData(&data);
255  return false;
256  }
257 
258  /* Check if image dimensions don't overflow a size_t to avoid memory corruption. */
259  if ((uint64)info.width * info.height >= (size_t)-1 / (info.bpp == 24 ? 3 : 1)) {
260  ShowErrorMessage(STR_ERROR_BMPMAP, STR_ERROR_HEIGHTMAP_TOO_LARGE, WL_ERROR);
261  fclose(f);
262  BmpDestroyData(&data);
263  return false;
264  }
265 
266  if (map != nullptr) {
267  if (!BmpReadBitmap(&buffer, &info, &data)) {
268  ShowErrorMessage(STR_ERROR_BMPMAP, STR_ERROR_BMPMAP_IMAGE_TYPE, WL_ERROR);
269  fclose(f);
270  BmpDestroyData(&data);
271  return false;
272  }
273 
274  *map = MallocT<byte>(info.width * info.height);
275  ReadHeightmapBMPImageData(*map, &info, &data);
276  }
277 
278  BmpDestroyData(&data);
279 
280  *x = info.width;
281  *y = info.height;
282 
283  fclose(f);
284  return true;
285 }
286 
294 static void GrayscaleToMapHeights(uint img_width, uint img_height, byte *map)
295 {
296  /* Defines the detail of the aspect ratio (to avoid doubles) */
297  const uint num_div = 16384;
298 
299  uint width, height;
300  uint row, col;
301  uint row_pad = 0, col_pad = 0;
302  uint img_scale;
303  uint img_row, img_col;
304  TileIndex tile;
305 
306  /* Get map size and calculate scale and padding values */
308  default: NOT_REACHED();
310  width = MapSizeX();
311  height = MapSizeY();
312  break;
313  case HM_CLOCKWISE:
314  width = MapSizeY();
315  height = MapSizeX();
316  break;
317  }
318 
319  if ((img_width * num_div) / img_height > ((width * num_div) / height)) {
320  /* Image is wider than map - center vertically */
321  img_scale = (width * num_div) / img_width;
322  row_pad = (1 + height - ((img_height * img_scale) / num_div)) / 2;
323  } else {
324  /* Image is taller than map - center horizontally */
325  img_scale = (height * num_div) / img_height;
326  col_pad = (1 + width - ((img_width * img_scale) / num_div)) / 2;
327  }
328 
330  for (uint x = 0; x < MapSizeX(); x++) MakeVoid(TileXY(x, 0));
331  for (uint y = 0; y < MapSizeY(); y++) MakeVoid(TileXY(0, y));
332  }
333 
334  /* Form the landscape */
335  for (row = 0; row < height; row++) {
336  for (col = 0; col < width; col++) {
338  default: NOT_REACHED();
339  case HM_COUNTER_CLOCKWISE: tile = TileXY(col, row); break;
340  case HM_CLOCKWISE: tile = TileXY(row, col); break;
341  }
342 
343  /* Check if current tile is within the 1-pixel map edge or padding regions */
345  (row < row_pad) || (row >= (height - row_pad - (_settings_game.construction.freeform_edges ? 0 : 1))) ||
346  (col < col_pad) || (col >= (width - col_pad - (_settings_game.construction.freeform_edges ? 0 : 1)))) {
347  SetTileHeight(tile, 0);
348  } else {
349  /* Use nearest neighbour resizing to scale map data.
350  * We rotate the map 45 degrees (counter)clockwise */
351  img_row = (((row - row_pad) * num_div) / img_scale);
353  default: NOT_REACHED();
355  img_col = (((width - 1 - col - col_pad) * num_div) / img_scale);
356  break;
357  case HM_CLOCKWISE:
358  img_col = (((col - col_pad) * num_div) / img_scale);
359  break;
360  }
361 
362  assert(img_row < img_height);
363  assert(img_col < img_width);
364 
365  uint heightmap_height = map[img_row * img_width + img_col];
366 
367  if (heightmap_height > 0) {
368  /* 0 is sea level.
369  * Other grey scales are scaled evenly to the available height levels > 0.
370  * (The coastline is independent from the number of height levels) */
371  heightmap_height = 1 + (heightmap_height - 1) * _settings_game.construction.max_heightlevel / 255;
372  }
373 
374  SetTileHeight(tile, heightmap_height);
375  }
376  /* Only clear the tiles within the map area. */
377  if (IsInnerTile(tile)) {
378  MakeClear(tile, CLEAR_GRASS, 3);
379  }
380  }
381  }
382 }
383 
388 void FixSlopes()
389 {
390  uint width, height;
391  int row, col;
392  byte current_tile;
393 
394  /* Adjust height difference to maximum one horizontal/vertical change. */
395  width = MapSizeX();
396  height = MapSizeY();
397 
398  /* Top and left edge */
399  for (row = 0; (uint)row < height; row++) {
400  for (col = 0; (uint)col < width; col++) {
401  current_tile = MAX_TILE_HEIGHT;
402  if (col != 0) {
403  /* Find lowest tile; either the top or left one */
404  current_tile = TileHeight(TileXY(col - 1, row)); // top edge
405  }
406  if (row != 0) {
407  if (TileHeight(TileXY(col, row - 1)) < current_tile) {
408  current_tile = TileHeight(TileXY(col, row - 1)); // left edge
409  }
410  }
411 
412  /* Does the height differ more than one? */
413  if (TileHeight(TileXY(col, row)) >= (uint)current_tile + 2) {
414  /* Then change the height to be no more than one */
415  SetTileHeight(TileXY(col, row), current_tile + 1);
416  }
417  }
418  }
419 
420  /* Bottom and right edge */
421  for (row = height - 1; row >= 0; row--) {
422  for (col = width - 1; col >= 0; col--) {
423  current_tile = MAX_TILE_HEIGHT;
424  if ((uint)col != width - 1) {
425  /* Find lowest tile; either the bottom and right one */
426  current_tile = TileHeight(TileXY(col + 1, row)); // bottom edge
427  }
428 
429  if ((uint)row != height - 1) {
430  if (TileHeight(TileXY(col, row + 1)) < current_tile) {
431  current_tile = TileHeight(TileXY(col, row + 1)); // right edge
432  }
433  }
434 
435  /* Does the height differ more than one? */
436  if (TileHeight(TileXY(col, row)) >= (uint)current_tile + 2) {
437  /* Then change the height to be no more than one */
438  SetTileHeight(TileXY(col, row), current_tile + 1);
439  }
440  }
441  }
442 }
443 
453 static bool ReadHeightMap(DetailedFileType dft, const char *filename, uint *x, uint *y, byte **map)
454 {
455  switch (dft) {
456  default:
457  NOT_REACHED();
458 
459 #ifdef WITH_PNG
460  case DFT_HEIGHTMAP_PNG:
461  return ReadHeightmapPNG(filename, x, y, map);
462 #endif /* WITH_PNG */
463 
464  case DFT_HEIGHTMAP_BMP:
465  return ReadHeightmapBMP(filename, x, y, map);
466  }
467 }
468 
477 bool GetHeightmapDimensions(DetailedFileType dft, const char *filename, uint *x, uint *y)
478 {
479  return ReadHeightMap(dft, filename, x, y, nullptr);
480 }
481 
489 void LoadHeightmap(DetailedFileType dft, const char *filename)
490 {
491  uint x, y;
492  byte *map = nullptr;
493 
494  if (!ReadHeightMap(dft, filename, &x, &y, &map)) {
495  free(map);
496  return;
497  }
498 
499  GrayscaleToMapHeights(x, y, map);
500  free(map);
501 
502  FixSlopes();
504 }
505 
510 void FlatEmptyWorld(byte tile_height)
511 {
512  int edge_distance = _settings_game.construction.freeform_edges ? 0 : 2;
513  for (uint row = edge_distance; row < MapSizeY() - edge_distance; row++) {
514  for (uint col = edge_distance; col < MapSizeX() - edge_distance; col++) {
515  SetTileHeight(TileXY(col, row), tile_height);
516  }
517  }
518 
519  FixSlopes();
521 }
TileIndex
uint32 TileIndex
The index/ID of a Tile.
Definition: tile_type.h:78
BmpInfo::height
uint32 height
bitmap height
Definition: bmp.h:18
FixSlopes
void FixSlopes()
This function takes care of the fact that land in OpenTTD can never differ more than 1 in height.
Definition: heightmap.cpp:388
LoadHeightmap
void LoadHeightmap(DetailedFileType dft, const char *filename)
Load a heightmap from file and change the map in his current dimensions to a landscape representing t...
Definition: heightmap.cpp:489
ConstructionSettings::max_heightlevel
uint8 max_heightlevel
maximum allowed heightlevel
Definition: settings_type.h:307
HM_CLOCKWISE
@ HM_CLOCKWISE
Rotate the map clockwise 45 degrees.
Definition: heightmap.h:21
BmpInfo
Definition: bmp.h:15
BmpInfo::bpp
uint16 bpp
bits per pixel
Definition: bmp.h:20
MakeClear
static void MakeClear(TileIndex t, ClearGround g, uint density)
Make a clear tile.
Definition: clear_map.h:259
BmpInfo::width
uint32 width
bitmap width
Definition: bmp.h:17
CLEAR_GRASS
@ CLEAR_GRASS
0-3
Definition: clear_map.h:20
void_map.h
GrayscaleToMapHeights
static void GrayscaleToMapHeights(uint img_width, uint img_height, byte *map)
Converts a given grayscale map to something that fits in OTTD map system and create a map of that dat...
Definition: heightmap.cpp:294
HEIGHTMAP_DIR
@ HEIGHTMAP_DIR
Subdirectory of scenario for heightmaps.
Definition: fileio_type.h:113
ReadHeightmapPNGImageData
static void ReadHeightmapPNGImageData(byte *map, png_structp png_ptr, png_infop info_ptr)
The PNG Heightmap loader.
Definition: heightmap.cpp:44
saveload.h
fileio_func.h
MAX_TILE_HEIGHT
static const uint MAX_TILE_HEIGHT
Maximum allowed tile height.
Definition: tile_type.h:22
clear_map.h
fios.h
ShowErrorMessage
void ShowErrorMessage(StringID summary_msg, StringID detailed_msg, WarningLevel wl, int x=0, int y=0, const GRFFile *textref_stack_grffile=nullptr, uint textref_stack_size=0, const uint32 *textref_stack=nullptr)
Display an error message in a window.
Definition: error_gui.cpp:372
GameSettings::game_creation
GameCreationSettings game_creation
settings used during the creation of a game (map)
Definition: settings_type.h:550
gfx_func.h
MapSizeX
static uint MapSizeX()
Get the size of the map along the X.
Definition: map_func.h:72
heightmap.h
TileHeight
static uint TileHeight(TileIndex tile)
Returns the height of a tile.
Definition: tile_map.h:29
FioFOpenFile
FILE * FioFOpenFile(const std::string &filename, const char *mode, Subdirectory subdir, size_t *filesize)
Opens a OpenTTD file somewhere in a personal or global directory.
Definition: fileio.cpp:406
DistanceFromEdge
uint DistanceFromEdge(TileIndex tile)
Param the minimum distance to an edge.
Definition: map.cpp:217
bmp.h
MakeVoid
static void MakeVoid(TileIndex t)
Make a nice void tile ;)
Definition: void_map.h:19
ReadHeightmapBMP
static bool ReadHeightmapBMP(const char *filename, uint *x, uint *y, byte **map)
Reads the heightmap and/or size of the heightmap from a BMP file.
Definition: heightmap.cpp:233
HM_COUNTER_CLOCKWISE
@ HM_COUNTER_CLOCKWISE
Rotate the map counter clockwise 45 degrees.
Definition: heightmap.h:20
_settings_game
GameSettings _settings_game
Game settings of a running game or the scenario editor.
Definition: settings.cpp:80
safeguards.h
ConstructionSettings::freeform_edges
bool freeform_edges
allow terraforming the tiles at the map edges
Definition: settings_type.h:319
error.h
MapSizeY
static uint MapSizeY()
Get the size of the map along the Y.
Definition: map_func.h:82
stdafx.h
SetTileHeight
static void SetTileHeight(TileIndex tile, uint height)
Sets the height of a tile.
Definition: tile_map.h:57
GetHeightmapDimensions
bool GetHeightmapDimensions(DetailedFileType dft, const char *filename, uint *x, uint *y)
Get the dimensions of a heightmap.
Definition: heightmap.cpp:477
BmpBuffer
Definition: bmp.h:32
FlatEmptyWorld
void FlatEmptyWorld(byte tile_height)
Make an empty world where all tiles are of height 'tile_height'.
Definition: heightmap.cpp:510
GameCreationSettings::heightmap_rotation
byte heightmap_rotation
rotation director for the heightmap
Definition: settings_type.h:292
DetailedFileType
DetailedFileType
Kinds of files in each AbstractFileType.
Definition: fileio_type.h:28
TileXY
static TileIndex TileXY(uint x, uint y)
Returns the TileIndex of a coordinate.
Definition: map_func.h:163
ReadHeightmapBMPImageData
static void ReadHeightmapBMPImageData(byte *map, BmpInfo *info, BmpData *data)
The BMP Heightmap loader.
Definition: heightmap.cpp:176
RGBToGrayscale
static byte RGBToGrayscale(byte red, byte green, byte blue)
Convert RGB colours to Grayscale using 29.9% Red, 58.7% Green, 11.4% Blue (average luminosity formula...
Definition: heightmap.cpp:29
WL_ERROR
@ WL_ERROR
Errors (eg. saving/loading failed)
Definition: error.h:24
BmpInfo::palette_size
uint32 palette_size
number of colours in palette
Definition: bmp.h:22
MarkWholeScreenDirty
void MarkWholeScreenDirty()
This function mark the whole screen as dirty.
Definition: gfx.cpp:1619
BmpData
Definition: bmp.h:25
DFT_HEIGHTMAP_BMP
@ DFT_HEIGHTMAP_BMP
BMP file.
Definition: fileio_type.h:34
ReadHeightmapPNG
static bool ReadHeightmapPNG(const char *filename, uint *x, uint *y, byte **map)
Reads the heightmap and/or size of the heightmap from a PNG file.
Definition: heightmap.cpp:103
GameSettings::construction
ConstructionSettings construction
construction of things in-game
Definition: settings_type.h:551
free
static void free(const void *ptr)
Version of the standard free that accepts const pointers.
Definition: stdafx.h:454
ReadHeightMap
static bool ReadHeightMap(DetailedFileType dft, const char *filename, uint *x, uint *y, byte **map)
Reads the heightmap with the correct file reader.
Definition: heightmap.cpp:453
DFT_HEIGHTMAP_PNG
@ DFT_HEIGHTMAP_PNG
PNG file.
Definition: fileio_type.h:35
IsInnerTile
static bool IsInnerTile(TileIndex tile)
Check if a tile is within the map (not a border)
Definition: tile_map.h:109