자바 2D에 익숙한 독자라면 임의 형상(arbitrary shape)을 이용하여 렌더링을 클립(clip)할 수 있다는 것을 이미 알고 있을 것이다. 예를 들어, 원형 클립을 설정한 다음 정상적으로 장면을 렌더링하면 애플리케이션에 플래시를 비추는 것 같은 효과를 낼 수 있다. 자바 2D 클리핑에 관한 보충 설명을 보려면 자바 튜토리얼(영문)을 참조하기 바란다.
복잡한 형상을 이용하여 클립할 경우에는 일반적으로 클립된 영역의 가장자리에 '계단 현상(jaggies)'이 나타나는 것을 볼 수 있는데, 이 계단 현상(jaggies)이란 렌더링된 이미지의 가장자리가 매끄럽지 못하고 우둘두둘하게 되는 현상을 말한다. 다음의 예제를 통해 필자가 '하드 클리핑'이라고 부르는 효과에 대해 알아보기로 하자.
// Clear the background to black
g.setColor(Color.BLACK);
g.fillRect(0, 0, width, height);
// Set the clip shape
g.clip(new java.awt.geom.Ellipse2D.Float(width/4, height/4, width/2, height/2));
// Fill the area with a gradient; this will be clipped by our ellipse, but
// note the ugly jaggies
g.setPaint(new GradientPaint(0, 0, Color.RED, 0, height, Color.YELLOW));
g.fillRect(0, 0, width, height);
결과 이미지는 다음과 같다.
그림 1: 클리핑을 하면 거칠고 우둘두둘한 가장자리가 생길 수 있다.
이런 거친 가장자리를 앤티앨리어싱(antialias)하여 클리핑으로 인한 계단 현상(jaggies)을 없앨 수 있다면 좋지 않을까? 하지만 불행히도 썬의 자바 2D 구현은 '소프트 클리핑'을 지원하지 않는다. 한편 필자는 위 코드를 Mac에서 시험해 보았을 때 계단 현상(jaggies)이 나타나지 않는 것을 보고 상당히 놀랐던 적이 있다. 도대체 어떻게 된 것일까? 알고 보니 Apple의 자바 2D 구현에서는 후드 아래에 Quartz를 사용하고 있었는데, 이것이 기본값으로 소프트 클리핑을 지원하는 것으로 여겨진다. Apple은 Java SE 6에서 Quartz 렌더러 대신 썬의 소프트웨어 렌더러를 기본값으로 사용할 계획이므로 본 팁은 Mac에도 해당된다고 할 수 있다.
RenderingHint
객체로 이 동작을 제어할 수 있을 거라고 기대할지 모르지만, 유감스럽게도
그것은 불가능하다. 과거에 몇몇 개발자가 소프트 클리핑을 요구한 적이 있기는 하지만, 아직까지는 우리 구현에 지원을 추가할 만큼
일반화되지는 못한 것 같다.
다행스럽게도, 우리는 중간 화상(intermediate image: 이 주제를 다룬 Chet Haases의 기사 참조)과 SrcAtop
으로 알려진 AlphaComposite
규칙을 이용하여 소프트 클리핑 효과를 내는 아주 간단한 방법을 찾아냈다. 이 예제에서 SrcIn
도 마찬가지 기능을 하지만, SrcAtop
은 소스를 단순히 교체하는 것이 아니라 소스와 데스티네이션을 혼합하는 추가적 이점을 제공한다는 점에 특히 주목할 필요가 있다. 다음 코드 단편을 시험해보자.
import java.awt.image.*;
// Clear the background to black
g.setColor(Color.BLACK);
g.fillRect(0, 0, width, height);
// Create a translucent intermediate image in which
// we can perform the soft clipping
GraphicsConfiguration gc = g.getDeviceConfiguration();
BufferedImage img = gc.createCompatibleImage
(width, height, Transparency.TRANSLUCENT);
Graphics2D g2 = img.createGraphics();
// Clear the image so all pixels have zero alpha
g2.setComposite(AlphaComposite.Clear);
g2.fillRect(0, 0, width, height);
// Render our clip shape into the image.
// Note that we enable
// antialiasing to achieve the soft clipping effect.
// Try commenting out the line that enables antialiasing,
//and you will see that you end up with the usual
// hard clipping.
g2.setComposite(AlphaComposite.Src);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.WHITE);
g2.fillOval(width/4, height/4, width/2, height/2);
// Here's the trick... We use SrcAtop, which effectively
// uses the alpha value as a coverage value for each
// pixel stored in the destination.
// For the areas outside our clip shape, the destination
// alpha will be zero, so nothing is rendered
// in those areas. For the areas inside our clip shape,
// the destination alpha will be fully opaque, so the full
// color is rendered. At the edges, the original
// antialiasing is carried over to give us the desired
// soft clipping effect.
g2.setComposite(AlphaComposite.SrcAtop);
g2.setPaint(new GradientPaint
(0, 0, Color.RED, 0, height, Color.YELLOW));
g2.fillRect(0, 0, width, height);
g2.dispose();
// Copy our intermediate image to the screen
g.drawImage(img, 0, 0, null);
그림 2의 결과 이미지를 계단 현상이 나타나는 위의 그림과 비교해보자.
그림 2: 앤티앨리어싱을 통한 소프트 클리핑 효과
더 나아 보이지 않는가? 필자도 이 예제가 다소 인위적이고 현실 세계에 적용하기는 어려울 수 있다는 점은 인정하는 바이다. 다음 테크팁에서는 임의 형상을 위한 조명 효과를 낼 때 이 기법이 어떻게 적용되는지 알아보도록 하자.