sammycage / lunasvg

SVG rendering and manipulation library in C++
MIT License
866 stars 124 forks source link

accessing the internal objects #98

Open rossanoparis opened 2 years ago

rossanoparis commented 2 years ago

Hi @sammycage

This point probably requires a deep discussion, below I try to explain my needs. It would be useful to identify an object inside SVG code in order to change some properties after the SVG file has been loaded in memory.

Perhaps the attribute ID ( https://www.w3.org/TR/SVG2/struct.html#IDAttribute ) is the right one to identify objects.

Mainly the purpose is for being able to change colours, lines width, visibility, etc … It would be very useful to get the position and size occupied by every addressable SVG objects

LunaSVG library should keep a list of SVG objects and expose it in some way The user should inform the library about the object ID and the attribute to change with the new value.

Regads Rossano

jry2 commented 2 years ago

We are looking for SVG rendering library for our icons and we also need modify some object properties such as color, opacity, or stroke width. It is possible with NanoSVG.

sammycage commented 2 years ago

@jry2 Are you still searching?

jry2 commented 2 years ago

Yes. We also need to hide group with specified id.

sammycage commented 2 years ago

@jry2 I'll let you know when it's ready.

kariem2k commented 1 year ago

Maybe classes as well? It will help mass manipulation based on the class

sammycage commented 9 months ago

@rossanoparis @kariem2k @jry2 Added with this commit https://github.com/sammycage/lunasvg/commit/760e7e86519ced749e359d472a3a04620aa5cf00

sammycage commented 9 months ago

Example:

    auto document = Document::loadFromData("<svg viewBox='0 0 200 200'><circle id='a' cx='50' cy='50' r='40' fill='green'/></svg>");
    auto element = document->getElementById("a");
    element.setAttribute("cx", "100");
    element.setAttribute("cy", "100");
    element.setAttribute("r", "100");
    element.setAttribute("fill", "red");
    document->updateLayout();

@rossanoparis Reminder: Don't forget to call Document::updateLayout after modifying the document.

rossanoparis commented 9 months ago

@sammycage it sounds great! Thank you for starting this kind of implementation

I couldn't wait to test it ... what about gradients

gradients.zip

Is there a chance to operate with gradients handled by using urls instead of constant colours image

sammycage commented 9 months ago

@rossanoparis Hello! You have the flexibility to modify any attribute using the versatile setAttribute method. Take a look at the code snippet below for a practical example. This snippet demonstrates how to transition the fill of a circle seamlessly, progressing from a solid color to a gradient and finally to a pattern:


    std::string content = R"SVG(
        <svg width="200" height="200">
            <defs>
            <!-- Gradient -->
            <linearGradient id="myGradient" x1="0%" y1="0%" x2="100%" y2="100%">
                <stop offset="0%" style="stop-color: #ff0000;"/>
                <stop offset="100%" style="stop-color: #0000ff;"/>
            </linearGradient>

            <!-- Pattern -->
            <pattern id="myPattern" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
                <circle cx="10" cy="10" r="8" fill="#00ff00"/>
            </pattern>
        </defs>
        <circle id="myCircle" cx="100" cy="100" r="50" fill="red"/>
        </svg>)SVG";

    auto document = Document::loadFromData(content);
    writeToPng(*document, "circle-with-solid-color.png");

    auto circle = document->getElementById("myCircle");
    circle.setAttribute("fill", "url(#myGradient)");
    document->updateLayout();
    writeToPng(*document, "circle-with-gradient.png");

    circle.setAttribute("fill", "url(#myPattern)");
    document->updateLayout();
    writeToPng(*document, "circle-with-pattern.png");

circle-with-solid-color circle-with-gradient circle-with-pattern

rossanoparis commented 9 months ago

Yes, It's clear, but my question was different. What I meant was related to gradient itself, how can I modify gradient or pattern attributes

sammycage commented 9 months ago

@rossanoparis Yes, I understand now. To modify the attributes of a gradient or a pattern, you can use a similar approach with the setAttribute method. For example:

// To modify gradient attributes
auto gradient = document->getElementById("yourGradientId");
gradient.setAttribute("x1", "newX1Value");
gradient.setAttribute("y1", "newY1Value");
// Add more attributes as needed

// To modify pattern attributes
auto pattern = document->getElementById("yourPatternId");
pattern.setAttribute("x", "newXValue");
pattern.setAttribute("y", "newYValue");
// Add more attributes as needed

Feel free to adapt this code based on the specific attributes you want to modify. Let me know if you have any further questions or if there's anything else I can assist you with. But it seems like you've got the method now!

rossanoparis commented 9 months ago

Thank you @sammycage, it is clear

Regarding attributes I'd like to introduce two new requests/discussions.

1) Groups or single object "transformations" With "transformation" I mainly mean: 1) rotation 2) scale 3) move 4) vertical or horizontal flip

