001 /*
002 Copyright (c) 1996-2012, Damon Hart-Davis
003 All rights reserved.
004
005 Redistribution and use in source and binary forms, with or without
006 modification, are permitted provided that the following conditions are
007 met:
008
009 * Redistributions of source code must retain the above copyright
010 notice, this list of conditions and the following disclaimer.
011
012 * Redistributions in binary form must reproduce the above copyright
013 notice, this list of conditions and the following disclaimer in the
014 documentation and/or other materials provided with the
015 distribution.
016
017 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
018 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
019 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
020 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
021 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
022 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
023 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
024 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
025 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
026 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
027 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028 */
029 /*
030 * Created by IntelliJ IDEA.
031 * User: Administrator
032 * Date: 28-Dec-02
033 * Time: 22:24:51
034 * To change template for new class use
035 * Code Style | Class Templates options (Tools | IDE Options).
036 */
037 package org.hd.d.pg2k.test.dev;
038
039 import java.awt.Dimension;
040 import java.awt.image.BufferedImage;
041 import java.awt.image.DataBuffer;
042 import java.awt.image.IndexColorModel;
043 import java.io.ByteArrayInputStream;
044 import java.io.DataInputStream;
045 import java.io.File;
046 import java.io.FileInputStream;
047 import java.io.IOException;
048 import java.util.Random;
049 import java.util.Set;
050 import java.util.TreeSet;
051
052 import junit.framework.TestCase;
053
054 import org.hd.d.pg2k.svrCore.AllExhibitProperties;
055 import org.hd.d.pg2k.svrCore.ExhibitStaticAttr;
056 import org.hd.d.pg2k.svrCore.ExhibitThumbnails;
057 import org.hd.d.pg2k.svrCore.ImageUtils;
058 import org.hd.d.pg2k.svrCore.Name;
059 import org.hd.d.pg2k.svrCore.TextUtils;
060 import org.hd.d.pg2k.svrCore.MIME.ExhibitMIME;
061 import org.hd.d.pg2k.svrCore.MIME.Handler;
062 import org.hd.d.pg2k.svrCore.MIME.Quantize;
063 import org.hd.d.pg2k.svrCore.datasource.ExhibitDataFileSource;
064 import org.hd.d.pg2k.svrCore.props.LocalProps;
065 import org.w3c.dom.Node;
066
067 /**Tests of media-generation classes, eg for making thumbnails.
068 * This will test features such as transcoding, etc.
069 * <p>
070 * Also tests some relevant features of the ExhibitMIME class.
071 */
072 public final class MediaHandlerTest extends TestCase
073 {
074 public MediaHandlerTest(final String name)
075 {
076 super(name);
077 }
078
079 /**Test some basic features of ExhibitMIME.
080 * Including all exhibit type values being in range, in order, and unique.
081 */
082 public void testExhibitMIMEBasics()
083 {
084 // Make sure that all types within the max/min limits.
085 final ExhibitMIME.ExhibitTypeParameters[] allTypes = ExhibitMIME.getAllValidExhibitTypes();
086 assertTrue("ExhibitMIME.getAllValidExhibitTypes() must not return null", allTypes != null);
087
088 int prevType = ExhibitMIME.ET__max + 1; // Previous type value.
089 for(int i = allTypes.length; --i >= 0; )
090 {
091 final ExhibitMIME.ExhibitTypeParameters etp = allTypes[i];
092 final int t = etp.type;
093 assertTrue("All ExhibitTypeParameters values must be in max/min range",
094 ((t >= ExhibitMIME.ET__min) && (t <= ExhibitMIME.ET__max)));
095 assertTrue("All ExhibitTypeParameters values must unique and in order",
096 t < prevType);
097 prevType = t;
098 // assertTrue("Minimum format overhead should be no smaller than magic",
099 // (etp.handler == null) ||
100 // (etp.handler.get() >= etp.magic.length()));
101 }
102 }
103
104 /**Does some basic tests on the JPG (JPEG) handler.
105 * For example, make sure that it always exists.
106 */
107 public void testJPGBasics()
108 throws Exception
109 {
110 // Check that we have a .jpg handler.
111 final ExhibitMIME.ExhibitTypeParameters type = ExhibitMIME.getParamsByType(ExhibitMIME.ET_JPEG);
112 final Handler h = type.handler;
113 assertTrue("Must always have a valid JPEG (.jpg) handler", h != null);
114 // Check that .jpg handler is prepared to try to make thumbnails.
115 assertTrue("JPEG (.jpg) handler should be prepared to make thumbnails", h.canMakeThumbnails());
116
117 // Test that we can create a big image.
118 final BufferedImage bigImage = makeTestRGBTrueColourBufferedImage(
119 bigImageWidth, bigImageHeight);
120 final byte[] bigJPEGImage =
121 h.makeImageBinary(bigImage, 95);
122 //makeJPEGImage(bigImageWidth, bigImageHeight);
123 assertTrue("Must be able to create JPEG image from BufferedImage",
124 bigJPEGImage != null);
125 assertTrue("Must be able to create valid JPEG image from BufferedImage",
126 ExhibitMIME.magicOK(type, new ByteArrayInputStream(bigJPEGImage)));
127 assertTrue("Encoded image should be much bigger than 1kByte",
128 bigJPEGImage.length > 1024);
129
130 final ExhibitThumbnails tn = h.makeThumbnails(
131 new ByteArrayInputStream(bigJPEGImage),
132 bigJPEGImage.length
133 );
134 assertTrue("Must be able to make JPEG thumbnails of some sort",
135 tn != null);
136 assertTrue("Must be able to make small JPEG thumbnail",
137 tn.getSmall() != null);
138 assertTrue("Must be able to make standard JPEG thumbnail",
139 tn.getStandard() != null);
140 assertTrue("Small thumbnail must be smaller than standard thumbnail",
141 tn.getSmall().size() < tn.getStandard().size());
142 assertTrue("Standard thumbnail must be smaller than original",
143 tn.getStandard().size() < bigJPEGImage.length);
144 assertTrue("Small thumbnail must be no larger than size limit",
145 tn.getSmall().size() < ExhibitThumbnails.SML_ABS_MAX_BYTES);
146 assertTrue("Standard thumbnail must be no larger than size limit",
147 tn.getStandard().size() < ExhibitThumbnails.STD_ABS_MAX_BYTES);
148
149 // Fetch the metadata.
150 // We expect this to be non-null for JPEG.
151 final Node metadata = h.getMetadata(new ByteArrayInputStream(bigJPEGImage), Name.ExhibitFull.create("a/dummyName-X.jp2"));
152 assertNotNull("Standard meta-data should always exist for JPEG", metadata);
153 System.out.println(TextUtils.toXML(metadata, false, false));
154 }
155
156 /**Does some basic tests on the JP2 (JPEG 2000) handler.
157 * For example, make sure that it always exists.
158 */
159 public void testJP2Basics()
160 throws Exception
161 {
162 // Check that we have a .jp2 handler.
163 final ExhibitMIME.ExhibitTypeParameters type = ExhibitMIME.getParamsByType(ExhibitMIME.ET_JP2);
164 final Handler h = type.handler;
165 assertTrue("Must always have a valid JPEG 2000 (.jp2) handler", h != null);
166
167 // Test that we can create a big image.
168 final BufferedImage bigImage = makeTestRGBTrueColourBufferedImage(
169 bigImageWidth, bigImageHeight);
170 final byte[] bigJPEG2000Image =
171 h.makeImageBinary(bigImage, 95);
172 assertTrue("Must be able to create JPEG 2000 image from BufferedImage",
173 bigJPEG2000Image != null);
174 assertTrue("Must be able to create valid JPEG 2000 image from BufferedImage",
175 ExhibitMIME.magicOK(type, new ByteArrayInputStream(bigJPEG2000Image)));
176 assertTrue("Encoded image should be much bigger than 1kByte",
177 bigJPEG2000Image.length > 1024);
178
179 // Fetch the metadata.
180 // We expect this to be non-null for JPEG.
181 final Node metadata = h.getMetadata(new ByteArrayInputStream(bigJPEG2000Image), Name.ExhibitFull.create("a/dummyName-X.jp2"));
182 assertNotNull("Standard meta-data should always exist for JPEG 2000", metadata);
183 System.out.println(TextUtils.toXML(metadata, false, false));
184 }
185
186 /**Does some basic tests on the GIF handler.
187 * Because of the Unisys LZW patent issues until 2004 this may not initially
188 * be prepared to make thumbnails.
189 */
190 public void testGIFBasics()
191 {
192 // Check that we have a .gif handler.
193 final Handler h = ExhibitMIME.getParamsByType(ExhibitMIME.ET_GIF).handler;
194 assertTrue("Should have a valid GIF (.gif) handler", h != null);
195 // Check that handler is prepared to try to make thumbnails.
196 assertTrue("GIF handler should be prepared to make thumbnails", h.canMakeThumbnails());
197 }
198
199 /**Does some basic tests on the PNG handler.
200 */
201 public void testPNGBasics()
202 {
203 // Check that we have a .png handler.
204 final Handler h = ExhibitMIME.getParamsByType(ExhibitMIME.ET_PNG).handler;
205 assertTrue("Should have a valid PNG (.png) handler", h != null);
206 // Check that handler is prepared to try to make thumbnails.
207 assertTrue("PNG handler should be prepared to make thumbnails", h.canMakeThumbnails());
208 }
209
210 /**Test thumbnail generation of a given set of exhibit types.
211 * In passing, tests that images can be generated at all.
212 */
213 public void testThumbnailGeneration()
214 throws Exception
215 {
216 final int typesToTest[] =
217 { ExhibitMIME.ET_PNG, ExhibitMIME.ET_JPEG, ExhibitMIME.ET_GIF };
218
219 for(int i = typesToTest.length; --i >= 0; )
220 {
221 final int t = typesToTest[i];
222
223 // Check that he have a handler.
224 final ExhibitMIME.ExhibitTypeParameters type = ExhibitMIME.getParamsByType(t);
225 final Handler h = type.handler;
226 assertTrue("Must always have a valid handler for t = " + t, h != null);
227 // Check that handler is prepared to try to make thumbnails.
228 assertTrue("Handler should be prepared to make thumbnails: " + type.suffixForInputFile, h.canMakeThumbnails());
229
230 // Test that we can create a big image.
231 // Note that we use an indexed image for GIF tests.
232 final BufferedImage bigImage = (t == ExhibitMIME.ET_GIF) ?
233 makeTestRGBIndexedColourBufferedImage(bigImageWidth, bigImageHeight) :
234 makeTestRGBTrueColourBufferedImage(bigImageWidth, bigImageHeight);
235 final int bigImageLongestEdge = Math.max(bigImageWidth, bigImageHeight);
236 final byte[] bigEncodedImage =
237 h.makeImageBinary(bigImage, 90);
238 //makeJPEGImage(bigImageWidth, bigImageHeight);
239 assertTrue("Must be able to create image from BufferedImage",
240 bigEncodedImage != null);
241 assertTrue("Must be able to create valid image from BufferedImage",
242 ExhibitMIME.magicOK(type, new ByteArrayInputStream(bigEncodedImage)));
243 assertTrue("Encoded image should be much bigger than 1kByte",
244 bigEncodedImage.length > 1024);
245
246 final ExhibitThumbnails tn = h.makeThumbnails(
247 new ByteArrayInputStream(bigEncodedImage),
248 bigEncodedImage.length);
249 assertTrue("Must be able to make thumbnails of some sort",
250 tn != null);
251 assertTrue("Must be able to make small thumbnail",
252 tn.getSmall() != null);
253 assertTrue("Must be able to make standard thumbnail",
254 tn.getStandard() != null);
255 assertTrue("Small thumbnail must be smaller than standard thumbnail",
256 tn.getSmall().size() < tn.getStandard().size());
257 assertTrue("Standard thumbnail should be smaller than original",
258 tn.getStandard().size() < bigEncodedImage.length);
259 assertTrue("Small thumbnail must be no larger than size limit",
260 tn.getSmall().size() < ExhibitThumbnails.SML_ABS_MAX_BYTES);
261 assertTrue("Standard thumbnail must be no larger than size limit",
262 tn.getStandard().size() < ExhibitThumbnails.STD_ABS_MAX_BYTES);
263 final Dimension stdTNDim = h.get2DImageDimensions(
264 new ByteArrayInputStream(tn.getStandard().toByteArrray()));
265 final int stdTNLongestEdge = Math.max(stdTNDim.width, stdTNDim.height);
266 assertTrue("Standard thumbnail should have correct longest edge",
267 stdTNLongestEdge == Math.min(ExhibitThumbnails.STD_STATIC_IMAGE_TN_LDIM_PX,
268 bigImageLongestEdge));
269 final Dimension smlTNDim = h.get2DImageDimensions(
270 new ByteArrayInputStream(tn.getSmall().toByteArrray()));
271 final int smlTNLongestEdge = Math.max(smlTNDim.width, smlTNDim.height);
272 assertTrue("Small thumbnail should have correct longest edge",
273 smlTNLongestEdge == Math.min(ExhibitThumbnails.SML_STATIC_IMAGE_TN_LDIM_PX,
274 bigImageLongestEdge)); }
275 }
276
277 /**Test ability to accurately extract image dimensions from raw binaries.
278 * In passing, we will make sure that the overhead for each format is
279 * no larger than the (presumed valid, and very small) test images.
280 */
281 public void testDimensionExtraction()
282 throws IOException
283 {
284 // Dimensions for random-sized JPEG
285 // with the dimensions sometimes needing two bytes to hold.
286 final int jpegX = 64 + rnd.nextInt(512);
287 final int jpegY = 64 + rnd.nextInt(512);
288
289 // Test data: exhibit type, XxY dimensions, raw data as byte array.
290 final class DataRow
291 {
292 public final int exhibitType;
293 public final Dimension xyPixels;
294 /**Raw exhibit data. */
295 public final byte data[];
296
297 /**Construct a test case.
298 * Does not defensively copy data as assumed to be in
299 * benign environment.
300 */
301 DataRow(final int type, final Dimension xy, final byte rawData[])
302 {
303 exhibitType = type;
304 xyPixels = xy;
305 data = rawData;
306 }
307 }
308
309 final DataRow testData[] =
310 {
311 // Simple 2x1 GIF image.
312 new DataRow(ExhibitMIME.ET_GIF, new Dimension(2, 1), new byte[]{
313 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x2, 0x0,
314 0x1, 0x0, (byte) 0xb3, 0x0, 0x0, (byte) 0xff, (byte) 0x99, 0x66,
315 (byte) 0xff, (byte) 0x99, (byte) 0x99, (byte) 0xff, (byte) 0x99, (byte) 0xcc, (byte) 0xff, (byte) 0x99,
316 (byte) 0xff, (byte) 0xff, (byte) 0xcc, 0x0, (byte) 0xff, (byte) 0xcc, 0x33, (byte) 0xff,
317 (byte) 0xcc, 0x66, (byte) 0xff, (byte) 0xcc, (byte) 0x99, (byte) 0xff, (byte) 0xcc, (byte) 0xcc,
318 (byte) 0xff, (byte) 0xcc, (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x0, (byte) 0xff, (byte) 0xff,
319 0x33, (byte) 0xff, (byte) 0xff, 0x66, (byte) 0xff, (byte) 0xff, (byte) 0x99, (byte) 0xff,
320 (byte) 0xff, (byte) 0xcc, (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x2c, 0x0, 0x0,
321 0x0, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x4,
322 0x3, (byte) 0xf0, (byte) 0xbd, 0x8, 0x0, 0x3b
323 }),
324
325 // Simple 2x1 PNG image.
326 new DataRow(ExhibitMIME.ET_PNG, new Dimension(2, 1), new byte[]{
327 (byte) 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa,
328 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52,
329 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x1,
330 0x4, 0x3, 0x0, 0x0, 0x0, 0x6, 0xc, 0x62,
331 (byte) 0xb9, 0x0, 0x0, 0x0, 0x2c, 0x74, 0x45, 0x58,
332 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f,
333 0x6e, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x0, 0x54,
334 0x75, 0x65, 0x20, 0x32, 0x36, 0x20, 0x41, 0x75,
335 0x67, 0x20, 0x32, 0x30, 0x30, 0x33, 0x20, 0x32,
336 0x30, 0x3a, 0x31, 0x34, 0x3a, 0x35, 0x34, 0x20,
337 0x2d, 0x30, 0x30, 0x30, 0x30, (byte) 0xe5, (byte) 0xc3, 0x1c,
338 0x21, 0x0, 0x0, 0x0, 0x7, 0x74, 0x49, 0x4d,
339 0x45, 0x7, (byte) 0xd3, 0x8, 0x1a, 0x13, 0x14, 0x36,
340 (byte) 0xa8, (byte) 0xde, 0x67, (byte) 0xfe, 0x0, 0x0, 0x0, 0x9,
341 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xb, 0x12,
342 0x0, 0x0, 0xb, 0x12, 0x1, (byte) 0xd2, (byte) 0xdd, 0x7e,
343 (byte) 0xfc, 0x0, 0x0, 0x0, 0x4, 0x67, 0x41, 0x4d,
344 0x41, 0x0, 0x0, (byte) 0xb1, (byte) 0x8f, 0xb, (byte) 0xfc, 0x61,
345 0x5, 0x0, 0x0, 0x0, 0x3, 0x50, 0x4c, 0x54,
346 0x45, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xa7, (byte) 0xc4, 0x1b, (byte) 0xc8,
347 0x0, 0x0, 0x0, 0xa, 0x49, 0x44, 0x41, 0x54,
348 0x78, (byte) 0xda, 0x63, 0x60, 0x0, 0x0, 0x0, 0x2,
349 0x0, 0x1, (byte) 0xe5, 0x27, (byte) 0xde, (byte) 0xfc, 0x0, 0x0,
350 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, (byte) 0xae, 0x42,
351 0x60, (byte) 0x82
352 }),
353
354 // Simple 2x1 JPG image.
355 new DataRow(ExhibitMIME.ET_JPEG, new Dimension(2, 1), new byte[]{
356 (byte) 0xff, (byte) 0xd8, (byte) 0xff, (byte) 0xe0, 0x0, 0x10, 0x4a, 0x46,
357 0x49, 0x46, 0x0, 0x1, 0x1, 0x0, 0x1, 0x2c,
358 0x1, 0x2c, 0x0, 0x0, (byte) 0xff, (byte) 0xdb, 0x0, 0x43,
359 0x0, 0x6, 0x4, 0x5, 0x6, 0x5, 0x4, 0x6,
360 0x6, 0x5, 0x6, 0x7, 0x7, 0x6, 0x8, 0xa,
361 0x10, 0xa, 0xa, 0x9, 0x9, 0xa, 0x14, 0xe,
362 0xf, 0xc, 0x10, 0x17, 0x14, 0x18, 0x18, 0x17,
363 0x14, 0x16, 0x16, 0x1a, 0x1d, 0x25, 0x1f, 0x1a,
364 0x1b, 0x23, 0x1c, 0x16, 0x16, 0x20, 0x2c, 0x20,
365 0x23, 0x26, 0x27, 0x29, 0x2a, 0x29, 0x19, 0x1f,
366 0x2d, 0x30, 0x2d, 0x28, 0x30, 0x25, 0x28, 0x29,
367 0x28, (byte) 0xff, (byte) 0xdb, 0x0, 0x43, 0x1, 0x7, 0x7,
368 0x7, 0xa, 0x8, 0xa, 0x13, 0xa, 0xa, 0x13,
369 0x28, 0x1a, 0x16, 0x1a, 0x28, 0x28, 0x28, 0x28,
370 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
371 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
372 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
373 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
374 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
375 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, (byte) 0xff, (byte) 0xc0,
376 0x0, 0x11, 0x8, 0x0, 0x1, 0x0, 0x2, 0x3,
377 0x1, 0x22, 0x0, 0x2, 0x11, 0x1, 0x3, 0x11,
378 0x1, (byte) 0xff, (byte) 0xc4, 0x0, 0x15, 0x0, 0x1, 0x1,
379 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
380 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8,
381 (byte) 0xff, (byte) 0xc4, 0x0, 0x14, 0x10, 0x1, 0x0, 0x0,
382 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
383 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, (byte) 0xff, (byte) 0xc4,
384 0x0, 0x14, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0,
385 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
386 0x0, 0x0, 0x0, 0x0, (byte) 0xff, (byte) 0xc4, 0x0, 0x14,
387 0x11, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
388 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
389 0x0, 0x0, (byte) 0xff, (byte) 0xda, 0x0, 0xc, 0x3, 0x1,
390 0x0, 0x2, 0x11, 0x3, 0x11, 0x0, 0x3f, 0x0,
391 (byte) 0xaa, 0x40, 0x7, (byte) 0xff, (byte) 0xd9
392 }),
393
394 // Random-sized JPG image.
395 new DataRow(ExhibitMIME.ET_JPEG, new Dimension(jpegX, jpegY),
396 ExhibitMIME.getParamsByType(ExhibitMIME.ET_JPEG).handler.
397 makeImageBinary(
398 makeTestRGBTrueColourBufferedImage(jpegX, jpegY), 50)),
399
400 // Random-sized PNG image.
401 new DataRow(ExhibitMIME.ET_PNG, new Dimension(jpegX, jpegY),
402 ExhibitMIME.getParamsByType(ExhibitMIME.ET_PNG).handler.
403 makeImageBinary(
404 makeTestRGBTrueColourBufferedImage(jpegX, jpegY), 50)),
405 };
406
407 // Passes data to given handler (which must exist)
408 // to test that it can correctly extract (X,Y) pixel dimensions.
409 for(int i = testData.length; --i >= 0; )
410 {
411 final DataRow dr = testData[i];
412 final ExhibitMIME.ExhibitTypeParameters etp = ExhibitMIME.getParamsByType(dr.exhibitType);
413 assertTrue("ExhibitMIME.getParamsByType() must find parameters for type: " + dr.exhibitType,
414 etp != null);
415 assertTrue("ExhibitMIME.getParamsByType() must find parameters for correct type",
416 etp.type == dr.exhibitType);
417 assertTrue("HandlerBase must exist for type: " + etp, etp.handler != null);
418 final Dimension dim = etp.handler.get2DImageDimensions(new ByteArrayInputStream(dr.data));
419 assertTrue("Wrong Dimension extracted",
420 dr.xyPixels.equals(dim));
421 // assertTrue("Minimum overhead is smaller than magic for: " + etp,
422 // etp.handler.approxMinOverheadBytes() >= etp.magic.length());
423 // assertTrue("Minimum overhead is larger than test exhibit for: " + etp,
424 // etp.handler.approxMinOverheadBytes() <= dr.data.length);
425 }
426 }
427
428
429 /**Width of image big enough to make both thumbnails of; several times longest large thumbnail dimension.
430 * Package-visible to be available to other tests.
431 */
432 static final int bigImageWidth = ExhibitThumbnails.STD_STATIC_IMAGE_TN_LDIM_PX * 2;
433
434 /**Height of image big enough to make both thumbnails of; several times longest large thumbnail dimension.
435 * Package-visible to be available to other tests.
436 */
437 static final int bigImageHeight = ExhibitThumbnails.STD_STATIC_IMAGE_TN_LDIM_PX * 3;
438
439
440 /**Create a test RGB fully-opaque true-colour image in memory.
441 * This image will be suitable to make thumbnails from, for example,
442 * if large enough.
443 * <p>
444 * This is an RGB image with a mixture of slowly-varying
445 * colours and noise.
446 * <p>
447 * Package-visible to be available to other tests.
448 */
449 static BufferedImage makeTestRGBTrueColourBufferedImage(final int width,
450 final int height)
451 {
452 if((width < 1) || (height < 1))
453 { throw new IllegalArgumentException(); }
454
455 // Create a blank RGB (buffered) image.
456 final BufferedImage bi = new BufferedImage(width,
457 height,
458 BufferedImage.TYPE_INT_ARGB);
459 // Set this image up to have a relatively smoothly-changing backdrop
460 // with some noise imposed on it.
461 // Efficiency is not a huge issue here...
462 for(int y = height; --y >= 0; )
463 {
464 final int r = Math.round((y / (float) height) * 128)
465 + rnd.nextInt(16);
466 for(int x = width; --x >= 0; )
467 {
468 final int g = Math.round((x / (float) width) * 128)
469 + rnd.nextInt(16);
470 final int b = ((x+y) & 0xff);
471 final int rgb = 0xff000000 | (r << 16) | (g << 8) | b;
472 bi.setRGB(x, y, rgb);
473 }
474 }
475
476 return(bi);
477 }
478
479 /**Create a byte-indexed fully-opaque RGB test image in memory.
480 * This image will be suitable to make thumbnails from, for example,
481 * if large enough.
482 * <p>
483 * This is an RGB image some geometric patterns
484 * in a small number of colours (but more than one colour),#
485 * but that should be colour-reducable.
486 * <p>
487 * In passing this also tests a technique for generating indexed-colour
488 * images given RGB values.
489 */
490 private static BufferedImage makeTestRGBIndexedColourBufferedImage(final int width, final int height)
491 {
492 if((width < 1) || (height < 1))
493 { throw new IllegalArgumentException(); }
494
495 // Create a small palette of colours to use in a byte-indexed model.
496 // These are ARGB values with no transparency.
497 final int palette[] =
498 {
499 0xfffffff0, 0xff0f0f00, 0xffff000f, 0xff0f0f0f,
500 0xffffffff, 0xffffff00, 0xffff00ff, rnd.nextInt() | 0xff000000
501 };
502
503 final IndexColorModel icm = new IndexColorModel(8, // 8-bit index.
504 palette.length,
505 palette,
506 0, // Offset into palette[].
507 false, // No alpha.
508 -1, // No transparent colour.
509 DataBuffer.TYPE_BYTE);
510
511 // Create a blank RGB (buffered) image.
512 final BufferedImage bi = new BufferedImage(width,
513 height,
514 BufferedImage.TYPE_BYTE_INDEXED,
515 icm);
516
517 // Set this image up to have a regular pattern with some noise.
518 // We ensure that even a 2-pixel image will have two colours.
519 for(int y = height; --y >= 0; )
520 {
521 // Have vertical stripes with an non-uniform selection of
522 // colours from the palette (biased towards low-numbered entries).
523 final int yindex = rnd.nextInt(rnd.nextInt(palette.length) + 1);
524 for(int x = width; --x >= 0; )
525 {
526 // Have an alternating pixel pattern horizontally.
527 final int index = (yindex + ((x^y)&1)) % palette.length;
528 bi.setRGB(x, y, palette[index]);
529 }
530 }
531
532
533 // Check that result is a multi-colour image if more than 1 pixel.
534 if(width * height > 1)
535 {
536 // Checks that the generated image has more than one colour
537 // (ignoring alpha).
538 final int firstPixelRGB = bi.getRGB(0, 0) & 0xffffff;
539 boolean oneColour = true; // Only one colour.
540 for(int y = height; --y >= 0; )
541 {
542 for(int x = width; --x >= 0; )
543 {
544 final int thisRGB = bi.getRGB(x, y) & 0xffffff;
545 if(thisRGB != firstPixelRGB)
546 {
547 oneColour = false;
548 break;
549 }
550 }
551 }
552 assertTrue("Generated test image should have more than one colour if more than one pixel",
553 !oneColour);
554 }
555
556 return(bi);
557 }
558
559
560 /**Make test JPEG encoded image of reasonably typical size; never null.
561 * The image is bigger than any thumbnail,
562 * and encoded at a typical quality.
563 * <p>
564 * Will throw a jUnit exception if something unexpected happens.
565 * <p>
566 * Package-visible to be available to other tests.
567 *
568 * @throws IOException if the JPEG encoder routines have difficulty
569 */
570 static byte[] makeTestRGBTrueColourJPEGImage()
571 throws IOException
572 {
573 // Choose JPEG type...
574 final int t = ExhibitMIME.ET_JPEG;
575
576 // Check that he have a handler.
577 final ExhibitMIME.ExhibitTypeParameters type = ExhibitMIME.getParamsByType(t);
578 final Handler h = type.handler;
579 assertTrue("Must always have a valid handler for t = " + t, h != null);
580 // Check that handler is prepared to try to make thumbnails.
581 assertTrue("Handler should be prepared to make thumbnails", h.canMakeThumbnails());
582
583 // Test that we can create a big image.
584 final BufferedImage bigImage = makeTestRGBTrueColourBufferedImage(
585 bigImageWidth, bigImageHeight);
586 // final int bigImageLongestEdge = Math.max(bigImageWidth, bigImageHeight);
587 final byte[] bigEncodedImage =
588 h.makeImageBinary(bigImage, 75);
589 //makeJPEGImage(bigImageWidth, bigImageHeight);
590 assertTrue("Must be able to create image from BufferedImage",
591 bigEncodedImage != null);
592 assertTrue("Must be able to create valid image from BufferedImage",
593 ExhibitMIME.magicOK(type, new ByteArrayInputStream(bigEncodedImage)));
594 assertTrue("Encoded image should be much bigger than 1kByte",
595 bigEncodedImage.length > 1024);
596
597 return(bigEncodedImage);
598 }
599
600
601
602
603 /**Test colour reduction of RGB and indexed images using ImageUtils routine.
604 * This tests that we don't get back more colours than we start with,
605 * and that this works regardless of source image type.
606 */
607 public void testColourReduction()
608 {
609 // Try alternately with indexed and non-indexed images
610 // at realistic thumbnail size.
611 for(int srcFormat = 2; --srcFormat >= 0; )
612 {
613 // Create a typical thumbnail-sized image.
614 final BufferedImage srcImage = (srcFormat == 0) ?
615 makeTestRGBTrueColourBufferedImage(64, 48) :
616 makeTestRGBIndexedColourBufferedImage(48, 64);
617
618 // Try alternately forcing the output to indexed format and not.
619 for(int dstFormat = 2; --dstFormat >= 0; )
620 {
621 final boolean forceByteIndexModel = (dstFormat == 0);
622
623 // Try making successively smaller colour spaces.
624 // This may not be able to do successful colour reduction
625 // to very small numbers of colours.
626 for(int colorBits = 17; --colorBits > 0; )
627 {
628 final int maxColours = (1 << colorBits);
629
630 // Extract copy of pixels from source image in TYPE_INT_ARGB format.
631 // Set scansize to be width to efficiently pack the result.
632 final int width = srcImage.getWidth();
633 final int height = srcImage.getHeight();
634 final int pixels[] =
635 srcImage.getRGB(0, 0, width, height, null, 0, width);
636
637 // Count the colours in the original image (ignoring alpha).
638 final Set<Integer> originalDistinctColours = new TreeSet<Integer>();
639 for(int i = pixels.length; --i >= 0; )
640 { originalDistinctColours.add(new Integer(pixels[i] & 0xffffff)); }
641 //System.out.println("testColourReduction(): original colour count: " + originalDistinctColours.size());
642
643 // Create a colour-reduced image.
644 final BufferedImage reducedColImage =
645 ImageUtils.makeColourReducedBufferedImage(srcImage,
646 maxColours,
647 forceByteIndexModel);
648 // Count the colours in the new image (ignoring alpha).
649 final int newColours =
650 ImageUtils.countDistinct24BitRGBColours(reducedColImage);
651 //System.out.println("testColourReduction(): new colour count (maxColours:"+maxColours+"): " + newColours);
652
653 assertTrue("Must produce an output image", reducedColImage != null);
654 assertTrue("Must produce an output image the same dimensions as the input",
655 (reducedColImage.getWidth() == width) &&
656 (reducedColImage.getHeight() == height));
657 assertTrue("Must not increase number of colours",
658 newColours <= originalDistinctColours.size());
659 assertTrue("Must leave no more than requested colours in result",
660 newColours <= maxColours);
661 //System.out.println("output colour model: " + reducedColImage.getColorModel());
662 assertTrue("Must have at least two colours in palette if original did: new colour map is: " + reducedColImage.getColorModel(),
663 (originalDistinctColours.size() < 2) || (newColours >= 2));
664 }
665 }
666 }
667 }
668
669
670
671 /**Test that we can do colour reduction with the ImageMagik-derived Quantizer.
672 */
673 public void testQuantizer()
674 {
675 // Create a typical thumbnail-sized image.
676 final BufferedImage bigImage = makeTestRGBTrueColourBufferedImage(
677 256, 192);
678
679 // Try making successively smaller colour spaces.
680 for(int colorBits = 9; --colorBits > 0; )
681 {
682 final int maxColours = (1 << colorBits);
683
684 // Extract copy of pixels from source image in TYPE_INT_ARGB format.
685 // Set scansize to be width to efficiently pack the result.
686 final int pixels[] =
687 bigImage.getRGB(0, 0, bigImage.getWidth(), bigImage.getHeight(),
688 null, 0, bigImage.getWidth());
689
690 // Count the colours in the original image (ignoring alpha).
691 final Set<Integer> originalDistinctColours = new TreeSet<Integer>();
692 for(int i = pixels.length; --i >= 0; )
693 { originalDistinctColours.add(new Integer(pixels[i] & 0xffffff)); }
694 //System.out.println("testQuantizer(): original colour count: " + originalDistinctColours.size());
695
696 // Now do the colour reduction...
697 final int[] palette = Quantize.quantizeImage(
698 new int[][]{pixels}, maxColours);
699 //System.out.println("testQuantizer(): new colour count: " + palette.length);
700 assertTrue("Must leave at least two colours in palette if orgininal had at least two",
701 (originalDistinctColours.size() < 2) || (palette.length >= 2));
702
703 // Create a new ARGB image and copy the new pixels in there.
704 final BufferedImage reducedColImage = new BufferedImage(
705 bigImage.getWidth(), bigImage.getHeight(),
706 BufferedImage.TYPE_INT_ARGB);
707 reducedColImage.setRGB(0, 0, bigImage.getWidth(), bigImage.getHeight(),
708 pixels, 0, bigImage.getWidth());
709
710 assertTrue("Must produce an output image", reducedColImage != null);
711 assertTrue("Must produce an output image the same dimensions as the input",
712 (reducedColImage.getWidth() == bigImage.getWidth()) &&
713 (reducedColImage.getHeight() == bigImage.getHeight()));
714 assertTrue("Must not increase number of colours",
715 palette.length <= originalDistinctColours.size());
716 }
717 }
718
719 /**Simple tests of exhibit metadata.
720 * For example, check that it is safe to attempt to extract metadata from all exhibits with a handler,
721 * even though there legitimately be no metadata to extract
722 * though we in fact expect to always extract some metadata from every object where we have a handler.
723 * <p/>
724 * We check that we can extract EXIF data from at least one (JPEG) exhibit.
725 */
726 public void testMetadataBasics()
727 throws Exception
728 {
729 // Set up a simple instance of a file data source using our sample data.
730 final ExhibitDataFileSource edfs = new ExhibitDataFileSource(null);
731
732 // Collect the raw exhibit info from the underlying file system.
733 final AllExhibitProperties aepRaw = edfs.getAllExhibitProperties(-1);
734
735 // Note if we found EXIF data.
736 boolean foundEXIF = false;
737 // We look for the existence of the following path.
738 final String EXIFPath[] = { "metadata", "native", "EXIF", "tag" };
739
740 // For every exhibit that has a handler,
741 // try to extract available metadata,
742 // ensuring that no failure (eg exception) is caused,
743 // and that metadata is definitely available where it should be.
744 for(final ExhibitStaticAttr esa : aepRaw.aeid.getAllStaticAttrs())
745 {
746 final ExhibitMIME.ExhibitTypeParameters et = (ExhibitMIME.getInputFileType(esa.getCharSequence()));
747 assertNotNull("All exhibits should have a type: " + esa, et);
748 final Handler h = et.handler;
749 if(h == null) { continue; }
750
751 // Read the data into memory in one go for simplicity.
752 // None of our sample exhibits is vast.
753 final byte data[] = new byte[(int) esa.length];
754 final File rawDataLoc = new File(LocalProps.getDataDir(), esa.getFilePath());
755 final DataInputStream dis = new DataInputStream(new FileInputStream(rawDataLoc));
756 try { dis.readFully(data); }
757 finally { dis.close(); }
758
759 // Fetch the metadata directly; we expect there to be some for every type that has a handler...
760 final Node metadata = h.getMetadata(new ByteArrayInputStream(data), esa.getExhibitFullName());
761 assertNotNull("Standard meta-data should be extractable where we have a handler: " + et.suffixForInputFile, metadata + " " + esa);
762 // System.out.println(TextUtils.toXML(metadata, false, false));
763
764 if(metadata != null)
765 {
766 // Check that top-level metadata node name is correct.
767 assertSame("Top-level metadata node incorrect", "metadata", metadata.getNodeName());
768
769 // Fetch the metadata from the EPC; should be equivalent to getting it directly.
770 final Node metadataEPC = aepRaw.getExhibitPropsComputable(esa.getExhibitFullName()).getMetadata();
771 assertNotNull("Metadata should be available via the EPC if available directly: "+metadata, metadataEPC);
772 // FIXME: test that we get an equivalent tree...
773
774 // Check if this exhibit has EXIF metadata...
775 Node n = metadata;
776 for(int i = 0; ++i < EXIFPath.length; )
777 {
778 final String name = EXIFPath[i];
779 boolean foundCorrectChild = false;
780 for(Node child = n.getFirstChild(); child != null; child = child.getNextSibling())
781 {
782 if(name.equals(child.getNodeName()))
783 {
784 n = child;
785 foundCorrectChild = true;
786 if(i + 1 == EXIFPath.length)
787 {
788 System.out.println("Found EXIF metadata for: " + esa.getCharSequence());
789 foundEXIF = true;
790 }
791 break;
792 }
793 }
794 if(!foundCorrectChild) { break; }
795 }
796 }
797 }
798
799 assertTrue("At least one (JPEG) exhibit must have EXIF metadata", foundEXIF);
800 }
801
802
803 /**Reasonable shared random-number generator. */
804 private static final Random rnd = new Random();
805 }