001/* 002 * Copyright 2012 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.core.util; 017 018import java.awt.Graphics; 019import java.awt.image.BufferedImage; 020import java.io.ByteArrayInputStream; 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.OutputStream; 024 025import javax.imageio.ImageIO; 026 027import org.apache.commons.io.IOUtils; 028 029import net.coobird.thumbnailator.ThumbnailParameter; 030import net.coobird.thumbnailator.Thumbnails; 031import net.coobird.thumbnailator.filters.ImageFilter; 032import net.coobird.thumbnailator.resizers.DefaultResizerFactory; 033import net.coobird.thumbnailator.tasks.io.InputStreamImageSource; 034import net.coobird.thumbnailator.tasks.io.OutputStreamImageSink; 035 036/** 037 * Helper for manipulating images. 038 */ 039public final class ImageHelper 040{ 041 private ImageHelper() 042 { 043 // empty constructor 044 } 045 046 /** 047 * Returns a BufferedImage as from the supplied input stream 048 * @param is The input stream 049 * @return The buffered image 050 * @throws IOException if an error occurs during reading. 051 */ 052 public static BufferedImage read(InputStream is) throws IOException 053 { 054 InputStreamImageSource imageSource = new InputStreamImageSource(is); 055 ThumbnailParameter param = new ThumbnailParameter(1.0f, 1.0f, null, true, null, null, 1.0f, 0, null, DefaultResizerFactory.getInstance(), true, true); 056 imageSource.setThumbnailParameter(param); 057 058 BufferedImage src = imageSource.read(); 059 060 // Perform the image filters 061 for (ImageFilter filter : param.getImageFilters()) 062 { 063 src = filter.apply(src); 064 } 065 066 return src; 067 } 068 069 /** 070 * Generates a thumbnail from a source InputStream. Note that if final width and height are equals to source width and height, the stream is just copied. 071 * @param is the source. 072 * @param os the destination. 073 * @param format the image format. Must be one of "gif", "png" or "jpg". 074 * @param height the specified height. Ignored if negative. 075 * @param width the specified width. Ignored if negative. 076 * @param maxHeight the maximum image height. Ignored if height or width is specified. 077 * @param maxWidth the maximum image width. Ignored if height or width is specified. 078 * @throws IOException if an error occurs when manipulating streams. 079 */ 080 public static void generateThumbnail(InputStream is, OutputStream os, String format, int height, int width, int maxHeight, int maxWidth) throws IOException 081 { 082 BufferedImage src = read(is); 083 BufferedImage dest = _resizeImage(src, height, width, maxHeight, maxWidth); 084 085 OutputStreamImageSink imageSink = new OutputStreamImageSink(os); 086 imageSink.setOutputFormatName(format); 087 imageSink.write(dest); 088 } 089 090 /** 091 * Generates a BufferedImage with specified size instructions, scaling if necessary.<br> 092 * @param src the source image. 093 * @param height the specified height. Ignored if negative. 094 * @param width the specified width. Ignored if negative. 095 * @param maxHeight the maximum image height. Ignored if height or width is specified. 096 * @param maxWidth the maximum image width. Ignored if height or width is specified. 097 * @return a scaled BufferedImage. If no size modification is required, this will return the src image. 098 * @throws IOException If the source image is not readable 099 */ 100 public static BufferedImage generateThumbnail(BufferedImage src, int height, int width, int maxHeight, int maxWidth) throws IOException 101 { 102 return _resizeImage(src, height, width, maxHeight, maxWidth); 103 } 104 105 /** 106 * Crop an image from a source InputStream. Note that if any of the coordinate and size are negatives, the image will just be copied. 107 * @param is the source. 108 * @param os the destination. 109 * @param format the image format. Must be one of "gif", "png" or "jpg". 110 * @param x The X coordinate of the upper-left corner of the specified rectangular region 111 * @param y the Y coordinate of the upper-left corner of the specified rectangular region 112 * @param height the width of the specified rectangular region 113 * @param width the height of the specified rectangular region 114 * @throws IOException If an error occurs 115 */ 116 public static void generateCroppedImage(InputStream is, OutputStream os, String format, int x, int y, int height, int width) throws IOException 117 { 118 byte[] fileContent = IOUtils.toByteArray(is); // keep a copy of the initial stream in case no thumbnail is necessary 119 120 BufferedImage src = read(new ByteArrayInputStream(fileContent)); 121 BufferedImage dest = _cropImage(src, x, y, height, width); 122 123 if (src == dest) 124 { 125 // Thumbnail is equals to src image, means that the image is the same 126 // We'd rather like return the initial stream 127 IOUtils.write(fileContent, os); 128 } 129 else 130 { 131 ImageIO.write(dest, format, os); 132 } 133 } 134 135 /** 136 * Crop the image by a specified rectangular region. The returned <code>BufferedImage</code> shares the same data array as the original image. 137 * @param src the source image. 138 * @param x The X coordinate of the upper-left corner of the specified rectangular region 139 * @param y the Y coordinate of the upper-left corner of the specified rectangular region 140 * @param height the width of the specified rectangular region 141 * @param width the height of the specified rectangular region 142 * @return a scaled BufferedImage. If no size modification is required, this will return the src image. 143 * @throws IOException If the source image is not readable 144 */ 145 public static BufferedImage generateCroppedImage(BufferedImage src, int x, int y, int height, int width) throws IOException 146 { 147 return _cropImage(src, x, y, height, width); 148 } 149 150 /** 151 * Crop the image by a specified rectangular region. The returned <code>BufferedImage</code> shares the same data array as the original image. 152 * @param src the source image. 153 * @param x The X coordinate of the upper-left corner of the specified rectangular region 154 * @param y the Y coordinate of the upper-left corner of the specified rectangular region 155 * @param height the width of the specified rectangular region 156 * @param width the height of the specified rectangular region 157 * @return a scaled BufferedImage. If no size modification is required, this will return the src image. 158 * @throws IOException If the source image is not readable 159 */ 160 protected static BufferedImage _cropImage(BufferedImage src, int x, int y, int height, int width) throws IOException 161 { 162 BufferedImage dest = new BufferedImage(Integer.valueOf(width), Integer.valueOf(height), src.getColorModel().hasAlpha() ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); 163 Graphics g = dest.getGraphics(); 164 int offsetX = x < 0 ? x * -1 : 0; 165 int offsetY = y < 0 ? y * -1 : 0; 166 g.drawImage(src, offsetX, offsetY, width, height, x + offsetX, y + offsetY, x + offsetX + width, y + offsetY + height, null); 167 g.dispose(); 168 return dest; 169 } 170 171 /** 172 * Resize the buffered image 173 * @param src the source image 174 * @param height the specified height. Ignored if negative. 175 * @param width the specified width. Ignored if negative. 176 * @param maxHeight the maximum image height. Ignored if height or width is specified. 177 * @param maxWidth the maximum image width. Ignored if height or width is specified. 178 * @return a scaled BufferedImage. If no size modification is required, this will return the src image. 179 * @throws IOException If the source image is not readable 180 */ 181 protected static BufferedImage _resizeImage(BufferedImage src, int height, int width, int maxHeight, int maxWidth) throws IOException 182 { 183 int srcHeight = src.getHeight(); 184 int srcWidth = src.getWidth(); 185 186 int destHeight = 0; 187 int destWidth = 0; 188 189 boolean keepAspectRatio = true; 190 191 if (height > 0) 192 { 193 // heigth is specified 194 destHeight = height; 195 196 if (width > 0) 197 { 198 // additionnally, width is also specified 199 destWidth = width; 200 keepAspectRatio = false; 201 } 202 else 203 { 204 // width is computed 205 destWidth = srcWidth * destHeight / srcHeight; 206 } 207 } 208 else if (width > 0) 209 { 210 // width is specified, height is computed 211 destWidth = width; 212 destHeight = srcHeight * destWidth / srcWidth; 213 } 214 else if (maxHeight > 0) 215 { 216 if (maxWidth > 0) 217 { 218 if (srcHeight <= maxHeight && srcWidth <= maxWidth) 219 { 220 // the source image is already smaller than the destination box 221// return thumbnail.scale(1); 222 return src; 223 } 224 225 destWidth = maxWidth; 226 destHeight = maxHeight; 227 } 228 else 229 { 230 if (srcHeight <= maxHeight) 231 { 232 // the source image is already smaller than the destination box 233 return src; 234 } 235 236 destHeight = maxHeight; 237 destWidth = srcWidth * destHeight / srcHeight; 238 } 239 } 240 else if (maxWidth > 0) 241 { 242 if (srcWidth <= maxWidth) 243 { 244 // the source image is already smaller than the destination box 245 return src; 246 } 247 248 destWidth = maxWidth; 249 destHeight = srcHeight * destWidth / srcWidth; 250 } 251 else 252 { 253 // No resize is required 254 return src; 255 } 256 257 if (destHeight == srcHeight && destWidth == srcWidth) 258 { 259 // already the good format, don't change anything 260 return src; 261 } 262 263 return Thumbnails.of(src).size(destWidth, destHeight).keepAspectRatio(keepAspectRatio).imageType(src.getColorModel().hasAlpha() ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB).asBufferedImage(); 264 } 265}