Android studio canvas bitmap

Класс Bitmap

Вам часто придётся иметь дело с изображениями котов, которые хранятся в файлах JPG, PNG, GIF. По сути, любое изображение, которое мы загружаем из графического файла, является набором цветных точек (пикселей). А информацию о каждой точке можно сохранить в битах. Отсюда и название — карта битов или по-буржуйски — bitmap. У нас иногда используется термин растр или растровое изображение. В Android есть специальный класс android.graphics.Bitmap для работы с подобными картинками.

Существуют готовые растровые изображения в файлах, о которых поговорим ниже. А чтобы создать с нуля объект Bitmap программным способом, нужно вызвать метод createBitmap():

В результате получится прямоугольник с заданными размерами в пикселях (первые два параметра). Третий параметр отвечает за информацию о прозрачности и качестве цвета (в конце статьи есть примеры).

Очень часто нужно знать размеры изображения. Чтобы узнать его ширину и высоту в пикселах, используйте соответствующие методы:

Bitmap.Config

Кроме размеров, желательно знать цветовую схему. У класса Bitmap есть метод getConfig(), который возвращает перечисление Bitmap.Config.

Всего существует несколько элементов перечисления.

  • Bitmap.Config ALPHA_8 — каждый пиксель содержит в себе информацию только о прозрачности, о цвете здесь ничего нет. Каждый пиксель требует 8 бит (1 байт) памяти.
  • Bitmap.Config ARGB_4444 — устаревшая конфигурация, начиная с API 13. Аналог ARGB_8888, только каждому ARGB-компоненту отведено не по 8, а по 4 бита. Соответственно пиксель весит 16 бит (2 байта). Рекомендуется использовать ARGB_8888
  • Bitmap.Config ARGB_8888 — на каждый из 4-х ARGB-компонентов пикселя (альфа, красный, зеленый, голубой) выделяется по 8 бит (1 байт). Каждый пиксель занимает 4 байта. Обладает наивысшим качеством для картинки.
  • Bitmap.Config RGB_565 — красному и и синему компоненту выделено по 5 бит (32 различных значений), а зелёному — шесть бит (64 возможных значений). Картинка с такой конфигурацией может иметь артефакты. Каждый пиксель будет занимать 16 бит или 2 байта. Конфигурация не хранит информацию о прозрачности. Можно использовать в тех случаях, когда рисунки не требуют прозрачности и высокого качества.

Конфигурация RGB_565 была очень популярна на старых устройствах. С увеличением памяти и мощности процессоров данная конфигурация теряет актуальность.

В большинстве случаев вы можете использовать ARGB_8888.

Получив объект в своё распоряжение, вы можете управлять каждой его точкой. Например, закрасить его синим цветом.

Чтобы закрасить отдельную точку, используйте метод setPixel() (парный ему метод getPixel позволит узнать информацию о точке). Закрасим красной точкой центр синего прямоугольника из предыдущего примера — имитация следа от лазерной указки. Котам понравится.

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

Созданный нами цветной прямоугольник и управление отдельными точками не позволят вам нарисовать фигуру, не говоря уже о полноценном рисунке. Класс Bitmap не имеет своих методов для рисования, для этого есть метод Canvas (Холст), на котором вы можете размещать объекты Bitmap.

Когда вы размещали в разметке активности компонент ImageView и присваивали атрибуту android:src ресурс из папок drawable-xxx, то система автоматически выводила изображение на экран.

Если нужно программно получить доступ к битовой карте (изображению) из ресурса, то используется такой код:

Обратный процес конвертации из Bitmap в Drawable:

Изображение можно сохранить, например, на SD-карту в виде файла (кусок кода):

Каждая точка изображения представлена в виде 4-байтного целого числа. Сначала идёт байт прозрачности — значение 0 соответствует полной прозрачности, а 255 говорит о полной непрозрачности. Промежуточные значения позволяют делать полупрозрачные изображения. Этим искусством в совершенстве владел чеширский кот, который умело управлял всеми точками своего тела и растворялся в пространстве, только улыбка кота долго ещё висела в воздухе (что-то я отвлёкся).

Следующие три байта отвечают за красный, зелёный и синий цвет, которые работают по такому же принципу. Т.е. значение 255 соответствует насыщенному красному цвету и т.д.

