|
첫 번째 테크팁에서 소프트 클리핑 효과를 내는 기법을 알아보았는데, 이번에는 그 기법을 직접 활용해 보도록 하자. 본 팁에서는 조명 효과를 추가하여 평면적인 형태에 입체감을 주는 방법을 소개한다.
문건을 도해로 꾸미고 싶다면 그림을 사용하는 것이 도움이 될 것이다. 우선 밋밋하고 평면적인 왼쪽의 형태를 약간 덜 밋밋하고 광택이 나는 오른쪽의 형태로 바꾸는 방법을 살펴보자.
그림 3: 컬러를 이용한 광택 효과
올바른 컬러로 이 기법을 이용하면 형상 전체에 걸쳐 컬러 조명을 시뮬레이트하여 미묘한 광택 효과를 낼 수 있다. 그렇다면 어떻게 이런 효과를 낼 수 있는가? 우선 아래 코드를 살펴보자. drawBorderGlow()
메소드 위의 코멘트는 핵심적인 접근방식을 좀더 자세히 설명하고 있다.
import java.awt.geom.*; import java.awt.image.*; private static final Color clrHi = new Color(255, 229, 63); private static final Color clrLo = new Color(255, 105, 0); private static final Color clrGlowInnerHi
= new Color(253, 239, 175, 148); private static final Color clrGlowInnerLo
= new Color(255, 209, 0); private static final Color clrGlowOuterHi
= new Color(253, 239, 175, 124); private static final Color clrGlowOuterLo
= new Color(255, 179, 0); private Shape createClipShape() { float border = 20.0f; float x1 = border; float y1 = border; float x2 = width - border; float y2 = height - border; float adj = 3.0f; // helps round out the sharp corners float arc = 8.0f; float dcx = 0.18f * width; float cx1 = x1-dcx; float cy1 = 0.40f * height; float cx2 = x1+dcx; float cy2 = 0.50f * height; GeneralPath gp = new GeneralPath(); gp.moveTo(x1-adj, y1+adj); gp.quadTo(x1, y1, x1+adj, y1); gp.lineTo(x2-arc, y1); gp.quadTo(x2, y1, x2, y1+arc); gp.lineTo(x2, y2-arc); gp.quadTo(x2, y2, x2-arc, y2); gp.lineTo(x1+adj, y2); gp.quadTo(x1, y2, x1, y2-adj); gp.curveTo(cx2, cy2, cx1, cy1, x1-adj, y1+adj); gp.closePath(); return gp; } private BufferedImage createClipImage(Shape s) { // 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.fill(s); g2.dispose(); return img; } private static Color getMixedColor
(Color c1, float pct1, Color c2, float pct2) { float[] clr1 = c1.getComponents(null); float[] clr2 = c2.getComponents(null); for (int i = 0; i < clr1.length; i++) { clr1[i] = (clr1[i] * pct1) + (clr2[i] * pct2); } return new Color(clr1[0], clr1[1], clr1[2], clr1[3]); } // Here's the trick... To render the glow, we start with a thick pen // of the "inner" color and stroke the desired shape. Then we repeat // with increasingly thinner pens, moving closer to the "outer" color // and increasing the opacity of the color so that it appears to // fade towards the interior of the shape. We rely on the "clip shape" // having been rendered into our destination image already so that // the SRC_ATOP rule will take care of clipping out the part of the // stroke that lies outside our shape. private void paintBorderGlow(Graphics2D g2, int glowWidth) { int gw = glowWidth*2; for (int i=gw; i >= 2; i-=2) { float pct = (float)(gw - i) / (gw - 1); Color mixHi = getMixedColor(clrGlowInnerHi, pct, clrGlowOuterHi, 1.0f - pct); Color mixLo = getMixedColor(clrGlowInnerLo, pct, clrGlowOuterLo, 1.0f - pct); g2.setPaint(new GradientPaint(0.0f, height*0.25f, mixHi, 0.0f, height, mixLo)); //g2.setColor(Color.WHITE); // See my "Java 2D Trickery: Soft Clipping" entry for more // on why we use SRC_ATOP here g2.setComposite(AlphaComposite.getInstance
(AlphaComposite.SRC_ATOP, pct)); g2.setStroke(new BasicStroke(i)); g2.draw(clipShape); } } Shape clipShape = createClipShape(); //Shape clipShape = new Ellipse2D.Float
(width/4, height/4, width/2, height/2); // Clear the background to white g.setColor(Color.WHITE); g.fillRect(0, 0, width, height); // Set the clip shape BufferedImage clipImage = createClipImage(clipShape); Graphics2D g2 = clipImage.createGraphics(); // Fill the shape with a gradient g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON); g2.setComposite(AlphaComposite.SrcAtop); g2.setPaint(new GradientPaint
(0, 0, clrHi, 0, height, clrLo)); g2.fill(clipShape); // Apply the border glow effect paintBorderGlow(g2, 8); g2.dispose(); g.drawImage(clipImage, 0, 0, null);
위의 예제에서 필자가 몇 개의 '대체' 코드 행에 코멘트를 남겼다는 점에 유의할 것. 시험 삼아 이 행들의 코멘트를 지우고 렌더링에 어떤 영향을 주는지 살펴보기 바란다.
보너스: 민감한 독자라면 위의 paintBorderGlow()
메소드에 사용된 것과 동일한 기법을 이용하여 형상 주위에 드롭 섀도우를 추가할 수 있다는 것을 알 수 있을 것이다. 어떤 원리로 이것이 가능한지 짐작이 가는가? 즉, 형상 위에 경계를 렌더링하는 대신에(클립은 스트로크가 형상의 안쪽에만 작용하도록 한다는 점을 상기) 미리 형상 주위에 변화하는 회색 경계를 렌더링할 수 있다. 이는 섀도우 스트로크가 형상 바깥에 나타난다는 것을 의미하는데, 섀도우 스트로크의 안쪽 부분은 형상에 의해 효과적으로 렌더링 오버된다.
다음은 동일한 형상에 섀도우 경계를 추가하기 위해 위의 예제에 삽입할 수 있는 코드들이다.
private void paintBorderShadow(Graphics2D g2, int shadowWidth) { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); int sw = shadowWidth*2; for (int i=sw; i >= 2; i-=2) { float pct = (float)(sw - i) / (sw - 1); g2.setColor(getMixedColor(Color.LIGHT_GRAY, pct, Color.WHITE, 1.0f-pct)); g2.setStroke(new BasicStroke(i)); g2.draw(clipShape); } } // Apply the border shadow before we paint the rest of the shape paintBorderShadow(g, 6);
결과 이미지는 다음과 같다.
그림 4: 자바 2D를 이용한 드롭 섀도우 추가
이는 다만 예시 차원에서 드롭 섀도우를 추가하는 간단한 방법을 보여준 것일 뿐이다. 필자가 좀더 부지런했다면 더 밝은 회색과 비선형 램프(non-linear ramp)를 이용하여 더욱 현실감 있는 효과를 냈을 것이다. 또한, 이는 자바 2D를 이용하여 드롭 섀도우를 추가하는 수많은 방법 중 하나에 불과하다는 점에 유의할 것. Romain Guy는 다음 2개의 블로그 엔트리에서 다양한 드롭 섀도우 구현에 관해 논하고 있다: Non Rectangular Shadows 및 Fast or Good Drop Shadows. SwingLabs 팀은 SwingX 프로젝트에 DropShadowBorder
를 보유하고 있고, 현재 DropShadowPanel
을 준비중이다.
관련 상세 정보
다음 사이트에서 자바 2D 및 자바 플랫폼에 관한 자세한 정보를 구할 수 있다.