mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-02 19:15:29 -04:00
TMI-JPEG: Fixed ICC profile issue. Now applies profiles when it should.
Profiles with bad indexes are now ignored on read. Added support for JPEG-LS SOF55 segment (no further JPEG-LS support) Added class documentation.
This commit is contained in:
parent
f8c40a3748
commit
544d60dabb
@ -41,10 +41,13 @@ import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment;
|
|||||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil;
|
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil;
|
||||||
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
||||||
import com.twelvemonkeys.lang.Validate;
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
|
||||||
import javax.imageio.*;
|
import javax.imageio.*;
|
||||||
import javax.imageio.event.IIOReadUpdateListener;
|
import javax.imageio.event.IIOReadUpdateListener;
|
||||||
import javax.imageio.event.IIOReadWarningListener;
|
import javax.imageio.event.IIOReadWarningListener;
|
||||||
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
import javax.imageio.spi.ImageReaderSpi;
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
@ -58,8 +61,26 @@ import java.util.List;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A JPEG {@code ImageReader} implementation based on the JRE {@code JPEGImageReader},
|
* A JPEG {@code ImageReader} implementation based on the JRE {@code JPEGImageReader},
|
||||||
* with support for CMYK/YCCK JPEGs, non-standard color spaces, broken ICC profiles
|
* that adds support and properly handles cases where the JRE version throws exceptions.
|
||||||
* and more.
|
* <p/>
|
||||||
|
* Main features:
|
||||||
|
* <ul>
|
||||||
|
* <li>Support for CMYK JPEGs (converted to RGB by default, using the embedded ICC profile if applicable)</li>
|
||||||
|
* <li>Support for Adobe YCCK JPEGs (converted to RGB by default, using the embedded ICC profile if applicable)</li>
|
||||||
|
* <li>Support for JPEGs containing ICC profiles with interpretation other than 'Perceptual' (profile is assumed to be 'Perceptual' and used)</li>
|
||||||
|
* <li>Support for JPEGs containing ICC profiles with class other than 'Display' (profile is assumed to have class 'Display' and used)</li>
|
||||||
|
* <li>Support for JPEGs containing ICC profiles that are incompatible with stream data (image data is read, profile is ignored)</li>
|
||||||
|
* <li>Support for JPEGs with corrupted ICC profiles (image data is read, profile is ignored)</li>
|
||||||
|
* <li>Support for JPEGs with corrupted {@code ICC_PROFILE} segments (image data is read, profile is ignored)</li>
|
||||||
|
* <li>Support for JPEGs using non-standard color spaces, unsupported by Java 2D (image data is read, profile is ignored)</li>
|
||||||
|
* <li>Issues warnings instead of throwing exceptions in cases of corrupted data where ever the image data can still be read in a reasonable way</li>
|
||||||
|
* </ul>
|
||||||
|
* Thumbnail support:
|
||||||
|
* <ul>
|
||||||
|
* <li>Support for JFIF thumbnails (even if stream contains "inconsistent metadata")</li>
|
||||||
|
* <li>Support for JFXX thumbnails (JPEG, Indexed and RGB)</li>
|
||||||
|
* <li>Support for EXIF thumbnails (JPEG, RGB and YCbCr)</li>
|
||||||
|
* </ul>
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author LUT-based YCbCR conversion by Werner Randelshofer
|
* @author LUT-based YCbCR conversion by Werner Randelshofer
|
||||||
@ -76,7 +97,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
private static final Map<Integer, List<String>> SEGMENT_IDENTIFIERS = createSegmentIds();
|
private static final Map<Integer, List<String>> SEGMENT_IDENTIFIERS = createSegmentIds();
|
||||||
|
|
||||||
private static Map<Integer, List<String>> createSegmentIds() {
|
private static Map<Integer, List<String>> createSegmentIds() {
|
||||||
Map<Integer, List<String>> map = new HashMap<Integer, List<String>>();
|
Map<Integer, List<String>> map = new LinkedHashMap<Integer, List<String>>();
|
||||||
|
|
||||||
// JFIF/JFXX APP0 markers
|
// JFIF/JFXX APP0 markers
|
||||||
map.put(JPEG.APP0, JPEGSegmentUtil.ALL_IDS);
|
map.put(JPEG.APP0, JPEGSegmentUtil.ALL_IDS);
|
||||||
@ -216,8 +237,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
|
||||||
ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
|
|
||||||
// If delegate can determine the spec, we'll just go with that
|
// If delegate can determine the spec, we'll just go with that
|
||||||
ImageTypeSpecifier rawType = delegate.getRawImageType(imageIndex);
|
ImageTypeSpecifier rawType = delegate.getRawImageType(imageIndex);
|
||||||
|
|
||||||
@ -276,7 +296,8 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
if (delegate.canReadRaster() && (
|
if (delegate.canReadRaster() && (
|
||||||
unsupported ||
|
unsupported ||
|
||||||
adobeDCT != null && adobeDCT.getTransform() == AdobeDCTSegment.YCCK ||
|
adobeDCT != null && adobeDCT.getTransform() == AdobeDCTSegment.YCCK ||
|
||||||
profile != null && (ColorSpaces.isOffendingColorProfile(profile) || profile.getColorSpaceType() == ColorSpace.TYPE_CMYK))) {
|
profile != null && !ColorSpaces.isCS_sRGB(profile))) {
|
||||||
|
// profile != null && (ColorSpaces.isOffendingColorProfile(profile) || profile.getColorSpaceType() == ColorSpace.TYPE_CMYK))) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
System.out.println("Reading using raster and extra conversion");
|
System.out.println("Reading using raster and extra conversion");
|
||||||
System.out.println("ICC color profile: " + profile);
|
System.out.println("ICC color profile: " + profile);
|
||||||
@ -316,12 +337,12 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
else if (intendedCS != null) {
|
else if (intendedCS != null) {
|
||||||
// Handle inconsistencies
|
// Handle inconsistencies
|
||||||
if (startOfFrame.componentsInFrame != intendedCS.getNumComponents()) {
|
if (startOfFrame.componentsInFrame() != intendedCS.getNumComponents()) {
|
||||||
if (startOfFrame.componentsInFrame < 4 && (csType == JPEGColorSpace.CMYK || csType == JPEGColorSpace.YCCK)) {
|
if (startOfFrame.componentsInFrame() < 4 && (csType == JPEGColorSpace.CMYK || csType == JPEGColorSpace.YCCK)) {
|
||||||
processWarningOccurred(String.format(
|
processWarningOccurred(String.format(
|
||||||
"Invalid Adobe App14 marker. Indicates YCCK/CMYK data, but SOF%d has %d color components. " +
|
"Invalid Adobe App14 marker. Indicates YCCK/CMYK data, but SOF%d has %d color components. " +
|
||||||
"Ignoring Adobe App14 marker, assuming YCbCr/RGB data.",
|
"Ignoring Adobe App14 marker, assuming YCbCr/RGB data.",
|
||||||
startOfFrame.marker & 0xf, startOfFrame.componentsInFrame
|
startOfFrame.marker & 0xf, startOfFrame.componentsInFrame()
|
||||||
));
|
));
|
||||||
|
|
||||||
csType = JPEGColorSpace.YCbCr;
|
csType = JPEGColorSpace.YCbCr;
|
||||||
@ -332,12 +353,15 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
"Embedded ICC color profile is incompatible with image data. " +
|
"Embedded ICC color profile is incompatible with image data. " +
|
||||||
"Profile indicates %d components, but SOF%d has %d color components. " +
|
"Profile indicates %d components, but SOF%d has %d color components. " +
|
||||||
"Ignoring ICC profile, assuming source color space %s.",
|
"Ignoring ICC profile, assuming source color space %s.",
|
||||||
intendedCS.getNumComponents(), startOfFrame.marker & 0xf, startOfFrame.componentsInFrame, csType
|
intendedCS.getNumComponents(), startOfFrame.marker & 0xf, startOfFrame.componentsInFrame(), csType
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// NOTE: Avoid using CCOp if same color space, as it's more compatible that way
|
// NOTE: Avoid using CCOp if same color space, as it's more compatible that way
|
||||||
else if (intendedCS != image.getColorModel().getColorSpace()) {
|
else if (intendedCS != image.getColorModel().getColorSpace()) {
|
||||||
|
if (DEBUG) {
|
||||||
|
System.err.println("Converting from " + intendedCS + " to " + (image.getColorModel().getColorSpace().isCS_sRGB() ? "sRGB" : image.getColorModel().getColorSpace()));
|
||||||
|
}
|
||||||
convert = new ColorConvertOp(intendedCS, image.getColorModel().getColorSpace(), null);
|
convert = new ColorConvertOp(intendedCS, image.getColorModel().getColorSpace(), null);
|
||||||
}
|
}
|
||||||
// Else, pass through with no conversion
|
// Else, pass through with no conversion
|
||||||
@ -346,10 +370,20 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
ColorSpace cmykCS = ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK);
|
ColorSpace cmykCS = ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK);
|
||||||
|
|
||||||
if (cmykCS instanceof ICC_ColorSpace) {
|
if (cmykCS instanceof ICC_ColorSpace) {
|
||||||
|
processWarningOccurred(
|
||||||
|
"No embedded ICC color profile, defaulting to \"generic\" CMYK ICC profile. " +
|
||||||
|
"Colors may look incorrect."
|
||||||
|
);
|
||||||
|
|
||||||
convert = new ColorConvertOp(cmykCS, image.getColorModel().getColorSpace(), null);
|
convert = new ColorConvertOp(cmykCS, image.getColorModel().getColorSpace(), null);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// ColorConvertOp using non-ICC CS is deadly slow, fall back to fast conversion instead
|
// ColorConvertOp using non-ICC CS is deadly slow, fall back to fast conversion instead
|
||||||
|
processWarningOccurred(
|
||||||
|
"No embedded ICC color profile, will convert using inaccurate CMYK to RGB conversion. " +
|
||||||
|
"Colors may look incorrect."
|
||||||
|
);
|
||||||
|
|
||||||
convert = new FastCMYKToRGB();
|
convert = new FastCMYKToRGB();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -664,7 +698,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
components[i] = new SOFComponent(id, ((sub & 0xF0) >> 4), (sub & 0xF), qtSel);
|
components[i] = new SOFComponent(id, ((sub & 0xF0) >> 4), (sub & 0xF), qtSel);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SOFSegment(segment.marker(), samplePrecision, lines, samplesPerLine, componentsInFrame, components);
|
return new SOFSegment(segment.marker(), samplePrecision, lines, samplesPerLine, components);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
data.close();
|
data.close();
|
||||||
@ -731,6 +765,10 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
// ICC v 1.42 (2006) annex B:
|
// ICC v 1.42 (2006) annex B:
|
||||||
// APP2 marker (0xFFE2) + 2 byte length + ASCII 'ICC_PROFILE' + 0 (termination)
|
// APP2 marker (0xFFE2) + 2 byte length + ASCII 'ICC_PROFILE' + 0 (termination)
|
||||||
// + 1 byte chunk number + 1 byte chunk count (allows ICC profiles chunked in multiple APP2 segments)
|
// + 1 byte chunk number + 1 byte chunk count (allows ICC profiles chunked in multiple APP2 segments)
|
||||||
|
|
||||||
|
// TODO: Allow metadata to contain the wrongly indexed profiles, if readable
|
||||||
|
// NOTE: We ignore any profile with wrong index for reading and image types, just to be on the safe side
|
||||||
|
|
||||||
List<JPEGSegment> segments = getAppSegments(JPEG.APP2, "ICC_PROFILE");
|
List<JPEGSegment> segments = getAppSegments(JPEG.APP2, "ICC_PROFILE");
|
||||||
|
|
||||||
if (segments.size() == 1) {
|
if (segments.size() == 1) {
|
||||||
@ -741,7 +779,8 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
int chunkCount = stream.readUnsignedByte();
|
int chunkCount = stream.readUnsignedByte();
|
||||||
|
|
||||||
if (chunkNumber != 1 && chunkCount != 1) {
|
if (chunkNumber != 1 && chunkCount != 1) {
|
||||||
processWarningOccurred(String.format("Bad number of 'ICC_PROFILE' chunks: %d of %d. Assuming single chunk.", chunkNumber, chunkCount));
|
processWarningOccurred(String.format("Unexpected number of 'ICC_PROFILE' chunks: %d of %d. Ignoring ICC profile.", chunkNumber, chunkCount));
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return readICCProfileSafe(stream);
|
return readICCProfileSafe(stream);
|
||||||
@ -752,13 +791,15 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
int chunkNumber = stream.readUnsignedByte();
|
int chunkNumber = stream.readUnsignedByte();
|
||||||
int chunkCount = stream.readUnsignedByte();
|
int chunkCount = stream.readUnsignedByte();
|
||||||
|
|
||||||
|
// TODO: Most of the time the ICC profiles are readable and should be obtainable from metadata...
|
||||||
boolean badICC = false;
|
boolean badICC = false;
|
||||||
if (chunkCount != segments.size()) {
|
if (chunkCount != segments.size()) {
|
||||||
// Some weird JPEGs use 0-based indexes... count == 0 and all numbers == 0.
|
// Some weird JPEGs use 0-based indexes... count == 0 and all numbers == 0.
|
||||||
// Others use count == 1, and all numbers == 1.
|
// Others use count == 1, and all numbers == 1.
|
||||||
// Handle these by issuing warning
|
// Handle these by issuing warning
|
||||||
|
processWarningOccurred(String.format("Bad 'ICC_PROFILE' chunk count: %d. Ignoring ICC profile.", chunkCount));
|
||||||
badICC = true;
|
badICC = true;
|
||||||
processWarningOccurred(String.format("Unexpected 'ICC_PROFILE' chunk count: %d. Ignoring count, assuming %d chunks in sequence.", chunkCount, segments.size()));
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!badICC && chunkNumber < 1) {
|
if (!badICC && chunkNumber < 1) {
|
||||||
@ -920,6 +961,51 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
return thumbnails.get(thumbnailIndex).read();
|
return thumbnails.get(thumbnailIndex).read();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
|
||||||
|
// TODO: Nice try, but no cigar.. getAsTree does not return a "live" view, so any modifications are thrown away
|
||||||
|
IIOMetadata metadata = delegate.getImageMetadata(imageIndex);
|
||||||
|
|
||||||
|
IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(metadata.getNativeMetadataFormatName());
|
||||||
|
Node jpegVariety = tree.getElementsByTagName("JPEGvariety").item(0);
|
||||||
|
|
||||||
|
// TODO: Allow EXIF (as app1EXIF) in the JPEGvariety (sic) node.
|
||||||
|
// As EXIF is (a subset of) TIFF, (and the EXIF data is a valid TIFF stream) probably use something like:
|
||||||
|
// http://download.java.net/media/jai-imageio/javadoc/1.1/com/sun/media/imageio/plugins/tiff/package-summary.html#ImageMetadata
|
||||||
|
/*
|
||||||
|
from: http://docs.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html
|
||||||
|
|
||||||
|
In future versions of the JPEG metadata format, other varieties of JPEG metadata may be supported (e.g. Exif)
|
||||||
|
by defining other types of nodes which may appear as a child of the JPEGvariety node.
|
||||||
|
|
||||||
|
(Note that an application wishing to interpret Exif metadata given a metadata tree structure in the
|
||||||
|
javax_imageio_jpeg_image_1.0 format must check for an unknown marker segment with a tag indicating an
|
||||||
|
APP1 marker and containing data identifying it as an Exif marker segment. Then it may use application-specific
|
||||||
|
code to interpret the data in the marker segment. If such an application were to encounter a metadata tree
|
||||||
|
formatted according to a future version of the JPEG metadata format, the Exif marker segment might not be
|
||||||
|
unknown in that format - it might be structured as a child node of the JPEGvariety node.
|
||||||
|
|
||||||
|
Thus, it is important for an application to specify which version to use by passing the string identifying
|
||||||
|
the version to the method/constructor used to obtain an IIOMetadata object.)
|
||||||
|
*/
|
||||||
|
|
||||||
|
IIOMetadataNode app2ICC = new IIOMetadataNode("app2ICC");
|
||||||
|
app2ICC.setUserObject(getEmbeddedICCProfile());
|
||||||
|
jpegVariety.getFirstChild().appendChild(app2ICC);
|
||||||
|
|
||||||
|
// new XMLSerializer(System.err, System.getProperty("file.encoding")).serialize(tree, false);
|
||||||
|
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IIOMetadata getStreamMetadata() throws IOException {
|
||||||
|
return delegate.getStreamMetadata();
|
||||||
|
}
|
||||||
|
|
||||||
private static void invertCMYK(final Raster raster) {
|
private static void invertCMYK(final Raster raster) {
|
||||||
byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
||||||
|
|
||||||
@ -1223,10 +1309,10 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
// image = new ResampleOp(reader.getWidth(0) / 4, reader.getHeight(0) / 4, ResampleOp.FILTER_LANCZOS).filter(image, null);
|
// image = new ResampleOp(reader.getWidth(0) / 4, reader.getHeight(0) / 4, ResampleOp.FILTER_LANCZOS).filter(image, null);
|
||||||
|
|
||||||
// int maxW = 1280;
|
int maxW = 1280;
|
||||||
// int maxH = 800;
|
int maxH = 800;
|
||||||
int maxW = 400;
|
// int maxW = 400;
|
||||||
int maxH = 400;
|
// int maxH = 400;
|
||||||
if (image.getWidth() > maxW || image.getHeight() > maxH) {
|
if (image.getWidth() > maxW || image.getHeight() > maxH) {
|
||||||
// start = System.currentTimeMillis();
|
// start = System.currentTimeMillis();
|
||||||
float aspect = reader.getAspectRatio(0);
|
float aspect = reader.getAspectRatio(0);
|
||||||
|
@ -49,8 +49,9 @@ import static com.twelvemonkeys.lang.Validate.notNull;
|
|||||||
*/
|
*/
|
||||||
final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
|
final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
|
||||||
// TODO: Rewrite JPEGSegment (from metadata) to store stream pos/length, and be able to replay data, and use instead of Segment?
|
// TODO: Rewrite JPEGSegment (from metadata) to store stream pos/length, and be able to replay data, and use instead of Segment?
|
||||||
// TODO: Change order of segments, to make sure APP0/JFIF is always before APP14/Adobe?
|
// TODO: Change order of segments, to make sure APP0/JFIF is always before APP14/Adobe? What about EXIF?
|
||||||
// TODO: Insert fake APP0/JFIF if needed by the reader?
|
// TODO: Insert fake APP0/JFIF if needed by the reader?
|
||||||
|
// TODO: Sort out ICC_PROFILE issues (duplicate sequence numbers etc)?
|
||||||
|
|
||||||
final private ImageInputStream stream;
|
final private ImageInputStream stream;
|
||||||
|
|
||||||
@ -155,7 +156,7 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
|
|||||||
return segment;
|
return segment;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isAppSegmentWithId(String segmentId, ImageInputStream stream) throws IOException {
|
private static boolean isAppSegmentWithId(final String segmentId, final ImageInputStream stream) throws IOException {
|
||||||
notNull(segmentId, "segmentId");
|
notNull(segmentId, "segmentId");
|
||||||
|
|
||||||
stream.mark();
|
stream.mark();
|
||||||
@ -228,7 +229,7 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read(byte[] b, int off, int len) throws IOException {
|
public int read(final byte[] b, final int off, final int len) throws IOException {
|
||||||
bitOffset = 0;
|
bitOffset = 0;
|
||||||
|
|
||||||
// NOTE: There is a bug in the JPEGMetadata constructor (JPEGBuffer.loadBuf() method) that expects read to
|
// NOTE: There is a bug in the JPEGMetadata constructor (JPEGBuffer.loadBuf() method) that expects read to
|
||||||
@ -270,7 +271,7 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
|
|||||||
final long start;
|
final long start;
|
||||||
final long length;
|
final long length;
|
||||||
|
|
||||||
Segment(int marker, long realStart, long start, long length) {
|
Segment(final int marker, final long realStart, final long start, final long length) {
|
||||||
this.marker = marker;
|
this.marker = marker;
|
||||||
this.realStart = realStart;
|
this.realStart = realStart;
|
||||||
this.start = start;
|
this.start = start;
|
||||||
|
@ -42,23 +42,25 @@ final class SOFSegment {
|
|||||||
final int samplePrecision;
|
final int samplePrecision;
|
||||||
final int lines; // height
|
final int lines; // height
|
||||||
final int samplesPerLine; // width
|
final int samplesPerLine; // width
|
||||||
final int componentsInFrame;
|
|
||||||
final SOFComponent[] components;
|
final SOFComponent[] components;
|
||||||
|
|
||||||
SOFSegment(int marker, int samplePrecision, int lines, int samplesPerLine, int componentsInFrame, SOFComponent[] components) {
|
SOFSegment(int marker, int samplePrecision, int lines, int samplesPerLine, SOFComponent[] components) {
|
||||||
this.marker = marker;
|
this.marker = marker;
|
||||||
this.samplePrecision = samplePrecision;
|
this.samplePrecision = samplePrecision;
|
||||||
this.lines = lines;
|
this.lines = lines;
|
||||||
this.samplesPerLine = samplesPerLine;
|
this.samplesPerLine = samplesPerLine;
|
||||||
this.componentsInFrame = componentsInFrame;
|
|
||||||
this.components = components;
|
this.components = components;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final int componentsInFrame() {
|
||||||
|
return components.length;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format(
|
return String.format(
|
||||||
"SOF%d[%04x, precision: %d, lines: %d, samples/line: %d, components: %s]",
|
"SOF%d[%04x, precision: %d, lines: %d, samples/line: %d, components: %s]",
|
||||||
marker & 0xf, marker, samplePrecision, lines, samplesPerLine, Arrays.toString(components)
|
marker & 0xff - 0xc0, marker, samplePrecision, lines, samplesPerLine, Arrays.toString(components)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,6 +82,10 @@ public interface JPEG {
|
|||||||
int SOF14 = 0xFFCE;
|
int SOF14 = 0xFFCE;
|
||||||
int SOF15 = 0xFFCF;
|
int SOF15 = 0xFFCF;
|
||||||
|
|
||||||
|
// JPEG-LS markers
|
||||||
|
int SOF55 = 0xFFF7; // NOTE: Equal to a normal SOF segment
|
||||||
|
int LSE = 0xFFF8; // JPEG-LS Preset Parameter marker
|
||||||
|
|
||||||
// TODO: Known/Important APPn marker identifiers
|
// TODO: Known/Important APPn marker identifiers
|
||||||
// "JFIF" APP0
|
// "JFIF" APP0
|
||||||
// "JFXX" APP0
|
// "JFXX" APP0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user