Подтвердить что ты не робот

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

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

  • У меня есть радиальный градиент от черного (альфа 0,5) до прозрачного радиуса 40 пикселей. центр радиального градиента равен x = 50, y = 50
  • У меня есть другой радиальный градиент от черного (альфа 0,5) до прозрачного радиуса 40 пикселей. центр радиального градиента находится при x = 80, y = 50

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

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

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

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

  • рисовать первый градиент
  • использовать составной пункт назначения-назначения
  • рисовать второй градиент → налагает перекрывающуюся область с первого градиента
  • использовать sourceOperation с исходным кодом
  • снова нарисуйте второй градиент

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

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

4b9b3361

Ответ 1

Ohhhhhh. У меня есть кое-что для вас здесь. Это действительно дурацкое, но оно делает то, что вы хотите, не прибегая к использованию imageData.

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

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

Подробнее об этом можно узнать .

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

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

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

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

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

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

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

enter image description here

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

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

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

Ответ 2

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

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

Поэтому я решил провести анализ. Я написал некоторый базовый JavaScript (модифицированная версия которого можно увидеть на https://jsfiddle.net/Flatfingers/4vd22rgg/), чтобы нарисовать 2000 копий каждого из пяти разных типов фигур на не -пересекающиеся секции холста 1250x600, записывающие прошедшее время для каждой из этих пяти операций в последних версиях пяти основных настольных браузеров плюс мобильный Safari. (Извините, 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

НАБЛЮДЕНИЯ

  • Среди заполненных форм заполненные прямоугольники - это самая быстрая операция во всех проверенных браузерах и средах.
  • Заполненные полные дуги (круги) примерно в 10 раз медленнее в IE 11 и Edge, чем заполненные прямоугольники, по сравнению с примерно 3,5 раза медленнее в других основных браузерах.
  • Градиенты примерно в 3 раза медленнее, чем прямые в IE 11, Chrome 48 и Opera 34, но в Firefox Firefox замечательны в 100 раз медленнее (см. Отчет Bugzilla 728453).
  • Изображения через drawImage() примерно в 1,5 раза быстрее, чем заполненные прямоугольники во всех настольных браузерах.
  • Затененные заполненные дуги самые медленные: от 50x медленнее, чем заполненные прямоугольники в IE, Edge, Chrome и Opera до 100 раз медленнее в Firefox.
  • Chrome 48 и Opera 34 являются очень быстрыми в каждой категории, кроме затененных заполненных дуг, но они не хуже других браузеров.
  • Ошибка Chrome и Opera, когда drawImage() рисует 1000 фигур, где либо shadowOffsetX, либо shadowOffsetY присваивается значение вне физического разрешения экрана.
  • IE 11 и Edge медленнее рисуют дуги и градиенты, чем другие браузеры на рабочем столе.
  • drawImage() работает медленно на мобильном Safari. На самом деле быстрее нарисовать несколько градиентов и затененных дуг, чем рисовать одну копию многократно с помощью drawImage().
  • Рисунок в Firefox чувствителен к предыдущим операциям: рисование теней и градиентов делает рисование дуг медленнее. Отображаются самые быстрые времена.
  • Рисование в Mobile Safari чувствительно к предыдущим операциям: тени делают градиенты медленнее; градиенты и дуги делают drawImage() еще медленнее, чем обычно. Отображаются самые быстрые времена.

АНАЛИЗ

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

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

Заключение

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

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

Ответ 3

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

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

Ответ 4

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

Итак, что такое пиксельные решения?

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

  • Нарисуйте скрытый контекст и совместите с ручной функцией

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

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

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

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

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

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

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

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

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


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

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