2012년 6월 22일 금요일

RGB_565로 decode 된 이미지 byte 배열을 RGB_8888로 변환하기


Bitmap 파일은 BitmapFactory.decodeFile 을 이용해서 이미지 정보를 읽어 들일 수 있습니다.
이때 읽어들인 이미지 정보는 안드로이드에서 기본적으로 RGB_565로 처리됩니다.

일반적으로 RGB는 24비트 R(8비트), G(8비트), B(8비트)로 구성되며 여기에 알파값(투명도) 8비트를 포함하여 32비트 컬러가 됩니다.
이러한 32비트 컬러를 RGB8888, RGB24Bit 또는 트루 컬러라고 말합니다.

하지만 LCD모니터의 경우 CRT모니터와 달리 색상 구현에 한계가 있다고 하며 LCD모니터가 출력하지 못 하는 비트를 제거하여 RGB555가 되며, 이중 사람의 눈에 가장 민감한 녹색 부분만을 1비트 추가하여 RGB565가 됩니다.

그래서 안드로이드에서 RGB_565의 값을 주고 decode된 이미지는 하나의 Pixel 정보를 R(5비트), G(6비트), B(5비트) 모두 2바이트 (16비트)로 처리하며, 이게 안드로이드의 기본 값입니다. 
(iOS에선 RGB8888이 기본값입니다.)

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inSampleSize = 1;
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = null;
if ((bitmap = BitmapFactory.decodeFile(imageFilePath, options)) == null
{
// 이미지 디코딩 실패
return XXXX;
}

위 코드에서 RGB_565를 특별히 지정하지 않아도 이 값으로 decode되며 또한 이 값을 RGB_8888로 지정해도 역시 RGB_565의 형태로 decode되는 것이 확인됩니다.

BitmapFactory.decodeFile 를 이용하여, 이미지 파일을 decode하는 경우.

RGB888 (24bit) 이미지와 달리,
ARGB8888 ( 32bit ) 이미지는 RBG565 로 (자동) converting 되지 않습니다.



마침 저희는 이미지를 읽어 들여 크기 조정, 그레이 스케일 후 다시 흑백 프린터에서 인쇄가 잘 되게 하기 위해 Error Diffusion 이란 알고리즘을 적용할 필요가 있어 픽셀단위로 RGB를 뽑아내야 하는 상황입니다.

Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, bitmapWidth, bitmapHeight, true);
bitmap.recycle();
bitmap = null;
ByteBuffer dataRgb565 = ByteBuffer.allocate(bitmapWidth * bitmapHeight * 2);
scaledBitmap.copyPixelsToBuffer(dataRgb);

이렇게 읽어들인 dataRgb를 RGB_8888로 변환시키기 위해 다음과 같이 했습니다.

dataRgb565.position(0);
byte dataRgb8888[] = BxlBitmap.byteBuffer2Bytes(dataRgb565, dataRgb565.remaining(), bitmapWidth, bitmapHeight);

사용된 메소드들의 소스는 아래와 같습니다.

static private int r(int word)
{
return ((word & 0xf800) >> 8) + 4;
}
static private int g(int word)
{
return ((word & 0x07e0) >> 3) + 2;
}
static private int b(int word)
{
return ((word & 0x1f) << 3) + 4;
}

static private int word(byte hi, byte lo)
{
return (((hi & 0xff) << 8) | (lo & 0xff));
}

static public byte[] byteBuffer2Bytes(ByteBuffer buffer, int size, int widthOfImage, int heightOfImage)
{
byte bytes[] new byte[widthOfImage * heightOfImage * 4];
int i, idx, word, r, g, b;
for( i = 0, idx = 0 ; i < size ; i+= 2 )
{
// RGB 정보가 2바이트로 나누어져 있는 경우, 상위 바이트가 2번째에 존재한다.
word = word(buffer.get(i+1), buffer.get(i));
r = r(word);
g = g(word);
b = b(word);
bytes[idx++] = (byte)r;
bytes[idx++] = (byte)g;
bytes[idx++] = (byte)b;
bytes[idx++] = (byte)255;
}
return bytes;
}

주의 사항은 copyPixelsToBuffer 을 통해 픽셀 정보가 바이트 배열로 변환되었을 때 RGB565를 표시하기 위한 하나의 2바이트 숫자가 두개의 1바이트 2개 배열로 변환되는 과정입니다.

2바이트 숫자의 상위바이트 => 바이트 배열 두번째
2바이트 숫자의 하위바이트 => 바이트 배열 첫번째

상위가 두번째, 하위가 첫번째로 간다는 것만 아시면 문제 없습니다.



댓글 없음:

댓글 쓰기