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.image.BufferedImage; 019import java.io.IOException; 020import java.io.InputStream; 021import java.io.OutputStream; 022 023import org.apache.commons.io.IOUtils; 024 025import net.coobird.thumbnailator.ThumbnailParameter; 026import net.coobird.thumbnailator.Thumbnails; 027import net.coobird.thumbnailator.filters.ImageFilter; 028import net.coobird.thumbnailator.geometry.Positions; 029import net.coobird.thumbnailator.resizers.DefaultResizerFactory; 030import net.coobird.thumbnailator.tasks.io.InputStreamImageSource; 031import net.coobird.thumbnailator.tasks.io.OutputStreamImageSink; 032 033/** 034 * Helper for manipulating images. 035 */ 036public final class ImageHelper 037{ 038 private ImageHelper() 039 { 040 // empty constructor 041 } 042 043 private static boolean _needsChanges(int height, int width, int maxHeight, int maxWidth, int cropHeight, int cropWidth) 044 { 045 return height > 0 || width > 0 || maxHeight > 0 || maxWidth > 0 || cropHeight > 0 || cropWidth > 0; 046 } 047 048 private static boolean _isJpeg(String format) 049 { 050 return format.equalsIgnoreCase("jpg") || format.equalsIgnoreCase("jpeg"); 051 } 052 053 /** 054 * Returns a BufferedImage as from the supplied input stream 055 * @param is The input stream 056 * @return The buffered image 057 * @throws IOException if an error occurs during reading. 058 */ 059 public static BufferedImage read(InputStream is) throws IOException 060 { 061 InputStreamImageSource imageSource = new InputStreamImageSource(is); 062 ThumbnailParameter param = new ThumbnailParameter(1.0f, 1.0f, null, true, null, null, 1.0f, 0, null, DefaultResizerFactory.getInstance(), true, true); 063 imageSource.setThumbnailParameter(param); 064 065 BufferedImage src = imageSource.read(); 066 067 // Perform the image filters 068 for (ImageFilter filter : param.getImageFilters()) 069 { 070 src = filter.apply(src); 071 } 072 073 return src; 074 } 075 076 /** 077 * 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. 078 * If the image should be both cropped and resized, the resizing will be done after the cropping. 079 * @param is the source. 080 * @param os the destination. 081 * @param format the image format. Must be one of "gif", "png" or "jpg". 082 * @param height the specified height. Ignored if negative. 083 * @param width the specified width. Ignored if negative. 084 * @param maxHeight the maximum image height. Ignored if height or width is specified. 085 * @param maxWidth the maximum image width. Ignored if height or width is specified. 086 * @param cropHeight the height of the cropped image. Ignore if negative. 087 * @param cropWidth the width of the cropped image. Ignore if negative. 088 * @throws IOException if an error occurs when manipulating streams. 089 */ 090 public static void generateThumbnail(InputStream is, OutputStream os, String format, int height, int width, int maxHeight, int maxWidth, int cropHeight, int cropWidth) throws IOException 091 { 092 if (!_isJpeg(format) && !_needsChanges(height, width, maxHeight, maxWidth, cropHeight, cropWidth)) 093 { 094 // no resizing nor cropping needed 095 // only do it for non JPEG, as JPEG may have some Exif orientation that we need to handle anyway 096 IOUtils.copy(is, os); 097 return; 098 } 099 100 BufferedImage dest = read(is); 101 102 if (cropHeight > 0 || cropWidth > 0) 103 { 104 dest = _cropImage(dest, cropHeight, cropWidth); 105 } 106 107 if (height > 0 || width > 0 || maxHeight > 0 || maxWidth > 0) 108 { 109 dest = _resizeImage(dest, height, width, maxHeight, maxWidth); 110 } 111 112 OutputStreamImageSink imageSink = new OutputStreamImageSink(os); 113 imageSink.setOutputFormatName(format); 114 imageSink.write(dest); 115 } 116 117 /** 118 * Generates a BufferedImage with specified size instructions, scaling if necessary.<br> 119 * If the image should be both cropped and resized, the resizing will be done after the cropping. 120 * @param src the source image. 121 * @param height the specified height. Ignored if negative. 122 * @param width the specified width. Ignored if negative. 123 * @param maxHeight the maximum image height. Ignored if height or width is specified. 124 * @param maxWidth the maximum image width. Ignored if height or width is specified. 125 * @param cropHeight the height of the cropped image. Ignore if negative. 126 * @param cropWidth the width of the cropped image. Ignore if negative. 127 * @return a scaled BufferedImage. If no size modification is required, this will return the src image. 128 * @throws IOException If the source image is not readable 129 */ 130 public static BufferedImage generateThumbnail(BufferedImage src, int height, int width, int maxHeight, int maxWidth, int cropHeight, int cropWidth) throws IOException 131 { 132 BufferedImage dest = src; 133 134 if (cropHeight > 0 || cropWidth > 0) 135 { 136 dest = _cropImage(src, cropHeight, cropWidth); 137 } 138 139 if (height > 0 || width > 0 || maxHeight > 0 || maxWidth > 0) 140 { 141 dest = _resizeImage(src, height, width, maxHeight, maxWidth); 142 } 143 144 return dest; 145 } 146 147 /** 148 * 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. 149 * @param is the source. 150 * @param os the destination. 151 * @param format the image format. Must be one of "gif", "png" or "jpg". 152 * @param height the specified height. Ignored if negative. 153 * @param width the specified width. Ignored if negative. 154 * @param maxHeight the maximum image height. Ignored if height or width is specified. 155 * @param maxWidth the maximum image width. Ignored if height or width is specified. 156 * @throws IOException if an error occurs when manipulating streams. 157 */ 158 public static void generateThumbnail(InputStream is, OutputStream os, String format, int height, int width, int maxHeight, int maxWidth) throws IOException 159 { 160 if (!_isJpeg(format) && !_needsChanges(height, width, maxHeight, maxWidth, 0, 0)) 161 { 162 // no resizing needed 163 // only do it for non JPEG, as JPEG may have some Exif orientation that we need to handle anyway 164 IOUtils.copy(is, os); 165 return; 166 } 167 168 BufferedImage src = read(is); 169 BufferedImage dest = _resizeImage(src, height, width, maxHeight, maxWidth); 170 171 OutputStreamImageSink imageSink = new OutputStreamImageSink(os); 172 imageSink.setOutputFormatName(format); 173 imageSink.write(dest); 174 } 175 176 /** 177 * Generates a BufferedImage with specified size instructions, scaling if necessary.<br> 178 * @param src the source image. 179 * @param height the specified height. Ignored if negative. 180 * @param width the specified width. Ignored if negative. 181 * @param maxHeight the maximum image height. Ignored if height or width is specified. 182 * @param maxWidth the maximum image width. Ignored if height or width is specified. 183 * @return a scaled BufferedImage. If no size modification is required, this will return the src image. 184 * @throws IOException If the source image is not readable 185 */ 186 public static BufferedImage generateThumbnail(BufferedImage src, int height, int width, int maxHeight, int maxWidth) throws IOException 187 { 188 return _resizeImage(src, height, width, maxHeight, maxWidth); 189 } 190 191 /** 192 * Crop an image from a source InputStream. Note that if any of the coordinate and size are negatives, the image will just be copied. 193 * @param is the source. 194 * @param os the destination. 195 * @param format the image format. Must be one of "gif", "png" or "jpg". 196 * @param x The X coordinate of the upper-left corner of the specified rectangular region 197 * @param y the Y coordinate of the upper-left corner of the specified rectangular region 198 * @param height the width of the specified rectangular region 199 * @param width the height of the specified rectangular region 200 * @throws IOException If an error occurs 201 */ 202 public static void generateCroppedImage(InputStream is, OutputStream os, String format, int x, int y, int height, int width) throws IOException 203 { 204 BufferedImage src = read(is); 205 BufferedImage dest = _cropImage(src, x, y, height, width); 206 207 OutputStreamImageSink imageSink = new OutputStreamImageSink(os); 208 imageSink.setOutputFormatName(format); 209 imageSink.write(dest); 210 } 211 212 /** 213 * Crop the image in the center, at the specified dimensions. The returned <code>BufferedImage</code> shares the same data array as the original image. 214 * @param src the source image. 215 * @param height the width of the specified rectangular region 216 * @param width the height of the specified rectangular region 217 * @return a scaled BufferedImage. If no size modification is required, this will return the src image. 218 * @throws IOException If the source image is not readable 219 */ 220 public static BufferedImage generateCroppedImage(BufferedImage src, int height, int width) throws IOException 221 { 222 return _cropImage(src, height, width); 223 } 224 /** 225 * Crop the image by a specified rectangular region. The returned <code>BufferedImage</code> shares the same data array as the original image. 226 * @param src the source image. 227 * @param x The X coordinate of the upper-left corner of the specified rectangular region 228 * @param y the Y coordinate of the upper-left corner of the specified rectangular region 229 * @param height the width of the specified rectangular region 230 * @param width the height of the specified rectangular region 231 * @return a scaled BufferedImage. If no size modification is required, this will return the src image. 232 * @throws IOException If the source image is not readable 233 */ 234 public static BufferedImage generateCroppedImage(BufferedImage src, int x, int y, int height, int width) throws IOException 235 { 236 return _cropImage(src, x, y, height, width); 237 } 238 239 /** 240 * Crop the image to the size specified to the center of the image. 241 * @param src the source image. 242 * @param height the width of the specified rectangular region 243 * @param width the height of the specified rectangular region 244 * @return The cropped image as a BufferedImage 245 * @throws IOException If the source image is not readable 246 */ 247 protected static BufferedImage _cropImage(BufferedImage src, int height, int width) throws IOException 248 { 249 return Thumbnails.of(src).size(width, height).crop(Positions.CENTER).asBufferedImage(); 250 } 251 252 /** 253 * Crop the image by a specified rectangular region. The returned <code>BufferedImage</code> shares the same data array as the original image. 254 * @param src the source image. 255 * @param x The X coordinate of the upper-left corner of the specified rectangular region. 256 * @param y the Y coordinate of the upper-left corner of the specified rectangular region. 257 * @param height the width of the specified rectangular region 258 * @param width the height of the specified rectangular region 259 * @return a scaled BufferedImage. If no size modification is required, this will return the src image. 260 * @throws IOException If the source image is not readable 261 */ 262 protected static BufferedImage _cropImage(BufferedImage src, int x, int y, int height, int width) throws IOException 263 { 264 return Thumbnails.of(src).scale(1).sourceRegion(x, y, width, height).asBufferedImage(); 265 } 266 267 /** 268 * Resize the buffered image 269 * @param src the source image 270 * @param height the specified height. Ignored if negative. 271 * @param width the specified width. Ignored if negative. 272 * @param maxHeight the maximum image height. Ignored if height or width is specified. 273 * @param maxWidth the maximum image width. Ignored if height or width is specified. 274 * @return a scaled BufferedImage. If no size modification is required, this will return the src image. 275 * @throws IOException If the source image is not readable 276 */ 277 protected static BufferedImage _resizeImage(BufferedImage src, int height, int width, int maxHeight, int maxWidth) throws IOException 278 { 279 int srcHeight = src.getHeight(); 280 int srcWidth = src.getWidth(); 281 282 int destHeight = 0; 283 int destWidth = 0; 284 285 boolean keepAspectRatio = true; 286 287 if (height > 0) 288 { 289 // heigth is specified 290 destHeight = height; 291 292 if (width > 0) 293 { 294 // additionnally, width is also specified 295 destWidth = width; 296 keepAspectRatio = false; 297 } 298 else 299 { 300 // width is computed 301 destWidth = srcWidth * destHeight / srcHeight; 302 } 303 } 304 else if (width > 0) 305 { 306 // width is specified, height is computed 307 destWidth = width; 308 destHeight = srcHeight * destWidth / srcWidth; 309 } 310 else if (maxHeight > 0) 311 { 312 if (maxWidth > 0) 313 { 314 if (srcHeight <= maxHeight && srcWidth <= maxWidth) 315 { 316 // the source image is already smaller than the destination box 317 return src; 318 } 319 320 destWidth = maxWidth; 321 destHeight = maxHeight; 322 } 323 else 324 { 325 if (srcHeight <= maxHeight) 326 { 327 // the source image is already smaller than the destination box 328 return src; 329 } 330 331 destHeight = maxHeight; 332 destWidth = srcWidth * destHeight / srcHeight; 333 } 334 } 335 else if (maxWidth > 0) 336 { 337 if (srcWidth <= maxWidth) 338 { 339 // the source image is already smaller than the destination box 340 return src; 341 } 342 343 destWidth = maxWidth; 344 destHeight = srcHeight * destWidth / srcWidth; 345 } 346 else 347 { 348 // No resize is required 349 return src; 350 } 351 352 if (destHeight == srcHeight && destWidth == srcWidth) 353 { 354 // already the good format, don't change anything 355 return src; 356 } 357 358 return Thumbnails.of(src).size(destWidth, destHeight).keepAspectRatio(keepAspectRatio).imageType(src.getColorModel().hasAlpha() ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB).asBufferedImage(); 359 } 360}