HTML5 Canvas globalCompositeOperation для наложения градиентов, не добавляющих более высокую интенсивность?

в настоящее время я работаю над теплокарты.js исправить, и мне было интересно, знает ли кто-нибудь, можно ли достичь следующего эффекта с <canvas>контекст 2d-рендеринга.

  • у меня есть радиальный градиент от черного (Альфа 0.5) до прозрачного радиуса 40pixel. центр радиального градиента находится при x=50, y=50
  • у меня есть еще один радиальный градиент от черного (Альфа 0.5) до прозрачного, радиус 40pixel. центр радиального градиента при x=80, y=50

два градиента перекрываются. Теперь моя проблема: перекрывающаяся область складывается вместе, что приводит к более высокому Альфа-значению, чем центры радиальных градиентов, и, таким образом, показывает неправильные данные (например, более горячие области в тепловой карте из-за этих добавлений между градиентами)

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

ожидаемое поведение будет: Темные участки градиенты центрируются, перекрывающаяся область двух градиентов сливается, но не складывается.

увидев, что ни одна из globalCompositeOperations не привела к ожидаемому поведению, я попробовал комбинации этих операций. Я думал, что это возможно, было бы следующим образом:

  • нарисуйте первый градиент
  • использовать compositeOperation 'destination-out'
  • нарисовать второй градиент - > область перекрытия подстрок от первого градиент
  • использовать compositeOperation 'source-over'
  • нарисуйте второй градиент снова

но, к сожалению, я не нашел комбинацию, которая работает. Я хотел бы услышать ваши отзывы, заранее спасибо!

PS: Я знаю, что это можно сделать, манипулируя пикселями вручную, но мне было интересно, есть ли более простое, элегантное и быстрое решение для этого.

4 ответов


это действительно странно, но он делает то, что вы хотите, не получая imageData участие.

что пришло на ум, так это то, что вы хотите точную функциональность, которую сами пути имеют на холсте, когда вы их гладите. Процитировать спецификацию:

в результате того, как определяется алгоритм трассировки пути, перекрывающиеся части путей в одной операции штриха рассматриваются, как если бы их объединение было то, что было окрашено.

вы можете прочитать подробнее об этом здесь.

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

единственная проблема заключается в том, что нет никакого способа, чтобы сделать размытый путь в холст. Или почти никак.

вместо использования самого пути мы можем использовать тень путь для имитации размытых кругов, которые подчиняются тем же правилам, что и пути делать.

но у теней есть свойства shadowOffsetX и shadowOffsetY, которые обычно используются для смещения тени на пиксель или два, чтобы создать иллюзию источника света.

но что, если мы рисуем тени далеко, что ты их не видишь? Или, скорее, что, если мы нарисуем пути так далеко, что вы не можете их видеть, все, что вы можете видеть, это тени?

Ну, это делает трюк. Вот быстрый результат, ваш исходный код находится сверху, а тени-второй холст:

enter image description here

это не совсем то, что у вас было раньше с точки зрения градиентов и размера, но его очень близко, и я уверен, что, играя со значениями, вы можете получить его еще ближе. Пара консолей.журнал подтверждает, что то, что мы хотим, Альфа, которая не идет выше 124 (из 255), правильно происходит в тех местах, где раньше было 143 и 134, делая это по-старому.

скрипка, чтобы увидеть код в действии:http://jsfiddle.net/g54Mz/

Так что у вас есть. Получение эффекта объединения двух радиальных градиентов возможно без imageData Если вы используете тени и смещаете фактические пути настолько, что их нет на экране.


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

реализация этой техники доставила взгляд, который я хотел. И мне нравилось, что это легко понять. Однако, на практике я обнаружил, что рендеринг даже несколько (~150) теней был намного медленнее по сравнению с моей предыдущей техникой (хотя и непривлекательной) рисования тысяч заполненных рект.

