commit 099ae04410abe18853c6fe2b768f7c17fdd6be60 Author: Autumn Naber Date: Wed Nov 18 18:29:09 2020 -0800 Initial commit of working code diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..298336c --- /dev/null +++ b/.classpath @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7be7d0f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +bin/ +target/ +.DS_Store diff --git a/.project b/.project new file mode 100644 index 0000000..3cefd23 --- /dev/null +++ b/.project @@ -0,0 +1,23 @@ + + + ImageWitch + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + org.eclipse.jdt.core.javanature + + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..0c44525 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,15 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=13 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=13 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=13 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..cc283d9 --- /dev/null +++ b/pom.xml @@ -0,0 +1,30 @@ + + 4.0.0 + ImageWitch + ImageWitch + 0.0.1-SNAPSHOT + + src + + + maven-compiler-plugin + 3.8.1 + + 13 + + + + + + + com.drewnoakes + metadata-extractor + 2.15.0 + + + org.imgscalr + imgscalr-lib + 4.2 + + + \ No newline at end of file diff --git a/src/ImageWitchGui.java b/src/ImageWitchGui.java new file mode 100644 index 0000000..136736c --- /dev/null +++ b/src/ImageWitchGui.java @@ -0,0 +1,486 @@ +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Image; +import java.awt.Insets; +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.awt.image.ImageObserver; +import java.io.File; +import java.io.IOException; + +import javax.imageio.ImageIO; +import javax.swing.InputVerifier; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JProgressBar; +import javax.swing.JTextField; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.filechooser.FileNameExtensionFilter; + +import org.imgscalr.Scalr.Rotation; + +import com.drew.imaging.ImageMetadataReader; +import com.drew.metadata.Metadata; +import com.drew.metadata.MetadataException; +import com.drew.metadata.exif.ExifIFD0Directory; + +public class ImageWitchGui extends JFrame { + private static final long serialVersionUID = 1L; + public static final String[] supportedExtensions = + {"jpg","jpeg","png","gif"}; + + File[] selectedFiles = null; + + JTextField heightField; + JTextField widthField; + + Integer maxWidth = 0; + Integer maxHeight = 0; + Boolean errorNotified = false; + + JButton loadButton; + JButton resizeButton; + JProgressBar progressBar; + volatile boolean conversionRunning; + + public static void main(String[] args) { + //Schedule a job for the event-dispatching thread: + //creating and showing this application's GUI. + javax.swing.SwingUtilities.invokeLater(new Runnable() { + public void run() { + ImageWitchGui gui = new ImageWitchGui(); + gui.setDefaultCloseOperation(EXIT_ON_CLOSE); + gui.createAndShowGui(); + } + }); + } + + public ImageWitchGui() { + setTitle("ImageWitch"); + try { + UIManager.setLookAndFeel( + UIManager.getCrossPlatformLookAndFeelClassName()); + } catch (Exception e) {} + + InputVerifier verifier = new NumberInputVerifier(); + + heightField = new JTextField("0"); + heightField.setColumns(10); + heightField.setInputVerifier(verifier); + heightField.getDocument().addDocumentListener( + new FieldListener(heightField)); + + widthField = new JTextField("0"); + widthField.setColumns(10); + widthField.setInputVerifier(verifier); + widthField.getDocument().addDocumentListener( + new FieldListener(widthField)); + + loadButton = new JButton("Load Files"); + loadButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JFileChooser chooser = new JFileChooser(); + chooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + chooser.setMultiSelectionEnabled(true); + chooser.setFileFilter( + new FileNameExtensionFilter("Image Files", + supportedExtensions)); + int returnVal = chooser.showOpenDialog(ImageWitchGui.this); + if (returnVal == JFileChooser.APPROVE_OPTION) { + selectedFiles = chooser.getSelectedFiles(); + resizeButton.setEnabled(true); + errorNotified = false; + } + + progressBar.setString("Ready"); + progressBar.setValue(0); + } + }); + + + resizeButton = new JButton("Resize"); + resizeButton.setEnabled(false); + resizeButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (conversionRunning) { + conversionRunning = false; + loadButton.setEnabled(true); + resizeButton.setText("Resize"); + } else { + errorNotified = false; + loadButton.setEnabled(false); + conversionRunning = true; + resizeButton.setText("Cancel"); + Thread conversionThread = new Thread() { + public void run() { + convertFiles(selectedFiles); + } + }; + conversionThread.start(); + } + } + }); + + progressBar = new JProgressBar(); + progressBar.setIndeterminate(false); + progressBar.setStringPainted(true); + progressBar.setString("Ready"); + progressBar.setValue(0); + progressBar.setMaximum(1); + } + + public void createAndShowGui() { + JLabel label; + JPanel panel = new JPanel(new GridBagLayout()); + getContentPane().add(panel); + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + + label = new JLabel("Max Width: "); + c.gridx = 0; + c.gridy = 0; + c.insets = new Insets(2, 2, 2, 2); + c.gridheight = 1; + c.gridwidth = 1; + c.weightx = 1.0; + c.weighty = 1.0; + panel.add(label, c); + + c.gridx = 1; + c.gridy = 0; + panel.add(widthField, c); + + label = new JLabel("Max Height: "); + c.gridx = 0; + c.gridy = 1; + panel.add(label, c); + + c.gridx = 1; + c.gridy = 1; + panel.add(heightField, c); + + c.gridx = 0; + c.gridy = 2; + panel.add(loadButton, c); + + c.gridx = 1; + c.gridy = 2; + panel.add(resizeButton, c); + + c.gridx = 0; + c.gridy = 3; + c.gridwidth = 2; + c.insets = new Insets(0, 0, 0, 0); + c.fill = GridBagConstraints.BOTH; + panel.add(progressBar, c); + + this.pack(); + this.setVisible(true); + } + + private String getExtension(String filename) { + String extension; + + extension = filename.substring(filename.lastIndexOf(".")+1).toLowerCase(); + if (extension.equals("jpeg")) { + extension = "jpg"; + } + + return extension; + } + + private String getNewFilename(String filename) { + String newFilename; + + int dotIdx = filename.lastIndexOf("."); + newFilename = filename.substring(0, dotIdx) + + "_scaled." + getExtension(filename); + + return newFilename; + } + + private Dimension getNewDimensions(int maxWidth, int maxHeight, + BufferedImage image) { + Dimension dim = new Dimension(); + double sourceRatio, targetRatio; + int targetWidth, targetHeight; + + sourceRatio = (double) image.getWidth() / (double) image.getHeight(); + targetRatio = (double) maxWidth / (double) maxHeight; + + if (maxWidth < 0 && maxHeight < 0) { + throw new IllegalArgumentException( + "At least one of width and height must be selected"); + } else if (maxWidth <= 0 || + (maxHeight > 0 && targetRatio > sourceRatio)) { + targetHeight = maxHeight; + targetWidth = (int) (targetHeight * sourceRatio); + + } else if (maxHeight <= 0 || + (maxWidth > 0 && targetRatio < sourceRatio)) { + targetWidth = maxWidth; + targetHeight = (int) (targetWidth / sourceRatio); + } else { + targetWidth = maxWidth; + targetHeight = maxHeight; + } + + dim.height = targetHeight; + dim.width = targetWidth; + + return dim; + } + + private BufferedImage rotateUpright(BufferedImage image, File file) { + Metadata metadata; + ExifIFD0Directory exifIFD0; + int orientation; + AffineTransform transform; + BufferedImage uprightImage; + AffineTransformOp transformOp; + + try { + metadata = ImageMetadataReader.readMetadata(file); + } catch (Exception e) { + e.printStackTrace(); + return image; + } + + exifIFD0 = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class); + + try { + orientation = exifIFD0.getInt(ExifIFD0Directory.TAG_ORIENTATION); + } catch (Exception e) { + return image; + } + + transform = new AffineTransform(); + + switch (orientation) { + case 6: // [Exif IFD0] Orientation - Right side, top (Rotate 90 CW) + uprightImage = new BufferedImage(image.getHeight(), image.getWidth(), image.getType()); + transform.translate(image.getHeight() / 2, image.getWidth() / 2); + transform.rotate(Math.PI/2); + transform.translate(-image.getWidth() / 2, -image.getHeight() / 2); + transformOp = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR); + transformOp.filter(image, uprightImage); + break; + case 3: // [Exif IFD0] Orientation - Bottom, right side (Rotate 180) + uprightImage = new BufferedImage(image.getWidth(), image.getHeight(), image.getType()); + transform.translate(image.getHeight() / 2, image.getWidth() / 2); + transform.rotate(Math.PI); + transform.translate(-image.getHeight() / 2, -image.getWidth() / 2); + transformOp = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR); + transformOp.filter(image, uprightImage); + break; + case 8: // [Exif IFD0] Orientation - Left side, bottom (Rotate 270 CW) + uprightImage = new BufferedImage(image.getHeight(), image.getWidth(), image.getType()); + transform.translate(image.getHeight() / 2, image.getWidth() / 2); + transform.rotate(-Math.PI/2); + transform.translate(-image.getWidth() / 2, -image.getHeight() / 2); + transformOp = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR); + transformOp.filter(image, uprightImage); + break; + default: + uprightImage = image; + } + + return uprightImage; + } + + private void updateProgress(int progress, int maxValue, String text) { + javax.swing.SwingUtilities.invokeLater(new Runnable() { + public void run() { + System.out.println(text); + if (maxValue != progressBar.getMaximum()) { + progressBar.setMaximum(maxValue); + } + progressBar.setValue(progress); + progressBar.setString(text); + + if (progress == maxValue) { + loadButton.setEnabled(true); + resizeButton.setText("Resize"); + } + } + }); + } + + private void convertFiles(File[] files) { + BufferedImage oldImage; + String newFilename; + int index = 0; + int numFiles = files.length; + + progressBar.setMaximum(numFiles); + for (File oldFile : files) { + if (!conversionRunning) { + updateProgress(0, 1, "Cancelled"); + return; + } + try { + oldImage = ImageIO.read(oldFile); + newFilename = getNewFilename(oldFile.getAbsolutePath()); + oldImage = rotateUpright(oldImage, oldFile); + resizeAndSaveImage(oldImage, maxWidth, maxHeight, newFilename); + + progressBar.setValue(index); + index++; + updateProgress(index, numFiles, + "Processing " + Integer.toString(index) + + " of " + Integer.toString(numFiles)); + } catch (IOException e) { + if (!errorNotified) { + errorNotified = true; + JOptionPane.showMessageDialog(ImageWitchGui.this, + "Unable to read file:
" + + oldFile.getAbsolutePath() + + "

Other files may be affected.", + "Read Error", + JOptionPane.ERROR_MESSAGE); + } + } + } + updateProgress(numFiles, numFiles, "Done"); + } + + private void resizeAndSaveImage(BufferedImage image, + int maxWidth, int maxHeight, + String newFilename) + throws IllegalArgumentException + { + Image newImage; + BufferedImage renderedImage; + ImageSaver saver; + Dimension newDim; + + newDim = getNewDimensions(maxWidth, maxHeight, image); + newImage = image.getScaledInstance( + newDim.width, newDim.height, Image.SCALE_SMOOTH); + renderedImage = new BufferedImage( + newDim.width, newDim.height, BufferedImage.TYPE_INT_RGB); + saver = new ImageSaver(newFilename, renderedImage); + renderedImage.getGraphics().drawImage(newImage, 0, 0, saver); + saver.saveImage(); + } + + private class NumberInputVerifier extends InputVerifier { + + @Override + public boolean verify(JComponent input) { + String text = ((JTextField) input).getText(); + try { + Integer.parseInt(text); + } catch (NumberFormatException e) { + return false; + } + return true; + } + + } + + private class ImageSaver implements ImageObserver { + + private String filename; + private BufferedImage image; + private String formatName; + + public ImageSaver(String filename, BufferedImage image) { + this.filename = filename; + this.image = image; + this.formatName = getExtension(filename); + } + + @Override + public boolean imageUpdate(Image img, int infoflags, int x, int y, + int width, int height) { + if (infoflags == ImageObserver.ERROR) { + if (!errorNotified) { + errorNotified = true; + JOptionPane.showMessageDialog(ImageWitchGui.this, + "Unable to resize file:
" + filename + + "

Other files may be affected.", + "Resize Error", + JOptionPane.ERROR_MESSAGE); + } + return false; + } + + saveImage(); + + return false; + } + + public void saveImage() { + File file = new File(filename); + try { + ImageIO.write(image, formatName, file); + } catch (IOException e) { + if (!errorNotified) { + errorNotified = true; + JOptionPane.showMessageDialog(ImageWitchGui.this, + "Unable to save file:
" + filename + + "

Other files may be affected.", + "Save Error", + JOptionPane.ERROR_MESSAGE); + } + } + } + + } + + private class FieldListener implements DocumentListener { + + private JTextField field; + + public FieldListener(JTextField field) { + this.field = field; + } + + private void update() { + Integer value = 0; + + if (!field.getText().isBlank()) { + value = Integer.parseInt(field.getText()); + } + if (field == heightField) { + maxHeight = value; + } else { + maxWidth = value; + } + } + + @Override + public void insertUpdate(DocumentEvent e) { + update(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + update(); + + } + + @Override + public void changedUpdate(DocumentEvent e) { + update(); + + } + + } + +}