저작권 안내: 저작권자표시 Yes 상업적이용 No 컨텐츠변경 No

스프링5 입문

JSP 2.3

JPA 입문

DDD Start

인프런 객체 지향 입문 강의

'이미지크기변환'에 해당되는 글 2건

  1. 2013.02.01 [팁] 안드로이드 이미지 크기 구하기 및 변경하기 (1)
  2. 2006.06.15 [팁] 이미지 크기 변환할 때 품질 유지하기 (2)

안드로이드에서 이미지를 ImageView에 출력할 때 발생할 수 있는 문제는 크게 다음의 두 가지가 있다.

이 두 가지 문제를 해소하려면 이미지 크기가 특정 크기를 넘어선 경우 이미지 크기를 줄여서 읽어오면 된다.

이미지 크기 구하기

이미지 크기를 구할 때에는 BitmapFactory.Options의 inJustDecodeBounds 값을 true로 지정해 주고, 이 옵션을 이용해서 BitmapFactory의 decode 메서드를 사용한다. 아래는 파일로부터 이미지 크기를 구할 때 사용되는 코드 예를 보여주고 있다.

private BitmapFactory.Options getBitmapSize(File imageFile) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options);
return options;
}


BitmapFactory의 decode 메서드는 옵션의 inJustDecodeBounds 값이 true일 경우, 이미지의 크기만 구해서 옵션에 설정한다. 이 메서드 실행 후, 옵션의 

BitmapFactory.options option = getBitmapSize(imgFile);
// option.outWidth : 이미지 폭
// options.outHeight : 이미지 높이

if (option.outWidth > maxWidthSize || option.outHeight > maxHeightSize) {
    // 최대 크기를 벗어난 경우의 처리, 이미지 크기 변환 등
}

이 값을 이용해서 이미지가 제한된 크기를 벗어났는지의 여부를 알 수 있다.

이미지 크기 변경하기

이미지 크기를 변경할 때에는 옵션의 inSampleSize 값을 이용한다. 예를 들어, 아래 코드는 크기를 2배 축소시켜서 Bitmap을 생성해주는 코드이다.

BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
Bitmap bitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options);

3000*2000 크기의 이미지가 있을 때, inSampleSize 값으로 2를 사용하면 생성되는 Bitmap의 크기는 1500*1000이 된다. inSampleSize의 값을 4로 주면 실제 폭/높이는 1/4로 줄고, 8을 주면 실페 폭/높이는 1/8로 준다. 2의 거듭제곱을 inSampleSize의 값으로 주면 연산이 좀 더 빠르게 처리된다.

관련 자료


Posted by 최범균 madvirus

댓글을 달아 주세요

  1. ㄹㄹㄹㄹㄹㄹㄹㄹㄹㄹㄹ 2016.04.04 18:15 신고  댓글주소  수정/삭제  댓글쓰기

    ㅎㅎㅎㅎㅎㅎㅎㅎ호ㅗㅗㅗㅗㅗㅗㅗㅗㅗㅗ

자바에서 이미지의 크기를 변환할 때 품질을 유지하는 방법을 살펴본다.

이미지 크기 변환시 품질 유지 방법

필자가 쓴 'JSP 2.0 프로그래밍' 책에서 자바 1.4부터 추가된 ImageIO 클래스를 사용해서 썸네일 이미지를 작성하는 방법을 소개한 바 있다. 이 때 소개한 코드는 다음과 같다.

    public static void resize(File src, File dest, 
                              int width, int height) throws IOException {
        BufferedImage srcImg = ImageIO.read(src);
        
        int srcWidth = srcImg.getWidth();
        int srcHeight = srcImg.getHeight();
        ...
        BufferedImage destImg = new BufferedImage(
             destWidth, destHeight, BufferedImage.TYPE_3BYTE_BGR);
        Graphics2D g = destImg.createGraphics();
        g.drawImage(srcImg, 0, 0, destWidth, destHeight, null);
        
        ImageIO.write(destImg, "jpg", dest);
    }

