diff options
27 files changed, 1667 insertions, 326 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ae3c172 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/bin/bjc/imgchain/ImgChain$LoadImageListener.class b/bin/bjc/imgchain/ImgChain$LoadImageListener.class Binary files differindex 8274e1d..dff51e8 100644 --- a/bin/bjc/imgchain/ImgChain$LoadImageListener.class +++ b/bin/bjc/imgchain/ImgChain$LoadImageListener.class diff --git a/bin/bjc/imgchain/ImgChain.class b/bin/bjc/imgchain/ImgChain.class Binary files differindex f17d07d..5eb912c 100644 --- a/bin/bjc/imgchain/ImgChain.class +++ b/bin/bjc/imgchain/ImgChain.class diff --git a/bin/bjc/imgchain/ImgViewer$ChangeImageListener.class b/bin/bjc/imgchain/ImgViewer$ChangeImageListener.class Binary files differindex f21eb96..bb25d35 100644 --- a/bin/bjc/imgchain/ImgViewer$ChangeImageListener.class +++ b/bin/bjc/imgchain/ImgViewer$ChangeImageListener.class diff --git a/bin/bjc/imgchain/ImgViewer$ReloadImageListener.class b/bin/bjc/imgchain/ImgViewer$ReloadImageListener.class Binary files differindex 1e51d8f..ea5b3ac 100644 --- a/bin/bjc/imgchain/ImgViewer$ReloadImageListener.class +++ b/bin/bjc/imgchain/ImgViewer$ReloadImageListener.class diff --git a/bin/bjc/imgchain/ImgViewer$ScaleImageListener.class b/bin/bjc/imgchain/ImgViewer$ScaleImageListener.class Binary files differdeleted file mode 100644 index a89b0cb..0000000 --- a/bin/bjc/imgchain/ImgViewer$ScaleImageListener.class +++ /dev/null diff --git a/bin/bjc/imgchain/ImgViewer.class b/bin/bjc/imgchain/ImgViewer.class Binary files differindex 5dc23f4..9ff5f94 100644 --- a/bin/bjc/imgchain/ImgViewer.class +++ b/bin/bjc/imgchain/ImgViewer.class diff --git a/src/bjc/imgchain/ImgChain.java b/src/bjc/imgchain/ImgChain.java index f90ae78..e4e32cc 100644 --- a/src/bjc/imgchain/ImgChain.java +++ b/src/bjc/imgchain/ImgChain.java @@ -1,126 +1,211 @@ -package bjc.imgchain; - -import java.awt.GridLayout; -import java.awt.Image; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.File; -import java.util.HashMap; -import java.util.Map; - -import javax.swing.JDesktopPane; -import javax.swing.JFileChooser; -import javax.swing.JFrame; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; -import javax.swing.JOptionPane; - -public class ImgChain { - private final class LoadImageListener implements ActionListener { - @Override - public void actionPerformed(ActionEvent ev) { - JFileChooser jfc = new JFileChooser(); - jfc.setMultiSelectionEnabled(true); - - int res = jfc.showOpenDialog(desktop); - - if (res != JFileChooser.APPROVE_OPTION) { - return; - } - - for (File fle : jfc.getSelectedFiles()) { - ImgViewer view = new ImgViewer(ImgChain.this, fle); - - if (view.isInitialized()) { - desktop.add(view); - view.setVisible(true); - } - } - } - } - - private JDesktopPane desktop; - - private Map<String, Image> imageRepo; - - public static void main(String[] args) { - System.out.println("ImgChain Loading..."); - - ImgChain chn = new ImgChain(); - - chn.setupGUI(); - } - - public ImgChain() { - imageRepo = new HashMap<>(); - } - - private void setupGUI() { - JFrame frame = new JFrame("ImgChain v1"); - frame.setLayout(new GridLayout(1, 1)); - - desktop = new JDesktopPane(); - - JMenuBar menu = setupMenubar(frame); - - frame.setJMenuBar(menu); - - frame.add(desktop); - - frame.setSize(640, 480); - - frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); - - frame.setVisible(true); - } - - private JMenuBar setupMenubar(JFrame frame) { - JMenuBar menu = new JMenuBar(); - - JMenu fileMenu = new JMenu("File"); - fileMenu.setMnemonic('F'); - - JMenuItem aboutItem = new JMenuItem("About"); - aboutItem.setMnemonic('A'); - aboutItem.addActionListener((ev) -> { - JOptionPane.showMessageDialog(frame, "ImgChain v1\nDeveloped by Benjamin Culkin", - "About ImgChain", JOptionPane.INFORMATION_MESSAGE); - }); - - JMenuItem closeItem = new JMenuItem("Close"); - closeItem.setMnemonic('C'); - closeItem.addActionListener((ev) -> { - frame.dispose(); - }); - - fileMenu.add(aboutItem); - fileMenu.addSeparator(); - fileMenu.add(closeItem); - - JMenu imageMenu = new JMenu("Images"); - imageMenu.setMnemonic('I'); - - JMenuItem loadImage = new JMenuItem("Load Images..."); - loadImage.setMnemonic('L'); - loadImage.addActionListener(new LoadImageListener()); - - imageMenu.add(loadImage); - - menu.add(fileMenu); - menu.add(imageMenu); - return menu; - } - - public void addImage(String name, Image img) { - if (imageRepo.containsKey(name)) { - String msg = String.format("Are you sure you want to overwrite stored image '%s'?", name); - - if (JOptionPane.showInternalConfirmDialog(desktop, msg) != JOptionPane.OK_OPTION) { - return; - } - } - - imageRepo.put(name, img); - } -} +package bjc.imgchain;
+
+import java.awt.GridLayout;
+import java.awt.Image;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.JDesktopPane;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+
+import bjc.imgchain.pipeline.Pipeline;
+
+/**
+ * Main class for ImgChain
+ *
+ * @author acm
+ *
+ */
+public class ImgChain {
+ /*
+ * Action to perform when loading an image.
+ */
+ private final class LoadImageListener implements ActionListener {
+ @Override
+ public void actionPerformed(ActionEvent ev) {
+ JFileChooser jfc = new JFileChooser();
+ jfc.setMultiSelectionEnabled(true);
+
+ int res = jfc.showOpenDialog(desktop);
+
+ if (res != JFileChooser.APPROVE_OPTION) {
+ return;
+ }
+
+ for (File fle : jfc.getSelectedFiles()) {
+ ImgViewer view = new ImgViewer(ImgChain.this, fle);
+
+ if (view.isInitialized()) {
+ desktop.add(view);
+ view.setVisible(true);
+ }
+ }
+ }
+ }
+
+ /**
+ * The desktop everything is attached to.
+ */
+ public JDesktopPane desktop;
+
+ /*
+ * The storage for images.
+ */
+ public final Map<String, Image> imageRepo;
+
+ /*
+ * The storage for images.
+ */
+ public final Map<String, Pipeline> pipelineRepo;
+
+ /**
+ * The image chain instance.
+ */
+ public static ImgChain chan;
+
+ /**
+ * Main method
+ *
+ * @param args
+ * Unused CLI args
+ */
+ public static void main(String[] args) {
+ System.out.println("ImgChain Loading...");
+
+ chan = new ImgChain();
+
+ chan.setupGUI();
+ }
+
+ /**
+ * Initialize image repo
+ */
+ public ImgChain() {
+ imageRepo = new HashMap<>();
+ pipelineRepo = new HashMap<>();
+ }
+
+ /*
+ * Setup the GUI
+ */
+ private void setupGUI() {
+ JFrame frame = new JFrame("ImgChain v1");
+ frame.setLayout(new GridLayout(1, 1));
+
+ desktop = new JDesktopPane();
+
+ JMenuBar menu = setupMenubar(frame);
+
+ frame.setJMenuBar(menu);
+
+ frame.add(desktop);
+
+ frame.setSize(640, 480);
+
+ frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+
+ frame.setVisible(true);
+ }
+
+ /*
+ * Setup the menubar.
+ */
+ private JMenuBar setupMenubar(JFrame frame) {
+ JMenuBar menu = new JMenuBar();
+
+ JMenu fileMenu = new JMenu("File");
+ fileMenu.setMnemonic('F');
+
+ JMenuItem aboutItem = new JMenuItem("About");
+ aboutItem.setMnemonic('A');
+ aboutItem.addActionListener((ev) -> {
+ JOptionPane.showMessageDialog(frame, "ImgChain v2\nDeveloped by Benjamin Culkin",
+ "About ImgChain", JOptionPane.INFORMATION_MESSAGE);
+ });
+
+ JMenuItem closeItem = new JMenuItem("Close");
+ closeItem.setMnemonic('C');
+ closeItem.addActionListener((ev) -> {
+ frame.dispose();
+ });
+
+ fileMenu.add(aboutItem);
+ fileMenu.addSeparator();
+ fileMenu.add(closeItem);
+
+ JMenu imageMenu = new JMenu("Images");
+ imageMenu.setMnemonic('I');
+
+ JMenuItem loadImage = new JMenuItem("Load Images...");
+ loadImage.setMnemonic('L');
+ loadImage.addActionListener(new LoadImageListener());
+
+ imageMenu.add(loadImage);
+
+ JMenu pipelineMenu = new JMenu("Pipelines");
+ JMenuItem createPipe = new JMenuItem("Create Pipeline");
+ createPipe.setMnemonic('C');
+ createPipe.addActionListener((ev) -> {
+ ImgPipeline pip = new ImgPipeline();
+
+ desktop.add(pip);
+ pip.setVisible(true);
+ });
+
+ pipelineMenu.add(createPipe);
+
+ menu.add(fileMenu);
+ menu.add(imageMenu);
+ menu.add(pipelineMenu);
+
+ return menu;
+ }
+
+ /**
+ * Add an image to the image repository.
+ *
+ * @param name
+ * The name of the image
+ * @param img
+ * The image to add.
+ */
+ public void addImage(String name, Image img) {
+ if (imageRepo.containsKey(name)) {
+ String msg = String.format("Are you sure you want to overwrite stored image '%s'?", name);
+
+ if (JOptionPane.showInternalConfirmDialog(desktop, msg) != JOptionPane.OK_OPTION) {
+ return;
+ }
+ }
+
+ imageRepo.put(name, img);
+ }
+
+ /**
+ * Add an pipeline to the pipeline repository.
+ *
+ * @param pipe
+ * The pipeline to add.
+ */
+ public void addPipe(Pipeline pipe) {
+ String name = pipe.name();
+
+ if (pipelineRepo.containsKey(name)) {
+ String msg = String.format("Are you sure you want to overwrite stored pipeline '%s'?", name);
+
+ if (JOptionPane.showInternalConfirmDialog(desktop, msg) != JOptionPane.OK_OPTION) {
+ return;
+ }
+ }
+
+ pipelineRepo.put(name, pipe);
+ }
+}
diff --git a/src/bjc/imgchain/ImgPipeline.java b/src/bjc/imgchain/ImgPipeline.java new file mode 100644 index 0000000..8dbbebf --- /dev/null +++ b/src/bjc/imgchain/ImgPipeline.java @@ -0,0 +1,206 @@ +package bjc.imgchain;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.text.MessageFormat;
+
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JInternalFrame;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.border.BevelBorder;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.TitledBorder;
+
+import bjc.imgchain.pipeline.MutablePipeline;
+import bjc.imgchain.pipeline.PipelineStage;
+import bjc.imgchain.pipeline.stages.ColorSkewStage;
+import bjc.imgchain.pipeline.stages.GaussStage;
+import bjc.imgchain.pipeline.stages.GreyscaleStage;
+import bjc.imgchain.pipeline.stages.IDStage;
+import bjc.imgchain.pipeline.stages.NegativeStage;
+import bjc.imgchain.pipeline.stages.StagePicker;
+
+/**
+ * Edit an image pipeline.
+ *
+ * @author acm
+ *
+ */
+public class ImgPipeline extends JInternalFrame {
+ private static final long serialVersionUID = 2252473157369730085L;
+
+ private MutablePipeline editing;
+
+ /**
+ * Create a editor for a new image pipeline.
+ */
+ public ImgPipeline() {
+ super("Pipeline Editor - New pipeline", true, true, true, true);
+
+ editing = new MutablePipeline();
+
+ setupGUI();
+ }
+
+ /**
+ * Create an editor for an existing pipeline.
+ *
+ * @param pipe
+ * The pipeline to edit.
+ */
+ public ImgPipeline(MutablePipeline pipe) {
+ super("Pipeline Editor - New pipeline", true, true, true, true);
+
+ editing = pipe;
+
+ setupGUI();
+ }
+
+ private void setupGUI() {
+ setSize(320, 320);
+ setLayout(new GridLayout(1, 1));
+
+ JPanel stageEditor = new JPanel();
+ stageEditor.setLayout(new BorderLayout());
+ TitledBorder border = new TitledBorder(new BevelBorder(BevelBorder.LOWERED), "No Stage Selected");
+ stageEditor.setBorder(border);
+
+ DefaultListModel<PipelineStage> mod = new DefaultListModel<>();
+ for (PipelineStage stage : editing.stages()) {
+ mod.addElement(stage);
+ }
+
+ JList<PipelineStage> stageList = new JList<>(mod);
+ stageList.addListSelectionListener(e -> {
+ if (e.getValueIsAdjusting())
+ return;
+
+ PipelineStage stag = stageList.getSelectedValue();
+
+ stageEditor.removeAll();
+
+ stageEditor.add(stag.getEditor(), BorderLayout.CENTER);
+ border.setTitle(stag.name());
+
+ // stageEditor.repaint();
+ });
+ JScrollPane stageScroll = new JScrollPane(stageList);
+
+ JPanel listPanel = new JPanel();
+ listPanel.setLayout(new BorderLayout());
+
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new GridLayout(2, 1));
+
+ JPanel stageButtons = new JPanel();
+ stageButtons.setLayout(new GridLayout(1, 2));
+
+ JButton addStage = new JButton("Add Stage");
+ addStage.addActionListener(new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ StagePicker pick = new StagePicker();
+ pick.pack();
+ pick.setVisible(true);
+
+ if (pick.stageName == null) {
+ System.out.println("WARN: picked null stage");
+ return;
+ }
+
+ PipelineStage stag;
+ /*
+ * :AddStage
+ */
+ switch (pick.stageName) {
+ case "Identity": {
+ stag = new IDStage();
+ }
+ break;
+ case "Greyscale": {
+ stag = new GreyscaleStage();
+ }
+ break;
+ case "Color Skew": {
+ stag = new ColorSkewStage();
+ }
+ break;
+ case "Sepia": {
+ /*
+ * NOTE: these values were pulled from somewhere on the internet, and tweaked
+ * slightly to work better.
+ */
+ stag = new ColorSkewStage(.393, .769, .189, .349, .686, .168, .272, .534, .131);
+ }
+ case "Negative": {
+ stag = new NegativeStage();
+ }
+ break;
+ case "Gaussian Blur": {
+ stag = new GaussStage();
+ }
+ break;
+ default:
+ JOptionPane.showMessageDialog(ImgChain.chan.desktop, String
+ .format("Attempted to add unknown stage '%s'", pick.stageName));
+ return;
+ }
+
+ editing.addStage(stag);
+ mod.addElement(stag);
+
+ stageList.setSelectedValue(stag, true);
+ }
+ });
+
+ JButton delStage = new JButton("Delete Stage");
+ delStage.addActionListener(e -> {
+ int res = JOptionPane.showInternalConfirmDialog(this,
+ "Are you sure you want to remove the stage?");
+
+ if (res != JOptionPane.YES_OPTION) {
+ return;
+ }
+
+ int idx = stageList.getSelectedIndex();
+
+ mod.remove(idx);
+ editing.removeStage(idx);
+
+ stageEditor.removeAll();
+ border.setTitle("No Stage Selected");
+ });
+
+ stageButtons.add(addStage);
+ stageButtons.add(delStage);
+
+ JButton savePipeline = new JButton("Save Pipeline");
+ savePipeline.addActionListener((ev) -> {
+ String newName = JOptionPane.showInternalInputDialog(this, "Enter Pipeline Name");
+
+ editing.name(newName);
+
+ ImgChain.chan.addPipe(editing);
+
+ setTitle("Pipeline Editor - " + newName);
+ });
+
+ buttonPanel.add(stageButtons);
+ buttonPanel.add(savePipeline);
+
+ listPanel.add(stageScroll, BorderLayout.CENTER);
+ listPanel.add(buttonPanel, BorderLayout.PAGE_END);
+
+ JSplitPane main = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, listPanel, stageEditor);
+
+ add(main);
+ }
+}
diff --git a/src/bjc/imgchain/ImgViewer.java b/src/bjc/imgchain/ImgViewer.java index 39586a8..06c2a1e 100644 --- a/src/bjc/imgchain/ImgViewer.java +++ b/src/bjc/imgchain/ImgViewer.java @@ -1,200 +1,216 @@ -package bjc.imgchain; - -import java.awt.GridLayout; -import java.awt.Image; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; - -import javax.imageio.ImageIO; -import javax.swing.*; - -import bjc.imgchain.utils.SimpleInputPanel; - -public class ImgViewer extends JInternalFrame { - private final class ScaleImageListener implements ActionListener { - @Override - public void actionPerformed(ActionEvent ev) { - int height = Integer.parseInt(JOptionPane.showInputDialog("Enter new image height")); - int width = Integer.parseInt(JOptionPane.showInputDialog("Enter new image width")); - - icon.setImage(icon.getImage().getScaledInstance(width, height, Image.SCALE_DEFAULT)); - - repaint(); - } - } - - private final class ReloadImageListener implements ActionListener { - private final File img; - - private ReloadImageListener(File img) { - this.img = img; - } - - @Override - public void actionPerformed(ActionEvent ev) { - try { - icon.setImage(ImageIO.read(img)); - } catch (IOException e) { - String msg = String.format("Error: Could not load image %s", img.getPath()); - - System.out.printf("%s\n", msg); - - e.printStackTrace(); - - JOptionPane.showInternalMessageDialog(ImgViewer.this, msg, "Error loading image", - JOptionPane.ERROR_MESSAGE); - } - } - } - - private final class ChangeImageListener implements ActionListener { - @Override - public void actionPerformed(ActionEvent ev) { - JFileChooser jfc = new JFileChooser(); - jfc.setMultiSelectionEnabled(false); - - int res = jfc.showOpenDialog(ImgViewer.this); - - if (res != JFileChooser.APPROVE_OPTION) { - return; - } - - try { - File tmp = jfc.getSelectedFile(); - - icon.setImage(ImageIO.read(tmp)); - - img = tmp; - } catch (IOException e) { - String msg = String.format("Error: Could not load image %s", img.getPath()); - - System.out.printf("%s\n", msg); - - e.printStackTrace(); - - JOptionPane.showInternalMessageDialog(null, msg, "Error loading image", - JOptionPane.ERROR_MESSAGE); - } - } - } - - private static final long serialVersionUID = -4727584150861526435L; - - private File img; - - private boolean initted; - - private ImageIcon icon; - - private ImgChain desktop; - - public ImgViewer(ImgChain desk, File img) { - super("Image Viewer - " + img.getName(), true, true, true, true); - initted = false; - - this.img = img; - this.desktop = desk; - - initted = setupGUI(img); - } - - private boolean setupGUI(File img) { - setSize(320, 240); - setLayout(new GridLayout(1, 1)); - - JMenuBar bar = setupMenubar(img); - - setJMenuBar(bar); - - JLabel lab = loadLabel(img); - if (lab == null) { - return false; - } - - add(lab); - - return true; - } - - private JMenuBar setupMenubar(File img) { - JMenuBar bar = new JMenuBar(); - - JMenu fileMenu = new JMenu("File"); - fileMenu.setMnemonic('F'); - - JMenuItem changeImage = new JMenuItem("Change Image"); - changeImage.setMnemonic('C'); - changeImage.addActionListener(new ChangeImageListener()); - - JMenuItem reloadImage = new JMenuItem("Reload Image"); - reloadImage.setMnemonic('R'); - reloadImage.addActionListener(new ReloadImageListener(img)); - - JMenuItem storeImage = new JMenuItem("Store Image"); - storeImage.setMnemonic('S'); - storeImage.addActionListener((ev) -> { - String inp = JOptionPane.showInternalInputDialog(this, "Enter name to store image under"); - - if(inp == null) return; - - inp = inp.trim(); - - if(inp.equals("")) { - return; - } - - desktop.addImage(inp, icon.getImage()); - }); - - fileMenu.add(changeImage); - fileMenu.add(reloadImage); - fileMenu.addSeparator(); - fileMenu.add(storeImage); - - JMenu editMenu = new JMenu("Edit Image"); - editMenu.setMnemonic('E'); - - JMenuItem scaleImage = new JMenuItem("Scale Image"); - scaleImage.setMnemonic('S'); - scaleImage.addActionListener(new ScaleImageListener()); - - editMenu.add(scaleImage); - - bar.add(fileMenu); - bar.add(editMenu); - return bar; - } - - private JLabel loadLabel(File img) { - JLabel lab = null; - - try { - URL imgURL = img.toURI().toURL(); - - icon = new ImageIcon(imgURL); - - setSize(icon.getIconWidth(), icon.getIconHeight()); - - lab = new JLabel(icon); - } catch (MalformedURLException e) { - String msg = String.format("Error: Could not load image %s", img.getPath()); - - System.out.printf("%s\n", msg); - - e.printStackTrace(); - - JOptionPane.showInternalMessageDialog(this, msg, "Error loading image", - JOptionPane.ERROR_MESSAGE); - } - - return lab; - } - - public boolean isInitialized() { - return initted; - } -} +package bjc.imgchain;
+
+import java.awt.GridLayout;
+import java.awt.Image;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.geom.AffineTransform;
+import java.awt.image.AffineTransformOp;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.imageio.ImageIO;
+import javax.swing.*;
+
+import bjc.imgchain.pipeline.Pipeline;
+import bjc.imgchain.pipeline.PipelinePicker;
+import bjc.imgchain.utils.SimpleInputPanel;
+import bjc.imgchain.utils.Utils;
+
+public class ImgViewer extends JInternalFrame {
+ private final class ReloadImageListener implements ActionListener {
+ private final File img;
+
+ private ReloadImageListener(File img) {
+ this.img = img;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent ev) {
+ try {
+ icon.setImage(ImageIO.read(img));
+ } catch (IOException e) {
+ String msg = String.format("Error: Could not load image %s", img.getPath());
+
+ System.out.printf("%s\n", msg);
+
+ e.printStackTrace();
+
+ JOptionPane.showInternalMessageDialog(ImgViewer.this, msg, "Error loading image",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ }
+
+ private final class ChangeImageListener implements ActionListener {
+ @Override
+ public void actionPerformed(ActionEvent ev) {
+ JFileChooser jfc = new JFileChooser();
+ jfc.setMultiSelectionEnabled(false);
+
+ int res = jfc.showOpenDialog(ImgViewer.this);
+
+ if (res != JFileChooser.APPROVE_OPTION) {
+ return;
+ }
+
+ try {
+ File tmp = jfc.getSelectedFile();
+
+ icon.setImage(ImageIO.read(tmp));
+
+ img = tmp;
+
+ setTitle("Image Viewer - " + img.getName());
+ } catch (IOException e) {
+ String msg = String.format("Error: Could not load image %s", img.getPath());
+
+ System.out.printf("%s\n", msg);
+
+ e.printStackTrace();
+
+ JOptionPane.showInternalMessageDialog(null, msg, "Error loading image",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ }
+
+ private static final long serialVersionUID = -4727584150861526435L;
+
+ private File img;
+
+ private boolean initted;
+
+ private ImageIcon icon;
+
+ private ImgChain desktop;
+
+ private JLabel lab;
+
+ public ImgViewer(ImgChain desk, File img) {
+ super("Image Viewer - " + img.getName(), true, true, true, true);
+ initted = false;
+
+ this.img = img;
+ this.desktop = desk;
+
+ initted = setupGUI(img);
+ }
+
+ private boolean setupGUI(File img) {
+ setSize(320, 240);
+ setLayout(new GridLayout(1, 1));
+
+ JMenuBar bar = setupMenubar(img);
+
+ setJMenuBar(bar);
+
+ lab = loadLabel(img);
+ if (lab == null) {
+ return false;
+ }
+
+ add(lab);
+
+ return true;
+ }
+
+ private JMenuBar setupMenubar(File img) {
+ JMenuBar bar = new JMenuBar();
+
+ JMenu fileMenu = new JMenu("File");
+ fileMenu.setMnemonic('F');
+
+ JMenuItem changeImage = new JMenuItem("Change Image");
+ changeImage.setMnemonic('C');
+ changeImage.addActionListener(new ChangeImageListener());
+
+ JMenuItem reloadImage = new JMenuItem("Reload Image");
+ reloadImage.setMnemonic('R');
+ reloadImage.addActionListener(new ReloadImageListener(img));
+
+ JMenuItem storeImage = new JMenuItem("Store Image");
+ storeImage.setMnemonic('S');
+ storeImage.addActionListener((ev) -> {
+ String inp = JOptionPane.showInternalInputDialog(this, "Enter name to store image under");
+
+ if (inp == null)
+ return;
+
+ inp = inp.trim();
+
+ if (inp.equals("")) {
+ return;
+ }
+
+ desktop.addImage(inp, icon.getImage());
+ });
+
+ fileMenu.add(changeImage);
+ fileMenu.add(reloadImage);
+ fileMenu.addSeparator();
+ fileMenu.add(storeImage);
+
+ JMenu editMenu = new JMenu("Edit Image");
+ editMenu.setMnemonic('E');
+
+ JMenuItem applyPipe = new JMenuItem("Apply Pipeline");
+ applyPipe.setMnemonic('A');
+ applyPipe.addActionListener((ev) -> {
+ PipelinePicker pick = new PipelinePicker();
+
+ pick.pack();
+ pick.setVisible(true);
+
+ if (pick.pipeName == null) {
+ System.out.println("WARN: picked null pipeline");
+ return;
+ }
+
+ Pipeline pipeline = ImgChain.chan.pipelineRepo.get(pick.pipeName);
+
+ icon.setImage(pipeline.process(Utils.toBuffered(icon.getImage())));
+ lab.repaint();
+ });
+
+ editMenu.addSeparator();
+ editMenu.add(applyPipe);
+
+ bar.add(fileMenu);
+ bar.add(editMenu);
+
+ return bar;
+ }
+
+ private JLabel loadLabel(File img) {
+ JLabel lab = null;
+
+ try {
+ URL imgURL = img.toURI().toURL();
+
+ icon = new ImageIcon(imgURL);
+
+ setSize(icon.getIconWidth(), icon.getIconHeight());
+
+ lab = new JLabel(icon);
+ } catch (MalformedURLException e) {
+ String msg = String.format("Error: Could not load image %s", img.getPath());
+
+ System.out.printf("%s\n", msg);
+
+ e.printStackTrace();
+
+ JOptionPane.showInternalMessageDialog(this, msg, "Error loading image",
+ JOptionPane.ERROR_MESSAGE);
+ }
+
+ return lab;
+ }
+
+ public boolean isInitialized() {
+ return initted;
+ }
+}
diff --git a/src/bjc/imgchain/pipeline/MutablePipeline.java b/src/bjc/imgchain/pipeline/MutablePipeline.java new file mode 100644 index 0000000..6272b30 --- /dev/null +++ b/src/bjc/imgchain/pipeline/MutablePipeline.java @@ -0,0 +1,102 @@ +package bjc.imgchain.pipeline;
+
+import java.awt.Image;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An editable {@link Pipeline}
+ *
+ * @author acm
+ *
+ */
+public class MutablePipeline implements Pipeline {
+ private final List<PipelineStage> stages;
+
+ private String name;
+
+ /**
+ * Create a new unnamed mutable pipeline.
+ */
+ public MutablePipeline() {
+ stages = new ArrayList<>();
+
+ name = "Unnamed Pipeline";
+ }
+
+ /**
+ * Create a new named mutable pipeline.
+ *
+ * @param name
+ * The name of the pipeline.
+ */
+ public MutablePipeline(String name) {
+ stages = new ArrayList<>();
+
+ this.name = name;
+ }
+
+ @Override
+ public Image process(Image input) {
+ Image proc = input;
+
+ for (PipelineStage stage : stages) {
+ System.out.println("Applying stage " + stage.name());
+ proc = stage.process(proc);
+ System.out.println("Applied stage " + stage.name());
+ }
+
+ return proc;
+ }
+
+ @Override
+ public List<PipelineStage> stages() {
+ return stages;
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ /**
+ * Set the name of the pipeline.
+ *
+ * @param nam
+ * The name of the pipeline.
+ */
+ public void name(String nam) {
+ name = nam;
+ }
+
+ /**
+ * Append a pipeline stage to the end of this pipeline.
+ *
+ * @param stag
+ * The stage to add.
+ */
+ public void addStage(PipelineStage stag) {
+ stages.add(stag);
+ }
+
+ /**
+ * Remove a pipeline stage.
+ *
+ * @param stag
+ * The stage to remove.
+ */
+ public void removeStage(PipelineStage stag) {
+ stages.remove(stag);
+ }
+
+ /**
+ * Remove a pipeline stage by index.
+ *
+ * @param idx
+ * The index of the stage to remove.
+ */
+ public void removeStage(int idx) {
+ stages.remove(idx);
+ }
+
+}
diff --git a/src/bjc/imgchain/pipeline/Pipeline.java b/src/bjc/imgchain/pipeline/Pipeline.java new file mode 100644 index 0000000..faba6d5 --- /dev/null +++ b/src/bjc/imgchain/pipeline/Pipeline.java @@ -0,0 +1,37 @@ +package bjc.imgchain.pipeline;
+
+import java.awt.Image;
+import java.util.List;
+
+/**
+ * Represents a pipeline for processing images.
+ *
+ * @author acm
+ *
+ */
+public interface Pipeline {
+ /**
+ * Process an image using the stages.
+ *
+ * @param input
+ * The input image, or null if no image is input.
+ * @return The output image, or null if no image is output.
+ */
+ Image process(Image input);
+
+ /**
+ * Get the stages of the pipeline.
+ *
+ * @return The stages of the pipeline.
+ */
+ List<PipelineStage> stages();
+
+ /**
+ * Get the name of the pipeline.
+ *
+ * @return The name of the pipeline.
+ */
+ default String name() {
+ return "Unnamed Pipeline";
+ }
+}
diff --git a/src/bjc/imgchain/pipeline/PipelinePicker.java b/src/bjc/imgchain/pipeline/PipelinePicker.java new file mode 100644 index 0000000..92478a6 --- /dev/null +++ b/src/bjc/imgchain/pipeline/PipelinePicker.java @@ -0,0 +1,63 @@ +package bjc.imgchain.pipeline;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+
+import bjc.imgchain.ImgChain;
+
+public class PipelinePicker extends JDialog {
+ private static final long serialVersionUID = 1L;
+
+ public String pipeName;
+
+ public PipelinePicker() {
+ super();
+
+ setModalityType(ModalityType.APPLICATION_MODAL);
+ setTitle("Add a Stage");
+
+ setupGUI();
+ }
+
+ private void setupGUI() {
+ setLayout(new BorderLayout());
+
+ DefaultListModel<String> pipeModel = new DefaultListModel<>();
+ for (String pipeName : ImgChain.chan.pipelineRepo.keySet()) {
+ pipeModel.addElement(pipeName);
+ }
+
+ JList<String> pipeList = new JList<>(pipeModel);
+ JScrollPane listScroll = new JScrollPane(pipeList);
+
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new GridLayout(2, 1));
+
+ JButton addStage = new JButton("Select Pipe");
+ addStage.addActionListener((ev) -> {
+ pipeName = pipeList.getSelectedValue();
+
+ setVisible(false);
+ dispose();
+ });
+
+ JButton cancel = new JButton("Cancel");
+ cancel.addActionListener((ev) -> {
+ setVisible(false);
+ dispose();
+ });
+
+ buttonPanel.add(addStage);
+ buttonPanel.add(cancel);
+
+ add(listScroll, BorderLayout.CENTER);
+ add(buttonPanel, BorderLayout.PAGE_END);
+ }
+}
diff --git a/src/bjc/imgchain/pipeline/PipelineStage.java b/src/bjc/imgchain/pipeline/PipelineStage.java new file mode 100644 index 0000000..46966c3 --- /dev/null +++ b/src/bjc/imgchain/pipeline/PipelineStage.java @@ -0,0 +1,52 @@ +package bjc.imgchain.pipeline;
+
+import java.awt.Image;
+
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+
+/**
+ * A stage in an image processing pipeline.
+ *
+ * @author acm
+ *
+ */
+public interface PipelineStage {
+ /**
+ * Get the input/output type of this stage.
+ *
+ * @return The type of the stage.
+ *
+ */
+ StageType getType();
+
+ /**
+ * Pass an image through the stage.
+ *
+ * @param inp
+ * The input image, if one is needed
+ * @return The output image, if one is provided.
+ */
+ Image process(Image inp);
+
+ /**
+ * Get the name of this stage.
+ *
+ * @return The name of this stage.
+ */
+ String name();
+
+ /**
+ * Get a brief description of what this stage does.
+ *
+ * @return A brief description of what the stage does.
+ */
+ String description();
+
+ /**
+ * Get an editor for this stage.
+ *
+ * @return
+ */
+ JComponent getEditor();
+}
diff --git a/src/bjc/imgchain/pipeline/StageType.java b/src/bjc/imgchain/pipeline/StageType.java new file mode 100644 index 0000000..f54588d --- /dev/null +++ b/src/bjc/imgchain/pipeline/StageType.java @@ -0,0 +1,21 @@ +package bjc.imgchain.pipeline;
+
+/**
+ * The type of stage a given {@link PipelineStage} is.
+ * @author acm
+ *
+ */
+public enum StageType {
+ /**
+ * No image -> Image
+ */
+ IMGSOURCE,
+ /**
+ * Image -> Image
+ */
+ IMGTRANS,
+ /**
+ * Image -> No image
+ */
+ IMGSINK
+}
diff --git a/src/bjc/imgchain/pipeline/stages/AbstractPipelineStage.java b/src/bjc/imgchain/pipeline/stages/AbstractPipelineStage.java new file mode 100644 index 0000000..4183f4f --- /dev/null +++ b/src/bjc/imgchain/pipeline/stages/AbstractPipelineStage.java @@ -0,0 +1,33 @@ +package bjc.imgchain.pipeline.stages;
+
+import bjc.imgchain.pipeline.PipelineStage;
+import bjc.imgchain.pipeline.StageType;
+
+/**
+ * Abstract base class for stages.
+ *
+ * @author acm
+ *
+ */
+public abstract class AbstractPipelineStage implements PipelineStage {
+ private final StageType type;
+
+ protected AbstractPipelineStage(StageType type) {
+ this.type = type;
+ }
+
+ @Override
+ public StageType getType() {
+ return type;
+ }
+
+ @Override
+ public String description() {
+ return name();
+ }
+
+ @Override
+ public String toString() {
+ return name();
+ }
+}
diff --git a/src/bjc/imgchain/pipeline/stages/AbstractPixelStage.java b/src/bjc/imgchain/pipeline/stages/AbstractPixelStage.java new file mode 100644 index 0000000..44e3c60 --- /dev/null +++ b/src/bjc/imgchain/pipeline/stages/AbstractPixelStage.java @@ -0,0 +1,30 @@ +package bjc.imgchain.pipeline.stages;
+
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+
+import bjc.imgchain.pipeline.StageType;
+import bjc.imgchain.utils.Utils;
+
+public abstract class AbstractPixelStage extends AbstractPipelineStage {
+
+ protected AbstractPixelStage(StageType type) {
+ super(type);
+ }
+
+ @Override
+ public Image process(Image inp) {
+ BufferedImage buf = (BufferedImage) inp;
+
+ for (int y = 0; y < buf.getHeight(); y++) {
+ for (int x = 0; x < buf.getWidth(); x++) {
+ int[] pix = Utils.toARGBQuad(buf.getRGB(x, y));
+ buf.setRGB(x, y, Utils.fromARGBQuad(pix));
+ }
+ }
+
+ return buf;
+ }
+
+ public abstract int[] processPixel(int[] pix);
+}
diff --git a/src/bjc/imgchain/pipeline/stages/BrightnessStage.java b/src/bjc/imgchain/pipeline/stages/BrightnessStage.java new file mode 100644 index 0000000..4f0feb2 --- /dev/null +++ b/src/bjc/imgchain/pipeline/stages/BrightnessStage.java @@ -0,0 +1,141 @@ +package bjc.imgchain.pipeline.stages;
+
+import java.awt.GridLayout;
+import java.awt.Image;
+
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.border.BevelBorder;
+import javax.swing.border.TitledBorder;
+
+import bjc.imgchain.pipeline.StageType;
+import bjc.imgchain.utils.LabeledInputPanel;
+
+public class BrightnessStage extends AbstractPixelStage {
+ public BrightnessStage() {
+ this(1, 0, 0, 0, 1, 0, 0, 0, 1);
+ }
+
+ public BrightnessStage(int rr, int rg, int rb, int gr, int gg, int gb,
+ int br, int bg, int bb) {
+ super(StageType.IMGTRANS);
+
+ this.rr = rr;
+ this.rg = rg;
+ this.rb = rb;
+ this.gr = gr;
+ this.gg = gg;
+ this.gb = gb;
+ this.br = br;
+ this.bg = bg;
+ this.bb = bb;
+ }
+
+
+ private int rr, rg, rb;
+ private int gr, gg, gb;
+ private int br, bg, bb;
+
+ @Override
+ public int[] processPixel(int[] pix) {
+ int[] ret = new int[4];
+
+ ret[0] = pix[0];
+ ret[1] = (int) ((pix[1] + rr) + (pix[2] + rg) + (pix[3] + rb));
+ ret[2] = (int) ((pix[1] + gr) + (pix[2] + gg) + (pix[3] + gb));
+ ret[3] = (int) ((pix[1] + br) + (pix[2] + bg) + (pix[3] + bb));
+
+ ret[1] = Math.max(0, Math.min(255, ret[1]));
+ ret[2] = Math.max(0, Math.min(255, ret[2]));
+ ret[3] = Math.max(0, Math.min(255, ret[3]));
+
+ return ret;
+ }
+
+ @Override
+ public String name() {
+ return "Tint";
+ }
+
+ @Override
+ public String description() {
+ return "Add/remove colors";
+ }
+
+ @Override
+ public JComponent getEditor() {
+ JPanel holder = new JPanel();
+ holder.setLayout(new GridLayout(3, 1));
+
+ JPanel rSkew = new JPanel();
+ rSkew.setLayout(new GridLayout(1, 3));
+ rSkew.setBorder(new TitledBorder(new BevelBorder(BevelBorder.LOWERED), "Red Balance"));
+
+ JPanel gSkew = new JPanel();
+ gSkew.setLayout(new GridLayout(1, 3));
+ gSkew.setBorder(new TitledBorder(new BevelBorder(BevelBorder.LOWERED), "Green Balance"));
+
+ JPanel bSkew = new JPanel();
+ bSkew.setLayout(new GridLayout(1, 3));
+ bSkew.setBorder(new TitledBorder(new BevelBorder(BevelBorder.LOWERED), "Blue Balance"));
+
+
+ LabeledInputPanel rpercRed = new LabeledInputPanel("% Red", rr);
+ LabeledInputPanel rpercGreen = new LabeledInputPanel("% Green", rg);
+ LabeledInputPanel rpercBlue = new LabeledInputPanel("% Blue", rb);
+ rSkew.add(rpercRed);
+ rSkew.add(rpercGreen);
+ rSkew.add(rpercBlue);
+
+ LabeledInputPanel gpercRed = new LabeledInputPanel("% Red", gr);
+ LabeledInputPanel gpercGreen = new LabeledInputPanel("% Green", gg);
+ LabeledInputPanel gpercBlue = new LabeledInputPanel("% Blue", gb);
+ gSkew.add(gpercRed);
+ gSkew.add(gpercGreen);
+ gSkew.add(gpercBlue);
+
+ LabeledInputPanel bpercRed = new LabeledInputPanel("% Red", br);
+ LabeledInputPanel bpercGreen = new LabeledInputPanel("% Green", bg);
+ LabeledInputPanel bpercBlue = new LabeledInputPanel("% Blue", bb);
+ bSkew.add(bpercRed);
+ bSkew.add(bpercGreen);
+ bSkew.add(bpercBlue);
+
+ rpercRed.addPropertyChangeListener("value", (ev) -> {
+ rr = (Integer)rpercRed.field.getValue();
+ });
+ gpercRed.addPropertyChangeListener("value", (ev) -> {
+ gr = (Integer)gpercRed.field.getValue();
+ });
+ bpercRed.addPropertyChangeListener("value", (ev) -> {
+ br = (Integer)bpercRed.field.getValue();
+ });
+
+ rpercGreen.addPropertyChangeListener("value", (ev) -> {
+ rg = (Integer)rpercGreen.field.getValue();
+ });
+ gpercGreen.addPropertyChangeListener("value", (ev) -> {
+ gg = (Integer)gpercGreen.field.getValue();
+ });
+ bpercGreen.addPropertyChangeListener("value", (ev) -> {
+ bg = (Integer)bpercGreen.field.getValue();
+ });
+
+ rpercBlue.addPropertyChangeListener("value", (ev) -> {
+ rb = (Integer)rpercBlue.field.getValue();
+ });
+ gpercBlue.addPropertyChangeListener("value", (ev) -> {
+ gb = (Integer)gpercBlue.field.getValue();
+ });
+ bpercBlue.addPropertyChangeListener("value", (ev) -> {
+ bb = (Integer)bpercBlue.field.getValue();
+ });
+
+ holder.add(rSkew);
+ holder.add(gSkew);
+ holder.add(bSkew);
+
+ return holder;
+ }
+
+}
\ No newline at end of file diff --git a/src/bjc/imgchain/pipeline/stages/ColorSkewStage.java b/src/bjc/imgchain/pipeline/stages/ColorSkewStage.java new file mode 100644 index 0000000..ba9d127 --- /dev/null +++ b/src/bjc/imgchain/pipeline/stages/ColorSkewStage.java @@ -0,0 +1,141 @@ +package bjc.imgchain.pipeline.stages;
+
+import java.awt.GridLayout;
+import java.awt.Image;
+
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.border.BevelBorder;
+import javax.swing.border.TitledBorder;
+
+import bjc.imgchain.pipeline.StageType;
+import bjc.imgchain.utils.LabeledInputPanel;
+
+public class ColorSkewStage extends AbstractPixelStage {
+ public ColorSkewStage() {
+ this(1, 0, 0, 0, 1, 0, 0, 0, 1);
+ }
+
+ public ColorSkewStage(double rr, double rg, double rb, double gr, double gg, double gb,
+ double br, double bg, double bb) {
+ super(StageType.IMGTRANS);
+
+ this.rr = rr;
+ this.rg = rg;
+ this.rb = rb;
+ this.gr = gr;
+ this.gg = gg;
+ this.gb = gb;
+ this.br = br;
+ this.bg = bg;
+ this.bb = bb;
+ }
+
+
+ private double rr, rg, rb;
+ private double gr, gg, gb;
+ private double br, bg, bb;
+
+ @Override
+ public int[] processPixel(int[] pix) {
+ int[] ret = new int[4];
+
+ ret[0] = pix[0];
+ ret[1] = (int) ((pix[1] * rr) + (pix[2] * rg) + (pix[3] * rb));
+ ret[2] = (int) ((pix[1] * gr) + (pix[2] * gg) + (pix[3] * gb));
+ ret[3] = (int) ((pix[1] * br) + (pix[2] * bg) + (pix[3] * bb));
+
+ ret[1] = Math.min(255, ret[1]);
+ ret[2] = Math.min(255, ret[2]);
+ ret[3] = Math.min(255, ret[3]);
+
+ return ret;
+ }
+
+ @Override
+ public String name() {
+ return "Color Skew";
+ }
+
+ @Override
+ public String description() {
+ return "Adjust color balance";
+ }
+
+ @Override
+ public JComponent getEditor() {
+ JPanel holder = new JPanel();
+ holder.setLayout(new GridLayout(3, 1));
+
+ JPanel rSkew = new JPanel();
+ rSkew.setLayout(new GridLayout(1, 3));
+ rSkew.setBorder(new TitledBorder(new BevelBorder(BevelBorder.LOWERED), "Red Balance"));
+
+ JPanel gSkew = new JPanel();
+ gSkew.setLayout(new GridLayout(1, 3));
+ gSkew.setBorder(new TitledBorder(new BevelBorder(BevelBorder.LOWERED), "Green Balance"));
+
+ JPanel bSkew = new JPanel();
+ bSkew.setLayout(new GridLayout(1, 3));
+ bSkew.setBorder(new TitledBorder(new BevelBorder(BevelBorder.LOWERED), "Blue Balance"));
+
+
+ LabeledInputPanel rpercRed = new LabeledInputPanel("% Red", rr);
+ LabeledInputPanel rpercGreen = new LabeledInputPanel("% Green", rg);
+ LabeledInputPanel rpercBlue = new LabeledInputPanel("% Blue", rb);
+ rSkew.add(rpercRed);
+ rSkew.add(rpercGreen);
+ rSkew.add(rpercBlue);
+
+ LabeledInputPanel gpercRed = new LabeledInputPanel("% Red", gr);
+ LabeledInputPanel gpercGreen = new LabeledInputPanel("% Green", gg);
+ LabeledInputPanel gpercBlue = new LabeledInputPanel("% Blue", gb);
+ gSkew.add(gpercRed);
+ gSkew.add(gpercGreen);
+ gSkew.add(gpercBlue);
+
+ LabeledInputPanel bpercRed = new LabeledInputPanel("% Red", br);
+ LabeledInputPanel bpercGreen = new LabeledInputPanel("% Green", bg);
+ LabeledInputPanel bpercBlue = new LabeledInputPanel("% Blue", bb);
+ bSkew.add(bpercRed);
+ bSkew.add(bpercGreen);
+ bSkew.add(bpercBlue);
+
+ rpercRed.addPropertyChangeListener("value", (ev) -> {
+ rr = (Double)rpercRed.field.getValue();
+ });
+ gpercRed.addPropertyChangeListener("value", (ev) -> {
+ gr = (Double)gpercRed.field.getValue();
+ });
+ bpercRed.addPropertyChangeListener("value", (ev) -> {
+ br = (Double)bpercRed.field.getValue();
+ });
+
+ rpercGreen.addPropertyChangeListener("value", (ev) -> {
+ rg = (Double)rpercGreen.field.getValue();
+ });
+ gpercGreen.addPropertyChangeListener("value", (ev) -> {
+ gg = (Double)gpercGreen.field.getValue();
+ });
+ bpercGreen.addPropertyChangeListener("value", (ev) -> {
+ bg = (Double)bpercGreen.field.getValue();
+ });
+
+ rpercBlue.addPropertyChangeListener("value", (ev) -> {
+ rb = (Double)rpercBlue.field.getValue();
+ });
+ gpercBlue.addPropertyChangeListener("value", (ev) -> {
+ gb = (Double)gpercBlue.field.getValue();
+ });
+ bpercBlue.addPropertyChangeListener("value", (ev) -> {
+ bb = (Double)bpercBlue.field.getValue();
+ });
+
+ holder.add(rSkew);
+ holder.add(gSkew);
+ holder.add(bSkew);
+
+ return holder;
+ }
+
+}
diff --git a/src/bjc/imgchain/pipeline/stages/GaussStage.java b/src/bjc/imgchain/pipeline/stages/GaussStage.java new file mode 100644 index 0000000..2b04ddf --- /dev/null +++ b/src/bjc/imgchain/pipeline/stages/GaussStage.java @@ -0,0 +1,110 @@ +package bjc.imgchain.pipeline.stages;
+
+import java.awt.GridLayout;
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+import java.awt.image.ConvolveOp;
+import java.awt.image.Kernel;
+
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+
+import bjc.imgchain.pipeline.StageType;
+import bjc.imgchain.utils.LabeledInputPanel;
+
+public class GaussStage extends AbstractPipelineStage {
+ private int m;
+ private double sig, k;
+
+ public GaussStage() {
+ super(StageType.IMGTRANS);
+ }
+
+ @Override
+ public Image process(Image inp) {
+ BufferedImage buf = (BufferedImage) inp;
+
+ ConvolveOp cop = new ConvolveOp(genKern());
+
+ BufferedImage ret = cop.createCompatibleDestImage(buf, null);
+
+ cop.filter(buf, ret);
+
+ return ret;
+ }
+
+ private Kernel genKern() {
+ float[][] w = new float[m][m];
+
+ float sum = 0.0f;
+
+ for (int i = 0; i < m - 1; i++) {
+ for (int j = 0; j < m - 1; j++) {
+ double s = i - (m / 2.0);
+ double t = j - (m / 2.0);
+
+ double r2 = (s * s) + (t * t);
+
+ double frac = r2 / (2.0 * sig * sig);
+
+ double val = k * Math.exp(-frac);
+
+ int idx1 = m - i;
+ int idx2 = m - j;
+
+ float fval = (float) val;
+
+ w[idx1 - 1][idx2 - 1] = fval;
+ sum += fval;
+ }
+ }
+
+ float invsum = 1 / sum;
+
+ float[] dat = new float[m * m];
+
+ for (int i = 0; i < m; i++) {
+ for (int j = 0; j < m; j++) {
+ dat[(i * m) + j] = w[i][j] * sum;
+ }
+ }
+
+ return new Kernel(m, m, dat);
+ }
+
+ @Override
+ public String name() {
+ return "Gaussian";
+ }
+
+ @Override
+ public String description() {
+ return "Perform a gaussian blur";
+ }
+
+ @Override
+ public JComponent getEditor() {
+ JPanel holder = new JPanel();
+ holder.setLayout(new GridLayout(3, 1));
+
+ LabeledInputPanel mField = new LabeledInputPanel("Size of kernel", 3);
+ mField.addPropertyChangeListener("value", (ev) -> {
+ m = (Integer) mField.field.getValue();
+ });
+ LabeledInputPanel sigField = new LabeledInputPanel("Value for sigma", 3.0);
+ sigField.addPropertyChangeListener("value", (ev) -> {
+ sig = (Double) sigField.field.getValue();
+ });
+ LabeledInputPanel kField = new LabeledInputPanel("Value for k", 1.0);
+ kField.addPropertyChangeListener("value", (ev) -> {
+ k = (Double) kField.field.getValue();
+ });
+
+ holder.add(mField);
+ holder.add(sigField);
+ holder.add(kField);
+
+ return holder;
+ }
+
+}
diff --git a/src/bjc/imgchain/pipeline/stages/GreyscaleStage.java b/src/bjc/imgchain/pipeline/stages/GreyscaleStage.java new file mode 100644 index 0000000..71bb743 --- /dev/null +++ b/src/bjc/imgchain/pipeline/stages/GreyscaleStage.java @@ -0,0 +1,45 @@ +package bjc.imgchain.pipeline.stages;
+
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+
+import bjc.imgchain.pipeline.StageType;
+import bjc.imgchain.utils.Utils;
+
+public class GreyscaleStage extends AbstractPixelStage {
+
+ public GreyscaleStage() {
+ super(StageType.IMGTRANS);
+ }
+
+ public int[] processPixel(int[] pix) {
+ int[] ret = new int[4];
+
+ int avg = (pix[1] + pix[2] + pix[3]) / 3;
+
+ ret[0] = pix[0];
+ ret[1] = avg;
+ ret[2] = avg;
+ ret[3] = avg;
+
+ return ret;
+ }
+ @Override
+ public String name() {
+ return "Greyscale";
+ }
+
+ @Override
+ public String description() {
+ return "Convert an image into greyscale color";
+ }
+
+ @Override
+ public JComponent getEditor() {
+ return new JLabel("No configuration possible");
+ }
+
+}
diff --git a/src/bjc/imgchain/pipeline/stages/IDStage.java b/src/bjc/imgchain/pipeline/stages/IDStage.java new file mode 100644 index 0000000..93aaebd --- /dev/null +++ b/src/bjc/imgchain/pipeline/stages/IDStage.java @@ -0,0 +1,44 @@ +package bjc.imgchain.pipeline.stages;
+
+import java.awt.Image;
+
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+
+import bjc.imgchain.pipeline.StageType;
+
+/**
+ * A pipeline stage that does nothing.
+ *
+ * @author acm
+ *
+ */
+public class IDStage extends AbstractPipelineStage {
+
+ /**
+ * Create a new identity stage.
+ */
+ public IDStage() {
+ super(StageType.IMGTRANS);
+ }
+
+ @Override
+ public String name() {
+ return "Identity";
+ }
+
+ @Override
+ public Image process(Image inp) {
+ return inp;
+ }
+
+ @Override
+ public String description() {
+ return "Passes an image straight through.";
+ }
+
+ @Override
+ public JComponent getEditor() {
+ return new JLabel("Nothing to edit");
+ }
+}
diff --git a/src/bjc/imgchain/pipeline/stages/NegativeStage.java b/src/bjc/imgchain/pipeline/stages/NegativeStage.java new file mode 100644 index 0000000..028139c --- /dev/null +++ b/src/bjc/imgchain/pipeline/stages/NegativeStage.java @@ -0,0 +1,33 @@ +package bjc.imgchain.pipeline.stages;
+
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+
+import bjc.imgchain.pipeline.StageType;
+
+public class NegativeStage extends AbstractPixelStage {
+ public NegativeStage() {
+ super(StageType.IMGTRANS);
+ }
+
+ @Override
+ public String name() {
+ return "Negative";
+ }
+
+ @Override
+ public String description() {
+ return "Invert image colors";
+ }
+
+ @Override
+ public JComponent getEditor() {
+ return new JLabel("No configuration available");
+ }
+
+ @Override
+ public int[] processPixel(int[] pix) {
+ return new int[] { pix[0], 255 - pix[1], 255 - pix[2], 255 - pix[3] };
+ }
+
+}
diff --git a/src/bjc/imgchain/pipeline/stages/StagePicker.java b/src/bjc/imgchain/pipeline/stages/StagePicker.java new file mode 100644 index 0000000..8b30ee4 --- /dev/null +++ b/src/bjc/imgchain/pipeline/stages/StagePicker.java @@ -0,0 +1,68 @@ +package bjc.imgchain.pipeline.stages;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+
+import bjc.imgchain.utils.ImmutableTableModel;
+
+public class StagePicker extends JDialog {
+ private static final long serialVersionUID = 1L;
+
+ public String stageName;
+
+ public StagePicker() {
+ super();
+
+ setModalityType(ModalityType.APPLICATION_MODAL);
+ setTitle("Add a Stage");
+
+ setupGUI();
+ }
+
+ private void setupGUI() {
+ setLayout(new BorderLayout());
+
+ String[] columnNames = new String[] { "Stage Name", "Stage Description" };
+
+ /*
+ * :AddStage
+ */
+ String[][] data = new String[][] { { "Identity", "Pass through image unchanged" },
+ { "Greyscale", "Convert a color image into greyscale" },
+ { "Color Skew", "Adjust color balance" }, { "Sepia", "Make your image sepia toned" },
+ { "Negative", "Invert your images colors" }, { "Gaussian Blur", "Blur images" } };
+
+ JTable stageTable = new JTable(new ImmutableTableModel(data, columnNames));
+
+ JScrollPane tableScroll = new JScrollPane(stageTable);
+
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new GridLayout(2, 1));
+
+ JButton addStage = new JButton("Add Stage");
+ addStage.addActionListener((ev) -> {
+ stageName = (String) stageTable.getModel().getValueAt(stageTable.getSelectedRow(), 0);
+
+ setVisible(false);
+ dispose();
+ });
+
+ JButton cancel = new JButton("Cancel");
+ cancel.addActionListener((ev) -> {
+ setVisible(false);
+ dispose();
+ });
+
+ buttonPanel.add(addStage);
+ buttonPanel.add(cancel);
+
+ add(tableScroll, BorderLayout.CENTER);
+ add(buttonPanel, BorderLayout.PAGE_END);
+ }
+}
diff --git a/src/bjc/imgchain/utils/ImmutableTableModel.java b/src/bjc/imgchain/utils/ImmutableTableModel.java new file mode 100644 index 0000000..515f0a0 --- /dev/null +++ b/src/bjc/imgchain/utils/ImmutableTableModel.java @@ -0,0 +1,17 @@ +package bjc.imgchain.utils;
+
+import javax.swing.table.DefaultTableModel;
+
+public class ImmutableTableModel extends DefaultTableModel {
+ private static final long serialVersionUID = 6459890821518409439L;
+
+ public ImmutableTableModel(Object[][] data, Object[] columnNames) {
+ super(data, columnNames);
+ }
+
+
+ @Override
+ public boolean isCellEditable(int row, int column) {
+ return false;
+ }
+}
\ No newline at end of file diff --git a/src/bjc/imgchain/utils/LabeledInputPanel.java b/src/bjc/imgchain/utils/LabeledInputPanel.java new file mode 100644 index 0000000..7eed8b0 --- /dev/null +++ b/src/bjc/imgchain/utils/LabeledInputPanel.java @@ -0,0 +1,42 @@ +package bjc.imgchain.utils;
+
+import java.awt.BorderLayout;
+
+import javax.swing.JFormattedTextField;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+/**
+ * A component for getting formatted input with a label.
+ *
+ * @author bjculkin
+ *
+ */
+public class LabeledInputPanel extends JPanel {
+ private static final long serialVersionUID = 1031310890698539040L;
+
+ /**
+ * The field input is stored in.
+ */
+ public final JFormattedTextField field;
+
+ /**
+ * Create a new labeled input component.
+ *
+ * @param label
+ * The label for the component.
+ * @param val
+ * The initial value for the field.
+ */
+ public LabeledInputPanel(String label, Object val) {
+ super();
+ setLayout(new BorderLayout());
+
+ JLabel xLabel = new JLabel(label);
+
+ field = new JFormattedTextField(val);
+
+ add(xLabel, BorderLayout.LINE_START);
+ add(field, BorderLayout.CENTER);
+ }
+}
\ No newline at end of file diff --git a/src/bjc/imgchain/utils/Utils.java b/src/bjc/imgchain/utils/Utils.java new file mode 100644 index 0000000..38e2ec2 --- /dev/null +++ b/src/bjc/imgchain/utils/Utils.java @@ -0,0 +1,54 @@ +package bjc.imgchain.utils;
+
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+
+public class Utils {
+ public static BufferedImage toBuffered(Image img) {
+ if (img instanceof BufferedImage) {
+ return (BufferedImage) img;
+ }
+
+ /*
+ * Create a buffered image with transparency
+ */
+ BufferedImage bufimg = new BufferedImage(img.getWidth(null), img.getHeight(null),
+ BufferedImage.TYPE_INT_ARGB);
+
+ /*
+ * Draw the image on to the buffered image
+ */
+ Graphics2D graph = bufimg.createGraphics();
+ graph.drawImage(img, 0, 0, null);
+ graph.dispose();
+
+ /*
+ * Return the buffered image
+ */
+ return bufimg;
+ }
+
+ public static int[] toARGBQuad(int rbg) {
+ return new int[] { (rbg >> 24) & 0xff, (rbg >> 16) & 0xff, (rbg >> 8) & 0xff, rbg & 0xff };
+ }
+
+ public static int fromARGBQuad(int[] argb) {
+ return (argb[0] << 24) | (argb[1] << 16) | (argb[2] << 8) | argb[3];
+ }
+
+ public static int[][] padarray(int[][] arr, int padWith, int numOfPads) {
+ int[][] temp = new int[arr.length + numOfPads * 2][arr[0].length + numOfPads * 2];
+ for (int i = 0; i < temp.length; i++) {
+ for (int j = 0; j < temp[i].length; j++) {
+ temp[i][j] = padWith;
+ }
+ }
+ for (int i = 0; i < arr.length; i++) {
+ for (int j = 0; j < arr[i].length; j++) {
+ temp[i + numOfPads][j + numOfPads] = arr[i][j];
+ }
+ }
+ return temp;
+ }
+}
|
