#include "iwa/resource/bitmap.hpp" #include #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 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& mappings) = 0; virtual void multiply(const glm::vec4& color) = 0; }; template> 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(&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(&mBitmap->getData()[offset * pixelSize]); } [[nodiscard]] TData convertChannel(float channel) const { if constexpr (normalized) { return static_cast(channel); } else { return static_cast(255 * channel); } } [[nodiscard]] float convertChannelBack(TData channel) const { if constexpr (normalized) { return static_cast(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& 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(std::round(x * static_cast(size.width))), 0u, size.width - 1); const unsigned myY = std::clamp(static_cast(std::round(y * static_cast(size.height))), 0u, size.height - 1); return getPixel(myX, myY); } [[nodiscard]] std::vector getPixels(unsigned x, unsigned y, unsigned width, unsigned height) const override { std::vector pixels; pixels.resize(static_cast(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 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& mappings) override { const vk::Extent2D size = mBitmap->getSize(); const std::vector otherPixels = other.getAllPixels(); if(otherPixels.size() == static_cast(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(mapping.to) < numComponents); TData* myPixel = getPixelRaw(x, y); myPixel[static_cast(mapping.to)] = convertChannel(color[static_cast(mapping.from)]); } } } } else { const vk::Extent2D otherSize = other.getSize(); const float factorX = static_cast(otherSize.width) / static_cast(size.width); const float factorY = static_cast(otherSize.height) / static_cast(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(std::round(factorX * static_cast(x))), 0u, otherSize.width - 1); const unsigned otherY = std::clamp(static_cast(std::round(factorY * static_cast(x))), 0u, otherSize.height - 1); const glm::vec4 color = otherPixels[otherX + otherY * size.height]; for (const ChannelMapping& mapping : mappings) { assert(static_cast(mapping.to) < numComponents); TData* myPixel = getPixelRaw(x, y); myPixel[static_cast(mapping.to)] = convertChannel(color[static_cast(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(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>(this); break; case vk::Format::eR8G8Uint: case vk::Format::eR8G8Unorm: case vk::Format::eR8G8Srgb: mView = std::make_unique>(this); break; case vk::Format::eR8G8B8Uint: case vk::Format::eR8G8B8Unorm: case vk::Format::eR8G8B8Srgb: mView = std::make_unique>(this); break; case vk::Format::eR8G8B8A8Uint: case vk::Format::eR8G8B8A8Unorm: case vk::Format::eR8G8B8A8Srgb: mView = std::make_unique>(this); break; case vk::Format::eR32Sfloat: mView = std::make_unique>(this); break; case vk::Format::eR32G32Sfloat: mView = std::make_unique>(this); break; case vk::Format::eR32G32B32Sfloat: mView = std::make_unique>(this); break; case vk::Format::eR32G32B32A32Sfloat: mView = std::make_unique>(this); break; default: logAndDie("Missing format for bitmap mView!"); } } // // public functions // Bitmap::Bitmap(BitmapCreationArgs args, ObjectPtr owner) : super_t(std::move(owner)), mFormat(args.format), mSize(args.size) { const std::size_t dataSize = static_cast(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 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& mappings) { mView->copyChannels(other, mappings); } void Bitmap::multiply(const glm::vec4& color) { mView->multiply(color); } } // namespace iwa