필자는 최근에 이미지 관련 서비스를 개발하고 있는데, 썸네일 이미지를 비롯하여 원본 이미지보다 작은 크기의 이미지를 몇 개 생성할 필요가 있어서 위와 같은 코드를 사용하였다. 위 코드는 비교적 빠른 속도로 이미지 크기 변환을 처리했지만, 새롭게 생성된 이미지의 품질이 떨어진다는 문제점을 발견하게 되었다.

이미지 품질을 높이기 위해 찾아낸 해결책은 Image.getScaledInstance(int width, int height, int hints) 메소드를 사용하는 것이었다. 로딩한 이미지를 getScaledInstance() 함수를 통해서 다음과 같이 크기 변환을 하면 변환된 이미지의 품질이 떨어지지 않게 되었다.

    BufferedImage srcImg = ImageIO.read(src);
    Image imgTarget = srcImg.getScaledInstance(destWidth, destHeight, Image.SCALE_SMOOTH);
    int pixels[] = new int[destWidth * destHeight]; 
    PixelGrabber pg = new PixelGrabber(imgTarget, 0, 0, destWidth, destHeight, pixels, 0, destWidth); 
    try {
        pg.grabPixels(); // JEPG 포맷의 경우 오랜 시간이 걸린다.
    } catch (InterruptedException e) {
        throw new IOException(e.getMessage());
    } 
    BufferedImage destImg = new BufferedImage(destWidth, destHeight, BufferedImage.TYPE_INT_RGB); 
    destImg.setRGB(0, 0, destWidth, destHeight, pixels, 0, destWidth); 
    

위 코드와 같이 getScaledInstance() 메소드의 세번째 파라미터로 Image.SCALE_SMOOTH를 사용하면 새롭게 생성된 이미지의 품질이 떨어지지 않게 된다. getScaledInstance() 메소드가 생성한 Image 객체로부터 픽셀 정보를 읽어온 뒤, 새롭게 생성한 BufferedImage에 채워 넣어주면 이미지 크기 변환이 마무리 된다.

그런데, 이미지 변환 과정에서 한가지 문제가 발생하였다. 그것은 바로 PNG, GIF, BMP와 달리 JPEG 포맷을 변환할 때는 PixelGrabber.granPixels() 함수를 실행할 때 시간이 오래 걸린다는 것이었다. (이미지 크기가 3000*2000인 경우 3분 이상 소요되기도 했다.) 그래서 몇가지 테스트 끝에 다음과 같이 ImageIcon 클래스를 사용해서 JPEG 이미지를 로딩할 경우 오랜 처리 시간 문제를 해결할 수 있다는 걸 알게 되었다. (왜 JPEG을 사용할 때 시간이 오래 걸리는 지의 문제는 아직 정확하게 파악하지 못했으나, BufferedImage가 JEPG 이미지를 저장할 때 사용되는 방식 때문인 것으로 판단된다.)

    Image srcImg = new ImageIcon(src.toURL()).getImage(); // JPEG 포맷인 경우

그런데, ImageIcon의 경우는 GIF와 JPEG 포맷의 이미지만 사용할 수 있기 때문에, BMP나 PNG 같은 파일은 ImageIcon으로 읽어올 수가 없다. 따라서, 다음과 같이 이미지 포맷에 따라서 서로 다른 방식으로 이미지를 로딩하도록 하였다.

    Image srcImg = null;
    String suffix = src.getName().substring(src.getName().lastIndexOf('.')+1).toLowerCase();
    if (suffix.equals("bmp") || suffix.equals("png") || suffix.equals("gif")) {
        srcImg = ImageIO.read(src);
    } else {
        // JPEG 포맷
        srcImg = new ImageIcon(src.toURL()).getImage();
    }

