Масштабирование Image в J2ME

Масштабирование Image в J2ME

Источник: MGDC
Автор: Arcadiy Gobuzov aka Ago

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

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

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

Test Criteria
Application appearance Verify that application fits appropriately with the device screensize.

Минимальным
размером экрана для J2ME устройств в MIDP1 указано разрешение 96 на 54
пикселя. В MIDP2 ограничение такое же. Верхнего предела не существует.
Теоретически, разрешение может быть и 640 на 480, и ещё больше. Любым.
В реальной жизни, существует несколько 'модных' размеров и в
большинстве случаев, размеры экрана j2me устройств отличаются в 1.5 - 2
раза, не более. Но, всё равно, если Вы хотите написать программу,
которая одинаково хорошо выглядит на Siemens M55 (101 x 80), Nokia 6610
(128 x 128), SE K700 (176 x 208) и т.д, Вам придётся предпринять много
усилий. Усилий не сложных, возможно и не сопоставимых с написанием
самого ядра, но кропотливых, долгих, точных. Причём, поработать
придётся всем: и художникам, добиваясь приемлемого качества графики, и
дизайнеру, и тестеру и главное программисту.

В тех приложениях,
где основным носителем информации является текст, не сложно
"подстроится" под различный экран. Существуют методы, изменяющие
размеры и атрибуты шрифта, подсчитывающие ширину той или иной строки
текста.
В приложениях, где основной упор сделан на графику (а это
99% игр), всё сложнее. Есть некоторый графический файл, мы можем
загрузить его в объект Image, мы можем узнать размеры экрана, можем
узнать размеры Image, можем нарисовать часть Image, но не существует
метода, масштабирующего Image. Такие методы существуют в API некоторых
производителей (например Nokia), но в стандартном MIDP1 такого метода
нет. К сожалению, не появился он и в MIDP2. Между тем, в стандартной
редакции J2SE такой метод существует начиная с JDK 1.1. Речь идёт о
методе

public Image getScaledInstance(int width, int height, int hints) класса java.awt.Image

Пользоваться им очень просто, достаточно написать что-то вроде:

Image newImage = oldImage.getScaledInstance(newWidth, newHeight, SCALE_SMOOTH); и мы получим Image нового размера

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

Но
повторяю, что удобно так делать НЕ В J2ME. В J2ME не доступен пакет
java.awt, и класс Image совсем другой: javax.microedition.lcdui.Image.
метода getScaledInstance здесь нет.

В MIDP1 мы вообще не имеем
доступа к отдельным точкам Image. И поэтому, девелоперам ничего не
остаётся, как создавать параллельно несколько проектов для различных
телефонов. Повторюсь, отличия проектов между собой, обусловлены только
размерами экрана. Ну, ещё возможно количеством цветов. Но с цветами
проще - здесь только несколько стандартов. В итоге, художник
масштабирует Images во внешней программе,типа Photoshop'a, дизайнер
пытается добиться приемлемого качества отображения карт и уровней,
программист следит за игровыми константами: если размеры спрайтов или
тайл различны, то должны поразному обрабатываться столкновения и
движения. Кроме того, именно программисту приходится следить за
директориями проектов: копировать файлы, редактировать настройки и т.д.
Это конечно отвлекает от программирования самой программы.

В
MIDP2 ситуация несколько улучшилась. Появилась возможность
масштабировать Images, хотя и не так удобно, как в J2SE. Возможно, в
MIDP3, если такой профиль когда-нибудь будет создан, будут методы,
аналогичные getScaledInstance, класса java.awt.Image. Но пока, это не произошло, я попытаюсь объяснить, как можно масштабировать Images, используя MIDP2.
Итак: согласно MIDP2 в класс Image было добавлено несколько новых методов:

  public void getRGB(int[] rgbData,
int offset,
int scanlength,
int x,
int y,
int width,
int height)

С
помощью этого метода можно скопировать пиксели Image (или его часть) в
некоторый массив целых чисел (rgbData). Причём каждый пиксель будет
храниться в 0xAARRGGBB формате - по одному байту на красную, зелёную и
синюю компоненты цвета. Так же один байт предназначен для хранения
значения ALPHA - прозрачность. Если ALPHA=0, то пиксель считается
полностью прозрачным, если ALPHA = 255, полностью НЕ прозрачным.

