|
22 декабря 2009 - Отрисовка SVG в J2ME без использования JSR 226 API.
Отрисовка SVG без
использования JSR 226 API.
Довольно часто при написании проектов на Java2ME
возникает
необходимость в рисовании несложной векторной графики - это может быть
логотип или график, а может быть и полноценный оконный
интерфейс.
При этом у программиста всего три доступных пути:
использовать
JSR 226 и резко ограничить количество моделей телефонов на которых
приложение будет запускаться, рисовать графику вручную прописывая
координаты в процедурах рисования и убив на это огромное количество
времени или написать простенький векторный редактор никак не
конкурирующий с большими системами. Существует и четвертый путь -
написать конвертер из любого доступного векторного формата, при этом
удобно использовать свободный кроссплатформенный формат - например тот
же SVG. Изнутри SVG представляет подмножество XML форматов и
является полностью текстовым. Формат достаточно сложный, поэтому на
написание конвертера у меня ушло почти две недели. Результатом является
консольная утилита InkToDraw.exe преобразовывающая исходный SVG файл в
максимально простой формат и набор процедур написанных на
Java2ME для отрисовки графики на мобильном устройстве. Конвертер
писался под Windows в среде Code::Block и дополнительно компилировался
под Linux ASP 9.2 в среде Anjuta, соответственно работать он будет и
под Windows и под Linux, такая вот кроссплатформа.
Все
необходимое для работы лежит в архиве.
Формат
используемый на мобильном устройстве привязан к особенностям вывода в
MIDP 2.0, для хранения координат используется один байт, что дает
максимальный размер рисунка 256 на 256 точек, перейти на 16 бит не
проблема - но при этом размер исходного блока данных увеличится в два
раза, что было признано нецелесообразным. В общем виде формат
состоит из байта типа и набора координат для этого типа. Так
как
возможности отрисовки привязаны к Java 2 ME спецификация
формата содержит Линию, Прямоугольник, Эллипс/Дугу и Треугольник.
Линия имеет два подвида: линия от точки к точке и полилиния когда
результирующая ломаная описывается координатами точек. Кривые Безье
апроксимируются 10 отрезками. Спецификация
формата:
| Вид |
Код |
Описание |
 |
100 |
100,3,X,Y,X1,Y1,X2,Y2,X3,Y3
(Ломаная линия, второй
параметр – количество сегментов, далее идут координаты начала и
координаты вершин) |
 |
101 |
101,X1,Y1,X2,Y2,X3,Y3,X4,Y4
(Кривая Безье, 4 пары координат полностью описывают кривую, первая и
последняя пара - координаты начала и конца кривой) |
 |
102 |
102,3,X1,Y1,X2,Y2,X3,Y3,X4,Y4,X12,Y12,X13,Y13,X14,Y14,X22,Y22,X23,Y23,X24,Y24 (Кривая
состоящая из нескольких кривых Безье. Второй параметр -
количество кривых, далее
идут координаты начала и координаты вершин) |
 |
103 |
103,X1,Y1,W,H,A1,A2 (Контур
эллипса или дуги, X1,Y1
- координаты левого верхнего угла прямоугольника в который вписан
эллипс, W,H
- ширина
и высота эллипса/дуги, A1,A2
стартовый и конечный угол для дуги деленный пополам, чтобы уложиться в
байт) |
 |
104 |
104,X1,Y1,W,H,A1,A2 (Залитый
эллипс или сектор, X1,Y1
- координаты левого верхнего угла прямоугольника в который вписан
эллипс, W,H
- ширина
и высота эллипса/дуги, A1,A2
стартовый и конечный угол для дуги деленный пополам, чтобы уложиться в
байт) |
 |
105 |
105,X1,Y1,W,H,A1,A2 (Залитый
эллипс или сектор, X1,Y1
- координаты левого верхнего угла прямоугольника в который вписан
эллипс, W,H
- ширина
и высота эллипса/дуги, A1,A2
стартовый и конечный угол для дуги деленный пополам, чтобы уложиться в
байт) |
 |
106 |
106,X1,Y1,W,H (Контур
прямоугольника, X1,Y1
- координаты левого верхнего угла, W,H
- ширина
и высота) |
 |
107 |
107,X1,Y1,W,H (Залитый
прямоугольник с обводкой, X1,Y1
- координаты левого верхнего угла, W,H
- ширина
и высота) |
 |
108 |
108,X1,Y1,W,H (Залитый
прямоугольник, X1,Y1
- координаты левого верхнего угла, W,H
- ширина
и высота) |
 |
109 |
109,X1,Y1,W,H,RW,RH (Контур
скругленного прямоугольника, X1,Y1
- координаты левого верхнего угла, W,H
- ширина
и высота, RW,RH - горизонтальный и вертикальный диаметр
дуги скругления) |
 |
110 |
110,X1,Y1,W,H,RW,RH (Залитый скругленный
прямоугольник с контуром, X1,Y1
- координаты левого верхнего угла, W,H
- ширина
и высота, RW,RH - горизонтальный и вертикальный диаметр
дуги скругления) |
 |
111 |
111,X1,Y1,W,H,RW,RH (Залитый скругленный
прямоугольник без контура, X1,Y1
- координаты левого верхнего угла, W,H
- ширина
и высота, RW,RH - горизонтальный и вертикальный диаметр
дуги скругления) |
 |
112 |
112,X1,Y1,X2,Y2,X3,Y3 (Контур
треугольника, X1,Y1,X2,Y2,X3,Y3 - координаты трех вершин) |
 |
113 |
113,X1,Y1,X2,Y2,X3,Y3 (Залитый треугольник
с контуром, X1,Y1,X2,Y2,X3,Y3 - координаты трех вершин) |
 |
114 |
114,X1,Y1,X2,Y2,X3,Y3 (Залитый треугольник
без контура, X1,Y1,X2,Y2,X3,Y3 - координаты трех вершин) |
 |
115 |
115,R,G,B (Цвет
заливки, используется для всех залитых объектов, R,G,B -
компоненты цвета: красный, зеленый, синий) |
 |
116 |
116,R,G,B (Цвет
рисования линий и контуров, R,G,B -
компоненты цвета: красный, зеленый, синий) |
 |
117 |
117,X1,Y1,X2,Y2
(Прямая, координаты задают начальную и конечную точку) |
 |
118 |
118
(После данного маркера все линии контура и кривые рисуются сплошной
линией) |
 |
119 |
119
(После данного маркера все линии контура и кривые рисуются пунктирной
линией) |
|
255 |
255
(Маркер конца блока данных, после него рисование блока данных
прекращается) |
Для
конвертирования под Windows используется утилита InkToDraw.exe, под
Linux соответственно inktodraw. Формат командной строки у них
идентичен. В общем виде коммандная строка состоит из трех аргументов -
исходного файла в формате SVG, файла в который будет записан результат
и опции. Из доступных опций только одна -show, вывод расширенной
информации о конвертировании.
Windows: InkToDraw.exe file.svg file.dat
-options Linux: inktodraw file.svg file.dat
-options
Если
задан только один файл то вывод будет идти в InkDrawing.dat, а если
утилита запущена без параметров будет осуществлена попытка открыть файл
ScreenTest.svg и результат будет сохранен в InkDrawing.dat. К примеру,
для конвертирования, мною была нарисована вот такая вот тыква.

Для
того чтобы конвертировать ее в понятный обработчику формат
достаточно
положить файл ScreenTest.svg в одну папку с конвертером и отдать
команду:
Windows: InkToDraw.exe ScreenTest.svg
Linux: inktodraw ScreenTest.svg
В
результате в этой же папке появится файл InkDrawing.dat размером 453
байта, исходный файл при этом занимает почти 18 килобайт,
первые
преимущества конвертера налицо ;). Теперь наша задача воспроизвести
полученную картинку на мобильном устройстве. Для нетерпеливых
в
папке Jar есть два готовых примера: DrawTest.jar и
DrawTest2.jar,
друг от друга они отличаются только движком рендеринга. Для
более
терпеливых читателей приготовлены проекты NetBean в папке NetBean и
исходный текст без ресурсов в папке Source. Для тех кто будет собирать
пакет руками достаточно создать новый проект, вставить в него исходный
текст из файлов DrawTest.java или DrawTest2.java и забросить результат
конвертации в папку res проекта. После сборки и запуска на экране
телефона отобразится все та же тыква, после нажатия на клавишу пять
апплет завершит свою работу, а любая другая клавиша отобразит ту же
тыкву
через другую процедуру вывода. Вариантов всего три: вывод без масштаба
от левого верхнего угла, вывод с произвольным смещением и
вывод со
смещением и масштабом.
Как
видим из сриншотов получилось довольно симпатично. Теперь перейдем к
процедурам осуществляющим вывод на экран в J2ME. В принципе
зная
формат каждый может написать что-то идеально отвечающее его
требованиям, я же просто предлагаю несколько возможных обработчиков
для конвертированных векторных данных. Доступны следующие
процедуры:
| InkDrawD |
Рисуем от левого верхнего угла экрана,
без масштабирования |
| InkDrawN |
Рисуем со сдвигом, без масштаба |
| InkDrawS |
Рисуем со сдвигом и масштабом |
Параметрами
для вывода является массив с данными m[], графический контекст
g,
смещение по x и y - dx,dy и масштабный коэффициент
заданный в
виде отношений двух пар целых чисел sx/mx и sy/my. К примеру
для
уменьшения картинки в два раза
нужно задать sx=1,mx=2,sy=1,my=2 (1/2 и 1/2). Для
рисования
кривых Безье используется отдельная процедура drawBesier(),
для
преобразования байта в целое - функция i(). Процедуры
восстанавливают значение текущего цвета и штриховки, так что в
программе об этом беспокоиться не стоит, вывод происходит поверх уже
существующего изображения. Теперь перейдем к вопросу почему в архиве
приведено два практически идентичных примера. Отличаются они только
способом хранения данных. В первом варианте массив данных хранится в
виде байтов, но медленнее обрабатывается из за конвертации в целое
каждого значения, во втором данные хранятся в виде целого (short) и
занимают в два раза больше памяти, но читаются без
преобразований.
Варианты выбираем исходя из того что нам надо: экономить скорость
вывода или память?
При
использовании данных процедур в своих проектах необходимо помнить о том
что InkDrawD(), InkDrawN(), InkDrawS()
независимы друг от друга, поэтому если вам нужен только вывод в
фиксированном разрешении достаточно включить в проект
только InkDrawD, при этом за собой она потянет процедуру
рисования
кривых Безье drawBesier(), а если используется хранение данных
в
байтовом массиве то и функцию
i(). В остальном никаких ограничений не накладывается. Если известно
что рисунок будет состоять только из линий то из обработчика можно
будет выкинуть все, кроме линий и цвета, что ускорит вывод и уменьшит
размер кода. В общем простор для модификации достаточен. Код использует
только одну возможность MIDP 2.0 - залитые треугольники, если их не
использовать работать будет на любом устройстве с J2ME.
Теперь
перейдем к особенностям рисования графики для конвертирования.
Используем для этого Inkspace, размер нового листа указываем не больше
255 на 255 точек. Рисовать желательно с привязкой к пиксельной сетке,
за границы рисунка не выходить, особенно при рисовании кривых Безье.
Для рисования доступны все инструменты за исключением заливки,
которую можно имитировать рисуя залитые фигуры -
овал/треугольник/прямоугольник. При использовании текста не забывайте
его конвертировать в кривые. Все нарисованные линии рисуются толщиной в
один пиксель (толщина не учитывается). Если нарисовать замкнутую кривую
с заливкой то после конвертации она будет в виде контура.
Кривые содержащие больше 255 линий - отбрасываются.
Недоступны градиенты и растровые эффекты, реализовать их на мобильном
устройстве с нормальной скоростью весьма проблематично. Конвертер не
поддерживает глобальные преобразование - поэтому не стоит ему
подсовывать уменьшенную картинку из клипарта - все равно качественнее
будет нарисовать или обвести готовый рисунок.
Область
применения данного конвертера достаточно широка - от
текстово-графических игр до создания масштабируемых интерфейсов.
Основное преимущество - возможность нарисовать картинку в произвольном
разрешении. При необходимости движок вывода легко портируется на любую
старую платформу и может быть использован на том же ZX-SPECTRUM или
Commodore 64. Было бы желание.
Ограничения и неточности в текущей версии:
- Залитые сектора с контуром рисуются без линий
ограничивающих сектор;
- Эллипсы и сектора нельзя поворачивать;
Что хотелось бы доработать в следующей версии (TO DO LIST ;):
- Триангуляцию залитых кривых для вывода их треугольниками;
- Рисование линиями разной толщины;
- Рисование залитых кривых с помощью овалов или секторов;
- Вывод текста векторным шрифтом;
Все необходимые упомянутые в статье файлы находятся здесь.
Автор: Shadowsshot. Почта:
shadwork(a)ukr.net.
|
|
|