From 1b69d5073156fc18605c4d44f44fe9e1e86a40db Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Fri, 3 Feb 2012 14:22:34 +0100
Subject: [PATCH] OO-85: implements a more robust and efficient way to load the
 image, load the height and size and optimize the scaling of very large image
 under low memory condition

---
 .../java/org/olat/core/util/IImageHelper.java |   5 +-
 .../java/org/olat/core/util/ImageHelper.java  | 259 ++++++++++++------
 .../CourseLayoutGeneratorController.java      |  16 +-
 3 files changed, 196 insertions(+), 84 deletions(-)

diff --git a/src/main/java/org/olat/core/util/IImageHelper.java b/src/main/java/org/olat/core/util/IImageHelper.java
index 7dd8c61c3bf..11f654bd1bd 100644
--- a/src/main/java/org/olat/core/util/IImageHelper.java
+++ b/src/main/java/org/olat/core/util/IImageHelper.java
@@ -19,7 +19,7 @@
  */
 package org.olat.core.util;
 
-import java.io.InputStream;
+import java.io.File;
 
 import org.olat.core.util.ImageHelper.Size;
 import org.olat.core.util.vfs.VFSLeaf;
@@ -36,7 +36,6 @@ import org.olat.core.util.vfs.VFSLeaf;
  */
 public interface IImageHelper {
 
-	Size scaleImage(InputStream image, VFSLeaf scaledImage, int maxWidth,
-			int maxHeight);
+	public Size scaleImage(File image, String imgExt, VFSLeaf scaledImage, int maxWidth, int maxHeight);
 
 }
diff --git a/src/main/java/org/olat/core/util/ImageHelper.java b/src/main/java/org/olat/core/util/ImageHelper.java
index c4708654fc4..cefbc3d1af5 100644
--- a/src/main/java/org/olat/core/util/ImageHelper.java
+++ b/src/main/java/org/olat/core/util/ImageHelper.java
@@ -35,6 +35,7 @@ import java.awt.color.CMMException;
 import java.awt.image.BufferedImage;
 import java.io.BufferedOutputStream;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -42,12 +43,21 @@ import java.util.Iterator;
 
 import javax.imageio.IIOImage;
 import javax.imageio.ImageIO;
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageReader;
 import javax.imageio.ImageWriteParam;
 import javax.imageio.ImageWriter;
+import javax.imageio.stream.FileImageInputStream;
 import javax.imageio.stream.FileImageOutputStream;
+import javax.imageio.stream.ImageInputStream;
 import javax.imageio.stream.ImageOutputStream;
+import javax.imageio.stream.MemoryCacheImageInputStream;
 import javax.imageio.stream.MemoryCacheImageOutputStream;
 
+import org.apache.commons.io.IOUtils;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.vfs.LocalFileImpl;
 import org.olat.core.util.vfs.VFSLeaf;
 
 // FIXME:as:c google for deployment of servers with no X installed (fj)
