asd asdsa f
Статьи

Как сделать круглой картинку или фото в Android

0 1030

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

Уже достаточно давно появился тренд делать скругленные фотографии пользователей. С примерами такого дизайна можно познакомиться на многих популярных сайтах (например. google+). Как добиться такого эффекта на Android рассматривается далее в этой статье.

Использование FrameLayout

FrameLayout используется как контейнер для единственного View. Если добавить более одного view, то они буду располагаться один поверх другого слоями. Поэтому, если верхний слой будет иметь прозрачные участки, то через них будет виден слой, который располагается ниже. Именно эта особенность используется для обрезки изображения для получения формы круга.

Сначала необходимо подготовить изображение-маску в формате png. Если фон, на котором будет располагаться пиктограмма, белый, то маска должна выглядеть таким образом (серый цвет обозначает прозрачные пиксели):

Screenshot at Jan 14 14-05-12

Потребуется как минимум четыре варианта этого изображения для разных плотностей пикселей.

Если радиус полученной пиктограммы должен быть 50dp, то изображения для разных плотностей экранов должны быть таких размеров:

Разрешение Размер, px
ldpi 37,5 (~38)
mdpi 50
hdpi 75
xhdpi 100

Если ресурс называется ic_mask.png, то xml-разметка будет иметь вид:

	<FrameLayout
		android:id="@+id/imgContainer1"
		android:layout_width="@dimen/imagesize"
		android:layout_height="@dimen/imagesize"
		android:layout_alignParentLeft="true"
		android:layout_alignParentTop="true" >
		<ImageView
			android:id="@+id/image1"
			android:layout_width="match_parent"
			android:layout_height="match_parent" />
		<ImageView
			android:layout_width="match_parent"
			android:layout_height="match_parent"
			android:src="@drawable/ic_mask" />
	</FrameLayout>

Изображение, которое нужно обрезать, содержится в ImageView  c  id=@+id/image1
Перекрывающая его маска  — во втором ImageView.

Этот способ работает прекрасно во многих случаях, но несет накладные расходы в связи с необходимостью дополнительной xml-разметки.

Использование clipPath

Данный метод основан на использовании метода clipPath объекта холста (Canvas). Класс холста (Canvas) содержит методы, которые помогают манипулировать битами на растровом изображении (Bitmap).

Все, что необходимо сделать, это задать путь (Path) в виде окружности и добавить этот путь к текущей обрезке (clip) на холсте.

Метод, который выполняет данную операцию, может выглядеть таким образом:

	public static Bitmap getCircleMaskedBitmapUsingClip(Bitmap source, int radius)
	{
		if (source == null)
		{
			return null;
		}

		int diam = radius << 1;

		Bitmap scaledBitmap = scaleTo(source, diam);

		final Path path = new Path();
		path.addCircle(radius, radius, radius, Path.Direction.CCW);

		Bitmap targetBitmap = Bitmap.createBitmap(diam, diam, Bitmap.Config.ARGB_8888);
		Canvas canvas = new Canvas(targetBitmap);

		canvas.clipPath(path);
		Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);

		canvas.drawBitmap(scaledBitmap, 0, 0, paint);

		return targetBitmap;
	}

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

Далее, создается объект пути (строка 12). Затем на него добавляется непосредственно путь в виде окружности.

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

Затем растр передается в качестве параметра в конструктор холста (строки 15-16 ).

Далее необходимо добавить обрезку, которая будет ограничивать область рисования на холсте (строка 18).

Последний шаг — непосредственно рисование на холсте (строка 21).

Данный метод имеет минимум два недостатка. Во-первых, clipPath не работает на аппаратно ускоренной графике (практически на любом современном устройстве). То есть, придется отключать ускорение для каждой View, на который необходимо использовать clip:

imageView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

Во вторых, обрезка, как правило, имеет хорошо заметные рваные края. Чтобы убедиться в этом, запустите проект из примера к статье. Можно частично решить эту проблему путем наложения дополнительной границы с размытыми краями, но это уже будет излишеством (тем более, есть более эффективные способы получения круглых ихображений — см. ниже).

