Чи можу я вимкнути згладжування елемента HTML <canvas>?


85

Я бавлюся <canvas>стихією, малюю лінії тощо.

Я помітив, що мої діагональні лінії згладжені. Я віддав би перевагу нерівному погляду того, що роблю - чи є спосіб вимкнути цю функцію?


Я думаю, що це скоріше пов'язано з браузером. Можливо, корисною буде додаткова інформація про те, яке програмне забезпечення ви використовуєте.
Томалак

Я б віддав перевагу крос-браузерному методу, але метод, який працює в будь-якому окремому браузері, все одно був би мені цікавий
Blorgbeard вийшов

Я просто хотів дізнатись, чи не відбулося якихось змін у цій темі?
vternal3


чи є оновлення щодо цього?
Роланд

Відповіді:


60

Для зображень є зараз .context.imageSmoothingEnabled= false

Однак немає нічого, що чітко контролює креслення ліній. Можливо, вам доведеться намалювати власні лінії ( важким способом ), використовуючи getImageDataта putImageData.


1
Цікаво про ефективність алгоритму рядка javascript .. Може, колись Брезенхем спробує.
Blorgbeard вийшов

Постачальники браузерів останнім часом рекламують нові надшвидкі двигуни JS, тому, нарешті, це буде корисно використовувати.
Корнель

1
Це справді працює? Я малюю лінію, використовуючи, putImageData але він все ще робить псевдонім ближніх пікселів, блін.
Пейс'єр

якщо я малюю на меншому полотні (кеш-пам’яті), а потім малюю Імідж на іншому полотні з таким налаштуванням, чи буде це працювати так, як задумано?
SparK

69

Намалюйте свої 1-pixelлінії на таких координатах, як ctx.lineTo(10.5, 10.5). Накреслення лінії в один піксель над точкою (10, 10)означає, що цей 1піксель у цій позиції сягає від 9.5до10.5 якого призводять дві лінії, які малюються на полотні.

Гарний фокус, коли не завжди потрібно додавати 0.5координати до фактичної координати, яку ви хочете намалювати, якщо у вас багато ліній з одним пікселем, - це ctx.translate(0.5, 0.5)на початку всього вашого полотна.


хм, у мене проблеми з позбавленням від згладжування за допомогою цієї техніки. Може, мені щось не вистачає? Не могли б ви розмістити приклад десь?
Хаві,

7
Це не позбавляє від згладжування, але робить згладжені лінії виглядають набагато краще - таких як позбавлення від тих незручних горизонтальних або вертикальних ліній, товщина яких становить два пікселі, коли ви насправді хотіли один піксель.
Девід Дано

1
@porneL: Ні, між кутами пікселів проводяться лінії. Коли ваша лінія має ширину 1 піксель, це поширюється на половину пікселя в будь-якому напрямку
Ерік,

Додавання +0,5 працює для мене, але ctx.translate(0.5,0.5)ні. на FF39.0
Пауло Буено

Дуже дякую! Я не можу повірити, що у мене є фактичні рядки розміром 1 піксель для зміни!
Chunky Chunk

24

Це можна зробити в Mozilla Firefox. Додайте це до свого коду:

contextXYZ.mozImageSmoothingEnabled = false;

Наразі в Opera це запит на функцію, але, сподіваємось, він буде доданий найближчим часом.


круто. +1 за ваш внесок. Цікаво, чи відключення AA прискорює вичерчування
marcusklaas

7
OP хоче зняти анти-псевдонім, але це працює лише на зображеннях. За специфікацією це визначає"whether pattern fills and the drawImage() method will attempt to smooth images if their pixels don't line up exactly with the display, when scaling images up"
rvighne

14

Він повинен згладжувати векторну графіку

Згладжування потрібне для правильного побудови векторної графіки, що включає нецілі координати (0,4, 0,4), що роблять усі, крім дуже мало клієнтів.

Якщо задано нецілі координати, полотно має два варіанти:

  • Згладжування - намалюйте пікселі навколо координати, виходячи з того, наскільки далеко ціла координата знаходиться від нецілої (тобто, помилка округлення).
  • Round - застосуйте якусь функцію округлення до нецілої координати (так, наприклад, 1.4 стане 1).

