Предотвратить субпиксельный рендеринг с SVG

Я сейчас работаю с SVG и зашел в тупик.

У SVG есть линии, которые должны масштабироваться вместе с масштабированием (чтобы они оставались сбалансированными: 100% ширина 10px → 10% ширина 1px например)

Я масштабирую все значения stroke-widths с помощью этого кода:

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

Где width - новая ширина после увеличения, а imgData.w - исходная немасштабированная ширина.

Проблема с этим, если я увеличу масштаб до далеких. Штрих с становится небольшим и приводит к субпиксельной визуализации. И предположительно черные линии становятся серыми.

Моя идея заключалась в том, чтобы обрезать значение в определенной точке, чтобы предотвратить его. Но, насколько я знаю, я также должен учитывать соотношение пикселей устройства, из-за разных экранов (рабочий стол, мобильный, 4K)

Было бы неплохо, если бы кто-то мог помочь мне с идеей, чтобы решить мою проблему

Ответы

Ответ 1

Мы наконец нашли решение для этого на случай, если у кого-то возникнут те же проблемы:

1) Из-за панорамирования this._$svgElement и вычисления vpx в совершенно другом разделе кода элемент находится "между" пикселями. (100,88945px для х например). Это вызывает размытие линий. Я исправил эту часть с помощью простого 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) Чтобы устранить проблему между областью просмотра SVG и силой линии, мне пришлось реализовать метод для вычисления ширины штрихов, равной 1 "реальному" пикселю, относительно измерения svgs.

обновленный код выглядит следующим образом: (Это исходный код после загрузки SVG с сервера. Внутри масштабирования старый код сверху остается прежним)

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

и вычисления:

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

Ответ 2

var newStrokeWidth = this._oldStrokeWidth[i] * (1 / (width / imgData.w));
newStrokeWidth = (newStrokeWidth < 1) ? 1 : newStrokeWidth;

newStrokeWidth всегда будет 1 или больше