From fb251c9df8280dc9531be0993f7892d28e25696a Mon Sep 17 00:00:00 2001
From: Lennart Eichhorn <lennart@madmanfred.com>
Date: Sun, 31 May 2020 17:45:17 +0200
Subject: [PATCH] Video generation is now done by a visualizer.

---
 .../java/MainClasses/GeneticAlgorithm.java    |   4 +
 src/main/java/MainClasses/Main.java           |   9 -
 .../java/Visualization/JCodecPNGtoMP4.java    |   4 +-
 .../Visualizers/BestFoldingsToVideo.java      | 227 ++++++++++++++++++
 4 files changed, 233 insertions(+), 11 deletions(-)
 create mode 100644 src/main/java/Visualization/Visualizers/BestFoldingsToVideo.java

diff --git a/src/main/java/MainClasses/GeneticAlgorithm.java b/src/main/java/MainClasses/GeneticAlgorithm.java
index 16f6c3d..dd03c4e 100644
--- a/src/main/java/MainClasses/GeneticAlgorithm.java
+++ b/src/main/java/MainClasses/GeneticAlgorithm.java
@@ -14,6 +14,7 @@ import Selectors.Tournament;
 import Visualization.Visualizers.BestFoldingToConsole;
 import Visualization.Visualizers.BestFoldingToImage;
 
+import Visualization.Visualizers.BestFoldingsToVideo;
 import Visualization.Visualizers.GenerationOverviewToConsole;
 import Visualization.Visualizers.GenerationProgressToLog;
 import java.io.IOException;
@@ -92,6 +93,9 @@ public class GeneticAlgorithm {
                 }else if (vm.equals(VisualizerMethods.Generation)) {
                     this.visualizers[j] = new GenerationOverviewToConsole(isHydrophobic, config);
                     j++;
+                }else if (vm.equals(VisualizerMethods.Video)) {
+                    this.visualizers[j] = new BestFoldingsToVideo(config);
+                    j++;
                 }
             }
 
diff --git a/src/main/java/MainClasses/Main.java b/src/main/java/MainClasses/Main.java
index d063f48..ffa497e 100644
--- a/src/main/java/MainClasses/Main.java
+++ b/src/main/java/MainClasses/Main.java
@@ -16,14 +16,5 @@ public class Main {
         GeneticAlgorithm ga = new GeneticAlgorithm(protein, config);
         ga.simulateGenerations();
 
-        // Create a new video if possible and desired
-        boolean imagesRefreshed = Arrays.asList(config.getVisualizers()).contains(VisualizerMethods.Image);
-        boolean videoEnabled = Arrays.asList(config.getVisualizers()).contains(VisualizerMethods.Video);
-        if (imagesRefreshed && videoEnabled){
-            String videoPath = config.getVideoDirectory() + "/" + config.getJobName() + ".mp4";
-            VideoCreator.createVideo(config.getImageSequenceDirectory(), videoPath,
-                config.getImageFps(), config.getImagesToFpsIncrease(), config.getImageFpsMax(),
-                    ga.getMaxH(), ga.getMaxW(), config.isZoom());
-        }
     }
 }
