Я хочу отобразить уменьшенное изображение на холсте. При этом на днище космического корабля появляются неровные края, кажется, что сглаживание отключено.
Вот увеличенное изображение, созданное в Firefox:
Изображение очень четкое, но мы видим неровные края (особенно днище космического корабля, лобовое стекло, носовое крыло).
И в Хроме:
Изображение остается четким (иллюминаторы остаются четкими, все линии) и у нас нет зазубренных краев. Только облака немного размылись.
А в Chrome с отключенным сглаживанием:
Я попытался установить для свойства imageSmoothingEnabled значение true, но в Firefox это не действует, мой пример:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv = "Content-Type" content = "text/html;charset=UTF-8">
</head>
<body>
<!-- <canvas id = "canvas1" width = "1280" height = "720" style = "width: 640px; height: 360px;"></canvas> -->
<canvas id = "canvas1" width = "640" height = "360" style = "width: 640px; height: 360px;"></canvas>
<script>
const canvas = document.getElementById("canvas1")
const ctx = canvas.getContext("2d")
console.info("canvas size", canvas.width, canvas.height);
const img = new Image()
img.onload = () => {
const smooth = true;
ctx.mozImageSmoothingEnabled = smooth;
ctx.webkitImageSmoothingEnabled = smooth;
ctx.msImageSmoothingEnabled = smooth;
ctx.imageSmoothingEnabled = smooth;
// ctx.filter = 'blur(1px)';
ctx.drawImage(img, 0, 0, 3840, 2160, 0, 0, canvas.width, canvas.height);
}
img.src = "https://upload.wikimedia.org/wikipedia/commons/f/f8/BFR_at_stage_separation_2-2018.jpg";
</script>
</body>
</html>
Как применить сглаживание?
Обновлено: сглаживание применяется при просмотре сайта в Chrome, но не в Firefox.
Изменить 2: более точно сравнить изображения. На самом деле кажется, что Firefox применяет некоторое улучшение изображения, но не отключает его при установке для imageSmoothingEnabled значения false.
Редактировать 3: Замените упоминания о сглаживании на сглаживание, потому что кажется, что здесь задействовано больше, чем просто сглаживание.
Скриншот с техникой размытия:
Спасибо за ваш ответ, но я просто увеличил свой браузер, чтобы показать проблему, проблема такая же, как и с холстом в разрешении 640x360. Я отредактирую свой вопрос, чтобы быть более явным.
Ваш комментарий вдохновляет на обходной путь: визуализируйте холст размером 1280 x 720, а затем уменьшите его до 640 x 360 с помощью CSS. Таким образом, Firefox выполняет сглаживание, но я все же хотел бы иметь сглаживание напрямую через контекстный API.
Тогда мало что можно сделать. FireFox не поддерживает ctx.imageSmoothingQuality = "high" Потеря качества связана с тем, что холст предназначен для скорости, а не для качества, и вы берете изображение с высоким разрешением и сжимаете его в холст с более низким разрешением. Если вы уменьшаете исходное изображение с помощью Photoshop (любой пакет для рисования) до Соответствуйте разрешению холста, вы получите лучшие результаты, так как пакеты с краской намного лучше справятся с уменьшением размера.
Еще раз спасибо, я сделал больше скриншотов, чтобы точнее описать проблему. Я понимаю компромисс между качеством и скоростью, но вывод Chrome выглядит намного лучше, а imageSmoothingEnabled не действует в Firefox. Действительно, использование внешнего инструмента будет работать, но я хочу изменить размер изображения в своем приложении!
Изображение I .imageSmoothingEnabled корректно работает только в Firefox при увеличении изображения. Артефакты, которые вы видите, кажутся фундаментальной частью алгоритма масштабирования холста Firefox. Использование чего-то вроде ctx.save(); ctx.scale(1/6, 1/6); ctx.drawImage(img, 0, 0, 3840, 2160, 0, 0, 3840, 2160); ctx.restore(); дает тот же эффект.
Это ответ на ваш вопрос? stackoverflow.com/questions/17861447/…
Или это? stackoverflow.com/questions/18922880/…
Отвечает ли это на ваш вопрос? Html5 canvas drawImage: как применить сглаживание
Спасибо за все ваши предложения! stackoverflow.com/questions/17861447 не отвечает на мой вопрос: применяет фильтр размытия, убирает неровные края, но результат получается размытым (хоть и тонким)





