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;
+ }
+}