Пізніше стратегія буде працювати для статичної графіки, хоча для невеликої графіки (коло з радіусом 2) криві відображатимуть чіткі кроки, а не плавну криву.

Справжня проблема полягає в тому, що графіка перекладається (переміщується) - стрибки між одним пікселем та іншим (1,6 => 2, 1,4 => 1) означають, що початок фігури може переходити по відношенню до батьківського контейнера (постійно зміщуючи 1 піксель вгору / вниз і вліво / вправо).

Кілька порад

Порада №1 . Ви можете пом’якшити (або затвердіти) згладжування, масштабуючи полотно (скажімо, x), а потім застосувати зворотну шкалу (1 / x) до геометрій самостійно (не використовуючи полотно).

Порівняйте (без масштабування):

Кілька прямокутників

з (масштаб полотна: 0,75; масштаб вручну: 1,33):

Ті самі прямокутники з більш м’якими краями

та (масштаб полотна: 1,33; масштаб вручну: 0,75):

Ті самі прямокутники з темнішими краями

Порада №2 : якщо нерівний вигляд - це справді те, що вам потрібно, спробуйте намалювати кожну фігуру кілька разів (не стираючи). З кожним малюванням пікселі згладжування темніють.

Порівняйте. Одного разу намалювавши:

Кілька стежок

Після малювання тричі:

Ті самі шляхи, але темніші та без видимих ​​згладжувань.


@vanowm сміливо клонуйте та грайте з: github.com/Izhaki/gefri . Всі зображення - це скріншоти з папки / demo (з кодом, дещо зміненим для підказки №2). Я впевнений, вам буде легко ввести цілочисельне округлення до намальованих фігур (це зайняло у мене 4 хвилини), а потім просто перетягніть, щоб побачити ефект.
Іжакі

9

Я б намалював усе, використовуючи власний алгоритм лінії, такий як лінійний алгоритм Брезенхама. Ознайомтесь із реалізацією JavaScript: http://members.chello.at/easyfilter/canvas.html

Думаю, це точно вирішить ваші проблеми.


2
Саме те, що мені потрібно було, єдине, що я хотів би додати, це те, що вам потрібно реалізувати setPixel(x, y); Я використав прийняту відповідь тут: stackoverflow.com/questions/4899799/…
Тіна Валл

8

Хочу додати, що у мене виникли проблеми при зменшенні розміру зображення та малюванні на полотні, воно все ще використовувало згладжування, хоча і не використовувало при збільшенні масштабу.

Я вирішив, використовуючи це:

function setpixelated(context){
    context['imageSmoothingEnabled'] = false;       /* standard */
    context['mozImageSmoothingEnabled'] = false;    /* Firefox */
    context['oImageSmoothingEnabled'] = false;      /* Opera */
    context['webkitImageSmoothingEnabled'] = false; /* Safari */
    context['msImageSmoothingEnabled'] = false;     /* IE */
}

Ви можете використовувати цю функцію так:

var canvas = document.getElementById('mycanvas')
setpixelated(canvas.getContext('2d'))

Можливо, це комусь корисно.


чому не context.imageSmoothingEnabled = false?
Martijn Scheffer

На момент написання моєї відповіді це не спрацювало. Це працює зараз?
eri0o,

1
це було, це ТОЧНО те саме, у написанні javascript obj ['name'] або obj.name завжди було і завжди буде однаковим, об'єкт - це колекція іменованих значень (кортежів), використовуючи щось, що нагадує хеш-таблиця, обидва позначення будуть оброблятися однаково, взагалі немає жодної причини, що ваш код раніше не працював, в гіршому випадку він присвоює значення, яке не має ефекту (оскільки воно призначене для іншого браузера. простий приклад: напишіть obj = {a: 123}; console.log (obj ['a'] === obj.a? "так, це правда": "ні, це не так")
Martijn Scheffer

Я думав, ви маєте на увазі, навіщо всі інші речі. Я мав на увазі свій коментар, що тоді браузери вимагали різних властивостей.
eri0o

добре так, звичайно :) я говорив про синтаксис, а не про дійсність самого коду (він працює)
Martijn Scheffer