В этом ответе представлен сэмплер с понижением, который будет иметь согласованные результаты в разных браузерах и допускает широкий диапазон сокращений, как однородных, так и неоднородных.
Он имеет значительное преимущество с точки зрения качества, поскольку может использовать 64-битные числа JS с плавающей запятой, а не 32-битные числа с плавающей запятой, используемые графическим процессором. Это также уменьшает sRGB, а не более низкое качество RGB, используемое 2d API.
Его недостаток, конечно, производительность. Это может сделать его непрактичным при уменьшении выборки больших изображений. Однако его можно запускать параллельно через веб-воркеры, чтобы не блокировать основной пользовательский интерфейс.
Только для понижающей выборки на уровне 50% или ниже. Для масштабирования до любого размера потребуется всего несколько незначительных модификаций, но в примере предпочтение отдается скорости, а не универсальности.
Прирост качества для 99% людей, просматривающих результат, будет едва заметен.
Метод выбирает исходные пиксели под новым целевым пикселем, вычисляя цвет на основе перекрывающихся областей пикселей.
Следующая иллюстрация поможет понять, как это работает.
Сначала мы создаем 3 значения, чтобы удерживать новый цвет R, G, B равным нулю (черный).
Мы выполняем следующее для каждого пикселя под целевым пикселем.
Когда все пиксели под новым пикселем обработаны, новые значения цветов R, G, B преобразуются обратно в RGB и добавляются к данным изображения.
По завершении данные пикселей добавляются на холст, который возвращается готовым к использованию.
Пример уменьшает изображение примерно на ~ 1/4.
После этого в примере отображается масштабированное изображение и изображения, масштабированные с помощью 2D API.
Вы можете щелкнуть верхнее изображение, чтобы переключиться между двумя методами и сравнить результаты.
/* Image source By SharonPapierdreams - Own work, CC BY-SA 4.0, https://commons.wikimedia.org/w/index.php?curid=97564904 */
// reduceImage(img, w, h)
// img is image to down sample. w, h is down sampled image size.
// returns down sampled image as a canvas.
function reduceImage(img, w, h) {
var x, y = 0, sx, sy, ssx, ssy, r, g, b, a;
const RGB2sRGB = 2.2; // this is an approximation of sRGB
const sRGB2RGB = 1 / RGB2sRGB;
const sRGBMax = 255 ** RGB2sRGB;
const srcW = img.naturalWidth;
const srcH = img.naturalHeight;
const srcCan = Object.assign(document.createElement("canvas"), {width: srcW, height: srcH});
const sCtx = srcCan.getContext("2d");
const destCan = Object.assign(document.createElement("canvas"), {width: w, height: h});
const dCtx = destCan.getContext("2d");
sCtx.drawImage(img, 0 , 0);
const srcData = sCtx.getImageData(0,0,srcW,srcH).data;
const destData = dCtx.getImageData(0,0,w,h);
// Warning if yStep or xStep span less than 2 pixels then there may be
// banding artifacts in the image
const xStep = srcW / w, yStep = srcH / h;
if (xStep < 2 || yStep < 2) {console.warn("Downsample too low. Should be at least 50%");}
const area = xStep * yStep
const sD = srcData, dD = destData.data;
while (y < h) {
sy = y * yStep;
x = 0;
while (x < w) {
sx = x * xStep;
const ssyB = sy + yStep;
const ssxR = sx + xStep;
r = g = b = a = 0;
ssy = sy | 0;
while (ssy < ssyB) {
const yy1 = ssy + 1;
const yArea = yy1 > ssyB ? ssyB - ssy : ssy < sy ? 1 - (sy - ssy) : 1;
ssx = sx | 0;
while (ssx < ssxR) {
const xx1 = ssx + 1;
const xArea = xx1 > ssxR ? ssxR - ssx : ssx < sx ? 1 - (sx - ssx) : 1;
const srcContribution = (yArea * xArea) / area;
const idx = (ssy * srcW + ssx) * 4;
r += ((sD[idx ] ** RGB2sRGB) / sRGBMax) * srcContribution;
g += ((sD[idx+1] ** RGB2sRGB) / sRGBMax) * srcContribution;
b += ((sD[idx+2] ** RGB2sRGB) / sRGBMax) * srcContribution;
a += (sD[idx+3] / 255) * srcContribution;
ssx += 1;
}
ssy += 1;
}
const idx = (y * w + x) * 4;
dD[idx] = (r * sRGBMax) ** sRGB2RGB;
dD[idx+1] = (g * sRGBMax) ** sRGB2RGB;
dD[idx+2] = (b * sRGBMax) ** sRGB2RGB;
dD[idx+3] = a * 255;
x += 1;
}
y += 1;
}
dCtx.putImageData(destData,0,0);
return destCan;
}
const scaleBy = 1/3.964;
const img = new Image;
img.crossOrigin = "Anonymous";
img.src = "https://upload.wikimedia.org/wikipedia/commons/7/71/800_Houston_St_Manhattan_KS_3.jpg";
img.addEventListener("load", () => {
const downScaled = reduceImage(img, img.naturalWidth * scaleBy | 0, img.naturalHeight * scaleBy | 0);
const downScaleByAPI = Object.assign(document.createElement("canvas"), {width: downScaled.width, height: downScaled.height});
const ctx = downScaleByAPI.getContext("2d");
ctx.drawImage(img, 0, 0, ctx.canvas.width, ctx.canvas.height);
const downScaleByAPI_B = Object.assign(document.createElement("canvas"), {width: downScaled.width, height: downScaled.height});
const ctx1 = downScaleByAPI_B.getContext("2d");
ctx1.drawImage(img, 0, 0, ctx.canvas.width, ctx.canvas.height);
img1.appendChild(downScaled);
img2.appendChild(downScaleByAPI_B);
info2.textContent = "Original image " + img.naturalWidth + " by " + img.naturalHeight + "px Downsampled to " + ctx.canvas.width + " by " + ctx.canvas.height+ "px"
var a = 0;
img1.addEventListener("click", () => {
if (a) {
info.textContent = "High quality JS downsampler";
img1.removeChild(downScaleByAPI);
img1.appendChild(downScaled);
} else {
info.textContent = "Standard 2D API downsampler";
img1.removeChild(downScaled);
img1.appendChild(downScaleByAPI);
}
a = (a + 1) % 2;
})
}, {once: true})body { font-family: arial }<br>Click first image to switch between JS rendered and 2D API rendered versions<br><br>
<span id = "info2"></span><br><br>
<div id = "img1"> <span id = "info">High quality JS downsampler </span><br></div>
<div id = "img2"> Down sampled using 2D API<br></div>
Image source <cite><a href = "https://commons.wikimedia.org/w/index.php?curid=97564904">By SharonPapierdreams - Own work, CC BY-SA 4.0,</a></cite>sRGB — это цветовое пространство, которое все цифровые мультимедийные устройства используют для отображения контента. Люди видят логарифмическую яркость, что означает, что динамический диапазон устройства отображения составляет от 1 до ~ 200 000, что требует 18 бит на канал.
Буферы дисплея преодолевают это, сохраняя значения канала как sRGB. Яркость в диапазоне 0–255. Когда аппаратное обеспечение дисплея преобразует это значение в фотоны, оно сначала расширяет 255 значений, возводя их в степень 2,2, чтобы обеспечить необходимый широкий динамический диапазон.
Проблема в том, что обработка буфера дисплея (2D API) игнорирует это и не расширяет значения sRGB. Он обрабатывается как RGB, что приводит к неправильному смешиванию цветов.
На изображении показана разница между рендерингом sRGB и RGB (RGB, используемый 2D API).
Обратите внимание на темные пиксели в центре и на правом изображении. Это результат рендеринга RGB. Левое изображение рендерится с использованием sRGB и не теряет яркости.
«RGB, используемый 2d API», откуда вы это взяли? Пока это не воплотится в жизнь, реализации должны использовать цветовое пространство, используемое для рендеринга элемента холста с помощью CSS, то есть sRGB.
@Kaiido Все цифровые медиа используют sRGB с тех пор. Мы не говорим об аппаратном обеспечении дисплея. Речь идет о рендеринге. Посмотрите на свой экран, увидите темные края при рисовании зеленым на красных линиях RGB, красный в смеси с зеленым не должен быть темнее (стремится к темно-красному или темно-зеленому). Это исправлено при рендеринге с использованием sRGB, имеющего тенденцию к желто-оранжевому цвету, чего 2D API НЕ МОЖЕТ СДЕЛАТЬ! .. Я добавлю пример к ответу.
Я уже не говорю о дисплейной части. Это по спецификациям они должны преобразовывать цвета только в drawImage и при рендеринге на устройство. Все, что между ними, должно быть sRGB.
@Kaiido Смотрите обновление на нижнем изображении ответа. 2D API явно не использует sRGB. Почему это так? Потому что «Композитные операторы Портера Даффа».drafts.fxtf.org/compositing-1/#porterduffcompositingoperators Весь отображаемый контент добавляется на холст с помощью композитных операций по умолчанию «source-over». Операции Портера-Даффа — это RGB, а не sRGB. До тех пор, пока старый Porter Duff не будет заменен на композитинг с гамма-коррекцией, мы застряли в беспорядке рендеринга RGB.
Спасибо @Blindman67 за этот подробный ответ, вы четко объясняете свой алгоритм для уменьшения изображения и даете рабочую реализацию javascript. Ваши пояснения по поводу sRGB также очень поучительны и убедительны. Теперь в моем конкретном случае, поскольку я хочу отображать только небольшие превью файлов изображений, я прибегаю к использованию CSS или масштабирования холста, чтобы он обрабатывался браузером.
Что касается причины сглаживания псевдонимов в Firefox, вы говорите, что это потому, что алгоритм масштабирования указан W3C и не позволяет использовать изображения sRGB!? Если это так, похоже, Chrome решил не уважать его!
@LouisCoulet Нет смысла, если sRGB (отсутствие его) является лишь частью проблемы. Проблема в том, что алгоритмы, используемые 2D API, не предназначены для таких высоких сокращений, поскольку они оптимизированы для повышения производительности.
У вас есть разрешение холста в коде как 640 на 360, но предоставленное вами изображение имеет размер 1382 на 750. Установите ширину и высоту CSS холста, чтобы они соответствовали ширине и высоте холста. например
canvas{width:640px;height:360px;}Ширина и высота холста задают разрешение холста (количество содержащихся в нем пикселей), а ширина и высота CSS задают размер отображения холста (насколько он велик на странице). Для достижения наилучшего результата вы должны убедиться, что размер дисплея соответствует разрешению. .