Размытый эффект с использованием canvas

#javascript #google-chrome #canvas

#javascript #google-chrome #холст

Вопрос:

Вступление

Я создаю страницу, которая содержит «строки» информации в соответствии со следующим:

  • Каждая строка содержит один холст
  • Все строки должны иметь одинаковую ширину: ширину самого широкого холста
  • Если ширина самого широкого холста меньше ширины окна просмотра, используется ширина окна просмотра.

Решение простое и простое: использование таблицы (с отдельными столбцами) и стиля min-width: 100%; . Это не проблема.

Проблема

Все, что нарисовано на холстах, имеет эффект размытия.

Это (по-видимому) происходит только в (последних версиях) Chrome.

Воспроизводимость

Чтобы воспроизвести проблему, я создал эту скрипку.

В примере я создал две строки с разным цветом фона каждая, на первом canvas отображается текст, на втором — графический контент. Для графического содержимого я использую следующее изображение:

100 дверей

Пожалуйста, обратите внимание, что изображение содержит только два цвета: чистый красный и чистый зеленый. Его размер составляет 100×100. На втором холсте изображение отображается 10 раз, одно рядом с другим, поэтому ширина холста составляет 1000×100.

Компьютер, на котором я тестирую, — это Linux с Chrome 87.0.4280.66 (2020-11-17), последней стабильной версией на момент написания этого вопроса.

Я протестировал его на других компьютерах и браузерах со следующими результатами:

  • Тот же компьютер Linux, (старый) Chrome: ОК
  • Тот же компьютер Linux, Mozilla: ОК
  • Другой компьютер Linux, (недавний) Chrome: ПРОБЛЕМА
  • Другой компьютер с Linux, Mozilla: ОК
  • MacBook, (недавний) Chrome: ПРОБЛЕМА
  • MacBook, Mozilla: ОК
  • MacBook, Safari. ОК
  • Планшет Android, (старый) Chrome: ОК

Если вы видите, что все в порядке, и вам интересно, как выглядит проблема, вот скриншот. (Возможно, вам придется щелкнуть, чтобы увидеть его на 100%, в скорректированном размере трудно увидеть проблему):

введите описание изображения здесь

Обратите внимание, что:

  • Изображение выглядит размытым, смешивая красный и зеленый цвета (в результате получается бордовый). Это особенно печально известно вокруг 5th. image.
  • Проблема также влияет на текст. Если вы внимательно посмотрите, текст выше 5-го. квадрат немного более размытый, чем в начале.

Эффект аналогичен тому, который достигается, если вы рисуете на изображении (или на внеэкранном холсте), а затем рисуете его содержимое на холсте, используя другой масштабный коэффициент (т. Е. Рисуете изображение размером 100×100 в пространстве 99×99), потому что эффект напоминает типичный результат повторной выборки.

То, что я пробовал, безуспешно

  1. context.resetTransform() перед рисованием.
  2. context.imageSmoothingEnabled = false . Даже если это сработало, это решило проблему для изображения, но не для текста.
  3. context.drawImage() с dWidth dHeight параметрами и, чтобы принудительно отрисовывать цель размером 100×100.
  4. #canvas { image-rendering: pixelated; }
  5. Chrome -> Настройки -> Внешний вид -> Использовать аппаратное ускорение, когда доступно -> (отключение)
  6. Использование других методов для макета. Я пытался использовать DIVs вместо таблицы.
  7. Согласно <canvas>: графический элемент холста:

Изменение размера холста с помощью CSS по сравнению с HTML

Отображаемый размер canvas можно изменить с помощью CSS, но если вы сделаете это, изображение будет масштабироваться во время рендеринга в соответствии с размером стилизованного изображения, что может привести к искажению конечного графического рендеринга.

Лучше указать размеры холста, установив атрибуты width и height непосредственно на элементах, либо непосредственно в HTML, либо с помощью JavaScript.

Все материалы canvas выполняются с помощью Javascript, без CSS, без HTML.


Вы как-то экспериментировали с этим эффектом раньше? Вы решили это? Как?

Есть идеи для решения?

Если вы экспериментируете с проблемой, и у вас браузер, отличный от Chrome, пожалуйста, прокомментируйте, чтобы исключить проблему как специфичную для Chrome.

Любая помощь будет высоко оценена.

Комментарии:

1. Учтите тот факт, что (0,0) индексирует верхний левый угол первого пикселя. Вертикальная линия шириной 1 пиксель, проведенная отсюда, даст полную интенсивность только первой 1/2 пикселя. При рисовании линий шириной 1 пиксель вы получаете гораздо более чистые результаты, если считать (0.5,0.5) центром пикселя. На самом деле вы были почти там. В скрипте раскомментируйте context.imageSmoothingEnabled = false; и добавьте 0,5 к координатам X и Y. И вуаля — неизмененные копии изображения. 🙂

2. Спасибо @enhzflep за ваш комментарий. К сожалению, из-за этого все 10 изображений выглядят одинаково, но эти копии определенно отличаются от исходного изображения. С уважением

3. Что означает console.log( devicePixelRatio ) вывод? Делает ли эта скрипка лучше: Странно, что у вас все в порядке с другими браузерами… Может ли в вашем браузере быть применено некоторое масштабирование?

4. Привет, @Kaiido. devicePixelRatio сообщает о странном значении 1.001312255859375. Я убедился, что масштабирование не используется. Ваша скрипка выглядит идеально!!! , я не знал о существовании devicePixelRatio . Я немедленно попробую это сделать на своей странице. Большое спасибо

5. Привет @Kaiido, ваше решение работает как шарм. Я предлагаю вам записать его в качестве ответа, чтобы я мог оценить его как правильный. С уважением

Ответ №1:

Решение требует (как отметил @Kaiido) использования Window.devicePixelRatio :

devicePixelRatio оконного интерфейса возвращает отношение разрешения в физических пикселях к разрешению в пикселях CSS для текущего устройства отображения. Это значение также можно интерпретировать как отношение размеров пикселей: размер одного пикселя CSS к размеру одного физического пикселя. Проще говоря, это сообщает браузеру, сколько фактических пикселей экрана следует использовать для рисования одного пикселя CSS.

Код, который решает проблему:

 if (devicePixelRatio != 1.0) {
    context.canvas.style.width = (context.canvas.width / devicePixelRatio)   "px";
    context.canvas.style.height = (context.canvas.height / devicePixelRatio)   "px";
}
 

Это также работает в браузерах, отличных от Chrome, они (в моем случае) возвращают значение 1 для Window.devicePixelRatio , оставляя предыдущую функциональность нетронутой.