@@ -61,6 +71,8 @@ import org.olat.core.util.vfs.VFSLeaf;
  */
 public class ImageHelper implements IImageHelper {
 	
+	private static final OLog log = Tracing.createLoggerFor(ImageHelper.class);
+	
 	private static final String OUTPUT_FORMAT = "jpeg";
 	
 	/**
@@ -73,7 +85,8 @@ public class ImageHelper implements IImageHelper {
 	 * @return boolean
 	 */
 	public static boolean scaleImage(File image, File scaledImage, int maxSize) {
-		return scaleImage(image, null, scaledImage, maxSize, maxSize);
+		String imageExt = FileUtils.getFileSuffix(image.getName());
+		return scaleImage(image, imageExt, scaledImage, maxSize, maxSize);
 	}
 	
 	/**
@@ -88,24 +101,6 @@ public class ImageHelper implements IImageHelper {
 	public static boolean scaleImage(File image, String imageExt, File scaledImage, int maxSize) {
 		return scaleImage(image, imageExt, scaledImage, maxSize, maxSize);
 	}
-
-	/**
-	 * @param image the image to scale
-	 * @param scaledImaged the new scaled image
-	 * @param maxSize the maximum size (height or width) of the new scaled image
-	 * @return
-	 */
-	public static boolean scaleImage(InputStream image, VFSLeaf scaledImage, int maxSize) {
-		try {
-			OutputStream bos = new BufferedOutputStream(scaledImage.getOutputStream(false));
-			boolean result = scaleImage(image, bos, maxSize, maxSize, getImageFormat(scaledImage));
-			FileUtils.closeSafely(image);
-			FileUtils.closeSafely(bos);
-			return result;
-		} catch (Exception e) {
-			return false;
-		}
-	}
 	
 	/**
 	 * @param image the image to scale
@@ -113,24 +108,39 @@ public class ImageHelper implements IImageHelper {
 	 * @param maxSize the maximum size (height or width) of the new scaled image
 	 * @return
 	 */
-	public Size scaleImage(InputStream image, VFSLeaf scaledImage, int maxWidth, int maxHeight) {
+	@Override
+	public Size scaleImage(File image, String imageExt, VFSLeaf scaledImage, int maxWidth, int maxHeight) {
+		
+		ImageInputStream imageIns = null;
 		OutputStream bos = new BufferedOutputStream(scaledImage.getOutputStream(false));
 		try {
-			BufferedImage imageSrc = ImageIO.read(image);
-			if (imageSrc == null) {
-				// happens with faulty Java implementation, e.g. on MacOSX Java 10, or
-				// unsupported image format
-				return null;				
+			imageIns = new FileImageInputStream(image);
+			SizeAndBufferedImage scaledSize = calcScaledSize(imageIns, imageExt, maxWidth, maxHeight);
+			if(scaledSize == null) {
+				return null;
 			}
-			Size scaledSize = calcScaledSize(imageSrc, maxWidth, maxHeight);
-			if(writeTo(scaleTo(imageSrc, scaledSize), bos, scaledSize, getImageFormat(scaledImage))) {
-				return scaledSize;
+			if(!scaledSize.getScaledSize().isChanged() && isSameFormat(image, scaledImage)) {
+				InputStream cloneIns = new FileInputStream(image);
+				IOUtils.copy(cloneIns, bos);
+				IOUtils.closeQuietly(cloneIns);
+				return scaledSize.getScaledSize();
+			} else {
+				BufferedImage imageSrc = scaledSize.getImage();
+				if (imageSrc == null) {
+					// happens with faulty Java implementation, e.g. on MacOSX Java 10, or
+					// unsupported image format
+					return null;				
+				}
+				BufferedImage scaledBufferedImage = scaleTo(imageSrc, scaledSize.getScaledSize());
+				if(writeTo(scaledBufferedImage, bos, scaledSize.getScaledSize(), getImageFormat(scaledImage))) {
+					return scaledSize.getScaledSize();
+				}
+				return null;
 			}
-			return null;
 		} catch (IOException e) {
 			return null;
 		} finally {
-			FileUtils.closeSafely(image);
+			closeQuietly(imageIns);
 			FileUtils.closeSafely(bos);
 		}
 	}
@@ -142,44 +152,66 @@ public class ImageHelper implements IImageHelper {
 	 * @return
 	 */
 	public static Size scaleImage(VFSLeaf image, VFSLeaf scaledImage, int maxWidth, int maxHeight) {
-		OutputStream bos = new BufferedOutputStream(scaledImage.getOutputStream(false));
-		InputStream ins = image.getInputStream();
+		OutputStream bos = null;
+		ImageInputStream ins = null;
 		try {
-			BufferedImage imageSrc = ImageIO.read(ins);
-			if (imageSrc == null) {
-				// happens with faulty Java implementation, e.g. on MacOSX Java 10, or
-				// unsupported image format
-				return null;				
+			ins = getInputStream(image);
+			String extension = FileUtils.getFileSuffix(image.getName());
+			SizeAndBufferedImage scaledSize = calcScaledSize(ins, extension, maxWidth, maxHeight);
+			if(scaledSize == null || scaledSize.getImage() == null) {
+				return null;
 			}
-			Size scaledSize = calcScaledSize(imageSrc, maxWidth, maxHeight);
-			if(!scaledSize.isChanged() && isSameFormat(image, scaledImage)) {
+			
+			ins = getInputStream(image);
+			bos = new BufferedOutputStream(scaledImage.getOutputStream(false));
+			if(!scaledSize.getScaledSize().isChanged() && isSameFormat(image, scaledImage)) {
 				InputStream cloneIns = image.getInputStream();
-				FileUtils.copy(cloneIns, bos);
-				FileUtils.closeSafely(cloneIns);
-				return scaledSize;
-			} else if(writeTo(scaleTo(imageSrc, scaledSize), bos, scaledSize, getImageFormat(scaledImage))) {
-				return scaledSize;
+				IOUtils.copy(cloneIns, bos);
+				IOUtils.closeQuietly(cloneIns);
+				return scaledSize.getScaledSize();
+			} else {
+				BufferedImage imageSrc = scaledSize.getImage();
+				BufferedImage scaledSrc = scaleTo(imageSrc, scaledSize.getScaledSize());
+				boolean scaled = writeTo(scaledSrc, bos, scaledSize.getScaledSize(), getImageFormat(scaledImage));
+				if(scaled) {
+					return scaledSize.getScaledSize();
+				}
+				return null;
 			}
-			return null;
 		} catch (IOException e) {
 			return null;
 		//fxdiff FXOLAT-109: prevent red screen if the image has wrong EXIF data
 		} catch (CMMException e) {
 			return null;
 		} finally {
-			FileUtils.closeSafely(ins);
+			closeQuietly(ins);
 			FileUtils.closeSafely(bos);
 		}
 	}
 	
+	/**
+	 * 
+	 * @param leaf
+	 * @return
+	 */
+	private static ImageInputStream getInputStream(VFSLeaf leaf)
+	throws IOException {
+		if(leaf instanceof LocalFileImpl) {
+			LocalFileImpl file = (LocalFileImpl)leaf;
+			return new FileImageInputStream(file.getBasefile());
+		}
+		return new MemoryCacheImageInputStream(leaf.getInputStream());
+	}
+	
 	public static Size scaleImage(BufferedImage image, VFSLeaf scaledImage, int maxWidth, int maxHeight) {
-		OutputStream bos = new BufferedOutputStream(scaledImage.getOutputStream(false));
+		OutputStream bos = null;
 		try {
 			if (image == null) {
 				// happens with faulty Java implementation, e.g. on MacOSX Java 10, or
 				// unsupported image format
 				return null;				
 			}
+			bos = new BufferedOutputStream(scaledImage.getOutputStream(false));
 			Size scaledSize = calcScaledSize(image, maxWidth, maxHeight);
 			if(writeTo(scaleTo(image, scaledSize), bos, scaledSize, getImageFormat(scaledImage))) {
 				return scaledSize;
@@ -212,23 +244,26 @@ public class ImageHelper implements IImageHelper {
 	 * @return
 	 */
 	public static boolean scaleImage(File image, String imageExt, File scaledImage, int maxWidth, int maxHeight) {
+		ImageInputStream imageSrc = null;
 		try {
-			BufferedImage imageSrc = ImageIO.read(image);
-			if (imageSrc == null) {
-				// happens with faulty Java implementation, e.g. on MacOSX Java 10, or
-				// unsupported image format
-				return false;				
+			imageSrc = new FileImageInputStream(image);
+			SizeAndBufferedImage scaledSize = calcScaledSize(imageSrc, imageExt, maxWidth, maxHeight);
+			if(scaledSize == null || scaledSize.image == null) {
+				return false;
 			}
-			Size scaledSize = calcScaledSize(imageSrc, maxWidth, maxHeight);
-			if(!scaledSize.isChanged() && isSameFormat(image, imageExt, scaledImage)) {
+			if(!scaledSize.getScaledSize().isChanged() && isSameFormat(image, imageExt, scaledImage)) {
 				return FileUtils.copyFileToFile(image, scaledImage, false);
 			}
-			return writeTo(scaleTo(imageSrc, scaledSize), scaledImage, scaledSize, getImageFormat(scaledImage));
+			BufferedImage bufferedImage = scaledSize.image;
+			BufferedImage scaledBufferedImage = scaleTo(bufferedImage, scaledSize.getScaledSize());
+			return writeTo(scaledBufferedImage, scaledImage, scaledSize.getScaledSize(), getImageFormat(scaledImage));
 		} catch (IOException e) {
 			return false;
 		//fxdiff FXOLAT-109: prevent red screen if the image has wrong EXIF data
 		} catch (CMMException e) {
 			return false;
+		} finally {
+			closeQuietly(imageSrc);
 		}
 	}
 	
@@ -248,6 +283,15 @@ public class ImageHelper implements IImageHelper {
 		return OUTPUT_FORMAT;
 	}
 	
+	private static boolean isSameFormat(File source, VFSLeaf scaled) {
+		String sourceExt = FileUtils.getFileSuffix(source.getName());
+		String scaledExt = getImageFormat(scaled);
+		if(sourceExt != null && sourceExt.equals(scaledExt)) {
+			return true;
+		}
+		return false;
+	}
+	
 	private static boolean isSameFormat(VFSLeaf source, VFSLeaf scaled) {
 		String sourceExt = FileUtils.getFileSuffix(source.getName());
 		String scaledExt = getImageFormat(scaled);
@@ -268,28 +312,6 @@ public class ImageHelper implements IImageHelper {
 		return false;
 	}
 	
-	/**
-	 * @param image the image to scale
-	 * @param scaledImaged the new scaled image
-	 * @param maxWidth the maximum width of the new scaled image
-	 * @param maxheight the maximum height of the new scaled image
-	 * @return
-	 */
-	public static boolean scaleImage(InputStream image, OutputStream scaledImage, int maxWidth, int maxHeight, String outputFormat) {
-		try {
-			BufferedImage imageSrc = ImageIO.read(image);
-			if (imageSrc == null) {
-				// happens with faulty Java implementation, e.g. on MacOSX Java 10, or
-				// unsupported image format
-				return false;				
-			}
-			Size scaledSize = calcScaledSize(imageSrc, maxWidth, maxHeight);
-			return writeTo(scaleTo(imageSrc, scaledSize), scaledImage, scaledSize, outputFormat);
-		} catch (IOException e) {
-			return false;
-		}
-	}
-	
 	/**
 	 * Calculate the size of the new image. The method keep the ratio and doesn't
 	 * scale up the image.
@@ -301,7 +323,51 @@ public class ImageHelper implements IImageHelper {
 	private static Size calcScaledSize(BufferedImage image, int maxWidth, int maxHeight) {
 		int width = image.getWidth();
 		int height = image.getHeight();
-		
+		return computeScaledSize(width, height, maxWidth, maxHeight);
+	}
+	
+
+	
+	private static SizeAndBufferedImage calcScaledSize(ImageInputStream stream, String suffix, int maxWidth, int maxHeight) {
+    Iterator<ImageReader> iter = ImageIO.getImageReadersBySuffix(suffix);
+    if (iter.hasNext()) {
+        ImageReader reader = iter.next();
+        try {
+            reader.setInput(stream);
+            int width = reader.getWidth(reader.getMinIndex());
+            int height = reader.getHeight(reader.getMinIndex());
+            Size size = new Size(width, height, false);
+            Size scaledSize = computeScaledSize(width, height, maxWidth, maxHeight);
+            SizeAndBufferedImage  all = new SizeAndBufferedImage(size, scaledSize);
+            
+            double memoryKB = (width * height * 4) / 1024d;
+            if(memoryKB > 2000) {//check limit at 20MB
+            	double free = Runtime.getRuntime().freeMemory() / 1024d;
+            	if(free > memoryKB) {
+                all.setImage(reader.read(reader.getMinIndex()));
+            	} else {
+            		//make sub sampling to save memory
+            		int ratio = (int)Math.round(Math.sqrt(memoryKB / free));
+            		ImageReadParam param = reader.getDefaultReadParam();
+            		param.setSourceSubsampling(ratio, ratio, 0, 0);
+                all.setImage(reader.read(reader.getMinIndex(), param));
+            	}
+            } else {
+            	all.setImage(reader.read(reader.getMinIndex()));
+            }
+            return all;
+        } catch (IOException e) {
+            log.error(e.getMessage());
+        } finally {
+            reader.dispose();
+        }
+    } else {
+        log.error("No reader found for given format: " + suffix, null);
+    }
+    return null;
+	}
+	
+	private static Size computeScaledSize(int width, int height, int maxWidth, int maxHeight) {
 		if(maxHeight > height && maxWidth > width) {
 			return new Size(width, height, false);
     }
@@ -499,6 +565,43 @@ public class ImageHelper implements IImageHelper {
     return ret;
 	}
 	
+	private final static void closeQuietly(ImageInputStream ins) {
+		if(ins != null) {
+			try {
+				ins.close();
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		}
+	}
+	
+	public static final class SizeAndBufferedImage {
+		private Size size;
+		private Size scaledSize;
+		private BufferedImage image;
+		
+		public SizeAndBufferedImage(Size size, Size scaledSize) {
+			this.size = size;
+			this.scaledSize = scaledSize;
+		}
+		
+		public Size getSize() {
+			return size;
+		}
+		
+		public Size getScaledSize() {
+			return scaledSize;
+		}
+
+		public BufferedImage getImage() {
+			return image;
+		}
+
+		public void setImage(BufferedImage image) {
+			this.image = image;
+		}
+	}
+	
 	public static final class Size {
 		private final int width;
 		private final int height;
diff --git a/src/main/java/org/olat/course/config/ui/courselayout/CourseLayoutGeneratorController.java b/src/main/java/org/olat/course/config/ui/courselayout/CourseLayoutGeneratorController.java
index 508ccd496fd..01eebebb151 100644
--- a/src/main/java/org/olat/course/config/ui/courselayout/CourseLayoutGeneratorController.java
+++ b/src/main/java/org/olat/course/config/ui/courselayout/CourseLayoutGeneratorController.java
@@ -22,6 +22,8 @@ package org.olat.course.config.ui.courselayout;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -285,18 +287,26 @@ public class CourseLayoutGeneratorController extends FormBasicController {
 			// scale image
 			try {
 				IImageHelper helper = CourseLayoutHelper.getImageHelperToUse();
-				helper.scaleImage(new FileInputStream(image), targetFile, maxWidth, maxHeight);
-			} catch (FileNotFoundException e) {
+				String extension = FileUtils.getFileSuffix(logoUpl.getUploadFileName());
+				helper.scaleImage(image, extension, targetFile, maxWidth, maxHeight);
+			} catch (Exception e) {
 				logError("could not find to be scaled image", e);
 				return false;
 			}
 		} else {
 			// only persist without scaling
+			InputStream in = null;
+			OutputStream out = null;
 			try {
-				FileUtils.copy(new FileInputStream(image), targetFile.getOutputStream(false));
+				in = new FileInputStream(image);
+				out = targetFile.getOutputStream(false);
+				FileUtils.copy(in, out);
 			} catch (FileNotFoundException e) {
 				logError("Problem reading uploaded image to copy", e);
 				return false;
+			} finally {
+				FileUtils.closeSafely(in);
+				FileUtils.closeSafely(out);
 			}
 		}
 		return true;
-- 
GitLab