From 0d7796434f267c5ba0fad77b9e481a8793027203 Mon Sep 17 00:00:00 2001 From: Skier233 Date: Tue, 27 Nov 2018 11:25:26 -0500 Subject: [PATCH 1/4] Fixed image distortion when scaling issue on the 2 imageviews on the main gui --- .../teaseai/api/media/MediaHandler.java | 699 ++++++++------- .../teaseai/gui/main/MainGuiController.java | 798 ++++++++++-------- src/me/goddragon/teaseai/gui/main/main.fxml | 2 +- .../imagescaling/AdvancedResizeOp.java | 120 +++ .../libraries/imagescaling/BSplineFilter.java | 45 + .../libraries/imagescaling/BellFilter.java | 43 + .../libraries/imagescaling/BiCubicFilter.java | 48 ++ .../imagescaling/BiCubicHighFreqResponse.java | 23 + .../libraries/imagescaling/BoxFilter.java | 33 + .../imagescaling/DimensionConstrain.java | 177 ++++ .../libraries/imagescaling/HermiteFilter.java | 37 + .../libraries/imagescaling/ImageUtils.java | 277 ++++++ .../imagescaling/Lanczos3Filter.java | 46 + .../imagescaling/MitchellFilter.java | 54 ++ .../imagescaling/MultiStepRescaleOp.java | 90 ++ .../imagescaling/ProgressListener.java | 11 + .../imagescaling/ResampleFilter.java | 15 + .../imagescaling/ResampleFilters.java | 55 ++ .../libraries/imagescaling/ResampleOp.java | 494 +++++++++++ .../imagescaling/ThumbnailRescaleOp.java | 117 +++ .../imagescaling/TriangleFilter.java | 37 + 21 files changed, 2559 insertions(+), 662 deletions(-) create mode 100644 src/me/goddragon/teaseai/utils/libraries/imagescaling/AdvancedResizeOp.java create mode 100644 src/me/goddragon/teaseai/utils/libraries/imagescaling/BSplineFilter.java create mode 100644 src/me/goddragon/teaseai/utils/libraries/imagescaling/BellFilter.java create mode 100644 src/me/goddragon/teaseai/utils/libraries/imagescaling/BiCubicFilter.java create mode 100644 src/me/goddragon/teaseai/utils/libraries/imagescaling/BiCubicHighFreqResponse.java create mode 100644 src/me/goddragon/teaseai/utils/libraries/imagescaling/BoxFilter.java create mode 100644 src/me/goddragon/teaseai/utils/libraries/imagescaling/DimensionConstrain.java create mode 100644 src/me/goddragon/teaseai/utils/libraries/imagescaling/HermiteFilter.java create mode 100644 src/me/goddragon/teaseai/utils/libraries/imagescaling/ImageUtils.java create mode 100644 src/me/goddragon/teaseai/utils/libraries/imagescaling/Lanczos3Filter.java create mode 100644 src/me/goddragon/teaseai/utils/libraries/imagescaling/MitchellFilter.java create mode 100644 src/me/goddragon/teaseai/utils/libraries/imagescaling/MultiStepRescaleOp.java create mode 100644 src/me/goddragon/teaseai/utils/libraries/imagescaling/ProgressListener.java create mode 100644 src/me/goddragon/teaseai/utils/libraries/imagescaling/ResampleFilter.java create mode 100644 src/me/goddragon/teaseai/utils/libraries/imagescaling/ResampleFilters.java create mode 100644 src/me/goddragon/teaseai/utils/libraries/imagescaling/ResampleOp.java create mode 100644 src/me/goddragon/teaseai/utils/libraries/imagescaling/ThumbnailRescaleOp.java create mode 100644 src/me/goddragon/teaseai/utils/libraries/imagescaling/TriangleFilter.java diff --git a/src/me/goddragon/teaseai/api/media/MediaHandler.java b/src/me/goddragon/teaseai/api/media/MediaHandler.java index a0ff146..56b07c3 100644 --- a/src/me/goddragon/teaseai/api/media/MediaHandler.java +++ b/src/me/goddragon/teaseai/api/media/MediaHandler.java @@ -1,319 +1,380 @@ -package me.goddragon.teaseai.api.media; - -import javafx.scene.image.Image; -import javafx.scene.image.ImageView; -import javafx.scene.layout.StackPane; -import javafx.scene.media.Media; -import javafx.scene.media.MediaPlayer; -import javafx.scene.media.MediaView; -import me.goddragon.teaseai.TeaseAI; -import me.goddragon.teaseai.utils.FileUtils; -import me.goddragon.teaseai.utils.TeaseLogger; -import me.goddragon.teaseai.utils.media.AnimatedGif; -import me.goddragon.teaseai.utils.media.Animation; - -import java.io.*; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URL; -import java.util.HashMap; -import java.util.Map; -import java.util.logging.Level; - -/** - * Created by GodDragon on 22.03.2018. - */ -public class MediaHandler { - - private static MediaHandler handler = new MediaHandler(); - - private HashMap playingAudioClips = new HashMap<>(); - - private MediaPlayer currentVideoPlayer = null; - private Animation currentAnimation = null; - private boolean imagesLocked = false; - - private String currentImageURL; - - public MediaPlayer playVideo(File file) { - return playVideo(file, false); - } - - public MediaPlayer playVideo(File file, boolean wait) { - if (!file.exists()) { - TeaseLogger.getLogger().log(Level.SEVERE, "Video " + file.getPath() + " does not exist."); - return null; - } - - try { - return playVideo(file.toURI().toURL().toExternalForm(), wait); - } catch (MalformedURLException e) { - e.printStackTrace(); - } - - return null; - } - - public MediaPlayer playVideo(String uri, boolean wait) { - currentVideoPlayer = new MediaPlayer(new Media(uri)); - currentVideoPlayer.setAutoPlay(true); - this.imagesLocked = true; - - TeaseAI.application.runOnUIThread(new Runnable() { - @Override - public void run() { - MediaView mediaView = TeaseAI.application.getController().getMediaView(); - StackPane mediaViewBox = TeaseAI.application.getController().getMediaViewBox(); - - //Handle visibilities - mediaView.setOpacity(1); - TeaseAI.application.getController().getImageView().setOpacity(0); - - mediaView.setPreserveRatio(true); - mediaView.fitWidthProperty().bind(mediaViewBox.widthProperty()); - mediaView.fitHeightProperty().bind(mediaViewBox.heightProperty()); - mediaView.setMediaPlayer(currentVideoPlayer); - } - }); - - //Check if we want to wait for the media to finish - if (wait) { - waitForPlayer(currentVideoPlayer); - currentVideoPlayer = null; - } else { - //Unlock the images again (of course they can be unlocked by the user during the video) - currentVideoPlayer.setOnEndOfMedia(new Runnable() { - @Override - public void run() { - imagesLocked = false; - currentVideoPlayer = null; - } - }); - } - - return currentVideoPlayer; - } - - public void stopVideo() { - currentVideoPlayer.stop(); - currentVideoPlayer = null; - } - - public void showPicture(File file) { - showPicture(file, 0); - } - - public void showPicture(File file, int durationSeconds) { - if (file == null) { - TeaseAI.application.runOnUIThread(new Runnable() { - @Override - public void run() { - removePicture(); - } - }); - - return; - } - - if (!file.exists()) { - TeaseLogger.getLogger().log(Level.SEVERE, "Picture " + file.getPath() + " does not exist."); - return; - } - - TeaseAI.application.runOnUIThread(new Runnable() { - @Override - public void run() { - MediaView mediaView = TeaseAI.application.getController().getMediaView(); - - if (mediaView.getMediaPlayer() != null) { - mediaView.getMediaPlayer().stop(); - } - - ImageView imageView = TeaseAI.application.getController().getImageView(); - - //Handle visibilities - mediaView.setOpacity(0); - imageView.setOpacity(1); - - //Stop any current image animation that might be running before displaying a new picture - stopCurrentAnimation(); - - if (FileUtils.getExtension(file).equalsIgnoreCase("gif")) { - currentAnimation = new AnimatedGif(file.toURI().toString()); - currentAnimation.setCycleCount(Integer.MAX_VALUE); - currentAnimation.play(imageView); - } else { - imageView.setImage(new Image(file.toURI().toString())); - } - } - }); - - if (durationSeconds > 0) { - TeaseAI.application.sleepPossibleScripThread(durationSeconds * 1000); - } - } - - private void removePicture() { - ImageView imageView = TeaseAI.application.getController().getImageView(); - stopCurrentAnimation(); - imageView.setImage(null); - } - - private void stopCurrentAnimation() { - if (currentAnimation != null) { - currentAnimation.stop(); - currentAnimation = null; - } - } - - private MediaPlayer getAudioPlayer(File file) { - Media hit = new Media(file.toURI().toString()); - MediaPlayer mediaPlayer = new MediaPlayer(hit); - return mediaPlayer; - } - - /*public MediaPlayer playSoundFromFolder(String path) { - return playSoundFromFolder(path, false); - } - - public MediaPlayer playSoundFromFolder(String path, boolean wait) { - return playAudio(new File("Sounds\\" + path), wait); - }*/ - - public MediaPlayer playAudio(String path) { - return playAudio(path, false); - } - - public MediaPlayer playAudio(String path, boolean wait) { - return playAudio(new File(path), wait); - } - - public MediaPlayer playAudio(File file) { - return playAudio(file, false); - } - - public MediaPlayer playAudio(File file, boolean wait) { - if (file == null || !file.exists()) { - TeaseLogger.getLogger().log(Level.SEVERE, "Audio " + (file == null ? "null" : file.getPath()) + " does not exist."); - return null; - } - - MediaPlayer mediaPlayer = getAudioPlayer(file); - playingAudioClips.put(file.toURI(), mediaPlayer); - mediaPlayer.play(); - - if (wait) { - waitForPlayer(mediaPlayer); - } - - return mediaPlayer; - } - - public void stopAudio(String path) { - stopAudio(new File(path)); - } - - public void stopAudio(File file) { - if (!playingAudioClips.containsKey(file.toURI())) { - return; - } - - playingAudioClips.get(file.toURI()).stop(); - playingAudioClips.remove(file.toURI()); - } - - public void stopAllAudio() { - for (Map.Entry clips : playingAudioClips.entrySet()) { - clips.getValue().stop(); - playingAudioClips.get(clips.getKey()).stop(); - } - } - - public File getImageFromURL(String url) throws IOException { - currentImageURL = url; - - String[] split = url.split("/"); - String path = split[split.length - 1]; - - path = MediaURL.IMAGE_DOWNLOAD_PATH + File.separator + path; - File file = new File(path); - - if (file.exists()) { - return file; - } - - InputStream in = new BufferedInputStream(new URL(url).openStream()); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - byte[] buf = new byte[1024]; - - int n; - while (-1 != (n = in.read(buf))) { - out.write(buf, 0, n); - } - out.close(); - in.close(); - - byte[] response = out.toByteArray(); - - FileOutputStream fos = new FileOutputStream(path); - fos.write(response); - fos.close(); - - if (file.exists()) { - return file; - } - - return null; - } - - public void waitForPlayer(MediaPlayer mediaPlayer) { - final boolean[] hasFinishedPlaying = {false}; - mediaPlayer.setOnEndOfMedia(new Runnable() { - @Override - public void run() { - imagesLocked = false; - - synchronized (TeaseAI.application.getScriptThread()) { - TeaseAI.application.getScriptThread().notify(); - } - - hasFinishedPlaying[0] = true; - } - }); - - while (!hasFinishedPlaying[0]) { - TeaseAI.application.waitPossibleScripThread(0); - - //Check whether there are new responses to handle - TeaseAI.application.checkForNewResponses(); - } - } - - public boolean isPlayingVideo() { - return currentVideoPlayer != null; - } - - public MediaPlayer getCurrentVideoPlayer() { - return currentVideoPlayer; - } - - public boolean isImagesLocked() { - return imagesLocked; - } - - public void setImagesLocked(boolean imagesLocked) { - this.imagesLocked = imagesLocked; - } - - public String getCurrentImageURL() { - return currentImageURL; - } - - public static MediaHandler getHandler() { - return handler; - } - - public static void setHandler(MediaHandler handler) { - MediaHandler.handler = handler; - } -} +package me.goddragon.teaseai.api.media; + +import javafx.embed.swing.SwingFXUtils; +import javafx.scene.control.SplitPane; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.StackPane; +import javafx.scene.media.Media; +import javafx.scene.media.MediaPlayer; +import javafx.scene.media.MediaView; +import me.goddragon.teaseai.TeaseAI; +import me.goddragon.teaseai.utils.FileUtils; +import me.goddragon.teaseai.utils.TeaseLogger; +import me.goddragon.teaseai.utils.libraries.imagescaling.ResampleFilters; +import me.goddragon.teaseai.utils.libraries.imagescaling.ResampleOp; +import me.goddragon.teaseai.utils.media.AnimatedGif; +import me.goddragon.teaseai.utils.media.Animation; + +import java.awt.image.BufferedImage; +import java.io.*; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; + +import javax.imageio.ImageIO; + +/** + * Created by GodDragon on 22.03.2018. + */ +public class MediaHandler { + + private static MediaHandler handler = new MediaHandler(); + + private HashMap playingAudioClips = new HashMap<>(); + + private MediaPlayer currentVideoPlayer = null; + private Animation currentAnimation = null; + private boolean imagesLocked = false; + + private String currentImageURL; + + public MediaPlayer playVideo(File file) { + return playVideo(file, false); + } + + public MediaPlayer playVideo(File file, boolean wait) { + if (!file.exists()) { + TeaseLogger.getLogger().log(Level.SEVERE, "Video " + file.getPath() + " does not exist."); + return null; + } + + try { + return playVideo(file.toURI().toURL().toExternalForm(), wait); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + + return null; + } + + public MediaPlayer playVideo(String uri, boolean wait) { + currentVideoPlayer = new MediaPlayer(new Media(uri)); + currentVideoPlayer.setAutoPlay(true); + this.imagesLocked = true; + + TeaseAI.application.runOnUIThread(new Runnable() { + @Override + public void run() { + MediaView mediaView = TeaseAI.application.getController().getMediaView(); + StackPane mediaViewBox = TeaseAI.application.getController().getMediaViewBox(); + + //Handle visibilities + mediaView.setOpacity(1); + TeaseAI.application.getController().getImageView().setOpacity(0); + + mediaView.setPreserveRatio(true); + mediaView.fitWidthProperty().bind(mediaViewBox.widthProperty()); + mediaView.fitHeightProperty().bind(mediaViewBox.heightProperty()); + mediaView.setMediaPlayer(currentVideoPlayer); + } + }); + + //Check if we want to wait for the media to finish + if (wait) { + waitForPlayer(currentVideoPlayer); + currentVideoPlayer = null; + } else { + //Unlock the images again (of course they can be unlocked by the user during the video) + currentVideoPlayer.setOnEndOfMedia(new Runnable() { + @Override + public void run() { + imagesLocked = false; + currentVideoPlayer = null; + } + }); + } + + return currentVideoPlayer; + } + + public void stopVideo() { + currentVideoPlayer.stop(); + currentVideoPlayer = null; + } + + public void showPicture(File file) { + showPicture(file, 0); + } + + public void showPicture(File file, int durationSeconds) { + if (file == null) { + TeaseAI.application.runOnUIThread(new Runnable() { + @Override + public void run() { + removePicture(); + } + }); + + return; + } + + if (!file.exists()) { + TeaseLogger.getLogger().log(Level.SEVERE, "Picture " + file.getPath() + " does not exist."); + return; + } + + TeaseAI.application.runOnUIThread(new Runnable() { + @Override + public void run() { + MediaView mediaView = TeaseAI.application.getController().getMediaView(); + + if (mediaView.getMediaPlayer() != null) { + mediaView.getMediaPlayer().stop(); + } + + ImageView imageView = TeaseAI.application.getController().getImageView(); + + //Handle visibilities + mediaView.setOpacity(0); + imageView.setOpacity(1); + + //Stop any current image animation that might be running before displaying a new picture + stopCurrentAnimation(); + + if (FileUtils.getExtension(file).equalsIgnoreCase("gif")) { + currentAnimation = new AnimatedGif(file.toURI().toString()); + currentAnimation.setCycleCount(Integer.MAX_VALUE); + currentAnimation.play(imageView); + } else { + double paneWidth = ((StackPane)imageView.getParent()).getWidth(); + double paneHeight = ((StackPane)imageView.getParent()).getHeight(); + BufferedImage scaledImage = null; + try + { + BufferedImage originalImage = ImageIO.read(file); + int originalImageHeight = originalImage.getHeight(); + int originalImageWidth = originalImage.getWidth(); + double scaleFactorWidth = originalImageWidth / paneWidth; + double scaleFactorHeight = originalImageHeight / paneHeight; + boolean needsScaling = true; + int newWidth = 0; + int newHeight = 0; + + if (scaleFactorHeight > scaleFactorWidth) + { + if (scaleFactorHeight <= 2.0) + { + needsScaling = false; + } + else + { + newWidth = (int)(originalImageWidth / scaleFactorHeight); + newHeight = (int)(originalImageHeight / scaleFactorHeight); + } + } + else + { + if (scaleFactorWidth <= 2.0) + { + needsScaling = false; + } + else + { + newWidth = (int)(originalImageWidth / scaleFactorWidth); + newHeight = (int)(originalImageHeight / scaleFactorWidth); + } + } + if (needsScaling) + { + ResampleOp resizeOp = new ResampleOp(newWidth, newHeight); + resizeOp.setFilter(ResampleFilters.getLanczos3Filter()); + scaledImage = resizeOp.filter(originalImage, null); + } + else + { + scaledImage = originalImage; + } + } + catch (Exception e) + { + e.printStackTrace(); + } + + imageView.setImage(SwingFXUtils.toFXImage(scaledImage, null)); + } + } + }); + + if (durationSeconds > 0) { + TeaseAI.application.sleepPossibleScripThread(durationSeconds * 1000); + } + } + + private void removePicture() { + ImageView imageView = TeaseAI.application.getController().getImageView(); + stopCurrentAnimation(); + imageView.setImage(null); + } + + private void stopCurrentAnimation() { + if (currentAnimation != null) { + currentAnimation.stop(); + currentAnimation = null; + } + } + + private MediaPlayer getAudioPlayer(File file) { + Media hit = new Media(file.toURI().toString()); + MediaPlayer mediaPlayer = new MediaPlayer(hit); + return mediaPlayer; + } + + /*public MediaPlayer playSoundFromFolder(String path) { + return playSoundFromFolder(path, false); + } + + public MediaPlayer playSoundFromFolder(String path, boolean wait) { + return playAudio(new File("Sounds\\" + path), wait); + }*/ + + public MediaPlayer playAudio(String path) { + return playAudio(path, false); + } + + public MediaPlayer playAudio(String path, boolean wait) { + return playAudio(new File(path), wait); + } + + public MediaPlayer playAudio(File file) { + return playAudio(file, false); + } + + public MediaPlayer playAudio(File file, boolean wait) { + if (file == null || !file.exists()) { + TeaseLogger.getLogger().log(Level.SEVERE, "Audio " + (file == null ? "null" : file.getPath()) + " does not exist."); + return null; + } + + MediaPlayer mediaPlayer = getAudioPlayer(file); + playingAudioClips.put(file.toURI(), mediaPlayer); + mediaPlayer.play(); + + if (wait) { + waitForPlayer(mediaPlayer); + } + + return mediaPlayer; + } + + public void stopAudio(String path) { + stopAudio(new File(path)); + } + + public void stopAudio(File file) { + if (!playingAudioClips.containsKey(file.toURI())) { + return; + } + + playingAudioClips.get(file.toURI()).stop(); + playingAudioClips.remove(file.toURI()); + } + + public void stopAllAudio() { + for (Map.Entry clips : playingAudioClips.entrySet()) { + clips.getValue().stop(); + playingAudioClips.get(clips.getKey()).stop(); + } + } + + public File getImageFromURL(String url) throws IOException { + currentImageURL = url; + + String[] split = url.split("/"); + String path = split[split.length - 1]; + + path = MediaURL.IMAGE_DOWNLOAD_PATH + File.separator + path; + File file = new File(path); + + if (file.exists()) { + return file; + } + + InputStream in = new BufferedInputStream(new URL(url).openStream()); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buf = new byte[1024]; + + int n; + while (-1 != (n = in.read(buf))) { + out.write(buf, 0, n); + } + out.close(); + in.close(); + + byte[] response = out.toByteArray(); + + FileOutputStream fos = new FileOutputStream(path); + fos.write(response); + fos.close(); + + if (file.exists()) { + return file; + } + + return null; + } + + public void waitForPlayer(MediaPlayer mediaPlayer) { + final boolean[] hasFinishedPlaying = {false}; + mediaPlayer.setOnEndOfMedia(new Runnable() { + @Override + public void run() { + imagesLocked = false; + + synchronized (TeaseAI.application.getScriptThread()) { + TeaseAI.application.getScriptThread().notify(); + } + + hasFinishedPlaying[0] = true; + } + }); + + while (!hasFinishedPlaying[0]) { + TeaseAI.application.waitPossibleScripThread(0); + + //Check whether there are new responses to handle + TeaseAI.application.checkForNewResponses(); + } + } + + public boolean isPlayingVideo() { + return currentVideoPlayer != null; + } + + public MediaPlayer getCurrentVideoPlayer() { + return currentVideoPlayer; + } + + public boolean isImagesLocked() { + return imagesLocked; + } + + public void setImagesLocked(boolean imagesLocked) { + this.imagesLocked = imagesLocked; + } + + public String getCurrentImageURL() { + return currentImageURL; + } + + public static MediaHandler getHandler() { + return handler; + } + + public static void setHandler(MediaHandler handler) { + MediaHandler.handler = handler; + } +} diff --git a/src/me/goddragon/teaseai/gui/main/MainGuiController.java b/src/me/goddragon/teaseai/gui/main/MainGuiController.java index ce68913..4d7d60b 100644 --- a/src/me/goddragon/teaseai/gui/main/MainGuiController.java +++ b/src/me/goddragon/teaseai/gui/main/MainGuiController.java @@ -1,343 +1,457 @@ -package me.goddragon.teaseai.gui.main; - -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; -import javafx.fxml.FXML; -import javafx.geometry.Insets; -import javafx.scene.control.*; -import javafx.scene.image.Image; -import javafx.scene.image.ImageView; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyEvent; -import javafx.scene.input.MouseEvent; -import javafx.scene.layout.*; -import javafx.scene.media.MediaView; -import javafx.scene.paint.Color; -import javafx.scene.text.TextFlow; -import javafx.stage.FileChooser; -import javafx.stage.Stage; -import me.goddragon.teaseai.TeaseAI; -import me.goddragon.teaseai.api.chat.ChatHandler; -import me.goddragon.teaseai.api.chat.ChatParticipant; -import me.goddragon.teaseai.api.scripts.personality.Personality; -import me.goddragon.teaseai.api.scripts.personality.PersonalityManager; -import me.goddragon.teaseai.gui.settings.SettingsController; -import me.goddragon.teaseai.utils.FileUtils; - -import java.io.File; - -public class MainGuiController { - - private final Stage stage; - private LazySubController lazySubController; - - @FXML - private MediaView mediaView; - - @FXML - private ImageView imageView; - - @FXML - private StackPane mediaViewBox; - - @FXML - private TextFlow chatWindow; - - @FXML - private TextField chatTextField; - - @FXML - private ScrollPane chatScrollPane; - - @FXML - private ChoiceBox personalityChoiceBox; - - @FXML - private Button startChatButton; - - @FXML - private Menu menuSettingsButton; - - @FXML - private TextField subNameTextField; - - @FXML - private TextField domNameTextField; - - @FXML - private ImageView domImageView; - - @FXML - private StackPane domImageViewStackPane; - - //Sidebar - @FXML - private FlowPane lazySubPane; - - @FXML - private GridPane contactImageGrid; - - //Run Script Menu Item - @FXML - private MenuItem runScriptMenuItem; - - public MainGuiController(Stage stage) { - this.stage = stage; - } - - public void initiate() { - personalityChoiceBox.setTooltip(new Tooltip("Select the personality")); - - mediaView.setPreserveRatio(true); - mediaView.fitWidthProperty().bind(mediaViewBox.widthProperty()); - mediaView.fitHeightProperty().bind(mediaViewBox.heightProperty()); - - imageView.setPreserveRatio(true); - imageView.fitWidthProperty().bind(mediaViewBox.widthProperty()); - imageView.fitHeightProperty().bind(mediaViewBox.heightProperty()); - mediaViewBox.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY))); - - chatTextField.setOnKeyPressed(new EventHandler() { - @Override - public void handle(KeyEvent ke) { - if (ke.getCode().equals(KeyCode.ENTER)) { - String currentText = chatTextField.getText(); - - if (currentText.length() == 0 || currentText == null) { - return; - } - - currentText = currentText.trim(); - - ChatHandler.getHandler().getSubParticipant().sendMessage(currentText); - - //Clear the chat field - chatTextField.setText(""); - - //Call the sub message event - ChatHandler.getHandler().onSubMessage(currentText); - } - } - }); - - startChatButton.setOnAction(new EventHandler() { - @Override - public void handle(ActionEvent e) { - if (PersonalityManager.getManager().getActivePersonality() == null) { - return; - } - - if (TeaseAI.application.getSession().isStarted()) { - startChatButton.setDisable(true); - - //Notify the thread because we want it continue and then end anyway - synchronized (TeaseAI.application.getScriptThread()) { - TeaseAI.application.getSession().setHaltSession(true); - TeaseAI.application.getScriptThread().notify(); - } - } else { - PersonalityManager.getManager().setActivePersonality((Personality) getPersonalityChoiceBox().getSelectionModel().getSelectedItem()); - - /*synchronized (TeaseAI.application.getScriptThread()) { - TeaseAI.application.getScriptThread().notify(); - }*/ - - TeaseAI.application.getSession().start(); - } - } - }); - - runScriptMenuItem.setOnAction(new EventHandler() { - @Override - public void handle(ActionEvent e) { - if (PersonalityManager.getManager().getActivePersonality() == null) { - return; - } - - FileChooser chooser = new FileChooser(); - chooser.setTitle("Select Script"); - - File defaultDirectory = TeaseAI.application.getSession().getActivePersonality().getFolder(); - chooser.setInitialDirectory(defaultDirectory); - - chooser.getExtensionFilters().addAll( - new FileChooser.ExtensionFilter("Javascript", "*.js") - ); - - File script = chooser.showOpenDialog(stage); - - if (script != null && script.exists()) { - String extension = FileUtils.getExtension(script); - if ((extension.equalsIgnoreCase("js"))) { - PersonalityManager.getManager().setActivePersonality((Personality) getPersonalityChoiceBox().getSelectionModel().getSelectedItem()); - - TeaseAI.application.getSession().startWithScript(script); - } else { - Alert alert = new Alert(Alert.AlertType.ERROR); - alert.setTitle("Invalid File"); - alert.setHeaderText(null); - alert.setContentText("The given file is not a supported script file."); - - alert.showAndWait(); - } - } - } - }); - - personalityChoiceBox.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue observableValue, Personality oldValue, Personality newValue) { - if (TeaseAI.application.getSession() != null) { - TeaseAI.application.getSession().setActivePersonality(newValue); - TeaseAI.application.LAST_SELECTED_PERSONALITY.setValue(newValue.getName().getValue()).save(); - } - } - }); - - Label settingsLabel = new Label("Settings"); - menuSettingsButton.setGraphic(settingsLabel); - settingsLabel.setOnMouseClicked(new EventHandler() { - @Override - public void handle(MouseEvent e) { - SettingsController.openGUI(); - } - }); - - lazySubController = new LazySubController(lazySubPane); - lazySubController.createDefaults(); - } - - public void loadDomInfo() { - domImageView.setPreserveRatio(true); - Pane pane = new Pane(); - contactImageGrid.add(pane, 0, 2); - domImageView.fitWidthProperty().bind(pane.widthProperty()); - domImageView.fitHeightProperty().bind(pane.heightProperty()); - - ChatParticipant domParticipant = ChatHandler.getHandler().getMainDomParticipant(); - ChatParticipant subParticipant = ChatHandler.getHandler().getSubParticipant(); - - File domImage = domParticipant.getContact().getImage(); - if (domImage != null && domImage.exists()) { - domImageView.setImage(new Image(domImage.toURI().toString())); - } else { - domImageView.setImage(null); - } - - domImageView.setOnMouseClicked(new EventHandler() { - @Override - public void handle(MouseEvent event) { - FileChooser chooser = new FileChooser(); - chooser.setTitle("Select Media Folder"); - - String dir; - if (new File(domParticipant.getContact().IMAGE_PATH.getValue()).exists()) { - dir = domParticipant.getContact().IMAGE_PATH.getValue(); - //Get parent folder - dir = dir.substring(0, dir.lastIndexOf(File.separator)); - } else { - dir = System.getProperty("user.dir"); - } - - File defaultDirectory = new File(dir); - chooser.setInitialDirectory(defaultDirectory); - - chooser.getExtensionFilters().addAll( - new FileChooser.ExtensionFilter("JPG", "*.jpg"), - new FileChooser.ExtensionFilter("PNG", "*.png") - ); - - File image = chooser.showOpenDialog(stage); - - if (image != null && image.exists()) { - String extension = FileUtils.getExtension(image); - - if ((extension.equalsIgnoreCase("jpg") || extension.equalsIgnoreCase("png"))) { - domParticipant.getContact().IMAGE_PATH.setValue(image.getPath()); - domParticipant.getContact().IMAGE_PATH.save(); - - if (image != null && image.exists()) { - domImageView.setImage(new Image(image.toURI().toString())); - } else { - domImageView.setImage(null); - } - } else { - Alert alert = new Alert(Alert.AlertType.ERROR); - alert.setTitle("Invalid File"); - alert.setHeaderText(null); - alert.setContentText("The given file is not a supported image file."); - - alert.showAndWait(); - } - } - } - }); - - domNameTextField.setText(domParticipant.getContact().NAME.getValue()); - subNameTextField.setText(subParticipant.getContact().NAME.getValue()); - - domNameTextField.focusedProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue arg0, Boolean oldPropertyValue, Boolean newPropertyValue) { - if (newPropertyValue) { - //On focus - } else { - //Lost focus - domParticipant.getContact().NAME.setValue(domNameTextField.getText()); - domParticipant.getContact().save(); - } - } - }); - - subNameTextField.focusedProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue arg0, Boolean oldPropertyValue, Boolean newPropertyValue) { - if (newPropertyValue) { - //On focus - } else { - //Lost focus - subParticipant.getContact().NAME.setValue(subNameTextField.getText()); - subParticipant.getContact().save(); - } - } - }); - } - - public TextField getChatTextField() { - return chatTextField; - } - - public MediaView getMediaView() { - return mediaView; - } - - public ImageView getImageView() { - return imageView; - } - - public StackPane getMediaViewBox() { - return mediaViewBox; - } - - public TextFlow getChatWindow() { - return chatWindow; - } - - public ScrollPane getChatScrollPane() { - return chatScrollPane; - } - - public ChoiceBox getPersonalityChoiceBox() { - return personalityChoiceBox; - } - - public Button getStartChatButton() { - return startChatButton; - } - - public LazySubController getLazySubController() { - return lazySubController; - } +package me.goddragon.teaseai.gui.main; + +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.embed.swing.SwingFXUtils; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.fxml.FXML; +import javafx.geometry.Insets; +import javafx.scene.control.*; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.*; +import javafx.scene.media.MediaView; +import javafx.scene.paint.Color; +import javafx.scene.text.TextFlow; +import javafx.stage.FileChooser; +import javafx.stage.Stage; +import me.goddragon.teaseai.TeaseAI; +import me.goddragon.teaseai.api.chat.ChatHandler; +import me.goddragon.teaseai.api.chat.ChatParticipant; +import me.goddragon.teaseai.api.scripts.personality.Personality; +import me.goddragon.teaseai.api.scripts.personality.PersonalityManager; +import me.goddragon.teaseai.gui.settings.SettingsController; +import me.goddragon.teaseai.utils.FileUtils; +import me.goddragon.teaseai.utils.libraries.imagescaling.ResampleFilters; +import me.goddragon.teaseai.utils.libraries.imagescaling.ResampleOp; + +import java.awt.image.BufferedImage; +import java.io.File; + +import javax.imageio.ImageIO; + +public class MainGuiController { + + private final Stage stage; + private LazySubController lazySubController; + + @FXML + private MediaView mediaView; + + @FXML + private ImageView imageView; + + @FXML + private StackPane mediaViewBox; + + @FXML + private TextFlow chatWindow; + + @FXML + private TextField chatTextField; + + @FXML + private ScrollPane chatScrollPane; + + @FXML + private ChoiceBox personalityChoiceBox; + + @FXML + private Button startChatButton; + + @FXML + private Menu menuSettingsButton; + + @FXML + private TextField subNameTextField; + + @FXML + private TextField domNameTextField; + + @FXML + private ImageView domImageView; + + @FXML + private StackPane domImageViewStackPane; + + //Sidebar + @FXML + private FlowPane lazySubPane; + + @FXML + private GridPane contactImageGrid; + + //Run Script Menu Item + @FXML + private MenuItem runScriptMenuItem; + + public MainGuiController(Stage stage) { + this.stage = stage; + } + + public void initiate() { + personalityChoiceBox.setTooltip(new Tooltip("Select the personality")); + + mediaView.setPreserveRatio(true); + mediaView.fitWidthProperty().bind(mediaViewBox.widthProperty()); + mediaView.fitHeightProperty().bind(mediaViewBox.heightProperty()); + + imageView.setPreserveRatio(true); + imageView.fitWidthProperty().bind(mediaViewBox.widthProperty()); + imageView.fitHeightProperty().bind(mediaViewBox.heightProperty()); + mediaViewBox.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY))); + + chatTextField.setOnKeyPressed(new EventHandler() { + @Override + public void handle(KeyEvent ke) { + if (ke.getCode().equals(KeyCode.ENTER)) { + String currentText = chatTextField.getText(); + + if (currentText.length() == 0 || currentText == null) { + return; + } + + currentText = currentText.trim(); + + ChatHandler.getHandler().getSubParticipant().sendMessage(currentText); + + //Clear the chat field + chatTextField.setText(""); + + //Call the sub message event + ChatHandler.getHandler().onSubMessage(currentText); + } + } + }); + + startChatButton.setOnAction(new EventHandler() { + @Override + public void handle(ActionEvent e) { + if (PersonalityManager.getManager().getActivePersonality() == null) { + return; + } + + if (TeaseAI.application.getSession().isStarted()) { + startChatButton.setDisable(true); + + //Notify the thread because we want it continue and then end anyway + synchronized (TeaseAI.application.getScriptThread()) { + TeaseAI.application.getSession().setHaltSession(true); + TeaseAI.application.getScriptThread().notify(); + } + } else { + PersonalityManager.getManager().setActivePersonality((Personality) getPersonalityChoiceBox().getSelectionModel().getSelectedItem()); + + /*synchronized (TeaseAI.application.getScriptThread()) { + TeaseAI.application.getScriptThread().notify(); + }*/ + + TeaseAI.application.getSession().start(); + } + } + }); + + runScriptMenuItem.setOnAction(new EventHandler() { + @Override + public void handle(ActionEvent e) { + if (PersonalityManager.getManager().getActivePersonality() == null) { + return; + } + + FileChooser chooser = new FileChooser(); + chooser.setTitle("Select Script"); + + File defaultDirectory = TeaseAI.application.getSession().getActivePersonality().getFolder(); + chooser.setInitialDirectory(defaultDirectory); + + chooser.getExtensionFilters().addAll( + new FileChooser.ExtensionFilter("Javascript", "*.js") + ); + + File script = chooser.showOpenDialog(stage); + + if (script != null && script.exists()) { + String extension = FileUtils.getExtension(script); + if ((extension.equalsIgnoreCase("js"))) { + PersonalityManager.getManager().setActivePersonality((Personality) getPersonalityChoiceBox().getSelectionModel().getSelectedItem()); + + TeaseAI.application.getSession().startWithScript(script); + } else { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle("Invalid File"); + alert.setHeaderText(null); + alert.setContentText("The given file is not a supported script file."); + + alert.showAndWait(); + } + } + } + }); + + personalityChoiceBox.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() { + @Override + public void changed(ObservableValue observableValue, Personality oldValue, Personality newValue) { + if (TeaseAI.application.getSession() != null) { + TeaseAI.application.getSession().setActivePersonality(newValue); + TeaseAI.application.LAST_SELECTED_PERSONALITY.setValue(newValue.getName().getValue()).save(); + } + } + }); + + Label settingsLabel = new Label("Settings"); + menuSettingsButton.setGraphic(settingsLabel); + settingsLabel.setOnMouseClicked(new EventHandler() { + @Override + public void handle(MouseEvent e) { + SettingsController.openGUI(); + } + }); + + lazySubController = new LazySubController(lazySubPane); + lazySubController.createDefaults(); + } + + public void loadDomInfo() { + domImageView.setPreserveRatio(true); + Pane pane = new Pane(); + contactImageGrid.add(pane, 0, 2); + domImageView.fitWidthProperty().bind(pane.widthProperty()); + domImageView.fitHeightProperty().bind(pane.heightProperty()); + + ChatParticipant domParticipant = ChatHandler.getHandler().getMainDomParticipant(); + ChatParticipant subParticipant = ChatHandler.getHandler().getSubParticipant(); + + File domImage = domParticipant.getContact().getImage(); + if (domImage != null && domImage.exists()) { + double paneWidth = ((StackPane)domImageView.getParent()).getWidth(); + double paneHeight = ((StackPane)domImageView.getParent()).getHeight(); + BufferedImage scaledImage = null; + try + { + BufferedImage originalImage = ImageIO.read(domImage); + int originalImageHeight = originalImage.getHeight(); + int originalImageWidth = originalImage.getWidth(); + double scaleFactorWidth = originalImageWidth / paneWidth; + double scaleFactorHeight = originalImageHeight / paneHeight; + boolean needsScaling = true; + int newWidth = 0; + int newHeight = 0; + + if (scaleFactorHeight > scaleFactorWidth) + { + if (scaleFactorHeight <= 2.0) + { + needsScaling = false; + } + else + { + newWidth = (int)(originalImageWidth / scaleFactorHeight); + newHeight = (int)(originalImageHeight / scaleFactorHeight); + } + } + else + { + if (scaleFactorWidth <= 2.0) + { + needsScaling = false; + } + else + { + newWidth = (int)(originalImageWidth / scaleFactorWidth); + newHeight = (int)(originalImageHeight / scaleFactorWidth); + } + } + if (needsScaling) + { + ResampleOp resizeOp = new ResampleOp(newWidth, newHeight); + resizeOp.setFilter(ResampleFilters.getLanczos3Filter()); + scaledImage = resizeOp.filter(originalImage, null); + } + else + { + scaledImage = originalImage; + } + } + catch (Exception e) + { + e.printStackTrace(); + } + + domImageView.setImage(SwingFXUtils.toFXImage(scaledImage, null)); + } else { + domImageView.setImage(null); + } + + domImageView.setOnMouseClicked(new EventHandler() { + @Override + public void handle(MouseEvent event) { + FileChooser chooser = new FileChooser(); + chooser.setTitle("Select Media Folder"); + + String dir; + if (new File(domParticipant.getContact().IMAGE_PATH.getValue()).exists()) { + dir = domParticipant.getContact().IMAGE_PATH.getValue(); + //Get parent folder + dir = dir.substring(0, dir.lastIndexOf(File.separator)); + } else { + dir = System.getProperty("user.dir"); + } + + File defaultDirectory = new File(dir); + chooser.setInitialDirectory(defaultDirectory); + + chooser.getExtensionFilters().addAll( + new FileChooser.ExtensionFilter("JPG", "*.jpg"), + new FileChooser.ExtensionFilter("PNG", "*.png") + ); + + File image = chooser.showOpenDialog(stage); + + if (image != null && image.exists()) { + String extension = FileUtils.getExtension(image); + + if ((extension.equalsIgnoreCase("jpg") || extension.equalsIgnoreCase("png"))) { + domParticipant.getContact().IMAGE_PATH.setValue(image.getPath()); + domParticipant.getContact().IMAGE_PATH.save(); + + if (image != null && image.exists()) { + double paneWidth = ((StackPane)domImageView.getParent()).getWidth(); + double paneHeight = ((StackPane)domImageView.getParent()).getHeight(); + BufferedImage scaledImage = null; + try + { + BufferedImage originalImage = ImageIO.read(image); + int originalImageHeight = originalImage.getHeight(); + int originalImageWidth = originalImage.getWidth(); + double scaleFactorWidth = originalImageWidth / paneWidth; + double scaleFactorHeight = originalImageHeight / paneHeight; + boolean needsScaling = true; + int newWidth = 0; + int newHeight = 0; + + if (scaleFactorHeight > scaleFactorWidth) + { + if (scaleFactorHeight <= 2.0) + { + needsScaling = false; + } + else + { + newWidth = (int)(originalImageWidth / scaleFactorHeight); + newHeight = (int)(originalImageHeight / scaleFactorHeight); + } + } + else + { + if (scaleFactorWidth <= 2.0) + { + needsScaling = false; + } + else + { + newWidth = (int)(originalImageWidth / scaleFactorWidth); + newHeight = (int)(originalImageHeight / scaleFactorWidth); + } + } + if (needsScaling) + { + ResampleOp resizeOp = new ResampleOp(newWidth, newHeight); + resizeOp.setFilter(ResampleFilters.getLanczos3Filter()); + scaledImage = resizeOp.filter(originalImage, null); + } + else + { + scaledImage = originalImage; + } + } + catch (Exception e) + { + e.printStackTrace(); + } + + domImageView.setImage(SwingFXUtils.toFXImage(scaledImage, null)); + } else { + domImageView.setImage(null); + } + } else { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle("Invalid File"); + alert.setHeaderText(null); + alert.setContentText("The given file is not a supported image file."); + + alert.showAndWait(); + } + } + } + }); + + domNameTextField.setText(domParticipant.getContact().NAME.getValue()); + subNameTextField.setText(subParticipant.getContact().NAME.getValue()); + + domNameTextField.focusedProperty().addListener(new ChangeListener() { + @Override + public void changed(ObservableValue arg0, Boolean oldPropertyValue, Boolean newPropertyValue) { + if (newPropertyValue) { + //On focus + } else { + //Lost focus + domParticipant.getContact().NAME.setValue(domNameTextField.getText()); + domParticipant.getContact().save(); + } + } + }); + + subNameTextField.focusedProperty().addListener(new ChangeListener() { + @Override + public void changed(ObservableValue arg0, Boolean oldPropertyValue, Boolean newPropertyValue) { + if (newPropertyValue) { + //On focus + } else { + //Lost focus + subParticipant.getContact().NAME.setValue(subNameTextField.getText()); + subParticipant.getContact().save(); + } + } + }); + } + + public TextField getChatTextField() { + return chatTextField; + } + + public MediaView getMediaView() { + return mediaView; + } + + public ImageView getImageView() { + return imageView; + } + + public StackPane getMediaViewBox() { + return mediaViewBox; + } + + public TextFlow getChatWindow() { + return chatWindow; + } + + public ScrollPane getChatScrollPane() { + return chatScrollPane; + } + + public ChoiceBox getPersonalityChoiceBox() { + return personalityChoiceBox; + } + + public Button getStartChatButton() { + return startChatButton; + } + + public LazySubController getLazySubController() { + return lazySubController; + } } \ No newline at end of file diff --git a/src/me/goddragon/teaseai/gui/main/main.fxml b/src/me/goddragon/teaseai/gui/main/main.fxml index 7883865..977cb9c 100644 --- a/src/me/goddragon/teaseai/gui/main/main.fxml +++ b/src/me/goddragon/teaseai/gui/main/main.fxml @@ -42,7 +42,7 @@ - + diff --git a/src/me/goddragon/teaseai/utils/libraries/imagescaling/AdvancedResizeOp.java b/src/me/goddragon/teaseai/utils/libraries/imagescaling/AdvancedResizeOp.java new file mode 100644 index 0000000..9b16e79 --- /dev/null +++ b/src/me/goddragon/teaseai/utils/libraries/imagescaling/AdvancedResizeOp.java @@ -0,0 +1,120 @@ +/* + * Copyright 2013, Morten Nobel-Joergensen + * + * License: The BSD 3-Clause License + * http://opensource.org/licenses/BSD-3-Clause + */ +package me.goddragon.teaseai.utils.libraries.imagescaling; + +//import com.jhlabs.image.UnsharpFilter; + +import java.awt.*; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.BufferedImageOp; +import java.awt.image.ColorModel; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Morten Nobel-Joergensen + */ +public abstract class AdvancedResizeOp implements BufferedImageOp { + public static enum UnsharpenMask{ + None(0), + Soft(0.15f), + Normal(0.3f), + VerySharp(0.45f), + Oversharpened(0.60f); + private final float factor; + + UnsharpenMask(float factor) { + this.factor = factor; + } + } + private List listeners = new ArrayList(); + + private final DimensionConstrain dimensionConstrain; + private UnsharpenMask unsharpenMask = UnsharpenMask.None; + + public AdvancedResizeOp(DimensionConstrain dimensionConstrain) { + this.dimensionConstrain = dimensionConstrain; + } + + public UnsharpenMask getUnsharpenMask() { + return unsharpenMask; + } + + public void setUnsharpenMask(UnsharpenMask unsharpenMask) { + this.unsharpenMask = unsharpenMask; + } + + protected void fireProgressChanged(float fraction){ + for (ProgressListener progressListener:listeners){ + progressListener.notifyProgress(fraction); + } + } + + public final void addProgressListener(ProgressListener progressListener) { + listeners.add(progressListener); + } + + public final boolean removeProgressListener(ProgressListener progressListener) { + return listeners.remove(progressListener); + } + + public final BufferedImage filter(BufferedImage src, BufferedImage dest){ + Dimension dstDimension = dimensionConstrain.getDimension(new Dimension(src.getWidth(),src.getHeight())); + int dstWidth = dstDimension.width; + int dstHeight = dstDimension.height; + BufferedImage bufferedImage = doFilter(src, dest, dstWidth, dstHeight); + + /*if (unsharpenMask!= UnsharpenMask.None){ + UnsharpFilter unsharpFilter= new UnsharpFilter(); + unsharpFilter.setRadius(2f); + unsharpFilter.setAmount(unsharpenMask.factor); + unsharpFilter.setThreshold(10); + return unsharpFilter.filter(bufferedImage, null); + }*/ + + return bufferedImage; + } + + protected abstract BufferedImage doFilter(BufferedImage src, BufferedImage dest, int dstWidth, int dstHeight); + + /** + * {@inheritDoc} + */ + public final Rectangle2D getBounds2D(BufferedImage src) { + return new Rectangle(0, 0, src.getWidth(), src.getHeight()); + } + + /** + * {@inheritDoc} + */ + public final BufferedImage createCompatibleDestImage(BufferedImage src, + ColorModel destCM) { + if (destCM == null) { + destCM = src.getColorModel(); + } + return new BufferedImage(destCM, + destCM.createCompatibleWritableRaster( + src.getWidth(), src.getHeight()), + destCM.isAlphaPremultiplied(), null); + } + + /** + * {@inheritDoc} + */ + public final Point2D getPoint2D(Point2D srcPt, Point2D dstPt) { + return (Point2D) srcPt.clone(); + } + + /** + * {@inheritDoc} + */ + public final RenderingHints getRenderingHints() { + return null; + } +} diff --git a/src/me/goddragon/teaseai/utils/libraries/imagescaling/BSplineFilter.java b/src/me/goddragon/teaseai/utils/libraries/imagescaling/BSplineFilter.java new file mode 100644 index 0000000..bb0c4ca --- /dev/null +++ b/src/me/goddragon/teaseai/utils/libraries/imagescaling/BSplineFilter.java @@ -0,0 +1,45 @@ +/* + * Copyright 2013, Morten Nobel-Joergensen + * + * License: The BSD 3-Clause License + * http://opensource.org/licenses/BSD-3-Clause + */ +package me.goddragon.teaseai.utils.libraries.imagescaling; + + +/** + * A B-spline resample filter. + */ +final class BSplineFilter implements ResampleFilter +{ + public float getSamplingRadius() { + return 2.0f; + } + + public final float apply(float value) + { + if (value < 0.0f) + { + value = - value; + } + if (value < 1.0f) + { + float tt = value * value; + return 0.5f * tt * value - tt + (2.0f / 3.0f); + } + else + if (value < 2.0f) + { + value = 2.0f - value; + return (1.0f / 6.0f) * value * value * value; + } + else + { + return 0.0f; + } + } + + public String getName() { + return "BSpline"; + } +} diff --git a/src/me/goddragon/teaseai/utils/libraries/imagescaling/BellFilter.java b/src/me/goddragon/teaseai/utils/libraries/imagescaling/BellFilter.java new file mode 100644 index 0000000..4c23610 --- /dev/null +++ b/src/me/goddragon/teaseai/utils/libraries/imagescaling/BellFilter.java @@ -0,0 +1,43 @@ +/* + * Copyright 2013, Morten Nobel-Joergensen + * + * License: The BSD 3-Clause License + * http://opensource.org/licenses/BSD-3-Clause + */ +package me.goddragon.teaseai.utils.libraries.imagescaling; + +/** + * A Bell resample filter. + */ +final class BellFilter implements ResampleFilter +{ + public float getSamplingRadius() { + return 1.5f; + } + + public final float apply(float value) + { + if (value < 0.0f) + { + value = - value; + } + if (value < 0.5f) + { + return 0.75f - (value * value); + } + else + if (value < 1.5f) + { + value = value - 1.5f; + return 0.5f * (value * value); + } + else + { + return 0.0f; + } + } + + public String getName() { + return "Bell"; + } +} diff --git a/src/me/goddragon/teaseai/utils/libraries/imagescaling/BiCubicFilter.java b/src/me/goddragon/teaseai/utils/libraries/imagescaling/BiCubicFilter.java new file mode 100644 index 0000000..8571245 --- /dev/null +++ b/src/me/goddragon/teaseai/utils/libraries/imagescaling/BiCubicFilter.java @@ -0,0 +1,48 @@ +/* + * Copyright 2013, Morten Nobel-Joergensen + * + * License: The BSD 3-Clause License + * http://opensource.org/licenses/BSD-3-Clause + */ +package me.goddragon.teaseai.utils.libraries.imagescaling; + + +/** + * @author Heinz Doerr + */ +class BiCubicFilter implements ResampleFilter { + + final protected float a; + + public BiCubicFilter() { + a= -0.5f; + } + + protected BiCubicFilter(float a) { + this.a= a; + } + + public final float apply(float value) { + if (value == 0) + return 1.0f; + if (value < 0.0f) + value = -value; + float vv= value * value; + if (value < 1.0f) { + return (a + 2f) * vv * value - (a + 3f) * vv + 1f; + } + if (value < 2.0f) { + return a * vv * value - 5 * a * vv + 8 * a * value - 4 * a; + } + return 0.0f; + } + + public float getSamplingRadius() { + return 2.0f; + } + + public String getName() + { + return "BiCubic"; // also called cardinal cubic spline + } +} diff --git a/src/me/goddragon/teaseai/utils/libraries/imagescaling/BiCubicHighFreqResponse.java b/src/me/goddragon/teaseai/utils/libraries/imagescaling/BiCubicHighFreqResponse.java new file mode 100644 index 0000000..c1741dd --- /dev/null +++ b/src/me/goddragon/teaseai/utils/libraries/imagescaling/BiCubicHighFreqResponse.java @@ -0,0 +1,23 @@ +/* + * Copyright 2013, Morten Nobel-Joergensen + * + * License: The BSD 3-Clause License + * http://opensource.org/licenses/BSD-3-Clause + */ +package me.goddragon.teaseai.utils.libraries.imagescaling; + +/** + * @author Heinz Doerr + */ +final class BiCubicHighFreqResponse extends BiCubicFilter { + + public BiCubicHighFreqResponse() { + super(-1.f); + } + + @Override + public String getName() { + return "BiCubicHighFreqResponse"; + } + +} \ No newline at end of file diff --git a/src/me/goddragon/teaseai/utils/libraries/imagescaling/BoxFilter.java b/src/me/goddragon/teaseai/utils/libraries/imagescaling/BoxFilter.java new file mode 100644 index 0000000..faf6c8e --- /dev/null +++ b/src/me/goddragon/teaseai/utils/libraries/imagescaling/BoxFilter.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013, Morten Nobel-Joergensen + * + * License: The BSD 3-Clause License + * http://opensource.org/licenses/BSD-3-Clause + */ +package me.goddragon.teaseai.utils.libraries.imagescaling; + +/** + * A box filter (also known as nearest neighbor). + */ +final class BoxFilter implements ResampleFilter +{ + public float getSamplingRadius() { + return 0.5f; + } + + public final float apply(float value) + { + if (value > -0.5f && value <= 0.5f) + { + return 1.0f; + } + else + { + return 0.0f; + } + } + + public String getName() { + return "Box"; + } +} diff --git a/src/me/goddragon/teaseai/utils/libraries/imagescaling/DimensionConstrain.java b/src/me/goddragon/teaseai/utils/libraries/imagescaling/DimensionConstrain.java new file mode 100644 index 0000000..2a94c80 --- /dev/null +++ b/src/me/goddragon/teaseai/utils/libraries/imagescaling/DimensionConstrain.java @@ -0,0 +1,177 @@ +/* + * Copyright 2013, Morten Nobel-Joergensen + * + * License: The BSD 3-Clause License + * http://opensource.org/licenses/BSD-3-Clause + */ +package me.goddragon.teaseai.utils.libraries.imagescaling; + +import java.awt.*; + +/** + * This class let you create dimension constrains based on a actual image. + * + * Class may be subclassed to create user defined behavior. To do this you need to overwrite + * the method getDimension(Dimension). + */ +public class DimensionConstrain { + protected DimensionConstrain () + { + } + + /** + * Will always return a dimension with positive width and height; + * @param dimension of the unscaled image + * @return the dimension of the scaled image + */ + public Dimension getDimension(Dimension dimension){ + return dimension; + } + + /** + * Used when the destination size is fixed. This may not keep the image aspect radio + * @param width destination dimension width + * @param height destination dimension height + * @return destination dimension (width x height) + */ + public static DimensionConstrain createAbsolutionDimension(final int width, final int height){ + assert width>0 && height>0:"Dimension must be a positive integer"; + return new DimensionConstrain(){ + public Dimension getDimension(Dimension dimension) { + return new Dimension(width, height); + } + }; + } + + /** + * Used when the destination size is relative to the source. This keeps the image aspect radio + * @param fraction resize fraction (must be a positive number) + * @return the new dimension (the input dimension times the fraction) + */ + public static DimensionConstrain createRelativeDimension(final float fraction){ + return createRelativeDimension(fraction,fraction); + } + + /** + * Used when the destination size is relative to the source. This keeps the image aspect radio if fractionWidth + * equals fractionHeight + * @param + * @return + */ + public static DimensionConstrain createRelativeDimension(final float fractionWidth, final float fractionHeight){ + assert fractionHeight>0 && fractionWidth>0:"Fractions must be larger than 0.0"; + return new DimensionConstrain(){ + public Dimension getDimension(Dimension dimension) { + int width = Math.max(1,Math.round(fractionWidth*dimension.width)); + int height = Math.max(1,Math.round(fractionHeight*dimension.height)); + return new Dimension(width, height); + } + }; + } + + /** + * Forces the image to keep radio and be keeped within the width and height + * @param width + * @param height + * @return + */ + public static DimensionConstrain createMaxDimension(int width, int height){ + return createMaxDimension(width, height,false); + } + + /** + * Forces the image to keep radio and be keeped within the width and height. + * @param width + * @param height + * @param neverEnlargeImage if true only a downscale will occour + * @return + */ + public static DimensionConstrain createMaxDimension(final int width, final int height, final boolean neverEnlargeImage){ + assert width >0 && height > 0 : "Dimension must be larger that 0"; + final double scaleFactor = width/(double)height; + return new DimensionConstrain(){ + public Dimension getDimension(Dimension dimension) { + double srcScaleFactor = dimension.width/(double)dimension.height; + double scale; + if (srcScaleFactor>scaleFactor){ + scale = width/(double)dimension.width; + } + else{ + scale = height/(double)dimension.height; + } + if (neverEnlargeImage){ + scale = Math.min(scale,1); + } + int dstWidth = (int)Math.round (dimension.width*scale); + int dstHeight = (int) Math.round(dimension.height*scale); + return new Dimension(dstWidth, dstHeight); + } + }; + } + + /** + * Forces the image to keep radio and be keeped within the width and height. Width and height are defined + * (length1 x length2) or (length2 x length1). + * + * This is usefull is the scaling allow a certain format (such as 16x9") but allow the dimension to be rotated 90 + * degrees (so also 9x16" is allowed). + * + * @param length1 + * @param length2 + * @return + */ + public static DimensionConstrain createMaxDimensionNoOrientation(int length1, int length2){ + return createMaxDimensionNoOrientation(length1, length2,false); + } + + /** + * Forces the image to keep radio and be keeped within the width and height. Width and height are defined + * (length1 x length2) or (length2 x length1). + * + * This is usefull is the scaling allow a certain format (such as 16x9") but allow the dimension to be rotated 90 + * degrees (so also 9x16" is allowed). + * + * @param length1 + * @param length2 + * @param neverEnlargeImage if true only a downscale will occour + * @return + */ + public static DimensionConstrain createMaxDimensionNoOrientation(final int length1, final int length2, final boolean neverEnlargeImage){ + assert length1 >0 && length2 > 0 : "Dimension must be larger that 0"; + final double scaleFactor = length1/(double)length2; + return new DimensionConstrain(){ + public Dimension getDimension(Dimension dimension) { + double srcScaleFactor = dimension.width/(double)dimension.height; + int width; + int height; + // swap length1 and length2 + if (srcScaleFactor>scaleFactor){ + width = length1; + height = length2; + } + else{ + width = length2; + height = length1; + } + + + final double scaleFactor = width/(double)height; + double scale; + if (srcScaleFactor>scaleFactor){ + scale = width/(double)dimension.width; + } + else{ + scale = height/(double)dimension.height; + } + if (neverEnlargeImage){ + scale = Math.min(scale,1); + } + int dstWidth = (int)Math.round (dimension.width*scale); + int dstHeight = (int) Math.round(dimension.height*scale); + return new Dimension(dstWidth, dstHeight); + } + }; + } + + +} diff --git a/src/me/goddragon/teaseai/utils/libraries/imagescaling/HermiteFilter.java b/src/me/goddragon/teaseai/utils/libraries/imagescaling/HermiteFilter.java new file mode 100644 index 0000000..f913a9d --- /dev/null +++ b/src/me/goddragon/teaseai/utils/libraries/imagescaling/HermiteFilter.java @@ -0,0 +1,37 @@ +/* + * Copyright 2013, Morten Nobel-Joergensen + * + * License: The BSD 3-Clause License + * http://opensource.org/licenses/BSD-3-Clause + */ +package me.goddragon.teaseai.utils.libraries.imagescaling; + +/** + * A Hermite resampling filter. + */ +class HermiteFilter implements ResampleFilter +{ + public float getSamplingRadius() { + return 1.0f; + } + + public float apply(float value) + { + if (value < 0.0f) + { + value = - value; + } + if (value < 1.0f) + { + return (2.0f * value - 3.0f) * value * value + 1.0f; + } + else + { + return 0.0f; + } + } + + public String getName() { + return "BSpline"; + } +} \ No newline at end of file diff --git a/src/me/goddragon/teaseai/utils/libraries/imagescaling/ImageUtils.java b/src/me/goddragon/teaseai/utils/libraries/imagescaling/ImageUtils.java new file mode 100644 index 0000000..536b2c7 --- /dev/null +++ b/src/me/goddragon/teaseai/utils/libraries/imagescaling/ImageUtils.java @@ -0,0 +1,277 @@ +/* + * Copyright 2013, Morten Nobel-Joergensen + * + * License: The BSD 3-Clause License + * http://opensource.org/licenses/BSD-3-Clause + */ +package me.goddragon.teaseai.utils.libraries.imagescaling; + +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.ImageWriter; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.stream.MemoryCacheImageInputStream; +import javax.imageio.stream.MemoryCacheImageOutputStream; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.io.*; +import java.util.Iterator; + +/** + * @author Heinz Doerr + * @author Morten Nobel-Joergensen + */ +public class ImageUtils { + + + static public String imageTypeName(BufferedImage img) { + switch (img.getType()) { + case BufferedImage.TYPE_3BYTE_BGR: return "TYPE_3BYTE_BGR"; + case BufferedImage.TYPE_4BYTE_ABGR: return "TYPE_4BYTE_ABGR"; + case BufferedImage.TYPE_4BYTE_ABGR_PRE: return "TYPE_4BYTE_ABGR_PRE"; + case BufferedImage.TYPE_BYTE_BINARY: return "TYPE_BYTE_BINARY"; + case BufferedImage.TYPE_BYTE_GRAY: return "TYPE_BYTE_GRAY"; + case BufferedImage.TYPE_BYTE_INDEXED: return "TYPE_BYTE_INDEXED"; + case BufferedImage.TYPE_CUSTOM: return "TYPE_CUSTOM"; + case BufferedImage.TYPE_INT_ARGB: return "TYPE_INT_ARGB"; + case BufferedImage.TYPE_INT_ARGB_PRE: return "TYPE_INT_ARGB_PRE"; + case BufferedImage.TYPE_INT_BGR: return "TYPE_INT_BGR"; + case BufferedImage.TYPE_INT_RGB: return "TYPE_INT_RGB"; + case BufferedImage.TYPE_USHORT_555_RGB: return "TYPE_USHORT_555_RGB"; + case BufferedImage.TYPE_USHORT_565_RGB: return "TYPE_USHORT_565_RGB"; + case BufferedImage.TYPE_USHORT_GRAY: return "TYPE_USHORT_GRAY"; + } + return "unknown image type #" + img.getType(); + } + + static public int nrChannels(BufferedImage img) { + switch (img.getType()) { + case BufferedImage.TYPE_3BYTE_BGR: return 3; + case BufferedImage.TYPE_4BYTE_ABGR: return 4; + case BufferedImage.TYPE_BYTE_GRAY: return 1; + case BufferedImage.TYPE_INT_BGR: return 3; + case BufferedImage.TYPE_INT_ARGB: return 4; + case BufferedImage.TYPE_INT_RGB: return 3; + case BufferedImage.TYPE_CUSTOM: return 4; + case BufferedImage.TYPE_4BYTE_ABGR_PRE: return 4; + case BufferedImage.TYPE_INT_ARGB_PRE: return 4; + case BufferedImage.TYPE_USHORT_555_RGB: return 3; + case BufferedImage.TYPE_USHORT_565_RGB: return 3; + case BufferedImage.TYPE_USHORT_GRAY: return 1; + } + return 0; + } + + + + /** + * + * returns one row (height == 1) of byte packed image data in BGR or AGBR form + * + * @param img + * @param y + * @param w + * @param array + * @param temp must be either null or a array with length of w*h + * @return + */ + public static byte[] getPixelsBGR(BufferedImage img, int y, int w, byte[] array, int[] temp) { + final int x= 0; + final int h= 1; + + assert array.length == temp.length * nrChannels(img); + assert (temp.length == w); + + int imageType= img.getType(); + Raster raster; + switch (imageType) { + case BufferedImage.TYPE_3BYTE_BGR: + case BufferedImage.TYPE_4BYTE_ABGR: + case BufferedImage.TYPE_4BYTE_ABGR_PRE: + case BufferedImage.TYPE_BYTE_GRAY: + raster= img.getRaster(); + //int ttype= raster.getTransferType(); + raster.getDataElements(x, y, w, h, array); + break; + case BufferedImage.TYPE_INT_BGR: + raster= img.getRaster(); + raster.getDataElements(x, y, w, h, temp); + ints2bytes(temp, array, 0, 1, 2); // bgr --> bgr + break; + case BufferedImage.TYPE_INT_RGB: + raster= img.getRaster(); + raster.getDataElements(x, y, w, h, temp); + ints2bytes(temp, array, 2, 1, 0); // rgb --> bgr + break; + case BufferedImage.TYPE_INT_ARGB: + case BufferedImage.TYPE_INT_ARGB_PRE: + raster= img.getRaster(); + raster.getDataElements(x, y, w, h, temp); + ints2bytes(temp, array, 2, 1, 0, 3); // argb --> abgr + break; + case BufferedImage.TYPE_CUSTOM: // TODO: works for my icon image loader, but else ??? + img.getRGB(x, y, w, h, temp, 0, w); + ints2bytes(temp, array, 2, 1, 0, 3); // argb --> abgr + break; + default: + img.getRGB(x, y, w, h, temp, 0, w); + ints2bytes(temp, array, 2, 1, 0); // rgb --> bgr + break; + } + + return array; + } + + /** + * converts and copies byte packed BGR or ABGR into the img buffer, + * the img type may vary (e.g. RGB or BGR, int or byte packed) + * but the number of components (w/o alpha, w alpha, gray) must match + * + * does not unmange the image for all (A)RGN and (A)BGR and gray imaged + * + */ + public static void setBGRPixels(byte[] bgrPixels, BufferedImage img, int x, int y, int w, int h) { + int imageType= img.getType(); + WritableRaster raster= img.getRaster(); + //int ttype= raster.getTransferType(); + if (imageType == BufferedImage.TYPE_3BYTE_BGR || + imageType == BufferedImage.TYPE_4BYTE_ABGR || + imageType == BufferedImage.TYPE_4BYTE_ABGR_PRE || + imageType == BufferedImage.TYPE_BYTE_GRAY) { + raster.setDataElements(x, y, w, h, bgrPixels); + } else { + int[] pixels; + if (imageType == BufferedImage.TYPE_INT_BGR) { + pixels= bytes2int(bgrPixels, 2, 1, 0); // bgr --> bgr + } else if (imageType == BufferedImage.TYPE_INT_ARGB || + imageType == BufferedImage.TYPE_INT_ARGB_PRE) { + pixels= bytes2int(bgrPixels, 3, 0, 1, 2); // abgr --> argb + } else { + pixels= bytes2int(bgrPixels, 0, 1, 2); // bgr --> rgb + } + if (w == 0 || h == 0) { + return; + } else if (pixels.length < w * h) { + throw new IllegalArgumentException("pixels array must have a length" + " >= w*h"); + } + if (imageType == BufferedImage.TYPE_INT_ARGB || + imageType == BufferedImage.TYPE_INT_RGB || + imageType == BufferedImage.TYPE_INT_ARGB_PRE || + imageType == BufferedImage.TYPE_INT_BGR) { + raster.setDataElements(x, y, w, h, pixels); + } else { + // Unmanages the image + img.setRGB(x, y, w, h, pixels, 0, w); + } + } + } + + + public static void ints2bytes(int[] in, byte[] out, int index1, int index2, int index3) { + for (int i= 0; i < in.length; i++) { + int index= i * 3; + int value= in[i]; + out[index + index1]= (byte)value; + value= value >> 8; + out[index + index2]= (byte)value; + value= value >> 8; + out[index + index3]= (byte)value; + } + } + + public static void ints2bytes(int[] in, byte[] out, int index1, int index2, int index3, int index4) { + for (int i= 0; i < in.length; i++) { + int index= i * 4; + int value= in[i]; + out[index + index1]= (byte)value; + value= value >> 8; + out[index + index2]= (byte)value; + value= value >> 8; + out[index + index3]= (byte)value; + value= value >> 8; + out[index + index4]= (byte)value; + } + } + + public static int[] bytes2int(byte[] in, int index1, int index2, int index3) { + int[] out= new int[in.length / 3]; + for (int i= 0; i < out.length; i++) { + int index= i * 3; + int b1= (in[index +index1] & 0xff) << 16; + int b2= (in[index + index2] & 0xff) << 8; + int b3= in[index + index3] & 0xff; + out[i]= b1 | b2 | b3; + } + return out; + } + + public static int[] bytes2int(byte[] in, int index1, int index2, int index3, int index4) { + int[] out= new int[in.length / 4]; + for (int i= 0; i < out.length; i++) { + int index= i * 4; + int b1= (in[index +index1] & 0xff) << 24; + int b2= (in[index +index2] & 0xff) << 16; + int b3= (in[index + index3] & 0xff) << 8; + int b4= in[index + index4] & 0xff; + out[i]= b1 | b2 | b3 | b4; + } + return out; + } + + public static BufferedImage convert(BufferedImage src, int bufImgType) { + BufferedImage img= new BufferedImage(src.getWidth(), src.getHeight(), bufImgType); + Graphics2D g2d= img.createGraphics(); + g2d.drawImage(src, 0, 0, null); + g2d.dispose(); + return img; + } + + /** + * Copy jpeg meta data (exif) from source to dest and save it to out. + * + * @param source + * @param dest + * @return result + * @throws IOException + */ + public static byte[] copyJpegMetaData(byte[] source, byte[] dest) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageOutputStream out = new MemoryCacheImageOutputStream(baos); + copyJpegMetaData(new ByteArrayInputStream(source),new ByteArrayInputStream(dest), out); + return baos.toByteArray(); + } + + /** + * Copy jpeg meta data (exif) from source to dest and save it to out + * + * @param source + * @param dest + * @param out + * @throws IOException + */ + public static void copyJpegMetaData(InputStream source, InputStream dest, ImageOutputStream out) throws IOException { + // Read meta data from src image + Iterator iter = ImageIO.getImageReadersByFormatName("jpeg"); + ImageReader reader=(ImageReader) iter.next(); + ImageInputStream iis = new MemoryCacheImageInputStream(source); + reader.setInput(iis); + IIOMetadata metadata = reader.getImageMetadata(0); + iis.close(); + // Read dest image + ImageInputStream outIis = new MemoryCacheImageInputStream(dest); + reader.setInput(outIis); + IIOImage image = reader.readAll(0,null); + image.setMetadata(metadata); + outIis.close(); + // write dest image + iter = ImageIO.getImageWritersByFormatName("jpeg"); + ImageWriter writer=(ImageWriter) iter.next(); + writer.setOutput(out); + writer.write(image); + } +} diff --git a/src/me/goddragon/teaseai/utils/libraries/imagescaling/Lanczos3Filter.java b/src/me/goddragon/teaseai/utils/libraries/imagescaling/Lanczos3Filter.java new file mode 100644 index 0000000..2251c07 --- /dev/null +++ b/src/me/goddragon/teaseai/utils/libraries/imagescaling/Lanczos3Filter.java @@ -0,0 +1,46 @@ +/* + * Copyright 2013, Morten Nobel-Joergensen + * + * License: The BSD 3-Clause License + * http://opensource.org/licenses/BSD-3-Clause + */ +package me.goddragon.teaseai.utils.libraries.imagescaling; + +final class Lanczos3Filter implements ResampleFilter { + private final static float PI_FLOAT = (float) Math.PI; + + private float sincModified(float value) + { + return ((float)Math.sin(value)) / value; + } + + public final float apply(float value) + { + if (value==0){ + return 1.0f; + } + if (value < 0.0f) + { + value = -value; + } + + if (value < 3.0f) + { + value *= PI_FLOAT; + return sincModified(value) * sincModified(value / 3.0f); + } + else + { + return 0.0f; + } + } + + public float getSamplingRadius() { + return 3.0f; + } + + public String getName() + { + return "Lanczos3"; + } +} diff --git a/src/me/goddragon/teaseai/utils/libraries/imagescaling/MitchellFilter.java b/src/me/goddragon/teaseai/utils/libraries/imagescaling/MitchellFilter.java new file mode 100644 index 0000000..9475464 --- /dev/null +++ b/src/me/goddragon/teaseai/utils/libraries/imagescaling/MitchellFilter.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013, Morten Nobel-Joergensen + * + * License: The BSD 3-Clause License + * http://opensource.org/licenses/BSD-3-Clause + */ +package me.goddragon.teaseai.utils.libraries.imagescaling; + +/** + * The Mitchell resample filter. + */ +final class MitchellFilter implements ResampleFilter +{ + private static final float B = 1.0f / 3.0f; + private static final float C = 1.0f / 3.0f; + + public float getSamplingRadius() { + return 2.0f; + } + + public final float apply(float value) + { + if (value < 0.0f) + { + value = -value; + } + float tt = value * value; + if (value < 1.0f) + { + value = (((12.0f - 9.0f * B - 6.0f * C) * (value * tt)) + + ((-18.0f + 12.0f * B + 6.0f * C) * tt) + + (6.0f - 2f * B)); + return value / 6.0f; + } + else + if (value < 2.0f) + { + value = (((-1.0f * B - 6.0f * C) * (value * tt)) + + ((6.0f * B + 30.0f * C) * tt) + + ((-12.0f * B - 48.0f * C) * value) + + (8.0f * B + 24 * C)); + return value / 6.0f; + } + else + { + return 0.0f; + } + } + + public String getName() { + return "BSpline"; + } +} + diff --git a/src/me/goddragon/teaseai/utils/libraries/imagescaling/MultiStepRescaleOp.java b/src/me/goddragon/teaseai/utils/libraries/imagescaling/MultiStepRescaleOp.java new file mode 100644 index 0000000..29ebfed --- /dev/null +++ b/src/me/goddragon/teaseai/utils/libraries/imagescaling/MultiStepRescaleOp.java @@ -0,0 +1,90 @@ +/* + * Copyright 2013, Morten Nobel-Joergensen + * + * License: The BSD 3-Clause License + * http://opensource.org/licenses/BSD-3-Clause + */ +package me.goddragon.teaseai.utils.libraries.imagescaling; + +import java.awt.*; +import java.awt.image.BufferedImage; + +/** + * This code is very inspired on Chris Campbells article "The Perils of Image.getScaledInstance()" + * + * The article can be found here: + * http://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html + * + * Note that the filter method is threadsafe + */ +public class MultiStepRescaleOp extends AdvancedResizeOp { + private final Object renderingHintInterpolation; + + public MultiStepRescaleOp(int dstWidth, int dstHeight) { + this (dstWidth, dstHeight, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + } + + public MultiStepRescaleOp(int dstWidth, int dstHeight, Object renderingHintInterpolation) { + this(DimensionConstrain.createAbsolutionDimension(dstWidth, dstHeight), renderingHintInterpolation); + } + + public MultiStepRescaleOp(DimensionConstrain dimensionConstain) { + this (dimensionConstain, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + } + + public MultiStepRescaleOp(DimensionConstrain dimensionConstain, Object renderingHintInterpolation) { + super(dimensionConstain); + this.renderingHintInterpolation = renderingHintInterpolation; + assert RenderingHints.KEY_INTERPOLATION.isCompatibleValue(renderingHintInterpolation) : + "Rendering hint "+renderingHintInterpolation+" is not compatible with interpolation"; + } + + + public BufferedImage doFilter(BufferedImage img, BufferedImage dest, int dstWidth, int dstHeight) { + int type = (img.getTransparency() == Transparency.OPAQUE) ? + BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; + BufferedImage ret = img; + int w, h; + + // Use multi-step technique: start with original size, then + // scale down in multiple passes with drawImage() + // until the target size is reached + w = img.getWidth(); + h = img.getHeight(); + + do { + if (w > dstWidth) { + w /= 2; + if (w < dstWidth) { + w = dstWidth; + } + } else { + w = dstWidth; + } + + if (h > dstHeight) { + h /= 2; + if (h < dstHeight) { + h = dstHeight; + } + } else { + h = dstHeight; + } + + BufferedImage tmp; + if (dest!=null && dest.getWidth()== w && dest.getHeight()== h && w==dstWidth && h==dstHeight){ + tmp = dest; + } else { + tmp = new BufferedImage(w,h,type); + } + Graphics2D g2 = tmp.createGraphics(); + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, renderingHintInterpolation); + g2.drawImage(ret, 0, 0, w, h, null); + g2.dispose(); + + ret = tmp; + } while (w != dstWidth || h != dstHeight); + + return ret; + } +} \ No newline at end of file diff --git a/src/me/goddragon/teaseai/utils/libraries/imagescaling/ProgressListener.java b/src/me/goddragon/teaseai/utils/libraries/imagescaling/ProgressListener.java new file mode 100644 index 0000000..3b74afc --- /dev/null +++ b/src/me/goddragon/teaseai/utils/libraries/imagescaling/ProgressListener.java @@ -0,0 +1,11 @@ +/* + * Copyright 2013, Morten Nobel-Joergensen + * + * License: The BSD 3-Clause License + * http://opensource.org/licenses/BSD-3-Clause + */ +package me.goddragon.teaseai.utils.libraries.imagescaling; + +public interface ProgressListener { + public void notifyProgress(float fraction); +} diff --git a/src/me/goddragon/teaseai/utils/libraries/imagescaling/ResampleFilter.java b/src/me/goddragon/teaseai/utils/libraries/imagescaling/ResampleFilter.java new file mode 100644 index 0000000..a31fb70 --- /dev/null +++ b/src/me/goddragon/teaseai/utils/libraries/imagescaling/ResampleFilter.java @@ -0,0 +1,15 @@ +/* + * Copyright 2013, Morten Nobel-Joergensen + * + * License: The BSD 3-Clause License + * http://opensource.org/licenses/BSD-3-Clause + */ +package me.goddragon.teaseai.utils.libraries.imagescaling; + +public interface ResampleFilter { + public float getSamplingRadius(); + + float apply(float v); + + public abstract String getName(); +} diff --git a/src/me/goddragon/teaseai/utils/libraries/imagescaling/ResampleFilters.java b/src/me/goddragon/teaseai/utils/libraries/imagescaling/ResampleFilters.java new file mode 100644 index 0000000..1f3d9d0 --- /dev/null +++ b/src/me/goddragon/teaseai/utils/libraries/imagescaling/ResampleFilters.java @@ -0,0 +1,55 @@ +/* + * Copyright 2013, Morten Nobel-Joergensen + * + * License: The BSD 3-Clause License + * http://opensource.org/licenses/BSD-3-Clause + */ +package me.goddragon.teaseai.utils.libraries.imagescaling; + +public class ResampleFilters { + private static BellFilter bellFilter = new BellFilter(); + private static BiCubicFilter biCubicFilter = new BiCubicFilter(); + private static BiCubicHighFreqResponse biCubicHighFreqResponse = new BiCubicHighFreqResponse(); + private static BoxFilter boxFilter = new BoxFilter(); + private static BSplineFilter bSplineFilter = new BSplineFilter(); + private static HermiteFilter hermiteFilter = new HermiteFilter(); + private static Lanczos3Filter lanczos3Filter = new Lanczos3Filter(); + private static MitchellFilter mitchellFilter = new MitchellFilter(); + private static TriangleFilter triangleFilter = new TriangleFilter(); + + public static ResampleFilter getBellFilter(){ + return bellFilter; + } + + public static ResampleFilter getBiCubicFilter(){ + return biCubicFilter; + } + + public static ResampleFilter getBiCubicHighFreqResponse(){ + return biCubicHighFreqResponse; + } + + public static ResampleFilter getBoxFilter(){ + return boxFilter; + } + + public static ResampleFilter getBSplineFilter(){ + return bSplineFilter; + } + + public static ResampleFilter getHermiteFilter(){ + return hermiteFilter; + } + + public static ResampleFilter getLanczos3Filter(){ + return lanczos3Filter; + } + + public static ResampleFilter getMitchellFilter(){ + return mitchellFilter; + } + + public static ResampleFilter getTriangleFilter(){ + return triangleFilter; + } +} diff --git a/src/me/goddragon/teaseai/utils/libraries/imagescaling/ResampleOp.java b/src/me/goddragon/teaseai/utils/libraries/imagescaling/ResampleOp.java new file mode 100644 index 0000000..0b691a9 --- /dev/null +++ b/src/me/goddragon/teaseai/utils/libraries/imagescaling/ResampleOp.java @@ -0,0 +1,494 @@ +/* + * Copyright 2013, Morten Nobel-Joergensen + * + * License: The BSD 3-Clause License + * http://opensource.org/licenses/BSD-3-Clause + */ +package me.goddragon.teaseai.utils.libraries.imagescaling; + +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Based on work from Java Image Util ( http://schmidt.devlib.org/jiu/ ) + * + * Note that the filter method is not thread safe + * + * @author Morten Nobel-Joergensen + * @author Heinz Doerr + */ +public class ResampleOp extends AdvancedResizeOp +{ + private final int MAX_CHANNEL_VALUE= 255; + + private int nrChannels; + private int srcWidth; + private int srcHeight; + private int dstWidth; + private int dstHeight; + + static class SubSamplingData{ + private final int[] arrN; // individual - per row or per column - nr of contributions + private final int[] arrPixel; // 2Dim: [wid or hei][contrib] + private final float[] arrWeight; // 2Dim: [wid or hei][contrib] + private final int numContributors; // the primary index length for the 2Dim arrays : arrPixel and arrWeight + + private SubSamplingData(int[] arrN, int[] arrPixel, float[] arrWeight, int numContributors) { + this.arrN = arrN; + this.arrPixel = arrPixel; + this.arrWeight = arrWeight; + this.numContributors = numContributors; + } + + + public int getNumContributors() { + return numContributors; + } + + public int[] getArrN() { + return arrN; + } + + public int[] getArrPixel() { + return arrPixel; + } + + public float[] getArrWeight() { + return arrWeight; + } + } + + private SubSamplingData horizontalSubsamplingData; + private SubSamplingData verticalSubsamplingData; + + private int processedItems; + private float totalItems; + + private int numberOfThreads = Runtime.getRuntime().availableProcessors(); + + private AtomicInteger multipleInvocationLock = new AtomicInteger(); + + private ResampleFilter filter = ResampleFilters.getLanczos3Filter(); + + + public ResampleOp(int destWidth, int destHeight) { + this(DimensionConstrain.createAbsolutionDimension(destWidth, destHeight)); + } + + public ResampleOp(DimensionConstrain dimensionConstrain) { + super(dimensionConstrain); + } + + public ResampleFilter getFilter() { + return filter; + } + + public void setFilter(ResampleFilter filter) { + this.filter = filter; + } + + public int getNumberOfThreads() { + return numberOfThreads; + } + + public void setNumberOfThreads(int numberOfThreads) { + this.numberOfThreads = numberOfThreads; + } + + public BufferedImage doFilter(BufferedImage srcImg, BufferedImage dest, int dstWidth, int dstHeight) { + this.dstWidth = dstWidth; + this.dstHeight = dstHeight; + + if (dstWidth<3 || dstHeight<3){ + throw new RuntimeException("Error doing rescale. Target size was "+dstWidth+"x"+dstHeight+" but must be at least 3x3."); + } + + assert multipleInvocationLock.incrementAndGet()==1:"Multiple concurrent invocations detected"; + + if (srcImg.getType() == BufferedImage.TYPE_BYTE_BINARY || + srcImg.getType() == BufferedImage.TYPE_BYTE_INDEXED || + srcImg.getType() == BufferedImage.TYPE_CUSTOM) + srcImg = ImageUtils.convert(srcImg, srcImg.getColorModel().hasAlpha() ? + BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR); + + this.nrChannels= ImageUtils.nrChannels(srcImg); + assert nrChannels > 0; + this.srcWidth = srcImg.getWidth(); + this.srcHeight = srcImg.getHeight(); + + byte[][] workPixels = new byte[srcHeight][dstWidth*nrChannels]; + + this.processedItems = 0; + this.totalItems = srcHeight + dstWidth; + + // Pre-calculate sub-sampling + horizontalSubsamplingData = createSubSampling(filter, srcWidth, dstWidth); + verticalSubsamplingData = createSubSampling(filter,srcHeight, dstHeight); + + + final BufferedImage scrImgCopy = srcImg; + final byte[][] workPixelsCopy = workPixels; + Thread[] threads = new Thread[numberOfThreads-1]; + for (int i=1;i= srcSize) { + n= srcSize - j + srcSize - 1; + } else { + n= j; + } + int k= arrN[i]; + //assert k == j-left:String.format("%s = %s %s", k,j,left); + arrN[i]++; + if (n < 0 || n >= srcSize) { + weight= 0.0f;// Flag that cell should not be used + } + arrPixel[subindex +k]= n; + arrWeight[subindex + k]= weight; + } + // normalize the filter's weight's so the sum equals to 1.0, very important for avoiding box type of artifacts + final int max= arrN[i]; + float tot= 0; + for (int k= 0; k < max; k++) + tot+= arrWeight[subindex + k]; + if (tot != 0f) { // 0 should never happen except bug in filter + for (int k= 0; k < max; k++) + arrWeight[subindex + k]/= tot; + } + } + } else + // super-sampling + // Scales from smaller to bigger height + { + numContributors= (int)(fwidth * 2.0f + 1); + arrWeight= new float[dstSize * numContributors]; + arrPixel= new int[dstSize * numContributors]; + // + for (int i= 0; i < dstSize; i++) { + final int subindex= i * numContributors; + float center= i / scale + centerOffset; + int left= (int)Math.floor(center - fwidth); + int right= (int)Math.ceil(center + fwidth); + for (int j= left; j <= right; j++) { + float weight= filter.apply(center - j); + if (weight == 0.0f) { + continue; + } + int n; + if (j < 0) { + n= -j; + } else if (j >= srcSize) { + n= srcSize - j + srcSize - 1; + } else { + n= j; + } + int k= arrN[i]; + arrN[i]++; + if (n < 0 || n >= srcSize) { + weight= 0.0f;// Flag that cell should not be used + } + arrPixel[subindex +k]= n; + arrWeight[subindex + k]= weight; + } + // normalize the filter's weight's so the sum equals to 1.0, very important for avoiding box type of artifacts + final int max= arrN[i]; + float tot= 0; + for (int k= 0; k < max; k++) + tot+= arrWeight[subindex + k]; + assert tot!=0:"should never happen except bug in filter"; + if (tot != 0f) { + for (int k= 0; k < max; k++) + arrWeight[subindex + k]/= tot; + } + } + } + return new SubSamplingData(arrN, arrPixel, arrWeight, numContributors); + } + + private void verticalFromWorkToDst(byte[][] workPixels, byte[] outPixels, int start, int delta) { + if (nrChannels==1){ + verticalFromWorkToDstGray(workPixels, outPixels, start,numberOfThreads); + return; + } + boolean useChannel3 = nrChannels>3; + for (int x = start; x < dstWidth; x+=delta) + { + final int xLocation = x*nrChannels; + for (int y = dstHeight-1; y >=0 ; y--) + { + final int yTimesNumContributors = y * verticalSubsamplingData.numContributors; + final int max= verticalSubsamplingData.arrN[y]; + final int sampleLocation = (y*dstWidth+x)*nrChannels; + + + float sample0 = 0.0f; + float sample1 = 0.0f; + float sample2 = 0.0f; + float sample3 = 0.0f; + int index= yTimesNumContributors; + for (int j= max-1; j >=0 ; j--) { + int valueLocation = verticalSubsamplingData.arrPixel[index]; + float arrWeight = verticalSubsamplingData.arrWeight[index]; + sample0+= (workPixels[valueLocation][xLocation]&0xff) *arrWeight ; + sample1+= (workPixels[valueLocation][xLocation+1]&0xff) * arrWeight; + sample2+= (workPixels[valueLocation][xLocation+2]&0xff) * arrWeight; + if (useChannel3){ + sample3+= (workPixels[valueLocation][xLocation+3]&0xff) * arrWeight; + } + + index++; + } + + outPixels[sampleLocation] = toByte(sample0); + outPixels[sampleLocation +1] = toByte(sample1); + outPixels[sampleLocation +2] = toByte(sample2); + if (useChannel3){ + outPixels[sampleLocation +3] = toByte(sample3); + } + + } + processedItems++; + if (start==0){ // only update progress listener from main thread + setProgress(); + } + } + } + + private void verticalFromWorkToDstGray(byte[][] workPixels, byte[] outPixels, int start, int delta) { + for (int x = start; x < dstWidth; x+=delta) + { + final int xLocation = x; + for (int y = dstHeight-1; y >=0 ; y--) + { + final int yTimesNumContributors = y * verticalSubsamplingData.numContributors; + final int max= verticalSubsamplingData.arrN[y]; + final int sampleLocation = (y*dstWidth+x); + + + float sample0 = 0.0f; + int index= yTimesNumContributors; + for (int j= max-1; j >=0 ; j--) { + int valueLocation = verticalSubsamplingData.arrPixel[index]; + float arrWeight = verticalSubsamplingData.arrWeight[index]; + sample0+= (workPixels[valueLocation][xLocation]&0xff) *arrWeight ; + + index++; + } + + outPixels[sampleLocation] = toByte(sample0); + } + processedItems++; + if (start==0){ // only update progress listener from main thread + setProgress(); + } + } + } + + /** + * Apply filter to sample horizontally from Src to Work + * @param srcImg + * @param workPixels + */ + private void horizontallyFromSrcToWork(BufferedImage srcImg, byte[][] workPixels, int start, int delta) { + if (nrChannels==1){ + horizontallyFromSrcToWorkGray(srcImg, workPixels, start, delta); + return; + } + final int[] tempPixels = new int[srcWidth]; // Used if we work on int based bitmaps, later used to keep channel values + final byte[] srcPixels = new byte[srcWidth*nrChannels]; // create reusable row to minimize memory overhead + final boolean useChannel3 = nrChannels>3; + + + for (int k = start; k < srcHeight; k=k+delta) + { + ImageUtils.getPixelsBGR(srcImg, k, srcWidth, srcPixels, tempPixels); + + for (int i = dstWidth-1;i>=0 ; i--) + { + int sampleLocation = i*nrChannels; + final int max = horizontalSubsamplingData.arrN[i]; + + float sample0 = 0.0f; + float sample1 = 0.0f; + float sample2 = 0.0f; + float sample3 = 0.0f; + int index= i * horizontalSubsamplingData.numContributors; + for (int j= max-1; j >= 0; j--) { + float arrWeight = horizontalSubsamplingData.arrWeight[index]; + int pixelIndex = horizontalSubsamplingData.arrPixel[index]*nrChannels; + + sample0 += (srcPixels[pixelIndex]&0xff) * arrWeight; + sample1 += (srcPixels[pixelIndex+1]&0xff) * arrWeight; + sample2 += (srcPixels[pixelIndex+2]&0xff) * arrWeight; + if (useChannel3){ + sample3 += (srcPixels[pixelIndex+3]&0xff) * arrWeight; + } + index++; + } + + workPixels[k][sampleLocation] = toByte(sample0); + workPixels[k][sampleLocation +1] = toByte(sample1); + workPixels[k][sampleLocation +2] = toByte(sample2); + if (useChannel3){ + workPixels[k][sampleLocation +3] = toByte(sample3); + } + } + processedItems++; + if (start==0){ // only update progress listener from main thread + setProgress(); + } + } + } + + /** + * Apply filter to sample horizontally from Src to Work + * @param srcImg + * @param workPixels + */ + private void horizontallyFromSrcToWorkGray(BufferedImage srcImg, byte[][] workPixels, int start, int delta) { + final int[] tempPixels = new int[srcWidth]; // Used if we work on int based bitmaps, later used to keep channel values + final byte[] srcPixels = new byte[srcWidth]; // create reusable row to minimize memory overhead + + for (int k = start; k < srcHeight; k=k+delta) + { + ImageUtils.getPixelsBGR(srcImg, k, srcWidth, srcPixels, tempPixels); + + for (int i = dstWidth-1;i>=0 ; i--) + { + int sampleLocation = i; + final int max = horizontalSubsamplingData.arrN[i]; + + float sample0 = 0.0f; + int index= i * horizontalSubsamplingData.numContributors; + for (int j= max-1; j >= 0; j--) { + float arrWeight = horizontalSubsamplingData.arrWeight[index]; + int pixelIndex = horizontalSubsamplingData.arrPixel[index]; + + sample0 += (srcPixels[pixelIndex]&0xff) * arrWeight; + index++; + } + + workPixels[k][sampleLocation] = toByte(sample0); + } + processedItems++; + if (start==0){ // only update progress listener from main thread + setProgress(); + } + } + } + + private byte toByte(float f){ + if (f<0){ + return 0; + } + if (f>MAX_CHANNEL_VALUE){ + return (byte) MAX_CHANNEL_VALUE; + } + return (byte)(f+0.5f); // add 0.5 same as Math.round + } + + private void setProgress(){ + fireProgressChanged(processedItems/totalItems); + } + + protected int getResultBufferedImageType(BufferedImage srcImg) { + return nrChannels == 3 ? BufferedImage.TYPE_3BYTE_BGR : + (nrChannels == 4 ? BufferedImage.TYPE_4BYTE_ABGR : + (srcImg.getSampleModel().getDataType() == DataBuffer.TYPE_USHORT ? + BufferedImage.TYPE_USHORT_GRAY : BufferedImage.TYPE_BYTE_GRAY)); + } +} + diff --git a/src/me/goddragon/teaseai/utils/libraries/imagescaling/ThumbnailRescaleOp.java b/src/me/goddragon/teaseai/utils/libraries/imagescaling/ThumbnailRescaleOp.java new file mode 100644 index 0000000..2840be9 --- /dev/null +++ b/src/me/goddragon/teaseai/utils/libraries/imagescaling/ThumbnailRescaleOp.java @@ -0,0 +1,117 @@ +/* + * Copyright 2013, Morten Nobel-Joergensen + * + * License: The BSD 3-Clause License + * http://opensource.org/licenses/BSD-3-Clause + */ +package me.goddragon.teaseai.utils.libraries.imagescaling; + + +import java.awt.image.BufferedImage; + +/** + * The idea of this class is to provide fast (and inaccurate) rescaling method + * suitable for creating thumbnails. + * + * Note that the algorithm assumes that the source image is significant larger + * than the destination image + */ +public class ThumbnailRescaleOp extends AdvancedResizeOp { + public static enum Sampling { + S_1SAMPLE(new float[][]{{0.5f,0.5f}}), + S_2X2_RGSS(new float[][]{ + {0.6f,0.2f}, + {0.2f,0.4f}, + {0.8f,0.6f}, + {0.4f,0.8f}, + }), + S_8ROCKS(new float[][]{ + {0/6f,2/6f}, + {2/6f,1/6f}, + {4/6f,0/6f}, + {5/6f,2/6f}, + {6/6f,4/6f}, + {4/6f,5/6f}, + {2/6f,6/6f}, + {1/6f,4/6f}, + }) + ; + final float[][] points; + final int rightshift; + + Sampling(float[][] points) { + this.points = points; + rightshift = Integer.numberOfTrailingZeros(points.length); + } + + + } + + private Sampling sampling = Sampling.S_8ROCKS; + + public ThumbnailRescaleOp(int destWidth, int destHeight) { + this(DimensionConstrain.createAbsolutionDimension(destWidth, destHeight)); + } + + public ThumbnailRescaleOp(DimensionConstrain dimensionConstrain) { + super(dimensionConstrain); + } + + protected BufferedImage doFilter(BufferedImage src, BufferedImage dest, int dstWidth, int dstHeight) { + int numberOfChannels = ImageUtils.nrChannels(src); + BufferedImage out; + if (dest!=null && dstWidth==dest.getWidth() && dstHeight==dest.getHeight()){ + out = dest; + }else{ + + out = new BufferedImage(dstWidth, dstHeight, numberOfChannels==4?BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); + } + + float scaleX = src.getWidth()/(float)dstWidth; + float scaleY = src.getHeight()/(float)dstHeight; + + float[][] scaledSampling = new float[sampling.points.length][2]; + for (int i=0;i>>8; + g += rgb&0xff; + rgb = rgb>>>8; + r += rgb&0xff; + rgb = rgb>>>8; + a += rgb&0xff; + } + r = r>>sampling.rightshift; + g = g>>sampling.rightshift; + b = b>>sampling.rightshift; + a = a>>sampling.rightshift; + + int rgb = (a<<24)+(r<<16)+(g<<8)+b; + out.setRGB(dstX, dstY, rgb); + } + } + return out; + } + + public void setSampling(Sampling sampling) { + this.sampling = sampling; + } +} diff --git a/src/me/goddragon/teaseai/utils/libraries/imagescaling/TriangleFilter.java b/src/me/goddragon/teaseai/utils/libraries/imagescaling/TriangleFilter.java new file mode 100644 index 0000000..dada4e8 --- /dev/null +++ b/src/me/goddragon/teaseai/utils/libraries/imagescaling/TriangleFilter.java @@ -0,0 +1,37 @@ +/* + * Copyright 2013, Morten Nobel-Joergensen + * + * License: The BSD 3-Clause License + * http://opensource.org/licenses/BSD-3-Clause + */ +package me.goddragon.teaseai.utils.libraries.imagescaling; + +/** + * A triangle filter (also known as linear or bilinear filter). + */ +final class TriangleFilter implements ResampleFilter +{ + public float getSamplingRadius() { + return 1.0f; + } + + public final float apply(float value) + { + if (value < 0.0f) + { + value = -value; + } + if (value < 1.0f) + { + return 1.0f - value; + } + else + { + return 0.0f; + } + } + + public String getName() { + return "Triangle"; + } +} From d09eadce6bfdaa4df814b946a44d91c01935d629 Mon Sep 17 00:00:00 2001 From: Skier233 Date: Tue, 27 Nov 2018 14:55:28 -0500 Subject: [PATCH 2/4] Fixed domme image set, showTaggedImagesExact, and the scrolling issue in the main gui --- .../teaseai/api/picture/PictureHandler.java | 562 +++++++++--------- .../teaseai/api/picture/PictureSelector.java | 129 ++-- .../teaseai/api/picture/PictureSet.java | 230 +++---- src/me/goddragon/teaseai/gui/main/main.fxml | 10 +- 4 files changed, 498 insertions(+), 433 deletions(-) diff --git a/src/me/goddragon/teaseai/api/picture/PictureHandler.java b/src/me/goddragon/teaseai/api/picture/PictureHandler.java index 4c05ef6..88f4128 100644 --- a/src/me/goddragon/teaseai/api/picture/PictureHandler.java +++ b/src/me/goddragon/teaseai/api/picture/PictureHandler.java @@ -1,274 +1,290 @@ -package me.goddragon.teaseai.api.picture; - -import me.goddragon.teaseai.TeaseAI; -import me.goddragon.teaseai.utils.TeaseLogger; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FilenameFilter; -import java.io.IOException; -import java.math.BigInteger; -import java.net.URISyntaxException; -import java.security.MessageDigest; -import java.util.*; -import java.util.logging.Level; - -public class PictureHandler { - - public static final PictureHandler handler = new PictureHandler(); - private HashMap uniquePictures; - private File[] folders; - - public PictureHandler() { - loadUniquePictures(); - } - - public static String getTeasePath() { - try { - return (new File(TeaseAI.class.getProtectionDomain().getCodeSource().getLocation().toURI())).getParent(); - } catch (URISyntaxException e) { - e.printStackTrace(); - } - return null; - } - - public static PictureHandler getHandler() { - return handler; - } - - public void setPictureFolders(File... folders) { - this.folders = folders; - if (uniquePictures != null) { - uniquePictures = null; - } - } - - public void setDefaultFolders() { - File normal = new File(getTeasePath() + "//images//normal"); - File liked = new File(getTeasePath() + "//images//liked"); - File loved = new File(getTeasePath() + "//images//loved"); - ArrayList defFiles = new ArrayList<>(); - - if (normal.exists()) { - defFiles.add(normal); - } - - if (liked.exists()) { - defFiles.add(liked); - } - - if (loved.exists()) { - defFiles.add(loved); - } - - folders = new File[defFiles.size()]; - folders = defFiles.toArray(folders); - } - - public File[] getFolders() { - if (folders == null) { - setDefaultFolders(); - } - - return folders.clone(); - } - - public static File getOrCreateFile(String path) { - File thisFile = new File(path); - new File(thisFile.getParent()).mkdirs(); - - try { - thisFile.createNewFile(); - } catch (IOException e) { - TeaseLogger.getLogger().log(Level.SEVERE, "File creation error: " + e.getMessage()); - } - - return thisFile; - } - - public static File moveFile(File file, String newPath) { - TaggedPicture thisPicture = new TaggedPicture(file); - thisPicture.move(newPath); - return thisPicture.getFile(); - } - - public void addUniquePicture(File unique) { - String fileMd5 = calculateMD5(unique); - - synchronized (uniquePictures) { - if (uniquePictures.containsKey(fileMd5)) { - TeaseLogger.getLogger().log(Level.SEVERE, "Tried to add unique image that already exists!"); - return; - } - - uniquePictures.put(fileMd5, unique); - } - } - - public void removeUniquePicture(File unique) { - String fileMd5 = calculateMD5(unique); - - synchronized (uniquePictures) { - if (!uniquePictures.containsKey(fileMd5)) { - TeaseLogger.getLogger().log(Level.SEVERE, "Tried to remove unique image that doesnt exist!"); - return; - } - - uniquePictures.remove(fileMd5); - } - } - - public List getTaggedPicturesExact(PictureTag... imageTags) { - return getTaggedPicturesExact(Arrays.asList(imageTags)); - } - - public List getTaggedPicturesExact(Collection imageTags) { - return getTaggedPicturesExact(null, imageTags); - } - - public ArrayList getTaggedPicturesExact(File folder, PictureTag... imageTags) { - return getTaggedPicturesExact(folder, Arrays.asList(imageTags)); - } - - public ArrayList getTaggedPicturesExact(File folder, String... imageTags) { - Collection pictureTags = new HashSet<>(); - - for (String tag : imageTags) { - pictureTags.add(PictureTag.valueOf(tag.toUpperCase())); - } - - return getTaggedPicturesExact(folder, pictureTags); - } - - public ArrayList getTaggedPicturesExact(File folder, Collection imageTags) { - ArrayList picturesWithTags = new ArrayList<>(); - - Collection files; - - if(folder != null && folder.list() != null && folder.list().length != 0) { - File[] fileArray = folder.listFiles(new FilenameFilter() { - public boolean accept(File dir, String name) { - return name.toLowerCase().endsWith(".jpg") || name.toLowerCase().endsWith(".png") || name.toLowerCase().endsWith(".gif"); - } - }); - - //Nothing found - if(fileArray == null) { - files = new ArrayList<>(); - } else { - files = Arrays.asList(fileArray); - } - } else { - files = uniquePictures.values(); - } - - synchronized (uniquePictures) { - for (File thisFile : files) { - TaggedPicture thisImage = new TaggedPicture(thisFile); - if (thisImage.hasTags(imageTags)) { - picturesWithTags.add(thisImage); - } - } - } - - if (picturesWithTags.size() == 0) { - return null; - } else { - return picturesWithTags; - } - } - - public boolean checkDuplicate(File isDupe) { - String fileMd5 = calculateMD5(isDupe); - - if(uniquePictures == null) { - loadUniquePictures(); - } - - synchronized (uniquePictures) { - if (uniquePictures.containsKey(fileMd5)) { - if (!uniquePictures.get(fileMd5).equals(isDupe)) { - return true; - } - } - } - - return false; - } - - public void loadUniquePictures() { - this.uniquePictures = new HashMap<>(); - - if (folders == null) { - setDefaultFolders(); - } - - synchronized (folders) { - for (File folder : folders) { - File[] files = folder.listFiles(new FilenameFilter() { - public boolean accept(File dir, String name) { - return name.toLowerCase().endsWith(".jpg") || name.toLowerCase().endsWith(".png") || name.toLowerCase().endsWith(".gif"); - } - }); - - for (File thisFile : files) { - String thisMd5 = calculateMD5(thisFile); - synchronized (uniquePictures) { - if (!uniquePictures.containsKey(thisMd5)) { - uniquePictures.put(thisMd5, thisFile); - } else { - TeaseLogger.getLogger().log(Level.WARNING, "Duplicate files: " + thisFile.getPath() + " and " + uniquePictures.get(thisMd5)); - } - } - } - } - } - } - - - /** - * calculateMD5 Internal method that will calculate the md5checksum for a file. - * Do not call this directly unless you know what you are doing! - **/ - public static String calculateMD5(File file) { - MessageDigest digest; - try { - digest = MessageDigest.getInstance("MD5"); - } catch (Exception e) { - TeaseLogger.getLogger().log(Level.SEVERE, "MD5 error: " + e.getMessage()); - return null; - } - - FileInputStream is; - try { - is = new FileInputStream(file); - } catch (Exception e) { - TeaseLogger.getLogger().log(Level.SEVERE, "MD5 error: " + e.getMessage()); - return null; - } - - byte[] buffer = new byte[8192]; - int read; - try { - while ((read = is.read(buffer)) > 0) { - digest.update(buffer, 0, read); - } - - byte[] md5sum = digest.digest(); - BigInteger bigInt = new BigInteger(1, md5sum); - String output = bigInt.toString(16); - output = String.format("%32s", output).replace(' ', '0'); - return output; - } catch (Exception e) { - TeaseLogger.getLogger().log(Level.SEVERE, "MD5 error: " + e.getMessage()); - return null; - } finally { - try { - is.close(); - } catch (Exception e) { - TeaseLogger.getLogger().log(Level.SEVERE, "MD5 error: " + e.getMessage()); - return null; - } - } - } +package me.goddragon.teaseai.api.picture; + +import me.goddragon.teaseai.TeaseAI; +import me.goddragon.teaseai.utils.TeaseLogger; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.math.BigInteger; +import java.net.URISyntaxException; +import java.security.MessageDigest; +import java.util.*; +import java.util.logging.Level; + +public class PictureHandler { + + public static final PictureHandler handler = new PictureHandler(); + private HashMap uniquePictures; + private File[] folders; + + public PictureHandler() { + loadUniquePictures(); + } + + public static String getTeasePath() { + try { + return (new File(TeaseAI.class.getProtectionDomain().getCodeSource().getLocation().toURI())).getParent(); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + return null; + } + + public static PictureHandler getHandler() { + return handler; + } + + public void setPictureFolders(File... folders) { + this.folders = folders; + if (uniquePictures != null) { + uniquePictures = null; + } + } + + public void setDefaultFolders() { + File normal = new File(getTeasePath() + "//images//normal"); + File liked = new File(getTeasePath() + "//images//liked"); + File loved = new File(getTeasePath() + "//images//loved"); + ArrayList defFiles = new ArrayList<>(); + + if (normal.exists()) { + defFiles.add(normal); + } + + if (liked.exists()) { + defFiles.add(liked); + } + + if (loved.exists()) { + defFiles.add(loved); + } + + folders = new File[defFiles.size()]; + folders = defFiles.toArray(folders); + } + + public File[] getFolders() { + if (folders == null) { + setDefaultFolders(); + } + + return folders.clone(); + } + + public static File getOrCreateFile(String path) { + File thisFile = new File(path); + new File(thisFile.getParent()).mkdirs(); + + try { + thisFile.createNewFile(); + } catch (IOException e) { + TeaseLogger.getLogger().log(Level.SEVERE, "File creation error: " + e.getMessage()); + } + + return thisFile; + } + + public static File moveFile(File file, String newPath) { + TaggedPicture thisPicture = new TaggedPicture(file); + thisPicture.move(newPath); + return thisPicture.getFile(); + } + + public void addUniquePicture(File unique) { + String fileMd5 = calculateMD5(unique); + + synchronized (uniquePictures) { + if (uniquePictures.containsKey(fileMd5)) { + TeaseLogger.getLogger().log(Level.SEVERE, "Tried to add unique image that already exists!"); + return; + } + + uniquePictures.put(fileMd5, unique); + } + } + + public void removeUniquePicture(File unique) { + String fileMd5 = calculateMD5(unique); + + synchronized (uniquePictures) { + if (!uniquePictures.containsKey(fileMd5)) { + TeaseLogger.getLogger().log(Level.SEVERE, "Tried to remove unique image that doesnt exist!"); + return; + } + + uniquePictures.remove(fileMd5); + } + } + + public List getTaggedPicturesExact(PictureTag... imageTags) { + return getTaggedPicturesExact(null, Arrays.asList(imageTags)); + } + + public List getTaggedPicturesExact(DressState dressState, Collection imageTags) { + return getTaggedPicturesExact(null, imageTags, dressState); + } + + public ArrayList getTaggedPicturesExact(File folder, DressState dressState, PictureTag... imageTags) { + return getTaggedPicturesExact(folder, Arrays.asList(imageTags), dressState); + } + + public ArrayList getTaggedPicturesExact(File folder, String... imageTags) { + Collection pictureTags = new HashSet<>(); + DressState[] allDressStates = DressState.values(); + DressState dressState = null; + + for (String tag : imageTags) { + boolean checkPictureTag = true; + tag = tag.toLowerCase().replaceAll("tag", ""); + for (DressState dress: allDressStates) + { + if (tag.equalsIgnoreCase(dress.toString())) + { + dressState = DressState.valueOf(tag.toUpperCase()); + checkPictureTag = false; + } + } + if (checkPictureTag) + { + pictureTags.add(PictureTag.valueOf(tag.toUpperCase())); + } + } + + return getTaggedPicturesExact(folder, pictureTags, dressState); + } + + public ArrayList getTaggedPicturesExact(File folder, Collection imageTags, DressState dressState) { + ArrayList picturesWithTags = new ArrayList<>(); + + Collection files; + + if(folder != null && folder.list() != null && folder.list().length != 0) { + File[] fileArray = folder.listFiles(new FilenameFilter() { + public boolean accept(File dir, String name) { + return name.toLowerCase().endsWith(".jpg") || name.toLowerCase().endsWith(".png") || name.toLowerCase().endsWith(".gif"); + } + }); + + //Nothing found + if(fileArray == null) { + files = new ArrayList<>(); + } else { + files = Arrays.asList(fileArray); + } + } else { + files = uniquePictures.values(); + } + + for (File thisFile : files) { + TaggedPicture thisImage = new TaggedPicture(thisFile); + if (thisImage.hasTags(imageTags)) { + if (dressState == null || thisImage.getDressState().equals(dressState)) + { + picturesWithTags.add(thisImage); + } + } + } + + if (picturesWithTags.size() == 0) { + return null; + } else { + return picturesWithTags; + } + } + + public boolean checkDuplicate(File isDupe) { + String fileMd5 = calculateMD5(isDupe); + + if(uniquePictures == null) { + loadUniquePictures(); + } + + synchronized (uniquePictures) { + if (uniquePictures.containsKey(fileMd5)) { + if (!uniquePictures.get(fileMd5).equals(isDupe)) { + return true; + } + } + } + + return false; + } + + public void loadUniquePictures() { + this.uniquePictures = new HashMap<>(); + + if (folders == null) { + setDefaultFolders(); + } + + synchronized (folders) { + for (File folder : folders) { + File[] files = folder.listFiles(new FilenameFilter() { + public boolean accept(File dir, String name) { + return name.toLowerCase().endsWith(".jpg") || name.toLowerCase().endsWith(".png") || name.toLowerCase().endsWith(".gif"); + } + }); + + for (File thisFile : files) { + String thisMd5 = calculateMD5(thisFile); + synchronized (uniquePictures) { + if (!uniquePictures.containsKey(thisMd5)) { + uniquePictures.put(thisMd5, thisFile); + } else { + TeaseLogger.getLogger().log(Level.WARNING, "Duplicate files: " + thisFile.getPath() + " and " + uniquePictures.get(thisMd5)); + } + } + } + } + } + } + + + /** + * calculateMD5 Internal method that will calculate the md5checksum for a file. + * Do not call this directly unless you know what you are doing! + **/ + public static String calculateMD5(File file) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("MD5"); + } catch (Exception e) { + TeaseLogger.getLogger().log(Level.SEVERE, "MD5 error: " + e.getMessage()); + return null; + } + + FileInputStream is; + try { + is = new FileInputStream(file); + } catch (Exception e) { + TeaseLogger.getLogger().log(Level.SEVERE, "MD5 error: " + e.getMessage()); + return null; + } + + byte[] buffer = new byte[8192]; + int read; + try { + while ((read = is.read(buffer)) > 0) { + digest.update(buffer, 0, read); + } + + byte[] md5sum = digest.digest(); + BigInteger bigInt = new BigInteger(1, md5sum); + String output = bigInt.toString(16); + output = String.format("%32s", output).replace(' ', '0'); + return output; + } catch (Exception e) { + TeaseLogger.getLogger().log(Level.SEVERE, "MD5 error: " + e.getMessage()); + return null; + } finally { + try { + is.close(); + } catch (Exception e) { + TeaseLogger.getLogger().log(Level.SEVERE, "MD5 error: " + e.getMessage()); + return null; + } + } + } } \ No newline at end of file diff --git a/src/me/goddragon/teaseai/api/picture/PictureSelector.java b/src/me/goddragon/teaseai/api/picture/PictureSelector.java index 76d96fb..308256b 100644 --- a/src/me/goddragon/teaseai/api/picture/PictureSelector.java +++ b/src/me/goddragon/teaseai/api/picture/PictureSelector.java @@ -1,37 +1,92 @@ -package me.goddragon.teaseai.api.picture; - -import me.goddragon.teaseai.TeaseAI; -import me.goddragon.teaseai.api.chat.ChatParticipant; -import me.goddragon.teaseai.api.session.Session; - -import java.util.concurrent.TimeUnit; - -/** - * Created by GodDragon on 26.03.2018. - */ -public class PictureSelector { - - public TaggedPicture getPicture(Session session, ChatParticipant participant) { - if(participant.getPictureSet().getTaggedPictures().isEmpty()) { - return null; - } - - long minutesPassed = TimeUnit.MILLISECONDS.toMinutes(session.getRuntime()); - int preferredSessionDuration = TeaseAI.application.PREFERRED_SESSION_DURATION.getInt(); - double percentage = minutesPassed/preferredSessionDuration*100D; - - if(percentage > 80) { - return participant.getPictureSet().getRandomPictureForStates(DressState.NAKED, DressState.SEE_THROUGH); - } else if(percentage > 60) { - return participant.getPictureSet().getRandomPictureForStates(DressState.HANDS_COVERING); - } else if(percentage > 40) { - return participant.getPictureSet().getRandomPictureForStates(DressState.GARMENT_COVERING); - } else if(percentage > 20) { - return participant.getPictureSet().getRandomPictureForStates(DressState.HALF_DRESSED); - } else if(percentage > 10) { - return participant.getPictureSet().getRandomPictureForStates(DressState.FULLY_DRESSED, DressState.HALF_DRESSED); - } else { - return participant.getPictureSet().getRandomPicture(DressState.FULLY_DRESSED, PictureTag.FACE); - } - } -} +package me.goddragon.teaseai.api.picture; + +import me.goddragon.teaseai.TeaseAI; +import me.goddragon.teaseai.api.chat.ChatParticipant; +import me.goddragon.teaseai.api.session.Session; +import me.goddragon.teaseai.utils.TeaseLogger; + +import java.io.File; +import java.io.FilenameFilter; +import java.util.Random; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; + +/** + * Created by GodDragon on 26.03.2018. + */ +public class PictureSelector { + + public TaggedPicture getPicture(Session session, ChatParticipant participant) { + if(participant.getPictureSet().getTaggedPictures().isEmpty()) { + return null; + } + + long minutesPassed = TimeUnit.MILLISECONDS.toMinutes(session.getRuntime()); + int preferredSessionDuration = TeaseAI.application.PREFERRED_SESSION_DURATION.getInt(); + double percentage = minutesPassed/preferredSessionDuration*100D; + + if (percentage <= 10) + { + TaggedPicture toReturn = participant.getPictureSet().getRandomPicture(DressState.FULLY_DRESSED, PictureTag.FACE); + if (toReturn != null) + { + return toReturn; + } + } + if (percentage <= 20) + { + TaggedPicture toReturn = participant.getPictureSet().getRandomPictureForStates(DressState.FULLY_DRESSED, DressState.HALF_DRESSED); + if (toReturn != null) + { + return toReturn; + } + } + if (percentage <= 40) + { + TaggedPicture toReturn = participant.getPictureSet().getRandomPictureForStates(DressState.HALF_DRESSED); + if (toReturn != null) + { + return toReturn; + } + } + if (percentage <= 60) + { + TaggedPicture toReturn = participant.getPictureSet().getRandomPictureForStates(DressState.GARMENT_COVERING); + if (toReturn != null) + { + return toReturn; + } + } + if (percentage <= 80) + { + TaggedPicture toReturn = participant.getPictureSet().getRandomPictureForStates(DressState.HANDS_COVERING); + if (toReturn != null) + { + return toReturn; + } + } + TaggedPicture toReturn = participant.getPictureSet().getRandomPictureForStates(DressState.NAKED, DressState.SEE_THROUGH); + if (toReturn != null) + { + return toReturn; + } + else + { + File setFolder = participant.getPictureSet().getFolder(); + File[] locFiles = setFolder.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return (name.toLowerCase().endsWith(".jpg") || name.toLowerCase().endsWith(".png") || name.toLowerCase().endsWith(".gif")); + } + }); + if (locFiles.length == 0) + { + TeaseLogger.getLogger().log(Level.SEVERE, "Image set selected has no images!!"); + return null; + } + + Random random = new Random(); + return new TaggedPicture(locFiles[random.nextInt(locFiles.length)]); + } + } +} diff --git a/src/me/goddragon/teaseai/api/picture/PictureSet.java b/src/me/goddragon/teaseai/api/picture/PictureSet.java index 121723e..4e6d031 100644 --- a/src/me/goddragon/teaseai/api/picture/PictureSet.java +++ b/src/me/goddragon/teaseai/api/picture/PictureSet.java @@ -1,115 +1,115 @@ -package me.goddragon.teaseai.api.picture; - -import me.goddragon.teaseai.utils.RandomUtils; -import me.goddragon.teaseai.utils.TeaseLogger; - -import java.io.*; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.logging.Level; - -/** - * Created by GodDragon on 26.03.2018. - */ -public class PictureSet { - - private final List taggedPictures = new ArrayList<>(); - - public PictureSet(File folder) { - TagsFile tagFile = TagsFile.getTagsFile(folder); - - /*for (File file : folder.listFiles()) { - if (file.isFile() && file.getName().endsWith(".txt")) { - tagFile = file; - break; - } - }*/ - - if(tagFile == null) { - TeaseLogger.getLogger().log(Level.SEVERE, "Folder '" + folder.getAbsolutePath() + "' is missing a tags file."); - return; - } - - for(File taggedFile : tagFile.getTaggedFiles()) { - taggedPictures.add(new TaggedPicture(taggedFile)); - } - } - - public TaggedPicture getRandomPictureForStates(DressState... dressStates) { - return getRandomPictureForTagStates(taggedPictures, Arrays.asList(dressStates), new ArrayList<>()); - } - - public TaggedPicture getRandomPicture(DressState dressState, PictureTag... pictureTags) { - return getRandomPictureForTagStates(Arrays.asList(new DressState[] {dressState}), Arrays.asList(pictureTags)); - } - - public TaggedPicture getRandomPicture(List taggedPictures, PictureTag... pictureTags) { - return getRandomPicture(taggedPictures, Arrays.asList(pictureTags)); - } - - public TaggedPicture getRandomPicture(List taggedPictures, List pictureTags) { - return getRandomPictureForTagStates(taggedPictures, new ArrayList<>(), pictureTags); - } - - public TaggedPicture getRandomPictureForTagStates(List dressStates, List pictureTags) { - return getRandomPictureForTagStates(taggedPictures, dressStates, pictureTags); - } - - - public TaggedPicture getRandomPictureForTagStates(List taggedPictures, List dressStates, List pictureTags) { - List validPictures = new ArrayList<>(); - - pictureLoop: - for (TaggedPicture taggedPicture : taggedPictures) { - //Remove all pictures that don't have a specific tag - for (PictureTag pictureTag : pictureTags) { - if (pictureTag != null && !taggedPicture.hasTag(pictureTag)) { - continue pictureLoop; - } - } - - //If we are supposed to search for a specific dress state we check whether our picture fits those guidelines - if(!dressStates.isEmpty() && !dressStates.contains(taggedPicture.getDressState())) { - continue pictureLoop; - } - - //If we reach this point the image is good to display - validPictures.add(taggedPicture); - } - - if (validPictures.isEmpty()) { - //Find the dress state in the list which shows least - DressState lowestDressState = null; - for (DressState dressState : dressStates) { - if (lowestDressState == null || lowestDressState.getRank() > dressState.getRank()) { - lowestDressState = dressState; - } - } - - DressState lowerDressState = lowestDressState.getNextLowerRank(); - - //Try finding an alternative image that shows less - if (lowerDressState != null) { - return getRandomPictureForTagStates(Arrays.asList(new DressState[] {lowerDressState}), pictureTags); - } - - //We don't have any pictures to show anyway - if (taggedPictures.isEmpty()) { - return null; - } - //Okay we have been beaten. Show a random image - else { - return taggedPictures.get(RandomUtils.randInt(0, taggedPictures.size() - 1)); - } - } - - return validPictures.get(RandomUtils.randInt(0, validPictures.size() - 1)); - } - - - public Collection getTaggedPictures() { - return taggedPictures; - } -} +package me.goddragon.teaseai.api.picture; + +import me.goddragon.teaseai.utils.RandomUtils; +import me.goddragon.teaseai.utils.TeaseLogger; + +import java.io.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.logging.Level; + +/** + * Created by GodDragon on 26.03.2018. + */ +public class PictureSet { + + private final List taggedPictures = new ArrayList<>(); + private final File folder; + + public PictureSet(File folder) { + this.folder = folder; + TagsFile tagFile = TagsFile.getTagsFile(folder); + + if(tagFile == null) { + TeaseLogger.getLogger().log(Level.SEVERE, "Folder '" + folder.getAbsolutePath() + "' is missing a tags file."); + return; + } + + for(File taggedFile : tagFile.getTaggedFiles()) { + taggedPictures.add(new TaggedPicture(taggedFile)); + } + } + + public TaggedPicture getRandomPictureForStates(DressState... dressStates) { + return getRandomPictureForTagStates(taggedPictures, Arrays.asList(dressStates), new ArrayList<>()); + } + + public TaggedPicture getRandomPicture(DressState dressState, PictureTag... pictureTags) { + return getRandomPictureForTagStates(Arrays.asList(new DressState[] {dressState}), Arrays.asList(pictureTags)); + } + + public TaggedPicture getRandomPicture(List taggedPictures, PictureTag... pictureTags) { + return getRandomPicture(taggedPictures, Arrays.asList(pictureTags)); + } + + public TaggedPicture getRandomPicture(List taggedPictures, List pictureTags) { + return getRandomPictureForTagStates(taggedPictures, new ArrayList<>(), pictureTags); + } + + public TaggedPicture getRandomPictureForTagStates(List dressStates, List pictureTags) { + return getRandomPictureForTagStates(taggedPictures, dressStates, pictureTags); + } + + + public TaggedPicture getRandomPictureForTagStates(List taggedPictures, List dressStates, List pictureTags) { + List validPictures = new ArrayList<>(); + + pictureLoop: + for (TaggedPicture taggedPicture : taggedPictures) { + //Remove all pictures that don't have a specific tag + for (PictureTag pictureTag : pictureTags) { + if (pictureTag != null && !taggedPicture.hasTag(pictureTag)) { + continue pictureLoop; + } + } + + //If we are supposed to search for a specific dress state we check whether our picture fits those guidelines + if(!dressStates.isEmpty() && !dressStates.contains(taggedPicture.getDressState())) { + continue pictureLoop; + } + + //If we reach this point the image is good to display + validPictures.add(taggedPicture); + } + + if (validPictures.isEmpty()) { + //Find the dress state in the list which shows least + DressState lowestDressState = null; + for (DressState dressState : dressStates) { + if (lowestDressState == null || lowestDressState.getRank() > dressState.getRank()) { + lowestDressState = dressState; + } + } + + DressState lowerDressState = lowestDressState.getNextLowerRank(); + + //Try finding an alternative image that shows less + if (lowerDressState != null) { + return getRandomPictureForTagStates(Arrays.asList(new DressState[] {lowerDressState}), pictureTags); + } + + //We don't have any pictures to show anyway + if (taggedPictures.isEmpty()) { + return null; + } + //Okay we have been beaten. Show a random image + else { + return taggedPictures.get(RandomUtils.randInt(0, taggedPictures.size() - 1)); + } + } + + return validPictures.get(RandomUtils.randInt(0, validPictures.size() - 1)); + } + + + public Collection getTaggedPictures() { + return taggedPictures; + } + + public File getFolder() + { + return folder; + } +} diff --git a/src/me/goddragon/teaseai/gui/main/main.fxml b/src/me/goddragon/teaseai/gui/main/main.fxml index 977cb9c..e88e1b8 100644 --- a/src/me/goddragon/teaseai/gui/main/main.fxml +++ b/src/me/goddragon/teaseai/gui/main/main.fxml @@ -1,11 +1,5 @@ - - - - - - @@ -24,7 +18,7 @@ - + @@ -53,7 +47,7 @@ - + From e5c55f72bbb204692d4e4f65eb99b5202395a157 Mon Sep 17 00:00:00 2001 From: Skier233 Date: Tue, 27 Nov 2018 16:49:06 -0500 Subject: [PATCH 3/4] Fixed minor issue with domme images. All is working now. --- .../teaseai/api/chat/ChatParticipant.java | 737 +++++++++--------- .../teaseai/api/picture/PictureSelector.java | 9 +- .../teaseai/api/picture/PictureSet.java | 2 +- .../goddragon/teaseai/utils/RandomUtils.java | 129 ++- 4 files changed, 444 insertions(+), 433 deletions(-) diff --git a/src/me/goddragon/teaseai/api/chat/ChatParticipant.java b/src/me/goddragon/teaseai/api/chat/ChatParticipant.java index 8a8c077..cbb6b79 100644 --- a/src/me/goddragon/teaseai/api/chat/ChatParticipant.java +++ b/src/me/goddragon/teaseai/api/chat/ChatParticipant.java @@ -1,363 +1,374 @@ -package me.goddragon.teaseai.api.chat; - -import javafx.scene.paint.Color; -import javafx.scene.text.Font; -import javafx.scene.text.FontWeight; -import javafx.scene.text.Text; -import me.goddragon.teaseai.TeaseAI; -import me.goddragon.teaseai.api.chat.response.Response; -import me.goddragon.teaseai.api.chat.response.ResponseHandler; -import me.goddragon.teaseai.api.chat.vocabulary.VocabularyHandler; -import me.goddragon.teaseai.api.media.MediaHandler; -import me.goddragon.teaseai.api.picture.PictureSet; -import me.goddragon.teaseai.api.picture.TaggedPicture; -import me.goddragon.teaseai.api.session.Session; -import me.goddragon.teaseai.utils.RandomUtils; -import me.goddragon.teaseai.utils.TeaseLogger; - -import java.io.File; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.logging.Level; - -/** - * Created by GodDragon on 22.03.2018. - */ -public class ChatParticipant { - - private static int latestId = 0; - - private int id; - private String name; - private Contact contact; - private SenderType type; - private boolean active = false; - private TypeSpeed typeSpeed; - private Color chatColor; - - private PictureSet pictureSet; - - public ChatParticipant(SenderType type, Contact contact) { - this(latestId + 1, type, contact); - } - - public ChatParticipant(String name, SenderType type) { - this(latestId + 1, name, type, null); - } - - public ChatParticipant(int id, SenderType type, Contact contact) { - this(id, contact.NAME.getValue(), type, contact); - } - - public ChatParticipant(int id, String name, SenderType type, Contact contact) { - this.id = id; - - //Update the latest id - latestId = Math.max(id, latestId); - - this.name = name; - this.type = type; - this.contact = contact; - this.typeSpeed = ChatHandler.getHandler().getTypeSpeed(); - - switch (id) { - case 0: - chatColor = Color.DARKCYAN; - break; - case 1: - chatColor = Color.RED; - break; - case 2: - chatColor = Color.ORANGE; - break; - case 3: - chatColor = Color.LIGHTGREEN; - break; - case 4: - chatColor = Color.MEDIUMVIOLETRED; - break; - case 5: - chatColor = Color.TEAL; - break; - default: - chatColor = Color.SALMON; - break; - } - - if (type == SenderType.SUB) { - active = true; - } - - choosePictureSet(); - } - - public ChatParticipant(int id, String name, SenderType type, Color chatColor, Contact contact) { - this.id = id; - this.name = name; - this.type = type; - this.chatColor = chatColor; - - if (type == SenderType.SUB) { - active = true; - } - - choosePictureSet(); - } - - public void sendJoin() { - sendInteract("joined"); - } - - public void sendLeave() { - sendInteract("left"); - } - - public void sendInteract(String type) { - Text nameText = new Text(name + " "); - nameText.setFont(Font.font(null, FontWeight.BOLD, TeaseAI.application.CHAT_TEXT_SIZE.getDouble() + 2)); - nameText.setFill(chatColor); - - Text messageText = new Text(type + " the chat room."); - messageText.setFill(Color.AQUA); - messageText.setFont(Font.font(null, FontWeight.BOLD, TeaseAI.application.CHAT_TEXT_SIZE.getDouble() + 2)); - - ChatHandler.getHandler().addLine(nameText, messageText); - } - - public void sendMessage(String message) { - sendMessage(message, ChatHandler.getHandler().getMillisToPause(message)); - } - - public void sendMessage(String message, int secondsToWait) { - sendMessage(message, secondsToWait * 1000L); - } - - public void sendMessage(String message, long millisToWait) { - //We need to wait BEFORE we replace the vocabularies. Otherwise any code triggered by the vocab will execute before the message is send - startTyping(message); - - //Replace all vocabularies - message = VocabularyHandler.getHandler().replaceAllVocabularies(message); - - Text messageText = new Text(message); - messageText.setFont(Font.font(null, FontWeight.NORMAL, TeaseAI.application.CHAT_TEXT_SIZE.getDouble())); - - sendMessage(message, messageText, millisToWait); - } - - public void sendMessage(String rawMessage, Text message, long millisToWait) { - sendMessage(rawMessage, millisToWait, message); - } - - public void sendMessage(String rawMessage, long millisToWait, Text... messages) { - sendMessage(rawMessage, millisToWait, Arrays.asList(messages)); - } - - public void sendMessage(String rawMessage, long millisToWait, List messages) { - DateFormat dateFormat = new SimpleDateFormat("hh:mm a"); - - Text dateText = new Text(dateFormat.format(new Date()) + " "); - dateText.setFill(Color.DARKGRAY); - dateText.setFont(Font.font(null, FontWeight.MEDIUM, TeaseAI.application.CHAT_TEXT_SIZE.getDouble())); - Text text = new Text(name + ": "); - - text.setFill(chatColor); - - text.setFont(Font.font(null, FontWeight.BOLD, TeaseAI.application.CHAT_TEXT_SIZE.getDouble() + 1)); - //Check whether we can find a response fitting right now - if (type == SenderType.SUB) { - Response response = ResponseHandler.getHandler().checkMessageForResponse(rawMessage); - if (response != null) { - //Set the message of the response so we know what triggered it later on - response.setMessage(rawMessage); - - //Queue the response so we can call it later - ResponseHandler.getHandler().addQueuedResponse(response); - } - } else { - //If the dom sends a message we will check for queued responses that need to be handle before continuing - - //This should always run on the script thread but maybe I am stupid so we will catch this here - if (Thread.currentThread() != TeaseAI.application.getScriptThread()) { - throw new IllegalStateException("Dom can only send messages on the script thread"); - } - - Response queuedResponse = ResponseHandler.getHandler().getLatestQueuedResponse(); - if (queuedResponse != null) { - ResponseHandler.getHandler().removeQueuedResponse(queuedResponse); - - //If this returns true the trigger was successful and we won't send the current message that was supposed to be sent - if (queuedResponse.trigger()) { - return; - } - } - } - - List lineMessages = new ArrayList<>(); - lineMessages.add(dateText); - lineMessages.add(text); - lineMessages.addAll(messages); - ChatHandler.getHandler().addLine(lineMessages); - - if (type != SenderType.SUB && !MediaHandler.getHandler().isImagesLocked() && pictureSet != null) { - Session session = TeaseAI.application.getSession(); - TaggedPicture taggedPicture = session.getActivePersonality().getPictureSelector().getPicture(session, this); - if (taggedPicture != null) { - MediaHandler.getHandler().showPicture(session.getActivePersonality().getPictureSelector().getPicture(session, this).getFile()); - } - } - - //Wait some time after this message before continuing (only if it is not the sub who send the message) - if (millisToWait > 0 && type != SenderType.SUB) { - TeaseAI.application.sleepPossibleScripThread(millisToWait); - } - } - - public Answer sendInput(String message) { - return sendInput(message, 0); - } - - public Answer sendInput(String message, int timeoutSeconds) { - return sendInput(message, new Answer(timeoutSeconds)); - } - - public Answer sendInput(String message, Answer answer) { - //No waiting here because we will wait later on anyway - sendMessage(message, 0); - - ChatHandler.getHandler().setCurrentCallback(answer); - - //Reset timeout (normally the answer is a new object, but we don't know whether they might reuse an old answer) - answer.setTimeout(false); - - //Reset the latest answer message - answer.setAnswer(null); - answer.setStartedAt(System.currentTimeMillis()); - - //Wait for answer - TeaseAI.application.waitPossibleScripThread(answer.getMillisTimeout()); - answer.checkTimeout(); - - return answer; - } - - private void startTyping(String message) { - if (type == SenderType.SUB) { - return; - } - - long millisToWait = typeSpeed.getTypeDuration(message); - - if (millisToWait > 0) { - Text text = new Text(name + " is typing..."); - text.setFill(Color.AQUA); - text.setFont(Font.font(null, FontWeight.BOLD, TeaseAI.application.CHAT_TEXT_SIZE.getDouble() + 2)); - ChatHandler.getHandler().addTemporaryMessage(text); - - TeaseAI.application.sleepPossibleScripThread(millisToWait, true); - - ChatHandler.getHandler().removeTemporaryMessage(text); - } - } - - public int getId() { - return id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public SenderType getType() { - return type; - } - - public void setType(SenderType type) { - this.type = type; - } - - public boolean isActive() { - return active; - } - - public void setActive(boolean active) { - if (type == SenderType.SUB) { - throw new IllegalStateException("Sub cannot be inactive in chat"); - } - - this.active = active; - } - - public TypeSpeed getTypeSpeed() { - return typeSpeed; - } - - public void setTypeSpeed(TypeSpeed typeSpeed) { - this.typeSpeed = typeSpeed; - } - - public Color getChatColor() { - return chatColor; - } - - public void setChatColor(Color chatColor) { - this.chatColor = chatColor; - } - - public Contact getContact() { - return contact; - } - - public void choosePictureSet() { - File imageFolder = contact == null? null : contact.getImageFolder(); - if (imageFolder == null || !imageFolder.exists()) { - return; - } - - List pictureSets = new ArrayList<>(); - for (File file : imageFolder.listFiles((current, name) -> new File(current, name).isDirectory())) { - PictureSet pictureSet = new PictureSet(file); - - //No pictures => ignore the set - if(pictureSet.getTaggedPictures().isEmpty()) { - continue; - } - - pictureSets.add(pictureSet); - } - - this.pictureSet = null; - TeaseLogger.getLogger().log(Level.INFO, "Loaded " + pictureSets.size() + " picture sets for " + name); - - if (!pictureSets.isEmpty()) { - int loops = 0; - while (this.pictureSet == null && loops < 20) { - PictureSet pictureSet = pictureSets.get(RandomUtils.randInt(0, pictureSets.size() - 1)); - - if (!pictureSet.getTaggedPictures().isEmpty()) { - this.pictureSet = pictureSet; - } - - loops++; - } - } - } - - public PictureSet getPictureSet() { - return pictureSet; - } - - public void setPictureSet(PictureSet pictureSet) { - this.pictureSet = pictureSet; - } - - @Override - public String toString() { - return name; - } -} +package me.goddragon.teaseai.api.chat; + +import javafx.scene.paint.Color; +import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; +import javafx.scene.text.Text; +import me.goddragon.teaseai.TeaseAI; +import me.goddragon.teaseai.api.chat.response.Response; +import me.goddragon.teaseai.api.chat.response.ResponseHandler; +import me.goddragon.teaseai.api.chat.vocabulary.VocabularyHandler; +import me.goddragon.teaseai.api.media.MediaHandler; +import me.goddragon.teaseai.api.picture.PictureSet; +import me.goddragon.teaseai.api.picture.TaggedPicture; +import me.goddragon.teaseai.api.session.Session; +import me.goddragon.teaseai.utils.RandomUtils; +import me.goddragon.teaseai.utils.TeaseLogger; + +import java.io.File; +import java.io.FilenameFilter; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.logging.Level; + +/** + * Created by GodDragon on 22.03.2018. + */ +public class ChatParticipant { + + private static int latestId = 0; + + private int id; + private String name; + private Contact contact; + private SenderType type; + private boolean active = false; + private TypeSpeed typeSpeed; + private Color chatColor; + + private PictureSet pictureSet; + + public ChatParticipant(SenderType type, Contact contact) { + this(latestId + 1, type, contact); + } + + public ChatParticipant(String name, SenderType type) { + this(latestId + 1, name, type, null); + } + + public ChatParticipant(int id, SenderType type, Contact contact) { + this(id, contact.NAME.getValue(), type, contact); + } + + public ChatParticipant(int id, String name, SenderType type, Contact contact) { + this.id = id; + + //Update the latest id + latestId = Math.max(id, latestId); + + this.name = name; + this.type = type; + this.contact = contact; + this.typeSpeed = ChatHandler.getHandler().getTypeSpeed(); + + switch (id) { + case 0: + chatColor = Color.DARKCYAN; + break; + case 1: + chatColor = Color.RED; + break; + case 2: + chatColor = Color.ORANGE; + break; + case 3: + chatColor = Color.LIGHTGREEN; + break; + case 4: + chatColor = Color.MEDIUMVIOLETRED; + break; + case 5: + chatColor = Color.TEAL; + break; + default: + chatColor = Color.SALMON; + break; + } + + if (type == SenderType.SUB) { + active = true; + } + + choosePictureSet(); + } + + public ChatParticipant(int id, String name, SenderType type, Color chatColor, Contact contact) { + this.id = id; + this.name = name; + this.type = type; + this.chatColor = chatColor; + + if (type == SenderType.SUB) { + active = true; + } + + choosePictureSet(); + } + + public void sendJoin() { + sendInteract("joined"); + } + + public void sendLeave() { + sendInteract("left"); + } + + public void sendInteract(String type) { + Text nameText = new Text(name + " "); + nameText.setFont(Font.font(null, FontWeight.BOLD, TeaseAI.application.CHAT_TEXT_SIZE.getDouble() + 2)); + nameText.setFill(chatColor); + + Text messageText = new Text(type + " the chat room."); + messageText.setFill(Color.AQUA); + messageText.setFont(Font.font(null, FontWeight.BOLD, TeaseAI.application.CHAT_TEXT_SIZE.getDouble() + 2)); + + ChatHandler.getHandler().addLine(nameText, messageText); + } + + public void sendMessage(String message) { + sendMessage(message, ChatHandler.getHandler().getMillisToPause(message)); + } + + public void sendMessage(String message, int secondsToWait) { + sendMessage(message, secondsToWait * 1000L); + } + + public void sendMessage(String message, long millisToWait) { + //We need to wait BEFORE we replace the vocabularies. Otherwise any code triggered by the vocab will execute before the message is send + startTyping(message); + + //Replace all vocabularies + message = VocabularyHandler.getHandler().replaceAllVocabularies(message); + + Text messageText = new Text(message); + messageText.setFont(Font.font(null, FontWeight.NORMAL, TeaseAI.application.CHAT_TEXT_SIZE.getDouble())); + + sendMessage(message, messageText, millisToWait); + } + + public void sendMessage(String rawMessage, Text message, long millisToWait) { + sendMessage(rawMessage, millisToWait, message); + } + + public void sendMessage(String rawMessage, long millisToWait, Text... messages) { + sendMessage(rawMessage, millisToWait, Arrays.asList(messages)); + } + + public void sendMessage(String rawMessage, long millisToWait, List messages) { + DateFormat dateFormat = new SimpleDateFormat("hh:mm a"); + + Text dateText = new Text(dateFormat.format(new Date()) + " "); + dateText.setFill(Color.DARKGRAY); + dateText.setFont(Font.font(null, FontWeight.MEDIUM, TeaseAI.application.CHAT_TEXT_SIZE.getDouble())); + Text text = new Text(name + ": "); + + text.setFill(chatColor); + + text.setFont(Font.font(null, FontWeight.BOLD, TeaseAI.application.CHAT_TEXT_SIZE.getDouble() + 1)); + //Check whether we can find a response fitting right now + if (type == SenderType.SUB) { + Response response = ResponseHandler.getHandler().checkMessageForResponse(rawMessage); + if (response != null) { + //Set the message of the response so we know what triggered it later on + response.setMessage(rawMessage); + + //Queue the response so we can call it later + ResponseHandler.getHandler().addQueuedResponse(response); + } + } else { + //If the dom sends a message we will check for queued responses that need to be handle before continuing + + //This should always run on the script thread but maybe I am stupid so we will catch this here + if (Thread.currentThread() != TeaseAI.application.getScriptThread()) { + throw new IllegalStateException("Dom can only send messages on the script thread"); + } + + Response queuedResponse = ResponseHandler.getHandler().getLatestQueuedResponse(); + if (queuedResponse != null) { + ResponseHandler.getHandler().removeQueuedResponse(queuedResponse); + + //If this returns true the trigger was successful and we won't send the current message that was supposed to be sent + if (queuedResponse.trigger()) { + return; + } + } + } + + List lineMessages = new ArrayList<>(); + lineMessages.add(dateText); + lineMessages.add(text); + lineMessages.addAll(messages); + ChatHandler.getHandler().addLine(lineMessages); + + //TeaseLogger.getLogger().log(Level.INFO, "Current PictureSet:" + pictureSet); + if (type != SenderType.SUB && !MediaHandler.getHandler().isImagesLocked() && pictureSet != null) { + Session session = TeaseAI.application.getSession(); + TaggedPicture taggedPicture = session.getActivePersonality().getPictureSelector().getPicture(session, this); + if (taggedPicture != null) { + MediaHandler.getHandler().showPicture(session.getActivePersonality().getPictureSelector().getPicture(session, this).getFile()); + } + } + + //Wait some time after this message before continuing (only if it is not the sub who send the message) + if (millisToWait > 0 && type != SenderType.SUB) { + TeaseAI.application.sleepPossibleScripThread(millisToWait); + } + } + + public Answer sendInput(String message) { + return sendInput(message, 0); + } + + public Answer sendInput(String message, int timeoutSeconds) { + return sendInput(message, new Answer(timeoutSeconds)); + } + + public Answer sendInput(String message, Answer answer) { + //No waiting here because we will wait later on anyway + sendMessage(message, 0); + + ChatHandler.getHandler().setCurrentCallback(answer); + + //Reset timeout (normally the answer is a new object, but we don't know whether they might reuse an old answer) + answer.setTimeout(false); + + //Reset the latest answer message + answer.setAnswer(null); + answer.setStartedAt(System.currentTimeMillis()); + + //Wait for answer + TeaseAI.application.waitPossibleScripThread(answer.getMillisTimeout()); + answer.checkTimeout(); + + return answer; + } + + private void startTyping(String message) { + if (type == SenderType.SUB) { + return; + } + + long millisToWait = typeSpeed.getTypeDuration(message); + + if (millisToWait > 0) { + Text text = new Text(name + " is typing..."); + text.setFill(Color.AQUA); + text.setFont(Font.font(null, FontWeight.BOLD, TeaseAI.application.CHAT_TEXT_SIZE.getDouble() + 2)); + ChatHandler.getHandler().addTemporaryMessage(text); + + TeaseAI.application.sleepPossibleScripThread(millisToWait, true); + + ChatHandler.getHandler().removeTemporaryMessage(text); + } + } + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public SenderType getType() { + return type; + } + + public void setType(SenderType type) { + this.type = type; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + if (type == SenderType.SUB) { + throw new IllegalStateException("Sub cannot be inactive in chat"); + } + + this.active = active; + } + + public TypeSpeed getTypeSpeed() { + return typeSpeed; + } + + public void setTypeSpeed(TypeSpeed typeSpeed) { + this.typeSpeed = typeSpeed; + } + + public Color getChatColor() { + return chatColor; + } + + public void setChatColor(Color chatColor) { + this.chatColor = chatColor; + } + + public Contact getContact() { + return contact; + } + + public void choosePictureSet() { + File imageFolder = contact == null? null : contact.getImageFolder(); + if (imageFolder == null || !imageFolder.exists()) { + return; + } + + List pictureSets = new ArrayList<>(); + for (File file : imageFolder.listFiles((current, name) -> new File(current, name).isDirectory())) { + PictureSet pictureSet = new PictureSet(file); + + //No pictures => ignore the set + if(pictureSet.getTaggedPictures().isEmpty() && pictureSet.getFolder().listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return (name.toLowerCase().endsWith(".jpg") || name.toLowerCase().endsWith(".png") || name.toLowerCase().endsWith(".gif")); + } + }).length == 0) { + continue; + } + + pictureSets.add(pictureSet); + } + + this.pictureSet = null; + TeaseLogger.getLogger().log(Level.INFO, "Loaded " + pictureSets.size() + " picture sets for " + name); + + if (!pictureSets.isEmpty()) { + int loops = 0; + while (this.pictureSet == null && loops < 20) { + PictureSet pictureSet = pictureSets.get(RandomUtils.randInt(0, pictureSets.size() - 1)); + if (!pictureSet.getTaggedPictures().isEmpty() || pictureSet.getFolder().listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return (name.toLowerCase().endsWith(".jpg") || name.toLowerCase().endsWith(".png") || name.toLowerCase().endsWith(".gif")); + } + }).length > 0) { + this.pictureSet = pictureSet; + } + + loops++; + } + } + } + + public PictureSet getPictureSet() { + return pictureSet; + } + + public void setPictureSet(PictureSet pictureSet) { + this.pictureSet = pictureSet; + } + + @Override + public String toString() { + return name; + } +} diff --git a/src/me/goddragon/teaseai/api/picture/PictureSelector.java b/src/me/goddragon/teaseai/api/picture/PictureSelector.java index 308256b..83c47d8 100644 --- a/src/me/goddragon/teaseai/api/picture/PictureSelector.java +++ b/src/me/goddragon/teaseai/api/picture/PictureSelector.java @@ -17,10 +17,15 @@ public class PictureSelector { public TaggedPicture getPicture(Session session, ChatParticipant participant) { - if(participant.getPictureSet().getTaggedPictures().isEmpty()) { + if(participant.getPictureSet().getTaggedPictures().isEmpty() && participant.getPictureSet().getFolder().listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return (name.toLowerCase().endsWith(".jpg") || name.toLowerCase().endsWith(".png") || name.toLowerCase().endsWith(".gif")); + } + }).length == 0) { return null; } - + long minutesPassed = TimeUnit.MILLISECONDS.toMinutes(session.getRuntime()); int preferredSessionDuration = TeaseAI.application.PREFERRED_SESSION_DURATION.getInt(); double percentage = minutesPassed/preferredSessionDuration*100D; diff --git a/src/me/goddragon/teaseai/api/picture/PictureSet.java b/src/me/goddragon/teaseai/api/picture/PictureSet.java index 4e6d031..fb65078 100644 --- a/src/me/goddragon/teaseai/api/picture/PictureSet.java +++ b/src/me/goddragon/teaseai/api/picture/PictureSet.java @@ -28,7 +28,7 @@ public PictureSet(File folder) { } for(File taggedFile : tagFile.getTaggedFiles()) { - taggedPictures.add(new TaggedPicture(taggedFile)); + taggedPictures.add(new TaggedPicture(taggedFile, true)); } } diff --git a/src/me/goddragon/teaseai/utils/RandomUtils.java b/src/me/goddragon/teaseai/utils/RandomUtils.java index 23cab8a..a8964f2 100644 --- a/src/me/goddragon/teaseai/utils/RandomUtils.java +++ b/src/me/goddragon/teaseai/utils/RandomUtils.java @@ -1,67 +1,62 @@ -package me.goddragon.teaseai.utils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ThreadLocalRandom; - -/** - * Created by GodDragon on 23.03.2018. - */ -public class RandomUtils { - - public static int randInt(int min, int max) { - if (min == max || min > max) { - return min; - } - - ArrayList list = new ArrayList<>(); - for (int i = min; i <= max; i++) { - list.add(new Integer(i)); - } - - Collections.shuffle(list); - - return list.get(0); - } - - public static Object getWinner(HashMap idChances) { - if(idChances.isEmpty()) { - return null; - } - - double maxChance = 0; - - for(Number chance : idChances.values()) { - if(chance instanceof Double) { - maxChance += (Double) chance; - } else if(chance instanceof Integer) { - maxChance += (Integer) chance; - } - } - - return getWinner(idChances, maxChance); - } - - - - public static Object getWinner(HashMap idChances, double maxChance) { - double randInt = ThreadLocalRandom.current().nextDouble(0, maxChance); - - double lastMax = 0; - for(Map.Entry mapEntry : idChances.entrySet()) { - if(mapEntry.getValue() instanceof Double) { - lastMax += (Double) mapEntry.getValue(); - } else if(mapEntry.getValue() instanceof Integer) { - lastMax += (Integer) mapEntry.getValue(); - } - - if(randInt <= lastMax) - return mapEntry.getKey(); - - } - //No Winner found - return null; - } -} +package me.goddragon.teaseai.utils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +/** + * Created by GodDragon on 23.03.2018. + */ +public class RandomUtils { + + public static int randInt(int min, int max) { + if (min == max || min > max) { + return min; + } + + //This is a better way to do random than using a list with shuffle + return new Random().nextInt(max - min + 1) + min; + } + + public static Object getWinner(HashMap idChances) { + if(idChances.isEmpty()) { + return null; + } + + double maxChance = 0; + + for(Number chance : idChances.values()) { + if(chance instanceof Double) { + maxChance += (Double) chance; + } else if(chance instanceof Integer) { + maxChance += (Integer) chance; + } + } + + return getWinner(idChances, maxChance); + } + + + + public static Object getWinner(HashMap idChances, double maxChance) { + double randInt = ThreadLocalRandom.current().nextDouble(0, maxChance); + + double lastMax = 0; + for(Map.Entry mapEntry : idChances.entrySet()) { + if(mapEntry.getValue() instanceof Double) { + lastMax += (Double) mapEntry.getValue(); + } else if(mapEntry.getValue() instanceof Integer) { + lastMax += (Integer) mapEntry.getValue(); + } + + if(randInt <= lastMax) + return mapEntry.getKey(); + + } + //No Winner found + return null; + } +} From 9aee1ba1ea1f8627842a1870895c48fecf509229 Mon Sep 17 00:00:00 2001 From: Skier233 Date: Tue, 27 Nov 2018 17:40:43 -0500 Subject: [PATCH 4/4] Media Tagger now correctly shows the correct original dress state of an image before it was loaded --- .../teaseai/gui/settings/MediaTagging.java | 676 +++++++++--------- 1 file changed, 338 insertions(+), 338 deletions(-) diff --git a/src/me/goddragon/teaseai/gui/settings/MediaTagging.java b/src/me/goddragon/teaseai/gui/settings/MediaTagging.java index fb7e746..3115c91 100644 --- a/src/me/goddragon/teaseai/gui/settings/MediaTagging.java +++ b/src/me/goddragon/teaseai/gui/settings/MediaTagging.java @@ -1,339 +1,339 @@ -package me.goddragon.teaseai.gui.settings; - -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; -import javafx.event.EventHandler; -import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; -import javafx.scene.Parent; -import javafx.scene.Scene; -import javafx.scene.control.*; -import javafx.scene.image.Image; -import javafx.scene.image.ImageView; -import javafx.scene.input.MouseEvent; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.ColumnConstraints; -import javafx.scene.layout.GridPane; -import javafx.scene.layout.Pane; -import javafx.stage.DirectoryChooser; -import javafx.stage.Stage; -import me.goddragon.teaseai.TeaseAI; -import me.goddragon.teaseai.api.picture.DressState; -import me.goddragon.teaseai.api.picture.PictureHandler; -import me.goddragon.teaseai.api.picture.PictureTag; -import me.goddragon.teaseai.api.picture.PictureTag.TagType; -import me.goddragon.teaseai.api.picture.TaggedPicture; - -import java.io.File; -import java.io.FilenameFilter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; - -public class MediaTagging { - - private final SettingsController settingsController; - - private Parent root; - - private static FXMLLoader loader; - - private Stage imageTaggingWindow; - - private File directory; - - private TaggedPicture[] files; - - private int currentFile; - - @FXML - private Label fileLabel; - - @FXML - private Button previousButton; - - @FXML - private Button nextButton; - - @FXML - private ImageView taggedImage; - - @FXML - private BorderPane imagePane; - - @FXML - private GridPane testGridPane; - - @FXML - private ColumnConstraints imageGridWidth; - - @FXML - private MenuButton bodyPartButton; - - @FXML - private MenuButton bodyTypeButton; - - @FXML - private MenuButton accessoriesButton; - - @FXML - private MenuButton viewButton; - - @FXML - private MenuButton actionButton; - - @FXML - private MenuButton peopleInvolvedButton; - - @FXML - private MenuButton categoryButton; - - @FXML - private MenuButton testMenuButton; - - @FXML - private MenuButton dressStateButton; - - private HashSet currentImageTags; - - private TaggedPicture currentTaggedPicture; - - private DressState currentDressState; - - private HashMap checkBoxes = new HashMap<>(); - - public MediaTagging(SettingsController settingsController) { - this.settingsController = settingsController; - } - - public static void create(SettingsController settingsController) { - MediaTagging tagger = new MediaTagging(settingsController); - loader = new FXMLLoader(TeaseAI.class.getResource("gui/settings/MediaTagger.fxml")); - loader.setController(tagger); - - try { - tagger.root = loader.load(); - } catch (IOException e) { - e.printStackTrace(); - } - - tagger.initiate(); - } - - public void initiate() { - settingsController.taggingFolderButton.setOnMouseClicked(new EventHandler() { - @Override - public void handle(MouseEvent event) { - DirectoryChooser chooser = new DirectoryChooser(); - chooser.setTitle("Select Tagging Folder"); - - String dir; - if (settingsController.taggingFolderText.getText() != null && new File(settingsController.taggingFolderText.getText()).exists()) { - dir = settingsController.taggingFolderText.getText(); - } else { - dir = System.getProperty("user.dir"); - } - - File defaultDirectory = new File(dir); - chooser.setInitialDirectory(defaultDirectory); - File selectedDirectory = chooser.showDialog(settingsController.stage); - - if (selectedDirectory != null) { - settingsController.taggingFolderText.setText(selectedDirectory.getPath()); - //Create new stage window - if (imageTaggingWindow != null) { - - } else { - imageTaggingWindow = new Stage(); - imageTaggingWindow.setTitle("Tease-AI Tagger"); - imageTaggingWindow.setScene(new Scene(root, 1280, 650)); - } - - directory = selectedDirectory; - - if(initiateNewStage()) { - imageTaggingWindow.show(); - } - } - } - }); - } - - public void setUpButtons() { - currentTaggedPicture = files[currentFile]; - currentImageTags = (HashSet) currentTaggedPicture.getTags().clone(); - currentDressState = currentTaggedPicture.getDressState(); - - if (currentImageTags == null) { - currentImageTags = new HashSet<>(); - } - - addTagsToButton(bodyPartButton, TagType.BODY_PART); - addTagsToButton(bodyTypeButton, TagType.BODY_TYPE); - addTagsToButton(categoryButton, TagType.CATEGORY); - addTagsToButton(peopleInvolvedButton, TagType.PEOPLE_INVOLVED); - addTagsToButton(actionButton, TagType.ACTION); - addTagsToButton(accessoriesButton, TagType.ACCESSORIES); - addTagsToButton(viewButton, TagType.VIEW); - - dressStateButton.getItems().clear(); - - DressState[] dressStateTags = DressState.values(); - ArrayList checkboxes = new ArrayList<>(); - - for (int i = 0; i < dressStateTags.length; i++) { - CheckBox thisCheckBox = new CheckBox(dressStateTags[i].getTagName().replace("Tag", "")); - checkboxes.add(thisCheckBox); - - if (currentDressState != null && currentDressState == dressStateTags[i]) { - thisCheckBox.setSelected(true); - } - - DressState thisTag = dressStateTags[i]; - thisCheckBox.selectedProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) { - if (newValue) { - currentDressState = thisTag; - for (CheckBox thisBox : checkboxes) { - if (!thisBox.equals(thisCheckBox)) { - thisBox.setSelected(false); - } - } - } else { - if (currentDressState.equals(thisTag)) { - currentDressState = null; - } - } - } - }); - - CustomMenuItem thisItem = new CustomMenuItem(thisCheckBox); - thisItem.setHideOnClick(false); - dressStateButton.getItems().add(thisItem); - } - } - - public void addTagsToButton(MenuButton button, TagType tagType) { - PictureTag[] viewTags = PictureTag.getPictureTagsByType(tagType); - if(button.getItems().isEmpty()) { - for (int i = 0; i < viewTags.length; i++) { - CheckBox thisCheckBox = new CheckBox(viewTags[i].getTagName().replace("Tag", "")); - checkBoxes.put(viewTags[i], thisCheckBox); - - PictureTag thisTag = viewTags[i]; - thisCheckBox.selectedProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) { - if (newValue) { - currentImageTags.add(thisTag); - } else { - currentImageTags.remove(thisTag); - } - } - }); - - CustomMenuItem thisItem = new CustomMenuItem(thisCheckBox); - thisItem.setHideOnClick(false); - button.getItems().add(thisItem); - } - } - - for(PictureTag pictureTag : viewTags) { - if(currentImageTags.contains(pictureTag)) { - checkBoxes.get(pictureTag).setSelected(true); - } else { - checkBoxes.get(pictureTag).setSelected(false); - } - } - } - - public boolean initiateNewStage() { - PictureHandler.getHandler().setDefaultFolders(); - taggedImage.setPreserveRatio(true); - Pane pane = new Pane(); - testGridPane.add(pane, 1, 1); - taggedImage.fitWidthProperty().bind(pane.widthProperty()); - taggedImage.fitHeightProperty().bind(pane.heightProperty()); - currentFile = 0; - - //File searchingDirectory = directory; - - File[] locFiles = directory.listFiles(new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - return (name.toLowerCase().endsWith(".jpg") || name.toLowerCase().endsWith(".png") || name.toLowerCase().endsWith(".gif")); - } - }); - - ArrayList taggedList = new ArrayList<>(); - - for (File thisFile : locFiles) { - TaggedPicture thisTaggedPic = new TaggedPicture(thisFile); - if (thisTaggedPic.getFile() != null) { - taggedList.add(thisTaggedPic); - } - } - - if(taggedList.isEmpty()) { - Alert alert = new Alert(Alert.AlertType.ERROR); - alert.setTitle("No images found"); - alert.setHeaderText(null); - alert.setContentText("There were no images found in the given folder."); - - alert.showAndWait(); - return false; - } - - files = taggedList.toArray(new TaggedPicture[] {}); - - fileLabel.setText("File " + (currentFile + 1) + " of " + files.length + " in " + directory.getPath()); - - Image image = new Image(files[currentFile].getFile().toURI().toString()); - - taggedImage.setImage(image); - setUpButtons(); - - //Store the current tags of the selected image too when closing - imageTaggingWindow.setOnCloseRequest(event -> { - currentTaggedPicture.setDressState(currentDressState); - currentTaggedPicture.setTags(currentImageTags); - }); - - nextButton.setOnMouseClicked(event -> { - currentTaggedPicture.setDressState(currentDressState); - currentTaggedPicture.setTags(currentImageTags); - - if ((currentFile + 1) < files.length) { - currentFile++; - } else { - currentFile = 0; - } - - taggedImage.setImage(new Image(files[currentFile].getFile().toURI().toString())); - setUpButtons(); - - fileLabel.setText("File " + (currentFile + 1) + " of " + files.length + " in " + directory.getPath()); - }); - - previousButton.setOnMouseClicked(event -> { - long currentMillis = System.currentTimeMillis(); - currentTaggedPicture.setDressState(currentDressState); - currentTaggedPicture.setTags(currentImageTags); - - if ((currentFile - 1) >= 0) { - currentFile--; - } else { - currentFile = files.length - 1; - } - - taggedImage.setImage(new Image(files[currentFile].getFile().toURI().toString())); - setUpButtons(); - fileLabel.setText("File " + (currentFile + 1) + " of " + files.length + " in " + directory.getPath()); - }); - - return true; - } - +package me.goddragon.teaseai.gui.settings; + +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.EventHandler; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.*; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Pane; +import javafx.stage.DirectoryChooser; +import javafx.stage.Stage; +import me.goddragon.teaseai.TeaseAI; +import me.goddragon.teaseai.api.picture.DressState; +import me.goddragon.teaseai.api.picture.PictureHandler; +import me.goddragon.teaseai.api.picture.PictureTag; +import me.goddragon.teaseai.api.picture.PictureTag.TagType; +import me.goddragon.teaseai.api.picture.TaggedPicture; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; + +public class MediaTagging { + + private final SettingsController settingsController; + + private Parent root; + + private static FXMLLoader loader; + + private Stage imageTaggingWindow; + + private File directory; + + private TaggedPicture[] files; + + private int currentFile; + + @FXML + private Label fileLabel; + + @FXML + private Button previousButton; + + @FXML + private Button nextButton; + + @FXML + private ImageView taggedImage; + + @FXML + private BorderPane imagePane; + + @FXML + private GridPane testGridPane; + + @FXML + private ColumnConstraints imageGridWidth; + + @FXML + private MenuButton bodyPartButton; + + @FXML + private MenuButton bodyTypeButton; + + @FXML + private MenuButton accessoriesButton; + + @FXML + private MenuButton viewButton; + + @FXML + private MenuButton actionButton; + + @FXML + private MenuButton peopleInvolvedButton; + + @FXML + private MenuButton categoryButton; + + @FXML + private MenuButton testMenuButton; + + @FXML + private MenuButton dressStateButton; + + private HashSet currentImageTags; + + private TaggedPicture currentTaggedPicture; + + private DressState currentDressState; + + private HashMap checkBoxes = new HashMap<>(); + + public MediaTagging(SettingsController settingsController) { + this.settingsController = settingsController; + } + + public static void create(SettingsController settingsController) { + MediaTagging tagger = new MediaTagging(settingsController); + loader = new FXMLLoader(TeaseAI.class.getResource("gui/settings/MediaTagger.fxml")); + loader.setController(tagger); + + try { + tagger.root = loader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + + tagger.initiate(); + } + + public void initiate() { + settingsController.taggingFolderButton.setOnMouseClicked(new EventHandler() { + @Override + public void handle(MouseEvent event) { + DirectoryChooser chooser = new DirectoryChooser(); + chooser.setTitle("Select Tagging Folder"); + + String dir; + if (settingsController.taggingFolderText.getText() != null && new File(settingsController.taggingFolderText.getText()).exists()) { + dir = settingsController.taggingFolderText.getText(); + } else { + dir = System.getProperty("user.dir"); + } + + File defaultDirectory = new File(dir); + chooser.setInitialDirectory(defaultDirectory); + File selectedDirectory = chooser.showDialog(settingsController.stage); + + if (selectedDirectory != null) { + settingsController.taggingFolderText.setText(selectedDirectory.getPath()); + //Create new stage window + if (imageTaggingWindow != null) { + + } else { + imageTaggingWindow = new Stage(); + imageTaggingWindow.setTitle("Tease-AI Tagger"); + imageTaggingWindow.setScene(new Scene(root, 1280, 650)); + } + + directory = selectedDirectory; + + if(initiateNewStage()) { + imageTaggingWindow.show(); + } + } + } + }); + } + + public void setUpButtons() { + currentTaggedPicture = files[currentFile]; + currentImageTags = (HashSet) currentTaggedPicture.getTags().clone(); + currentDressState = currentTaggedPicture.getDressState(); + + if (currentImageTags == null) { + currentImageTags = new HashSet<>(); + } + + addTagsToButton(bodyPartButton, TagType.BODY_PART); + addTagsToButton(bodyTypeButton, TagType.BODY_TYPE); + addTagsToButton(categoryButton, TagType.CATEGORY); + addTagsToButton(peopleInvolvedButton, TagType.PEOPLE_INVOLVED); + addTagsToButton(actionButton, TagType.ACTION); + addTagsToButton(accessoriesButton, TagType.ACCESSORIES); + addTagsToButton(viewButton, TagType.VIEW); + + dressStateButton.getItems().clear(); + + DressState[] dressStateTags = DressState.values(); + ArrayList checkboxes = new ArrayList<>(); + + for (int i = 0; i < dressStateTags.length; i++) { + CheckBox thisCheckBox = new CheckBox(dressStateTags[i].getTagName().replace("Tag", "")); + checkboxes.add(thisCheckBox); + + if (currentDressState != null && currentDressState == dressStateTags[i]) { + thisCheckBox.setSelected(true); + } + + DressState thisTag = dressStateTags[i]; + thisCheckBox.selectedProperty().addListener(new ChangeListener() { + @Override + public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) { + if (newValue) { + currentDressState = thisTag; + for (CheckBox thisBox : checkboxes) { + if (!thisBox.equals(thisCheckBox)) { + thisBox.setSelected(false); + } + } + } else { + if (currentDressState.equals(thisTag)) { + currentDressState = null; + } + } + } + }); + + CustomMenuItem thisItem = new CustomMenuItem(thisCheckBox); + thisItem.setHideOnClick(false); + dressStateButton.getItems().add(thisItem); + } + } + + public void addTagsToButton(MenuButton button, TagType tagType) { + PictureTag[] viewTags = PictureTag.getPictureTagsByType(tagType); + if(button.getItems().isEmpty()) { + for (int i = 0; i < viewTags.length; i++) { + CheckBox thisCheckBox = new CheckBox(viewTags[i].getTagName().replace("Tag", "")); + checkBoxes.put(viewTags[i], thisCheckBox); + + PictureTag thisTag = viewTags[i]; + thisCheckBox.selectedProperty().addListener(new ChangeListener() { + @Override + public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) { + if (newValue) { + currentImageTags.add(thisTag); + } else { + currentImageTags.remove(thisTag); + } + } + }); + + CustomMenuItem thisItem = new CustomMenuItem(thisCheckBox); + thisItem.setHideOnClick(false); + button.getItems().add(thisItem); + } + } + + for(PictureTag pictureTag : viewTags) { + if(currentImageTags.contains(pictureTag)) { + checkBoxes.get(pictureTag).setSelected(true); + } else { + checkBoxes.get(pictureTag).setSelected(false); + } + } + } + + public boolean initiateNewStage() { + PictureHandler.getHandler().setDefaultFolders(); + taggedImage.setPreserveRatio(true); + Pane pane = new Pane(); + testGridPane.add(pane, 1, 1); + taggedImage.fitWidthProperty().bind(pane.widthProperty()); + taggedImage.fitHeightProperty().bind(pane.heightProperty()); + currentFile = 0; + + //File searchingDirectory = directory; + + File[] locFiles = directory.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return (name.toLowerCase().endsWith(".jpg") || name.toLowerCase().endsWith(".png") || name.toLowerCase().endsWith(".gif")); + } + }); + + ArrayList taggedList = new ArrayList<>(); + + for (File thisFile : locFiles) { + TaggedPicture thisTaggedPic = new TaggedPicture(thisFile, true); + if (thisTaggedPic.getFile() != null) { + taggedList.add(thisTaggedPic); + } + } + + if(taggedList.isEmpty()) { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle("No images found"); + alert.setHeaderText(null); + alert.setContentText("There were no images found in the given folder."); + + alert.showAndWait(); + return false; + } + + files = taggedList.toArray(new TaggedPicture[] {}); + + fileLabel.setText("File " + (currentFile + 1) + " of " + files.length + " in " + directory.getPath()); + + Image image = new Image(files[currentFile].getFile().toURI().toString()); + + taggedImage.setImage(image); + setUpButtons(); + + //Store the current tags of the selected image too when closing + imageTaggingWindow.setOnCloseRequest(event -> { + currentTaggedPicture.setDressState(currentDressState); + currentTaggedPicture.setTags(currentImageTags); + }); + + nextButton.setOnMouseClicked(event -> { + currentTaggedPicture.setDressState(currentDressState); + currentTaggedPicture.setTags(currentImageTags); + + if ((currentFile + 1) < files.length) { + currentFile++; + } else { + currentFile = 0; + } + + taggedImage.setImage(new Image(files[currentFile].getFile().toURI().toString())); + setUpButtons(); + + fileLabel.setText("File " + (currentFile + 1) + " of " + files.length + " in " + directory.getPath()); + }); + + previousButton.setOnMouseClicked(event -> { + long currentMillis = System.currentTimeMillis(); + currentTaggedPicture.setDressState(currentDressState); + currentTaggedPicture.setTags(currentImageTags); + + if ((currentFile - 1) >= 0) { + currentFile--; + } else { + currentFile = files.length - 1; + } + + taggedImage.setImage(new Image(files[currentFile].getFile().toURI().toString())); + setUpButtons(); + fileLabel.setText("File " + (currentFile + 1) + " of " + files.length + " in " + directory.getPath()); + }); + + return true; + } + } \ No newline at end of file