iwa/source/io/bitmap.cpp
2024-04-06 14:11:26 +02:00

207 lines
6.6 KiB
C++

#include "iwa/io/bitmap.hpp"
#include <cstring>
#include <filesystem>
#include <mijin/detect.hpp>
#if MIJIN_COMPILER == MIJIN_COMPILER_GCC || MIJIN_COMPILER == MIJIN_COMPILER_CLANG
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#endif // MIJIN_COMPILER == MIJIN_COMPILER_GCC || MIJIN_COMPILER == MIJIN_COMPILER_CLANG
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define STBI_FAILURE_USERMSG
#include <stb_image.h>
#include <stb_image_write.h>
#undef STB_IMAGE_IMPLEMENTATION
#undef STB_IMAGE_WRITE_IMPLEMENTATION
#if MIJIN_COMPILER == MIJIN_COMPILER_GCC || MIJIN_COMPILER == MIJIN_COMPILER_CLANG
#pragma GCC diagnostic pop
#endif // MIJIN_COMPILER == MIJIN_COMPILER_GCC || MIJIN_COMPILER == MIJIN_COMPILER_CLANG
#include "iwa/log.hpp"
namespace iwa
{
namespace
{
vk::Format formatByComponents(int components, ImageFormatType formatType)
{
switch (formatType)
{
case ImageFormatType::UNORM:
switch (components)
{
case 1:
return vk::Format::eR8Unorm;
case 2:
return vk::Format::eR8G8Unorm;
case 3:
return vk::Format::eR8G8B8Unorm;
case 4:
return vk::Format::eR8G8B8A8Unorm;
}
break;
case ImageFormatType::UINT:
switch (components)
{
case 1:
return vk::Format::eR8Uint;
case 2:
return vk::Format::eR8G8Uint;
case 3:
return vk::Format::eR8G8B8Uint;
case 4:
return vk::Format::eR8G8B8A8Uint;
}
break;
case ImageFormatType::SRGB:
switch (components)
{
case 1:
return vk::Format::eR8Srgb;
case 2:
return vk::Format::eR8G8Srgb;
case 3:
return vk::Format::eR8G8B8Srgb;
case 4:
return vk::Format::eR8G8B8A8Srgb;
}
break;
}
logAndDie("Could not detect image format.");
}
ObjectPtr<Bitmap> loadBitmapFromSTBI(stbi_uc* data, std::size_t width, std::size_t height, std::size_t /* components */,
const BitmapLoadOptions& options)
{
if (data == nullptr)
{
logAndDie("Could not load texture: {}", stbi_failure_reason());
}
const BitmapCreationArgs args =
{
.format = formatByComponents(/*components */ 4, options.formatType),
.size = {static_cast<unsigned>(width), static_cast<unsigned>(height)}
};
ObjectPtr <Bitmap> bitmap = Bitmap::create(args);
std::memcpy(bitmap->getData().data(), data, bitmap->getData().size());
stbi_image_free(data);
return bitmap;
}
int stbiReadCallback(void* user, char* data, int size)
{
mijin::Stream& stream = *static_cast<mijin::Stream*>(user);
std::size_t bytesRead = 0;
const mijin::StreamError error = stream.readRaw(data, size, /* partial = */ true, &bytesRead);
if (error != mijin::StreamError::SUCCESS)
{
// TODO: return what?
return 0;
}
return static_cast<int>(bytesRead);
}
void stbiSkipCallback(void* user, int bytes)
{
mijin::Stream& stream = *static_cast<mijin::Stream*>(user);
(void) stream.seek(bytes, mijin::SeekMode::RELATIVE);
}
int stbiEofCallback(void* user)
{
mijin::Stream& stream = *static_cast<mijin::Stream*>(user);
return stream.isAtEnd();
}
} // namespace
ObjectPtr<Bitmap> loadBitmap(const BitmapLoadOptions& options)
{
int width, height, components; // NOLINT(cppcoreguidelines-init-variables)
const stbi_io_callbacks callbacks{
.read = &stbiReadCallback,
.skip = &stbiSkipCallback,
.eof = &stbiEofCallback
};
stbi_uc* data = stbi_load_from_callbacks(
/* clbk = */ &callbacks,
/* user = */ options.stream,
/* x = */ &width,
/* y = */ &height,
/* channels_in_file = */ &components,
/* desired_channels = */ 4);
// NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage) Yes, width isn't initialized if STB fails. No, that is not a problem
return loadBitmapFromSTBI(data, width, height, components, options);
}
void saveBitmap(const Bitmap& bitmap, const BitmapSaveOptions& options)
{
namespace fs = std::filesystem;
BitmapCodec codec = options.codec;
if (codec == BitmapCodec::NONE)
{
std::string extension = fs::path(options.fileName).extension().string();
std::ranges::transform(extension, extension.begin(), [](char chr)
{ return static_cast<char>(std::tolower(chr)); });
if (extension == ".png")
{
codec = BitmapCodec::PNG;
} else if (extension == ".jpg" || extension == ".jpeg")
{
codec = BitmapCodec::JPEG;
} else
{
logAndDie("Couldn't guess image file codec from file extension: {}.", extension);
}
}
int comp = 0;
switch (bitmap.getFormat())
{
case vk::Format::eR8G8B8A8Unorm:
case vk::Format::eR8G8B8A8Srgb:
comp = 4;
break;
case vk::Format::eR8G8B8Unorm:
case vk::Format::eR8G8B8Srgb:
comp = 3;
break;
default:
logAndDie("Cannot write this image format (yet).");
break;
}
switch (codec)
{
case BitmapCodec::NONE:
assert(0);
break;
case BitmapCodec::PNG:
stbi_write_png(
/* filename = */ options.fileName.c_str(),
/* w = */ static_cast<int>(bitmap.getSize().width),
/* h = */ static_cast<int>(bitmap.getSize().height),
/* comp = */ comp,
/* data = */ bitmap.getData().data(),
/* stride_in_bytes = */ comp * static_cast<int>(bitmap.getSize().width));
break;
case BitmapCodec::JPEG:
stbi_write_jpg(
/* filename = */ options.fileName.c_str(),
/* x = */ static_cast<int>(bitmap.getSize().width),
/* y = */ static_cast<int>(bitmap.getSize().height),
/* comp = */ comp,
/* data = */ bitmap.getData().data(),
/* quality = */ options.jpegQuality
);
break;
}
}
} // namespace iwa