Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Local transformation and internal SVG element access - Element translation #166

Open
rossanoparis opened this issue Apr 15, 2024 · 2 comments

Comments

@rossanoparis
Copy link

Data

This issue refers to: issue 98
Library version: v2.3.9 (master)
Testing SVG file: groups.svg.zip (InkScape)

Document Details InkScape SVG Properties
image unit: px

document
W: 163 H: 288
Top left (0, 0)
Bottom rigth (163, 288)
Center (81.5, 144)

g1 (group 1)
W: 163 H: 152
B1 (0, 0)
Bottom rigth (163, 152)
C1 (81.5, 76)

g2 (group 2)
W: 108 H: 111
B2 (50, 178)
Bottom rigth (158, 288)
C2 (104, 232.5)
image

Scope

I want to translate in X the element g1 applying a translation of -81.5px, which is half of document width.

Testing code

// Load document
auto document = Document::loadFromFile(filesvg);
Box dbox(0, 0, document->width(), document->height());

// Retrieve the SVG element - g1
auto elementg1 = document->getElementById("g1");
auto boxg1 = elementg1.getBBox().transformed(elementg1.getAbsoluteTransform());

// Retrieve element transformation matrix - g1
auto transformg1 = elementg1.getLocalTransform();

// Modify the transformation matrix
transformg1.translate(-81.5, 0);

// Apply the modified transformation to the SVG element.
setTransformAttribute(elementg1, transformg1);

// Document update
document->updateLayout();

// Render bitmap
auto bitmap = document->renderToBitmap();

Results

The element g1 disappears from the document; it happens using both methods
auto transformg1 = elementg1.getLocalTransform();
or
auto transformg1 = elementg1.getAbsoluteTransform();

Expected Obtained
image image
@sammycage
Copy link
Owner

sammycage commented May 21, 2024

@rossanoparis When working with SVG elements and applying transformations such as scaling, rotating, skewing, and translating, it's often useful to understand the mathematical principles behind these transformations. Specifically, when you want to apply a transformation around a specific point (transform origin), you need to use a sequence of transformations.

Mathematical Explanation

To transform an SVG element around a specific point, you typically use the following transformation sequence:

  1. Translate to the Origin: Move the transform origin to the coordinate origin.
  2. Apply the Desired Transformation: Perform the scaling, rotation, skewing, or any combination of these transformations.
  3. Translate Back: Move the transform origin back to its original position.

In terms of matrices, this is represented as:

T(transformOriginX, transformOriginY) * additionalTransform * T(-transformOriginX, -transformOriginY) * originalTransform

Practical Example with SVG

hello.svg

<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg">
   <g transform="translate(100, 100)">
      <rect id="rect1" fill="red" x="50" y="50" width="100" height="100"/>
      <rect id="rect2" opacity="0.8" fill="green" transform="translate(50, 50)" x="0" y="0" width="100" height="100"/>
   </g>
</svg>

Original

original

Rotate(45)

int main(int argc, char** argv)
{
    std::string filename("/home/sammycage/Projects/hello.svg");
    auto document = Document::loadFromFile(filename);

    // Retrieve the SVG element to animate by its ID
    auto element = document->getElementById("rect2");
    auto originalTransform = element.getLocalTransform();
    auto boundingBox = element.getBBox().transformed(originalTransform);

    auto transformOriginX = boundingBox.x;
    auto transformOriginY = boundingBox.y;

    // Optionally, adjust the transform origin to the center of the bounding box
    // transformOriginX += boundingBox.w / 2.f;
    // transformOriginY += boundingBox.h / 2.f;

    auto additionalTransform = Matrix::translated(transformOriginX, transformOriginY);
    additionalTransform.rotate(45);
    additionalTransform.translate(-transformOriginX, -transformOriginY);

    // Combine the new transformation with the original transformation
    additionalTransform.premultiply(originalTransform);

    // Convert the transformation matrix to a string representation for the SVG "transform" attribute
    std::string additionalTransformString("matrix(");
    additionalTransformString += std::to_string(additionalTransform.a);
    additionalTransformString += ' ';
    additionalTransformString += std::to_string(additionalTransform.b);
    additionalTransformString += ' ';
    additionalTransformString += std::to_string(additionalTransform.c);
    additionalTransformString += ' ';
    additionalTransformString += std::to_string(additionalTransform.d);
    additionalTransformString += ' ';
    additionalTransformString += std::to_string(additionalTransform.e);
    additionalTransformString += ' ';
    additionalTransformString += std::to_string(additionalTransform.f);
    additionalTransformString += ')';

    // Output the transformation matrix string
    std::cout << additionalTransformString << std::endl;

    // Apply the new transformation to the SVG element
    element.setAttribute("transform", additionalTransformString);

    // Update the layout of the document to apply the transformation
    document->updateLayout();

    // Render the updated SVG document to a bitmap
    auto bitmap = document->renderToBitmap();
    if(!bitmap.valid()) return 1;
    bitmap.convertToRGBA();

    // Create a filename for the PNG file based on the transformation type.
    std::string basename("rotate"  ".png");
    stbi_write_png(basename.c_str(), bitmap.width(), bitmap.height(), 4, bitmap.data(), 0);
    std::cout << "Generated PNG file : " << basename << std::endl;

    return 0;
}