diff --git a/src/main/java/Visualization/JCodecPNGtoMP4.java b/src/main/java/Visualization/JCodecPNGtoMP4.java
index bb6d05e..0b730a2 100644
--- a/src/main/java/Visualization/JCodecPNGtoMP4.java
+++ b/src/main/java/Visualization/JCodecPNGtoMP4.java
@@ -41,7 +41,7 @@ import java.util.List;
  */
 public class JCodecPNGtoMP4 {
 
-    static void sortByNumber(File[] files) {
+    public static void sortByNumber(File[] files) {
         Arrays.sort(files, new Comparator<File>() {
             @Override
             public int compare(File o1, File o2) {
@@ -64,7 +64,7 @@ public class JCodecPNGtoMP4 {
         });
     }
 
-    static void generateVideoBySequenceImages(File videoFile, String pathImages, String imageExt,
+    public static void generateVideoBySequenceImages(File videoFile, String pathImages, String imageExt,
                                               int fps, int imgToFpsIncrease, int maxFps) throws Exception {
         SeekableByteChannel out = null;
         try {
diff --git a/src/main/java/Visualization/Visualizers/BestFoldingsToVideo.java b/src/main/java/Visualization/Visualizers/BestFoldingsToVideo.java
new file mode 100644
index 0000000..12b18fd
--- /dev/null
+++ b/src/main/java/Visualization/Visualizers/BestFoldingsToVideo.java
@@ -0,0 +1,227 @@
+package Visualization.Visualizers;
+
+import Interfaces.Visualizer;
+import MainClasses.Candidate;
+import MainClasses.Config;
+import MainClasses.GeneticAlgorithm;
+import Visualization.JCodecPNGtoMP4;
+import Visualization.VideoCreator;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import javax.imageio.ImageIO;
+
+public class BestFoldingsToVideo implements Visualizer {
+
+  Config config;
+  private final int MAXIMUM_SIZE;
+  private final FilenameFilter imageFilter;
+
+  public BestFoldingsToVideo(Config config){
+    this.config = config;
+    MAXIMUM_SIZE = 3000;
+    imageFilter = new FilenameFilter() {
+      @Override
+      public boolean accept(final File dir, String name) {
+        for (final String ext : new String[]{"jpg", "png"}) {
+          if (name.endsWith("." + ext) && name.startsWith(config.getJobName())) {
+            return (true);
+          }
+        }
+        return (false);
+      }
+    };
+    try {
+      Files.createDirectories(Paths.get(config.getVideoDirectory()));
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+
+  }
+
+
+  @Override
+  public void drawProtein(Candidate[] generation, GeneticAlgorithm geneticAlgorithm) {
+    //TODO Only render video for the last generation
+    if(geneticAlgorithm.generation == config.getTotalGenerations()-2){
+      String filename = config.getLogfileDirectory() + "/" + config.getJobName() + ".mp4";
+      createVideo(
+          config.getImageSequenceDirectory(),
+          filename,
+          config.getImageFps(),
+          config.getImagesToFpsIncrease(),
+          config.getImageFpsMax(),
+          geneticAlgorithm.getMaxH(),
+          geneticAlgorithm.getMaxW(),
+          config.isZoom()
+      );
+    }
+  }
+
+  public void createVideo(String imagesPath, String videoPathAndFile,
+      int imgFps, int imgToFpsIncrease, int maxFps, int maxH, int maxW, boolean zoom) {
+    try {
+      System.err.println("\nStarting image resizing");
+      if (zoom) {
+        resizeImagesWithZoom(imagesPath);
+      } else {
+        resizeImages(imagesPath, maxH, maxW);
+      }
+
+      File videoFile = new File(videoPathAndFile);
+      if (!videoFile.exists()) {
+        videoFile.createNewFile();
+      }
+
+      try {
+        JCodecPNGtoMP4.generateVideoBySequenceImages(videoFile, imagesPath, "png", imgFps, imgToFpsIncrease, maxFps);
+      } catch (Exception e) {
+        e.printStackTrace();
+      }
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+  }
+
+  private void resizeImages(String imagesPath, int maxH, int maxW) throws IOException {
+    File dir = new File(imagesPath);
+
+    if (dir.isDirectory()) {
+      // reads input images and determines maximum required size
+      int maxHeight = maxH;
+      int maxWidth = maxW;
+
+      if (maxHeight <= 0 || maxWidth <= 0) {
+        int counter = 1;
+        for (final File f : dir.listFiles(imageFilter)) {
+          BufferedImage inputImage = ImageIO.read(f);
+          if (maxHeight < inputImage.getHeight()) {
+            maxHeight = inputImage.getHeight();
+          }
+          if (maxWidth < inputImage.getWidth()) {
+            maxWidth = inputImage.getWidth();
+          }
+        }
+      }
+
+      // Needed because video is not playable with bigger sizes, mostly a concern for straight line initialization
+      if (maxWidth > MAXIMUM_SIZE) {
+        maxWidth = MAXIMUM_SIZE;
+      }
+      if (maxHeight > MAXIMUM_SIZE) {
+        maxHeight = MAXIMUM_SIZE;
+      }
+
+      // Resizes all images
+      for (final File f : dir.listFiles(imageFilter)) {
+        BufferedImage inputImage = ImageIO.read(f);
+
+        // creates output image
+        BufferedImage outputImage = new BufferedImage(maxWidth,
+            maxHeight, inputImage.getType());
+
+        // draws input image to the top left corner
+        Graphics2D g2d = outputImage.createGraphics();
+        //TODO The following two lines should be unnecessary
+        //g2d.setColor(Config.imageBackground);
+        //g2d.fillRect(0,0, maxWidth, maxHeight);
+        g2d.drawImage(inputImage, 0, 0, inputImage.getWidth(), inputImage.getHeight(), null);
+        g2d.dispose();
+
+        // overwrites image
+        ImageIO.write(outputImage, "png", f);
+      }
+    }
+  }
+
+  private void resizeImagesWithZoom(String imagesPath) throws IOException {
+    File dir = new File(imagesPath);
+
+    int[] maxHeightAfterIndex = new int[dir.listFiles(imageFilter).length];
+    int[] maxWidthAfterIndex = new int[dir.listFiles(imageFilter).length];
+
+    if (dir.isDirectory()) {
+
+      Path directoryPath = Paths.get(new File(imagesPath).toURI());
+      DirectoryStream<Path> stream = Files.newDirectoryStream(directoryPath, "*.png");
+
+      List<File> filesList = new ArrayList<>();
+      for (Path path : stream) {
+        filesList.add(path.toFile());
+      }
+      File[] files = new File[filesList.size()];
+      filesList.toArray(files);
+
+      JCodecPNGtoMP4.sortByNumber(files);
+      List<Object> list = Arrays.asList(files);
+      Collections.reverse(list);
+      list.toArray(files);
+
+      // reads input images and determines maximum required size after that particular image
+      int currentMaxHeight = 0;
+      int currentMaxWidth = 0;
+      int counter = files.length - 1;
+      for (File f : files) {
+        BufferedImage inputImage = ImageIO.read(f);
+        if (currentMaxHeight < inputImage.getHeight()) {
+          currentMaxHeight = inputImage.getHeight();
+        }
+        if (currentMaxWidth < inputImage.getWidth()) {
+          currentMaxWidth = inputImage.getWidth();
+        }
+        maxHeightAfterIndex[counter] = currentMaxHeight;
+        maxWidthAfterIndex[counter] = currentMaxWidth;
+        counter--;
+      }
+
+      // Resizes all images
+      counter = files.length - 1;
+      for (final File f : files) {
+        BufferedImage inputImage = ImageIO.read(f);
+
+        // creates output image
+        BufferedImage outputImage = new BufferedImage(maxWidthAfterIndex[0], // Total maximum W/H
+            maxHeightAfterIndex[0], inputImage.getType());
+
+        // draws input image to the top left corner
+        Graphics2D g2d = outputImage.createGraphics();
+        //TODO The following two lines should be unnecessary
+        //g2d.setColor(Config.imageBackground);
+        //g2d.fillRect(0,0, maxWidthAfterIndex[0], maxHeightAfterIndex[0]);
+
+        int newHeight = 0;
+        int newWidth = 0;
+
+        // Width expansion to border if height fits accordingly
+        double expansionByWidth = (double) maxWidthAfterIndex[0] / maxWidthAfterIndex[counter];
+        if (maxHeightAfterIndex[counter] * expansionByWidth <= maxHeightAfterIndex[0]) {
+          newWidth = (int) Math.floor(inputImage.getWidth() * expansionByWidth);
+          newHeight = (int) Math.floor(inputImage.getHeight() * expansionByWidth);
+        } else {
+          // otherwise height expansion to border and width according to ratio
+          double expansionByHeight = (double) maxHeightAfterIndex[0] / maxHeightAfterIndex[counter];
+          newWidth = (int) Math.floor(inputImage.getWidth() * expansionByHeight);
+          newHeight = (int) Math.floor(inputImage.getHeight() * expansionByHeight);
+        }
+
+        g2d.drawImage(inputImage, 0, 0, newWidth, newHeight, null);
+        g2d.dispose();
+
+        // overwrites image
+        ImageIO.write(outputImage, "png", f);
+
+        counter--;
+      }
+    }
+  }
+}
-- 
GitLab