1
1
forked from 0ad/0ad
0ad/source/graphics/MapIO.cpp
wraitii 12aa35eb3b Fix loading grayscale heightmaps for RM maps.
d2948937a0 introduced code to read Heightmap images into RM terrain
data. However, the original diff contained a bug where it read
Out-of-bounds array data for grayscale images. This bug was hidden by
another issue until D1816 / cbc04ba83b, which changed the code and made
the OOB read actually relevant. The effect was twofold:
- The height chosen was not the max of the 3 color channels, but the max
of the 3 neighboring pixel (thus slightly lowering the quality of
generated maps)
- The height for the bottom-right coordinates were random memory values,
thus garbage.

This random height ended up resulting in non-deterministic map
generation, which was reported on Ngorongoro.

The cause of this silent failure is that the transformation to BGRA is
not applied for grayscale images, as the alpha transformation is
processed first and exits. This is not per-se buggy, so it is not
changed here.
The fixed behaviour is specialized for the common grayscale case, and
retains the max-of-3-color-channel intended behaviour otherwise.

Fixes #6261.

Reported by: Feldfeld
Differential Revision: https://code.wildfiregames.com/D4203
This was SVN commit r25843.
2021-07-31 17:52:05 +00:00

109 lines
3.7 KiB
C++

/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. 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, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. 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 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "MapIO.h"
#include "graphics/Patch.h"
#include "lib/allocators/shared_ptr.h"
#include "lib/file/file.h"
#include "lib/file/vfs/vfs_path.h"
#include "lib/os_path.h"
#include "lib/status.h"
#include "lib/tex/tex.h"
#include "maths/MathUtil.h"
#include "ps/Filesystem.h"
#include <algorithm>
#include <vector>
Status ParseHeightmapImage(const std::shared_ptr<u8>& fileData, size_t fileSize, std::vector<u16>& heightmap);
Status LoadHeightmapImageVfs(const VfsPath& filepath, std::vector<u16>& heightmap)
{
std::shared_ptr<u8> fileData;
size_t fileSize;
RETURN_STATUS_IF_ERR(g_VFS->LoadFile(filepath, fileData, fileSize));
return ParseHeightmapImage(fileData, fileSize, heightmap);
}
Status LoadHeightmapImageOs(const OsPath& filepath, std::vector<u16>& heightmap)
{
File file;
RETURN_STATUS_IF_ERR(file.Open(OsString(filepath), O_RDONLY));
size_t fileSize = lseek(file.Descriptor(), 0, SEEK_END);
lseek(file.Descriptor(), 0, SEEK_SET);
std::shared_ptr<u8> fileData;
RETURN_STATUS_IF_ERR(AllocateAligned(fileData, fileSize, maxSectorSize));
Status readvalue = read(file.Descriptor(), fileData.get(), fileSize);
file.Close();
RETURN_STATUS_IF_ERR(readvalue);
return ParseHeightmapImage(fileData, fileSize, heightmap);
}
Status ParseHeightmapImage(const std::shared_ptr<u8>& fileData, size_t fileSize, std::vector<u16>& heightmap)
{
// Decode to a raw pixel format
Tex tex;
RETURN_STATUS_IF_ERR(tex.decode(fileData, fileSize));
// Convert to uncompressed BGRA with no mipmaps.
// Note that grayscale images don't get converted - they remain grayscale, 8-bits per pixel.
RETURN_STATUS_IF_ERR(tex.transform_to((tex.m_Flags | TEX_BGR | TEX_ALPHA) & ~(TEX_DXT | TEX_MIPMAPS)));
// Pick smallest side of texture; truncate if not divisible by PATCH_SIZE
ssize_t tileSize = std::min(tex.m_Width, tex.m_Height);
tileSize -= tileSize % PATCH_SIZE;
u8* mapdata = tex.get_data();
ssize_t bytesPP = tex.m_Bpp / 8;
ssize_t mapLineSkip = tex.m_Width * bytesPP;
// Copy image data into the heightmap
heightmap.resize(SQR(tileSize + 1));
// if hoisted out of the loop as a micro-optimisation that doesn't harm readability much.
if (bytesPP == 1)
for (ssize_t y = 0; y < tileSize + 1; ++y)
for (ssize_t x = 0; x < tileSize + 1; ++x)
{
// Repeat the last pixel of the image for the last vertex of the heightmap
int offset = std::min(y, tileSize - 1) * mapLineSkip + std::min(x, tileSize - 1);
heightmap[(tileSize - y) * (tileSize + 1) + x] = static_cast<u16>(256) * mapdata[offset];
}
else if (bytesPP == 4)
for (ssize_t y = 0; y < tileSize + 1; ++y)
for (ssize_t x = 0; x < tileSize + 1; ++x)
{
// Repeat the last pixel of the image for the last vertex of the heightmap
int offset = std::min(y, tileSize - 1) * mapLineSkip + std::min(x, tileSize - 1) * bytesPP;
heightmap[(tileSize - y) * (tileSize + 1) + x] = static_cast<u16>(256) * std::max({
mapdata[offset],
mapdata[offset + 1],
mapdata[offset + 2]});
}
return INFO::OK;
}