diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/AbstractFilter.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/AbstractFilter.java new file mode 100644 index 00000000..064fae79 --- /dev/null +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/AbstractFilter.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2012, Harald Kuhr + * 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. + * * Neither the name "TwelveMonkeys" nor 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 com.twelvemonkeys.image; + +import javax.imageio.ImageIO; +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.BufferedImageOp; +import java.awt.image.ColorModel; +import java.io.File; +import java.io.IOException; + +/** + * AbstractFilter + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: AbstractFilter.java,v 1.0 18.06.12 16:55 haraldk Exp$ + */ +public abstract class AbstractFilter implements BufferedImageOp { + public abstract BufferedImage filter(BufferedImage src, BufferedImage dest); + + public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel destCM) { + throw new UnsupportedOperationException("Method createCompatibleDestImage not implemented"); // TODO: Implement + } + + public Rectangle2D getBounds2D(BufferedImage src) { + return new Rectangle2D.Double(0, 0, src.getWidth(), src.getHeight()); + } + + public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) { + if (dstPt == null) { + dstPt = new Point2D.Double(); + } + + dstPt.setLocation(srcPt); + + return dstPt; + } + + public RenderingHints getRenderingHints() { + return null; + } + + protected static void exercise(final String[] args, final BufferedImageOp filter, final Color background) throws IOException { + boolean original = false; + + for (String arg : args) { + if (arg.startsWith("-")) { + if (arg.equals("-o") || arg.equals("--original")) { + original = true; + } + + continue; + } + + final File file = new File(arg); + BufferedImage image = ImageIO.read(file); + + if (image.getWidth() > 640) { + image = new ResampleOp(640, Math.round(image.getHeight() * (640f / image.getWidth())), null).filter(image, null); + } + + if (!original) { + filter.filter(image, image); + } + + final Color bg = original ? Color.BLACK : background; + final BufferedImage img = image; + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + JFrame frame = new JFrame(filter.getClass().getSimpleName().replace("Filter", "") + "Test: " + file.getName()); + frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(final WindowEvent e) { + Window[] windows = Window.getWindows(); + if (windows == null || windows.length == 0) { + System.exit(0); + } + } + }); + frame.getRootPane().getActionMap().put("window-close", new AbstractAction() { + public void actionPerformed(ActionEvent e) { + Window window = SwingUtilities.getWindowAncestor((Component) e.getSource()); + window.setVisible(false); + window.dispose(); + } + }); + frame.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "window-close"); + + JLabel label = new JLabel(new BufferedImageIcon(img)); + if (bg != null) { + label.setOpaque(true); + label.setBackground(bg); + } + label.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + JScrollPane scrollPane = new JScrollPane(label); + scrollPane.setBorder(BorderFactory.createEmptyBorder()); + frame.add(scrollPane); + + frame.pack(); + frame.setLocationByPlatform(true); + frame.setVisible(true); + } + }); + } + } +} diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/InstaCRTFilter.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/InstaCRTFilter.java new file mode 100644 index 00000000..1929d7fe --- /dev/null +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/InstaCRTFilter.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2012, Harald Kuhr + * 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. + * * Neither the name "TwelveMonkeys" nor 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 com.twelvemonkeys.image; + +import java.awt.*; +import java.awt.color.ColorSpace; +import java.awt.image.BufferedImage; +import java.awt.image.ColorConvertOp; +import java.awt.image.RescaleOp; +import java.io.IOException; +import java.util.Random; + +/** + * InstaCRTFilter + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: InstaCRTFilter.java,v 1.0 15.06.12 13:24 haraldk Exp$ + */ +public class InstaCRTFilter extends AbstractFilter { + + // NOTE: This is a PoC, and not good code... + public BufferedImage filter(BufferedImage src, BufferedImage dest) { + if (dest == null) { + dest = createCompatibleDestImage(src, null); + } + + // Make grayscale + BufferedImage image = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), getRenderingHints()).filter(src, null); + + // Make image faded/too bright + image = new RescaleOp(1.2f, 120f, getRenderingHints()).filter(image, image); + + // Blur + image = ImageUtil.blur(image, 2.5f); + + Graphics2D g = dest.createGraphics(); + try { + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); + g.drawImage(image, 0, 0, null); + + // Rotate it slightly for a more analogue feeling + double angle = .0055; + g.rotate(angle); + + // Apply fake green-ish h-sync line at random position + Random random = new Random(); + int lineStart = random.nextInt(image.getHeight() - 80); + int lineHeight = random.nextInt(10) + 20; + + g.setComposite(AlphaComposite.SrcOver.derive(.3f)); + g.setPaint(new LinearGradientPaint( + 0, lineStart, 0, lineStart + lineHeight, + new float[] {0, .3f, .9f, 1}, + new Color[] {new Color(0, true), new Color(0x90AF66), new Color(0x99606F33, true), new Color(0, true)} + )); + g.fillRect(0, lineStart, image.getWidth(), lineHeight); + + // Apply fake large dot-pitch (black lines w/transparency) + g.setComposite(AlphaComposite.SrcOver.derive(.55f)); + g.setColor(Color.BLACK); + + for (int y = 0; y < image.getHeight(); y += 3) { + g.setStroke(new BasicStroke(random.nextFloat() / 3 + .8f)); + g.drawLine(0, y, image.getWidth(), y); + } + + // Vignette/border + g.setComposite(AlphaComposite.SrcOver.derive(.75f)); + int focus = Math.min(image.getWidth() / 8, image.getHeight() / 8); + g.setPaint(new RadialGradientPaint( + new Point(image.getWidth() / 2, image.getHeight() / 2), + Math.max(image.getWidth(), image.getHeight()) / 1.6f, + new Point(focus, focus), + new float[] {0, .3f, .9f, 1f}, + new Color[] {new Color(0x99FFFFFF, true), new Color(0x00FFFFFF, true), new Color(0x0, true), Color.BLACK}, + MultipleGradientPaint.CycleMethod.NO_CYCLE + )); + g.fillRect(-2, -2, image.getWidth() + 4, image.getHeight() + 4); + + g.rotate(-angle); + + g.setComposite(AlphaComposite.SrcOver.derive(.35f)); + g.setPaint(new RadialGradientPaint( + new Point(image.getWidth() / 2, image.getHeight() / 2), + Math.max(image.getWidth(), image.getHeight()) / 1.65f, + new Point(image.getWidth() / 2, image.getHeight() / 2), + new float[] {0, .85f, 1f}, + new Color[] {new Color(0x0, true), new Color(0x0, true), Color.BLACK}, + MultipleGradientPaint.CycleMethod.NO_CYCLE + )); + g.fillRect(0, 0, image.getWidth(), image.getHeight()); + + // Highlight + g.setComposite(AlphaComposite.SrcOver.derive(.55f)); + g.setPaint(new RadialGradientPaint( + new Point(image.getWidth(), image.getHeight()), + Math.max(image.getWidth(), image.getHeight()) * 1.1f, + new Point(image.getWidth() / 2, image.getHeight() / 2), + new float[] {0, .75f, 1f}, + new Color[] {new Color(0x00FFFFFF, true), new Color(0x00FFFFFF, true), Color.WHITE}, + MultipleGradientPaint.CycleMethod.NO_CYCLE + )); + g.fillRect(0, 0, image.getWidth(), image.getHeight()); + } + finally { + g.dispose(); + } + + // Round corners + BufferedImage foo = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB); + Graphics2D graphics = foo.createGraphics(); + try { + graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + graphics.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); + graphics.setColor(Color.WHITE); + double angle = -0.04; + g.rotate(angle); + graphics.fillRoundRect(1, 1, image.getWidth() - 2, image.getHeight() - 2, 20, 20); + } + finally { + graphics.dispose(); + } + + foo = ImageUtil.blur(foo, 4.5f); + + // Compose image into rounded corners + graphics = foo.createGraphics(); + try { + graphics.setComposite(AlphaComposite.SrcIn); + graphics.drawImage(dest, 0, 0, null); + } + finally { + graphics.dispose(); + } + + // Draw it all back to dest + g = dest.createGraphics(); + try { + g.setComposite(AlphaComposite.SrcOver); + g.setColor(Color.BLACK); + g.fillRect(0, 0, image.getWidth(), image.getHeight()); + g.drawImage(foo, 0, 0, null); + } + finally { + g.dispose(); + } + + return dest; + } + + public static void main(String[] args) throws IOException { + exercise(args, new InstaCRTFilter(), Color.BLACK); + } +} diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/InstaLomoFilter.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/InstaLomoFilter.java new file mode 100644 index 00000000..6d277717 --- /dev/null +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/InstaLomoFilter.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2012, Harald Kuhr + * 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. + * * Neither the name "TwelveMonkeys" nor 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 com.twelvemonkeys.image; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.awt.image.RescaleOp; +import java.io.IOException; +import java.util.Random; + +/** + * InstaLomoFilter + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: InstaLomoFilter.java,v 1.0 15.06.12 13:24 haraldk Exp$ + */ +public class InstaLomoFilter extends AbstractFilter { + final private Random random = new Random(); + + // NOTE: This is a PoC, and not good code... + public BufferedImage filter(BufferedImage src, BufferedImage dest) { + if (dest == null) { + dest = createCompatibleDestImage(src, null); + } + + // Make image faded/washed out/red-ish + // DARK WARM + float[] scales = new float[] { 2.2f, 2.0f, 1.55f}; + float[] offsets = new float[] {-20.0f, -90.0f, -110.0f}; + + // BRIGHT NATURAL +// float[] scales = new float[] { 1.1f, .9f, .7f}; +// float[] offsets = new float[] {20, 30, 80}; + + // Faded, old-style +// float[] scales = new float[] { 1.1f, .7f, .3f}; +// float[] offsets = new float[] {20, 30, 80}; + +// float[] scales = new float[] { 1.2f, .4f, .4f}; +// float[] offsets = new float[] {0, 120, 120}; + + // BRIGHT WARM +// float[] scales = new float[] {1.1f, .8f, 1.6f}; +// float[] offsets = new float[] {60, 70, -80}; + BufferedImage image = new RescaleOp(scales, offsets, getRenderingHints()).filter(src, null); + + // Blur + image = ImageUtil.blur(image, 2.5f); + + Graphics2D g = dest.createGraphics(); + try { + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); + g.drawImage(image, 0, 0, null); + + // Rotate it slightly for a more analogue feeling + double angle = .0055; + g.rotate(angle); + + // Scratches + g.setComposite(AlphaComposite.SrcOver.derive(.025f)); + for (int i = 0; i < 100; i++) { + g.setColor(random.nextBoolean() ? Color.WHITE : Color.BLACK); + g.setStroke(new BasicStroke(random.nextFloat() * 2f)); + int x = random.nextInt(image.getWidth()); + + int off = random.nextInt(100); + for (int j = random.nextInt(3); j > 0; j--) { + g.drawLine(x + j, 0, x + off - 50 + j, image.getHeight()); + } + } + + // Vignette/border + g.setComposite(AlphaComposite.SrcOver.derive(.75f)); + int focus = Math.min(image.getWidth() / 8, image.getHeight() / 8); + g.setPaint(new RadialGradientPaint( + new Point(image.getWidth() / 2, image.getHeight() / 2), + Math.max(image.getWidth(), image.getHeight()) / 1.6f, + new Point(focus, focus), + new float[] {0, .3f, .9f, 1f}, + new Color[] {new Color(0x99FFFFFF, true), new Color(0x00FFFFFF, true), new Color(0x0, true), Color.BLACK}, + MultipleGradientPaint.CycleMethod.NO_CYCLE + )); + g.fillRect(-2, -2, image.getWidth() + 4, image.getHeight() + 4); + + g.rotate(-angle); + + g.setComposite(AlphaComposite.SrcOver.derive(.35f)); + g.setPaint(new RadialGradientPaint( + new Point(image.getWidth() / 2, image.getHeight() / 2), + Math.max(image.getWidth(), image.getHeight()) / 1.65f, + new Point(image.getWidth() / 2, image.getHeight() / 2), + new float[] {0, .85f, 1f}, + new Color[] {new Color(0x0, true), new Color(0x0, true), Color.BLACK}, + MultipleGradientPaint.CycleMethod.NO_CYCLE + )); + g.fillRect(0, 0, image.getWidth(), image.getHeight()); + + // Highlight + g.setComposite(AlphaComposite.SrcOver.derive(.35f)); + g.setPaint(new RadialGradientPaint( + new Point(image.getWidth(), image.getHeight()), + Math.max(image.getWidth(), image.getHeight()) * 1.1f, + new Point(image.getWidth() / 2, image.getHeight() / 2), + new float[] {0, .75f, 1f}, + new Color[] {new Color(0x00FFFFFF, true), new Color(0x00FFFFFF, true), Color.PINK}, + MultipleGradientPaint.CycleMethod.NO_CYCLE + )); + g.fillRect(0, 0, image.getWidth(), image.getHeight()); + } + finally { + g.dispose(); + } + + // Noise + NoiseFilter noise = new NoiseFilter(); + noise.setAmount(10); + noise.setDensity(2); + dest = noise.filter(dest, dest); + + // Round corners + BufferedImage foo = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB); + Graphics2D graphics = foo.createGraphics(); + try { + graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + graphics.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); + graphics.setColor(Color.WHITE); + double angle = (random.nextDouble() * .01) - .005; + graphics.rotate(angle); + graphics.fillRoundRect(4, 4, image.getWidth() - 8, image.getHeight() - 8, 20, 20); + } + finally { + graphics.dispose(); + } + + noise.setAmount(20); + noise.setDensity(1); + noise.setMonochrome(true); + foo = noise.filter(foo, foo); + + foo = ImageUtil.blur(foo, 4.5f); + + // Compose image into rounded corners + graphics = foo.createGraphics(); + try { + graphics.setComposite(AlphaComposite.SrcIn); + graphics.drawImage(dest, 0, 0, null); + } + finally { + graphics.dispose(); + } + + // Draw it all back to dest + g = dest.createGraphics(); + try { + if (dest.getTransparency() != Transparency.OPAQUE) { + g.setComposite(AlphaComposite.Clear); + } + g.setColor(Color.WHITE); + g.fillRect(0, 0, image.getWidth(), image.getHeight()); + g.setComposite(AlphaComposite.SrcOver); + g.drawImage(foo, 0, 0, null); + } + finally { + g.dispose(); + } + + return dest; + } + + public static void main(String[] args) throws IOException { + exercise(args, new InstaLomoFilter(), Color.WHITE); + } +} diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/InstaSepiaFilter.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/InstaSepiaFilter.java new file mode 100644 index 00000000..b82c6b9c --- /dev/null +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/InstaSepiaFilter.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2012, Harald Kuhr + * 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. + * * Neither the name "TwelveMonkeys" nor 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 com.twelvemonkeys.image; + +import java.awt.*; +import java.awt.color.ColorSpace; +import java.awt.image.*; +import java.io.IOException; +import java.util.Random; + +/** + * InstaLomoFilter + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: InstaLomoFilter.java,v 1.0 15.06.12 13:24 haraldk Exp$ + */ +public class InstaSepiaFilter extends AbstractFilter { + final private Random random = new Random(); + + // NOTE: This is a PoC, and not good code... + @Override + public BufferedImage filter(BufferedImage src, BufferedImage dest) { + if (dest == null) { + dest = createCompatibleDestImage(src, null); + } + + BufferedImage image = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), getRenderingHints()).filter(src, dest); + + Graphics2D g2d = dest.createGraphics(); + try { + g2d.drawImage(image, 0, 0, null); + } + finally { + g2d.dispose(); + } + + // Blur + image = ImageUtil.blur(image, 2.5f); + + Graphics2D g = dest.createGraphics(); + try { + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); + g.drawImage(image, 0, 0, null); + + // Rotate it slightly for a more analogue feeling + double angle = -.0055; + g.rotate(angle); + + // Vignette/border + g.setComposite(AlphaComposite.SrcOver.derive(.35f)); + g.setPaint(new RadialGradientPaint( + new Point(image.getWidth() / 2, image.getHeight() / 2), + Math.max(image.getWidth(), image.getHeight()) / 1.65f, + new Point(image.getWidth() / 2, image.getHeight() / 2), + new float[] {0, .85f, 1f}, + new Color[] {new Color(0x0, true), new Color(0x0, true), Color.BLACK}, + MultipleGradientPaint.CycleMethod.NO_CYCLE + )); + g.fillRect(0, 0, image.getWidth(), image.getHeight()); + + } + finally { + g.dispose(); + } + + // Round corners + BufferedImage foo = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB); + Graphics2D graphics = foo.createGraphics(); + try { + graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + graphics.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); + graphics.setColor(Color.WHITE); + double angle = (random.nextDouble() * .01) - .005; + graphics.rotate(angle); + graphics.fillRoundRect(4, 4, image.getWidth() - 8, image.getHeight() - 8, 20, 20); + } + finally { + graphics.dispose(); + } + + // Noise + NoiseFilter noise = new NoiseFilter(); + noise.setAmount(20); + noise.setDensity(1); + noise.setMonochrome(true); + foo = noise.filter(foo, foo); + + foo = ImageUtil.blur(foo, 4.5f); + + // Compose image into rounded corners + graphics = foo.createGraphics(); + try { + graphics.setComposite(AlphaComposite.SrcIn); + graphics.drawImage(dest, 0, 0, null); + } + finally { + graphics.dispose(); + } + + float[] scales = new float[] {1, 1, 1, 1}; + float[] offsets = new float[] {80, 40, 0, 0}; + foo = new RescaleOp(scales, offsets, getRenderingHints()).filter(foo, foo); + + // Draw it all back to dest + g = dest.createGraphics(); + try { + g.setComposite(AlphaComposite.SrcOver); + g.setColor(Color.WHITE); + g.fillRect(0, 0, image.getWidth(), image.getHeight()); + g.drawImage(foo, 0, 0, null); + } + finally { + g.dispose(); + } + + return dest; + } + + public static void main(String[] args) throws IOException { + exercise(args, new InstaSepiaFilter(), null); + } +} diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/NoiseFilter.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/NoiseFilter.java new file mode 100644 index 00000000..73c8d50b --- /dev/null +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/NoiseFilter.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2012, Harald Kuhr + * 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. + * * Neither the name "TwelveMonkeys" nor 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. + */ +/* +Copyright 2006 Jerry Huxtable + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package com.twelvemonkeys.image; + +import java.awt.image.BufferedImage; +import java.awt.image.WritableRaster; +import java.util.Random; + +/** + * NoiseFilter + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: NoiseFilter.java,v 1.0 15.06.12 22:59 haraldk Exp$ + */ +public class NoiseFilter extends AbstractFilter { + + /** + * Gaussian distribution for the noise. + */ + public final static int GAUSSIAN = 0; + + /** + * Uniform distribution for the noise. + */ + public final static int UNIFORM = 1; + + private int amount = 25; + private int distribution = UNIFORM; + private boolean monochrome = false; + private float density = 1; + private Random randomNumbers = new Random(); + + public NoiseFilter() { + } + + /** + * Set the amount of effect. + * + * @param amount the amount + * @min-value 0 + * @max-value 1 + * @see #getAmount + */ + public void setAmount(int amount) { + this.amount = amount; + } + + /** + * Get the amount of noise. + * + * @return the amount + * @see #setAmount + */ + public int getAmount() { + return amount; + } + + /** + * Set the distribution of the noise. + * + * @param distribution the distribution + * @see #getDistribution + */ + public void setDistribution(int distribution) { + this.distribution = distribution; + } + + /** + * Get the distribution of the noise. + * + * @return the distribution + * @see #setDistribution + */ + public int getDistribution() { + return distribution; + } + + /** + * Set whether to use monochrome noise. + * + * @param monochrome true for monochrome noise + * @see #getMonochrome + */ + public void setMonochrome(boolean monochrome) { + this.monochrome = monochrome; + } + + /** + * Get whether to use monochrome noise. + * + * @return true for monochrome noise + * @see #setMonochrome + */ + public boolean getMonochrome() { + return monochrome; + } + + /** + * Set the density of the noise. + * + * @param density the density + * @see #getDensity + */ + public void setDensity(float density) { + this.density = density; + } + + /** + * Get the density of the noise. + * + * @return the density + * @see #setDensity + */ + public float getDensity() { + return density; + } + + private int random() { + return (int) (((distribution == GAUSSIAN ? randomNumbers.nextGaussian() : 2 * randomNumbers.nextFloat() - 1)) * amount); + } + + private static int clamp(int x) { + if (x < 0) { + return 0; + } + else if (x > 0xff) { + return 0xff; + } + return x; + } + + public int filterRGB(int x, int y, int rgb) { + if (randomNumbers.nextFloat() <= density) { + int a = rgb & 0xff000000; + int r = (rgb >> 16) & 0xff; + int g = (rgb >> 8) & 0xff; + int b = rgb & 0xff; + + if (monochrome) { + int n = random(); + r = clamp(r + n); + g = clamp(g + n); + b = clamp(b + n); + } + else { + r = clamp(r + random()); + g = clamp(g + random()); + b = clamp(b + random()); + } + return a | (r << 16) | (g << 8) | b; + } + return rgb; + } + + public BufferedImage filter(BufferedImage src, BufferedImage dst) { + int width = src.getWidth(); + int height = src.getHeight(); + int type = src.getType(); + WritableRaster srcRaster = src.getRaster(); + + if (dst == null) { + dst = createCompatibleDestImage(src, null); + } + WritableRaster dstRaster = dst.getRaster(); + + int[] inPixels = new int[width]; + for (int y = 0; y < height; y++) { + // We try to avoid calling getRGB on images as it causes them to become unmanaged, causing horrible performance problems. + if (type == BufferedImage.TYPE_INT_ARGB) { + srcRaster.getDataElements(0, y, width, 1, inPixels); + for (int x = 0; x < width; x++) { + inPixels[x] = filterRGB(x, y, inPixels[x]); + } + dstRaster.setDataElements(0, y, width, 1, inPixels); + } + else { + src.getRGB(0, y, width, 1, inPixels, 0, width); + for (int x = 0; x < width; x++) { + inPixels[x] = filterRGB(x, y, inPixels[x]); + } + dst.setRGB(0, y, width, 1, inPixels, 0, width); + } + } + + return dst; + } +}