From 27ab04293a1c52eb02fc74a07529282eb561f093 Mon Sep 17 00:00:00 2001 From: Victor Date: Mon, 4 Jan 2021 19:54:18 +0200 Subject: [PATCH] Add ResizableImage to reduce scalings count due to endless repaint --- .../java/com/annimon/imagetagger/Main.java | 4 +- .../imagetagger/logic/FilesProvider.java | 35 ++++++++ .../imagetagger/logic/ImageProcessor.java | 36 ++++---- .../annimon/imagetagger/views/ImagePanel.java | 38 +++------ .../imagetagger/views/ResizableImage.java | 82 +++++++++++++++++++ 5 files changed, 152 insertions(+), 43 deletions(-) create mode 100644 src/main/java/com/annimon/imagetagger/logic/FilesProvider.java create mode 100644 src/main/java/com/annimon/imagetagger/views/ResizableImage.java diff --git a/src/main/java/com/annimon/imagetagger/Main.java b/src/main/java/com/annimon/imagetagger/Main.java index b801754..421307c 100644 --- a/src/main/java/com/annimon/imagetagger/Main.java +++ b/src/main/java/com/annimon/imagetagger/Main.java @@ -1,6 +1,7 @@ package com.annimon.imagetagger; import com.annimon.imagetagger.beans.Config; +import com.annimon.imagetagger.logic.FilesProvider; import com.annimon.imagetagger.logic.ImageProcessor; import com.annimon.imagetagger.logic.KeyProcessor; import com.annimon.imagetagger.views.ImagePanel; @@ -33,7 +34,8 @@ public class Main extends JFrame { final var tagButtons = config.getButtons(config.getProfile()); final var tagPanel = new TagPanel(tagButtons); - final var imageProcessor = new ImageProcessor(config.getDir(), config.getFilter(), config.getSort()); + final var files = new FilesProvider(config.getDir()); + final var imageProcessor = new ImageProcessor(files, config.getFilter(), config.getSort()); final var imagePanel = new ImagePanel(imageProcessor); final var keyProcessor = new KeyProcessor(tagButtons); keyProcessor.setTagPanel(tagPanel); diff --git a/src/main/java/com/annimon/imagetagger/logic/FilesProvider.java b/src/main/java/com/annimon/imagetagger/logic/FilesProvider.java new file mode 100644 index 0000000..de91807 --- /dev/null +++ b/src/main/java/com/annimon/imagetagger/logic/FilesProvider.java @@ -0,0 +1,35 @@ +package com.annimon.imagetagger.logic; + +import com.annimon.imagetagger.views.ResizableImage; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.stream.Stream; +import javax.imageio.ImageIO; + +public class FilesProvider { + + protected final File dir; + + public FilesProvider(String dir) { + this.dir = new File(dir); + } + + public Stream stream() { + final var files = dir.listFiles(); + if (files == null) { + throw new RuntimeException("There are no files in directory " + dir); + } + return Arrays.stream(files); + } + + public ResizableImage loadImage(File file) { + try { + return new ResizableImage(ImageIO.read(file)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void preloadImage(File file) { } +} diff --git a/src/main/java/com/annimon/imagetagger/logic/ImageProcessor.java b/src/main/java/com/annimon/imagetagger/logic/ImageProcessor.java index 3ec1ac4..4c75d94 100644 --- a/src/main/java/com/annimon/imagetagger/logic/ImageProcessor.java +++ b/src/main/java/com/annimon/imagetagger/logic/ImageProcessor.java @@ -1,9 +1,8 @@ package com.annimon.imagetagger.logic; import com.annimon.imagetagger.beans.ImageInfo; -import java.awt.*; +import com.annimon.imagetagger.views.ResizableImage; import java.io.File; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -13,28 +12,27 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.imageio.ImageIO; public class ImageProcessor { - private final File dir; + private final FilesProvider files; private final String filter; private final String sort; private final List imageInfos; private int imagesCount; private int index; private ImageInfo currentInfo; - private Image image; + private ResizableImage image; - public ImageProcessor(String dir, String filter, String sort) { - this.dir = new File(dir); + public ImageProcessor(FilesProvider files, String filter, String sort) { + this.files = files; this.filter = filter; this.sort = sort; imageInfos = new ArrayList<>(); imagesCount = 0; } - public Image getImage() { + public ResizableImage getImage() { return image; } @@ -57,6 +55,7 @@ public class ImageProcessor { index = imagesCount - 1; } loadImage(); + preloadImage(-1); } public void nextImage() { @@ -65,6 +64,7 @@ public class ImageProcessor { index = 0; } loadImage(); + preloadImage(1); } public void writeTagsToFile() { @@ -90,11 +90,7 @@ public class ImageProcessor { } public void populateFiles() { - final var files = dir.listFiles(); - if (files == null) { - throw new RuntimeException("There are no files in directory " + dir); - } - final var infos = Arrays.stream(files) + final var infos = files.stream() .filter(this::allowedFiles) .map(ImageInfo::new) .map(ImageInfo::loadTagFile) @@ -114,11 +110,17 @@ public class ImageProcessor { if (index < 0 || index >= imagesCount) return; currentInfo = imageInfos.get(index); - try { - image = ImageIO.read(currentInfo.getFile()); - } catch (IOException e) { - throw new RuntimeException(e); + image = files.loadImage(currentInfo.getFile()); + } + + private void preloadImage(int delta) { + int newIndex = index + delta; + if (newIndex < 0) { + newIndex = imagesCount - 1; + } else if (newIndex >= imagesCount) { + newIndex = 0; } + files.preloadImage(imageInfos.get(newIndex).getFile()); } private boolean allowedFiles(File file) { diff --git a/src/main/java/com/annimon/imagetagger/views/ImagePanel.java b/src/main/java/com/annimon/imagetagger/views/ImagePanel.java index 081c09e..870ca40 100644 --- a/src/main/java/com/annimon/imagetagger/views/ImagePanel.java +++ b/src/main/java/com/annimon/imagetagger/views/ImagePanel.java @@ -2,9 +2,8 @@ package com.annimon.imagetagger.views; import com.annimon.imagetagger.logic.ImageProcessor; import java.awt.*; -import javax.swing.*; -public class ImagePanel extends JPanel { +public class ImagePanel extends Component { private static final Color TEXT_COLOR = new Color(0x80FFFFFF, true); private final ImageProcessor imageProcessor; @@ -13,38 +12,27 @@ public class ImagePanel extends JPanel { this.imageProcessor = imageProcessor; } - @SuppressWarnings("UnusedAssignment") @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); + public void paint(Graphics g) { + super.paint(g); final var g2d = (Graphics2D) g; final int width = getWidth(); final int height = getHeight(); - var image = imageProcessor.getImage(); - if ((width <= 5) || (height <= 5) || (image == null)) { + final var resizableImage = imageProcessor.getImage(); + if ((width <= 5) || (height <= 5) || (resizableImage == null)) { return; } - final int imgWidth = image.getWidth(this); - final int imgHeight = image.getHeight(this); - int scaleX = imgWidth; - int scaleY = imgHeight; + var image = resizableImage.getOriginal(); + int imgWidth = resizableImage.getOriginalWidth(); + int imgHeight = resizableImage.getOriginalHeight(); if (imgWidth > width || imgHeight > height) { - if (scaleX > width) { - // Fit by width - scaleX = width; - scaleY = imgHeight * scaleX / imgWidth; - } - if (scaleY > height) { - // Fit by height - scaleY = height; - scaleX = imgWidth * scaleY / imgHeight; - } - image = image.getScaledInstance(scaleX, scaleY, Image.SCALE_SMOOTH); + image = resizableImage.getResized(width, height); + imgWidth = resizableImage.getResizedWidth(); + imgHeight = resizableImage.getResizedHeight(); } - - final int x = (width - scaleX) / 2; - final int y = (height - scaleY) / 2; + final int x = (width - imgWidth) / 2; + final int y = (height - imgHeight) / 2; g2d.drawImage(image, x, y, this); int yy = 5; diff --git a/src/main/java/com/annimon/imagetagger/views/ResizableImage.java b/src/main/java/com/annimon/imagetagger/views/ResizableImage.java new file mode 100644 index 0000000..d59eb49 --- /dev/null +++ b/src/main/java/com/annimon/imagetagger/views/ResizableImage.java @@ -0,0 +1,82 @@ +package com.annimon.imagetagger.views; + +import java.awt.*; +import java.awt.image.BufferedImage; + +public class ResizableImage { + + private final Image original; + private Image resizedImage; + private int resizedWidth, resizedHeight; + + public ResizableImage(Image original) { + this.original = original; + } + + public Image getOriginal() { + return original; + } + + public BufferedImage getOriginalAsBufferedImage() { + return toBufferedImage(original); + } + + public int getOriginalWidth() { + return original.getWidth(null); + } + + public int getOriginalHeight() { + return original.getHeight(null); + } + + public Image getResized(int prefWidth, int prefHeight) { + boolean sizeMatched = (resizedWidth == prefWidth) || (resizedHeight == prefHeight); + if (resizedImage == null || !sizeMatched) { + resizedImage = resizeIfNecessary(original, prefWidth, prefHeight); + resizedWidth = resizedImage.getWidth(null); + resizedHeight = resizedImage.getHeight(null); + } + return resizedImage; + } + + public BufferedImage getResizedAsBufferedImage(int prefWidth, int prefHeight) { + return toBufferedImage(getResized(prefWidth, prefHeight)); + } + + public int getResizedWidth() { + return resizedWidth; + } + + public int getResizedHeight() { + return resizedHeight; + } + + private static Image resizeIfNecessary(Image image, int prefWidth, int prefHeight) { + final int width = image.getWidth(null); + final int height = image.getHeight(null); + if (width <= prefWidth && height <= prefHeight) { + return image; + } + int newWidth = width; + int newHeight = height; + if (width > prefWidth) { + newWidth = prefWidth; + newHeight = height * newWidth / width; + } + if (newHeight > prefHeight) { + newHeight = prefHeight; + newWidth = width * newHeight / height; + } + return image.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH); + } + + private static BufferedImage toBufferedImage(Image image) { + final int width = image.getWidth(null); + final int height = image.getHeight(null); + final var img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + final var g = img.createGraphics(); + g.drawImage(image, 0, 0, null); + g.dispose(); + return img; + } +}