Initial commit of working code

This commit is contained in:
Autumn Naber
2020-11-18 18:29:09 -08:00
commit 099ae04410
7 changed files with 581 additions and 0 deletions

20
.classpath Normal file
View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-13">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/classes" path="src">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
bin/
target/
.DS_Store

23
.project Normal file
View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>ImageWitch</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@@ -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

View File

@@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

30
pom.xml Normal file
View File

@@ -0,0 +1,30 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ImageWitch</groupId>
<artifactId>ImageWitch</artifactId>
<version>0.0.1-SNAPSHOT</version>
<build>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<release>13</release>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.drewnoakes</groupId>
<artifactId>metadata-extractor</artifactId>
<version>2.15.0</version>
</dependency>
<dependency>
<groupId>org.imgscalr</groupId>
<artifactId>imgscalr-lib</artifactId>
<version>4.2</version>
</dependency>
</dependencies>
</project>

486
src/ImageWitchGui.java Normal file
View File

@@ -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,
"<html>Unable to read file: <br>" +
oldFile.getAbsolutePath() +
"<br><br>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,
"<html>Unable to resize file: <br>" + filename +
"<br><br>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,
"<html>Unable to save file: <br>" + filename +
"<br><br>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();
}
}
}