Так как любое изображение кота — это набор точек, то с помощью метода getPixels() мы можем получить массив этих точек, сделать с этой точкой что-нибудь нехорошее (поменять прозрачность или цвет), а потом с помощью родственного метода setPixels() записать новые данные обратно в изображение. Так можно перекрасить чёрного кота в белого и наоборот. Если вам нужна конкретная точка на изображении, то используйте методы getPixel()/setPixel(). Подобный подход используется во многих графических фильтрах. Учтите, что операция по замене каждой точки в большом изображении занимает много времени. Желательно проводить подобные операции в отдельном потоке.

Читайте также:  Android mobile gaming device

На этом базовая часть знакомства с битовой картой закончена. Теперь подробнее.

Учитывая ограниченные возможности памяти у мобильных устройств, следует быть осторожным при использовании объекта Bitmap во избежание утечки памяти. Не забывайте освобождать ресурсы при помощи метода recycle(), если вы в них не нуждаетесь. Например:

Почему это важно? Если не задумываться о ресурсах памяти, то можете получить ошибку OutOfMemoryError. На каждое приложение выделяется ограниченное количество памяти (heap size), разное в зависимости от устройства. Например, 16мб, 24мб и выше. Современные устройства как правило имеют 24мб и выше, однако это не так много, если ваше приложение злоупотребляет графическими файлами.

Bitmap на каждый пиксель тратит в общем случае 2 или 4 байта (зависит от битности изображения – 16 бит RGB_555 или 32 бита ARGB_888). Можно посчитать, сколько тратится ресурсов на Bitmap, содержащий изображение, снятое на 5-мегапиксельную камеру.

При соотношении сторон 4:3 получится изображение со сторонами 2583 х 1936. В конфигурации RGB_555 объект Bitmap займёт 2592 * 1936 * 2 = около 10Мб, а в ARGB_888 (режим по умолчанию) в 2 раза больше – чуть более 19Мб.

Во избежание проблем с памятью прибегают к помощи методов decodeXXX() класса BitmapFactory.

Если установить атрибут largeHeap в манифесте, то приложению будет выделен дополнительный блок памяти.

Ещё одна потенциальная проблема. У вас есть Bitmap и присвоили данный объект кому-то. Затем объект был удалён из памяти, а ссылка на него осталась. Получите крах приложения с ошибкой типа «Exception on Bitmap, throwIfRecycled».

Возможно, лучше сделать копию.

Получить Bitmap из ImageView

Если в ImageView имеется изображение, то получить Bitmap можно следующим образом:

Но с этим способом нужно быть осторожным. Например, если в ImageView используются элементы LayerDrawable, то возникнет ошибка. Можно попробовать такой вариант.

Более сложный вариант, но и более надёжный.

Изменение размеров — метод createScaledBitmap()

С помощью метода createScaledBitmap() можно изменить размер изображения.

Будем тренироваться на кошках. Добавим картинку в ресурсы (res/drawable). В разметку добавим два элемента ImageView

В последнем параметре у метода идёт булева переменная, отвечающая за сглаживание пикселей. Обычно его применяют, когда маленькое изображение увеличивают в размерах, чтобы улучшить качество картинки. При уменьшении, как правило, в этом нет такой необходимости.

Кадрирование — метод createBitmap()

Существует несколько перегруженных версий метода Bitmap.createBitmap(), с помощью которых можно скопировать участок изображения.

  • сreateBitmap(Bitmap source, int x, int y, int width, int height, Matrix m, boolean filter) — Returns an immutable bitmap from subset of the source bitmap, transformed by the optional matrix.
  • createBitmap(int width, int height, Bitmap.Config config) — Returns a mutable bitmap with the specified width and height.
  • createBitmap(Bitmap source, int x, int y, int width, int height) — Returns an immutable bitmap from the specified subset of the source bitmap.
  • createBitmap(int[] colors, int offset, int stride, int width, int height, Bitmap.Config config) — Returns a immutable bitmap with the specified width and height, with each pixel value set to the corresponding value in the colors array.
  • createBitmap(Bitmap src) — Returns an immutable bitmap from the source bitmap.
  • createBitmap(int[] colors, int width, int height, Bitmap.Config config) — Returns a immutable bitmap with the specified width and height, with each pixel value set to the corresponding value in the colors array.

Описываемый ниже код не является оптимальным и очень ресурсоёмкий. На больших изображениях код будет сильно тормозить. Приводится для ознакомления. Чтобы вывести часть картинки, можно сначала создать нужный Bitmap с заданными размерами, занести в массив каждый пиксель исходного изображения, а затем этот же массив вернуть обратно. Но, так как мы уже задали другие размеры, то часть пикселей не выведутся.

По аналогии мы можем вывести и нижнюю правую часть изображения:

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

Читайте также:  Антенны для андроид телефонов

Скриншот приводить не буду, проверьте самостоятельно.

Меняем цвета каждого пикселя

Через метод getPixels() мы можем получить массив всех пикселей растра, а затем в цикле заменить определённым образом цвета в пикселе и получить перекрашенную картинку. Для примера возьмем стандартный значок приложения, поместим его в ImageView, извлечём информацию из значка при помощи метода decodeResource(), применим собственные методы замены цвета и полученный результат поместим в другие ImageView:

Код для класса активности:

На скриншоте представлен оригинальный значок и три варианта замены цветов.

Ещё один пример, где также в цикле меняем цвет каждого пикселя Green->Blue, Red->Green, Blue->Red (добавьте на экран два ImageView):

Конвертируем Bitmap в байтовый массив и обратно

Сжимаем картинку

В предыдущем примере вызывался метод compress(). Несколько слов о нём. В первом аргументе передаём формат изображения, поддерживаются форматы JPEG, PNG, WEBP. Во втором аргументе указываем степень сжатия от 0 до 100, 0 — для получения малого размера файла, 100 — максимальное качество. Формат PNG не поддерживает сжатие с потерей качества и будет игнорировать данное значение. В третьем аргументе указываем файловый поток.

Как раскодировать Bitmap из Base64

Если изображение передаётся в текстовом виде через Base64-строку, то воспользуйтесь методом, позволяющим получить картинку из этой строки:

Вычисляем средние значения цветов

Дополнительные материалы

На StackOverFlow есть интересный пример программной генерации цветных квадратов с первой буквой слова. В пример квадрат используется как значок к приложению. Также популярен этот приём в списках. Квадраты также заменять кружочками.

Источник

Полный список

— читаем Bitmap
— выводим его на канву
— получаем информацию о нем

Начинаем тему Bitmap. Без нее в рисовании никуда, т.к. Bitmap – это объект, который хранит в себе изображение. Та же канва, с которой мы обычно работаем, это обертка, которая принимает команды от нас и рисует их на Bitmap, который мы видим в результате.

Мы рассмотрим все основные операции с Bitmap и обязательно разберем интересные материалы с официального сайта по этой теме.

В этом уроке начнем с основ. Посмотрим какие методы есть для создания Bitmap из файла, как вывести его на канву и какую инфу о себе может рассказать Bitmap.

Для получения изображения из файла используется фабрика BitmapFactory. У нее есть несколько decode* методов, которые принимают на вход массив байтов, путь к файлу, поток, файловый дескриптор или идентификатор ресурса. И на выходе мы получаем Bitmap.

Можно заметить, что все эти методы имеют также версии с использованием объекта BitmapFactory.Options. Это очень полезная штука, о ней мы отдельно еще поговорим в одном из следующих уроков.

Самые используемые из методов чтения это, конечно, следующие:

decodeFile(String pathName) – получить Bitmap из файла, указав его путь. Т.е. этим методом можем считать картинку с SD-карты. (getExternalStorageDirectory)

decodeResource(Resources res, int id) – получить Bitmap из drawable-ресурса, указав его ID. Этот метод вернет нам картинку из папок res/drawable нашего приложения.

Project name: P1571_BitmapRead
Build Target: Android 4.4
Application name: BitmapRead
Package name: ru.startandroid.develop.p1571bitmapread
Create Activity: MainActivity

В конструкторе DrawView мы получаем Bitmap из drawable-ресурса ic_launcher. На вход методу decodeResource мы передали объект ресурсов и ID требуемого ресурса.

Далее в переменную info сформируем строку с инфой о картинке:
getWidth – ширина картинки в px
getHeight – высота картинки в px
getByteCount – число байт, которое занимает картинка (доступен только с API Level 12)
getRowBytes – число байт в одной строке картинки
getConfig – инфа о способе хранения данных о пикселах

И выводим эту инфу в лог.

Настраиваем матрицу, которая повернет картинку на 45 градусов, растянет картинку в два раза в ширину и в три раза в высоту, и переместит ее на 200 вправо и 50 вниз.

Создаем два Rect объекта. rectSrc со сторонами равными половине сторон картинки. Т.е. этот прямоугольник охватывает левую верхнюю четверть картинки. Эту часть мы будем брать для вывода на экран далее в примере. А выводить мы ее будем в прямоугольник rectDst, это просто произвольная область на экране.