6
ctx.translate(0.5, 0.5);
ctx.lineWidth = .5;

За допомогою цієї комбінації я можу намалювати гарні тонкі лінії розміром 1 піксель.


6
Вам не потрібно встановлювати lineWidth на .5 ..., що буде (або повинно) зробити лише половину непрозорості.
aaaidan

4

Зверніть увагу на дуже обмежений фокус. Якщо ви хочете створити двоколірне зображення, ви можете намалювати будь-яку потрібну вам фігуру кольором # 010101 на тлі з кольором # 000000. Після цього ви можете протестувати кожен піксель у imageData.data [] і встановити значення 0xFF, яке б значення не було 0x00:

imageData = context2d.getImageData (0, 0, g.width, g.height);
for (i = 0; i != imageData.data.length; i ++) {
    if (imageData.data[i] != 0x00)
        imageData.data[i] = 0xFF;
}
context2d.putImageData (imageData, 0, 0);

Результатом буде чорно-біла картинка без згладжування. Це буде не ідеально, оскільки відбудеться деяке згладжування, але це згладжування буде дуже обмеженим, колір фігури дуже нагадує колір фону.


1

Для тих, хто все ще шукає відповіді. ось моє рішення.

Припускаючи, що зображення є 1-канальним сірим. Я просто поріг після ctx.stroke ().

ctx.beginPath();
ctx.moveTo(some_x, some_y);
ctx.lineTo(some_x, some_y);
...
ctx.closePath();
ctx.fill();
ctx.stroke();

let image = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height)
for(let x=0; x < ctx.canvas.width; x++) {
  for(let y=0; y < ctx.canvas.height; y++) {
    if(image.data[x*image.height + y] < 128) {
      image.data[x*image.height + y] = 0;
    } else {
      image.data[x*image.height + y] = 255;
    }
  }
}

якщо ваш канал зображення 3 або 4. вам потрібно змінити індекс масиву, як

x*image.height*number_channel + y*number_channel + channel

0

Лише дві нотатки щодо відповіді StashOfCode:

  1. Це працює лише для сірого, непрозорого полотна (fillRect білим, а потім намалюйте чорним або навпаки)
  2. Це може вийти з ладу, коли лінії тонкі (~ 1px ширина рядка)

Краще зробити це замість цього:

Обведіть і заповніть #FFFFFF, а потім зробіть так:

imageData.data[i] = (imageData.data[i] >> 7) * 0xFF

Це вирішує це для рядків шириною 1 піксель.

Окрім цього, рішення StashOfCode ідеально підходить, оскільки не вимагає писати власні функції растеризації (думайте не лише про лінії, але про безіє, кругові дуги, заповнені багатокутники дірками тощо ...)


0

Ось основна реалізація алгоритму Брезенхама в JavaScript. Він базується на цілочислово-арифметичній версії, описаній у цій статті Вікіпедії: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm

    function range(f=0, l) {
        var list = [];
        const lower = Math.min(f, l);
        const higher = Math.max(f, l);
        for (var i = lower; i <= higher; i++) {
            list.push(i);
        }
        return list;
    }

    //Don't ask me.
    //https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
    function bresenhamLinePoints(start, end) {

        let points = [];

        if(start.x === end.x) {
            return range(f=start.y, l=end.y)
                        .map(yIdx => {
                            return {x: start.x, y: yIdx};
                        });
        } else if (start.y === end.y) {
            return range(f=start.x, l=end.x)
                        .map(xIdx => {
                            return {x: xIdx, y: start.y};
                        });
        }

        let dx = Math.abs(end.x - start.x);
        let sx = start.x < end.x ? 1 : -1;
        let dy = -1*Math.abs(end.y - start.y);
        let sy = start.y < end.y ? 1 : - 1;
        let err = dx + dy;

        let currX = start.x;
        let currY = start.y;

        while(true) {
            points.push({x: currX, y: currY});
            if(currX === end.x && currY === end.y) break;
            let e2 = 2*err;
            if (e2 >= dy) {
                err += dy;
                currX += sx;
            }
            if(e2 <= dx) {
                err += dx;
                currY += sy;
            }
        }

        return points;

    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.