실제 업무에서 Java로 image를 처리하여 서비스 하는 경우는 그렇게 많지 않을것이다.
예시 프로젝트를 통해, Java로 이미지를 읽고, 가공하고, 다시 인코딩을 하는 일련의 과정을 쓸 예정이다.
하지만 이미지 처리에서 가장 중요한 건 역시 속도 다. 멋있는 이미지를 서버에서 만들었다 해도, 다수의 접속자가
쾌적하게 그 이미지를 볼수 있어야 한다.
그런 의미에서 performance 중심 위주로 기술하려고 한다.
예시 프로젝트 소개
아래와 같은 1~9까지의 이미지 조각들을 읽어서(image decoding),
각 조각 이미지를 BufferedImage
를 이용하여 합친뒤, 합친 이미지를 아래와 같이 png로 변환(image encoding)
할 것이며, 각 단계에 따른 속도 측정을 할 예정이다.(stress tool은 ngrinder 를 사용 )
예시에 사용된 모든 소스는 여기 에 가면 볼수 있다
이미지 로딩
숫자 9개의 이미지를 모두 로딩해 보자.
ImageIO 를 사용하여 아래 3가지 방법으로 이미지를 로딩 후 BufferedImage로 변환 한다.
public BufferedImage getImage ( String filePath ) throws IOException {
BufferedImage image = null ;
image = ImageIO . read ( new File ( filePath ));
return image ;
}
성능 측정 결과
public BufferedImage getImageByNio ( String filePath ) throws IOException {
BufferedImage image = null ;
Path path = Paths . get ( filePath );
ByteArrayInputStream bais = null ;
try {
if ( Files . exists ( path )) {
byte [] imageBytes = Files . readAllBytes ( path );
bais = new ByteArrayInputStream ( imageBytes );
image = ImageIO . read ( bais );
}
} catch ( Exception e ) {
e . printStackTrace ();
} finally {
if ( bais != null ) bais . close ();
}
return image ;
}
성능 측정 결과
public BufferedImage getImageByBuffered ( String filePath ) throws IOException {
BufferedImage image = null ;
FileInputStream fis = null ;
BufferedInputStream bis = null ;
try {
fis = new FileInputStream ( new File ( filePath ));
bis = new BufferedInputStream ( fis );
image = ImageIO . read ( bis );
} catch ( Exception e ) {
e . printStackTrace ();
} finally {
if ( bis != null ) bis . close ();
if ( fis != null ) fis . close ();
}
return image ;
}
성능 측정 결과
약간의 차이가 있긴 하지만, 어떤 방식이던간에 이미지 데이터 자체를 로딩하고, ImageIO 를 통해 BufferedImage
를 만드는 속도 차이는 피부로 느낄만한 수준이 아니다.
이미지 합치기 및 이미지 인코딩
간단한 restful api 를 만들어 api를 호출하면 이미 로딩된 1~9까지의 이미지를 합치고, png로 인코딩후, png 자체를
response로 리턴하게 한다.
api 소스는 아래와 같다.
@RequestMapping ( "/combineImages" )
public ResponseEntity < byte []> combineNumbers ( HttpServletRequest request ,
HttpServletResponse response ){
HttpHeaders headers = new HttpHeaders ();
headers . setContentType ( MediaType . IMAGE_PNG );
ImageWriterType writerType = null ;
String write = request . getParameter ( "write" );
if ( StringUtils . isEmpty ( write ) || write . equals ( "1" )) {
writerType = ImageWriterType . ImageIO ;
} else if ( write . equals ( "2" )) {
writerType = ImageWriterType . JDELI ;
} else {
writerType = ImageWriterType . OBJECTPLANET ;
}
BufferedImage image = new BufferedImage ( 150 , 150 , BufferedImage . TYPE_INT_ARGB );
Graphics2D graphics = image . createGraphics ();
int x = 0 ;
int y = 0 ;
for ( int i = 1 ; i < 10 ; i ++) {
BufferedImage numberImage = imageMap . get ( i );
graphics . drawImage ( numberImage , x , y , null );
x += 50 ;
if ( i % 3 == 0 ) {
x = 0 ;
y += 50 ;
}
}
graphics . dispose ();
ByteArrayOutputStream baos = new ByteArrayOutputStream ();
imageService . writeImage ( writerType , baos , image );
return new ResponseEntity < byte []>( baos . toByteArray (), headers , HttpStatus . OK );
}
먼저 150X150 의 BufferedImage
를 생성후, 이미 로딩된 1~9까지의 이미지를 합치고, 그 다음
png로 인코딩 후, response로 던져주는 소스이다.
합쳐진 이미지에 대한 png enconding은 아래와 같은 3가지 방식으로 처리 했다.
public void writeImage ( OutputStream outputStream , BufferedImage image ) throws IOException {
try {
ImageIO . write ( image , "png" , outputStream );
} catch ( Exception e ) {
e . printStackTrace ();
} finally {
if ( outputStream != null ) outputStream . close ();
}
}
성능 측정 결과
public void writeImage ( OutputStream outputStream , BufferedImage image ) throws IOException {
try {
PngEncoder encoder = new PngEncoder ();
encoder . setCompressed ( true );
encoder . write ( image , outputStream );
} catch ( Exception e ) {
e . printStackTrace ();
} finally {
if ( outputStream != null ) outputStream . close ();
}
}
성능 측정 결과
public void writeImage ( OutputStream outputStream , BufferedImage image ) throws IOException {
try {
PngEncoder encoder = new PngEncoder ();
encoder . setColorType ( PngEncoder . COLOR_TRUECOLOR_ALPHA );
encoder . setCompression ( PngEncoder . DEFAULT_COMPRESSION );
encoder . encode ( image , outputStream );
} catch ( Exception e ) {
e . printStackTrace ();
} finally {
if ( outputStream != null ) outputStream . close ();
}
}
성능 측정 결과
BufferedImage
를 png로 인코딩 시키는데 있어서, 어떠한 라이브러리를 쓰는지에 따라서 굉장한 차이 가 나는것을 볼수 있다. 적어도 Java에서 image를 생성하여 실시간 서비스를 하기 위해서는, ImageIO 로 png를 생성하는 부분은 피하는게
좋을것 같다.
마치며
Java image 처리는 위에 글 외에, blending composite
, xBRZ
등 여러 이미지 가공이 가능하다.
대용량 트래픽에서 실시간으로 이미지를 읽고, 디코딩하고, 합성하고, 다시 인코딩 하는 처리는 부담스러울수 밖에 없다. 특히 java에서 image를 인코딩, 디코딩 하는 부분은 예상외로 시간이 좀 걸린다.
소스단에서 이미지 처리 프로세스를 하나하나씩 점검하면서, 계속 튜닝을 해야할 것이다. 또한 File I/O 가 빈번히 일어나며, 이미지 처리 특성상 cpu도 많이 사용함으로, 하드웨어적으로는 SSD 및 고사양 CPU가 받쳐준다면, 유저 서비스하는데 큰 무리는 없을 것이다.
예제에 사용된 모든 소스는 여기 에서 볼 수 있다.