View Javadoc
1   /*
2    * Copyright (c) 2002-2022, City of Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.plugins.galleryimage.util;
35  
36  import org.apache.commons.fileupload.FileItem;
37  import org.apache.commons.fileupload.FileItemFactory;
38  import org.apache.commons.fileupload.disk.DiskFileItemFactory;
39  import org.apache.commons.imaging.ImageInfo;
40  import org.apache.commons.imaging.ImageParser;
41  import org.apache.commons.imaging.Imaging;
42  import org.apache.commons.imaging.formats.bmp.BmpImageParser;
43  import org.apache.commons.imaging.formats.dcx.DcxImageParser;
44  import org.apache.commons.imaging.formats.gif.GifImageParser;
45  import org.apache.commons.imaging.formats.pcx.PcxImageParser;
46  import org.apache.commons.imaging.formats.png.PngImageParser;
47  import org.apache.commons.imaging.formats.tiff.TiffImageParser;
48  import org.apache.commons.imaging.formats.wbmp.WbmpImageParser;
49  import org.apache.commons.imaging.formats.xbm.XbmImageParser;
50  import org.apache.commons.imaging.formats.xpm.XpmImageParser;
51  import org.apache.commons.lang3.StringUtils;
52  import org.imgscalr.Scalr;
53  
54  import fr.paris.lutece.portal.service.image.ImageResource;
55  import fr.paris.lutece.portal.service.util.AppLogService;
56  
57  import java.awt.Graphics;
58  import java.awt.Image;
59  import java.awt.image.BufferedImage;
60  
61  import java.io.ByteArrayOutputStream;
62  import java.io.IOException;
63  import java.io.OutputStream;
64  import java.util.Base64;
65  import java.util.Iterator;
66  
67  import javax.imageio.ImageIO;
68  import javax.imageio.ImageReader;
69  import javax.imageio.stream.ImageInputStream;
70  
71  /**
72   * Image utils class
73   */
74  public final class ImageUtils
75  {
76      /** Parameter JPG */
77      private static final String PARAMETER_JPG = "jpg";
78  
79      /**
80       * Private constructor
81       */
82      private ImageUtils( )
83      {
84      }
85  
86      /**
87       * resizeImage
88       * 
89       * @param fileItem
90       * @param width
91       * @return fileItem
92       */
93      public static FileItem resizeImage( FileItem fileItem, int width )
94      {
95          // Crop image if needed
96          try
97          {
98              ByteArrayOutputStream out = new ByteArrayOutputStream( );
99              BufferedImage image = ImageIO.read( fileItem.getInputStream( ) );
100             BufferedImage resizedImage = Scalr.resize( image, Scalr.Mode.FIT_TO_WIDTH, width );
101             ImageIO.write( resizedImage, PARAMETER_JPG, out );
102 
103             return createFileItem( out.toByteArray( ), fileItem.getFieldName( ), fileItem.getContentType( ), fileItem.getName( ) );
104         }
105         catch( Exception e )
106         {
107             AppLogService.error( "ImageUtils:resizeImage( ): {} ", e.getMessage( ), e );
108         }
109         return fileItem;
110     }
111 
112     /**
113      * Create a FileItem with the specfied content bytes.
114      */
115     private static FileItem createFileItem( byte [ ] contentBytes, String strFieldName, String strContentType, String strFileName )
116     {
117         FileItemFactory factory = new DiskFileItemFactory( );
118         FileItem item = factory.createItem( strFieldName, strContentType, false, strFileName );
119 
120         try
121         {
122             OutputStream os = item.getOutputStream( );
123             os.write( contentBytes );
124             os.close( );
125         }
126         catch( IOException e )
127         {
128             AppLogService.error( "ImageUtils:createFileItem( ): {} ", e.getMessage( ), e );
129         }
130         return item;
131     }
132 
133     /**
134      * Check if the parameter fileItem is safe
135      * 
136      * @param fileItem
137      * @return true if file is safe
138      */
139     public static boolean safeImage( FileItem fileItem )
140     {
141         boolean safeState = false;
142         boolean fallbackOnApacheCommonsImaging;
143         try
144         {
145             if ( fileItem != null )
146             {
147                 // Get the image format
148                 String formatName;
149                 try ( ImageInputStream iis = ImageIO.createImageInputStream( fileItem.getInputStream( ) ) )
150                 {
151                     Iterator<ImageReader> imageReaderIterator = ImageIO.getImageReaders( iis );
152                     // If there not ImageReader instance found so it's means that the current
153                     // format is not supported by the Java built-in API
154                     if ( !imageReaderIterator.hasNext( ) )
155                     {
156                         ImageInfo imageInfo = Imaging.getImageInfo( fileItem.get( ) );
157                         if ( imageInfo != null && imageInfo.getFormat( ) != null && imageInfo.getFormat( ).getName( ) != null )
158                         {
159                             formatName = imageInfo.getFormat( ).getName( );
160                             fallbackOnApacheCommonsImaging = true;
161                         }
162                         else
163                         {
164                             throw new IOException( "Format of the original image is " + "not supported for read operation !" );
165                         }
166                     }
167                     else
168                     {
169                         ImageReader reader = imageReaderIterator.next( );
170                         formatName = reader.getFormatName( );
171                         fallbackOnApacheCommonsImaging = false;
172                     }
173                 }
174 
175                 // Load the image
176                 BufferedImage originalImage;
177                 if ( !fallbackOnApacheCommonsImaging )
178                 {
179                     originalImage = ImageIO.read( fileItem.getInputStream( ) );
180                 }
181                 else
182                 {
183                     originalImage = Imaging.getBufferedImage( fileItem.getInputStream( ) );
184                 }
185 
186                 // Check that image has been successfully loaded
187                 if ( originalImage == null )
188                 {
189                     throw new IOException( "Cannot load the original image !" );
190                 }
191 
192                 // Get current Width and Height of the image
193                 int originalWidth = originalImage.getWidth( null );
194                 int originalHeight = originalImage.getHeight( null );
195 
196                 // Resize the image by removing 1px on Width and Height
197                 Image resizedImage = originalImage.getScaledInstance( originalWidth - 1, originalHeight - 1, Image.SCALE_SMOOTH );
198 
199                 // Resize the resized image by adding 1px on Width and Height
200                 // In fact set image to is initial size
201                 Image initialSizedImage = resizedImage.getScaledInstance( originalWidth, originalHeight, Image.SCALE_SMOOTH );
202 
203                 // Save image by overwriting the provided source file content
204                 BufferedImage sanitizedImage = new BufferedImage( initialSizedImage.getWidth( null ), initialSizedImage.getHeight( null ),
205                         BufferedImage.TYPE_INT_RGB );
206                 Graphics bg = sanitizedImage.getGraphics( );
207                 bg.drawImage( initialSizedImage, 0, 0, null );
208                 bg.dispose( );
209 
210                 try ( OutputStream fos = new ByteArrayOutputStream( ) )
211                 {
212                     if ( !fallbackOnApacheCommonsImaging )
213                     {
214                         ImageIO.write( sanitizedImage, formatName, fos );
215                     }
216                     else
217                     {
218                         ImageParser imageParser;
219                         // Handle only formats for which Apache Commons Imaging can successfully write
220                         // (YES in Write column of the reference link) the image format
221                         // See reference link in the class header
222                         switch( formatName )
223                         {
224                             case "TIFF":
225                             {
226                                 imageParser = new TiffImageParser( );
227                                 break;
228                             }
229                             case "PCX":
230                             {
231                                 imageParser = new PcxImageParser( );
232                                 break;
233                             }
234                             case "DCX":
235                             {
236                                 imageParser = new DcxImageParser( );
237                                 break;
238                             }
239                             case "BMP":
240                             {
241                                 imageParser = new BmpImageParser( );
242                                 break;
243                             }
244                             case "GIF":
245                             {
246                                 imageParser = new GifImageParser( );
247                                 break;
248                             }
249                             case "PNG":
250                             {
251                                 imageParser = new PngImageParser( );
252                                 break;
253                             }
254                             case "WBMP":
255                             {
256                                 imageParser = new WbmpImageParser( );
257                                 break;
258                             }
259                             case "XBM":
260                             {
261                                 imageParser = new XbmImageParser( );
262                                 break;
263                             }
264                             case "XPM":
265                             {
266                                 imageParser = new XpmImageParser( );
267                                 break;
268                             }
269                             default:
270                             {
271                                 throw new IOException( "Format of the original image is not" + " supported for write operation !" );
272                             }
273 
274                         }
275                         imageParser.writeImage( sanitizedImage, fos, null );
276                     }
277 
278                 }
279 
280                 // Set state flag
281                 safeState = true;
282             }
283         }
284         catch( Exception e )
285         {
286             safeState = false;
287             AppLogService.error( "Error during Image file processing ! {}", e.getMessage( ), e );
288         }
289 
290         return safeState;
291     }
292 
293     /**
294      * imageBase64
295      * @param imageResource
296      * @return
297      */
298     public static String imageBase64 ( ImageResource imageResource )
299     {
300         if ( imageResource != null )
301         {
302             return "data:" + imageResource.getMimeType( ) + ";base64," + Base64.getEncoder( ).encodeToString( imageResource.getImage( ) );
303         }
304         return StringUtils.EMPTY;
305     }
306 }