Вторым полезным методом является:

  public static Image createRGBImage(int[] rgb,
int width,
int height,
boolean processAlpha)

Этот метод создаёт новый Image из массива целых чисел (rgb). Здесь параметр processAlpha указывает: надо ли учитывать значения ALPHA. Если processAlpha = false, то создаётся полностью НЕ прозрачный Image, независимо от значений ALPHA.

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

1. Загружаем Image
2. "Грабим" значение его пикселей в массив.
3. Создаём новый массив,соответствующий Image-у с новыми размерами.
4. Переносим пиксели из старого массива в новый по некоторому алгоритму.
5. Создаём из нового массива новый Image.

Самым интересным представляется пункт 4. Существует много алгоритмов масштабирования Image. Желающие могут почитать на Demo.Design.FAQ
о некоторых из них. На мой взгляд, алгоритм для J2ME должен отвечать
3-ём, взаимоисключающим требованиям: быстрая скорость, отличное
качество и небольшой размер кода. К сожалению, нельзя скопировать
классы, или даже методы отдельных классов из java.awt.image.* Там
слишком много кода, участвующего в масштабировании. и если всё
включить, размер приложения увеличится в несколько раз.
Я буду
объяснять, в чём суть моего алгоритма на простом примере. Предположим,
у нас некоторая картинка шириной 5 пикселей, и высотой в 1 пиксель, мы
хотим так изменить её размеры, что-бы она стала шириной в 3 пикселя.
Для ещё большей простоты, предположим, что у каждой точки значения
компонент зелёного и синего цветов равны 0.
Пусть R1...R5 - значения
величин красного цвета в старом Image, а значения N1...N3 - значения
величин красного цвета в новом Image. Тогда N1...N3 будем получать как
усреднённое значение R1...R5 для точек стоящих рядом

Изображение N1 = (3*R1 + 2*R2) / 5
N2 = (R2 + 3*R3 + R4) / 5

N3 = (2*R4 + 3*R5) / 5

В более общем случае, мы 5 заменяем на количество точек в старом Image. И изменим коэффициенты в скобках.
Понятно, что в реальных Image необходимо выполнить эти вычисления и для синей и зелёной компоненты.
Так
же, Вам не часто придётся работать с Image высотой 1 пиксель, поэтому
необходимо сначала пройтись по каждой линии и усреднить значения всех
точек, а затем пройтись по всем столбцам.
Как выполнять
масштабирование Image в случае, если в новом Image размеры больше чем в
старом? Точно так-же, но в знаменателе дроби будет число точек нового
Image.

Код иллюстрирующий всё вышесказанное, я оформил не в виде
мидлета, а в виде Swing - приложения. Для того, чтобы было удобно
выбирать разные графические файлы, задавать размеры и сравнивать с
getScaledInstance. Так-же там расположены 2 варианта алгоритма: один
простой, но медленный, второй быстрый, но требует больше кода. Я
постарался написать читаемый код, с комментариями и "говорящими"
именами переменных. Так-же, тем, кто захочет применить его в своём
мидлете, необходимо будет заменить часть переноса Image в массив int[]
и часть, создания нового Image из int[].
В завершении я продемонстрирую работу алгоритма скриншотами:

big2small Слева располагается оригинальный Image размером 128 x 110
справа - новый, размером 101 x 80

Этот пример показывает масштабирование статичного Image c logo или splash - экраном

small2big Слева располагается оригинальный Image размером 101 x 80
справа - новый, размером 176 x 208

Этот пример также показывает масштабирование статичного Image c logo или splash - экраном, но теперь уже от меньшего к большему

resize Слева располагается оригинальный Image размером 88 x 32
справа - новый, размером 132 x 88

Этот пример показывает масштабирование Image, содержащего несколько спрайтов.

Так
же, необходимо помнить, что работая с ALPHA не нужно усреднять
значений. В случае logo и splash всё понятно - ALPHA = 255, Image НЕ
прозрачный. Когда масштабируем спрайт, то он может быть либо полностью
прозрачным, либо НЕ прозрачным.

Исходный текст алгоритма и Swing программа, иллюстрирующая его работу находится здесь (29 кб)

С автором статьи можно связаться по адресу:
j2me@pisem.NOSPAMnet

Этот e-mail защищен от спам-ботов. Для его просмотра в вашем браузере должна быть включена поддержка Java-script

(удалите "NOSPAM" в адресе).