#javascript #html #canvas
#javascript #HTML #холст
Вопрос:
Я разрабатываю приложение, в котором есть функция рисования. Пользователь может рисовать на изображении, которое изначально состоит только из чисто черных и чисто белых пикселей. Позже, после того, как пользователь закончил рисовать, мне нужно выполнить некоторую обработку этого изображения на основе цветов каждого пикселя.
Однако я понял, что к тому времени, когда я обработал изображение, пиксели больше не были чисто черно-белыми, но между ними было много серого, даже если пользователь ничего не рисовал. Я написал некоторый код, чтобы проверить это, и обнаружил, что на изображении более 250 разных цветов, в то время как я ожидал только два (черный и белый). Я подозреваю, что canvas каким-то образом портит мои цвета, но я не могу понять, почему.
Я разместил демонстрацию на GitHub, демонстрирующую проблему.
Изображение
Это изображение. Он явно состоит только из черно-белых пикселей, но если вы хотите проверить сами, вы можете воспользоваться этим сайтом. Его исходный код доступен на GitHub, и я использовал его в качестве ссылки для моей собственной реализации подсчета цветов.
Мой код
Вот код, в котором я загружаю изображение и считаю уникальные цвета. Вы можете получить полный исходный код здесь.
class AppComponent {
/* ... */
// Rendering the image
ngAfterViewInit() {
this.context = this.canvas.nativeElement.getContext('2d');
const image = new Image();
image.src = 'assets/image.png';
image.onload = () => {
if (!this.context) return;
this.context.globalCompositeOperation = 'source-over';
this.context.drawImage(image, 0, 0, this.width, this.height);
};
}
// Counting unique colors
calculate() {
const imageData = this.context?.getImageData(0, 0, this.width, this.height);
const data = imageData?.data || [];
const uniqueColors = new Set();
for (let i = 0; i < data?.length; i = 4) {
const [red, green, blue, alpha] = data.slice(i, i 4);
const color = `rgba(${red}, ${green}, ${blue}, ${alpha})`;
uniqueColors.add(color);
}
this.uniqueColors = String(uniqueColors.size);
}
Это реализация с другого сайта:
function countPixels(data) {
const colorCounts = {};
for(let index = 0; index < data.length; index = 4) {
const rgba = `rgba(${data[index]}, ${data[index 1]}, ${data[index 2]}, ${(data[index 3] / 255)})`;
if (rgba in colorCounts) {
colorCounts[rgba] = 1;
} else {
colorCounts[rgba] = 1;
}
}
return colorCounts;
}
Как вы можете видеть, помимо схожести реализаций, они выдают очень разные результаты — на моем сайте написано, что у меня 256 уникальных цветов, в то время как на другом написано, что их всего два. Я также попытался просто скопировать и вставить реализацию, но получил те же 256. Вот почему я предполагаю, что проблема в моем canvas, но я не могу понять, что происходит.
Комментарии:
1. Если говорить о предыстории редактирования изображений, то это довольно типично для преобразования растрового изображения в 2 цвета в цветовую палитру RGB. Если при загрузке изображения вы не преобразуете белый в белый, а черный в черный, вы получите случайные вариации, поскольку преобразование просматривает соседние пиксели, чтобы определить значение, которое оно «должно быть». Даже Gimp и Photoshop не на 100% сопоставляют белое с белым и черное с черным, выполняя подобное преобразование.
2. Это имеет смысл… но это работает на другом веб-сайте! Я имею в виду, что он перебирает все пиксели и находит только два цвета — черный и белый.
3. Один веб-сайт, вероятно, учитывает цветовую палитру и рассматривает цвета по отношению к ней, поэтому он видит только 2 цвета, на которые вы его настроили. Веб-сайт, который видит более 2 цветов, скорее всего, преобразует его в изображение RGB, как и вы, и именно тогда «волшебным образом» появляются другие цвета. Поскольку я не знаю, что вы делаете с пикселями, я не могу рекомендовать переход на SVG вместо PNG, но, возможно, вам стоит изучить его и посмотреть, может ли это все еще работать для ваших нужд. SVG обеспечит вам желаемую четкость цвета, но IDK о пикселях.
Ответ №1:
Вы масштабируете свое изображение, и, поскольку вы не указали, какой алгоритм интерполяции использовать, используется сглаживающий алгоритм по умолчанию.
Это приведет к тому, что все пиксели, которые были на фиксированных границах и теперь должны занимать несколько пикселей, будут «смешиваться» со своими белыми соседями и создавать оттенки серого.
Существует imageSmoothingEnabled
свойство, которое сообщает браузеру использовать алгоритм ближайшего соседа, что улучшит ситуацию, но даже тогда у вас может быть не идеальный результат:
const canvas = document.querySelector("canvas");
const width = canvas.width = innerWidth;
const height = canvas.height = innerHeight;
const ctx = canvas.getContext("2d");
const img = new Image();
img.crossOrigin = "anonymous";
img.src = "https://raw.githubusercontent.com/ajsaraujo/unique-color-count-mre/master/src/assets/image.png";
img.decode().then(() => {
ctx.imageSmoothingEnabled = false;
ctx.drawImage(img, 0, 0, width, height);
const data = ctx.getImageData(0, 0, width, height).data;
const pixels = new Set(new Uint32Array(data.buffer));
console.log(pixels.size);
});
<canvas></canvas>
Поэтому лучше всего было бы не масштабировать изображение или делать это удобным для компьютера способом (используя коэффициент, кратный 2).