sp-item-transform.cpp revision 79f4d4c2bc43a94dad953df0e83eccf976499183
#include "sp-item.h"
#include "sp-item-transform.h"
if (bbox) {
* Calculate the affine transformation required to transform one visual bounding box into another, accounting for a uniform strokewidth.
* PS: This function will only return accurate results for the visual bounding box of a selection of one or more objects, all having
* the same strokewidth. If the stroke width varies from object to object in this selection, then the function
* When scaling or stretching an object using the selector, e.g. by dragging the handles or by entering a value, we will
* need to calculate the affine transformation for the old dimensions to the new dimensions. When using a geometric bounding
* box this is very straightforward, but when using a visual bounding box this become more tricky as we need to account for
* the strokewidth, which is either constant or scales width the area of the object. This function takes care of the calculation
* @param transform_stroke If true then the stroke will be scaled proportional to the square root of the area of the geometric bounding box
* @param preserve If true then the transform element will be preserved in XML, and evaluated after stroke is applied
* PS: we have to pass each coordinate individually, to find out if we are mirroring the object; Using a Geom::Rect() instead is
* not possible here because it will only allow for a positive width and height, and therefore cannot mirror
Geom::Affine get_scale_transform_for_uniform_stroke(Geom::Rect const &bbox_visual, gdouble stroke_x, gdouble stroke_y, bool transform_stroke, bool preserve, gdouble x0, gdouble y0, gdouble x1, gdouble y1)
Geom::Affine unbudge = Geom::Translate (0, 0); // moves the object(s) to compensate for the drift caused by stroke width change
// 1) We start with a visual bounding box (w0, h0) which we want to transfer into another visual bounding box (w1, h1)
// 3) Given this visual bounding box we can calculate the geometric bounding box by subtracting half the stroke from each side;
// -> The width and height of the geometric bounding box will therefore be (w0 - 2*0.5*r0) and (h0 - 2*0.5*r0)
// 4) If preserve transforms is true, then stroke_x != stroke_y, since these are the apparent stroke widths, after transforming
gdouble r0 = sqrt(stroke_x*stroke_y); // r0 is redundant, used only for those cases where stroke_x = stroke_y
// We will now try to calculate the affine transformation required to transform the first visual bounding box into
// Make sure that the lower-left corner of the visual bounding box stays where it is, even though the stroke width has changed
unbudge *= Geom::Translate (-flip_x * 0.5 * (r0 * scale_x - r1), -flip_y * 0.5 * (r0 * scale_y - r1));
unbudge *= Geom::Translate (-flip_x * 0.5 * r0 * (scale_x - 1), -flip_y * 0.5 * r0 * (scale_y - 1));
g_message("stroke scaling error : %d, %f, %f, %f, %f, %f, %f", preserve, stroke_x, stroke_y, w0, h0, w1, h1);
unbudge *= Geom::Translate (-flip_x * 0.5 * stroke_x * scale_x * (1.0 - sqrt(1.0/scale_x/scale_y)), -flip_y * 0.5 * stroke_y * scale_y * (1.0 - sqrt(1.0/scale_x/scale_y)));
* Calculate the affine transformation required to transform one visual bounding box into another, accounting for a VARIABLE strokewidth.
* Note: Please try to understand get_scale_transform_for_uniform_stroke() first, and read all it's comments carefully. This function
* (get_scale_transform_for_variable_stroke) is a bit different because it will allow for a strokewidth that's different for each
* side of the visual bounding box. Such a situation will arise when transforming the visual bounding box of a selection of objects,
* each having a different stroke width. In fact this function is a generalized version of get_scale_transform_for_uniform_stroke(), but
* will not (yet) replace it because it has not been tested as carefully, and because the old function is can serve as an introduction to
* When scaling or stretching an object using the selector, e.g. by dragging the handles or by entering a value, we will
* need to calculate the affine transformation for the old dimensions to the new dimensions. When using a geometric bounding
* box this is very straightforward, but when using a visual bounding box this become more tricky as we need to account for
* the strokewidth, which is either constant or scales width the area of the object. This function takes care of the calculation
* @param bbox_geometric Current geometric bounding box (allows for calculating the strokewidth of each edge)
* @param transform_stroke If true then the stroke will be scaled proportional to the square root of the area of the geometric bounding box
* PS: we have to pass each coordinate individually, to find out if we are mirroring the object; Using a Geom::Rect() instead is
* not possible here because it will only allow for a positive width and height, and therefore cannot mirror
Geom::Affine get_scale_transform_for_variable_stroke(Geom::Rect const &bbox_visual, Geom::Rect const &bbox_geom, bool transform_stroke, gdouble x0, gdouble y0, gdouble x1, gdouble y1)
Geom::Affine unbudge = Geom::Translate (0, 0); // moves the object(s) to compensate for the drift caused by stroke width change
// 1) We start with a visual bounding box (w0, h0) which we want to transfer into another visual bounding box (w1, h1)
// 2) We will also know the geometric bounding box, which can be used to calculate the strokewidth. The strokewidth will however
// We will now try to calculate the affine transformation required to transform the first visual bounding box into
gdouble r0w = w0 - bbox_geom.width(); // r0w is the average strokewidth of the left and right edges, i.e. 0.5*(r0l + r0r)
gdouble r0h = h0 - bbox_geom.height(); // r0h is the average strokewidth of the top and bottom edges, i.e. 0.5*(r0t + r0b)
ratio_x = (w1 - r0w) / (w0 - r0w); // Only valid when the stroke is kept constant, in which case r1 = r0
* Now we have to solve this set of two equations and find r1w and r1h; this too complicated to do by hand,
* so I used wxMaxima for that (http://wxmaxima.sourceforge.net/). These lines can be copied into Maxima
* PS1: The last two lines are only needed for readability of the output, and can be omitted if desired
* PS2: A0 is known beforehand and assumed to be constant, instead of using A0 = (w0-r0w)*(h0-r0h). This reduces the
* PS3: You'll get 8 solutions, 4 for each of the strokewidths r1w and r1h. Some experiments quickly showed which of the solutions
if (operant >= 0) {
r1w = fabs(-((h1*r0w*A0+r0h2*r0w*w1)*sqrt(operant)+(-3*h1*r0h*r0w*w1-h12*r0w2)*A0-r0h3*r0w*w12+h1*r0h2*r0w2*w1)/((r0h*A0-r0h2*r0w)*sqrt(operant)-2*h1*A02+(3*h1*r0h*r0w-r0h2*w1)*A0+r0h3*r0w*w1-h1*r0h2*r0w2));
} else { // Can't find the roots of the quadratic equation. Likely the input parameters are invalid?
// Check whether the stroke is negative; i.e. the geometric bounding box is larger than the visual bounding box, which
// How should we handle the stroke width scaling of clipped object? I don't know if we can/should handle this,
// The calculation of the new strokewidth will only use the average stroke for each of the dimensions; To find the new stroke for each
// of the edges individually though, we will use the boundary condition that the ratio of the left/right strokewidth will not change due to the
gdouble stroke_ratio_w = fabs(r0w) < 1e-6 ? 1 : (bbox_geom[Geom::X].min() - bbox_visual[Geom::X].min())/r0w;
gdouble stroke_ratio_h = fabs(r0h) < 1e-6 ? 1 : (bbox_geom[Geom::Y].min() - bbox_visual[Geom::Y].min())/r0h;
// If the stroke is not kept constant however, the scaling of the geometric bbox is more difficult to find
if (transform_stroke && r0w != 0 && r0w != Geom::infinity() && r0h != 0 && r0h != Geom::infinity()) { // Check if there's stroke, and we need to scale it
// Make sure that the lower-left corner of the visual bounding box stays where it is, even though the stroke width has changed
unbudge *= Geom::Translate (-flip_x * stroke_ratio_w * (r0w * scale_x - r1w), -flip_y * stroke_ratio_h * (r0h * scale_y - r1h));
if (r0w == 0 || r0w == Geom::infinity() || r0h == 0 || r0h == Geom::infinity()) { // can't calculate, because apparently strokewidth is zero or infinite
scale *= Geom::Scale(flip_x * ratio_x, flip_y * ratio_y); // Scaling of the geometric bounding box for constant stroke width
unbudge *= Geom::Translate (flip_x * stroke_ratio_w * r0w * (1 - ratio_x), flip_y * stroke_ratio_h * r0h * (1 - ratio_y));
Geom::Rect get_visual_bbox(Geom::OptRect const &initial_geom_bbox, Geom::Affine const &abs_affine, gdouble const initial_strokewidth, bool const transform_stroke)
if (transform_stroke) {
// scale stroke by: sqrt (((w1-r0)/(w0-r0))*((h1-r0)/(h0-r0))) (for visual bboxes, see get_scale_transform_for_stroke)
gdouble const new_strokewidth = initial_strokewidth * sqrt (new_geom_bbox.area() / initial_geom_bbox->area());
return new_visual_bbox;