커다란 비트맵 효율적으로 불러오기

이미지는 다양한 모습과 크기로존재합니다. 보통 유저 인터페이스(UI)가 필요로 하는 크기보다 해당 이미지가 큰 경우가 많습니다. 예를 들면, 기본 겔러리 어플리케이션이 안드로이드 기기의 카메라로 찍은 사진을 보여줄 때, 해당 사진이 기기의 화면 밀도보다 더 높은 화소를 가지고 있는 경우입니다.

여러분이 한정된 메모리에서 작업을 한다 했을 때, 이상적으로 당신은 화면에 표시되는 UI요소에 맞는 크기로 조절한 이미지를 사용하고자 할 것입니다. 더 큰 크기의 이미지는 더 많은 시각적인 효과를 주지 못합니다. 하지만 여전히 큰 크기의 이미지를 사용하기 위해 처리 과정에서 추가적으로 필요 이상의 메모리를 사용하게 됩니다.

이 강좌를 통해서 당신은 애플리케이션 메모리 한계를 초과하지 않으면서 큰 크기의 이미지를 부표본화(subsampled)하여 디코딩하는 방식을 배우게 될 것입니다.

비트맵 치수와 타입 읽기

BitmapFactory클래스는 다양한 소스로부터 비트맵 (Bitmap)을 만들기 위해 여러가지 디코딩 방식(decodeByteArray()), decodeFile()), decodeResource()))을 제공합니다. 이들 중 사용하려는 소스에 따라 가장 적덜한 디코딩 방식을 결정합니다. 이 방법들은 완성된 비트맵 을 위해 메모리를 할당하려 하는데, 이는 쉽게 메모리 부족 오류(OutofMemory)오류를 야기합니다. 각각의 디코딩 방식들은 BitmapFactory.Options 클래스를 통해 디코딩 옵션을 구체적으로 정할 수 있습니다. inJustDecodeBounds를 true로 설정하여 메모리 할당을 피하며 비트맵 객체의 높이, 너비, 타입을 얻을 수 있습니다. 이때에 디코딩된 비트맵은 실제 온전한 객체로 디코딩 된것이 아니라 null값이 반환됩니다. 이를 통해 비트맵을 만들고 메모리 할당을 하기 전에 이미지의 크기와 타입을 구할 수 있습니다.

java.lang.OutofMemory 오류를 피하기 위해서, 소스 크기가 메모리 한계를 넘지 않을 것이라는 확신이 없는 한, 디코딩에 앞서서 크기를 확인하십시오.

크기 조정된 버전을 메모리에 올리기

이제 이미지의 크기를 알게 되어 온전한 이미지를 메모리에 올릴 것인지 아니면 부표본(subsampled)화된버전을 올릴 것인지 결정할 수 있습니다. 이 때 몇가지 고려할 사항들이 있습니다.

  • 온전한 이미지를 메모리에 올릴 때 사용 될 것으로 추정되는 메모리 양

  • 당신의 애플리케이션이 필요로 하는 메모리 양을 고려했을 때, 추가적으로 사용할 용의가 있는 메모리양

  • 이미지가 들어가게 될 이미지 뷰(ImageView) 또는 다른 UI요소의 크기

  • 기기의 화면 크기와 밀도

예를 들어 128X96 크기의 썸네일 이미지 뷰를 위해서 1024X768 크기의 이미지를 메모리에 올리는 것은 불필요 합니다. 부표본화 된 이미지로 디코딩하기 위해서BitmapFactory.OptionsinImageSize값을 줘야 합니다.

inSampleSize는 정수 값을 갖습니다. 1이하의 값에 대해서는 1로 처리를 합니다. 2의 자승값 만을 사용하기에 3과 같은 수의 경우 2의 2승값이 4로 반올림되어 적용됩니다. 4의 inSampleSize는 높이와 너비값에 각각 1/4을 곱한 값을 높이와 너비로 갖게 되며 결국 전체 pixel 수는 1/16배 됩니다.

예를 들어 크기가 2048X1536인 이미지가 4의 inSampleSIze 값으로 디코딩 될 때 비트맵 결과값의 크기는 512X384정도가 됩니다. 이는 12MB에 해당하는 이미지가 0.75MB가 되는 효과를 얻습니다. 이때 이미지의 속성값은 ARGB_8888(각 pixel이 4bytes의 값을 갖습니다)로 가정합니다. 아래는 2자승 값에 기준을 둔 inSampleSize값을 얻는 메소드 소스코드입니다.

public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // 사전에 inJustDecodeBounds를 통해 얻은 높이와 너비값을 구한다.
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {
    //본래의 예제는 절반으로 나눈 높이와 너비값을 사용하나 불필요하다 생각되어 수정하였습니다.
        while ((height / inSampleSize) >= reqHeight
                && (width / inSampleSize) >= reqWidth) {
                //inSampleSize는 2의 자승값을 사용하기에 지속적으로 2를 곱한 값을 사용합니다.
            inSampleSize *= 2;
        }
    }
    return inSampleSize;
}

이 메소드를 사용하기 위해서 우선 inJustDecodeBounds를 true값으로 설정하여 이미지의 높이와 너비값을 가져옵니다(outHeight, outWidth). 이후 inJustDecodeBounds를 false값으로 설정한 뒤 위의 메소드를 통해 얻은 inSampleSize로 디코딩하여 비트맵을 얻습니다.

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {

    // 이미지 높이와 너비를 얻기위해 inJustDecodeBound를 true로 설정하여 디코딩합니다.
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // inSampleSize를 계산한 후 option에 적용합니다.
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // option을 이용해 비트맵으로 디코딩합니다.
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

이 메소드는 쉽게 임의의 커다란 크기의 비트맵을 불러와 이미지 뷰 (ImageView)에 표시하기 좋습니다.

아래의 예제는 100X100dml 썸네일을 이미지뷰에 적용하는 소스코드입니다.

 mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

유사한 과정을 적절한 다른 디코딩 메소드(BitmapFactory.decode))를 사용하여 진행할 수 있습니다.

results matching ""

    No results matching ""