14.4: Image › Rotate Canvas 90° Clockwise and Counterclockwise; quarter turns on whole pixels copy exactly
This commit is contained in:
21 files changed
+419
-54
No files matched your search
@@ -92,7 +92,7 @@ QTransform BrushRaster::pixelToDocument(const LayerTransform &transform, int wid
|
||||
const QPointF center = transform.center();
|
||||
QTransform map;
|
||||
map.translate(center.x(), center.y());
|
||||
map.rotateRadians(transform.radians());
|
||||
map.rotate(transform.degrees());
|
||||
map.scale(transform.size.width() / width * (transform.flipX ? -1 : 1),
|
||||
transform.size.height() / height * (transform.flipY ? -1 : 1));
|
||||
map.translate(-width / 2.0, -height / 2.0);
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
// Swift's CanvasRotation extension: Image › Rotate Canvas 90°.
|
||||
public:
|
||||
// A quarter turn of everything; layers placed, not resampled.
|
||||
void rotateCanvas(bool clockwise);
|
||||
@@ -0,0 +1,51 @@
|
||||
#include "Document/EditorSession.h"
|
||||
#include <cmath>
|
||||
|
||||
LayerTransform LayerTransform::quarterTurned(bool clockwise, QSizeF canvas) const
|
||||
{
|
||||
LayerTransform result = *this;
|
||||
// The middle moves where the turn takes it.
|
||||
const QPointF middle = clockwise ? QPointF(canvas.height() - center().y(), center().x()) : QPointF(center().y(), canvas.width() - center().x());
|
||||
result.origin = QPointF(middle.x() - size.width() / 2, middle.y() - size.height() / 2);
|
||||
result.rotation = std::fmod(rotation + (clockwise ? 90 : -90), 360.0);
|
||||
return result;
|
||||
}
|
||||
|
||||
CanvasGuide CanvasGuide::quarterTurned(bool clockwise, QSizeF canvas) const
|
||||
{
|
||||
CanvasGuide guide = *this;
|
||||
if (axis == Axis::vertical) {
|
||||
guide.axis = Axis::horizontal;
|
||||
if (!clockwise)
|
||||
guide.position = canvas.width() - position;
|
||||
} else {
|
||||
guide.axis = Axis::vertical;
|
||||
if (clockwise)
|
||||
guide.position = canvas.height() - position;
|
||||
}
|
||||
return guide;
|
||||
}
|
||||
|
||||
void EditorSession::rotateCanvas(bool clockwise)
|
||||
{
|
||||
commitTransform();
|
||||
cancelCrop();
|
||||
if (!canEditLayers())
|
||||
return;
|
||||
const QSizeF canvas = m_document->size();
|
||||
beginEdit(clockwise ? QStringLiteral("Rotate Canvas 90° Clockwise") : QStringLiteral("Rotate Canvas 90° Counterclockwise"));
|
||||
for (ImageLayer &layer : m_document->layers) {
|
||||
layer.transform = layer.transform.quarterTurned(clockwise, canvas);
|
||||
if (layer.mask && layer.mask->placement)
|
||||
layer.mask->placement = layer.mask->placement->quarterTurned(clockwise, canvas);
|
||||
}
|
||||
std::swap(m_document->width, m_document->height);
|
||||
for (CanvasGuide &guide : m_document->guides)
|
||||
guide = guide.quarterTurned(clockwise, canvas);
|
||||
if (m_document->selection) {
|
||||
const QTransform turn = clockwise ? QTransform(0, 1, -1, 0, canvas.height(), 0) : QTransform(0, -1, 1, 0, 0, canvas.width());
|
||||
m_document->selection->path = turn.map(m_document->selection->path);
|
||||
}
|
||||
viewport.fit(m_document->size());
|
||||
endEdit();
|
||||
}
|
||||
@@ -222,7 +222,7 @@ Corners DistortWarp::carried(const LayerTransform &placement, const LayerTransfo
|
||||
{
|
||||
QTransform toDocument;
|
||||
toDocument.translate(transform.center().x(), transform.center().y());
|
||||
toDocument.rotateRadians(transform.radians());
|
||||
toDocument.rotate(transform.degrees());
|
||||
toDocument.scale(transform.size.width(), transform.size.height());
|
||||
toDocument.translate(-0.5, -0.5);
|
||||
const QTransform toUnit = toDocument.inverted();
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "IO/ImageExporter.h"
|
||||
#include "Logging.h"
|
||||
#include <cmath>
|
||||
#include <tuple>
|
||||
|
||||
EditorSession::EditorSession(QObject *parent) : QObject(parent), m_busyTimer(this)
|
||||
{
|
||||
@@ -229,7 +230,10 @@ void EditorSession::restore(const DocumentHistory::Snapshot &snapshot)
|
||||
{
|
||||
cancelCrop();
|
||||
cancelGradient();
|
||||
const auto canvas = [](const std::optional<CanvasDocument> &document) { return document ? std::optional(document->id) : std::nullopt; };
|
||||
const auto canvas = [](const std::optional<CanvasDocument> &document) {
|
||||
return document ? std::optional(std::tuple(document->id, document->width, document->height)) : std::nullopt;
|
||||
};
|
||||
// Another canvas, or another size (a rotation): fit anew.
|
||||
const bool changedCanvas = canvas(m_document) != canvas(snapshot.document);
|
||||
// The mask stays the target while layer and mask remain.
|
||||
const bool keepsMaskTarget = m_isMaskSelected && m_activeLayerID == snapshot.activeLayerID;
|
||||
|
||||
@@ -276,6 +276,7 @@ public:
|
||||
#include "Document/Distort+Session.h"
|
||||
#include "Document/LayerMerge+Session.h"
|
||||
#include "Document/LayerFlip+Session.h"
|
||||
#include "Document/CanvasRotation+Session.h"
|
||||
#include "Document/Selection+Session.h"
|
||||
#include "Document/MagicWand+Session.h"
|
||||
#include "Document/SubjectRemoval+Session.h"
|
||||
|
||||
@@ -19,6 +19,8 @@ struct CanvasGuide {
|
||||
CanvasGuide scaled(double x, double y) const;
|
||||
// Mirrored when it crosses the flip, staying on content.
|
||||
CanvasGuide mirrored(bool horizontally, double center) const;
|
||||
// Turned with the canvas: the other axis, same content.
|
||||
CanvasGuide quarterTurned(bool clockwise, QSizeF canvas) const;
|
||||
friend bool operator==(const CanvasGuide &, const CanvasGuide &) = default;
|
||||
};
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ void FolderMaskClip::apply(QPointF center, const QPainter &context, QImage &cove
|
||||
{
|
||||
QTransform placement;
|
||||
placement.translate(center.x(), center.y());
|
||||
placement.rotateRadians(transform.radians());
|
||||
placement.rotate(transform.degrees());
|
||||
placement.scale(transform.flipX ? -1 : 1, transform.flipY ? -1 : 1);
|
||||
QPainter masking(&veil);
|
||||
masking.setRenderHint(QPainter::SmoothPixmapTransform, quality(transform.sampling) != InterpolationQuality::none);
|
||||
|
||||
@@ -89,6 +89,11 @@ double LayerTransform::radians() const
|
||||
return std::fmod(rotation, 360) * std::numbers::pi / 180;
|
||||
}
|
||||
|
||||
double LayerTransform::degrees() const
|
||||
{
|
||||
return std::fmod(rotation, 360);
|
||||
}
|
||||
|
||||
bool LayerTransform::isValid() const
|
||||
{
|
||||
const std::array<double, 5> values = {origin.x(), origin.y(), size.width(), size.height(), rotation};
|
||||
|
||||
@@ -36,6 +36,8 @@ struct LayerTransform {
|
||||
friend bool operator==(const LayerTransform &lhs, const LayerTransform &rhs);
|
||||
QPointF center() const;
|
||||
double radians() const;
|
||||
// The turn in degrees: QTransform::rotate makes quarter turns exact.
|
||||
double degrees() const;
|
||||
bool isValid() const;
|
||||
QPointF point(QPointF unit) const;
|
||||
bool contains(QPointF point) const;
|
||||
@@ -48,6 +50,8 @@ struct LayerTransform {
|
||||
bool samePlacement(const LayerTransform &other) const;
|
||||
// Mirrored across a line at `axis`; defined in LayerFlip.cpp.
|
||||
LayerTransform mirrored(bool horizontally, double axis) const;
|
||||
// Turned a quarter with its canvas; nothing resampled.
|
||||
LayerTransform quarterTurned(bool clockwise, QSizeF canvas) const;
|
||||
};
|
||||
|
||||
// Several layers under one box; each follows it.
|
||||
|
||||
Reference in new issue
Block a user