JavaFX в действии

Разбираемся с эффектами

В прошлый раз мы начали разговор о применении различных эффектов в RIA-приложениях, создаваемых на этой платформе. Сегодня продолжим сей интересный и полезный разговор.

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


Игра с цветом

Любой дизайнер может рассказать много интересного о цветах и оттенках, а также об их важности. В свете этого было бы естественно предположить наличие в арсенале JavaFX специального эффекта, который позволял бы всерьёз работать с цветами и их оттенками. И действительно, такой эффект есть - называется он ColorAdjust.

Как написано в справке по JavaFX в разделе, касающемся этого эффекта, "an effect that allows for per-pixel adjustments of hue, saturation, brightness, and contrast" - то есть, этот эффект позволяет попиксельно настраивать оттенок, насыщенность, яркость и контрастность изображения. Собственно, основные параметры этого эффекта я уже перечислил - кроме них, есть ещё стандартный input, о котором вы знаете из первой части нашего разговора об эффектах для платформы JavaFX.

Что касается самих параметров, то hue изменяется от -1.0 до 1.0 и по умолчанию равен нулю, что соответствует неизменённому оттенку изображения. Между двумя крайними значениями мы получаем разные оттенки, которые имеет смысл подбирать только экспериментально. Параметр saturation изменяется в том же диапазоне и имеет точно такое же значение по умолчанию. Здесь значение -1 соответствует полностью обесцвеченному изображению ("чёрно-белой фотографии"), а значение +1 вряд ли понадобится, потому что с ним практически любая картинка начинает выглядеть просто дико. Яркость (brightness) изменяется в тех же самых диапазонах и по умолчанию также имеет нулевое значение. Экстремальные значения диапазона соответствуют абсолютно чёрному (минимум) и абсолютно белому (максимум) изображению. Параметр contrast единственный выпадает из стандартных для этого эффекта диапазонов: он изменяется от 0.25 до 4.00. Здесь про предельные значения что-то сказать достаточно сложно - лучше, как и в случае с hue, попробовать самостоятельно и подобрать оптимальное соотношение экспериментально.


Румянец

Именно так "Мультилекс" перевёл название эффекта Bloom, что, в общем-то, вполне отражает его суть. Этот эффект придаёт виртуальному изображению некоторое сходство с настоящей фотографией, эмулируя действие солнечного света на некоторые части изображения, на которых становятся практически полностью неразличимыми мелкие детали. В чём-то этот эффект похож на Glow, но тот действует, я бы сказал, несколько грубее из-за равномерности подсветки. В общем, чтобы представить себе в точности действие эффекта Bloom на изображение, нужно написать собственный небольшой пример, в котором этот эффект будет работать. Мы же здесь пока поговорим о его параметрах.

Вернее, даже не о параметрах, а о параметре - один из двух, input, мы по традиции опустим. Второй же называется threshold, варьируется от нуля до единицы, по умолчанию имеет значение 0.3 и очень сильно отличается от параметра level "родственного" эффекта glow. Чем именно отличается? Тем, что максимальное свечение у Bloom'а будет не при 1.0, как можно было бы подумать, а при 0.0. К этому, наверное, будет достаточно непросто привыкнуть, и сложно сказать, почему разработчики JavaFX сделали именно так (спасибо, хоть не назвали параметр level'ом, а то путаницы было бы) - впрочем, нас никто не спрашивал, а потому будем приспосабливаться.


Смещаемся!

Сейчас мы с вами попробуем разобраться с достаточно комплексным и сложным эффектом, имеющим название DisplacementMap. Этот эффект модифицирует форму изображения. Причём в документации по JavaFX даже приводится формула, по которой мы получаем результирующее изображение:

dst[x,y] = src[(x,y) + (offset + scale * map[x,y]) * (srcw, srch)]

Здесь, соответственно, dst и src - это пикселы в результирующем и в исходном изображениях, offset - смещение, scale - масштабирующий множитель, map - специальный параметр, задающий форму, а srcw и srch - это, соответственно, ширина и высота исходного изображения. Всё сложно? На самом деле, как только вы попробуете применять эффект DisplacementMap на практике, то поймёте, что сложным казалось его абстрактное описание на бумаге, на самом же деле применять этот эффект не так уж и сложно.

