정상에서 IT를 외치다

[Android, Glide] Glide BitmapTransform 파헤쳐 보자! 본문

안드로이드

[Android, Glide] Glide BitmapTransform 파헤쳐 보자!

Black-Jin 2019. 3. 26. 16:04
반응형

안녕하세요. 블랙진입니다.


안드로이드 캔버스 기본예제

비트맵에 대한 탐구_이미지 자르기

모서리가 둥근 비트맵 만들기


3가지 포스팅을 하면서 캔버스와 비트맵 사용법에 대해 알아봤습니다. 이번에는 이 모든걸 종합하여 Glide bitmapTransform  파헤처 보겠습니다.


1. Glide?


Glide is a fast and efficient open source media management and image loading framework for Android that wraps media decoding, memory and disk caching, and resource pooling into a simple and easy to use interface.


간단히 말해 이미지 로딩 라이브러리 입니다. 오픈소스로 빠르고 효과적으로 이미지를 로딩 할 수 있습니다. 현재는 v4 버전까지 나왔지만 저는 v3 버전으로 작업을 했습니다.


App-build.gradle 의 라이브러리를 추가해 주세요

//glide
def glideVersion = '3.7.0'
implementation "com.github.bumptech.glide:glide:$glideVersion"


2. Glide Transformation?


Transformations in Glide take a resource, mutate it, and return the mutated resource. Typically transformations are used to crop, or apply filters to Bitmaps, but they can also be used to transform animated GIFs, or even custom resource types.


Glide 로 받아온 이미지를 CenterCrop, FitCenter, CircleCrop 와 같은 기능을 사용해 변환해 줍니다. 물론 커스터마이징하여 원하는대로 이미지를 변경할 수 있고 이번 포스팅의 내용입니다.



3. Customzing BitmapTransformation


예제 파일


  출처 : 구글


 출처 : 구글


가로가 긴 이미지와 세로가 긴 이미지를 준비했습니다.


<사용할 커트터마이징 방식>


1. ImageView 크기에 맞춰 마블 이미지를 비율에 맞게 채워줍니다.

2. 채운 이미지의 모서리를 라운드 처리해줍니다.


그럼 Glide 에서 받아온 이미지를 ImageView크기에 맞추고 라운드 처리하는 법에 대해 알아보겠습니다!! 가시죠!!


AndoridManifest.xml

<uses-permission android:name="android.permission.INTERNET" />

인터넷으로 부터 이미지를 받아오기 때문에 인터넷 접근 권한을 설정해 주어야 합니다.


activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
tools:context=".MainActivity">

<ImageView
android:id="@+id/imageView"
android:layout_width="300dp"
android:layout_height="300dp"/>

</LinearLayout>

xml 은 매우 간단합니다. 가로 세로 300dp 인 ImageView를 화면 중앙에 그려줍니다.


MainActivity.class


class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

val blackPanther = "https://i.ytimg.com/vi/WCXM4DnwT1g/maxresdefault.jpg"
val captionMarvel = "http://img.sbs.co.kr/newsnet/etv/upload/2018/12/05/30000618805_700.jpg"

Glide.with(this.applicationContext)
.load(blackPanther)
.into(imageView)

}
}

MainActivity 에서는 Glide 를 사용해 blackPanther 와 captionMarvel 이미지를 보여주고 있습니다.


  


위와 같이 가로가 긴 이미지는 가로가 300dp, 세로가 긴 이미지는 세로가 300dp 로 채워지면서 화면에 그러졌습니다.



가로 및 세로 비율에 맞춰 이미지 늘리기


MyTransformation.class


Glide 의 bitmapTransform 을 사용하기 위해서는 BitmapTransformation 을 상속하는 클래스를 만들어야 합니다.

public class MyTransformation extends BitmapTransformation

이렇게 BitmapTransformation 을 상속하면 transform 함수를 오버라이드 하게 됩니다.

@Override
protected Bitmap transform(BitmapPool bitmapPool, Bitmap original, int width, int height) {

Log.d("MyTag", "imageView 사이즈 width : " + width + " , height : " + height); // imageView 사이즈

int orgWidth = original.getWidth();
int orgHeight = original.getHeight();

Log.d("MyTag", "받아온 이미지 사이즈 orgWidth : " + orgWidth + " , orgHeight : " + orgHeight); // 받아온 이미지의 사이즈

float scaleX = (float) width / orgWidth;
float scaleY = (float) height / orgHeight;

float scaledWidth;
float scaledHeight;

if (orgWidth >= orgHeight) {
scaledWidth = scaleY * orgWidth;
scaledHeight = height;
} else {
scaledWidth = width;
scaledHeight = scaleX * orgHeight;
}

Log.d("MyTag", "스케일 사이즈 scaledWidth : " + scaledWidth + " , scaledHeight : " + scaledHeight);

return original;
}

