Frontend/Android

[안드로이드] Bitmap 최적화 로딩 (Bitmap Resize)

에반황 2016. 7. 17. 23:07


이 글은 PC 버전 TISTORY에 최적화 되어있습니다.


서론

안드로이드에서 사용하는 이미지들의 모양과 크기는 다양합니다. 많은 경우 이미지들은 User Interface에서 요구하는 것보다 큰 크기를 가지고 있죠. 예를 들어 '갤러리' 어플리케이션은 당신의 카메라로부터 당신의 디바이스보다 높은 해상도의 사진을 가져와 사용하게되죠. 당신은 주어진 메모리가 한정적이기 때문에, 이미지를 메모리에 로딩할 때 더 낮은 해상도의 이미지를 로딩해야하는 경우가 많습니다. 낮은 버젼의 이미지는 UI 컴포넌트의 사이즈와 맞춰져 디스플레이 되어야합니다. 고해상도를 사용하게 되면 오버헤드가 발생하게 되는 것 입니다. 

 이번 장에서는 메모리가 작은 서브 이미지를 로딩해 어플의 메모리 제한을 초과하지 않는 방법을 배워보도록 하겠습니다.



비트맵의 치수와 타입 읽어들이기

 BitmapFactory 클래스는 decodeByteArray(), decodeFile(), decodeResource() 등 다양한 자원으로부터 Bitmap을 생성하기 위해 몇몇의 메소드를 제공합니다. 이 메소드들은 비트맵을 성생하기 위해 메모리를 할당하려고 시도하는데, 그 결과 쉽게 OutOfMemory에 도달해버립니다. 하지만 각각의 디코드 메소드는 BitmapFactory.Options를 통해 옵션들을 조정할 수 있습니다. inJustDecodeBounds 속성을 true로 설정하는 것은 메모리 할당을 피하면서 비트맵에 null을 반환하지만, outWidth, outHeight 그리고  outMimeType를 세팅합니다. 이 기술은 당신에게 이미지 데이터를 비트맵으로 생성하기 (그리고 메모리에 할당함) 전에 치수와 타입을 알 수 있게 해줍니다.


BitmapFactory.Options options = new BitmapFactory.Options();
options
.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;

java.lang.OutOfMemory를 피하기 위해, 메모리에 여유롭게 맞는 이미지 데이터의 예상된 사이즈에 대해서 당신이 완전한 신뢰가 없다면,  비트맵을 디코딩하기 전에 치수를 체크하세요.


크기 줄인 이미지 메모리에 불러들이기


이제 당신이 이미지의 치수에 대해 알게 됬습니다. 그것은 풀 사이즈 이미지를 그대로 넣어버릴지, 또는 Subsampled 된 버전을 대신 넣어야 될지 결정합니다. 고려해야할 사항은 아래와 같습니다.


    •  메모리에 전체 이미지를 로딩할 때 예상되는 메모리 사용량
    •  이미지가 로딩될 이미지 뷰, UI 컴포넌트 요소의 면적
    •  현재 디바이스 크기, 해상도
    •  이미지 로딩을 제외한 다른  메모리 요구사항의 양


 예를 들어 128x96 pixel의 썸네일 이미지가 필요한 뷰에 1024x768 pixel 이미지를 로딩하는 것은 가치 없는 행동입니다. 디코더가 이미지의 서브 버전 생성하기 위해, inSampleSize를 true로 설정하여 더 작은 크기의 이미지를 메모리에 로드 해야합니다. 2048x1536 해상도의 이미지를 4라는 값으로 inSampleSize로 설정하여 디코딩하면 512x384 크기의 비트맵이 생성됩니다. ARGB_8888을 기준으로 하면 Full size 이미지가 12MB를 사용하는 반면에 0.75MB 만 사용하면 되는 것이죠. 타겟의 너비와 높이를 기준으로 2의 지수 형태로 샘플의 사이즈를 계산하는 방법이 Android Developers에 나와있습니다.



public static int calculateInSampleSize(
           
BitmapFactory.Options options, int reqWidth, int reqHeight) {
   
// Raw height and width of image
   
final int height = options.outHeight;
   
final int width = options.outWidth;
   
int inSampleSize = 1;

   
if (height > reqHeight || width > reqWidth) {

       
final int halfHeight = height / 2;
       
final int halfWidth = width / 2;

       
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
       
// height and width larger than the requested height and width.
       
while ((halfHeight / inSampleSize) >= reqHeight
               
&& (halfWidth / inSampleSize) >= reqWidth) {
            inSampleSize
*= 2;
       
}
   
}

   
return inSampleSize;
}




이미지 Resize 과정


  // First decode with inJustDecodeBounds=true to check dimensions
   
final BitmapFactory.Options options = new BitmapFactory.Options();
    options
.inJustDecodeBounds = true;
   
BitmapFactory.decodeStream(is, null, options);

   
// Calculate inSampleSize
    options
.inSampleSize = calculateInSampleSize(options, 100, 100);

   
// Decode bitmap with inSampleSize set
    options
.inJustDecodeBounds = false;
   
return BitmapFactory.decodeResource(url.openStream(), null, options);


 필자는 웹에서 이미지 url을 파싱해 와서 비트맵을 생성하므로 decodeStream을 사용했습니다. inJustDecodeBounds 옵션을 true로 설정해주어 이미지를 디코딩 할 때 크기만을 먼저 불러와 OutOfMemory Exception을 불러일으킬만한 큰 이미지를 선처리 하도록 해줍니다. 

다음으로 inSampleSize가 나오는데 이미지 Resize에서 중요한 역할을 하는 녀석입니다. 쉽게 설명하자면 decoding 시 얼마나 이미지를 줄여서 decoding 할지를 결정하는 옵션이죠. inSampleSize의 값은 다음과 같이 해석되어집니다. inSampleSize = 4 라면 4개의 픽셀을 1개의 픽셀로 간주하여 decode하라, 즉 1/4배 크기로 만들라는 것이죠. inSampleSize 옵션은 1보다 작은 값일 때는 무조건 1로 세팅도어지고 그 이상이 되면 이미지를 1/N배로 샘플링 합니다. 여기서 이미지 디코드 로직상 2의 지수의 값이 들어갈 때 가장 빠른 속도를 보여주기 때문에 2의 배수로 나누어 주는 것입니다.

마지막으로 inJustDecodeBounds를 다시 false 해주어 inSampleSize가 세팅된 상태로 스트림으로부터 디코딩된 비트맵을 생성하게 됩니다.  중요한 점은 url.openStream() 입니다. InputStream은 한번 읽은 Stream은 다시 읽지 못하므로 다시 생성해주는 것 입니다.



참고사이트

    1.  decodeStream 후 Inputstream 다시 사용 못하는 문제 해결 
    2.  Android Developers








반응형