DataInterpreterJpeg.java
/*
* 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.
*/
package org.apache.commons.imaging.formats.tiff.datareaders;
import static org.apache.commons.imaging.formats.tiff.constants.AdobePhotoshopTagConstants.EXIF_TAG_JPEGTABLES;
import static org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants.PHOTOMETRIC_INTERPRETATION_VALUE_RGB;
import static org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants.TIFF_TAG_PHOTOMETRIC_INTERPRETATION;
import java.awt.image.BufferedImage;
import java.io.IOException;
import org.apache.commons.imaging.ImagingException;
import org.apache.commons.imaging.bytesource.ByteSource;
import org.apache.commons.imaging.common.ImageBuilder;
import org.apache.commons.imaging.formats.jpeg.decoder.JpegDecoder;
import org.apache.commons.imaging.formats.tiff.TiffDirectory;
import org.apache.commons.imaging.formats.tiff.TiffField;
/**
* Provides logic for obtaining image data from a JPEG-encoded TIFF strip or tile (the "block") from the source file.
*/
final class DataInterpreterJpeg {
/**
* Interpret the content of a TIFF strip or tile obtained from the source file. The workingBuilder is an image builder that is configured to receive the
* extracted data.
* <p>
* The dimensions of the workingBuilder may or may not be identical to those of the source image. If the calling module is extracting a partial image, the
* workingBuilder will be sized so that its upper-left corner coordinate (row and column) and width and height are all even multiples of the dimensions of
* the tile or strip definition from the source. The reason for that approach is to simplify some of the logic in the legacy code by eliminating
* bounds-checking in the transfer loops.
* </p>
*
* @param directory the source directory containing JPEG tables
* @param workingBuilder the output image-builder to receive pixels.
* @param xBlock column offset of the block within the workingBuilder
* @param yBlock row offset of the block within the workingBuilder
* @param blockWidth the width of the block (may be smaller than the full width of the JPEG image obtained from the source data)
* @param blockHeight the height of the block (may be smaller than the full height of the JPEG image obtained from the source data)
* @param compressed the raw bytes from the TIFF source file
* @throws ImagingException in the event of an unsupported feature or JPEG-specific encoding error
* @throws IOException in the event of an unrecoverable I/O error
*/
static void intepretBlock(final TiffDirectory directory, final ImageBuilder workingBuilder, final int xBlock, final int yBlock, final int blockWidth,
final int blockHeight, final byte[] compressed) throws ImagingException, IOException {
// the data for the block. The TIFF format can store some
// of the internal JPEG tables in a separate TIFF field (tag) called
// "JPEG Tables" which are used for all of the tiles in the file.
// So we need to concatenate the field bytes into the
// compressed sequence. Both the field bytes and compressed
// arrays start and end with the sequences SOI (0xffd8)
// and EOI (0xffd9). To concatenate the two, we need to get rid
// of the EOI from field bytes and SOI from compressed.
if (compressed.length <= 4) {
return;
}
byte[] concat;
final byte[] field = directory.getFieldValue(EXIF_TAG_JPEGTABLES, false);
if (field == null || field.length == 0) {
// The TIFF information was ommitted.
// We will just pass the compressed sequence to the
// decoder and let it throw the relevant exception.
// There is a possibility that some non-standard
// TIFF encoders might not use the JPEG Tables tag
// but embed to coding values in the compressed sequence
concat = compressed;
} else {
concat = new byte[field.length + compressed.length - 4];
System.arraycopy(field, 0, concat, 0, field.length - 2);
System.arraycopy(compressed, 2, concat, field.length - 2, compressed.length - 2);
}
final ByteSource bsArray = ByteSource.array(concat, "JPEGtile");
// Set up the decoder. In some cases, TIFF files may
// use the RGB encoding (which is slightly unusual for JPEGs).
// So check the photometric interpretation. If it is
// RGB, we make a special setting in the decoder. Otherwise,
// we let the decoder act according to the standard
// JPEG rules.
final JpegDecoder decoder = new JpegDecoder();
final TiffField piField = directory.findField(TIFF_TAG_PHOTOMETRIC_INTERPRETATION);
if (piField != null) {
final int pi = piField.getIntValue();
if (pi == PHOTOMETRIC_INTERPRETATION_VALUE_RGB) {
decoder.setTiffRgb();
}
}
final BufferedImage bImage = decoder.decode(bsArray);
// a null result is not expected. An exception would
// be thrown if there was a formatting error
final int iWidth = bImage.getWidth();
final int iHeight = bImage.getHeight();
final int[] argb = new int[iWidth * iHeight];
bImage.getRGB(0, 0, iWidth, iHeight, argb, 0, iWidth);
// Limit iHeight and iWidth in case the JPEG block
// extends past the output image size
final int i1 = iHeight > blockHeight ? blockHeight : iHeight;
final int j1 = iWidth > blockWidth ? blockWidth : iWidth;
for (int i = 0; i < i1; i++) {
for (int j = 0; j < j1; j++) {
workingBuilder.setRgb(j + xBlock, i + yBlock, argb[i * iWidth + j]);
}
}
}
}