{{ label!='' ? 'Label : ' : (q!='' ? '검색 : ' : '전체 게시글') }} {{ label }} {{ q }} {{ ('('+(pubs|date:'yyyy-MM')+')') }}

[Java] 자바로 프로그래밍 입문하기: 3.1. 자료형 (2)

색(Color)

  색은 전자기파를 눈으로 느낄 때 볼 수 있는 것입니다. 우리는 컴퓨터에서 이미지를 생성하고 싶어 했습니다. 과거로부터 색은 컴퓨터 그래픽에서 범용적으로 사용된 추상화의 일종입니다. 자바 역시 Color 자료형을 지원합니다. 출판, 웹과 같은 전문 분야에서 색을 이용해 작업을 처리하는 것은 꽤 복잡한 일이죠. 컬러 사진의 형상은 이미지를 표현하는 매체에 따라서 크게 달라지기 때문입니다. 디자이너는 원하는 색깔을 시스템이 완벽하게 재현할 수 있을지에 대해 고민하지만, Color 자료형은 이러한 고민으로부터 디자이너를 완벽하게 분리해냅니다.

  자바는 수많은 자료형을 라이브러리에 내장하고 있습니다. 우리 프로그램을 작성하면서, 수많은 라이브러리를 사용하게 될 것입니다. 이 때, 같은 이름을 사용하는 자료형이 있을 수도 있습니다. 우리는 이러한 충돌을 해결하기 위해, 명시적으로 어떤 라이브러리를 사용할지에 대한 목록을 작성합니다. 다음 구문은 Color를 사용하고 싶은 프로그램이라면 반드시 포함해야 할 구문입니다. 여태까지 우리는 표준 자바 라이브러리만을 사용하고 있었기 때문에, 이러한 작업이 필요 없었습니다.


import java.awt.Color;

  색의 값을 표현하기 위해, Color는 RGB 체계를 사용합니다. 여기서 색은 0~255 사이의 3가지 정수로 정의되죠. 각각 적색, 녹색, 청색의 채도를 결정합니다. 다른 색의 값들은 전부 적색, 녹색, 청색을 조합하여 만들어냅니다. 따라서, Color의 값은 3개의 8-bit 정수로 구성됩니다. Color가 int, short, char 값 중 어느 것을 사용하는지 굳이 알 필요는 없습니다만, 어쨌거나 자바는 색을 표현하기 위해 24 비트를 사용하고 있습니다. 1700만 여가지의 색을 표현할 수 있죠. 과학자들은 인간이 천만 개의 색을 구분할 수 있다고 합니다.

  Color는 생성자로 세 정수 인자를 받습니다. 따라서 여러분은 다음과 같이 쓸 수 있겠죠.


Color red = new Color(255, 0, 0);
Color bopokBlue = new Color(9, 90, 166);

  역자가 번역하고 있는 이 책을 인쇄하기 위해 필요한 색의 조합이라고 합니다. StdDraw도 색을 칠할 수 있었지만, 미리 정의된 색에 한했습니다. StdDraw.BLACK, StdDraw.RED, StdDraw.PINK와 같은 것들이죠. 이제는 수백 만의 색들을 이용할 수 있습니다. 


프로그램 3.1.2: Albers squares

import java.awt.Color;

public class AlbersSquares {
	public static void main(String[] args) { 	
                // Display Albers squares for the two RGB
		// colors entered on the command line,
		int rl = Integer.parseInt(args[0]);
		int gl = Integer.parseInt(args[1]);
		int bl = Integer.parseInt(args[2]);
		Color cl = new Color(rl, gl, bl);
		int r2 = Integer.parseInt(args[3]);
		int g2 = Integer.parseInt(args[4]);
		int b2 = Integer.parseInt(args[5]);
		Color c2 = new Color(r2, g2, b2);
		StdDraw.setPenColor(cl);
		StdDraw.filledSquare(.25, .5, .2);
		StdDraw.setPenColor(c2);
		StdDraw.filledSquare(.25, .5, .1);
		StdDraw.setPenColor(c2);
		StdDraw.filledSquare(.75, .5, .2);
		StdDraw.setPenColor(cl);
		StdDraw.filledSquare(.75, .5, .1);
	}
}

  늘 그랬듯이, 여러분에게 Color라는 자바 컬러 모델의 필수적인 요소를 소개해볼까 합니다. Color API는 몇몇 개의 생성자와 20개가 넘는 메소드로 구성되어 있습니다. 중요한 몇 개만 봐보도록 하죠.