rotate

SkewX(45)

...
    auto additionalTransform = Matrix::translated(transformOriginX, transformOriginY);
    additionalTransform.shear(45, 0);
    additionalTransform.translate(-transformOriginX, -transformOriginY);

    // Combine the new transformation with the original transformation
    additionalTransform.premultiply(originalTransform);
...

shear

Translate(50, 50)

...
    auto additionalTransform = Matrix::translated(transformOriginX, transformOriginY);
    additionalTransform.translate(50, 50);
    additionalTransform.translate(-transformOriginX, -transformOriginY);

    // Combine the new transformation with the original transformation
    additionalTransform.premultiply(originalTransform);
...

translate

Translate(50, 50) Rotate(45)

...
    auto additionalTransform = Matrix::translated(transformOriginX, transformOriginY);
    additionalTransform.translate(50, 50);
    additionalTransform.rotate(45);
    additionalTransform.translate(-transformOriginX, -transformOriginY);

    // Combine the new transformation with the original transformation
    additionalTransform.premultiply(originalTransform);
...

translate-rotate

Demo

int main(int argc, char** argv)
{
    std::string filename("/home/sammycage/Projects/hello.svg");
    auto document = Document::loadFromFile(filename);

    // Retrieve the SVG element to animate by its ID
    auto element = document->getElementById("rect2");
    auto originalTransform = element.getLocalTransform();
    auto boundingBox = element.getBBox().transformed(originalTransform);

    auto transformOriginX = boundingBox.x;
    auto transformOriginY = boundingBox.y;

    // Optionally, adjust the transform origin to the center of the bounding box
    // transformOriginX += boundingBox.w / 2.f;
    // transformOriginY += boundingBox.h / 2.f;

    // Loop through angles from 0 to 360 degrees, incrementing by 30 degrees
    for(auto angle = 0; angle <= 360; angle += 30) {
        auto additionalTransform = Matrix::translated(transformOriginX, transformOriginY);
        additionalTransform.rotate(angle);
        additionalTransform.translate(-transformOriginX, -transformOriginY);

        // Combine the new transformation with the original transformation
        additionalTransform.premultiply(originalTransform);

        // Convert the transformation matrix to a string representation for the SVG "transform" attribute
        std::string additionalTransformString("matrix(");
        additionalTransformString += std::to_string(additionalTransform.a);
        additionalTransformString += ' ';
        additionalTransformString += std::to_string(additionalTransform.b);
        additionalTransformString += ' ';
        additionalTransformString += std::to_string(additionalTransform.c);
        additionalTransformString += ' ';
        additionalTransformString += std::to_string(additionalTransform.d);
        additionalTransformString += ' ';
        additionalTransformString += std::to_string(additionalTransform.e);
        additionalTransformString += ' ';
        additionalTransformString += std::to_string(additionalTransform.f);
        additionalTransformString += ')';

        // Output the transformation matrix string
        std::cout << additionalTransformString << std::endl;

        // Apply the new transformation to the SVG element
        element.setAttribute("transform", additionalTransformString);

        // Update the layout of the document to apply the transformation
        document->updateLayout();

        // Render the updated SVG document to a bitmap
        auto bitmap = document->renderToBitmap();
        if(!bitmap.valid()) return 1;
        bitmap.convertToRGBA();

        // Create a filename for the PNG file based on the current angle
        std::string basename("hello-" + std::to_string(angle) + ".png");
        stbi_write_png(basename.c_str(), bitmap.width(), bitmap.height(), 4, bitmap.data(), 0);
        std::cout << "Generated PNG file : " << basename << std::endl;
    }

    return 0;
}

Output

animation

Uncomment the following lines to adjust the transform origin to the center of the bounding box:
// transformOriginX += boundingBox.w / 2.f;
// transformOriginY += boundingBox.h / 2.f;

Output

animation

Demo 2 (with your SVG file groups.svg)

int main(int argc, char** argv)
{
    std::string filename("/home/sammycage/Projects/groups.svg");
    auto document = Document::loadFromFile(filename);

    // Retrieve the SVG element to animate by its ID
    auto element = document->getElementById("g1");
    auto originalTransform = element.getLocalTransform();
    auto boundingBox = element.getBBox().transformed(originalTransform);

    auto transformOriginX = boundingBox.x;
    auto transformOriginY = boundingBox.y;

    // Adjust the transform origin to the center of the bounding box for better visibility.
    transformOriginX += boundingBox.w / 2.f;
    transformOriginY += boundingBox.h / 2.f;

    // Rest of the code Here

    return 0;
}

Output

animation

@rossanoparis
Copy link
Author

Thank you @sammycage for these precious explanation.
As soon as I can, I'll apply them to my debugging code ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants