Довольно часто при написании проектов на 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.