wraitii
12aa35eb3b
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.
109 lines
3.7 KiB
C++
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;
|
|
}
|