Skip to content

Commit

Permalink
- Defined additional JPEG segment types: DNL, DRI, DHP and EXP
Browse files Browse the repository at this point in the history
- Removed the requirement in HuffmanTablesDirectory.isTypical() that the number of Huffman tables must be 4 to accommodate progressive JPEGs
- Made JpegDhtReader handle stuffing bytes in Huffman tables
- Created JpegDnlReader for DNL segment support
  • Loading branch information
Nadahar committed Feb 4, 2017
1 parent 8544d9b commit e9daa3d
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 4 deletions.
4 changes: 3 additions & 1 deletion Source/com/drew/imaging/jpeg/JpegMetadataReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import com.drew.metadata.jfxx.JfxxReader;
import com.drew.metadata.jpeg.JpegCommentReader;
import com.drew.metadata.jpeg.JpegDhtReader;
import com.drew.metadata.jpeg.JpegDnlReader;
import com.drew.metadata.jpeg.JpegReader;
import com.drew.metadata.photoshop.DuckyReader;
import com.drew.metadata.photoshop.PhotoshopReader;
Expand Down Expand Up @@ -65,7 +66,8 @@ public class JpegMetadataReader
new DuckyReader(),
new IptcReader(),
new AdobeJpegReader(),
new JpegDhtReader()
new JpegDhtReader(),
new JpegDnlReader()
);

@NotNull
Expand Down
12 changes: 12 additions & 0 deletions Source/com/drew/imaging/jpeg/JpegSegmentType.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,18 @@ public enum JpegSegmentType
/** Define Quantization Table segment identifier. */
DQT((byte)0xDB, false),

/** Define Number of Lines segment identifier. */
DNL((byte)0xDC, false),

/** Define Restart Interval segment identifier. */
DRI((byte)0xDD, false),

/** Define Hierarchical Progression segment identifier. */
DHP((byte)0xDE, false),

/** EXPand reference component(s) segment identifier. */
EXP((byte)0xDF, false),

/** Define Huffman Table segment identifier. */
DHT((byte)0xC4, false),

