Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent subpixel rendering with svg

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

like image 217
breezertwo Avatar asked May 27 '19 13:05

breezertwo


1 Answers

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;
  }
like image 82
breezertwo Avatar answered Sep 18 '22 05:09

breezertwo