image

It would be very useful, using the same approach as attributes, to apply transformations to all objects contained in a group or even to a single object.

I imagine something like:

auto group1 = document->getElementById("Group-01");
group1.Rotate(angle, center-x, center-y);
group1.Move(new-x, new-y);
group1.Scale(scale, center-x, center-y);
group1.FlipVertical(reference-x, reference-y);
group1.FlipHorizontal(reference-x, reference-y);

auto triangle = document->getElementById("OBJ2");
triangle.Rotate(angle, center-x, center-y);
triangle.FlipVertical(reference-x, reference-y);
triangle.Move(new-x, new-y);

2) Groups or single object "references" With "reference" I mainly mean methodd based on which a user can get information from objects regarding the area occupied by a group or single objects; even for texts which is not drawn by the library, but that could be drawn in other way by the user.

image

It would be very useful, using the same approach as attributes, to get such information.

I imagine something like:

auto group1 = document->getElementById("Group-01");
group1.getP1();
group1.getP2();
group1.getP3();
group1.getP4();

auto triangle = document->getElementById("OBJ2");
triangle.getP1();
triangle.getP2();

auto text = document->getElementById("MyText1");
text.getP1();
text.getP2();
text.getP3();
text.getP4();

Transformations and references features would be very powerful to "animate" a SVG document ... what do you think about them.

sammycage commented 9 months ago

@rossanoparis I appreciate your detailed suggestions and the illustrative diagrams you provided. I'm confident that incorporating the DomElement::getBBox and DomElement::getLocalTransform methods will effectively address the concerns you raised regarding "transformations" and "references. https://github.com/sammycage/lunasvg/commit/6947ee1a3c8a189f9bfb39107e101d0babf8e008

This code snippet illustrates the usage of DomElement::getBBox to reposition an SVG group to a new reference position.


    std::string content = R"SVG(
        <svg width="200" height="200">
            <g id="myGroup">
              <rect width="50" height="50" fill="blue"/>
            </g>
        </svg>)SVG";

    auto document = Document::loadFromData(content);
    writeToPng(*document, "original.png");

    // Get the SVG element and its bounding box
    auto element = document->getElementById("myGroup");
    auto bbox = element.getBBox();

    // Calculate the new position (e.g., move 150 units to the right and 30 units down)
    auto newX = 150;
    auto newY = 30;

    // Calculate the translation values
    auto deltaX = newX - bbox.x;
    auto deltaY = newY - bbox.y;

    // Apply the transform attribute to move the SVG group
    element.setAttribute("transform", "translate(" + std::to_string(deltaX) + " " + std::to_string(deltaY) + ")");
    document->updateLayout();
    writeToPng(*document, "translated.png");

original translated

rossanoparis commented 9 months ago

Kind @sammycage thank you for aving accepted my proposal and ... wow! how fast you are :)

I don't have time in this week to test the latest commits. Just for my information, did you already implemented the two points above described ?

Again thank you, regards

sammycage commented 5 months ago

@rossanoparis You can adjust SVG transformations with the following code. Feel free to ask any further questions!


void writeToPng(Document& document, const char* filename)
{
    auto bitmap = document.renderToBitmap();

    stbi_write_png(filename, bitmap.width(), bitmap.height(), 4, bitmap.data(), 0);

    std::cout << "Generated PNG file : " << filename << std::endl;
}

void setTransformAttribute(DomElement& element, const Matrix& matrix)
{
    std::string transform("matrix(");
    transform += std::to_string(matrix.a);
    transform += ' ';
    transform += std::to_string(matrix.b);
    transform += ' ';
    transform += std::to_string(matrix.c);
    transform += ' ';
    transform += std::to_string(matrix.d);
    transform += ' ';
    transform += std::to_string(matrix.e);
    transform += ' ';
    transform += std::to_string(matrix.f);
    transform += ')';

    element.setAttribute("transform", transform);
}

int main(int argc, char** argv)
{
    std::string content = R"SVG(
        <svg width="200" height="200">
            <g id="myGroup">
              <rect width="50" height="50" fill="blue"/>
            </g>
        </svg>)SVG";

    auto document = Document::loadFromData(content);
    writeToPng(*document, "original.png");

    // Retrieve the SVG element and its transformation matrix.
    auto element = document->getElementById("myGroup");
    auto transform = element.getLocalTransform();

    // Modify the transformation matrix.
    transform.translate(50, 50);
    transform.rotate(45);

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

    document->updateLayout();
    writeToPng(*document, "transformed.png");

    return 0;
}
rossanoparis commented 5 months ago

Thank you @sammycage

There are some points I don't understand their behaviour, perhaps because of my lack of knowledge ...

I'm debugging this feature, and I'm going to open different issues for each test I'm doing related to: scale, rotate, translate In this way, treating one by one it results in tidy resources for all, I think

...