개발/Java|Spring

대량 이미지 동일한 사이즈로 분할하기(JAVA)

달리초이 2023. 2. 13. 17:11

 

웹툰처럼 세로로 엄청나게 긴 대량의 이미지를 동일한 사이즈로 분할해야 하는 상황이 발생했다.

 

이미지 편집 도구를 사용해도 되지만, 세로 길이가 10만 픽셀이 넘어가는 이미지를 일단 불러오는 것조차 버벅거리며 문제가 된다. 이런 반복적이고 간단한 로직에는 역시 기계만큼 든든한 게 없다. 그래서 이미지를 자르는 프로그램을 Java로 간단하게 만들어서 해결했다.

 

일단 결과 샘플을 먼저 보고 전체 코드를 살펴보자.

 

 

 

작업 샘플 이미지

샘플용으로 인터넷에 돌아다니는 유튭 캡쳐짤을 하나 줏어왔다.

 

 

윗부분만 짤라놔서 짧아보이지만, 실제로는 세로 길이가 2만 픽셀이 넘는 이미지이다.

이걸 프로그램에 넣고 돌리면,

 

 

 

이렇게 나온다.

젤 위에 한 컷을 썸네일/표지 용으로 한 컷 자르고, 나머지를 장당 4컷이 들어가도록 이미지를 분할했다.

더 설명할 필요는 없을 거 같아 바로 전체 코드를 보자.

 

 

 

 

이미지 자르기 전체 코드

먼저 images 디렉토리에 자르길 원하는 원본 이미지파일을 ['1.png', '2.png', '3.png', ...] 처럼 숫자와 확장자 파일이름으로 넣어둔 상태에서 시작한다.(무지성 코드 주의)

package image;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Scanner;

import javax.imageio.ImageIO;

/**
 * @author dalichoi.tistory.com
 * 웹툰처럼 긴 이미지 정해진 사이즈로 일괄적으로 자르기
 * startNum 시작 파일번호
 * endNum 마지막 파일번호
 * firstHeight 표지 높이
 * height 자를 이미지 높이
 */
public class CropImage {
	private static final String PATH = "C:\\Users\\dalichoi\\Desktop\\test\\";
	private static final String EXTENSION = ".png";
	private static int firstHeight;
	private static int height;

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		System.out.print("시작 번호를 입력: ");
		int startNum = sc.nextInt();
		System.out.print("끝 번호를 입력: ");
		int endNum = sc.nextInt();
		System.out.print("표지 이미지 세로길이: ");
		firstHeight = sc.nextInt();
		System.out.print("자를 이미지 세로길이: ");
		height = sc.nextInt();

		for (int i = startNum; i <= endNum; i++) {
			String num = String.valueOf(i);
			try {
				BufferedImage originalImage = ImageIO.read(new File(PATH + num + EXTENSION));
				int originalImageHeight = originalImage.getHeight();
				int count = ((originalImageHeight - firstHeight) / height) + 2;
				int cropHeight = 0;

				for (int j = 0; j < count; j++) {
					if (j == 0) {
						cropHeight = firstHeight;
					} else if(j == count - 1) {
						cropHeight = originalImageHeight - firstHeight - ((j-1) * height);
					} else {
						cropHeight = height;
					}
					if (cropHeight > 0) {
						// 정해진 세로길이만큼 반복적으로 이미지 자르고 저장하기
						saveCropImage(num, originalImage, cropHeight, j);
					}
				}
			} catch (IOException e) {
				System.out.println("Error cropping image: " + e.getMessage());
			}
		}
	}

	private static void saveCropImage(String num, BufferedImage originalImage, int cropHeight, int i) throws IOException {
		// 원본 이미지에서 지정된 영역만큼 서브이미지를 리턴받는다.
		int originalImageWidth = originalImage.getWidth();
		BufferedImage subImage = originalImage.getSubimage(0, i == 0 ? 0 : (i-1) * height + firstHeight, originalImageWidth, cropHeight);

		// 새로운 BufferedImage 객체에 원본 이미지를 그린다.
		BufferedImage croppedImage = new BufferedImage(originalImageWidth, cropHeight, originalImage.getType());
		Graphics2D g = croppedImage.createGraphics();
		g.drawImage(subImage, 0, 0, originalImageWidth, cropHeight, null);
		g.dispose();

		// 파일명으로 디렉토리 만들기
		File dir = new File(PATH + num);
		if (!dir.exists()) {
			dir.mkdirs();
		}

		// 자른 이미지를 PNG 파일로 저장하기
		ImageIO.write(croppedImage, "PNG", new File(dir.getAbsolutePath() + "/" + num + "-" + i + EXTENSION));
	}
}

 

