Crafter.Graphics/interfaces/Crafter.Graphics-RenderingElement.cppm

400 lines
19 KiB
Text
Raw Normal View History

2025-11-25 19:54:03 +01:00
/*
Crafter®.Graphics
Copyright (C) 2025 Catcrafts®
catcrafts.net
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License version 3.0 as published by the Free Software Foundation;
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
2025-12-28 00:05:03 +01:00
module;
#define STB_IMAGE_IMPLEMENTATION
#include "../lib/stb_image.h"
2025-11-25 19:54:03 +01:00
export module Crafter.Graphics:RenderingElement;
import std;
import :Transform;
import :Types;
2025-12-28 00:47:27 +01:00
import :Image;
2025-12-28 00:05:03 +01:00
import :Window;
2025-11-25 19:54:03 +01:00
export namespace Crafter {
2025-12-28 00:05:03 +01:00
struct RenderElementScalingOwning {
std::vector<Pixel_BU8_GU8_RU8_AU8> scalingBuffer;
2025-11-26 03:35:58 +01:00
std::uint_fast32_t bufferWidth;
std::uint_fast32_t bufferHeight;
2025-12-28 00:05:03 +01:00
bool bufferUpdated = true;
RenderElementScalingOwning() = default;
RenderElementScalingOwning(std::uint_fast32_t bufferWidth, std::uint_fast32_t bufferHeight) : scalingBuffer(bufferWidth*bufferHeight), bufferWidth(bufferWidth), bufferHeight(bufferHeight) {
}
2025-11-25 19:54:03 +01:00
};
2025-12-21 19:52:41 +01:00
2025-12-28 00:05:03 +01:00
struct RenderElementScalingNonOwning {
Pixel_BU8_GU8_RU8_AU8* scalingBuffer;
2025-12-21 19:52:41 +01:00
std::uint_fast32_t bufferWidth;
std::uint_fast32_t bufferHeight;
2025-12-28 00:05:03 +01:00
bool bufferUpdated = true;
RenderElementScalingNonOwning() = default;
RenderElementScalingNonOwning(Pixel_BU8_GU8_RU8_AU8* scalingBuffer, std::uint_fast32_t bufferWidth, std::uint_fast32_t bufferHeight) : scalingBuffer(scalingBuffer), bufferWidth(bufferWidth), bufferHeight(bufferHeight) {
}
};
struct RenderElementRotating {
2025-12-21 19:52:41 +01:00
std::uint_fast32_t rotation;
bool rotationUpdated = true;
2025-12-28 00:05:03 +01:00
RenderElementRotating() = default;
RenderElementRotating(std::uint_fast32_t rotation) : rotation(rotation) {
}
};
struct EmptyScalingBase {};
struct EmptyRotatingBase {};
template<bool Scaling, bool Owning>
using ScalingBase =
std::conditional_t<
Scaling,
std::conditional_t<Owning,
RenderElementScalingOwning,
RenderElementScalingNonOwning>,
EmptyScalingBase
>;
template<bool Rotating>
using RotatingBase =
std::conditional_t<
Rotating,
RenderElementRotating,
EmptyRotatingBase
>;
class RenderingElementBase : public Transform {
public:
std::vector<Pixel_BU8_GU8_RU8_AU8> buffer;
OpaqueType opaque;
2025-12-28 00:47:27 +01:00
RenderingElementBase(Anchor anchor) : Transform(anchor) {
scaled.width = 0;
}
2025-12-28 00:05:03 +01:00
RenderingElementBase(Anchor anchor, OpaqueType opaque) : Transform(anchor), opaque(opaque) {
scaled.width = 0;
}
};
template<bool Scaling, bool Owning, bool Rotating> requires ((!Rotating || Scaling) && (!Owning || Scaling))
class RenderingElement : public RenderingElementBase, public ScalingBase<Scaling, Owning>, public RotatingBase<Rotating> {
public:
RenderingElement() = default;
RenderingElement(Anchor anchor, OpaqueType opaque) : RenderingElementBase(anchor, opaque) {
}
RenderingElement(Anchor anchor, OpaqueType opaque, std::uint_fast32_t rotation) requires(Rotating) : RenderingElementBase(anchor, opaque), RotatingBase<Rotating>(rotation) {
}
2025-12-28 00:47:27 +01:00
RenderingElement(Anchor anchor, const std::string_view imagePath) : RenderingElementBase(anchor) {
2025-12-28 00:05:03 +01:00
LoadImage(imagePath);
}
2025-12-28 00:47:27 +01:00
RenderingElement(Anchor anchor, const std::string_view imagePath, std::uint_fast32_t rotation) requires(Rotating) : RenderingElementBase(anchor), RotatingBase<Rotating>(rotation) {
2025-12-28 00:05:03 +01:00
LoadImage(imagePath);
}
RenderingElement(Anchor anchor, const std::string_view imagePath, OpaqueType opaque) : RenderingElementBase(anchor, opaque) {
LoadImageNoOpaqueCheck(imagePath);
}
RenderingElement(Anchor anchor, const std::string_view imagePath, OpaqueType opaque, std::uint_fast32_t rotation) requires(Rotating) : RenderingElementBase(anchor, opaque), RotatingBase<Rotating>(rotation) {
LoadImageNoOpaqueCheck(imagePath);
}
RenderingElement(Anchor anchor, OpaqueType opaque, std::uint_fast32_t bufferWidth, std::uint_fast32_t bufferHeight, Pixel_BU8_GU8_RU8_AU8* scalingBuffer) requires(Scaling && !Owning) : RenderingElementBase(anchor, opaque), ScalingBase<Scaling, Owning>(bufferWidth, bufferHeight, scalingBuffer) {
}
RenderingElement(Anchor anchor, OpaqueType opaque, std::uint_fast32_t bufferWidth, std::uint_fast32_t bufferHeight, Pixel_BU8_GU8_RU8_AU8* scalingBuffer, std::uint_fast32_t rotation) requires(Scaling && !Owning && Rotating) : RenderingElementBase(anchor, opaque), ScalingBase<Scaling, Owning>(bufferWidth, bufferHeight, scalingBuffer), RotatingBase<Rotating>(rotation) {
}
2025-12-28 00:47:27 +01:00
RenderingElement(Anchor anchor, OpaqueType opaque, Image& image) requires(Scaling && !Owning) : RenderingElementBase(anchor, opaque), ScalingBase<Scaling, Owning>(image.buffer.data(), image.width, image.height) {
}
RenderingElement(Anchor anchor, OpaqueType opaque, Image& image, std::uint_fast32_t rotation) requires(Scaling && !Owning && Rotating) : RenderingElementBase(anchor, opaque), ScalingBase<Scaling, Owning>(image.buffer.data(), image.width, image.height), RotatingBase<Rotating>(rotation) {
}
RenderingElement(Anchor anchor, Image& image) requires(Scaling && !Owning) : RenderingElementBase(anchor, image.opaque), ScalingBase<Scaling, Owning>(image.buffer.data(), image.width, image.height) {
}
RenderingElement(Anchor anchor, Image& image, std::uint_fast32_t rotation) requires(Scaling && !Owning && Rotating) : RenderingElementBase(anchor, image.opaque), ScalingBase<Scaling, Owning>(image.buffer.data(), image.width, image.height), RotatingBase<Rotating>(rotation) {
}
2025-12-28 00:05:03 +01:00
RenderingElement(Anchor anchor, OpaqueType opaque, std::uint_fast32_t bufferWidth, std::uint_fast32_t bufferHeight) requires(Owning) : RenderingElementBase(anchor, opaque), ScalingBase<Scaling, Owning>(bufferWidth, bufferHeight) {
}
RenderingElement(Anchor anchor, OpaqueType opaque, std::uint_fast32_t bufferWidth, std::uint_fast32_t bufferHeight, std::uint_fast32_t rotation) requires(Owning && Rotating) : RenderingElementBase(anchor, opaque), ScalingBase<Scaling, Owning>(bufferWidth, bufferHeight) , RotatingBase<Rotating>(rotation) {
}
RenderingElement(RenderingElement&) = delete;
RenderingElement& operator=(RenderingElement&) = delete;
void ScaleNearestNeighbor() requires(Scaling) {
for (std::uint_fast32_t y = 0; y < scaled.height; y++) {
std::uint_fast32_t srcY = y * ScalingBase<true, Owning>::bufferHeight / scaled.height;
for (std::uint_fast32_t x = 0; x < scaled.width; x++) {
std::uint_fast32_t srcX = x * ScalingBase<true, Owning>::bufferWidth / scaled.width;
buffer[y * scaled.width + x] = ScalingBase<true, Owning>::scalingBuffer[srcY * ScalingBase<true, Owning>::bufferWidth + srcX];
}
}
}
void ScaleRotating() requires(Scaling) {
const double rad = (static_cast<double>(RotatingBase<true>::rotation) / static_cast<double>(std::numeric_limits<std::uint_fast32_t>::max())) * 2.0 * std::numbers::pi;
const std::uint_fast32_t dstWidth = scaled.width;
const std::uint_fast32_t dstHeight = scaled.height;
const double c2 = std::abs(std::cos(rad));
const double s2 = std::abs(std::sin(rad));
const double rotatedWidth = dstWidth * c2 + dstHeight * s2;
const double rotatedHeight = dstWidth * s2 + dstHeight * c2;
const std::uint_fast32_t diffX = static_cast<std::uint_fast32_t>(std::ceil((rotatedWidth - dstWidth) * 0.5));
const std::uint_fast32_t diffY = static_cast<std::uint_fast32_t>(std::ceil((rotatedHeight - dstHeight) * 0.5));
scaled.width += diffX + diffX;
scaled.height += diffY + diffY;
scaled.x -= diffX;
scaled.y -= diffY;
2025-12-28 00:47:27 +01:00
buffer.clear();
buffer.resize(scaled.width * scaled.height);
2025-12-28 00:05:03 +01:00
// Destination center
const double dstCx = (static_cast<double>(scaled.width) - 1.0) * 0.5;
const double dstCy = (static_cast<double>(scaled.height) - 1.0) * 0.5;
// Source center
const double srcCx = (static_cast<double>(ScalingBase<true, Owning>::bufferWidth) - 1.0) * 0.5;
const double srcCy = (static_cast<double>(ScalingBase<true, Owning>::bufferHeight) - 1.0) * 0.5;
const double c = std::cos(rad);
const double s = std::sin(rad);
// Scale factors (destination → source)
const double scaleX = static_cast<double>(ScalingBase<true, Owning>::bufferWidth) / dstWidth;
const double scaleY = static_cast<double>(ScalingBase<true, Owning>::bufferHeight) / dstHeight;
for (std::uint_fast32_t yB = 0; yB < scaled.height; ++yB) {
for (std::uint_fast32_t xB = 0; xB < scaled.width; ++xB) {
// ---- Destination pixel relative to center ----
const double dx = (static_cast<double>(xB) - dstCx) * scaleX;
const double dy = (static_cast<double>(yB) - dstCy) * scaleY;
// ---- Inverse rotation ----
const double sx = (c * dx - s * dy) + srcCx;
const double sy = (s * dx + c * dy) + srcCy;
// ---- Nearest neighbour sampling ----
const std::int_fast32_t srcX = static_cast<std::int_fast32_t>(std::round(sx));
const std::int_fast32_t srcY = static_cast<std::int_fast32_t>(std::round(sy));
if (srcX >= 0 && srcX < ScalingBase<true, Owning>::bufferWidth && srcY >= 0 && srcY < ScalingBase<true, Owning>::bufferHeight) {
buffer[yB * scaled.width + xB] = ScalingBase<true, Owning>::scalingBuffer[srcY * ScalingBase<true, Owning>::bufferWidth + srcX];
}
}
}
}
2025-12-21 19:52:41 +01:00
2025-12-28 00:05:03 +01:00
void UpdatePosition(Window& window, ScaleData oldScale) {
if constexpr(Scaling && !Rotating) {
if(oldScale.width != scaled.width || oldScale.height != scaled.height) {
buffer.resize(scaled.width * scaled.height);
ScaleNearestNeighbor();
window.AddDirtyRect(oldScale);
window.AddDirtyRect(scaled);
} else if(oldScale.x != scaled.x || oldScale.y != scaled.y) {
window.AddDirtyRect(oldScale);
window.AddDirtyRect(scaled);
if(ScalingBase<true, Owning>::bufferUpdated) {
ScaleNearestNeighbor();
ScalingBase<true, Owning>::bufferUpdated = false;
}
} else if(ScalingBase<true, Owning>::bufferUpdated) {
ScaleNearestNeighbor();
ScalingBase<true, Owning>::bufferUpdated = false;
}
} else if constexpr(Rotating) {
if(oldScale.width != scaled.width || oldScale.height != scaled.height) {
buffer.resize(scaled.width * scaled.height);
2025-12-28 17:34:14 +01:00
ScaleRotating();
2025-12-28 00:05:03 +01:00
window.AddDirtyRect(oldScale);
window.AddDirtyRect(scaled);
} else if(oldScale.x != scaled.x || oldScale.y != scaled.y) {
window.AddDirtyRect(oldScale);
window.AddDirtyRect(scaled);
2025-12-28 17:34:14 +01:00
if(ScalingBase<true, Owning>::bufferUpdated || RotatingBase<true>::rotationUpdated) {
2025-12-28 00:05:03 +01:00
ScaleRotating();
ScalingBase<true, Owning>::bufferUpdated = false;
RotatingBase<true>::rotationUpdated = false;
}
} else if(ScalingBase<true, Owning>::bufferUpdated || RotatingBase<true>::rotationUpdated) {
ScaleRotating();
ScalingBase<true, Owning>::bufferUpdated = false;
RotatingBase<true>::rotationUpdated = false;
}
} else {
if(oldScale.width != scaled.width || oldScale.height != scaled.height) {
buffer.resize(scaled.width * scaled.height);
window.AddDirtyRect(oldScale);
window.AddDirtyRect(scaled);
}
if(oldScale.x != scaled.x || oldScale.y != scaled.y) {
window.AddDirtyRect(oldScale);
window.AddDirtyRect(scaled);
}
}
}
void UpdatePosition(Window& window) override {
ScaleData oldScale = scaled;
window.ScaleElement(*this);
UpdatePosition(window, oldScale);
for(Transform* child : children) {
child->UpdatePosition(window, *this);
}
}
void UpdatePosition(Window& window, Transform& parent) override {
ScaleData oldScale = scaled;
window.ScaleElement(*this, parent);
UpdatePosition(window, oldScale);
for(Transform* child : children) {
child->UpdatePosition(window, *this);
}
}
void LoadImage(const std::string_view imagePath) {
std::filesystem::path abs = std::filesystem::absolute(imagePath);
int xSize;
int ySize;
unsigned char* bgData = stbi_load(abs.string().c_str(), &xSize, &ySize, nullptr, 4);
if constexpr(Scaling && !Owning) {
ScalingBase<true, false>::bufferUpdated = true;
} else if constexpr(Scaling && Owning) {
ScalingBase<true, true>::bufferWidth = xSize;
ScalingBase<true, true>::bufferHeight = ySize;
ScalingBase<true, true>::bufferUpdated = true;
2025-12-28 00:47:27 +01:00
ScalingBase<true, Owning>::scalingBuffer.resize(xSize*ySize);
2025-12-28 00:05:03 +01:00
} else {
buffer.resize(xSize*ySize);
}
opaque = OpaqueType::FullyOpaque;
if constexpr(Scaling) {
for(std::uint_fast32_t x = 0; x < xSize; x++) {
for(std::uint_fast32_t y = 0; y < ySize; y++) {
std::uint_fast32_t idx = (x*ySize+y)*4;
ScalingBase<true, Owning>::scalingBuffer[x*ySize+y].r = bgData[idx];
ScalingBase<true, Owning>::scalingBuffer[x*ySize+y].g = bgData[idx+1];
ScalingBase<true, Owning>::scalingBuffer[x*ySize+y].b = bgData[idx+2];
2025-12-28 00:47:27 +01:00
ScalingBase<true, Owning>::scalingBuffer[x*ySize+y].a = bgData[idx+3];
2025-12-28 00:05:03 +01:00
}
}
for(std::uint_fast32_t i = 0; i < xSize*ySize; i++) {
if(ScalingBase<true, Owning>::scalingBuffer[i].a != 255) {
opaque = OpaqueType::SemiOpaque;
for(std::uint_fast32_t i2 = 0; i2 < xSize*ySize; i2++) {
if(ScalingBase<true, Owning>::scalingBuffer[i2].a != 0 && ScalingBase<true, Owning>::scalingBuffer[i2].a != 255) {
opaque = OpaqueType::Transparent;
return;
}
}
return;
}
}
} else {
for(std::uint_fast32_t x = 0; x < xSize; x++) {
for(std::uint_fast32_t y = 0; y < ySize; y++) {
std::uint_fast32_t idx = (x*ySize+y)*4;
buffer[x*ySize+y].r = bgData[idx];
buffer[x*ySize+y].g = bgData[idx+1];
buffer[x*ySize+y].b = bgData[idx+2];
buffer[x*ySize+y].a = bgData[idx+3];
}
}
for(std::uint_fast32_t i = 0; i < xSize*ySize; i++) {
if(buffer[i].a != 255) {
opaque = OpaqueType::SemiOpaque;
for(std::uint_fast32_t i2 = 0; i2 < xSize*ySize; i2++) {
if(buffer[i2].a != 0 && buffer[i2].a != 255) {
opaque = OpaqueType::Transparent;
return;
}
}
return;
}
}
}
}
void LoadImageNoOpaqueCheck(const std::string_view imagePath) {
std::filesystem::path abs = std::filesystem::absolute(imagePath);
int xSize;
int ySize;
unsigned char* bgData = stbi_load(abs.string().c_str(), &xSize, &ySize, nullptr, 4);
if constexpr(Scaling && !Owning) {
ScalingBase<true, false>::bufferUpdated = true;
} else if constexpr(Scaling && Owning) {
ScalingBase<true, true>::bufferWidth = xSize;
ScalingBase<true, true>::bufferHeight = ySize;
ScalingBase<true, true>::bufferUpdated = true;
2025-12-28 00:47:27 +01:00
ScalingBase<true, Owning>::scalingBuffer.resize(xSize*ySize);
2025-12-28 00:05:03 +01:00
} else {
buffer.resize(xSize*ySize);
}
if constexpr(Scaling) {
for(std::uint_fast32_t x = 0; x < xSize; x++) {
for(std::uint_fast32_t y = 0; y < ySize; y++) {
std::uint_fast32_t idx = (x*ySize+y)*4;
ScalingBase<true, Owning>::scalingBuffer[x*ySize+y].r = bgData[idx];
ScalingBase<true, Owning>::scalingBuffer[x*ySize+y].g = bgData[idx+1];
ScalingBase<true, Owning>::scalingBuffer[x*ySize+y].b = bgData[idx+2];
ScalingBase<true, Owning>::scalingBuffer[x*ySize+y].a = bgData[idx+3];
}
}
} else {
for(std::uint_fast32_t x = 0; x < xSize; x++) {
for(std::uint_fast32_t y = 0; y < ySize; y++) {
std::uint_fast32_t idx = (x*ySize+y)*4;
buffer[x*ySize+y].r = bgData[idx];
buffer[x*ySize+y].g = bgData[idx+1];
buffer[x*ySize+y].b = bgData[idx+2];
buffer[x*ySize+y].a = bgData[idx+3];
}
}
}
}
2025-12-21 19:52:41 +01:00
};
2025-11-25 19:54:03 +01:00
}