View Javadoc

1   package com.atlassian.core.util.thumbnail;
2   
3   import com.atlassian.core.util.ImageInfo;
4   import com.google.common.base.Predicates;
5   import junit.framework.TestCase;
6   import org.apache.log4j.Appender;
7   import org.apache.log4j.Level;
8   import org.apache.log4j.Logger;
9   import org.apache.log4j.spi.LoggingEvent;
10  import org.mockito.ArgumentCaptor;
11  
12  import javax.imageio.ImageIO;
13  import java.awt.*;
14  import java.awt.image.BufferedImage;
15  import java.io.File;
16  import java.io.FileInputStream;
17  import java.io.IOException;
18  
19  import static org.mockito.Mockito.mock;
20  import static org.mockito.Mockito.spy;
21  import static org.mockito.Mockito.verify;
22  
23  /**
24   * This tests vary on the level of abstraction tested.
25   * The reason is that I did not want to change public interface (method visibility) of Thumber class (in order to
26   * keep clients compatible).
27   * Thus sometimes higher level methods are tested and the test are not really unit -> e.g. testing
28   * {@link com.atlassian.core.util.thumbnail.Thumber#retrieveOrCreateThumbNail(java.io.InputStream, String, java.io.File, int, int, long)}
29   */
30  public class ThumberTest extends TestCase
31  {
32  	private static final String[] CMYK_IMAGES = new String[] {
33  			"/cmyk/CMYK Coated FOGRA27 (ISO 12647-2-2004).jpg",
34  			"/cmyk/CMYK Coated FOGRA39 (ISO 12647-2-2004).jpg",
35  			"/cmyk/CMYK Euroscale Coated v2.jpg",
36  			"/cmyk/CMYK Euroscale Uncoated v2.jpg",
37  			"/cmyk/CMYK Generic Profile.jpg",
38  			"/cmyk/CMYK Japan Color 2001 Coated.jpg",
39  			"/cmyk/CMYK Japan Color 2001 Uncoated.jpg",
40  			"/cmyk/CMYK Japan Color 2002 Newspaper.jpg",
41  			"/cmyk/CMYK Japan Web Coated (Ad).jpg",
42  			"/cmyk/CMYK No Profile.jpg",
43  			"/cmyk/CMYK Uncoated FOGRA29 (ISO 12647-2-2004).jpg",
44  			"/cmyk/CMYK US Sheetfed Coated v2.jpg",
45  			"/cmyk/CMYK US Sheetfed Uncoated v2.jpg",
46  			"/cmyk/CMYK US Web Coated (SWOP) v2.jpg",
47  			"/cmyk/CMYK US Web Uncoated v2.jpg",
48  			"/cmyk/CMYK Web Coated FOGRA28 (ISO 12647-2-2004).jpg",
49  	};
50  
51      private static final int MAX_WIDTH = 300;
52      private static final int MAX_HEIGHT = 400;
53      private final File tempFile;
54      private static final String TEST_FILE_NAME = "mypicture.png";
55  
56      public ThumberTest() throws IOException
57      {
58          tempFile = File.createTempFile("atlassian-core-thumbnail", "test");
59      }
60  
61      @Override
62      protected void setUp() throws Exception
63      {
64          //noinspection ResultOfMethodCallIgnored
65          tempFile.delete(); // this file must not exist, other wise thumber won't try to scale the image
66      }
67  
68      @Override
69      protected void tearDown() throws Exception
70      {
71          //noinspection ResultOfMethodCallIgnored
72          tempFile.delete();
73      }
74  
75      public void testRetrieveOrCreateThumbnailTransparentPng() throws IOException
76      {
77          Thumber thumber = new Thumber(Thumbnail.MimeType.PNG);
78          testRetrieveOrCreateThumbnail(thumber, "/transparent-png.png", Transparency.TRANSLUCENT, ImageInfo.FORMAT_PNG);
79      }
80  
81      public void testRetrieveOrCreateThumbnailBrokenGif() throws IOException
82      {
83          Appender appender = mock(Appender.class);
84          Logger.getRootLogger().addAppender(appender);
85          try
86          {
87              ArgumentCaptor arguments = ArgumentCaptor.forClass(LoggingEvent.class);
88              Thumber thumber = spy(new Thumber());
89              //broken.gif is no longer broken for Thumber - JAI has high tolerance for broken files, read some text file instead!
90              assertNull(thumber.retrieveOrCreateThumbNail(getClass().getResourceAsStream("/NotAnImage.jpg"), TEST_FILE_NAME, tempFile, MAX_WIDTH, MAX_HEIGHT, 1));
91              verify(appender).doAppend((LoggingEvent) arguments.capture());
92  
93              final LoggingEvent loggingEvent = (LoggingEvent) arguments.getValue();
94              assertEquals(Level.ERROR, loggingEvent.getLevel());
95              assertEquals("com.atlassian.core.util.thumbnail.Thumber", loggingEvent.categoryName);
96              assertEquals("Unable to create thumbnail image for id 1", loggingEvent.getMessage());
97          }
98          finally
99          {
100             Logger.getRootLogger().removeAppender(appender);
101         }
102     }
103 
104     public void testRetrieveOrCreateThumbnailOpaqueGif() throws IOException
105     {
106         Thumber thumber = new Thumber();
107         testRetrieveOrCreateThumbnail(thumber, "/opaque-gif.gif", Transparency.OPAQUE, ImageInfo.FORMAT_JPEG);
108     }
109 
110     public void testRetrieveOrCreateThumbnailOpaqueGifAsStream() throws IOException
111     {
112         Thumber thumber = new Thumber();
113         testRetrieveOrCreateThumbnailAsStream(thumber, "/opaque-gif.gif", Transparency.OPAQUE, ImageInfo.FORMAT_JPEG);
114     }
115 
116     public void testRetrieveOrCreateThumbnailOpaquePng() throws IOException
117     {
118         Thumber thumber = new Thumber();
119         testRetrieveOrCreateThumbnail(thumber, "/opaque-png.png", Transparency.OPAQUE, ImageInfo.FORMAT_JPEG);
120     }
121 
122     public void testRetrieveOrCreateThumbnailOpaquePngAsStream() throws IOException
123     {
124         Thumber thumber = new Thumber();
125         testRetrieveOrCreateThumbnailAsStream(thumber, "/opaque-png.png", Transparency.OPAQUE, ImageInfo.FORMAT_JPEG);
126     }
127 
128     public void testRetrieveOrCreateThumbnailJpg() throws IOException
129     {
130         Thumber thumber = new Thumber();
131         testRetrieveOrCreateThumbnail(thumber, "/test-jpg.jpg", Transparency.OPAQUE, ImageInfo.FORMAT_JPEG);
132     }
133 
134     public void testRetrieveOrCreateThumbnailJpgAsStream() throws IOException
135     {
136         Thumber thumber = new Thumber();
137         testRetrieveOrCreateThumbnailAsStream(thumber, "/test-jpg.jpg", Transparency.OPAQUE, ImageInfo.FORMAT_JPEG);
138     }
139 
140     public void testRetrieveOrCreateThumbnailTransparentGif() throws IOException
141     {
142         Thumber thumber = new Thumber(Thumbnail.MimeType.PNG);
143         testRetrieveOrCreateThumbnail(thumber, "/transparent-gif.gif", Transparency.TRANSLUCENT, ImageInfo.FORMAT_PNG);
144     }
145 
146     public void testRetrieveOrCreateThumbnailTransparentGifAsStream() throws IOException
147     {
148         Thumber thumber = new Thumber(Thumbnail.MimeType.PNG);
149         //Streaming git image changes the transparency
150         testRetrieveOrCreateThumbnailAsStream(thumber, "/transparent-gif.gif", Transparency.BITMASK, ImageInfo.FORMAT_PNG);
151     }
152 
153     public void testRetrieveOrCreateThumbnailCMYKJpg() throws IOException {
154         Thumber thumber = new Thumber();
155 
156         final Thumbnail thumbnail = thumber.retrieveOrCreateThumbNail(
157                 getClass().getResourceAsStream("/cmyk.jpg"), TEST_FILE_NAME,
158                 tempFile, MAX_WIDTH, MAX_HEIGHT, 1);
159 
160         //Cannot use: testRetrieveOrCreateThumbnail, because ImageIO cannot read CMYK images
161         //Passing hardcoded test image width and height
162         assertThumbnail(thumbnail, Transparency.OPAQUE, ImageInfo.FORMAT_JPEG,
163 				MAX_WIDTH, MAX_WIDTH * 1134 / 1134);
164     }
165 
166 	public void testAllCmykImagesShouldBeLoaded() throws Exception {
167 		Thumber thumber = new Thumber();
168 
169 		for (String cmykImage : CMYK_IMAGES) {
170 			final Thumbnail thumbnail = thumber.retrieveOrCreateThumbNail(
171 					getClass().getResourceAsStream(cmykImage), TEST_FILE_NAME,
172 					tempFile, MAX_WIDTH, MAX_HEIGHT, 1);
173 			assertNotNull(thumbnail);
174 		}
175 	}
176 
177     public void testScaleImageForNonBufferedImage()
178     {
179         // this should normally produce ToolkitImage
180         final Image image = Toolkit.getDefaultToolkit().getImage(getClass().getResource("/opaque-gif.gif"));
181         final BufferedImage thumbnail = new Thumber().scaleImage(image,
182                 new Thumber.WidthHeightHelper(MAX_WIDTH, MAX_HEIGHT));
183 
184         // scaleImage ignores aspect ratio -> always produces desired with & height
185         assertEquals(MAX_WIDTH, thumbnail.getWidth());
186         assertEquals(MAX_HEIGHT, thumbnail.getHeight());
187         assertEquals(Transparency.OPAQUE, thumbnail.getTransparency());
188     }
189 
190     public void testScaleImageForNonBufferedImageTransparentGif()
191     {
192         // this should normally produce ToolkitImage
193         final Image image = Toolkit.getDefaultToolkit().getImage(getClass().getResource("/transparent-gif.gif"));
194         final BufferedImage thumbnail = new Thumber().scaleImage(image,
195                 new Thumber.WidthHeightHelper(MAX_WIDTH, MAX_HEIGHT));
196 
197         // scaleImage ignores aspect ratio -> always produces desired with & height
198         assertEquals(MAX_WIDTH, thumbnail.getWidth());
199         assertEquals(MAX_HEIGHT, thumbnail.getHeight());
200         assertEquals(Transparency.TRANSLUCENT, thumbnail.getTransparency());
201     }
202 
203     public void testUpscalingShouldReturnOriginalSize() throws IOException
204     {
205         final String resourceName = "/opaque-png.png";
206         final BufferedImage image = ImageIO.read(getClass().getResourceAsStream(resourceName));
207         final int width = image.getWidth();
208         final int height = image.getHeight();
209         final Thumbnail thumbnail = new Thumber().retrieveOrCreateThumbNail(
210                 getClass().getResourceAsStream(resourceName), TEST_FILE_NAME,
211                 tempFile, width * 3, height * 3, 1);
212 
213         assertThumbnail(thumbnail, Transparency.OPAQUE, ImageInfo.FORMAT_JPEG, width, height);
214     }
215 
216     public void testScaleImageUpscaling() throws IOException
217     {
218         final String resourceName = "/opaque-png.png";
219         final BufferedImage image = ImageIO.read(getClass().getResourceAsStream(resourceName));
220         final int width = image.getWidth();
221         final int height = image.getHeight();
222         assertScaleImage(image, width * 3, height * 3);
223         assertScaleImage(image, width - 10, height + 100);
224         assertScaleImage(image, width + 100, height - 20);
225     }
226 
227     public void testScaleImageWithInvalidParams() throws IOException
228     {
229         final String resourceName = "/opaque-png.png";
230         final BufferedImage image = ImageIO.read(getClass().getResourceAsStream(resourceName));
231         try
232         {
233             assertScaleImage(image, -30, -20);
234             fail(IllegalArgumentException.class.getName() + " expected");
235         }
236         catch (IllegalArgumentException ignore)
237         {
238             // this is expected
239         }
240     }
241 
242     private void assertScaleImage(final BufferedImage image, int aWidth, int aHeight)
243     {
244         final BufferedImage thumbnail = new Thumber().scaleImage(image, new Thumber.WidthHeightHelper(aWidth,
245                 aHeight));
246         assertEquals(aWidth, thumbnail.getWidth());
247         assertEquals(aHeight, thumbnail.getHeight());
248         assertEquals(image.getTransparency(), thumbnail.getTransparency());
249     }
250 
251     private void testRetrieveOrCreateThumbnail(Thumber thumber, String imageResourceName, int expectedTransparency, int expectedFormat) throws IOException
252     {
253         final Thumbnail thumbnail = thumber.retrieveOrCreateThumbNail(
254                 getClass().getResourceAsStream(imageResourceName), TEST_FILE_NAME,
255                 tempFile, MAX_WIDTH, MAX_HEIGHT, 1);
256 
257         assertNotNull(thumbnail);
258         final BufferedImage image = ImageIO.read(getClass().getResourceAsStream(imageResourceName));
259 
260         assertThumbnail(thumbnail, expectedTransparency, expectedFormat,
261                 MAX_WIDTH, MAX_WIDTH * image.getHeight() / image.getWidth());
262     }
263 
264     private void testRetrieveOrCreateThumbnailAsStream(Thumber thumber, String imageResourceName, int expectedTransparency, int expectedFormat) throws IOException
265     {
266         final Thumbnail thumbnail = thumber.retrieveOrCreateThumbNail(
267                 getClass().getResourceAsStream(imageResourceName), TEST_FILE_NAME,
268                 tempFile, MAX_WIDTH, MAX_HEIGHT, 1, Predicates.<Dimensions>alwaysFalse());
269 
270         assertNotNull(thumbnail);
271         final BufferedImage image = ImageIO.read(getClass().getResourceAsStream(imageResourceName));
272 
273         assertThumbnail(thumbnail, expectedTransparency, expectedFormat,
274                 MAX_WIDTH, MAX_WIDTH * image.getHeight() / image.getWidth());
275     }
276 
277     private void assertThumbnail(final Thumbnail thumbnail, final int expectedTransparency, final int expectedFormat,
278             int expectedWidth, int expectedHeight) throws IOException
279     {
280         assertEquals(expectedWidth, thumbnail.getWidth());
281         assertEquals(expectedHeight, thumbnail.getHeight());
282         assertEquals(TEST_FILE_NAME, thumbnail.getFilename());
283 
284         final BufferedImage thumbnailImage = ImageIO.read(tempFile);
285         assertEquals(expectedWidth, thumbnailImage.getWidth());
286         assertEquals(expectedHeight, thumbnailImage.getHeight());
287         assertEquals(expectedTransparency, thumbnailImage.getTransparency());
288         assertImageType(tempFile, expectedFormat);
289     }
290 
291 
292     private void assertImageType(final File tempFile, int format) throws IOException
293     {
294         final ImageInfo imageInfo = new ImageInfo();
295         final FileInputStream fis = new FileInputStream(tempFile);
296         imageInfo.setInput(fis);
297         assertTrue(imageInfo.check());
298         assertEquals(format, imageInfo.getFormat());
299         fis.close();
300     }
301 }