xmlgraphics-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From lberna...@apache.org
Subject svn commit: r1550785 - in /xmlgraphics/commons/trunk: src/java/org/apache/xmlgraphics/image/loader/impl/PreloaderJPEG.java test/java/org/apache/xmlgraphics/image/loader/impl/PreloaderJPEGTestCase.java
Date Fri, 13 Dec 2013 18:07:18 GMT
Author: lbernardo
Date: Fri Dec 13 18:07:18 2013
New Revision: 1550785

URL: http://svn.apache.org/r1550785
Log:
XGC-87: Add support for APP1 (Exif) segment in JPEG images

Added:
    xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/image/loader/impl/PreloaderJPEGTestCase.java
  (with props)
Modified:
    xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/image/loader/impl/PreloaderJPEG.java

Modified: xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/image/loader/impl/PreloaderJPEG.java
URL: http://svn.apache.org/viewvc/xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/image/loader/impl/PreloaderJPEG.java?rev=1550785&r1=1550784&r2=1550785&view=diff
==============================================================================
--- xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/image/loader/impl/PreloaderJPEG.java
(original)
+++ xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/image/loader/impl/PreloaderJPEG.java
Fri Dec 13 18:07:18 2013
@@ -20,6 +20,7 @@
 package org.apache.xmlgraphics.image.loader.impl;
 
 import java.io.IOException;
+import java.nio.ByteOrder;
 
 import javax.imageio.stream.ImageInputStream;
 import javax.xml.transform.Source;
