Могу ли я отключить сглаживание для элемента HTML <canvas>?

88

Я играю с <canvas> элементом, рисую линии и тому подобное.

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

Blorgbeard отсутствует
источник
Я думаю, что это скорее связано с браузером. Может быть, вам будет полезна дополнительная информация о том, какое программное обеспечение вы используете.
Tomalak
Я бы предпочел кроссбраузерный метод, но метод, который работает в любом отдельном браузере, все равно был бы мне интересен
Blorgbeard вышел
Я просто хотел узнать, есть ли какие-либо изменения по этой теме?
vternal3
есть ли обновления по этому поводу?
Роланд

Ответы:

64

Для изображений есть сейчас .context.imageSmoothingEnabled= false

Однако нет ничего, что явно управляло бы рисованием линий. Возможно, вам придется рисовать свои собственные линии ( сложный способ ), используя getImageDataи putImageData.

Корнель
источник
1
Мне интересно узнать о производительности строкового алгоритма javascript ... Возможно, в какой-то момент Брезенхэм попробует.
Blorgbeard выйдет
В последнее время поставщики браузеров рекламируют новые сверхбыстрые JS-движки, так что, наконец, им нашлось хорошее применение.
Kornel
1
Это действительно работает? Я рисую линию с помощью, putImageData но он все еще чертовски сглаживает соседние пиксели.
Pacerier
если я рисую на холсте меньшего размера (кеш), а затем отрисовываю изображение на другом холсте с этой настройкой, будет ли он работать должным образом?
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)это вначале весь ваш холст.

Аллан
источник
хм, у меня не получается избавиться от сглаживания с помощью этой техники. Может, я чего-то не понимаю? Не могли бы вы разместить где-нибудь пример?
Xavi
7
Это не избавляет от сглаживания, но делает линии со сглаживанием намного лучше - например, избавление от этих смущающих горизонтальных или вертикальных линий толщиной в два пикселя, когда вам действительно нужен один пиксель.
Дэвид Гивен
1
@porneL: Нет, линии рисуются между углами пикселей. Когда ваша линия имеет ширину 1 пиксель, она расширяется на полпикселя в любом направлении
Эрик
Добавление +0,5 работает для меня, но ctx.translate(0.5,0.5)не работает. on 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
13

Это должно быть антиалиас векторная графика

Сглаживание требуется для правильного построения векторной графики, которая использует нецелочисленные координаты (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 минуты), а затем просто перетащить, чтобы увидеть эффект.
Ижаки
Это может показаться невероятным, но бывают редкие ситуации, когда вы хотите отключить антиалиас. На самом деле, я только что запрограммировал игру, в которой люди должны рисовать области на холсте, и мне нужно, чтобы там было только 4 цвета, и я застрял из-за сглаживания. Повторение шаблона трижды не решило проблему (я увеличил число до 200), и все еще есть пиксели неправильного цвета.
Арно
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'))

Может это кому пригодится.

eri0o
источник
почему бы не context.imageSmoothingEnabled = false?
Martijn Scheffer
Это не сработало в то время, когда я писал свой ответ. А теперь работает?
eri0o
1
он сделал, это ТОЧНО то же самое, в написании javascript obj ['name'] или obj.name всегда было и всегда будет одним и тем же, объект представляет собой набор именованных значений (кортежей), используя что-то похожее на хеш-таблица, обе нотации будут обрабатываться одинаково, нет никакой причины, по которой ваш код не работал бы раньше, в худшем случае он присваивает значение, которое не имеет никакого эффекта (потому что оно предназначено для другого браузера. простой пример: написать obj = {a: 123}; console.log (obj ['a'] === obj.a? «да, это правда»: «нет, это не так»)
Мартин Шеффер
Я думал, вы имели в виду, зачем нужны все остальные вещи. В своем комментарии я имел в виду, что в то время браузеры требовали других свойств.
eri0o
хорошо, да, конечно :) Я говорил о синтаксисе, а не о действительности самого кода (он работает)
Мартейн Шеффер
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);

В результате будет черно-белое изображение без антиалиасинга. Это не будет идеально, поскольку будет иметь место некоторое сглаживание, но это сглаживание будет очень ограниченным, так как цвет формы очень похож на цвет фона.

StashOfCode
источник
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
Jaewon.AC
источник
0

Всего две заметки об ответе StashOfCode:

  1. Он работает только для непрозрачного холста в оттенках серого (fillRect с белым, затем рисование с черным, или наоборот)
  2. Он может не работать, если линии тонкие (ширина линии ~ 1 пиксель)

Лучше сделать так:

Обведите и залейте #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;

    }
Elliottdehn
источник
0

Попробуйте что-нибудь вроде canvas { image-rendering: pixelated; }.

Это может не сработать, если вы пытаетесь сделать не сглаженным только одну строку.

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");

ctx.fillRect(4, 4, 2, 2);
canvas {
  image-rendering: pixelated;
  width: 100px;
  height: 100px; /* Scale 10x */
}
<html>
  <head></head>
  <body>
    <canvas width="10" height="10">Canvas unsupported</canvas>
  </body>
</html>

Однако я не тестировал это во многих браузерах.

Сошимэ
источник