Pull to refresh

Практический опыт работы с Bitmap средствами Android

Reading time4 min
Views33K

Не так давно по долгу службы я столкнулся с одной задачей: нужно было придумать и реализовать дизайн медиа-плеера для Android. И если продумать и организовать более или менее сносное размещение элементов управления и информации оказалось делом не хитрым, то чтобы привнести в дизайн какую-то изюминку, пришлось хорошенько подумать. К счастью, в запасе у меня был такой элемент, как картинка с обложкой альбома проигрываемой мелодии. Именно он должен был добавить красок всей картинке.
Однако, будучи просто выведенной среди кнопок и надписей, обложка выглядела бумажным стикером, наклеенным на экран. Я понял, что без обработки изображения здесь не обойтись.

Некоторые раздумья насчёт того, что можно было бы тут придумать увенчались решением сделать для изображения обложки эффект отражения и тени. Сразу оговорюсь, что практическая реализация отражения не является моей оригинальной идеей. Её я подсмотрел в найденной англоязычной статье. В настоящем посте я лишь хочу привести некоторое осмысление производимых над изображением манипуляций, свои дополнения к процессу обработки и отметить некоторые нюансы работы с Bitmap в Android.
Итак, на входе я имел Bitmap с картинкой. Для начала я создал пустой Bitmap размером с оригинальную картинку, который позже должен был стать той же обложкой, но с нанесённой на неё тенью.
width = sourceBitmap.getWidth();
height = sourceBitmap.getHeight();

Bitmap gradCover;
try {
    gradCover = Bitmap.createBitmap(width, height, config);
} catch (OutOfMemoryError e) {
    gradCover = null;
}

Этот метод создаёт изменяемый (что важно) Bitmap.
Здесь обязательно нужно отметить, что при работе с Bitmap'ами необходимо внимательно следить за памятью. Придётся ловить исключения. И ещё один момент: изучение профайлером показало, что перед вызовом метода createBitmap() работает сборщик мусора. Учтите это, если в вашем приложении скорость работы критична.

Далее я создал холст и нанёс на него исходное изображение.
Canvas canvas = new Canvas(gradCover);
canvas.drawBitmap(sourceBitmap, 0, 0, null);

//Free up memory, associated with the original bitmap
sourceBitmap.recycle();

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

После всей подготовки я нанёс на холст краску с тенью.
Paint paint = new Paint();

int[] colors = {0xffffffff, 0xffffffff, 0xb0c0c0c0, 0xb0c0c0c0};
float radius = (((float) width * (float) width) / 4 + (float) height * (float) height) / ((float) width);
int fullRadius = (int) (1.92 * radius);
float[] positions = {0, 0.499f, 0.511f, 1};
RadialGradient shader = new RadialGradient((width - radius), 0, fullRadius, colors, positions, Shader.TileMode.CLAMP);
paint.setShader(shader);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawRect(0, 0, width, height, paint);

int[] linColors = {0x00ffffff, 0xffffffff, 0xff000000, 0x00000000};
float[] linPositions = {0, 0.03f, 0.97f, 1};
LinearGradient vertical = new LinearGradient(0, 0, 0, height, linColors, linPositions, Shader.TileMode.CLAMP);
paint.setShader(vertical);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawRect(0, 0, width, height, paint);
LinearGradient horizontal = new LinearGradient(0, 0, width, 0, linColors, linPositions, Shader.TileMode.CLAMP);
paint.setShader(horizontal);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawRect(0, 0, width, height, paint);

RadialGradient в моём случае представляет тень, падающую по полукругу из правого верхнего угла изображения в центр нижней грани. Ему нужно установить центр (может выходить за пределы картинки), радиус, последовательность цветов и расстояния от центра по радиусу для каждого цвета. Для тени использовалось изменение альфы в цветах на радиусе.
LinearGradient использовался для фэйда краёв картинки. Его применение очень похоже на RadialGradient. Нужно задать начало и конец линии, вдоль которой пойдёт градиент, цвета и их позиции на этой линии.

Наконец, я приступил к рисованию отражения. К этому моменту у меня уже был Bitmap с нанесёнными тенями gradBitmap. Опять надо было создавать холст, создавать пустое изображение (на этот раз на треть длиннее оригинального), помещать его на холст и наносить на верх него Bitmap с тенями.
Bitmap refCover = Bitmap.createBitmap(width, height + height / 3, Bitmap.Config.ARGB_8888);
Canvas refCanvas = new Canvas(refCover);
refCanvas.drawBitmap(gradCover, 0, 0, null);


После недолгих приготовлений начиналось самое интересное. Я создал матрицу, переворачивающую изображение снизу вверх. С её помощью создал Bitmap из трети исходного и нанёс его на холст под оригинальным изображением.
Matrix matrix = new Matrix();
matrix.preScale(1, -1);

Bitmap reflectionImage = Bitmap.createBitmap(gradCover, 0, 2 * height / 3, width, height / 3, matrix, false);
refCanvas.drawBitmap(reflectionImage, 0, height, null);
reflectionImage.recycle();

Кстати, краткое замечание: в классе Bitmap существует несколько методов createBitmap, и лишь один из них создаёт изменяемые Bitmap'ы, на которых можно рисовать. Остальные для рисования НА них не годятся.

И наконец, нанесение прозрачного градиента для придания эффекта отражения.
Paint refPaint = new Paint();
LinearGradient refShader = new LinearGradient(0, height, 0, height + height / 3, 0xc0ffffff, 0x00ffffff, Shader.TileMode.CLAMP);
refPaint.setShader(refShader);
refPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
refCanvas.drawRect(0, height, width, refCover.getHeight() + height, refPaint);

Краска наносится на ту часть рисунка, которая является отражением.

Всё. Я получил refCover — Bitmap, на котором изображена обложка альбома с тенью, сглаженными краями и отражением.

P.S. В данной статье для меня был важен не сам факт достижения визуальных эффектов, а способы их получения и нюансы, с ними связанные. Если для кого-то вещи, описанные здесь, очевидны — прекрасно. Всем остальным, я надеюсь, статья поможет в написании своих приложений под Android.

UPD: картинки ДО и ПОСЛЕ
Tags:
Hubs:
+36
Comments13

Articles