@@ -38,6 +39,13 @@ import org.apache.xmlgraphics.util.UnitC
 public class PreloaderJPEG extends AbstractImagePreloader implements JPEGConstants {
 
     private static final int JPG_SIG_LENGTH = 3;
+    private static final int[] BYTES_PER_COMPONENT = {0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4,
8}; // ignore 0
+    private static final int EXIF = 0x45786966;
+    private static final int II = 0x4949; // Intel
+    private static final int MM = 0x4d4d; // Motorola
+    private static final int X_RESOLUTION = 0x011a;
+    private static final int Y_RESOLUTION = 0x011b;
+    private static final int RESOLUTION_UNIT = 0x0128;
 
     /** {@inheritDoc}
      * @throws ImageException */
@@ -68,8 +76,6 @@ public class PreloaderJPEG extends Abstr
             ImageSize size = new ImageSize();
             JPEGFile jpeg = new JPEGFile(in);
 
-            //TODO Read resolution from EXIF if there's no APP0
-            //(for example with JPEGs from digicams)
             while (true) {
                 int segID = jpeg.readMarkerSegment();
                 //System.out.println("Segment: " + Integer.toHexString(segID));
@@ -101,6 +107,111 @@ public class PreloaderJPEG extends Abstr
                     }
                     in.skipBytes(reclen - 14);
                     break;
+                case APP1:
+                    // see http://www.media.mit.edu/pia/Research/deepview/exif.html
+                    reclen = jpeg.readSegmentLength();
+                    int bytesToEnd = reclen - 2;
+                    // read Exif Header: 0x.45.78.69.66.00.00
+                    int exif = in.readInt(); // 0x.45.78.69.66
+                    int tail = in.readUnsignedShort(); // 0x.00.00
+                    // in.skipBytes(6);
+                    bytesToEnd -= 6;
+                    if (exif != EXIF) {
+                        // there may be multiple APP1 segments but we want the Exif one
+                        in.skipBytes(bytesToEnd);
+                        break;
+                    }
+                    // start TIFF data
+                    int currentTIFFOffset = 0;
+                    // byte align: 0x.49.49 (19789) means Intel, 0x.4D.4D means Motorola
+                    int align = in.readUnsignedShort();
+                    bytesToEnd -= 2;
+                    currentTIFFOffset += 2;
+                    ByteOrder originalByteOrder = in.getByteOrder();
+                    // Intel = little, Motorola = big
+                    in.setByteOrder(align == MM ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
+                    in.skipBytes(2); // 0x.2A.00 (Intel) or 0x.00.2A (Motorola)
+                    bytesToEnd -= 2;
+                    currentTIFFOffset += 2;
+                    int firstIFDOffset = in.readInt();
+                    bytesToEnd -= 4;
+                    currentTIFFOffset += 4;
+                    in.skipBytes(firstIFDOffset - 8);
+                    bytesToEnd -= firstIFDOffset - 8;
+                    currentTIFFOffset += firstIFDOffset - 8;
+                    int directoryEntries = in.readUnsignedShort();
+                    bytesToEnd -= 2;
+                    currentTIFFOffset += 2;
+                    int resolutionOffset = 0;
+                    int resolutionFormat = 0;
+                    int resolutionUnits = 0;
+                    int resolution = 0;
+                    boolean foundResolution = false;
+                    for (int j = 0; j < directoryEntries; j++) {
+                        int tag = in.readUnsignedShort();
+                        if ((tag == X_RESOLUTION || tag == Y_RESOLUTION) && !foundResolution)
{
+                            // 0x011A (XResolution), 0x011B (YResolution)
+                            int format = in.readUnsignedShort();
+                            int components = in.readInt();
+                            int dataByteLength = components * BYTES_PER_COMPONENT[format];
+                            int value = in.readInt();
+                            if (dataByteLength > 4) {
+                                // value is offset to data value
+                                resolutionOffset = value;
+                            } else {
+                                // value is data value
+                                resolution = value;
+                            }
+                            resolutionFormat = format;
+                            foundResolution = true;
+                        } else if (tag == RESOLUTION_UNIT) {
+                            // 0x0128 (ResolutionUnit)
+                            int format = in.readUnsignedShort();
+                            int components = in.readInt();
+                            int dataByteLength = components * BYTES_PER_COMPONENT[format];
+                            if (dataByteLength < 5 && format == 3) {
+                                int value = in.readUnsignedShort();
+                                in.skipBytes(2);
+                                resolutionUnits = value;
+                            } else {
+                                in.skipBytes(4);
+                            }
+                        } else {
+                            in.skipBytes(10);
+                        }
+                        bytesToEnd -= 12;
+                        currentTIFFOffset += 12;
+                    }
+                    int nextIFDOffset = in.readInt(); // not needed, but has thumbnail info
+                    bytesToEnd -= 4;
+                    currentTIFFOffset += 4;
+                    if (resolutionOffset != 0) {
+                        in.skipBytes(resolutionOffset - currentTIFFOffset);
+                        bytesToEnd -= resolutionOffset - currentTIFFOffset;
+                        if (resolutionFormat == 5 || resolutionFormat == 10) {
+                            int numerator = in.readInt();
+                            int denominator = in.readInt();
+                            resolution = numerator / denominator;
+                            bytesToEnd -= 8;
+                        }
+                    }
+                    in.skipBytes(bytesToEnd);
+                    in.setByteOrder(originalByteOrder);
+                    if (resolutionUnits == 3) {
+                        // dots per centimeter
+                        size.setResolution(resolution * UnitConv.IN2CM, resolution * UnitConv.IN2CM);
+                    } else if (resolutionUnits == 2) {
+                        // dots per inch
+                        size.setResolution(resolution, resolution);
+                    } else {
+                        // resolution not specified
+                        size.setResolution(context.getSourceResolution());
+                    }
+                    if (size.getWidthPx() != 0) {
+                        size.calcSizeFromPixels();
+                        return size;
+                    }
+                    break;
                 case SOF0:
                 case SOF1:
                 case SOF2: // SOF2 and SOFA are only supported by PDF 1.3
@@ -118,7 +229,7 @@ public class PreloaderJPEG extends Abstr
                     break;
                 case SOS:
                 case EOI:
-                    //Break as early as possible (we don't want to read the whole file here)
+                    // Break as early as possible (we don't want to read the whole file here)
                     if (size.getDpiHorizontal() == 0) {
                         size.setResolution(context.getSourceResolution());
                         size.calcSizeFromPixels();

Added: xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/image/loader/impl/PreloaderJPEGTestCase.java
URL: http://svn.apache.org/viewvc/xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/image/loader/impl/PreloaderJPEGTestCase.java?rev=1550785&view=auto
==============================================================================
--- xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/image/loader/impl/PreloaderJPEGTestCase.java
(added)
+++ xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/image/loader/impl/PreloaderJPEGTestCase.java
Fri Dec 13 18:07:18 2013
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.xmlgraphics.image.loader.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.imageio.stream.MemoryCacheImageInputStream;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.apache.xmlgraphics.image.loader.ImageContext;
+import org.apache.xmlgraphics.image.loader.ImageException;
+import org.apache.xmlgraphics.image.loader.ImageInfo;
+import org.apache.xmlgraphics.image.loader.ImageSize;
+import org.apache.xmlgraphics.image.loader.ImageSource;
+
+public class PreloaderJPEGTestCase {
+
+    @Test
+    public void testAPP1Segment() throws IOException, ImageException {
+
+        // example from http://www.media.mit.edu/pia/Research/deepview/exif.html (adapted
and expanded)
+        // the bytes below have three markers: 0xFFD8 (SOI), 0xFFE1 (APP1) and 0xFFC0 (SOF0);
this all
+        // it is needed to get the image size and resolution for this test
+        byte[] jpegBytes = {(byte) 0xFF, (byte) 0xD8, (byte) 0xFF, (byte) 0xE1, (byte) 0x00,
(byte) 0x42,
+                (byte) 0x45, (byte) 0x78, (byte) 0x69, (byte) 0x66, (byte) 0x00, (byte) 0x00,
(byte) 0x49,
+                (byte) 0x49, (byte) 0x2A, (byte) 0x00, (byte) 0x08, (byte) 0x00, (byte) 0x00,
(byte) 0x00,
+                (byte) 0x03, (byte) 0x00, (byte) 0x1A, (byte) 0x01, (byte) 0x05, (byte) 0x00,
(byte) 0x01,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x32, (byte) 0x00, (byte) 0x00,
(byte) 0x00,
+                (byte) 0x28, (byte) 0x01, (byte) 0x03, (byte) 0x00, (byte) 0x01, (byte) 0x00,
(byte) 0x00,
+                (byte) 0x00, (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x69,
(byte) 0x87,
+                (byte) 0x04, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x11,
+                (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x40, (byte) 0x00, (byte) 0x00,
(byte) 0x00,
+                (byte) 0xC0, (byte) 0xC6, (byte) 0x2D, (byte) 0x00, (byte) 0x10, (byte) 0x27,
(byte) 0x00,
+                (byte) 0x00, (byte) 0xFF, (byte) 0xC0, (byte) 0x00, (byte) 0x14, (byte) 0x00,
(byte) 0x0D,
+                (byte) 0xB4, (byte) 0x09, (byte) 0xB0, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00};
+        InputStream jpegInputStream = new ByteArrayInputStream(jpegBytes);
+        MemoryCacheImageInputStream jpeg = new MemoryCacheImageInputStream(jpegInputStream);
+
+        String uri = "image.jpg";
+        ImageSource imageSource = mock(ImageSource.class);
+        ImageContext context = mock(ImageContext.class);
+        when(imageSource.getImageInputStream()).thenReturn(jpeg);
+
+        PreloaderJPEG preloaderJPEG = new PreloaderJPEG();
+        ImageInfo imageInfo = preloaderJPEG.preloadImage(uri, imageSource, context);
+        ImageSize imageSize = imageInfo.getSize();
+
+        double expectedDPI = 300.0;
+        assertEquals(expectedDPI, imageSize.getDpiHorizontal(), 0.01);
+    }
+
+}

Propchange: xmlgraphics/commons/trunk/test/java/org/apache/xmlgraphics/image/loader/impl/PreloaderJPEGTestCase.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: commits-help@xmlgraphics.apache.org


Mime
View raw message