OpenTTD
heightmap.cpp
Go to the documentation of this file.
1 /* $Id: heightmap.cpp 27650 2016-09-04 12:57:43Z alberth $ */
2 
3 /*
4  * This file is part of OpenTTD.
5  * 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.
6  * 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.
7  * 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/>.
8  */
9 
12 #include "stdafx.h"
13 #include "heightmap.h"
14 #include "clear_map.h"
15 #include "void_map.h"
16 #include "error.h"
17 #include "saveload/saveload.h"
18 #include "bmp.h"
19 #include "gfx_func.h"
20 #include "fios.h"
21 #include "fileio_func.h"
22 
23 #include "table/strings.h"
24 
25 #include "safeguards.h"
26 
31 static inline byte RGBToGrayscale(byte red, byte green, byte blue)
32 {
33  /* To avoid doubles and stuff, multiply it with a total of 65536 (16bits), then
34  * divide by it to normalize the value to a byte again. */
35  return ((red * 19595) + (green * 38470) + (blue * 7471)) / 65536;
36 }
37 
38 
39 #ifdef WITH_PNG
40 
41 #include <png.h>
42 
46 static void ReadHeightmapPNGImageData(byte *map, png_structp png_ptr, png_infop info_ptr)
47 {
48  uint x, y;
49  byte gray_palette[256];
50  png_bytep *row_pointers = NULL;
51  bool has_palette = png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_PALETTE;
52  uint channels = png_get_channels(png_ptr, info_ptr);
53 
54  /* Get palette and convert it to grayscale */
55  if (has_palette) {
56  int i;
57  int palette_size;
58  png_color *palette;
59  bool all_gray = true;
60 
61  png_get_PLTE(png_ptr, info_ptr, &palette, &palette_size);
62  for (i = 0; i < palette_size && (palette_size != 16 || all_gray); i++) {
63  all_gray &= palette[i].red == palette[i].green && palette[i].red == palette[i].blue;
64  gray_palette[i] = RGBToGrayscale(palette[i].red, palette[i].green, palette[i].blue);
65  }
66 
73  if (palette_size == 16 && !all_gray) {
74  for (i = 0; i < palette_size; i++) {
75  gray_palette[i] = 256 * i / palette_size;
76  }
77  }
78  }
79 
80  row_pointers = png_get_rows(png_ptr, info_ptr);
81 
82  /* Read the raw image data and convert in 8-bit grayscale */
83  for (x = 0; x < png_get_image_width(png_ptr, info_ptr); x++) {
84  for (y = 0; y < png_get_image_height(png_ptr, info_ptr); y++) {
85  byte *pixel = &map[y * png_get_image_width(png_ptr, info_ptr) + x];
86  uint x_offset = x * channels;
87 
88  if (has_palette) {
89  *pixel = gray_palette[row_pointers[y][x_offset]];
90  } else if (channels == 3) {
91  *pixel = RGBToGrayscale(row_pointers[y][x_offset + 0],
92  row_pointers[y][x_offset + 1], row_pointers[y][x_offset + 2]);
93  } else {
94  *pixel = row_pointers[y][x_offset];
95  }
96  }
97  }
98 }
99 
105 static bool ReadHeightmapPNG(const char *filename, uint *x, uint *y, byte **map)
106 {
107  FILE *fp;
108  png_structp png_ptr = NULL;
109  png_infop info_ptr = NULL;
110 
111  fp = FioFOpenFile(filename, "rb", HEIGHTMAP_DIR);
112  if (fp == NULL) {
113  ShowErrorMessage(STR_ERROR_PNGMAP, STR_ERROR_PNGMAP_FILE_NOT_FOUND, WL_ERROR);
114  return false;
115  }
116 
117  png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
118  if (png_ptr == NULL) {
119  ShowErrorMessage(STR_ERROR_PNGMAP, STR_ERROR_PNGMAP_MISC, WL_ERROR);
120  fclose(fp);
121  return false;
122  }
123 
124  info_ptr = png_create_info_struct(png_ptr);
125  if (info_ptr == NULL || setjmp(png_jmpbuf(png_ptr))) {
126  ShowErrorMessage(STR_ERROR_PNGMAP, STR_ERROR_PNGMAP_MISC, WL_ERROR);
127  fclose(fp);
128  png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
129  return false;
130  }
131 
132  png_init_io(png_ptr, fp);
133 
134  /* Allocate memory and read image, without alpha or 16-bit samples
135  * (result is either 8-bit indexed/grayscale or 24-bit RGB) */
136  png_set_packing(png_ptr);
137  png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_PACKING | PNG_TRANSFORM_STRIP_ALPHA | PNG_TRANSFORM_STRIP_16, NULL);
138 
139  /* Maps of wrong colour-depth are not used.
140  * (this should have been taken care of by stripping alpha and 16-bit samples on load) */
141  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)) {
142  ShowErrorMessage(STR_ERROR_PNGMAP, STR_ERROR_PNGMAP_IMAGE_TYPE, WL_ERROR);
143  fclose(fp);
144  png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
145  return false;
146  }
147 
148  uint width = png_get_image_width(png_ptr, info_ptr);
149  uint height = png_get_image_height(png_ptr, info_ptr);
150 
151  /* Check if image dimensions don't overflow a size_t to avoid memory corruption. */
152  if ((uint64)width * height >= (size_t)-1) {
153  ShowErrorMessage(STR_ERROR_PNGMAP, STR_ERROR_HEIGHTMAP_TOO_LARGE, WL_ERROR);
154  fclose(fp);
155  png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
156  return false;
157  }
158 
159  if (map != NULL) {
160  *map = MallocT<byte>(width * height);
161  ReadHeightmapPNGImageData(*map, png_ptr, info_ptr);
162  }
163 
164  *x = width;
165  *y = height;
166 
167  fclose(fp);
168  png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
169  return true;
170 }
171 
172 #endif /* WITH_PNG */
173 
174 
178 static void ReadHeightmapBMPImageData(byte *map, BmpInfo *info, BmpData *data)
179 {
180  uint x, y;
181  byte gray_palette[256];
182 
183  if (data->palette != NULL) {
184  uint i;
185  bool all_gray = true;
186 
187  if (info->palette_size != 2) {
188  for (i = 0; i < info->palette_size && (info->palette_size != 16 || all_gray); i++) {
189  all_gray &= data->palette[i].r == data->palette[i].g && data->palette[i].r == data->palette[i].b;
190  gray_palette[i] = RGBToGrayscale(data->palette[i].r, data->palette[i].g, data->palette[i].b);
191  }
192 
199  if (info->palette_size == 16 && !all_gray) {
200  for (i = 0; i < info->palette_size; i++) {
201  gray_palette[i] = 256 * i / info->palette_size;
202  }
203  }
204  } else {
209  gray_palette[0] = 0;
210  gray_palette[1] = 16;
211  }
212  }
213 
214  /* Read the raw image data and convert in 8-bit grayscale */
215  for (y = 0; y < info->height; y++) {
216  byte *pixel = &map[y * info->width];
217  byte *bitmap = &data->bitmap[y * info->width * (info->bpp == 24 ? 3 : 1)];
218 
219  for (x = 0; x < info->width; x++) {
220  if (info->bpp != 24) {
221  *pixel++ = gray_palette[*bitmap++];
222  } else {
223  *pixel++ = RGBToGrayscale(*bitmap, *(bitmap + 1), *(bitmap + 2));
224  bitmap += 3;
225  }
226  }
227  }
228 }
229 
235 static bool ReadHeightmapBMP(const char *filename, uint *x, uint *y, byte **map)
236 {
237  FILE *f;
238  BmpInfo info;
239  BmpData data;
240  BmpBuffer buffer;
241 
242  /* Init BmpData */
243  memset(&data, 0, sizeof(data));
244 
245  f = FioFOpenFile(filename, "rb", HEIGHTMAP_DIR);
246  if (f == NULL) {
247  ShowErrorMessage(STR_ERROR_BMPMAP, STR_ERROR_PNGMAP_FILE_NOT_FOUND, WL_ERROR);
248  return false;
249  }
250 
251  BmpInitializeBuffer(&buffer, f);
252 
253  if (!BmpReadHeader(&buffer, &info, &data)) {
254  ShowErrorMessage(STR_ERROR_BMPMAP, STR_ERROR_BMPMAP_IMAGE_TYPE, WL_ERROR);
255  fclose(f);
256  BmpDestroyData(&data);
257  return false;
258  }
259 
260  /* Check if image dimensions don't overflow a size_t to avoid memory corruption. */
261  if ((uint64)info.width * info.height >= (size_t)-1 / (info.bpp == 24 ? 3 : 1)) {
262  ShowErrorMessage(STR_ERROR_BMPMAP, STR_ERROR_HEIGHTMAP_TOO_LARGE, WL_ERROR);
263  fclose(f);
264  BmpDestroyData(&data);
265  return false;
266  }
267 
268  if (map != NULL) {
269  if (!BmpReadBitmap(&buffer, &info, &data)) {
270  ShowErrorMessage(STR_ERROR_BMPMAP, STR_ERROR_BMPMAP_IMAGE_TYPE, WL_ERROR);
271  fclose(f);
272  BmpDestroyData(&data);
273  return false;
274  }
275 
276  *map = MallocT<byte>(info.width * info.height);
277  ReadHeightmapBMPImageData(*map, &info, &data);
278  }
279 
280  BmpDestroyData(&data);
281 
282  *x = info.width;
283  *y = info.height;
284 
285  fclose(f);
286  return true;
287 }
288 
296 static void GrayscaleToMapHeights(uint img_width, uint img_height, byte *map)
297 {
298  /* Defines the detail of the aspect ratio (to avoid doubles) */
299  const uint num_div = 16384;
300 
301  uint width, height;
302  uint row, col;
303  uint row_pad = 0, col_pad = 0;
304  uint img_scale;
305  uint img_row, img_col;
306  TileIndex tile;
307 
308  /* Get map size and calculate scale and padding values */
310  default: NOT_REACHED();
312  width = MapSizeX();
313  height = MapSizeY();
314  break;
315  case HM_CLOCKWISE:
316  width = MapSizeY();
317  height = MapSizeX();
318  break;
319  }
320 
321  if ((img_width * num_div) / img_height > ((width * num_div) / height)) {
322  /* Image is wider than map - center vertically */
323  img_scale = (width * num_div) / img_width;
324  row_pad = (1 + height - ((img_height * img_scale) / num_div)) / 2;
325  } else {
326  /* Image is taller than map - center horizontally */
327  img_scale = (height * num_div) / img_height;
328  col_pad = (1 + width - ((img_width * img_scale) / num_div)) / 2;
329  }
330 
332  for (uint x = 0; x < MapSizeX(); x++) MakeVoid(TileXY(x, 0));
333  for (uint y = 0; y < MapSizeY(); y++) MakeVoid(TileXY(0, y));
334  }
335 
336  /* Form the landscape */
337  for (row = 0; row < height; row++) {
338  for (col = 0; col < width; col++) {
340  default: NOT_REACHED();
341  case HM_COUNTER_CLOCKWISE: tile = TileXY(col, row); break;
342  case HM_CLOCKWISE: tile = TileXY(row, col); break;
343  }
344 
345  /* Check if current tile is within the 1-pixel map edge or padding regions */
347  (row < row_pad) || (row >= (height - row_pad - (_settings_game.construction.freeform_edges ? 0 : 1))) ||
348  (col < col_pad) || (col >= (width - col_pad - (_settings_game.construction.freeform_edges ? 0 : 1)))) {
349  SetTileHeight(tile, 0);
350  } else {
351  /* Use nearest neighbour resizing to scale map data.
352  * We rotate the map 45 degrees (counter)clockwise */
353  img_row = (((row - row_pad) * num_div) / img_scale);
355  default: NOT_REACHED();
357  img_col = (((width - 1 - col - col_pad) * num_div) / img_scale);
358  break;
359  case HM_CLOCKWISE:
360  img_col = (((col - col_pad) * num_div) / img_scale);
361  break;
362  }
363 
364  assert(img_row < img_height);
365  assert(img_col < img_width);
366 
367  uint heightmap_height = map[img_row * img_width + img_col];
368 
369  if (heightmap_height > 0) {
370  /* 0 is sea level.
371  * Other grey scales are scaled evenly to the available height levels > 0.
372  * (The coastline is independent from the number of height levels) */
373  heightmap_height = 1 + (heightmap_height - 1) * _settings_game.construction.max_heightlevel / 255;
374  }
375 
376  SetTileHeight(tile, heightmap_height);
377  }
378  /* Only clear the tiles within the map area. */
379  if (IsInnerTile(tile)) {
380  MakeClear(tile, CLEAR_GRASS, 3);
381  }
382  }
383  }
384 }
385 
390 void FixSlopes()
391 {
392  uint width, height;
393  int row, col;
394  byte current_tile;
395 
396  /* Adjust height difference to maximum one horizontal/vertical change. */
397  width = MapSizeX();
398  height = MapSizeY();
399 
400  /* Top and left edge */
401  for (row = 0; (uint)row < height; row++) {
402  for (col = 0; (uint)col < width; col++) {
403  current_tile = MAX_TILE_HEIGHT;
404  if (col != 0) {
405  /* Find lowest tile; either the top or left one */
406  current_tile = TileHeight(TileXY(col - 1, row)); // top edge
407  }
408  if (row != 0) {
409  if (TileHeight(TileXY(col, row - 1)) < current_tile) {
410  current_tile = TileHeight(TileXY(col, row - 1)); // left edge
411  }
412  }
413 
414  /* Does the height differ more than one? */
415  if (TileHeight(TileXY(col, row)) >= (uint)current_tile + 2) {
416  /* Then change the height to be no more than one */
417  SetTileHeight(TileXY(col, row), current_tile + 1);
418  }
419  }
420  }
421 
422  /* Bottom and right edge */
423  for (row = height - 1; row >= 0; row--) {
424  for (col = width - 1; col >= 0; col--) {
425  current_tile = MAX_TILE_HEIGHT;
426  if ((uint)col != width - 1) {
427  /* Find lowest tile; either the bottom and right one */
428  current_tile = TileHeight(TileXY(col + 1, row)); // bottom edge
429  }
430 
431  if ((uint)row != height - 1) {
432  if (TileHeight(TileXY(col, row + 1)) < current_tile) {
433  current_tile = TileHeight(TileXY(col, row + 1)); // right edge
434  }
435  }
436 
437  /* Does the height differ more than one? */
438  if (TileHeight(TileXY(col, row)) >= (uint)current_tile + 2) {
439  /* Then change the height to be no more than one */
440  SetTileHeight(TileXY(col, row), current_tile + 1);
441  }
442  }
443  }
444 }
445 
455 static bool ReadHeightMap(DetailedFileType dft, const char *filename, uint *x, uint *y, byte **map)
456 {
457  switch (dft) {
458  default:
459  NOT_REACHED();
460 
461 #ifdef WITH_PNG
462  case DFT_HEIGHTMAP_PNG:
463  return ReadHeightmapPNG(filename, x, y, map);
464 #endif /* WITH_PNG */
465 
466  case DFT_HEIGHTMAP_BMP:
467  return ReadHeightmapBMP(filename, x, y, map);
468  }
469 }
470 
479 bool GetHeightmapDimensions(DetailedFileType dft, const char *filename, uint *x, uint *y)
480 {
481  return ReadHeightMap(dft, filename, x, y, NULL);
482 }
483 
491 void LoadHeightmap(DetailedFileType dft, const char *filename)
492 {
493  uint x, y;
494  byte *map = NULL;
495 
496  if (!ReadHeightMap(dft, filename, &x, &y, &map)) {
497  free(map);
498  return;
499  }
500 
501  GrayscaleToMapHeights(x, y, map);
502  free(map);
503 
504  FixSlopes();
506 }
507 
512 void FlatEmptyWorld(byte tile_height)
513 {
514  int edge_distance = _settings_game.construction.freeform_edges ? 0 : 2;
515  for (uint row = edge_distance; row < MapSizeY() - edge_distance; row++) {
516  for (uint col = edge_distance; col < MapSizeX() - edge_distance; col++) {
517  SetTileHeight(TileXY(col, row), tile_height);
518  }
519  }
520 
521  FixSlopes();
523 }