자바 Color 자료형의 API 일부

  우리는 Color를 사용함과 동시에, 이것을 좀 더 편리하고 유용하게 사용할 수 있도록 몇몇 도구를 만들어볼 것입니다. 물론, 객체지향 프로그래밍이 무엇인가에 대해서도 알아봐야겠죠. 위 예제를 통해, 여러분들은 객체지향 코드가 색과 같은 추상화 개념을 처리하는데 얼마나 편리한지 납득할 수 있을 것입니다.


휘도(Luminance)

  LCD 모니터, 플라즈마 TV, 핸드폰 화면과 같은 현대 디스플레이의 이미지 품질은 색의 속성에 대해 이해하는 것으로부터 결정됩니다. 예를 들어 단색 휘도(monochrome luminance), 유효 밝기(effective brightness)와 같은 것들이죠. 휘도의 표준 공식은 적색, 녹색, 청색에 대한 눈의 민감도로부터 유도됩니다. 이 공식은 선형적인 조합으로, 적색, 녹색, 청색을 각각 r, g, b라고 할 때 다음 등식을 따릅니다.

Y = 0.299r + 0.587g + 0.114b

  각 계수가 모두 양수이고 합이 1이며, 각 변수는 0부터 255 사이의 값이므로, 휘도는 0부터 255 사이의 실수가 됩니다.


그레이스케일(Grayscale)

  RGB 색상 모델은 세 가지 색의 채도가 같으면 검정색(모두 0)에서 흰색(모두 255)이 되는, 일련의 그레이스케일의 색상이 된다는 특징이 있습니다. 컬러 사진을 흑백 신문에나 책에 인쇄하려면, 컬러를 그레이스케일로 변환하는 함수가 필요하죠. 빨강, 초록, 파랑 값을 이용해 회색 휘도를 계산하면 컬러를 그레이스케일로 쉽게 변환할 수 있습니다.


색 호환성

  휘도 값은 두 색의 호환성을 결정하는데 중요합니다. 예를 들어, 배경의 색과 글자의 색이 읽기에 좋은지와 같은 것들이죠. 널리 이용되는 최고의 규칙은, 전경과 배경의 휘도가 최소한 128 이상 차이를 내는 것입니다. 예를 들어, 흰 배경에 검정 글자는 255의 휘도 차이를 갖습니다. 반면 청색 배경에 검정 글자는 휘도 차이가 74밖에 안 되죠. 이는 광고, 표지판, 웹사이트 등 수많은 응용 프로그램의 디자인에서도 중요한 규칙입니다.

  <Luminance>는 색을 그레이스케일로 변환하고, 두 색의 호환성이 적합한지 검증해주는 정적 메소드를 지닌 라이브러리입니다. <Luminance> 안의 메소드들은 정보를 다루기 위해 자료형을 사용하는 것이 얼마나 용이한지 보여줍니다. Color 참조형을 이용해 인자로 넘겨주는 것이 채도 값 3개를 넘겨주는 것보다 훨씬 간단합니다. 여러 개의 값을 반환하는 것 또한 참조형이 없었더라면 약간의 고민거리가 되었겠죠.


프로그램 3.1.3: Luminance library

import java.awt.Color;

public class Luminance {
	public static double lum(Color color) {
 	// Compute luminance of color.
		int r = color.getRed();
		int g = color.getGreen();
		int b = color.getBlue();
		return .299 * r + .587 * g + .114 * b;
	}

	public static Color toGray(Color color) {
 	// Use luminance to convert to grayscale.
		int y = (int) Math.round(lum(color));
		Color gray = new Color(y, y, y);
		return gray;
	}

	public static boolean compatible(Color a, Color b) {
 	// Print true if colors are compatible, false otherwise,
		return Math.abs(lum(a) - lum(b)) >= 128;
	}

	public static void main(String[] args) {
 	// Are the two given RGB colors compatible?
		int[] a = new int[6];
		for (int i = 0; i < 6; i++)
			a[i] = Integer.parseInt(args[i]);
		Color cl = new Color(a[0], a[1], a[2]);
		Color c2 = new Color(a[3], a[4], a[5]);
		StdOut.println(compatible(cl, c2));
	}
}
% java Luminance 232 232 232 0 0 0
true
% java Luminance 9 90 166 232 232 232
true
% java Luminance 9 90 166 0 0 0
false


  색을 추상화하는 것은 이를 직접 사용할 수 있을 뿐만 아니라, Color 값을 가지는 더 상위의 자료형을 만들 수 있게 해줍니다. 다음은 디지털 이미지를 처리하는 프로그램을 작성하기 위해, 색 추상화를 포함하는 자료형을 만들어보죠.


디지털 이미지 처리

  여러분은 사진이라는 개념을 다들 알고 계실 것입니다. 기술적으로, 사진이란 한 순간의 전자기파로부터 발생하는 가시광선을 모아 2차원 이미지로 만들어진 것입니다. 여러분의 카메라 및 핸드폰은, 렌즈와 디지털 형태로 이미지를 포착하는 빛 센서, 이미지 처리 소프트웨어를 지닌 컴퓨터로 구성되어 있습니다. 여러분은 이미지를 자를 수도 있고, 확대 및 축소하고, 대비와 밝기를 조절하고, 적목 현상을 제거하는 등 수많은 연산을 수행할 수 있습니다. 디지털 이미지를 다루는 자료형만 있다면, 여러분들은 이러한 연산을 쉽게 구현할 수 있습니다. 직접 해보죠.


디지털 이미지

  우리는 StdDraw를 이용해서 컴퓨터 화면에 기하학적 객체(점, 선, 원, 사각형)들을 그려왔습니다. 디지털 이미지를 처리하기 위해 어떤 값들이 필요하고, 값들을 다루기 위한 어떤 연산들이 필요할까요? 컴퓨터 화면의 기본적 추상화는 디지털 사진에서 사용된 것과 별반 다르지 않습니다. 그리고 꽤 간단하죠. 디지털 이미지는 픽셀로 이루어진 격자이며, 픽셀의 색은 각각 정의되어 있습니다. 디지털 이미지의 종류는 래스터(raster)나 비트맵(bitmap) 이미지, 그리고 우리가 StdDraw로 만들어낼 이미지인 벡터(vector) 이미지 등이 있습니다.

디지털 이미지의 구조


  <Picture> 클래스는 디지털 이미지 추상화를 구현한 자료형입니다. 값들은 Color값의 2차원 행렬 그 이상 그 이하도 아닙니다. 연산은 다음과 같습니다. 이미지(주어진 높이와 폭의 비어있는 이미지 혹은 사진 파일으로부터 초기화 된 이미지)를 생성하는 연산, 픽셀 값들을 주어진 색으로 설정하는 연산, 주어진 픽셀의 색을 반환하는 연산, 컴퓨터 화면에 이미지를 보여주는 연산, 이미지를 파일로 저장하는 연산입니다. 우리는 배열이라는 용어 대신 행렬을 사용할 것인데, 이는 우리가 하는 것이 특정한 구현(Color 객체의 2차원 배열)이 아닌 추상화(픽셀들의 행렬)임을 강조하기 위함입니다.

  "자료형을 사용하기 위해, 세부 구현을 알 필요는 없습니다." 전형적인 이미지는 수많은 픽셀들을 갖고 있습니다. 따라서 구현 시 Color 배열을 사용하는 것보다 좀 더 효율적인 방법을 사용할 수도 있죠. 이러한 것들은 흥미로운 고려사항이지만, 클라이언트 코드와 별개로 처리할 수 있는 것들입니다. 이미지를 생성하는 클라이언트 프로그램을 작성하기 위해서는, 단지 다음 API들만 숙지하면 됩니다.(Std* 라이브러리와 함께 이미 포함되어 있습니다)

이미지 처리를 위한 자료형의 API

  관습 상, (0, 0)은 가장 좌측상단의 픽셀입니다. 배열과 함께 직관적으로 생각하기 위함이죠.(반면, StdDraw는 가장 좌측 하단이 (0, 0)이었습니다. 이는 데카르트 좌표계를 표현하기 위함이었습니다) 대부분의 이미지 처리 프로그램은 원본 이미지의 픽셀들을 2차원 배열로 나누어 검사하여, 각 픽셀의 색을 결정하는 일종의 필터입니다. .png와 .jpg 파일 형식을 지원하므로, 여러분은 언제든지 본인의 사진을 처리하고 웹사이트나 앨범에 게재할 수 있습니다. 


그레이스케일(Grayscale)

  웹사이트에서 수많은 컬러 이미지 예제들을 찾아볼 수 있고, 우리의 메소드들은 컬러 이미지에도 효과적이지만, 글로서 보는 것은 그레이스케일로만 한정합시다.(책은 컬러 책이 아니기 때문이죠) 우리의 첫 과제는 이미지를 흑백으로 변환하는 것입니다.


프로그램 3.1.4: Converting color to grayscale

 
import java.awt.Color;

public class Grayscale {
	public static void main(String[] args) {
	 	// Show image in grayscale.
		Picture pic = new Picture(args[0]);
		for (int x = 0; x < pic.width(); x++) {
			for (int y = 0; y < pic.height(); y++) {
				Color color = pic.get(x, y);
				Color gray = Luminance.toGray(color);
				pic.set(x, y, gray);
			}
		}
		pic.show();
	}
}
% java Grayscale mandrill.jpg



  <Grayscale>은 파일 이름을 명령행 인자로 받아, 해당 이미지를 흑백 이미지로 변환해주는 일종의 필터입니다. 새로운 Picture 객체를 초기화하여 이미지를 다룰 준비를 한 뒤, 각 픽셀을 <Luminance>의 toGray() 메소드에 의해 계산된 그레이스케일 색을 갖는 Color 객체로 치환해 흑백 이미지를 생성합니다.


스케일링(Scaling, 크기 조절)

  가장 흔한 이미지 처리 작업 중 하나는 이미지를 작거나 크게 만드는 것입니다. 스케일링이라고 불리는 기본 연산이죠. 채팅방이나 핸드폰에 사용할 썸네일 사진을 만들거나, 고해상도 사진을 웹 페이지의 크기에 맞게 조절하거나, 위성 이미지 혹은 현미경 이미지를 다룰 때 사용됩니다. 광학 시스템에서는 단순히 렌즈를 이동해서 이를 해결할 수 있지만, 디지털 이미지에서는 좀 더 많은 작업들을 처리해주어야 합니다.

  몇몇 경우는 명쾌하게 해결할 수 있습니다. 예를 들어, 반절의 축소하는 경우라면 단순히 반절의 픽셀을 선택하고, 반절의 픽셀을 삭제하면 됩니다. 이러한 기법을 샘플링(sampling)이라고 합니다. 두 배로 확대하는 경우도 마찬가지입니다. 한 픽셀을 같은 색상인 4개의 픽셀로 늘려서 표현하면 되죠. 이러한 방법은 이미지 손실을 일으키므로, 반절로 축소했다가 다시 두 배로 확대했을 때 원본이 보존되지는 않음에 유의하세요.

  단 하나의 전략으로 이미지의 축소 및 확대를 해결할 수도 있습니다. 각 픽셀을 하나씩 순회하면서, 해당하는 좌표가 원본 이미지의 어느 부분에 속하는지를 계산해 픽셀의 색을 정하는 방법입니다. 만약 원본 이미지의 높이와 너비가 ws, hs이고 목표 이미지의 높이와 너비가 wt, ht일 때, 너비는 ws/wt, 높이는 hs/ht만큼 스케일링하는 것입니다. 만약 (x, y)의 색상을 구하고 싶다면, 원본 이미지의 (x*ws/wt, y*hs/ht)를 참조하면 됩니다. <Scale>은 이와 같은 전략을 구현합니다. 


프로그램 3.1.5: Image scaling

public class Scale {
	public static void main(String[] args) {
		int w = Integer.parseInt(args[1]);
		int h = Integer.parseInt(args[2]);
		Picture source = new Picture(args[0]);
		Picture target = new Picture(w, h);
		for (int tx = 0; tx < w; tx++) {
			for (int ty = 0; ty < h; ty++) {
				int sx = tx * source.width() / w;
				int sy = ty * source.height() / h;
				target.set(tx, ty, source.get(sx, sy));
			}
		}
		source.show();
		target.show();
	}
}
% java Scale mandrill.jpg 600 300



  <Scale>은 이와 같은 전략을 구현합니다. 실제 상용 프로그램에서 이렇게 단순한 방식으로 스케일링을 처리하지는 않습니다. 이보다 좀 더 세련된 전략들도 존재하죠. 예를 들면, 반절로 축소할 때 한 픽셀의 색상을 네 개의 픽셀 색상의 평균값으로 정하는 것입니다. 더 나아가면 고급진 알고리즘을 통해, 변화가 많은 점의 색상을 선택하여 좀 더 자연스럽게 축소 및 확대를 하는 전략도 있습니다.

  원본 픽셀의 색상 값을 인자로 함수에서 각 픽셀의 색상 값을 계산해내는, 이런 기초적인 개념은 어떤 이미지 처리 작업이든 효과적으로 사용됩니다.


페이드 효과(Fade effect)

  다음 이미지 처리 예제는 엔터테인먼트 분야에서 사용되는 것입니다. 단계적으로 한 이미지에서 다른 이미지로 전환하는 효과죠. 이러한 전환을 페이드 효과라고 합니다. <Fade>는 선형 보간법(linear interpolation) 전략을 사용하는 <Picture>와 <Color>의 클라이언트입니다. 

  M-1개의 이미지들을 생성하며, t번째 이미지의 각 픽셀들은 원본과 대상 이미지의 가중 평균입니다. 정적 메소드 blend()는 보간을 구현합니다. 원본 색상은 1 - t / M의 가중 인자를 지니며, 대상 색상은 t / M이죠. 예를 들어, t가 0이면 원본 색상은 온전히 유지되며, 반면 대상 색상은 존재하지 않습니다. 이 간단한 계산은 꽤 멋진 결과를 불러옵니다.


프로그램 3.1.6: Fade effect

import java.awt.Color;

public class Fade {
	public static Color blend(Color c, Color d, double alpha) {
	 	// Compute blend of c and d, weighted by x.
		double r = (1 - alpha) * c.getRed() + alpha * d.getRed();
		double g = (1 - alpha) * c.getGreen() + alpha * d.getGreen();
		double b = (1 - alpha) * c.getBlue() + alpha * d.getBlue();
		return new Color((int) r, (int) g, (int) b);
	}

	public static void main(String[] args) {
	 	// Show M-image fade sequence from source to target.
		Picture source = new Picture(args[0]);
		Picture target = new Picture(args[1]);
		int M = Integer.parseInt(args[2]);
		int width = source.width();
		int height = source.height();
		Picture pic = new Picture(width, height);
		for (int t = 0; t <= M; t++) {
			for (int x = 0; x < width; x++) {
				for (int y = 0; y < height; y++) {
					Color c0 = source.get(x, y);
					Color cM = target.get(x, y);
					Color c = blend(c0, cM, (double) t / M);
					pic.set(x, y, c);
				}
			}
			pic.show();
		}
	}
}

% java Fade darwin.jpg mandrill.jpg 3


계속.

댓글

이 블로그의 인기 게시물

[코딩의탑] 4층: 툰 쉐이딩

[코딩의탑] 3층: 바다 렌더링

[코딩의탑] 5층: 포탈(Portal), 더 나아가기