Итак, что же за параметры ожидают нас, так сказать, под его маской? Первым, как обычно, идёт input. Затем - mapData, параметр типа FloatMap. Это достаточно важный параметр, который заполняется с помощью функций setSamples или setSample, которые имеют много разных параметров с разными их комбинациями. Наиболее общей является следующая конструкция:

public setSample(x: Integer, y: Integer, band: Integer, s: Number) : Void

Здесь x и y - координаты, band - номер "ленты" (от 1 до 4), s - номер, служащий образцом. В DisplacementMap используются только первые две "ленты" FloatMap'а, поэтому имеет смысл вместо этого воспользоваться следующей функцией:

public setSamples(x: Integer, y: Integer, s0: Number, s1: Number) : Void

Здесь s0 и s1 - образцы для первой и второй ленты FloatMap'а, соответственно.

У вас может возникнуть вполне резонный вопрос: как же этот FloatMap всё-таки работает? Что ж, вопрос хороший. Давайте представим, что мы хотим сделать некоторое изображение волнистым - говоря языком преобразований, с помощью DisplacementMap мы запишем во FloatMap синусоиду. Причём, скажем, волна у нас будет идти вдоль оси X. Тогда мы должны использовать вызов следующего вида:

map.setSamples(x, y, 0.0, Math.sin(i*Math.PI/4))

Здесь, конечно же, map - это экземпляр класса FloatMap, который мы готовим для нашего с вами эффекта. FloatMap удобно заполнять с помощью цикла, где x и y (лучше, конечно, было бы назвать их при таком раскладе, соответственно i и j) будут циклическими переменными.

Интересный момент: значения, которые входят в mapData, задают смещение картинки относительно её первоначального положения в долях размеров этой самой картинки. Говоря по-русски, если мы заполним всю mapData значениями, равными 0.5, то изображение сместится на половину своей величины вверх и влево.

Думаю, ещё подробнее останавливаться на особенностях заполнения экземпляра FloatMap для эффекта DisplacementMap вряд ли стоит. Лучше вернёмся к самому эффекту - ведь мы разобрали буквально один параметр, совершенно забыв про все остальные.

Вторым (вернее, третьим, если считать вместе с input'ом) параметром рассматриваемого эффекта является offsetX. Если вы ещё не забыли приведённую выше формулу, то помните, что там был параметр, задающий смещение. Вот этот самый параметр и задаёт смещение - правильно, вы угадали, по оси X. Есть и другой, ему аналогичный, только работающий для оси Y, называется он, конечно же, offsetY. По умолчанию смещение равно нулю. В отличие от смещения, задаваемого mapData, здесь оно действует на все части изображения равномерно.

Следующие два параметра называются scaleX и scaleY, они задают, соответственно, масштабирование по каждой из осей. По умолчанию масштабирующий множитель для каждой из осей равен единице, что соответствует отсутствию масштабирования.

Последним по списку идёт булевский параметр wrap. Он определяет, обрезаются ли края, если в результате всех трансформаций вылезает за края отведённой ему области. По умолчанию не обрезаются.

DisplacementMap можно назвать даже в некотором смысле подлым эффектом, потому что есть одна оговорка, которая несколько ограничивает область его применения. Дело в том, что в результате трансформаций форма и размеры объекта могут меняться, и в то же время иногда нам нужно знать, где пользователь, скажем, кликнул мышью по объекту. Или мы можем применять знания о размерах в каких-то методах. Так вот, этот эффект не адаптирует координаты для методов или событий в соответствии с новыми жизненными реалиями, то есть координаты, попросту говоря, "плывут". Как написано в документации "the results of mouse picking and the containment methods are undefined when a Node has a DisplacementMap effect in place". То есть, никто вам не может сказать, какие координаты вы получите, если попытаетесь как-то обрабатывать события, связанные с координатами, для объекта, содержащего эффект DisplacementMap.

Что ж, на этой радостной ноте разговор о DisplacementMap и закончим.

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

Вадим СТАНКЕВИЧ,
dreamdrusch@tut.by

Версия для печатиВерсия для печати

Номер: 

28 за 2009 год

Рубрика: 

Software
Заметили ошибку? Выделите ее мышкой и нажмите Ctrl+Enter!