Prevent subpixel rendering with svg
Asked Answered
D

2

12

I'm working with SVGs currently and came to a dead end.

The SVG has lines, which should scale together with zooming (so that they stay in balance: 100% width 10px --> 10% width 1px for example)

i scale all stroke-widths with this code:

var svgPath = this._svgContainer.find('svg [class*="style"]');
for (var i = 0; i < svgPath.length; ++i) {
  var newStrokeWidth = this._oldStrokeWidth[i] * (1 / (width / imgData.w));

  $(svgPath[i]).css(
    'stroke-width', newStrokeWidth
  );
}

Where width is the new width after zoom and imgData.w is the original unscaled width.

The problem with this is, if i zoom in to far. The stroke with becomes to small and leads to sub-pixel rendering. And supposedly black lines get grey-ish.

My Idea was to clip the value at a certain point to prevent it. But as far as I know, I have to consider the Device Pixel ratio too, because of different screens (desktop, mobile, 4K)

Would be nice If someone can help me with an idea to fix my problem

Duque answered 27/5, 2019 at 13:53 Comment(4)
could you provide a fiddle as example? by zooming do you mean, browser zoom, or do you control zomming via javascript?Vasques
One easy solution to your problem would be using vector-effect="non-scaling-stroke" MDN quote: The value non-scaling-stroke is a keyword for a predefined vector effect that causes an object's stroke-width to be unaffected by transformations and zooming.Onslaught
@Vasques i controll the zooming via javascript. based on the zoom level and the position of the pointer the stroke-width is calculated. I can provide a fiddle but not until wednesday cause i#m currently on a business trip...Duque
maybe try shape-rendering="crispEdges" ?Footfall
D
1

We finally found a solution for this, in case anyone has the same problems:

1) Because of the panning of this._$svgElement and the calculation of vpx in a completely different section of the code the element is 'between' pixels. ( 100.88945px for x for example). This causes lines to blur. I fixed this part with a simple Math.round().

this._hammerCanvas.on('panmove', (event: any) => {
        const translate3d = 'translate3d(' + Math.round(this._oldDeltaX + ((vpx === imgData.x) ? 0 : vpx) + event.deltaX) + 'px, ' + Math.round(this._oldDeltaY + ((vpy === imgData.y) ? 0 : vpy) + event.deltaY) + 'px, 0)';
        this._$svgElement.css({
          transform: translate3d
        });
}

2) To fix the problem between the SVG viewport and the line strength, I had to implement a method to calculate the strokewidth equal to 1 'real' pixel regarding the svgs dimension.

the updated code looks like this: (This is the inital code, after the SVG was loaded from the server. Inside the zooming, the old code from above is still the same)

    const pixelRatio = devicePixelRatio || 1;
    const widthRatio = this._initSVGWidth / svgContainerWidth;
    const heightRatio = this._initSVGHeight / svgContainerHeight;
    this._svgZoomFactor = Math.max(widthRatio, heightRatio);
    const strokeWidth1px = this.computeStrokeWidth1px(widthRatio, heightRatio);

    for (let i = 0; i < svgPaths.length; ++i) {
      this._initalStrokeWidth[i] = parseFloat($(svgPaths[i]).css('stroke-width'));

      const newStrokeWidth = Math.max(strokeWidth1px / pixelRatio, this._svgZoomFactor * this._initalStrokeWidth[i]);

      $(svgPaths[i])[0].setAttribute('style', 'stroke-width:' + newStrokeWidth);
      this._oldStrokeWidth[i] = newStrokeWidth;
    }

and the compute:

  protected computeStrokeWidth1px (widthRatio: number, heightRatio: number): number {
    const viewBox = this._$svgElement[0].getAttribute('viewBox').split(' ');
    const viewBoxWidthRatio = parseFloat(viewBox[2]) / this._$svgElement.width();
    const viewBoxHeightRatio = parseFloat(viewBox[3]) / this._$svgElement.height();
    return widthRatio > heightRatio ? viewBoxWidthRatio : viewBoxHeightRatio;
  }
Duque answered 6/8, 2019 at 13:59 Comment(0)
F
0
var newStrokeWidth = this._oldStrokeWidth[i] * (1 / (width / imgData.w));
newStrokeWidth = (newStrokeWidth < 1) ? 1 : newStrokeWidth;

newStrokeWidth will always be 1 or greater

Finedrawn answered 31/7, 2019 at 13:1 Comment(1)
This is the wrong way to solve it: the stroke width value would be 1 but due to the viewport beeing anything between (0 0 0 0) and (inf inf inf inf) a stroke-width of one does not equal 1 px on the monitor.Duque

© 2022 - 2024 — McMap. All rights reserved.