Шейдеры

В контексте графики для Android шейдеры — это объекты, которые являются источниками цветовой информации для всех объектов, которые могут быть нарисованы с помощью холста (кроме растровых объектов, конечно, т.к. использование пикселей из шейдеров для них не имеет никакого смысла).

В Android, чтобы задать цвет графических примитивов, используется объект Paint. Прежде чем вызвать метод рисования линии или окружности, нужно установить соответствующие параметры в Paint и передать его параметром методу рисования (drawCircle, drawRect и т.д.)

В отличие от установки одного цвета для всех пикселей получившейся фигуры, шейдер позволяет установить цвет каждого пикселя по отдельности.

Сначала необходимо подготовить шейдер — используя градиент или готовую картинку. Затем, нужно установить шейдер в Paint методом setShader. Теперь, при рисовании круга, цвет каждого пикселя фигуры будет взят из пикселя с той же координатой из шейдера.

Именно эти действия и предпринимаются в примере ниже:

	public static Bitmap getCircleMaskedBitmapUsingShader(Bitmap source, int radius)
	{
		if (source == null)
		{
			return null;
		}

		int diam = radius << 1;

		Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);

		Bitmap scaledBitmap = scaleTo(source, diam);
		final Shader shader = new BitmapShader(scaledBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
		paint.setShader(shader);

		Bitmap targetBitmap = Bitmap.createBitmap(diam, diam, Bitmap.Config.ARGB_8888);
		Canvas canvas = new Canvas(targetBitmap);

		canvas.drawCircle(radius, radius, radius, paint);

		return targetBitmap;
	}

Этот способ является во многом предпочтительнее первых двух. Во-первых, он не требует никакой дополнительной xml-разметки, во-вторых, сохраняется прозрачность вне границ круга.

Такими же преимуществами обладает и последний, рассмотренный здесь способ — PorterDuff.

Комбинирование по Портеру и Даффу

Данный способ основан на применении специального режима комбинирования двух изображений.
В 1984 году два инженера, Томас Портер и Тоб Дафф, предложили двенадцать режимов наложения изображений для получения различных эффектов.
Эти режимы — не более чем простые алгебраические преобразования над значениями цветов пикселей и их прозрачностями.
Все доступные режимы описаны в Android API. Например, режим SRC_IN описывается таким преобразованием:
[Sa * Da, Sc * Da],
где Sa и Sс — соответственно, прозрачность и вектор с компонентами цветовой информации первого изображения, Da — прозрачность пикселей второго изображения.
То есть, данный режим позволяет создать маску из второго изображения, задав прозрачность нужных пикселей. Например, если сделать полностью непрозрачными только пиксели внутри круга, то именно эти пиксели и войдут в полученное в результате преобразования изображение.

Пример ниже демонстрирует использование этого подхода:

	public static Bitmap getCircleMaskedBitmapUsingPorterDuff(Bitmap source, int radius)
	{
		if (source == null)
		{
			return null;
		}

		int diam = radius << 1;
		Bitmap scaledBitmap = scaleTo(source, diam);

		Bitmap targetBitmap = Bitmap.createBitmap(diam, diam, Bitmap.Config.ARGB_8888);

		Canvas canvas = new Canvas(targetBitmap);

		final int color = 0xff424242;
		final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
		final Rect rect = new Rect(0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight());

		canvas.drawARGB(0, 0, 0, 0);
		paint.setColor(color);

		canvas.drawCircle(radius, radius, radius, paint);
		paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
		canvas.drawBitmap(scaledBitmap, rect, rect, paint);
		return targetBitmap;
	}

Ключевой тут является строка 23, где, с помощью объекта Paint задается режим преобразования пикселей с помощью метода setXfermode.

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

Заключение

Результат применения описанных в статье методов выглядит следующим образом:

Screenshot at Jan 16 17-48-47Чтобы запустить проект на своем компьютере, нужно клонировать его из репозитория.

P.S.

Оставляйте в комментариях ссылки на изветные вам методы преобразования формы изображений, не описанные в статье.

About the author / 

admin