diff --git a/examples/src/main/java/edu/umd/cs/piccolo/examples/ExampleRunner.java b/examples/src/main/java/edu/umd/cs/piccolo/examples/ExampleRunner.java index 0a2adb7..f8f856d 100644 --- a/examples/src/main/java/edu/umd/cs/piccolo/examples/ExampleRunner.java +++ b/examples/src/main/java/edu/umd/cs/piccolo/examples/ExampleRunner.java @@ -111,9 +111,9 @@ KeyEventFocusExample.class, LayoutExample.class, LensExample.class, NavigationExample.class, NodeCacheExample.class, NodeEventExample.class, NodeExample.class, NodeLinkExample.class, PanToExample.class, PathExample.class, PositionExample.class, PositionPathActivityExample.class, - PulseExample.class, ScrollingExample.class, SelectionExample.class, SquiggleExample.class, - StickyExample.class, StickyHandleLayerExample.class, StrokeExample.class, TextExample.class, - TooltipExample.class, TwoCanvasExample.class, WaitForActivitiesExample.class }); + PulseExample.class, ScrollingExample.class, SelectionExample.class, ShadowExample.class, + SquiggleExample.class, StickyExample.class, StickyHandleLayerExample.class, StrokeExample.class, + TextExample.class, TooltipExample.class, TwoCanvasExample.class, WaitForActivitiesExample.class }); } private void addExampleButtons(final JPanel panel, final Class[] exampleClasses) { diff --git a/examples/src/main/java/edu/umd/cs/piccolo/examples/ShadowExample.java b/examples/src/main/java/edu/umd/cs/piccolo/examples/ShadowExample.java new file mode 100755 index 0000000..7c68e3e --- /dev/null +++ b/examples/src/main/java/edu/umd/cs/piccolo/examples/ShadowExample.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2008-2009, Piccolo2D project, http://piccolo2d.org + * Copyright (c) 1998-2008, University of Maryland + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + * and the following disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * None of the name of the University of Maryland, the name of the Piccolo2D project, or the names of its + * contributors may be used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package edu.umd.cs.piccolo.examples; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Paint; + +import java.awt.image.BufferedImage; + +import edu.umd.cs.piccolo.PCanvas; + +import edu.umd.cs.piccolo.nodes.PImage; +import edu.umd.cs.piccolo.nodes.PPath; +import edu.umd.cs.piccolo.nodes.PText; + +import edu.umd.cs.piccolox.PFrame; + +import edu.umd.cs.piccolox.nodes.PShadow; + +/** + * Shadow example. + */ +public final class ShadowExample extends PFrame { + + /** + * Create a new shadow example. + */ + public ShadowExample() { + this(null); + } + + /** + * Create a new shadow example with the specified canvas. + * + * @param canvas canvas for this shadow example + */ + public ShadowExample(final PCanvas canvas) { + super("ShadowExample", false, canvas); + } + + + /** {@inheritDoc} */ + public void initialize() { + BufferedImage src = new BufferedImage(75, 75, BufferedImage.TYPE_INT_ARGB); + Paint shadowPaint = new Color(20, 20, 20, 200); + Graphics2D g = src.createGraphics(); + g.setPaint(Color.RED); + g.fillRect(0, 0, 75, 75); + g.dispose(); + + PText header1 = new PText("Shadow nodes drawn from an image, with increasing blur radius:"); + getCanvas().getLayer().addChild(header1); + + double x = 25.0d; + double y = 25.0d; + + for (int blurRadius = 4; blurRadius < 28; blurRadius += 4) { + PImage node = new PImage(src); + PShadow shadowNode = new PShadow(src, shadowPaint, blurRadius); + + node.setOffset(x, y); + // offset the shadow to account for blur radius offset and light direction + shadowNode.setOffset(x - (2 * blurRadius) + 5.0d, y - (2 * blurRadius) + 5.0d); + + // add shadow node before node, or set Z explicitly (e.g. sendToBack()) + getCanvas().getLayer().addChild(shadowNode); + getCanvas().getLayer().addChild(node); + + x += 125.0d; + if (x > 300.0d) { + y += 125.0d; + x = 25.0d; + } + } + + PText header2 = new PText("Shadow nodes drawn from node.toImage():"); + header2.setOffset(0.0d, 300.0d); + getCanvas().getLayer().addChild(header2); + + PPath rectNode = PPath.createRectangle(0.0f, 0.0f, 75.0f, 75.0f); + rectNode.setPaint(Color.RED); + rectNode.setStroke(null); + rectNode.setOffset(25.0d, 325.0d); + + PShadow rectShadow = new PShadow(rectNode.toImage(), shadowPaint, 8); + rectShadow.setOffset(25.0d - (2 * 8) + 5.0d, 325.0d - (2 * 8) + 5.0d); + + getCanvas().getLayer().addChild(rectShadow); + getCanvas().getLayer().addChild(rectNode); + + PText textNode = new PText("Shadow Text"); + textNode.setTextPaint(Color.RED); + textNode.setFont(textNode.getFont().deriveFont(36.0f)); + textNode.setOffset(125.0d, 325.0d); + + PShadow textShadow = new PShadow(textNode.toImage(), shadowPaint, 8); + textShadow.setOffset(125.0d - (2 * 8) + 2.5d, 325.0d - (2 * 8) + 2.5d); + + getCanvas().getLayer().addChild(textShadow); + getCanvas().getLayer().addChild(textNode); + } + + /** + * Main. + * + * @param args command line arguments, ignored + */ + public static void main(final String[] args) { + new ShadowExample(); + } +} \ No newline at end of file diff --git a/extras/src/main/java/edu/umd/cs/piccolox/nodes/PShadow.java b/extras/src/main/java/edu/umd/cs/piccolox/nodes/PShadow.java new file mode 100755 index 0000000..c6683a0 --- /dev/null +++ b/extras/src/main/java/edu/umd/cs/piccolox/nodes/PShadow.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2008-2009, Piccolo2D project, http://piccolo2d.org + * Copyright (c) 1998-2008, University of Maryland + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + * and the following disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * None of the name of the University of Maryland, the name of the Piccolo2D project, or the names of its + * contributors may be used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package edu.umd.cs.piccolox.nodes; + +import java.awt.Image; +import java.awt.Paint; + +import java.awt.image.BufferedImage; + +import edu.umd.cs.piccolo.nodes.PImage; + +import edu.umd.cs.piccolox.util.ShadowUtils; + +/** + * Shadow node. + */ +public final class PShadow extends PImage { + + /** + * Create a new shadow node containing a shadow of the specified source image using the + * specified shadow paint and gaussian blur radius. The dimensions of this node will be + * src.getWidth() + 4 * blurRadius x src.getHeight() + 4 * blurRadius + * to account for blurring beyond the bounds of the source image. Thus the source image + * will appear to be be offset by (2 * blurRadius, 2 * blurRadius) + * in this node. + * + * @param src source image, must not be null + * @param shadowPaint shadow paint + * @param blurRadius gaussian blur radius, must be > 0 + */ + public PShadow(final Image src, final Paint shadowPaint, final int blurRadius) { + super(ShadowUtils.createShadow(src, shadowPaint, blurRadius)); + } +} \ No newline at end of file diff --git a/extras/src/main/java/edu/umd/cs/piccolox/util/ShadowUtils.java b/extras/src/main/java/edu/umd/cs/piccolox/util/ShadowUtils.java new file mode 100755 index 0000000..9c67b55 --- /dev/null +++ b/extras/src/main/java/edu/umd/cs/piccolox/util/ShadowUtils.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2008-2009, Piccolo2D project, http://piccolo2d.org + * Copyright (c) 1998-2008, University of Maryland + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + * and the following disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * None of the name of the University of Maryland, the name of the Piccolo2D project, or the names of its + * contributors may be used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package edu.umd.cs.piccolox.util; + +import java.awt.AlphaComposite; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Paint; + +import java.awt.image.BufferedImage; +import java.awt.image.ConvolveOp; +import java.awt.image.Kernel; + +/** + * Static utility methods for creating shadows. + */ +public final class ShadowUtils { + + /** + * Private no-arg constructor. + */ + private ShadowUtils() { + // empty + } + + + /** + * Create and return a new buffered image containing a shadow of the specified source image + * using the specifed shadow paint and gaussian blur radius. The dimensions of the returned image will be + * src.getWidth() + 4 * blurRadius x src.getHeight() + 4 * blurRadius + * to account for blurring beyond the bounds of the source image. Thus the source image + * will appear to be be offset by (2 * blurRadius, 2 * blurRadius) + * in the returned image. + * + * @param src source image, must not be null + * @param shadowPaint shadow paint + * @param blurRadius gaussian blur radius, must be > 0 + * @return a new buffered image containing a shadow of the specified source image + * using the specifed shadow paint and gaussian blur radius + */ + public static BufferedImage createShadow(final Image src, final Paint shadowPaint, final int blurRadius) { + if (src == null) { + throw new IllegalArgumentException("src image must not be null"); + } + if (blurRadius < 1) { + throw new IllegalArgumentException("blur radius must be greater than zero, was " + blurRadius); + } + int w = src.getWidth(null) + (4 * blurRadius); + int h = src.getHeight(null) + (4 * blurRadius); + + // paint src image into mask + BufferedImage mask = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = mask.createGraphics(); + g.drawImage(src, 2 * blurRadius, 2 * blurRadius, null); + + // composite mask with shadow paint + g.setComposite(AlphaComposite.SrcIn); + g.setPaint(shadowPaint); + g.fillRect(0, 0, w, h); + g.dispose(); + + // apply convolve op for blur + ConvolveOp convolveOp = new ConvolveOp(new GaussianKernel(blurRadius)); + BufferedImage shadow = convolveOp.filter(mask, null); + return shadow; + } + + /** + * Gaussian kernel. + */ + private static class GaussianKernel extends Kernel { + + /** + * Create a new gaussian kernel with the specified blur radius. + * + * @param blurRadius blur radius + */ + GaussianKernel(final int blurRadius) { + super((2 * blurRadius) + 1, (2 * blurRadius) + 1, createKernel(blurRadius)); + } + + + /** + * Create an array of floats representing a gaussian kernel with the specified radius. + * + * @param r radius + * @return an array of floats representing a gaussian kernel with the specified radius + */ + private static float[] createKernel(final int r) { + int w = (2 * r) + 1; + float kernel[] = new float[w * w]; + double m = 2.0d * Math.pow((r / 3.0d), 2); + double n = Math.PI * m; + + double sum = 0.0d; + for (int i = 0; i < w; i++) { + for (int j = 0; j < w; j++) { + kernel[i * w + j] = (float) (Math.pow(Math.E, -((j - r) * (j - r) + (i - r) * (i - r)) / m) / n); + sum += kernel[i * w + j]; + } + } + for (int i = 0; i < w; i++) { + for (int j = 0; j < w; j++) { + kernel[i * w + j] /= sum; + } + } + return kernel; + } + } +} \ No newline at end of file diff --git a/extras/src/test/java/edu/umd/cs/piccolox/nodes/PShadowTest.java b/extras/src/test/java/edu/umd/cs/piccolox/nodes/PShadowTest.java new file mode 100755 index 0000000..86874f7 --- /dev/null +++ b/extras/src/test/java/edu/umd/cs/piccolox/nodes/PShadowTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2008-2009, Piccolo2D project, http://piccolo2d.org + * Copyright (c) 1998-2008, University of Maryland + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + * and the following disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * None of the name of the University of Maryland, the name of the Piccolo2D project, or the names of its + * contributors may be used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package edu.umd.cs.piccolox.nodes; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Paint; + +import java.awt.image.BufferedImage; + +import junit.framework.TestCase; + +/** + * Unit test for PShadow. + */ +public final class PShadowTest extends TestCase { + + public void testConstructor() { + BufferedImage src = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB); + Paint srcPaint = new Color(255, 0, 0, 200); + Paint shadowPaint = new Color(20, 20, 20, 200); + Graphics2D g = src.createGraphics(); + g.setPaint(srcPaint); + g.drawRect(25, 25, 50, 50); + g.dispose(); + + for (int blurRadius = 1; blurRadius < 33; blurRadius += 4) { + PShadow shadowNode = new PShadow(src, shadowPaint, blurRadius); + assertNotNull(shadowNode); + assertEquals(src.getWidth() + 4 * blurRadius, shadowNode.getWidth(), 0.001d); + assertEquals(src.getHeight() + 4 * blurRadius, shadowNode.getHeight(), 0.001d); + } + + try { + PShadow shadowNode = new PShadow(null, shadowPaint, 4); + fail("ctr(null, ...) expected IllegalArgumentException"); + } + catch (IllegalArgumentException e) { + // expected + } + try { + PShadow shadowNode = new PShadow(src, shadowPaint, 0); + fail("ctr(..., -1) expected IllegalArgumentException"); + } + catch (IllegalArgumentException e) { + // expected + } + try { + PShadow shadowNode = new PShadow(src, shadowPaint, -1); + fail("ctr(..., -1) expected IllegalArgumentException"); + } + catch (IllegalArgumentException e) { + // expected + } + } +} \ No newline at end of file diff --git a/extras/src/test/java/edu/umd/cs/piccolox/util/ShadowUtilsTest.java b/extras/src/test/java/edu/umd/cs/piccolox/util/ShadowUtilsTest.java new file mode 100755 index 0000000..5f3139d --- /dev/null +++ b/extras/src/test/java/edu/umd/cs/piccolox/util/ShadowUtilsTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2008-2009, Piccolo2D project, http://piccolo2d.org + * Copyright (c) 1998-2008, University of Maryland + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + * and the following disclaimer in the documentation and/or other materials provided with the + * distribution. + * + * None of the name of the University of Maryland, the name of the Piccolo2D project, or the names of its + * contributors may be used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package edu.umd.cs.piccolox.util; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Paint; + +import java.awt.image.BufferedImage; + +import junit.framework.TestCase; + +/** + * Unit test for ShadowUtils. + */ +public final class ShadowUtilsTest extends TestCase { + + public void testCreateShadow() { + BufferedImage src = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB); + Paint srcPaint = new Color(255, 0, 0, 200); + Paint shadowPaint = new Color(20, 20, 20, 200); + Graphics2D g = src.createGraphics(); + g.setPaint(srcPaint); + g.drawRect(25, 25, 50, 50); + g.dispose(); + + for (int blurRadius = 1; blurRadius < 33; blurRadius += 4) { + BufferedImage dest = ShadowUtils.createShadow(src, shadowPaint, blurRadius); + assertNotNull(dest); + assertEquals(src.getWidth() + 4 * blurRadius, dest.getWidth()); + assertEquals(src.getHeight() + 4 * blurRadius, dest.getHeight()); + } + + try { + ShadowUtils.createShadow(null, shadowPaint, 4); + fail("createShadow(null, ...) expected IllegalArgumentException"); + } + catch (IllegalArgumentException e) { + // expected + } + try { + ShadowUtils.createShadow(src, shadowPaint, 0); + fail("createShadow(..., 0) expected IllegalArgumentException"); + } + catch (IllegalArgumentException e) { + // expected + } + try { + ShadowUtils.createShadow(src, shadowPaint, -1); + fail("createShadow(..., -1) expected IllegalArgumentException"); + } + catch (IllegalArgumentException e) { + // expected + } + } +} \ No newline at end of file