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

306 lines
10 KiB
C++

#include "iwa/resource/bitmap.hpp"
#include <cmath>
#include "iwa/log.hpp"
#include "iwa/util/vkutil.hpp"
namespace iwa
{
enum class InterpolationMethod
{
NEAREST
};
class BitmapViewBase
{
protected:
Bitmap* mBitmap;
protected:
explicit inline BitmapViewBase(Bitmap* bitmap_) : mBitmap(bitmap_) {}
public:
BitmapViewBase(const BitmapViewBase&) = delete;
BitmapViewBase(BitmapViewBase&&) = default;
BitmapViewBase& operator=(const BitmapViewBase&) = delete;
BitmapViewBase& operator=(BitmapViewBase&&) = default;
virtual ~BitmapViewBase() = default; // just to make clang happy, should be optimised out again
[[nodiscard]] virtual glm::vec4 getPixel(unsigned x, unsigned y) const = 0;
[[nodiscard]] virtual glm::vec4 getPixelRelative(float x, float y, InterpolationMethod interpolation = InterpolationMethod::NEAREST) const = 0;
[[nodiscard]] virtual std::vector<glm::vec4> getPixels(unsigned x, unsigned y, unsigned width, unsigned height) const = 0;
virtual void fill(const glm::vec4& color) = 0;
virtual void copyChannels(const Bitmap& other, const std::vector<ChannelMapping>& mappings) = 0;
virtual void multiply(const glm::vec4& color) = 0;
};
template<typename TData, int numComponents, bool normalized = std::is_floating_point_v<TData>>
class BitmapView : public BitmapViewBase
{
private:
static constexpr std::size_t pixelSize = sizeof(TData) * numComponents;
public:
explicit inline BitmapView(Bitmap* bitmap_) : BitmapViewBase(bitmap_) {}
TData* getPixelRaw(unsigned x, unsigned y)
{
const std::size_t offset = x + y * mBitmap->getSize().width;
return reinterpret_cast<TData*>(&mBitmap->getData()[offset * pixelSize]);
}
[[nodiscard]] const TData* getPixelRaw(unsigned x, unsigned y) const
{
const std::size_t offset = x + y * mBitmap->getSize().width;
return reinterpret_cast<const TData*>(&mBitmap->getData()[offset * pixelSize]);
}
[[nodiscard]] TData convertChannel(float channel) const
{
if constexpr (normalized) {
return static_cast<TData>(channel);
} else {
return static_cast<TData>(255 * channel);
}
}
[[nodiscard]] float convertChannelBack(TData channel) const
{
if constexpr (normalized) {
return static_cast<float>(channel);
}
else {
return channel / 255.f;
}
}
[[nodiscard]] TData clampChannel(TData value)
{
if constexpr (normalized) {
return std::clamp(value, TData(0), TData(1));
}
else {
return std::clamp(value, TData(0), TData(255));
}
}
void convertColor(const glm::vec4& color, std::array<TData, numComponents>& outConverted) const
{
for (int comp = 0; comp < numComponents; ++comp)
{
outConverted[comp] = convertChannel(color[comp]);
}
}
[[nodiscard]] glm::vec4 convertColorBack(const TData* raw) const
{
glm::vec4 result;
for (int comp = 0; comp < numComponents; ++comp)
{
result[comp] = convertChannelBack(raw[comp]);
}
return result;
}
// overrides
[[nodiscard]] glm::vec4 getPixel(unsigned x, unsigned y) const override
{
const TData* pixelRaw = getPixelRaw(x, y);
return convertColorBack(pixelRaw);
}
[[nodiscard]] glm::vec4 getPixelRelative(float x, float y, InterpolationMethod interpolation = InterpolationMethod::NEAREST) const override
{
const vk::Extent2D size = mBitmap->getSize();
(void) interpolation; // TODO
const unsigned myX = std::clamp(static_cast<unsigned>(std::round(x * static_cast<float>(size.width))), 0u, size.width - 1);
const unsigned myY = std::clamp(static_cast<unsigned>(std::round(y * static_cast<float>(size.height))), 0u, size.height - 1);
return getPixel(myX, myY);
}
[[nodiscard]] std::vector<glm::vec4> getPixels(unsigned x, unsigned y, unsigned width, unsigned height) const override
{
std::vector<glm::vec4> pixels;
pixels.resize(static_cast<std::size_t>(width) * height);
for (unsigned ypos = y; ypos < y + height; ++ypos)
{
for (unsigned xpos = x; xpos < x + width; ++xpos)
{
pixels[xpos + height * ypos] = getPixel(xpos, ypos);
}
}
return pixels;
}
void fill(const glm::vec4& color) override
{
std::array<TData, numComponents> convertedColor; // NOLINT(cppcoreguidelines-pro-type-member-init)
convertColor(color, convertedColor);
for (unsigned y = 0; y < mBitmap->getSize().height; ++y)
{
for (unsigned x = 0; x < mBitmap->getSize().width; ++x)
{
TData* pixel = getPixelRaw(x, y);
for (int comp = 0; comp < numComponents; ++comp)
{
pixel[comp] = convertedColor[comp];
}
}
}
};
// TODO: maybe introduce a "dual-mView" class that includes both types at once?
void copyChannels(const Bitmap& other, const std::vector<ChannelMapping>& mappings) override
{
const vk::Extent2D size = mBitmap->getSize();
const std::vector<glm::vec4> otherPixels = other.getAllPixels();
if(otherPixels.size() == static_cast<std::size_t>(size.width) * size.height)
{
for (unsigned y = 0; y < size.height; ++y)
{
for (unsigned x = 0; x < size.width; ++x)
{
const glm::vec4 color = otherPixels[x + y * size.height];
for (const ChannelMapping& mapping : mappings)
{
assert(static_cast<int>(mapping.to) < numComponents);
TData* myPixel = getPixelRaw(x, y);
myPixel[static_cast<int>(mapping.to)] = convertChannel(color[static_cast<int>(mapping.from)]);
}
}
}
}
else
{
const vk::Extent2D otherSize = other.getSize();
const float factorX = static_cast<float>(otherSize.width) / static_cast<float>(size.width);
const float factorY = static_cast<float>(otherSize.height) / static_cast<float>(size.height);
for (unsigned y = 0; y < size.height; ++y)
{
for (unsigned x = 0; x < size.width; ++x)
{
const unsigned otherX = std::clamp(static_cast<unsigned>(std::round(factorX * static_cast<float>(x))), 0u, otherSize.width - 1);
const unsigned otherY = std::clamp(static_cast<unsigned>(std::round(factorY * static_cast<float>(x))), 0u, otherSize.height - 1);
const glm::vec4 color = otherPixels[otherX + otherY * size.height];
for (const ChannelMapping& mapping : mappings)
{
assert(static_cast<int>(mapping.to) < numComponents);
TData* myPixel = getPixelRaw(x, y);
myPixel[static_cast<int>(mapping.to)] = convertChannel(color[static_cast<int>(mapping.from)]);
}
}
}
}
}
void multiply(const glm::vec4& color) override
{
for (unsigned y = 0; y < mBitmap->getSize().height; ++y)
{
for (unsigned x = 0; x < mBitmap->getSize().width; ++x)
{
TData* pixel = getPixelRaw(x, y);
for (int comp = 0; comp < numComponents; ++comp)
{
pixel[comp] = clampChannel(static_cast<TData>(pixel[comp] * color[comp]));
}
}
}
}
};
void Bitmap::createView()
{
switch(mFormat)
{
case vk::Format::eR8Uint:
case vk::Format::eR8Unorm:
case vk::Format::eR8Srgb:
mView = std::make_unique<BitmapView<std::uint8_t, 1>>(this);
break;
case vk::Format::eR8G8Uint:
case vk::Format::eR8G8Unorm:
case vk::Format::eR8G8Srgb:
mView = std::make_unique<BitmapView<std::uint8_t, 2>>(this);
break;
case vk::Format::eR8G8B8Uint:
case vk::Format::eR8G8B8Unorm:
case vk::Format::eR8G8B8Srgb:
mView = std::make_unique<BitmapView<std::uint8_t, 3>>(this);
break;
case vk::Format::eR8G8B8A8Uint:
case vk::Format::eR8G8B8A8Unorm:
case vk::Format::eR8G8B8A8Srgb:
mView = std::make_unique<BitmapView<std::uint8_t, 4>>(this);
break;
case vk::Format::eR32Sfloat:
mView = std::make_unique<BitmapView<float, 1>>(this);
break;
case vk::Format::eR32G32Sfloat:
mView = std::make_unique<BitmapView<float, 2>>(this);
break;
case vk::Format::eR32G32B32Sfloat:
mView = std::make_unique<BitmapView<float, 3>>(this);
break;
case vk::Format::eR32G32B32A32Sfloat:
mView = std::make_unique<BitmapView<float, 4>>(this);
break;
default:
logAndDie("Missing format for bitmap mView!");
}
}
//
// public functions
//
Bitmap::Bitmap(BitmapCreationArgs args, ObjectPtr<BaseObject> owner)
: super_t(std::move(owner)), mFormat(args.format), mSize(args.size)
{
const std::size_t dataSize = static_cast<std::size_t>(args.size.width) * args.size.height * vkFormatSize(args.format);
if (args.initialData.empty())
{
mData.resize(dataSize);
}
else
{
MIJIN_ASSERT(args.initialData->byteSize() == dataSize, "Bitmap initial data size is invalid.");
mData = std::move(*args.initialData);
}
createView();
}
Bitmap::~Bitmap() // NOLINT(modernize-use-equals-default)
{
// needs to be implemented for the mView to be deleted correctly
}
glm::vec4 Bitmap::getPixel(unsigned int x, unsigned int y) const
{
return mView->getPixel(x, y);
}
std::vector<glm::vec4> Bitmap::getPixels(unsigned x, unsigned y, unsigned width, unsigned height) const
{
return mView->getPixels(x, y, width, height);
}
void Bitmap::fill(const glm::vec4& color)
{
mView->fill(color);
}
void Bitmap::copyChannels(const Bitmap& other, const std::vector<ChannelMapping>& mappings)
{
mView->copyChannels(other, mappings);
}
void Bitmap::multiply(const glm::vec4& color)
{
mView->multiply(color);
}
} // namespace iwa