pixelmed
JPEGLosslessImageReader.java
Go to the documentation of this file.
1 /* Copyright (c) 2015, David A. Clunie DBA Pixelmed Publishing. All rights reserved. */
2 
3 package com.pixelmed.imageio;
4 
5 // follow the pattern described in "http://docs.oracle.com/javase/1.5.0/docs/guide/imageio/spec/extending.fm3.html"
6 
10 
11 import java.io.ByteArrayOutputStream;
12 import java.io.InputStream;
13 import java.io.IOException;
14 
15 import java.nio.ByteOrder;
16 
17 import java.util.ArrayList;
18 import java.util.Iterator;
19 import java.util.List;
20 
21 import java.awt.Point;
22 import java.awt.Transparency;
23 
24 import java.awt.color.ColorSpace;
25 
26 import java.awt.image.BufferedImage;
27 import java.awt.image.ComponentColorModel;
28 import java.awt.image.ComponentSampleModel;
29 import java.awt.image.DataBuffer;
30 import java.awt.image.DataBufferByte;
31 import java.awt.image.DataBufferUShort;
32 import java.awt.image.Raster;
33 import java.awt.image.WritableRaster;
34 
35 import javax.imageio.ImageReader;
36 import javax.imageio.IIOException;
37 import javax.imageio.ImageReadParam;
38 import javax.imageio.ImageTypeSpecifier;
39 import javax.imageio.metadata.IIOMetadata;
40 import javax.imageio.spi.ImageReaderSpi;
41 import javax.imageio.stream.ImageInputStream;
42 
43 public class JPEGLosslessImageReader extends ImageReader {
44 
45  private static final String identString = "@(#) $Header: /userland/cvs/codec/com/pixelmed/imageio/JPEGLosslessImageReader.java,v 1.8 2015/10/19 15:34:42 dclunie Exp $";
46 
47  ImageInputStream stream = null;
48 
49  int width;
50  int height;
51  int bitDepth;
52 
53  Parse.DecompressedOutput decompressedOutput = null;
54 
55  boolean gotEverything = false;
56 
57  public JPEGLosslessImageReader(ImageReaderSpi originatingProvider) {
58  super(originatingProvider);
59  }
60 
61  public void reset() {
62 System.err.println("reset()");
63  super.reset();
64  stream = null;
65  gotEverything = false;
66  decompressedOutput = null;
67  }
68 
69  public void setInput(Object input, boolean isStreamable,boolean ignoreMetadata) { // contrary to docs, need to override three argument method
70 //System.err.println("JPEGLosslessImageReader.setInput("+input+","+isStreamable+"/*isStreamable*/,"+ignoreMetadata+"/*ignoreMetadata*/)");
71  super.setInput(input,isStreamable,ignoreMetadata);
72  if (input == null) {
73  this.stream = null;
74  return;
75  }
76  if (input instanceof ImageInputStream) {
77  this.stream = (ImageInputStream)input;
78  }
79  else {
80  throw new IllegalArgumentException("bad input");
81  }
82  // just in case we don't call reset() before reusing reader ...
83  gotEverything = false;
84  decompressedOutput = null;
85  }
86 
87  public int getNumImages(boolean allowSearch) throws IIOException {
88  return 1; // format can only encode a single image
89  }
90 
91  private void checkIndex(int imageIndex) {
92  // format can only encode a single image
93  if (imageIndex != 0) {
94  throw new IndexOutOfBoundsException("bad index");
95  }
96  }
97 
98  public int getWidth(int imageIndex) throws IIOException {
99  checkIndex(imageIndex); // will throw an exception if != 0
100  readEverything();
101  return width;
102  }
103 
104  public int getHeight(int imageIndex) throws IIOException {
105  checkIndex(imageIndex); // will throw an exception if != 0
106  readEverything();
107  return height;
108  }
109 
110  public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IIOException {
111  checkIndex(imageIndex);
112  readEverything();
113 
114  ImageTypeSpecifier imageType = null;
115  List l = new ArrayList<ImageTypeSpecifier>();
116  imageType = ImageTypeSpecifier.createGrayscale(
117  bitDepth,
118  bitDepth <= 8 ? DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT,
119  false/*isSigned*/); // have no way to determine from the JPEG lossless bitstream if signed or not
120  l.add(imageType);
121  return l.iterator();
122  }
123 
124  private final class WrapImageInputStreamAsInputStream extends InputStream {
125  private final ImageInputStream iis;
126 
127  private WrapImageInputStreamAsInputStream() {
128  iis = null;
129  }
130 
131  public WrapImageInputStreamAsInputStream(ImageInputStream iis) {
132  this.iis = iis;
133  }
134 
135  public final int available() { return 0; } // no such method in ImageInputStream
136 
137  public final void close() throws IOException { iis.close(); }
138 
139  public final void mark(int readlimit) { iis.mark(); } // ImageInputStream has no readlimit
140 
141  public final boolean markSupported() { return true; } // always supported
142 
143  public final int read() throws IOException { return iis.read(); }
144 
145  public final int read(byte[] b) throws IOException { return iis.read(b); }
146 
147  public final int read(byte[] b, int off, int len) throws IOException { return iis.read(b,off,len); }
148 
149  public final void reset() throws IOException { iis.reset(); }
150 
151  public final long skip(long n) throws IOException { return iis.skipBytes(n); }
152  }
153 
154  public void readEverything() throws IIOException {
155  if (gotEverything) {
156  return;
157  }
158  gotEverything = true;
159 
160  if (stream == null) {
161  throw new IllegalStateException("No input stream");
162  }
163  decompressedOutput = new Parse.DecompressedOutput(); // allocation to byte or short, and setting of correct size, will be done by com.pixelmed.codec.jpeg.Parse
164  try {
165  Parse.MarkerSegmentsFoundDuringParse markerSegments = Parse.parse(new WrapImageInputStreamAsInputStream(stream),null,null,decompressedOutput);
166  MarkerSegmentSOF sof = markerSegments != null ? markerSegments.getSOF() : null;
167  if (sof != null) {
168  if (sof.getNComponentsInFrame() != 1 && sof.getNComponentsInFrame() != 3) {
169  throw new IIOException("Error reading JPEG stream - only single component (grayscale) or three component supported)");
170  }
171  width = sof.getNSamplesPerLine();
172  height = sof.getNLines();
173  bitDepth = sof.getSamplePrecision();
174  }
175  else {
176  throw new IIOException("Error reading JPEG stream - no SOS or SOF marker segment parsed");
177  }
178  }
179  catch (Exception e) {
180  throw new IIOException("Error reading JPEG stream",e);
181  }
182  }
183 
184  public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException {
185  checkIndex(imageIndex);
186  readEverything();
187 
188  BufferedImage image = null;
189 
190  OutputArrayOrStream[] decompressedOutputPerComponent = decompressedOutput.getDecompressedOutputPerComponent();
191 
192  ComponentColorModel cm = null;
193  ComponentSampleModel sm = null;
194  DataBuffer buf = null;
195  if (decompressedOutputPerComponent.length == 1) {
196  if (bitDepth <= 8) {
197  // copied from com.pixelmed.display.SourceImage.createByteGrayscaleImage() ...
198  cm=new ComponentColorModel(
199  ColorSpace.getInstance(ColorSpace.CS_GRAY),
200  new int[] {8},
201  false, // has alpha
202  false, // alpha premultipled
203  Transparency.OPAQUE,
204  DataBuffer.TYPE_BYTE
205  );
206  sm = new ComponentSampleModel(
207  DataBuffer.TYPE_BYTE,
208  width,
209  height,
210  1,
211  width,
212  new int[] {0}
213  );
214  buf = new DataBufferByte(decompressedOutputPerComponent[0].getByteArray(),width,0);
215  }
216  else {
217  // copied from com.pixelmed.display.SourceImage.createUnsignedShortGrayscaleImage() ...
218  cm=new ComponentColorModel(
219  ColorSpace.getInstance(ColorSpace.CS_GRAY),
220  new int[] {16},
221  false, // has alpha
222  false, // alpha premultipled
223  Transparency.OPAQUE,
224  DataBuffer.TYPE_USHORT
225  );
226  sm = new ComponentSampleModel(
227  DataBuffer.TYPE_USHORT,
228  width,
229  height,
230  1,
231  width,
232  new int[] {0}
233  );
234  buf = new DataBufferUShort(decompressedOutputPerComponent[0].getShortArray(),width,0);
235  }
236  }
237  else if (decompressedOutputPerComponent.length == 3) {
238  // the decompressedOutput has separated the input into separate arrays, each of which we can use as a bank and use a band interleaved model
239  if (bitDepth <= 8) {
240  // copied from com.pixelmed.display.SourceImage.createBandInterleavedByteRGBImage(), except that we have three rather than one banks ...
241  cm=new ComponentColorModel(
242  ColorSpace.getInstance(ColorSpace.CS_sRGB), // lie if YCbCr (we don't know at this point) :(
243  new int[] {8,8,8},
244  false, // has alpha
245  false, // alpha premultipled
246  Transparency.OPAQUE,
247  DataBuffer.TYPE_BYTE
248  );
249  sm = new ComponentSampleModel(
250  DataBuffer.TYPE_BYTE,
251  width,
252  height,
253  1/*pixelStride*/,
254  width/*scanlineStride*/,
255  new int[] {0,1,2}/*bankIndices*/,
256  new int[] {0,0,0}/*bandOffsets*/
257  );
258  buf = new DataBufferByte(
259  new byte[][] {
260  decompressedOutputPerComponent[0].getByteArray(),
261  decompressedOutputPerComponent[1].getByteArray(),
262  decompressedOutputPerComponent[2].getByteArray(),
263  },
264  width*height);
265  }
266  else {
267  // not really expecting to see > 8 bit color per channel, but no reason not to build it ... probably not tested yet though :(
268  cm=new ComponentColorModel(
269  ColorSpace.getInstance(ColorSpace.CS_sRGB), // lie if YCbCr (we don't know at this point) :(
270  new int[] {16,16,16},
271  false, // has alpha
272  false, // alpha premultipled
273  Transparency.OPAQUE,
274  DataBuffer.TYPE_USHORT
275  );
276  sm = new ComponentSampleModel(
277  DataBuffer.TYPE_USHORT,
278  width,
279  height,
280  1/*pixelStride*/,
281  width/*scanlineStride*/,
282  new int[] {0,1,2}/*bankIndices*/,
283  new int[] {0,0,0}/*bandOffsets*/
284  );
285  buf = new DataBufferUShort(
286  new short[][] {
287  decompressedOutputPerComponent[0].getShortArray(),
288  decompressedOutputPerComponent[1].getShortArray(),
289  decompressedOutputPerComponent[2].getShortArray(),
290  },
291  width*height);
292  }
293  }
294 
295  if (buf != null) {
296  WritableRaster wr = Raster.createWritableRaster(sm,buf,new Point(0,0));
297  image = new BufferedImage(cm,wr,true,null); // no properties hash table
298  }
299 
300  return image;
301  }
302 
303  JPEGLosslessMetadata metadata = null;
304 
305  public IIOMetadata getStreamMetadata() throws IIOException {
306  return null;
307  }
308 
309  public IIOMetadata getImageMetadata(int imageIndex) throws IIOException {
310  if (imageIndex != 0) {
311  throw new IndexOutOfBoundsException("imageIndex != 0!");
312  }
313  readMetadata();
314  return metadata;
315  }
316 
317  public void readMetadata() throws IIOException {
318  if (metadata != null) {
319  return;
320  }
321  readEverything();
322  this.metadata = new JPEGLosslessMetadata();
323  //try {
324  //}
325  //catch (IOException e) {
326  // throw new IIOException("Exception reading metadata", e);
327  //}
328  }
329 }
com.pixelmed.imageio.JPEGLosslessImageReader.getNumImages
int getNumImages(boolean allowSearch)
Definition: JPEGLosslessImageReader.java:87
com.pixelmed.codec.jpeg.OutputArrayOrStream
Definition: OutputArrayOrStream.java:22
com.pixelmed.codec.jpeg.Parse.parse
static MarkerSegmentsFoundDuringParse parse(InputStream in, OutputStream copiedRedactedOutputStream, Vector< Shape > redactionShapes, DecompressedOutput decompressedOutput)
Definition: Parse.java:169
com.pixelmed.codec.jpeg
Definition: EntropyCodedSegment copy.java:3
com.pixelmed.imageio.JPEGLosslessImageReader.reset
void reset()
Definition: JPEGLosslessImageReader.java:61
com.pixelmed.imageio.JPEGLosslessImageReader.setInput
void setInput(Object input, boolean isStreamable, boolean ignoreMetadata)
Definition: JPEGLosslessImageReader.java:69
com.pixelmed.imageio.JPEGLosslessImageReader.readMetadata
void readMetadata()
Definition: JPEGLosslessImageReader.java:317
com.pixelmed
com.pixelmed.codec.jpeg.MarkerSegmentSOF.getSamplePrecision
int getSamplePrecision()
Definition: MarkerSegmentSOF.java:26
com.pixelmed.imageio.JPEGLosslessImageReader.getHeight
int getHeight(int imageIndex)
Definition: JPEGLosslessImageReader.java:104
com.pixelmed.codec.jpeg.Parse
Definition: Parse.java:33
com.pixelmed.codec.jpeg.OutputArrayOrStream.getShortArray
short[] getShortArray()
Definition: OutputArrayOrStream.java:106
com.pixelmed.codec.jpeg.OutputArrayOrStream.getByteArray
byte[] getByteArray()
Definition: OutputArrayOrStream.java:102
com.pixelmed.imageio.JPEGLosslessMetadata
Definition: JPEGLosslessMetadata.java:18
com.pixelmed.codec.jpeg.MarkerSegmentSOF.getNLines
int getNLines()
Definition: MarkerSegmentSOF.java:27
com.pixelmed.imageio.JPEGLosslessImageReader.read
BufferedImage read(int imageIndex, ImageReadParam param)
Definition: JPEGLosslessImageReader.java:184
com.pixelmed.codec.jpeg.MarkerSegmentSOF
Definition: MarkerSegmentSOF.java:10
com.pixelmed.imageio.JPEGLosslessImageReader.getStreamMetadata
IIOMetadata getStreamMetadata()
Definition: JPEGLosslessImageReader.java:305
com.pixelmed.imageio.JPEGLosslessImageReader.readEverything
void readEverything()
Definition: JPEGLosslessImageReader.java:154
com.pixelmed.codec.jpeg.Parse.DecompressedOutput
Definition: Parse.java:59
com.pixelmed.codec
com.pixelmed.imageio.JPEGLosslessImageReader.JPEGLosslessImageReader
JPEGLosslessImageReader(ImageReaderSpi originatingProvider)
Definition: JPEGLosslessImageReader.java:57
com.pixelmed.imageio.JPEGLosslessImageReader.getImageTypes
Iterator< ImageTypeSpecifier > getImageTypes(int imageIndex)
Definition: JPEGLosslessImageReader.java:110
com.pixelmed.codec.jpeg.MarkerSegmentSOF.getNSamplesPerLine
int getNSamplesPerLine()
Definition: MarkerSegmentSOF.java:28
com.pixelmed.imageio.JPEGLosslessImageReader.getImageMetadata
IIOMetadata getImageMetadata(int imageIndex)
Definition: JPEGLosslessImageReader.java:309
com.pixelmed.imageio.JPEGLosslessImageReader
Definition: JPEGLosslessImageReader.java:43
com.pixelmed.codec.jpeg.MarkerSegmentSOF.getNComponentsInFrame
int getNComponentsInFrame()
Definition: MarkerSegmentSOF.java:29
com
com.pixelmed.codec.jpeg.Parse.MarkerSegmentsFoundDuringParse
Definition: Parse.java:133
com.pixelmed.imageio.JPEGLosslessImageReader.getWidth
int getWidth(int imageIndex)
Definition: JPEGLosslessImageReader.java:98