이전 시간에는 어떻게 그릴 것인가에 대해 간략하게 설명해 보았다. 이제 우리의 기대감 속에는 보다 복잡한 이미지는 어떻게 다룰 것인 지 생각하고 있을 것이다. 말하자면 복잡하게 표현된 이미지는 어떠한 경로로, 방식으로 표현되는 지 말이다. 프로세싱에서는 이러한 가능성을 쉽게 해결할 수 있는 라이브러리들을 제공하고 있다. 지난 번에 형태를 표현하면서 꼴라쥬에 대한 이야기를 한 적이 있는데 아마도 이러한 이미지편집 방식에 잘 어울릴 것이다.
지난 시간에 배웠던 점, 선, 면, 원 등의 표현에 덧붙여 이번 시간에는 어떻게 기존의 컴퓨터 이미지를 이해하고 표현할 지 설명해보자.
처음에 컴퓨터이미지를 이해하기 위해서는 기본적으로 "Pixel(픽셀)"이라는 것을 이해하는 것이 순서에 맞을 것이다. 지난 시간에 설명했던 "Coordination System"을 통해서 약간은 설명한 바 있지만 상세한 설명은 하지 않았다. 프로세싱 튜토리얼 중 파트 Images and Pixel부분에서 마음에 드는 부분 중 이런 말이 있다. "A Digital image is nothing more than data"라는 말인데 여러분들도 아시겠지만 "디지털 이미지는 단지 데이터에 지나지 않는다."라는 뜻이다. 그렇다라면 여러분들의 컴퓨터에 저장된 이미지들은 무엇을 의미하는가? 예를 들어 "xxxx.jpg"라는 이미지를 설명해보자. 여기서 "xxxx"라는 것은 다른 이름과 구별하기위한 고유한 이미지의 이름을 나타내고, "jpg"라는 것은 여러분들도 아시겠지만 이미지의 형식을 의미하는데 컴퓨터에서 나타내고자하는 이미지 데이터 정보의 저장방식을 의미한다. 물론 ISO표준에 따른 것이다.
JPEG Wiki설명을 참조한다. 이 시간에 이미지데이터세계규약에 대한 역사를 배우은 것이 아니니 대력적으로 설명하기로 하고 본론으로 들어가보자. 컴퓨터를 통해서 재생된 이미지란 실재 이미지를 Red, Green, Blue의 모니터색으로 표현하기위해 데이터로 해석하여 저장하고, 이데이터들을 불러 모니터라는 장치를 통해서 보여주는 것이다. 물론 컴퓨터에 저장된 "xxxx.jpg"는 정보에 해당하고 모니터를 통해 보인 이미지는 전기적인 신호로 재생된 빛의 집합이라고 할 수 있다. 이때 전기적인 신호로 변환하여 빛으로 환산할 때 각각의 쉐라의 그림과 같이 점묘법처럼 표현되는 방식을 일컬어 "Pixel(픽셀)"이라고 한다. 여기에서 수많은 픽셀들이 숫자들이 모이면 멀리서 보았을 때 그 점의 부분 부분이 보이기 보다는 전체의 이미지가 보이게 되고 결국 이를 우리는 이미지로 파악하게 되는 것이다. 우리가 예전에 배웠듯이 이를 빛의 "가산혼합"과 연관지어 생각해 보면 훨씬 간단하게 색을 표현할 수 있을 것이다.
그럼 더욱 쉽게 이미지를 이해하기 위해서 Processing의 예제를 들어보자.
Example: "Hello World" images
// Declaring a variable of type PImage
PImage img;
void setup() {
size(320,240);
// Make a new instance of a PImage by loading an image file
img = loadImage("mysummervacation.jpg");
}
void draw() {
background(0);
// Draw the image to the screen at coordinate (0,0)
image(img,0,0);
}
여기서 PImage란 프로세싱은 정말 다양한 클래스들을 제공하는데 이들 이미지 클래스 중 하나라 할 수 있다. "PImage"는 다양한 이미지데이터를 불러온다. 위에서는 "mysummervacation.jpg"라는 이미지를 불러왔는데 위에서말한 바와 같이 "이름.jpg"라는 "jpg"형식의 이미지를 불러온 것이다. 따라서 첫번째로 변수 "PImage img;"를 선언해 주었고, 아래 img=loadImage("mysummervacation.jpg");를 통해 이미지를 메모리에 위치시키기 위해 불러왔다. 이로서 준비작업인 "void setup(){}"은 끝난 셈이다. 이렇듯 이용할 물감과 붓이 준비가 되면 그리면 되는 것이다. 프로그래밍에서 그만큼 순서가 중요하다 할 수 있다. 준비가 되지 않으면 당연히 그림을 그릴 수 없을 것이다.
이제 그려보자. "void draw(){}"는 바로 이러한 작업을 수행한다. 먼저 그릴 그림의 캔버스의 색을 지정한다. "background(0);"을 통해 색을 검은색으로 지정하고, "image(img, 0,0);"을 통해서 이미지를 위치시킨다. 물론 여기서 "img"는 위 치시킨 이미지데이터를 의미하고 0,0은 이미지의 위치를 의미하는데 이미지 왼쪽 코너를 의미한다. 프로세싱프로그래밍 창에서 "image"를 선택하고 래퍼런스를 보자. 그러면 보다 자세한 "image"의 변수에 대한 내용을 확인할 수 있을 것이다.데이터폴더: 프로세싱에서는 어떻게 데이터를 운영할 수 있을까?우리는 아래와 같이 데이터폴더에 이미지를 자동으로 추가할 수 있다.
Sketch -->Add File...
또는 수동으로
Sketch --> Show Sketch Folder를 통해 스케치 폴더를 불로올 수 있다. 만일 스케치폴더에 불러오려는 이미지 데이터가 없다면 생성한다. 프로세싱은 이미지 타입으로 GIF, JPG, TGA, PNG 허용한다.
위의 예제에서 보면 클래스 "PImage"의 보통의 자바프로그래밍에서 보는 것처럼 특정한 초기화(initiation) 과정을 거치는 것 을 볼 수 있다. 예를 들자면 "PImage img = new PImage();"와는 다르게; 이는 보통의 경우 자바프로그래밍에서와 같이 PImage()클래스를 "PImage img"로서 이용하기 위해서 보통 초기화하는 과정이다. "PImage img = loadImage("mysummervacation.jpg")"는 이러한 보통의 초기화와는 다른 형태를 볼 수 있다. 말하자면 intiation(초기화)의 역할을 하는 기능은 "loadImage("mysummervacation.jpg")"인 것이다. 물론 초기화의 과정을 통해 데이터초기화한 이미지가 "mysummervacation.jpg"일 것은 당연한 것이다. 만일 여러분들이 완전히 "nullpoint"에러를 피하여 캔버스를 초기화하고 싶다면 "createImage()"를 사용하기를 바란다.
예)createImage()
PImage img = createImage(66, 66, RGB);
img.loadPixels();
for (int i = 0; i < img.pixels.length; i++) {
img.pixels[i] = color(0, 90, 102);
}
img.updatePixels();
image(img, 17, 17);
PImage img = createImage(66, 66, ARGB);지정된 이름의 새로운 "PImage"의 인스턴스로서 "loadImage()"가 활용되는 것처럼,빈이미지를 생성하기위서는 위와같이"createImage()" function이 이용된다.
img.loadPixels();
for (int i = 0; i < img.pixels.length; i++) {
img.pixels[i] = color(0, 90, 102, i % img.width * 2);
}
img.updatePixels();
image(img, 17, 17);
image(img, 34, 34);
// Create a blank image, 200x200 pixels with RGB color
PImage img = createImage(200,200,RGB);우리가 또한 알아야할 것이 있는데 이미지를 불러오는 과정은 하드디스크에서 이미지데이터를 메모리에 위치시키는 과정이라고 할 수 있다. 매번 수행할 때 이미지를 불러온다면 과정이 느려질 수 있을 것이다. 때문에 처음 "void setup()"에서 이미지를 한 번 불러와 이미지들을 메모리에 위치시키는 작업을 하는 것이 프로세스의 속도를 위해서 좋은 방법이라할 수 있을 것이다. 만일 그렇지 않고 void draw()를 통해서 이미지를 불러오기할 경우 처리속도가 느려질 뿐만 아니라 "Out of Memory"의 에러가 발생할 수도 있다.
한 번 불어온 이미지를 표현하기 위해서는 "image(아규먼트1, 아규먼트2, 아규먼트3)"에서는 보통 3개의 아규먼트를 취하는데 이미지데이터 이름과 x, y 위치이다. 물론 "pixel"단위로 표현된다. 물론 2개의 아규먼트가 추가될 수 있는데 이미지를 resize할 경우 이용된다.
image(img,10,20,90,60);Background Image: 배경이미지
자 이제 배경이미지에 대해서 이야기를 해보자. 물론 "PImage"를 통해서 이미지를 인식하고 "loadImage()"를 통해서 이미지를 불러온다. 그리고 프로세싱의 배경이미지기능인 "background()"에 이미지를 입히면 된다. 프로세싱에서 examples 중 이미지, "background"를 열어보자. 아래의 예제는 이러한 배경이미지에 대한 예라 할 수 있는데 모듈로를 이용한 그래픽이미지 반복을 코딩으로 마무리하였다.
PImage bg;
int a;
void setup()
{
size(200,200);
frameRate(30);
// The background image must be the same size as the parameters
// into the size() method. In this program, the size of "milan_rubbish.jpg"
// is 200 x 200 pixels.
bg = loadImage("milan_rubbish.jpg");
}
void draw()
{
background(bg);
a = (a + 1)%(width+32);
stroke(226, 204, 0);
line(0, a, width, a-26);
line(0, a-6, width, a-32);
}
Basic Image Processing
이제 이미지를 어떻게 다룰 지에 대해서 이야기해 보자. 여러분들은 아마도 이미지를 표현할 때 어떻게 어둡게 표현할 수 있을 지 혹은 밝게 표현할 수 있을 지, 그리고 콘트라스트는 어떻게 줄 수 있는 지 등 등 질문을 할 수 있을 것이다. 물론 이러한 궁금증들을 해결할 수 있도록 프로세싱은 클래스들을 제공한다. 그 중 알려져있는 것이 "tint()"이다. 주어진 R.G.B혹은 칼라색의 양을 조절하며 마지막으로 투명도를 제어한다.
만일 "tint()"가 하나의 아규먼트를 받으면 아래와 같이 밝기의 정도에 영향을 미친다.
PImage sunflower = loadImage("sunflower.jpg");
PImage dog = loadImage("dog.jpg");
background(dog);
// The image retains its original state.
tint(255);
image(sunflower,0,0);
//The image appears darker.두번째 아규먼트를 받으면 투명도를 결정한다. 때문에 아래와 같이 두개의 이미지가 겹쳐 보이게 될 것이다.
tint(100);
image(sunflower,0,0);
// The image is at 50% opacity.세개의 아규먼트를 받게되면 R.G.B색의 정도를 의미힌다. tint(r, g, b)가 붉은색, 녹색, 파란색을 나타낸다고 할 경우, 아래의 경우 red는 0, green은 200, blue는 255에 해당된다.
tint(255,127);
image(sunflower,0,0);
// None of its red, most of its green, and all of its blue.마지막으로 네번째 아규먼트를 추가하면 (r,g,b, 투명도)와 같이 투명도를 나타낸다.
tint(0,200,255);
image(sunflower,0,0);
// The image is tinted red and transparent.이렇듯 이미지프로세싱에 필요한 기본적인 픽셀을 어떻게 조정할 수 있는지를 배웠는데 "tint()"의 경우 "colorMode()"를 통해서 색상의 방식; R,G,B, HSV, 등을 정할 수 있다.
tint(255,0,0,100);
image(sunflower,0,0);
Transparency: 투명도
자 위에서 배웠던 간단한 이미지 프로세싱을 이용하여 투명도를 이용한 코드를 작성하여보자. 프로세싱 examples에서 image/transparency 예제를 열어보자. 이 예제를 보면 마우스x의 움직임에 따라서 위의 겹쳐진 이미지가 좌우로 움직이는 것을 볼 수 있다. 코드 "float offsetTarget = map(mouseX, 0, width, -b.width/2 - width/2, 0);"는 "offsetTarget" 값을 계산하는 방식을 나타내는데 "map()"은 마우스의 위치를 rescale하는데 0에서 디스플레이너비의 값에서 "-b.width/2 - width/2"에서 0으로 리스케일하였으므로 여러분들이 생각할 때 마우스의 위치의 디폴트 값이 0이라 할 때, 그림의 위치가 대충 -위치에 위치하게 된다는 것을 알수 있을 것이다. 그리고 마우스를 우측으로 옮김에 따라서 오른쪽으로 이미지가 움직이게된다. 물론 이미지가 아래의 이미지를 투명한 상태로 가리려면 충분한 너비를 갖고 있어야할 것이다. 이미지가 최소한 얼마만큼의 너비를 가져야하는 지 계산해보자. 또한 주의해야할 것은 "tint(value1, value2)"의 경우 "value1"은 색이고 "value2"는 투명도를 나타낸다.
PImage a, b;
float offset;
void setup() {
size(200, 200);
a = loadImage("construct.jpg"); // Load an image into the program
b = loadImage("wash.jpg"); // Load an image into the program
}
void draw() {
image(a, 0, 0);
float offsetTarget = map(mouseX, 0, width, -b.width/2 - width/2, 0);
offset += (offsetTarget-offset)*0.05;
tint(255, 153);
image(b, offset, 20);
}