sammycage / lunasvg

lunasvg is a standalone SVG rendering library in C++
MIT License
818 stars 115 forks source link

Local transformation and internal SVG element access - Element Box attributes #164

Closed rossanoparis closed 2 months ago

rossanoparis commented 3 months ago

Data

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

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: 108
B1 (0, 0)
Bottom rigth (163, 108)
C1 (81.5, 54)

g2 (group 2)
W: 108 H: 112
B2 (49, 176)
Bottom rigth (157, 288)
C2 (103, 232)
image

Scope

I want to get elements size and position from the SVG content

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();

// Retrieve the SVG element - g2
auto elementg2 = document->getElementById("g2");
auto boxg2 = elementg2.getBBox();

Results

The following details are wrong compared to the SVG document.

Element g1 (group 1) details boxg1.x = 72 boxg1.y = 39 boxg1.w = 54 boxg1.h = 36 The expected values should be same as B1

Element g2 (group 2) details boxg2.x = 84 boxg2.y = 108 boxg2.w = 37 boxg2.h = 39 The expected values should be same as B2

Remarks

Perhaps the library doesn't use the SVG "scale", in this example the scale is 3.0 (see document SVG properties above). Should the library calculate the scale using the following SVG attributes ? Even though this doesn't explain values for X and Y

<svg
   width="162.61"
   height="287.64658"
   viewBox="0 0 54.203334 95.882195"

Scale: 162.61 / 54.203334 = 3

sammycage commented 3 months ago

With the latest commit https://github.com/sammycage/lunasvg/commit/17b595a6f1fa10ff5f56a7ab9a7134b511735968, you can now map the local coordinates to absolute coordinates.


// 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 the SVG element - g2
auto elementg2 = document->getElementById("g2");
auto boxg2 = elementg2.getBBox().transformed(elementg2.getAbsoluteTransform());
rossanoparis commented 3 months ago

I did two other tests using: Testing SVG file: groups.svg.zip It seems that something wrong is still present Applying the code below using the two groups, g1 and g2, produces different results.

Element g1 (group 1)

InkScape details image

Values from code auto boxg1 = elementg1.getBBox().transformed(elementg1.getAbsoluteTransform()); X: 0px Y: 0px W: 162,61px H: 108,36px They could be considered OK

Element g2 (group 2)

InkScape details image

Values from code auto boxg2 = elementg2.getBBox().transformed(elementg2.getAbsoluteTransform()); X: 48.28px Y: 171.44px W: 112.25px H: 117,79px They are not correct

Document with single element

Testing SVG file: single.svg.zip

InkScape details image

Values from code: X: 103.47px Y: 490.03px W: 85.93px H: 85.93px They are not correct

sammycage commented 3 months ago

This line of code, element.getBBox().transformed(element.getAbsoluteTransform()), returns a Box object that provides information about the size of an element and its position relative to the viewport.

Example:

In this simple example, the code retrieves the Box object representing the bounding client rectangle of respective elements and renders their outlines.


int main(int argc, char** argv)
{
    // Load document
    auto document = Document::loadFromFile("/home/sammycage/Projects/groups.svg");
    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 the SVG element - g2
    auto elementg2 = document->getElementById("g2");
    auto boxg2 = elementg2.getBBox().transformed(elementg2.getAbsoluteTransform());

    // Retrieve the SVG element - path1
    auto elementpath1 = document->getElementById("path1");
    auto boxpath1 = elementpath1.getBBox().transformed(elementpath1.getAbsoluteTransform());

    // Retrieve the SVG element - path2
    auto elementpath2 = document->getElementById("path2");
    auto boxpath2 = elementpath2.getBBox().transformed(elementpath2.getAbsoluteTransform());

    // Retrieve the SVG element - path3
    auto elementpath3 = document->getElementById("path3");
    auto boxpath3 = elementpath3.getBBox().transformed(elementpath3.getAbsoluteTransform());

    // Retrieve the SVG element - path4
    auto elementpath4 = document->getElementById("path4");
    auto boxpath4 = elementpath4.getBBox().transformed(elementpath4.getAbsoluteTransform());

    auto bitmap = document->renderToBitmap(0, 0, 0xFFFFFFFF);
    plutovg_surface_t* surface = plutovg_surface_create_for_data(bitmap.data(), bitmap.width(), bitmap.height(), bitmap.stride());
    plutovg_t* pluto = plutovg_create(surface);

    const double dash[] = {3, 3};

    plutovg_set_rgb(pluto, 0, 0, 1);
    plutovg_set_dash(pluto, 0, dash, 2);

    // Draw outline around element "g1"
    plutovg_rect(pluto, boxg1.x, boxg1.y, boxg1.w, boxg1.h);
    plutovg_stroke(pluto);

    // Draw outline around element "g2"
    plutovg_rect(pluto, boxg2.x, boxg2.y, boxg2.w, boxg2.h);
    plutovg_stroke(pluto);

    // Draw outline around element "path1"
    plutovg_rect(pluto, boxpath1.x, boxpath1.y, boxpath1.w, boxpath1.h);
    plutovg_stroke(pluto);

    // Draw outline around element "path2"
    plutovg_rect(pluto, boxpath2.x, boxpath2.y, boxpath2.w, boxpath2.h);
    plutovg_stroke(pluto);

    // Draw outline around element "path3"
    plutovg_rect(pluto, boxpath3.x, boxpath3.y, boxpath3.w, boxpath3.h);
    plutovg_stroke(pluto);

    // Draw outline around element "path4"
    plutovg_rect(pluto, boxpath4.x, boxpath4.y, boxpath4.w, boxpath4.h);
    plutovg_stroke(pluto);

    plutovg_surface_write_to_png(surface, "outlined.png");

    plutovg_destroy(pluto);
    plutovg_surface_destroy(surface);

    return 0;
}

outlined

sammycage commented 3 months ago

Hi @rossanoparis, just a quick note: In JavaScript, element.getBoundingClientRect() method provides similar functionality to the element.getBBox().transformed(element.getAbsoluteTransform()).

According to ChatGPT:

When using getBoundingClientRect(), it's essential to consider potential differences across various browsers. These discrepancies can arise due to variations in rendering, layout handling, and coordinate systems interpretation. Factors such as CSS styles interpretation, sub-pixel rendering, and rounding algorithms can contribute to variations in the returned values. Additionally, browser zoom levels, screen resolutions, and device pixel ratios may also influence the results.

To ensure consistent behavior across different browsers and devices, it's advisable not to rely solely on the exact values returned by getBoundingClientRect().

rossanoparis commented 3 months ago

Thank you @sammycage for your detailed explanation. It is important for users to consider a certain level of approximation when referencing measurements from Inkscape.

These are two examples of using wxWidgets functions to draw red rectangles; I got the same results. Groups Single
image image

Once again, thank you!