benfry / processing4

Processing 4.x releases for Java 17
https://processing.org
Other
1.35k stars 237 forks source link

P2D, P3D shift .svg place #788

Open mrbbp opened 1 year ago

mrbbp commented 1 year ago
## Description i was playing with svg jerking on screen and to verify P2D or P3D performances i found strange behavior. The svg shape shift to right. it is supposed to center it, and it is with no renderer added. ## Expected Behavior the svg should be in the same place ## Current Behavior when a renderer is added, the svg shift to the right. ## Steps to Reproduce
PShape motif;

void setup() {
  size(600,600);
// size(600,600,P2D);
  motif = loadShape("mot.svg");
  shapeMode(CENTER);
}

void draw() {
  background(200);
  shape(motif, width/2, height/2, width, height);
}
  1. add the renderer in size (uncomment the line)

Your Environment

hx2A commented 1 year ago

Hi @mrbbp ! Can you share the "mot.svg" file with us? I believe I know what is causing this and I would like the "mot.svg" file to reproduce the problem and investigate.

mrbbp commented 1 year ago
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 134 134" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
    <path d="M38.923,131.355c9.61,0 16.682,-6.165 16.682,-16.864l0,-95.019c0,-10.698 -7.072,-16.864 -16.682,-16.864c-9.611,0 -16.683,6.166 -16.683,16.864l-0,95.019c-0,10.699 7.072,16.864 16.683,16.864Zm-2.176,-27.019l-0,-74.709c-0,-1.632 0.725,-2.72 2.176,-2.72c1.45,0 2.176,1.088 2.176,2.72l-0,74.709c-0,1.632 -0.726,2.72 -2.176,2.72c-1.451,0 -2.176,-1.088 -2.176,-2.72Z" style="fill-rule:nonzero;"/>
    <path d="M76.096,131.355c9.429,0 16.32,-5.984 16.32,-16.501l-0,-111.339l-14.144,0l-0,100.821c-0,1.632 -0.725,2.72 -2.176,2.72c-1.451,0 -2.176,-1.088 -2.176,-2.72l-0,-100.821l-14.144,0l-0,111.339c-0,10.517 6.891,16.501 16.32,16.501Z" style="fill-rule:nonzero;"/>
    <rect x="96.768" y="3.515" width="14.144" height="126.933" style="fill-rule:nonzero;"/>
</svg>

hope this help

hx2A commented 1 year ago

Thank you, @mrbbp . The problem is what I thought it was.

The issue is the default renderer and the P2D renderer measure the height and width of a shape differently.

Consider this code:

PShape motif;

void setup() {
  size(600,600);
 //size(600,600,P2D);
  motif = loadShape("mot.svg");
  shapeMode(CENTER);

  println(motif.getWidth(), motif.getHeight());
}

void draw() {
  background(200);
  shape(motif, width/2, height/2, width, height);
}

With the default renderer, the output is "100.0, 100.0". For the P2D renderer, it is "66.17313 96.079865".

The default renderer measures the size of a shape using the height and width of the shape's drawing surface. In this case, it comes from the width="100%" height="100%" in the SVG file, or if you remove that, the height and width will become "134.0 134.0" from viewBox="0 0 134 134". Since this SVG file has blank space on the right and left sides and also a bit on the top and bottom, the actual thing being drawn is smaller than the reported width and height.

The P2D render measures the size of the actual thing being drawn by looking at the range of the individual vertices in the shape. Since there is a lot of blank space on the right and left sides of the image, the reported width of 66.17313 is much less than 100.

What is the "correct" way of measuring the width or height? The differences are partly the result of internal differences between how the two renderers represent shapes.

In addition, for this shape, the thing being drawn is offset from the origin by the extra white space. That is causing problems for the shape() method, as I'll explain in a moment.

But why are they drawn so differently? Both renderers share the same shape() code, and which is dependent on the results of getWidth() and getHeight(). The difference causes that call to scale() to have different values, resulting in something different drawn to the window.

So what can you do here? If you need the default renderer and the P2D renderer to always look the same, I recommend editing SVG file in Illustrator or Inkscape to remove the blank space on the top, bottom, left and right. If the blank space was removed, both renderers would measure the shape's size and get the same results. You could also not use shapeMode(CENTER) and write your code with the default shape drawing mode.

However, I think there is an issue here with how the P2D renderer draws CENTERed shapes. The blank space is tripping up Processing's ability to "center" a shape. If more and more blank space were to be added to the left and top margins of this shape, it would be drawn in a location that is more and more off from the actual center. It might be better if the P2D renderer did not share the shape() code with the default renderer, and instead considered the minimum x and y vertices.

I did some experimenting and this code will draw the shape properly centered, regardless of how much blank space there is. It is not exactly the same as what the default renderer does (and I don't think they can be exactly the same), but at least it is not tripped up by the blank space.

PShape motif;

void setup() {
 size(600,600,P2D);
  motif = loadShape("mot.svg");
  //shapeMode(CENTER);
}

void draw() {
  background(200);

  float minX = Float.MAX_VALUE;
  float maxX = Float.MIN_VALUE;
  float minY = Float.MAX_VALUE;
  float maxY = Float.MIN_VALUE;

  for (PShape child : motif.getChildren()) {
    for (int i = 0; i < child.getVertexCount(); i++) {
      float x = child.getVertexX(i);
      float y = child.getVertexY(i);
      minX = min(x, minX);
      maxX = max(x, maxX);
      minY = min(y, minY);
      maxY = max(y, maxY);
    }
  }

  // necessary because the width="100%" height="100%" viewBox="0 0 134 134"
  // bit in the SVG file scales the shape
  minX *= motif.getWidth() / (maxX - minX);
  minY *= motif.getHeight() / (maxY - minY);

  // move to the center
  translate(width/2, height/2);
  // scale so drawn shape fills the window
  scale(width / motif.getWidth(), height / motif.getHeight());
  // offset so it is properly centered, factoring the blank space in the top and
  // left margins
  translate(-(minX + motif.getWidth() / 2), -(minY + motif.getHeight() / 2));

  shape(motif);
}

I'm not sure if it is a good idea to make this change to the Processing code though - a lot of people use this and it would break people's code if they are dependent on it working the way it does now. I think it is a better idea to consider how the two renderers measure shape sizes considering blank space and minimize the blank space if possible.