그럼 transForm 함수의 파라미터를 살펴보겠습니다.


위 파라미터중 width, height 는 ImageView 의 넓이와 높이입니다. 우리가 300dp로 설정해준 그 ImageView 입니다. Emulator Nexus_6p 에서 실행 한 결과 ImageView 사이즈는 각각 1050, 1050이 나왔습니다. (로그에 찍힌 값은 dp 를 px 로 변환한 값입니다.)


original 은 받아온 마블 이미지를 Glide 가 비트맵으로 반환한 값입니다. 블랙펜서 이미지를 넣었을 경우 original은 가로 1280 세로 720 으로 나왔습니다.



자~! 여기서 약간의 수학이 필요합니다. ImageView 는 가로가 1050, 1050 이고 블랙펜서 이미지는 1280, 720 입니다. 블랙펜서 이미지를 ImageView에 꽉 채울려면 어떻게 해야 할까요? 가로 길이는 충분하니 세로를 720 -> 1050 으로 늘려주어야 합니다. 그러면 이에 맞춰 가로 길이 또한 세로길이가 늘어난 비율 만큼 늘어나야됩니다. 이제 비례식을 세워 봅시다.


블랙펜서 가로 : x = 블랙펜서 세로 : 이미지뷰 세로 -> 1280 : x = 720 : 1050 입니다. x 값이 코드에서 scaleWidth 입니다. 반대로 세로가 긴 이미지에서는 scaleHeight 값을 구하는 비례식을 세워줘야 합니다.


이렇게 식을 구하고 로그값을 보겠습니다.

블랙펜서 이미지의 세로를 1050으로 만드니 가로가 1866이 되었습니다. 그럼 블랙펜서 이미지를 Canvas를 사용해 가로 1866 세로 1050 으로 늘려보겠습니다.


Bitmap result = bitmapPool.get(width, height, Bitmap.Config.ARGB_8888);
if (result == null) {
result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
}

Canvas canvas = new Canvas(result);

RectF targetRect = new RectF(0, 0, scaledWidth, scaledHeight);
canvas.drawBitmap(original, null, targetRect, null);

return result;

transForm  함수의 bitmapPool 파라미터를 통해 비트맵을 재사용 할 수 있습니다. ImageView 와 같은 크기의 비트맵을 만들어 준 후 캔버스를 사용해 늘려줍니다.


Android Developer - Canvas 


Canvas 의 함수 중 하나로 drawBitmap 을 사용했는데 위 정의에서 처럼 자동으로 스케일링 해주는 기능이 있습니다.


이제 결과를 살펴 볼까요?

    



이미지 뷰의 가로와 세로 길이에 맞춰 마블 영웅들이 들어갔습니다!!! 여기선 정렬이 안되어 있기 때문에 왼쪽 상단이 기준이 됩니다. 그래서 블랙펜서 이미지는 ImageView보다 가로길이가 크므로(imageView width : 1050, blackPanther width : 1866) 왼쪽 상단을 기준으로 그림이 짤렸습니다. 캡틴마블은 세로길이가 늘어나 하단부분이 짤렸습니다.


아래는 이미지 늘리기의 전체 코드 입니다.

@Override
protected Bitmap transform(BitmapPool bitmapPool, Bitmap original, int width, int height) {

Log.d("MyTag","imageView 사이즈 width : " + width + " , height : " + height); // imageView 사이즈

int orgWidth = original.getWidth();
int orgHeight = original.getHeight();

Log.d("MyTag","받아온 이미지 사이즈 orgWidth : " + orgWidth + " , orgHeight : " + orgHeight); // 받아온 이미지의 사이즈

float scaleX = (float) width / orgWidth;
float scaleY = (float) height / orgHeight;

float scaledWidth;
float scaledHeight;

if(orgWidth >= orgHeight) {
scaledWidth = scaleY * orgWidth;
scaledHeight = height;
} else {
scaledWidth = width;
scaledHeight = scaleX * orgHeight;
}

Log.d("MyTag","스케일 사이즈 scaledWidth : " + scaledWidth + " , scaledHeight : " + scaledHeight);

Bitmap result = bitmapPool.get(width, height, Bitmap.Config.ARGB_8888);
if (result == null) {
result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
}

//캔버스 준비
Canvas canvas = new Canvas(result);

RectF targetRect = new RectF(0, 0, scaledWidth, scaledHeight);
canvas.drawBitmap(original, null, targetRect, null);

return result;
}