Expand Down
2 changes: 1 addition & 1 deletion Source/com/drew/metadata/jpeg/HuffmanTablesDirectory.java
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ protected List<HuffmanTable> getTables() {
* Huffman tables.
*/
public boolean isTypical() {
if (tables.size() != 4) {
if (tables.size() == 0) {
return false;
}
for (HuffmanTable table : tables) {
Expand Down
19 changes: 17 additions & 2 deletions Source/com/drew/metadata/jpeg/JpegDhtReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,12 @@ public void extract(@NotNull final SequentialReader reader, @NotNull final Metad
HuffmanTableClass tableClass = HuffmanTableClass.typeOf((header & 0xF0) >> 4);
int tableDestinationId = header & 0xF;

byte[] lBytes = reader.getBytes(16);
byte[] lBytes = getBytes(reader, 16);
int vCount = 0;
for (byte b : lBytes) {
vCount += b;
}
byte[] vBytes = reader.getBytes(vCount);
byte[] vBytes = getBytes(reader, vCount);
directory.getTables().add(new HuffmanTable(tableClass, tableDestinationId, lBytes, vBytes));
}
} catch (IOException me) {
Expand All @@ -83,4 +83,19 @@ public void extract(@NotNull final SequentialReader reader, @NotNull final Metad

directory.setInt(HuffmanTablesDirectory.TAG_NUMBER_OF_TABLES, directory.getTables().size());
}

private byte[] getBytes(@NotNull final SequentialReader reader, int count) throws IOException {
byte[] bytes = new byte[count];
for (int i = 0; i < count; i++) {
byte b = reader.getByte();
if (b == 0xFF) {
byte stuffing = reader.getByte();
if (stuffing != 0x00) {
throw new IOException("Marker " + JpegSegmentType.fromByte(stuffing) + " found inside DHT segment");
}
}
bytes[i] = b;
}
return bytes;
}
}
76 changes: 76 additions & 0 deletions Source/com/drew/metadata/jpeg/JpegDnlReader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright 2002-2017 Drew Noakes
*
* Licensed 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.
*
* More information about this project is available at:
*
* https://drewnoakes.com/code/exif/
* https://github.com/drewnoakes/metadata-extractor
*/
package com.drew.metadata.jpeg;

import com.drew.imaging.jpeg.JpegSegmentMetadataReader;
import com.drew.imaging.jpeg.JpegSegmentType;
import com.drew.lang.SequentialByteArrayReader;
import com.drew.lang.SequentialReader;
import com.drew.lang.annotations.NotNull;
import com.drew.metadata.ErrorDirectory;
import com.drew.metadata.Metadata;

import java.io.IOException;
import java.util.Arrays;

/**
* Decodes JPEG DNL data, adjusting the image height with information missing from the JPEG SOFx segment.
*
* @author Nadahar
*/
public class JpegDnlReader implements JpegSegmentMetadataReader
{
@NotNull
public Iterable<JpegSegmentType> getSegmentTypes()
{
return Arrays.asList(JpegSegmentType.DNL);
}

@NotNull
public void readJpegSegments(@NotNull Iterable<byte[]> segments, @NotNull Metadata metadata, @NotNull JpegSegmentType segmentType)
{
for (byte[] segmentBytes : segments) {
extract(segmentBytes, metadata, segmentType);
}
}

public void extract(byte[] segmentBytes, Metadata metadata, JpegSegmentType segmentType)
{
JpegDirectory directory = metadata.getFirstDirectoryOfType(JpegDirectory.class);
if (directory == null) {
ErrorDirectory errorDirectory = new ErrorDirectory();
metadata.addDirectory(errorDirectory);
errorDirectory.addError("DNL segment found without SOFx - illegal JPEG format");
}

SequentialReader reader = new SequentialByteArrayReader(segmentBytes);

try {
// Only set height from DNL if it's not already defined
Integer i = directory.getInteger(JpegDirectory.TAG_IMAGE_HEIGHT);
if (i == null || i.intValue() == 0) {
directory.setInt(JpegDirectory.TAG_IMAGE_HEIGHT, reader.getUInt16());
}
} catch (IOException ex) {
directory.addError(ex.getMessage());
}
}
}
28 changes: 28 additions & 0 deletions Tests/com/drew/metadata/jpeg/HuffmanTablesDirectoryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@

import static org.junit.Assert.*;

import com.drew.metadata.jpeg.HuffmanTablesDirectory.HuffmanTable;
import com.drew.metadata.jpeg.HuffmanTablesDirectory.HuffmanTable.HuffmanTableClass;

/**
* @author Nadahar
*/
Expand Down Expand Up @@ -63,4 +66,29 @@ public void testGetNumberOfTables() throws Exception
assertEquals(9,_directory.getNumberOfTables());
assertEquals("9 Huffman tables", _directory.getDescription(HuffmanTablesDirectory.TAG_NUMBER_OF_TABLES));
}

@Test
public void testIsTypical() throws Exception
{
_directory.tables.add(new HuffmanTable(
HuffmanTableClass.AC,
0,
HuffmanTablesDirectory.TYPICAL_CHROMINANCE_AC_LENGTHS,
HuffmanTablesDirectory.TYPICAL_CHROMINANCE_AC_VALUES
));
_directory.tables.add(new HuffmanTable(
HuffmanTableClass.DC,
0,
HuffmanTablesDirectory.TYPICAL_LUMINANCE_DC_LENGTHS,
HuffmanTablesDirectory.TYPICAL_LUMINANCE_DC_VALUES
));

assertTrue(_directory.getTable(0).isTypical());
assertFalse(_directory.getTable(0).isOptimized());
assertTrue(_directory.getTable(1).isTypical());
assertFalse(_directory.getTable(1).isOptimized());

assertTrue(_directory.isTypical());
assertFalse(_directory.isOptimized());
}
}

0 comments on commit e9daa3d

Please sign in to comment.