/* 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 as published by the Free Software Foundation; either version 3.0 of the License, or (at your option) any later version. 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 */ module Crafter.Graphics:RenderingElementScalingRotating2D_impl; import :RenderingElement; import :Window; import :Types; import :Font; import std; using namespace Crafter; RenderingElementScalingRotating2D::RenderingElementScalingRotating2D(OpaqueType opaque) : RenderingElement(opaque), rotation(0) { } RenderingElementScalingRotating2D::RenderingElementScalingRotating2D(OpaqueType opaque, std::int_fast32_t anchorX, std::int_fast32_t anchorY, std::uint_fast32_t relativeWidth, std::uint_fast32_t relativeHeight, std::int_fast32_t anchorOffsetX, std::int_fast32_t anchorOffsetY, std::int_fast32_t z, bool ignoreScaling) : RenderingElement(opaque, anchorX, anchorY, relativeWidth, relativeHeight, anchorOffsetX, anchorOffsetY, z, ignoreScaling), rotation(0) { } RenderingElementScalingRotating2D::RenderingElementScalingRotating2D(OpaqueType opaque, std::uint_fast32_t bufferWidth, std::uint_fast32_t bufferHeight, std::uint_fast32_t rotation, std::int_fast32_t anchorX, std::int_fast32_t anchorY, std::uint_fast32_t relativeWidth, std::uint_fast32_t relativeHeight, std::int_fast32_t anchorOffsetX, std::int_fast32_t anchorOffsetY, std::int_fast32_t z, bool ignoreScaling) : bufferWidth(bufferWidth), bufferHeight(bufferHeight), buffer(bufferWidth*bufferHeight), rotation(rotation), RenderingElement(opaque, anchorX, anchorY, relativeWidth, relativeHeight, anchorOffsetX, anchorOffsetY, z, ignoreScaling) { } void RenderingElementScalingRotating2D::ResizeBuffer(std::uint_fast32_t width, std::uint_fast32_t height) { this->bufferWidth = width; this->bufferHeight = height; buffer.resize(width * height); } void RenderingElementScalingRotating2D::UpdateRotation(std::uint_fast32_t rotation) { rotationUpdated = true; this->rotation = rotation; } void RenderingElementScalingRotating2D::UpdateScaledBuffer(ScaleData& scaled) { // Rotation const double rad = (static_cast(rotation) / static_cast(std::numeric_limits::max())) * 2.0 * std::numbers::pi; const std::uint_fast32_t dstWidth = scaled.width; const std::uint_fast32_t dstHeight = scaled.height; const double rotatedWidth = scaled.width * (std::abs(std::cos(rad)) + std::abs(std::sin(rad))); const double rotatedHeight = scaled.height * (std::abs(std::cos(rad)) + std::abs(std::sin(rad))); const std::uint_fast32_t diffX = std::ceil((rotatedWidth - scaled.width)/2); const std::uint_fast32_t diffY = std::ceil((rotatedHeight - scaled.height)/2); scaled.width += diffX + diffX; scaled.height += diffY + diffY; scaled.x -= diffX; scaled.y -= diffY; bufferScaled.clear(); bufferScaled.resize(scaled.width * scaled.height); // Destination center const double dstCx = (static_cast(scaled.width) - 1.0) * 0.5; const double dstCy = (static_cast(scaled.height) - 1.0) * 0.5; // Source center const double srcCx = (static_cast(bufferWidth) - 1.0) * 0.5; const double srcCy = (static_cast(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(bufferWidth) / dstWidth; const double scaleY = static_cast(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(xB) - dstCx) * scaleX; const double dy = (static_cast(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::round(sx)); const std::int_fast32_t srcY = static_cast(std::round(sy)); if (srcX >= 0 && srcX < bufferWidth && srcY >= 0 && srcY < bufferHeight) { bufferScaled[yB * scaled.width + xB] = buffer[srcY * bufferWidth + srcX]; } } } } void RenderingElementScalingRotating2D::UpdatePosition(Window& window) { ScaleData oldScale = scaled; window.ScaleElement(*this); if(oldScale.width != scaled.width || oldScale.height != scaled.height || rotationUpdated) { UpdateScaledBuffer(scaled); window.AddDirtyRect(oldScale); window.AddDirtyRect(scaled); rotationUpdated = false; } else if(oldScale.x != scaled.x || oldScale.y != scaled.y) { const double rad = (static_cast(rotation) / static_cast(std::numeric_limits::max())) * 2.0 * std::numbers::pi; const double rotatedWidth = scaled.width * (std::abs(std::cos(rad)) + std::abs(std::sin(rad))); const double rotatedHeight = scaled.height * (std::abs(std::cos(rad)) + std::abs(std::sin(rad))); scaled.width = static_cast(std::ceil(rotatedWidth)); scaled.height = static_cast(std::ceil(rotatedHeight)); window.AddDirtyRect(oldScale); window.AddDirtyRect(scaled); } for(Transform* child : children) { child->UpdatePosition(window, *this); } } void RenderingElementScalingRotating2D::UpdatePosition(Window& window, Transform& parent) { ScaleData oldScale = scaled; window.ScaleElement(*this, parent); if(oldScale.width != scaled.width || oldScale.height != scaled.height || rotationUpdated) { UpdateScaledBuffer(scaled); window.AddDirtyRect(oldScale); window.AddDirtyRect(scaled); rotationUpdated = false; } else if(oldScale.x != scaled.x || oldScale.y != scaled.y) { const double rad = (static_cast(rotation) / static_cast(std::numeric_limits::max())) * 2.0 * std::numbers::pi; const double rotatedWidth = scaled.width * (std::abs(std::cos(rad)) + std::abs(std::sin(rad))); const double rotatedHeight = scaled.height * (std::abs(std::cos(rad)) + std::abs(std::sin(rad))); scaled.width = static_cast(std::ceil(rotatedWidth)); scaled.height = static_cast(std::ceil(rotatedHeight)); window.AddDirtyRect(oldScale); window.AddDirtyRect(scaled); } for(Transform* child : children) { child->UpdatePosition(window, *this); } }