위와 같은 코드를 사용하면, JPEG인 경우에는 ImageIcon을 사용해서 Image를 생성하고 그 외에 경우에는 ImageIO.read()를 사용해서 Image를 읽어오게 된다. 이제 JPEG 포맷에 대해서 PixelGrabber.granPixels() 메소드를 사용하더라도 빠르게 이미지 크기 변환을 수행할 수 있게 되었다.

이미지 변환과 관련된 완전한 코드는 다음과 같다.

    public class ImageUtil {
        public static final int RATIO = 0;
        public static final int SAME = -1;
        
        public static void resize(File src, File dest, int width, int height) throws IOException {
            Image srcImg = null;
            String suffix = src.getName().substring(src.getName().lastIndexOf('.')+1).toLowerCase();
            if (suffix.equals("bmp") || suffix.equals("png") || suffix.equals("gif")) {
                srcImg = ImageIO.read(src);
            } else {
                // BMP가 아닌 경우 ImageIcon을 활용해서 Image 생성
                // 이렇게 하는 이유는 getScaledInstance를 통해 구한 이미지를
                // PixelGrabber.grabPixels로 리사이즈 할때
                // 빠르게 처리하기 위함이다.
                srcImg = new ImageIcon(src.toURL()).getImage();
            }
            
            int srcWidth = srcImg.getWidth(null);
            int srcHeight = srcImg.getHeight(null);
            
            int destWidth = -1, destHeight = -1;
            
            if (width == SAME) {
                destWidth = srcWidth;
            } else if (width > 0) {
                destWidth = width;
            }
            
            if (height == SAME) {
                destHeight = srcHeight;
            } else if (height > 0) {
                destHeight = height;
            }
            
            if (width == RATIO && height == RATIO) {
                destWidth = srcWidth;
                destHeight = srcHeight;
            } else if (width == RATIO) {
                double ratio = ((double)destHeight) / ((double)srcHeight);
                destWidth = (int)((double)srcWidth * ratio);
            } else if (height == RATIO) {
                double ratio = ((double)destWidth) / ((double)srcWidth);
                destHeight = (int)((double)srcHeight * ratio);
            }
            
            Image imgTarget = srcImg.getScaledInstance(destWidth, destHeight, Image.SCALE_SMOOTH); 
            int pixels[] = new int[destWidth * destHeight]; 
            PixelGrabber pg = new PixelGrabber(imgTarget, 0, 0, destWidth, destHeight, pixels, 0, destWidth); 
            try {
                pg.grabPixels();
            } catch (InterruptedException e) {
                throw new IOException(e.getMessage());
            } 
            BufferedImage destImg = new BufferedImage(destWidth, destHeight, BufferedImage.TYPE_INT_RGB); 
            destImg.setRGB(0, 0, destWidth, destHeight, pixels, 0, destWidth); 
            
            ImageIO.write(destImg, "jpg", dest);
        }
    }

관련링크:
Posted by 최범균 madvirus

댓글을 달아 주세요

  1. lae 2016.05.10 14:05 신고  댓글주소  수정/삭제  댓글쓰기

    이 클래스를 사용할 경우에
    java.lang.ArrayIndexOutOfBoundsException: 700
    at java.awt.image.BufferedImage.setRGB(Unknown Source)
    이러한 에러가 뜨는데 어떻게 사용을 해야할지 답변좀 부탁드릴게요!!

    • 최범균 madvirus 2016.05.10 23:54 신고  댓글주소  수정/삭제

      혹시 실행할 때 사용하신 코드와 이미지를 madvirus@madvirus.net으로 보내주실 수 있을까요? 간단한 이미지 파일로 확인해보니 동작은 되는듯 한데, 실제 에러 나는 파일을 갖고 해 봐야 좀더 구체적으로 알 수 있을 것 같아요.
      그리고, 이게 10년 전 코드라 더 좋은 방법이 있을 것도 같습니다.