- Полный список
- Создание на основе другого Bitmap
- Создание из массива цветов
- Создание чистого Bitmap
- Density
- Mutable
- Getting Started with Android Canvas Drawing 🖼
- Learn the basics of drawing with the Android Canvas Class
- What is a Canvas?
- Canvas Coordinate System
- How do I use a Canvas?
- Get access to a Canvas instance
- What can I draw on a Canvas? ✏️
- Paint 🎨
- Next up…
Полный список
— создаем и меняем 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. И начиная от него будут взяты цвета для первой строки, т.е. элементы 16, всего 100 элементов, т.к. ширина = 100). Для второй строки индекс первого элемента будет (2-1)*150 = 150. И для второй строки буду взяты цвета 208. И т.д.
В итоге мы получаем 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 для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник
Getting Started with Android Canvas Drawing 🖼
Learn the basics of drawing with the Android Canvas Class
Diving into using the Android Canvas class can unlock magical super powers you never knew you had 🤯. Imagine being able to draw anything* your heart desires just with some basic shapes, paths and bitmaps? Well, the Android Canvas gives you just that ability.
What is a Canvas?
Canvas is a class in Android that performs 2D drawing of different objects onto the screen. The saying “a blank canvas” is very similar to what a Canvas object is on Android. It is basically, an empty space to draw onto.
The Canvas class is not a new concept, this class is actually wrapping a SKCanvas under the hood. The SKCanvas comes from SKIA, which is a 2D Graphics Library that is used on many different platforms. SKIA is used on platforms such as Google Chrome, Firefox OS, Flutter, Fuschia etc. Once you understand how the Canvas works on Android, the same drawing concepts apply to many other different platforms.
Tip: Check out the SKIA source code for a deeper understanding of the Canvas implementation.
It is useful to know that SKIA is used in the underlying code for Android, so when you get stuck trying to understand how a certain API works, you can look at the source for SKIA to gain a deeper understanding.
Canvas Coordinate System
The coordinate system of the Android canvas starts in the top left corner, where [0,0] represents that point. The y axis is positive downwards, and x axis positive towards the right.
All elements drawn on a canvas are placed relative to the [0,0] point.
When working with the Canvas, you are working with px and not dp, so any methods such as translating, or resizing will be done in pixel sizes. This means you need to translate any dp values into px before calling any canvas operations. This will ensure that your drawing looks consistent across devices with different pixel densities.
Canvas draw commands will draw over previously drawn items. The last draw command will be the topmost item drawn onto your canvas. It is up to you to ensure that your items are laid out correctly (Alternatively, you might want to use some of the built-in layout mechanisms for this — such as LinearLayout ).
How do I use a Canvas?
To draw onto a canvas in Android, you will need four things:
- A bitmap or a view — to hold the pixels where the canvas will be drawn.
- Canvas — to run the drawing commands on.
- Drawing commands — to indicate to the canvas what to draw.
- Paint — to describe how to draw the commands.
Get access to a Canvas instance
In order to get access to a Canvas instance, you will need to create a class that extends from View . This will then allow you to override the onDraw method, which has a Canvas as a parameter.
You can then include this view inside your layout XML and this will then automatically invoke the onDraw method of the Custom View.
You can also get access to a Canvas object by programatically creating one in code, like this:
It’s worth noting at this point that any Canvas created programmatically without using a View , will be software rendered and not hardware rendered. This can affect the appearance of some of the drawing commands. For instance, some commands are just not supported with hardware rendering, or only supported from a certain API level. For more information about the differences between hardware rendering and software rendering, read this post.
What can I draw on a Canvas? ✏️
There are many different things you can draw onto a Canvas . One of the most common drawing operations is to draw a bitmap (image) onto the canvas. The method for doing this is just called drawBitmap and it takes in the bitmap object that is loaded up either with Android’s built-in mechanisms, or with Glide.
The second parameter here allows us to pass in the portion of the bitmap that you want to render, when passing in null the whole bitmap will be rendered. The third parameter is a RectF object which represents the scale and translation of the bitmap that you want to draw on screen.
Tip: Make sure your RectF object that you pass into the drawBitmap function is scaled with the correct aspect ratio otherwise your output may be stretched
You need to be careful with this, since it can quite easily stretch the bitmap, as the Canvas calls don’t take into account the aspect ratio of the provided image. You need to ensure the rect that is passed in is properly scaled. The fourth parameter is the paint object, we will cover the purpose of this parameter soon.
There are many other Canvas drawing methods that can give you some great looking views. We won’t be covering them all here, but here are two other examples of drawing methods on the Canvas class:
To draw a circle onto the view, give it a center point x,y , its size and a paint object:
Another one is the drawRect() method. This draws a rectangle on screen:
This is not the full list of drawing methods but just a small highlight of some of them to get you more comfortable with the concepts, feel free to browse the Canvas documentation for a comprehensive list of all of them.
Paint 🎨
In my opinion, the Paint class is possibly the most interesting graphics class and it is also my favourite, for that very reason. There is so much that a Paint object can do that can really make your drawing operations shine. ✨
The Paint class typically holds colour and style information. The Paint object is then used for drawing objects (i.e. bitmap, text, paths etc) onto a Canvas .
To create a Paint object:
This object should be created before using it in Canvas#onDraw() . It is not recommended to create it in onDraw() since you shouldn’t be doing object allocations in that method.
Tip: Use the isAntiAlias flag to ensure your drawing has smooth edges.
The isAntiAlias flag is quite an important one. If you are drawing objects to your canvas and you notice that the edges of your objects have jagged edges, it is likely that you haven’t set this flag to true. This flag indicates to the paint to smooth out the edges of the object you are drawing to the canvas.
The Paint class has more than just those three properties, there are many more things you can do with it. For instance, you can also set properties related to text rendering, such as the typeface , letterSpacing (kerning) and textSize .
Tip: Check that the Canvas/Paint APIs you are using work across different API versions. See this site for more information.
It is worth noting that the Paint#setShadowlayer() method doesn’t work consistently across API levels and drawing commands. It works when drawing text on a Canvas, but applying the shadow to other commands such as drawBitmap doesn’t yield the same results across API levels.
The reason for the inconsistency between API levels is because the Canvas APIs are bundled with the Android Platform and therefore are not updated until the OS is updated. See the list on this page for more information about which APIs work on which Android versions.
Once you’ve created the Paint object, pass that object into your Canvas#draw*() calls and the drawing will then take on the properties you’ve specified in the paint.
Next up…
In the next few articles, we will be diving into other parts of working with Canvas and drawing on Android. Be sure to subscribe to updates and follow me on Twitter for more tips.
If you prefer watching this content — be sure to check out my talk from Mobile Matters London for a summary of this content.
Источник