В методе onDraw рисуем картинку на канве тремя разными версиями метода drawBitmap. В первом случае просто выводим картинку как есть в точке (50,50). Во втором применяем матрицу, в которой мы уже настроили поворот, трансформацию и перемещение. И третий вариант возьмет от картинки часть, входящую в область rectSrc (мы там задали левую верхнюю четверть) и нарисует ее на канве в области rectDst, применив необходимые трансформации и перемещения.

Читайте также:  Фнаф сестер локейшен андроид

Слева-направо видим все три варианта вывода. В первом случае без изменений и в указанной точке. Во втором случае преобразования были описаны в матрице. В третьем случае мы отсекли от картинки часть и нарисовали ее в указанной области, канва сама при этом растянула изображение под размеры области.

Info: size = 48 x 48, bytes = 9216 (192), config = ARGB_8888

Размер картинки = 48 (ширина) на 48 (высота). У вас тут могут быть другие цифры, т.к. метод decodeResource учитывает density устройства и вытаскивает картинку из необходимой папки. В моем случае он взял ее из drawable-mdpi.

Далее мы вывели вес картинки в байтах — 9216, и кол-во байтов в одной строке — 192. Тут понятно, что вес картинки = кол-во байтов в строке * высоту = 192 * 48 = 9126.

А если мы разделим кол-во байтов строки на ширину, то получим сколько байтов занимает один пиксел: 192 / 48 = 4 байта.

Это же подтверждает и config = ARGB_8888. Это означает, что на каждый из 4-х ARGB-компонентов пиксела (альфа, красный, зеленый, голубой) выделяется по 8 бит (= 1 байт). Следовательно, пиксел будет весить 4 байта.

Кроме ARGB_8888 есть еще несколько конфигураций:

ALPHA_8 – пиксел содержит в себе инфу только о прозрачности, о цвете здесь инфы нет. Каждый пиксел требует 8 бит (1 байт).

ARGB_4444 — аналог ARGB_8888, только каждому ARGB-компоненту отведено не по 8, а по 4 бита. Соответственно пиксел весит 16 бит (2 байта). С API Level 13 эта конфигурация объявлена устаревшей.

RGB_565 – здесь нет инфы о прозрачности, а трем RGB-компонентам выделено, соответственно по 5,6 и 5 бит. Каждый пиксел будет весить 16 бит или 2 байта.

Все вышенаписанное — это достаточно важные вещи, которые надо понимать и учитывать в разработке. Если, например, ваше приложение работает с картинками, и вы точно знаете, что они будут без прозрачности, то лучше использовать RGB_565. Все ваши картинки в памяти займут в два раза меньше места, чем при использовании дефолтового ARGB_8888. При большом кол-ве картинок это существенная оптимизация!

На размер (а следовательно и вес) изображения также следует обращать внимание. Следите, чтобы ваши картинки в памяти не были размера больше, чем вам нужно. Приведу пример из практики. Как-то пришлось оптимизировать приложение, в котором был экран со списком юзеров с аватарками. Аватарки эти изначально грузились с сайта и кешировались на SD. Для списка использовался memory-кэш, но он переполнялся мгновенно и постоянно подчитывал инфу с SD.

Вскрытие показало, что с сайта картинки грузились в разрешении 200 х 200 и прямо так и сохранялись на SD. В memory-кэш они помещались в том же разрешении, занимая, соответственно, по 200 * 200 * 4 = 160 000 байт каждая! Т.е. 6 картинок в кэше и уже метр памяти занят. А список там на сотни позиций. Конечно, никакого кэша не хватит при скроллинге.

Глянули на layout строки списка. Каждый ImageView, который отображал аватарку, был размером всего 32х32 dp. Т.е. в случае mdpi нам требовалась картинка 32х32 пиксела. Т.е. 32 * 32 * 4 = 4096 байт. Получается, что вместо одной аватарки 200х200 в кэше свободно могли бы разместиться почти 40 аватарок 32х32.

В итоге, при чтении картинок с сайта и сохранении их на SD поставили сразу изменение размера до необходимого, и стало значительно лучше. Еще как вариант, у сайта сразу просить требуемый размер картинки.

В общем, старайтесь использовать минимально-требуемый вам формат и размер. А о том как выбрать формат, поменять размер и использовать кэши мы обязательно поговорим в следующих уроках.

На следующем уроке:

— создаем и меняем Bitmap
— разбираемся с density и mutable

Присоединяйтесь к нам в Telegram:

— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование

— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме

Источник

Оцените статью