поэтому я решил провести некоторый анализ. Я написал несколько базовых JavaScript (модифицированную версию которого можно увидеть вhttps://jsfiddle.net/Flatfingers/4vd22rgg/) чтобы нарисовать 2000 копий каждого из пяти различных типов фигур на неперекрывающихся участках холста 1250x600, записывая прошедшее время для каждой из этих пяти операций в последних версиях пяти основных рабочих столов браузеры плюс мобильное сафари. (Извините, desktop Safari. У меня также нет Android для тестирования.) Затем я попробовал различные комбинации эффектов и записал истекшее время.

вот упрощенный пример того, как я рисую два градиента с внешним видом, похожим на затененные заполненные дуги:

var gradient1 = context.createRadialGradient(75,100,2,75,100,80);
gradient1.addColorStop(0,"yellow");
gradient1.addColorStop(1,"black");

var gradient2 = context.createRadialGradient(125,100,2,125,100,80);
gradient2.addColorStop(0,"blue");
gradient2.addColorStop(1,"black");

context.beginPath();
context.globalCompositeOperation = "lighter";
context.globalAlpha = 0.5;
context.fillStyle = gradient1;
context.fillRect(0,0,200,200);
context.fillStyle = gradient2;
context.fillRect(0,0,200,200);
context.globalAlpha = 1.0;
context.closePath();

тайминги

(2000 неперекрывающихся фигур, наборов globalAlpha, drawImage() используется для градиентов, но не тени)

IE 11 (64-bit Windows 10)
 Rects     =   4 ms
 Arcs      =  35 ms
 Gradients =  57 ms
 Images    =   8 ms
 Shadows   = 160 ms

Edge (64-bit Windows 10)
 Rects     =   3 ms
 Arcs      =  47 ms
 Gradients =  52 ms
 Images    =   7 ms
 Shadows   = 171 ms

Chrome 48 (64-bit Windows 10)
 Rects     =   4 ms
 Arcs      =  10 ms
 Gradients =   8 ms
 Images    =   8 ms
 Shadows   = 203 ms

Firefox 44 (64-bit Windows 10)
 Rects     =   4 ms
 Arcs      =  21 ms
 Gradients =   7 ms
 Images    =   8 ms
 Shadows   = 468 ms

Opera 34 (64-bit Windows 10)
 Rects     =   4 ms
 Arcs      =   9 ms
 Gradients =   8 ms
 Images    =   8 ms
 Shadows   = 202 ms

Mobile Safari (iPhone5, iOS 9)
 Rects     =  12 ms
 Arcs      =  31 ms
 Gradients =  67 ms
 Images    =  82 ms
 Shadows   =  32 ms

наблюдения

  1. среди заполненных фигур заполненные прямоугольники последовательно являются самой быстрой операцией во всех проверенных браузерах и средах.
  2. заполненные полные дуги (круги) примерно в 10 раз медленнее в IE 11 и Edge, чем заполненные ректы, по сравнению с примерно 3.5 x медленнее в других основных браузерах.
  3. градиенты примерно в 3 раза медленнее, чем rects в IE 11, Chrome 48 и Opera 34, но замечательный 100x медленнее в В Firefox 44 (см. Bugzilla report 728453).
  4. изображения через drawImage () примерно 1.5 x так же быстро, как заполненные прямоугольники во всех настольных браузерах.
  5. затененные заполненные дуги медленнее всего, начиная от примерно 50x медленнее, чем заполненные ректы в IE, Edge, Chrome и Opera до 100x медленнее в Firefox.
  6. Chrome 48 и Opera 34 оба удивительно быстры в каждой категории формы, кроме затененных заполненных дуг, но они не хуже, чем другие браузеры там.
  7. Chrome и Opera аварийно завершают работу, когда drawImage () рисует 1000 фигур, где shadowOffsetX или shadowOffsetY задается значение вне физического разрешения экрана.
  8. IE 11 и Edge медленнее рисуют дуги и градиенты, чем другие настольные браузеры.
  9. drawImage () медленно работает на мобильном Safari. На самом деле быстрее рисовать несколько градиентов и затененных дуг, чем рисовать одну копию много раз с помощью drawImage().
  10. рисование в Firefox чувствительность к предыдущим операциям: рисование теней и градиентов замедляет рисование дуг. Лучшее время показали.
  11. рисование в Mobile Safari чувствительно к предыдущим операциям: тени замедляют градиенты; Градиенты и дуги делают drawImage () еще медленнее, чем обычно. Лучшее время показали.

анализ

хотя функция shadowOffset является простым и визуально эффективным способом смешивания форм, это значительно медленнее, чем все остальные техники. Это ограничивает его полезность приложениями, которым нужно только нарисовать несколько теней, и которым не нужно рисовать много теней быстро и неоднократно. Кроме того, при ускоренном использовании drawImage (), давая shadowOffsetX или shadowOffsetY значение больше, чем около 3000, Chrome 48 и Opera 34 зависают почти на минуту, потребляя циклы процессора, а затем сбой моего драйвера дисплея nVidia, даже после обновления его до последней версии. (Поиск Google не найден отчеты об ошибках для Chromium, описывающие эту ошибку, когда большой shadowOffset и drawImage() используются вместе.)

для приложений, которым необходимо смешивать нечеткие фигуры, наиболее визуально похожий подход к теням-установить globalCompositeOperation на "светлее" и использовать drawImage() со значением globalAlpha для многократного рисования предварительно окрашенного радиального градиента или для рисования отдельных градиентов, если они должны быть разными цветами. Это не идеальное совпадение для перекрывающихся теней, но это близко и позволяет избежать выполнения вычислений на пиксель. (Однако обратите внимание, что в mobile Safari прямое рисование затененных заполненных дуг на самом деле быстрее, чем градиенты и drawImage().) Хотя установка globalCompositeOperation в "светлее" приводит к тому, что IE 11 и Edge примерно в 10 раз медленнее в рисовании дуг, использование радиального градиента по-прежнему быстрее, чем использование затененных заполненных дуг во всех основных настольных браузерах, и только в два раза медленнее, чем затененные заполненные дуги в мобильных Сафари.

вывод

Если ваша единственная целевая платформа-iPad / iPhone, самый быстрый метод для симпатичных смешанных фигур-это затененные заполненные дуги. В противном случае самый быстрый метод с сопоставимым внешним видом, который я нашел до сих пор, который работает во всех основных настольных браузерах, - это рисование радиальных градиентов с globalCompositeOperation, установленным на "легче", и управление непрозрачностью с globalAlpha.

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


эта скрипка http://jsfiddle.net/2qQLz/ является попыткой предоставить решение. Если он близок к тому, что вам нужно, его можно развивать дальше. Он ограничивает градиентную заливку ограничивающим прямоугольником, одна сторона которого является линией пересечения "кругов". Для двух "кругов" одинакового радиуса, лежащих вдоль горизонтальной линии, достаточно легко найти значение x точек пересечения "кругов" и нарисовать ограничивающие прямоугольники для градиентной заливки для каждого "круг".

Это было бы сложнее для двух произвольных "кругов", но линия пересечения все еще может быть найдена, и ограничивающий прямоугольник все еще может быть нарисован для каждого "круга". Она будет становиться все более сложной по мере добавления новых "кругов".


на композитный режимы - как вы их найдете. Насколько мне известно, вы не можете составить лучше, чем это, без ручного настройки пикселей. Если вы обновите свой вопрос с явным описанием того, как вы хотите смешать пиксели, я обновлю свой ответ соответственно.

Итак, каковы решения для каждого пикселя?

есть 2 основных пиксельных подхода, которые я могу видеть, которые начнут решать это проблема

  1. нарисуйте скрытый контекст и смешайте с ручной функцией

  2. написать функцию, которая вычисляет градиент вручную и применяет пользовательскую функцию смешивания.

ваш первый вариант более универсален, чем второй, в том смысле, что вы можете рисовать все, что вам нравится, используя обычные методы canvas, а затем смешать этот холст на видимом холсте. Увидеть @Phrogz контекст-blender проект для хорошей идеи о том, как смешать один контекст на другой

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

самая большая трудность в борьбе с Альфа-прозрачностью из-за того, что у вас может быть содержимое за радиальным градиентом. После того, как вы нарисовали поверх фонового изображения, почти невозможно увидеть, что это было до того, как вы нарисовали поверх это, если только вы не сохраните копию этого фона. Даже на основе каждого пикселя у вас есть трудность:смешивание изображения поверх другого изображения не будет работать.

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

в духе вариант 1, Вы можете создать пустой контекст и отобразить на нем несколько градиентов. Затем сделайте это сверху.

и в духе варианта 2, Если вы можете определить все из точек перед рендерингом вы можете рассчитать изображение и смешивание из этих точек за один проход.

объединение метода однопроходного рендеринга с фоновым контекстом позволит вам нарисовать контур поверх видимого холста без создания одного ручного пикселя читайте, только пиксель пишет.

нигде не так элегантно, как я знаю, но это может быть единственный реальный способ достичь того, что я бы назвал эффектом Альфа-смешанного контура на 2D-холсте.


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

if (src.a <= dst.a) {
    result = dst;
} else {
    result = src;
}