다음으로 모서리를 둥글게 만드는 것입니다. 모서리를 둥글게 만들기 위해서는 PorterDuff.Mode 를 사용해야 합니다.

(참고로 PorterDuff.Mode 는 비트맵을 합성하는 방식입니다. 합성할 때에는 같은 형식의 Bitmap 만 합성이 됩니다. 그렇기 때문에 Bitmap result 를 Config.ARGB_8888 형식으로 만들었습니다.)



모서리 둥글게 만들기


모서리를 둥글게 만들 코너 타입을 정해줍니다.

public enum CornerType {
NONE,
ALL,
TOP,
BOTTOM,
}

private int radius;
private CornerType cornerType;

4변을 모두 둥글게 할 수 있지만 위에만 혹은 아래만 둥글게 만들고 싶을 수 있으니깐 준비해 보았습니다:)


//캔버스 준비
Canvas canvas = new Canvas(result);

//크레파스 준비
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(0xff424242);

//모서리가 둥근 사각형(destination) 그리기
switch (cornerType) {

case ALL: {
RectF rectF = new RectF(0, 0, width, height);
canvas.drawRoundRect(rectF, radius, radius, paint);

//SRC_IN -> source 이미지가 destination 이미지를 덮습니다.
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

break;
}

case TOP:{

Rect rect = new Rect(0, 0, width, height);
RectF rectF = new RectF(rect);

canvas.drawRoundRect(rectF, radius, radius, paint);

//Fill in bottom corner
Rect bottomRect = new Rect(0, height/2, width, height);
canvas.drawRect(bottomRect, paint);

//SRC_IN -> source 이미지가 destination 이미지를 덮습니다.
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

break;
}

case BOTTOM: {

Rect rect = new Rect(0, 0, width, height);
RectF rectF = new RectF(rect);

canvas.drawRoundRect(rectF, radius, radius, paint);

//Fill in top corner
Rect topRect = new Rect(0, 0, width, height/2);
canvas.drawRect(topRect, paint);

//SRC_IN -> source 이미지가 destination 이미지를 덮습니다.
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

break;
}

default: {
break;
}

}

radius은 모서리를 얼마나 둥글게 할지 정하는 값입니다. 그리고 cornerType 에 따라 어느 모서리를 둥글게 할지 분기처리되어 있습니다.  PorterDuff.Mode 보시면 source 이미지와 destination 이미지가 있습니다. 이는 paint.setXfermode 를 기준으로 그 이전에 캔버스에 그려 진것이 destination 이며 이후 캔버스에 그려진것이 source 라고 생각하시면 됩니다. 이렇게 작업을 다 하고 실행하시면 아래와 같이 모서리가 둥근 Glide Transform 이 완성됩니다!!



아래는 전체코드입니다.


MainActivity



MyTransformation




MyTransformation 에서 getId() 


MyTransformation 을 보시면 오버로드 된 getId() 가 있습니다. 이 값이 동일하면 BitmapTransformaion 이 호출될 때 기존에 생성된 기능을 사용하게 됩니다. 이에 기능이 달라져도 getId가 같으면 기존에 만들어진 BitmapTransformaion 의 transform() 을 사용하게 되어 원하는 결과를 얻지 못할 수 있습니다. 저같은 경우 아래와 같이 radius 와 cornerType 을 사용해 기능이 다른경우 getId()가 겹치지 않게 했습니다. 여기서 반대로 성능을 위해서는 같은 기능의 경우 getId가 같아야겠죠?

@Override
public String getId() {
return "blakjin transformation -> radius : " + radius + " , cornerType : " + cornerType;
}


정리


이렇게 Glide 의 BitmapTransform 이 어떻게 동작되는지 알아 봤습니다. 저는 Glide v3 를 사용했지만 v4 버전이 성능이 좋다 하니 추후 v4 를 공부해 수정해봐야겠습니다. 그럼! 오늘도 즐거운 코딩 하시기 바랍니다. 감사합니다.


bottom rounded image :)



<참고자료>

Glide Transformations

구글문서 - Canvas

구글문서 - PorterDuff.Mode

반응형
Comments