먼저 반복 작업할 파일의 시작과 끝 번호를 넣고 첫번째 표지용 이미지 높이와 그 이후 쭉 반복해서 슬라이스할 높이를 적어 넣으면 작업을 시작한다. 코드에서 사용된 주요 클래스와 메소드를 간단히 살펴보자.

 

 

ImageIO

package javax.imageio

Java 이미지 I/O를 다루는 클래스로, ImageIO 클래스의 정적 메서드를 사용하여 이미지 I/O 작업을 처리할 수 있다.

 

- read(File input)

올인원 메소드로, 현재 등록된 이미지 리더 중에서 자동으로 선택된 이미지 리더를 사용하여 제공된 파일을 디코딩한 결과로 BufferedImage를 반환한다. 매개변수로 문자열이 아닌 java.io.File 객체를 받는다.

 

- write(RenderedImage im, String formatName, File output)

지정된 형식을 지원하는 임의의 ImageWriter를 사용하여 이미지를 파일에 쓴다. 이미 File이 있는 경우 그 내용은 삭제된다. 렌더링된 이미지와 파일 포멧 형식, 저장될 java.io.File 객체를 매개변수로 받는다.

 

 

 

BufferedImage

package java.awt.image

이미지 처리에 좋은 BufferedImage 클래스는 이미지의 각 픽셀의 정보를 버퍼에 담아두고, 그 버퍼에 직접 접근하여 이미지의 정보를 바꿀 수 있다. 따라서 BufferedImage를 사용하면 이미지를 다양하게 처리할 수 있다. BufferedImage는 ColorModel과 이미지 데이터의 Raster로 구성되고, 모든 BufferedImage 객체의 좌측 상단 모서리 좌표는 (0, 0)이다. 따라서 BufferedImage를 구성하는 데 사용되는 모든 Raster는 최소 X=0과 최소 Y=0을 가져야 한다.

- getWidth(), getHeight()

width, height 값을 가져온다.

 

- getSubimage(int x, int y, int w, int h)

매개변수에 지정된 직사각형 영역으로 원본이미지에서 정의된 하위 이미지를 반환한다. 위치 매개변수인 x,y 좌표에서부터 가로세로 값인 width, height 값을 불러온다. 원본이미지에서 벗어난 값을 넣으면 예외를 반환한다.

 

- createGraphics()

BufferedImage에 그리는 데 사용할 Graphics2D 객체를 리턴한다.

 

 

 

Graphics2D

package java.awt

- drawImage(Image img, int x, int y, int width, int height, ImageObserver observer)

위 예시에서 사용한 메소드이다. 매개변수가 다르게 오버로드 된 메소드가 총 6가지가 있으니 참고.

새로 그릴 이미지의 원본이미지와,  x / y 위치값, 가로세로 길이를 넣어서 새로운 이미지를 만들 수 있다.

옵저버 옵션을 이용하면 이미지가 더 많이 변환될 때 알림을 받을 수 있다.

 

- dispose()

해당 그래픽 컨텍스트를 없애고 사용 중인 모든 시스템 리소스를 해제한다.

 

 

728x90
반응형