lunasvg icon indicating copy to clipboard operation
lunasvg copied to clipboard

accessing the internal objects

Open rossanoparis opened this issue 2 years ago • 16 comments

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

rossanoparis avatar Aug 23 '22 06:08 rossanoparis

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.

jry2 avatar Aug 24 '22 04:08 jry2

@jry2 Are you still searching?

sammycage avatar Aug 24 '22 04:08 sammycage

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

jry2 avatar Aug 24 '22 10:08 jry2

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

sammycage avatar Aug 24 '22 10:08 sammycage

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

kariem2k avatar Nov 08 '22 12:11 kariem2k

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

sammycage avatar Jan 07 '24 15:01 sammycage

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.

sammycage avatar Jan 07 '24 15:01 sammycage

@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

rossanoparis avatar Jan 08 '24 18:01 rossanoparis

@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

sammycage avatar Jan 08 '24 18:01 sammycage

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

rossanoparis avatar Jan 08 '24 19:01 rossanoparis

@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!

sammycage avatar Jan 08 '24 20:01 sammycage

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.

rossanoparis avatar Jan 09 '24 07:01 rossanoparis

@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

sammycage avatar Jan 09 '24 16:01 sammycage

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

rossanoparis avatar Jan 11 '24 07:01 rossanoparis

@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;
}

sammycage avatar Apr 11 '24 21:04 sammycage

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

...

rossanoparis avatar Apr 12 '24 07:04 rossanoparis