💿🐜 Antkeeper source code https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

179 lines
5.0 KiB

/*
* Copyright (C) 2021 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code 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 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "resource-loader.hpp"
#include "stb/stb_image.h"
#include "resources/image.hpp"
#include <cstring>
#include <stdexcept>
#include <physfs.h>
#include <tinyexr.h>
template <>
image* resource_loader<image>::load(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path)
{
unsigned char* buffer;
int size;
::image* image = nullptr;
// Read input stream into buffer
size = static_cast<int>(PHYSFS_fileLength(file));
buffer = new unsigned char[size];
PHYSFS_readBytes(file, buffer, size);
// Select loader according to file extension
if (path.extension() == ".exr")
{
// Load OpenEXR with TinyEXR
int status = TINYEXR_SUCCESS;
const char* error = nullptr;
// Read EXR version
EXRVersion exr_version;
status = ParseEXRVersionFromMemory(&exr_version, buffer, size);
if (status != TINYEXR_SUCCESS)
{
delete[] buffer;
throw std::runtime_error("TinyEXR parse version error (" + std::to_string(status) + "): invalid EXR file");
}
// Check if image is multipart
if (exr_version.multipart)
{
throw std::runtime_error("OpenEXR multipart images not supported");
}
// Read EXR header
EXRHeader exr_header;
InitEXRHeader(&exr_header);
status = ParseEXRHeaderFromMemory(&exr_header, &exr_version, buffer, size, &error);
if (status != TINYEXR_SUCCESS)
{
std::string error_string(error);
FreeEXRErrorMessage(error);
delete[] buffer;
throw std::runtime_error("TinyEXR parse header error (" + std::to_string(status) + "): " + error_string);
}
// Check if image is tiled
if (exr_header.tiled)
{
FreeEXRHeader(&exr_header);
delete[] buffer;
throw std::runtime_error("OpenEXR tiled images not supported");
}
// Read half channels as float
for (int i = 0; i < exr_header.num_channels; ++i)
{
if (exr_header.pixel_types[i] == TINYEXR_PIXELTYPE_HALF)
{
exr_header.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT;
}
}
// Read EXR data
EXRImage exr_image;
InitEXRImage(&exr_image);
status = LoadEXRImageFromMemory(&exr_image, &exr_header, buffer, size, &error);
if (status != TINYEXR_SUCCESS)
{
std::string error_string(error);
FreeEXRErrorMessage(error);
FreeEXRHeader(&exr_header);
delete[] buffer;
throw std::runtime_error("TinyEXR load error (" + std::to_string(status) + "): " + error_string);
}
// Free file buffer
delete[] buffer;
// Create image
image = new ::image();
image->format(sizeof(float), exr_image.num_channels);
image->resize(static_cast<unsigned int>(exr_image.width), static_cast<unsigned int>(exr_image.height));
// Fill image pixels
float* component = static_cast<float*>(image->get_pixels());
for (int y = exr_image.height - 1; y >= 0; --y)
{
int row_offset = y * exr_image.width;
for (int x = 0; x < exr_image.width; ++x)
{
int pixel_index = row_offset + x;
for (int c = exr_image.num_channels - 1; c >= 0; --c)
{
*(component++) = reinterpret_cast<float**>(exr_image.images)[c][pixel_index];
}
}
}
// Free EXR data
FreeEXRImage(&exr_image);
FreeEXRHeader(&exr_header);
}
else
{
// Load all other formats with stb_image
// Determine if image is in an HDR format
bool hdr = (stbi_is_hdr_from_memory(buffer, size) != 0);
// Set vertical flip on load in order to upload pixels correctly to OpenGL
stbi_set_flip_vertically_on_load(true);
// Load image data
void* pixels = nullptr;
int width = 0;
int height = 0;
int channels = 0;
if (hdr)
{
pixels = stbi_loadf_from_memory(buffer, size, &width, &height, &channels, 0);
}
else
{
pixels = stbi_load_from_memory(buffer, size, &width, &height, &channels, 0);
}
// Free file buffer
delete[] buffer;
// Check if image was loaded
if (!pixels)
{
throw std::runtime_error("STBI failed to load image from memory.");
}
// Create image
std::size_t component_size = (hdr) ? sizeof(float) : sizeof(unsigned char);
image = new ::image();
image->format(component_size, channels);
image->resize(static_cast<unsigned int>(width), static_cast<unsigned int>(height));
std::memcpy(image->get_pixels(), pixels, image->get_size());
// Free loaded image data
stbi_image_free(pixels);
}
return image;
}