div.main {margin-left: 20pt; margin-right: 20pt}
Растровые изображения в Java.
Вначале о модели растровых изображений в java. В java
используется следующая модель представления растрового изображения:
производитель - потребитель - наблюдатель и класс утилит Image. Производитель
реализует интерфейс ImageProducer, потребитель - ImageConsumer, а наблюдатель -
ImageObserver из пакета java.awt.image. Иллюстрацией работы этой модели может
стать процесс загрузки изображения из сети. Вначале продюсер читает размеры
картинки и методом void setDimension(int width, int height) передаёт потребителю
размеры картинки, потом, в случае gif файла, читает таблицу цветов изображения,
производитель создаёт новую таблицу цветов, роль которой выполняет
IndexColorModel и методом setColorModel пересылает её потребителю. В случае jpeg
файла производитель пересылает DirectColorModel потребителю тем же методом.
После этого производитель информирует потребителя методом setHints, в каком
порядке он будет выдавать информацию, собственно кодирующую изображение. Этот
порядок может быть следующим: выдавать картинку горизонтальными линиями, в
произвольном порядке, одним "куском", покадрово (в случае наличия анимации),
одиночными точками или постепенно в порядке сверху-вниз, справа-налево. После
установки порядка выдачи, производитель начинает методом setPixels заполнять
буфер потребителя. Соответственно, в зависимости от выбора передачи изображения,
это происходит мгновенно (как в случае MemoryImageSource - одного из классов,
реализующего интерфейс ImageProducer) или постепенно, по мере получения
информации из потока и раскодирования её, как это получается при загрузке
картинки из jpeg или gif файла из сети или с диска. По завершении передачи
данных, производитель вызывает метод ImageComplete. Если конец передачи данных
произошел из-за ошибки, этот метод вызывается с флагом ImageConsumer.IMAGEERROR,
если из-за отмены пересылки, то с флагом ImageConsumer.IMAGEABORT. При
пофреймовой загрузки анимированного файла, по окончанию загрузки каждого фрейма
тоже вызывается метод ImageComplete, но с флагом ImageConsumer.SINGLEFRAMEDONE.
По полному завершению процесса без ошибок, выбрасывается флаг
ImageConsumer.STATICIMAGEDONE.
Для полного счастья программиста в java имеется вышеупомянутый
третий интерфейс - ImageObserver. Он применяется для третьей заинтересованной
стороны. Например, его реализует java.awt.Component и его наследники.
Соответственно при вызове команды Image.getHeight(this) из наследников
Component, в метод передаётся ссылка на класс, реализующего интерфейс
наблюдателя. Поэтому, если нужна частая работа с картинками через класс Image
или Graphics, а класс не является наследником Component, то в определении класса
достаточно указать implements ImageObserver, в заголовке класса импортировать
java.awt.image.* и написать метод boolean imageUpdate(Image img, int infoflags,
x, y, width, height). Этот метод будет вызываться каждый раз, когда будут
производится действия над картинкой, если в методах вызывающих это действие есть
упоминание этого класса.
Подробнее в этом интерфейсе поможет разобраться документация для
java, а так как у меня не стоит задача повторять её, то описывать флаги
переменной infoflags я не буду, скажу лишь вкратце, что метод imageUpdate вполне
может заменить класс MediaTracker, поскольку MediaTracker тоже реализует
интерфейс ImageObserver и использует imageUpdate для задач по загрузке
изображений. Также полезно знать, что класс PixelGrabber реализует интерфейс
ImageConsumer, и если хочется написать его замену, то можно воспользоваться
RGBImageFilter, реализующим тот же интерфейс, чтобы получить BITMAP "снимок"
картинки.
Кстати, о фильтрах - все фильтры реализуют интерфейс ImageConsumer,
но при этом важно знать, что фильтр имеет потоковое вычисление и нужно дождаться
команды ImageComplete или воспользоваться MediaTracker, чтобы получить полностью
обработанную картинку, а не обработанный её кусок с флагом
ImageConsumer.IMAGEABORT.
Полезным знанием о фильтрах (как они устанавливаются через
ImageFilteredSourse, я писать не буду - это детально описано в документации к
java) также является установка и снятие в RGBImageFilter флажка обработки
ColorModel. Дело в том, что при применении IndexColorModel невозможно
использование всей ARGB палитры и для обработки каждого пикселя именно в ARGB
режиме в этом классе нужно установить boolean canFilterIndexColorModel как
false.
Полезными фильтрами являются CropImageFilter - он вырезает
определённую область в картинке, ReplicateScaleFilter и
AreaAverangingScaleFilter - эти фильтры осуществляют сжатие/растягивание
картинки, причём второй делает "гладкое" растягивание. Все фильтры определены в
пакете java.awt.image и доступны через класс Image - каждый раз, когда нужно
вырезать кусок из картинки, растянуть/сжать со сглаживанием или без, Image
вызывает именно эти фильтры, поэтому для скорости иногда можно вызвать их
самостоятельно.
Теперь о кодировании компонент Alpha Red Green Blue в Int значении
пикселя. Int значения этих компонентов можно получить с помощью операции
сдвига/отсечения:
Если с - это цвет в int представлении, то a = (c &
0xff000000)>>24; r = (c & 0xff0000)>>16; g = (c &
0xff00)>>8; b = (c & 0xff); , где a, r, g и b соответственно
значения компонентов от 0 до 255.
Обратную трансформацию можно получить командой с = ((a << 24)
| ((r << 16) | ((g << 8) | b))); Интересно, что массив, получаемый с
помощью PixelGrabber, без установки ColorModel, содержит пиксели, записанные
именно в таком формате, чем и можно воспользоваться. Также не стоит забывать,
что значение 0 для alpha означает непрозрачность, а 255 - абсолютную
прозрачность.
Теперь о ColorModel. Этот класс является совершенно
бесполезным в случае преобразования картинки, но совершенно незаменимым, если
нужно установить только цвета CMYK или использовать другую специальную не RGB
палитру. Набор ColorModel достаточно подробно описан в документации, поэтому я
опущу его описание.
В заключении о том, что я здесь не упомянул: я не рассказал о
способах вывода картинки в Graphics и о рисовании Graphics в картинку. Не
упомянул о новых классах, вошедших в пакет Image в java2 - это RenderableImage и
несколько интерфейсов работы с ними, поскольку весьма удачный интерфейс работы с
ними есть у Graphics2, а ускорять работу смысла нету - это достаточно быстрые
классы, более чем на половину реализованные через машинные коды JVM. Так же не
стал касаться проблем сериализации изображений и записи их в файл в известных
форматах - последняя проблема решается пакетом JIMI, который можно найти через
сайт java.sun.com, введя JIMI в строку поиска.
И пара советов по использованию класса Image.
Если картинка загрузилась из сети, нужно её обязательно
закэшировать, то есть получить BITMAP массив этой картинки через класс
PixelGrabber и создать из этого массива MemoryImageSource, из которого потом
можно создать новую картинку. Особенно это касается тех программ, в которых
потом собираются использовать фильтры, растягивать/сжимать картинку и так
далее.
Примерный код процесса загрузки/кэширования для аплета я привожу здесь:
MediaTracker mt = new MediaTracker(this); img_in =
getImage("http://somehost/someimage"); mt.addImage(img_in,
0); try{mt.waitForID(0);} catch(Exception
e){} mt.removeImage(img_in); int w = img_in.getWidth(this), h =
img_in.getHeight(this), img_g[] = new int[w*h+1]; PixelGrabber pg = new
PixelGrabber(img_in, 0, 0, w, h, img_g, 0,
w); try{pg.grabPixels();} catch(Exception e){} img_in = createImage(new
MemoryImageSource(w, h, img_g, 0, w));
Обратите внимание на частоту this - эта переменная везде, кроме
конструктора MediaTracker, указывает на аплет, как на класс с интерфейсом
ImageObserver. Аплет наследует этот интерфейс у Component.
И второй совет - если вам потребовалось использовать гладкое
расширение/сжатие, причём сжатие идёт после расширения картинки, то используйте
метод Image.getScaledInstance c флагом hints = Image.SCALE_AREA_AVERAGING - это
улучшит качество результата.
Ну вот, пожалуй, и всё...
|