- Класс Bitmap
- Bitmap.Config
- Получить Bitmap из ImageView
- Изменение размеров — метод createScaledBitmap()
- Кадрирование — метод createBitmap()
- Меняем цвета каждого пикселя
- Конвертируем Bitmap в байтовый массив и обратно
- Сжимаем картинку
- Как раскодировать Bitmap из Base64
- Вычисляем средние значения цветов
- Дополнительные материалы
- Полный список
- Создание на основе другого Bitmap
- Создание из массива цветов
- Создание чистого Bitmap
- Density
- Mutable
Класс 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(). Подобный подход используется во многих графических фильтрах. Учтите, что операция по замене каждой точки в большом изображении занимает много времени. Желательно проводить подобные операции в отдельном потоке.
На этом базовая часть знакомства с битовой картой закончена. Теперь подробнее.
Учитывая ограниченные возможности памяти у мобильных устройств, следует быть осторожным при использовании объекта 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
— разбираемся с density и mutable
В прошлом уроке мы научились читать Bitmap из файла. Сейчас рассмотрим способы его создания. Для этого есть несколько статических методов createBitmap. Все эти методы создания Bitmap можно разделить на три группы:
1) создание на основе другого Bitmap
2) создание из массива цветов
3) создание пустого Bitmap
Рассмотрим эти группы подробно.
Создание на основе другого Bitmap
Эти методы позволяют нам взять готовый Bitmap и скопировать его целиком или частично в новый bitmap. При этом поддерживаются преобразования с помощью матрицы.
В этой группе 4 метода.
параметры:
source – Bitmap-источник, от которого будем брать его часть
x,y – координаты точки, от которой будем отсчитывать часть
width, height – высота и ширина части
m – матрица для применения преобразований к взятой части
filter – если true, то будет включено сглаживание при scale- и rotate-преобразованиях матрицы (но это приведет к некоторой потере в производительности)
Этот метод возьмет от Bitmap-источника часть указанную координатами и размерами, применит к ней матрицу и фильтр и вернет нам как новый Bitmap. (Хотя, вовсе не обязательно это будет новый объект. Об этом подробнее в конце урока.)
Аналогичен методу 1, но без использования матрицы и фильтра. Т.е. просто возьмет указанную часть от Bitmap-источника.
Вызывает метод 2 с параметрами createBitmap(src, 0, 0, src.getWidth(), src.getHeight()). Т.е. указанная часть равна всему Bitmap.
В итоге мы получим тот же Bitmap, что и источник.
Вызывает метод 2 с параметрами createBitmap(src, 0, 0, src.getWidth(),src.getHeight(), m, filter). Т.е. указанная часть равна всему Bitmap. Но дополнительно применяется матрица m, где рассчитаны преобразования, чтобы новый Bitmap получился размерами dstWidth на dstHeight. Также, можно включить сглаживающий фильтр.
В итоге мы получим тот же Bitmap, что и источник, но он будет нужных нам размеров, заданных параметрами dstWidth и dstHeight.
Рассмотрим первый метод этой группы на примере.
Project name: P1581_BitmapCreate
Build Target: Android 4.4
Application name: BitmapCreate
Package name: ru.startandroid.develop.p1581bitmapcreate
Create Activity: MainActivity
В конструкторе DrawView создаем bitmapSource из стандартной иконки ic_launcher. Настраиваем матрицу на поворот и изменение размера. И создаем bitmap на основе bitmapSource. Берем кусок начиная с точки (0,0) размерами в половину ширины и половину высоты. Т.е. получается левая верхняя четверть изображения. Система возьмет эту часть, применит к ней матрицу и фильтр, и выдаст нам, как новый Bitmap.
В onDraw отображаем полученный Bitmap.
А так будет выглядеть полученный Bitmap если фильтр выключить
Создание из массива цветов
Эти методы позволяют нам создать Bitmap из готового массива цветов.
В этой группе 4 метода.
параметры:
display – объект DisplayMetrics, из которого Bitmap возьмет значение densityDpi (зачем он нужен, рассмотрим чуть позже)
colors – массив цветов, т.е. фактически массив пикселов из которых будет состоять созданный Bitmap
offset – отступ от начала colors при чтении его значений
stride – шаг, который будет использован для перемещения по массиву при смене строки Bitmap
width, height – размеры создаваемого Bitmap
config – конфигурация (используемый способ хранения данных о пикселах, мы их рассмотрели подробно на прошлом уроке)
Все параметры в целом понятны. Объясню немного подробнее про stride, насколько мне удалось его понять. Рассмотрим пример, где параметр offset будем считать равным 0, ширину bitmap = 100, stride = 150.
Система создает Bitmap и заполняет его цветами из массива построчно. Но элементы массива она берет не все подряд, а высчитывает индекс первого элемента для каждой новой строки по формуле:
индекс первого элемента для каждой строки = (номер строки – 1) * stride
Т.е для первой строки – индекс первого элемента будет (1-1)*150 = 0. И начиная от него будут взяты цвета для первой строки, т.е. элементы 48, всего 100 элементов, т.к. ширина = 100). Для второй строки индекс первого элемента будет (2-1)*150 = 150. И для второй строки буду взяты цвета 238. И т.д.
В итоге мы получаем Bitmap для которого сами указали значения цветов всех его пикселов.
Вызывает метод 1 с параметрами createBitmap(display, colors, 0, width, width, height, config). Т.е. offset = 0, а stride = ширине Bitmap (цвета для строк будут браться из массива последовательно, без пропусков при переходе на новую строку).
Аналогичен методу 1, но без использования display.
Аналогичен методу 2, но без использования display.
Рассмотрим второй метод этой группы на примере. Перепишем класс DrawView:
В конструкторе DrawView создаем массив цветов количеством 300 * 300 = 90 000 элементов. Число выбрано такое, т.к. картинку мы будем создавать с шириной и высотой 300. Соответственно и цветов (читай пикселов) нам надо будет 300 * 300.
Заполняем первую треть массива полупрозрачным красным цветом, вторую – зеленым, третью – синим.
Создаем bitmap, используя массив цветов и указав: ширину 300, высоту 300, конфигурацию — RGB_565.
Аналогично создаем bitmapAlpha, но конфигурацию укажем ARGB_8888.
В onDraw выводим оба Bitmap-а на канву.
Bitmap-ы получились в целом одинаковые, но первый проигнорил прозрачность красного цвета. Это произошло из-за того, что мы указали ему конфиг RGB_565. Он не поддерживает прозрачность и, при создании Bitmap, аlpha-компонент был отброшен. Сохранилась информация только о цвете. Зато этот Bitmap в два раза меньше весит в памяти.
Создание чистого Bitmap
Эти методы позволяют создать чистый Bitmap без каких-либо данных.
В этой группе 2 метода.
параметры:
display – объект DisplayMetrics, из которого Bitmap возьмет значение densityDpi (зачем он нужен, рассмотрим чуть позже)
width, height – размеры создаваемого Bitmap
config – конфигурация (используемый способ хранения данных о пикселах, мы их рассмотрели подробно на прошлом уроке)
Создается чистый Bitmap с указанными характеристиками.
Аналогичен методу 1, но без использования display
Рассмотрим второй метод этой группы на примере. Перепишем класс DrawView:
В конструкторе DrawView создаем чистый Bitmap размером 100х100.
Далее попробуем сами в нем что-нить нарисовать.
Методом setPixel ставим в нем три пиксела (20,20), (70,50) и (30,80) в красный цвет.
Методом setPixels можем менять пикселы не по одному, а массивом. Этот метод аналогичен методам создания Bitmap из массива цветов. Первые три параметра – это массив, offset и stride. Затем идут координаты точки, с которой начнем закраску (40,40), затем размер закрашиваемой области (10,15).
Как вы помните, канва – это всего лишь оболочка для Bitmap. Соответственно и наш Bitmap мы можем обернуть в канву и что-нить на нем с помощью этой канвы нарисовать. В нашем примере оборачиваем свежесозданный Bitmap в канву и рисуем на нем синий круг.
В onDraw выводим результат на экран.
Видим все фигуры, которые мы выводили.
Также, Bitmap имеет методы:
getPixel – получить значение цвета определенного пиксела
getPixels – получить значения цветов набора пикселов
getGenerationId – получить generationId, который меняется каждый раз, когда меняется Bitmap. Т.е. с его помощью можно отслеживать, что Bitmap был изменен.
Density
У Bitmap есть метод setDensity, туда мы можем передать density, к которому должна относиться эта картинка. Зачем это нужно я не очень понимаю, т.к. у нас один экран и density всегда, вроде как, один и тот же. Но штука интересная, рассмотрим на примере как оно работает и на что влияет.
В манифест для Activity добавьте строку:
В конструкторе DrawView создаем три Bitmap размерами 100х100. В bitmapIcon читаем иконку ic_launcher (она размером 48х48 при mdpi). Далее с помощью канвы рисуем иконку на все три Bitmap-а, но с небольшими нюансами.
В первом случае просто рисуем.
Во втором случае рисуем, а затем для bitmap2 ставим density в xhigh.
В третьем случае для bitmap3 ставим density в xhigh, а затем рисуем иконку.
В onDraw выводим все три Bitmap-а и для наглядности рисуем рамки размером 100х100 там же.
У меня на эмуляторе экран с density = mdpi. Если у вас density другое, то и результат будет другим.
Разбираемся, почему так получилось.
С первым случаем все понятно. Нарисовали иконку 48х48 и вывели на канву Bitmap размером 100х100.
Во втором случае мы нарисовали иконку на Bitmap, затем поменяли у него density на xhdpi, а затем уже вывели его на канву. Умная канва при этом спросила у Bitmap каков его density (xhdpi) и сравнила со своим (mdpi). Увидев, что Bitmap имеет density в два раза больший (xhdpi = 2 * mdpi) чем нужно, канва просто уменьшила в два раза стороны Bitmap (т.е. он стал 50х50, это видно на скрине) при выводе и тем самым компенсировала разницу в density.
В третьем случае мы сначала поменяли density на xhdpi, затем обернули его в канву (чтобы отличать от основной канвы, которая в onDraw, назовем ее bm3-канва). Эта bm3-канва автоматически подхватила density = xhdpi от bitmap3. Затем мы рисуем на bitmap3 с помощью bm3-канвы иконку. При этом bm3-канва определила, что иконка имеет density = mdpi (mdpi, т.к. это density устройства и оно всем ставится по умолчанию). В итоге получается, что density иконки в два раза меньше, чем density канвы. И канва, чтобы выровнять density просто увеличивает размер иконки при выводе. Т.к. иконка становится размером 96х96, и занимает почти весь Bitmap, который 100х100. Далее bitmap3 выводится на канву и здесь повторяются все те же рассуждения, что были во втором случае и bitmap в итоге выводится уменьшенным в два раза.
Основная мысль тут такова: канва сравнивает density – свой и у Bitmap, который она собирается рисовать. И если они различны, то выполняется подгонка размеров.
Если убрать из манифеста опцию hardwareAccelerated, то основная канва перестанет подгонять размер Bitmap. Почему так происходит, я не знаю.
Mutable
Свойство mutable означает, что Bitmap может быть изменен (через обертку канвой, методом setPixel, и т.п.). Соответственно immutable означает, что Bitmap не может быть изменен, т.е. он readOnly.
Из рассмотренных нами трех групп методов, третья группа возвращает mutable Bitmap, вторая – immutable Bitmap. А первая группа зависит от параметров и Bitmap-источника:
— если источник immutable и новый Bitmap будет являться точной копией исходника, то мы получим на выходе просто Bitmap-исходник. Т.е. это будет тот же Bitmap объект, и даже не его копия. И он останется immutable.
— если же источник mutable или новый Bitmap чем-то отличен от исходника, то мы получаем новый mutable Bitmap объект
Для определения mutable состояния у Bitmap используется метод isMutable.
На следующем уроке:
— разбираемся с BitmapFactory.Options
— сохраняем Bitmap в файл
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник