File: JPEGLosslessImageReader.java

package info (click to toggle)
pixelmed-codec 20200328-6
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 432 kB
  • sloc: java: 2,855; makefile: 249; sh: 1
file content (329 lines) | stat: -rw-r--r-- 10,701 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
/* Copyright (c) 2015, David A. Clunie DBA Pixelmed Publishing. All rights reserved. */

package com.pixelmed.imageio;

// follow the pattern described in "http://docs.oracle.com/javase/1.5.0/docs/guide/imageio/spec/extending.fm3.html"

import com.pixelmed.codec.jpeg.MarkerSegmentSOF;
import com.pixelmed.codec.jpeg.OutputArrayOrStream;
import com.pixelmed.codec.jpeg.Parse;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.IOException;

import java.nio.ByteOrder;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import java.awt.Point;
import java.awt.Transparency;

import java.awt.color.ColorSpace;

import java.awt.image.BufferedImage;
import java.awt.image.ComponentColorModel;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferUShort;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;

import javax.imageio.ImageReader;
import javax.imageio.IIOException;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;

public class JPEGLosslessImageReader extends ImageReader {

	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 $";

	ImageInputStream stream = null;
	
	int width;
	int height;
	int bitDepth;
	
	Parse.DecompressedOutput decompressedOutput = null;
	
	boolean gotEverything = false;

	public JPEGLosslessImageReader(ImageReaderSpi originatingProvider) {
		super(originatingProvider);
	}
	
	public void reset() {
System.err.println("reset()");
		super.reset();
		stream = null;
		gotEverything = false;
		decompressedOutput = null;
	}

	public void setInput(Object input, boolean isStreamable,boolean ignoreMetadata) {	// contrary to docs, need to override three argument method
//System.err.println("JPEGLosslessImageReader.setInput("+input+","+isStreamable+"/*isStreamable*/,"+ignoreMetadata+"/*ignoreMetadata*/)");
		super.setInput(input,isStreamable,ignoreMetadata);
		if (input == null) {
			this.stream = null;
			return;
		}
		if (input instanceof ImageInputStream) {
			this.stream = (ImageInputStream)input;
		}
		else {
			throw new IllegalArgumentException("bad input");
		}
		// just in case we don't call reset() before reusing reader ...
		gotEverything = false;
		decompressedOutput = null;
	}

	public int getNumImages(boolean allowSearch) throws IIOException {
		return 1; // format can only encode a single image
	}

	private void checkIndex(int imageIndex) {
		// format can only encode a single image
		if (imageIndex != 0) {
			throw new IndexOutOfBoundsException("bad index");
		}
	}

	public int getWidth(int imageIndex) throws IIOException {
		checkIndex(imageIndex); // will throw an exception if != 0
		readEverything();
		return width;
	}

	public int getHeight(int imageIndex) throws IIOException {
		checkIndex(imageIndex); // will throw an exception if != 0
		readEverything();
		return height;
	}

	public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IIOException {
		checkIndex(imageIndex);
		readEverything();
		
		ImageTypeSpecifier imageType = null;
		List l = new ArrayList<ImageTypeSpecifier>();
		imageType = ImageTypeSpecifier.createGrayscale(
			bitDepth,
			bitDepth <= 8 ? DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT,
			false/*isSigned*/);	// have no way to determine from the JPEG lossless bitstream if signed or not
		l.add(imageType);
		return l.iterator();
	}
	
	private final class WrapImageInputStreamAsInputStream extends InputStream {
		private final ImageInputStream iis;
		
		private WrapImageInputStreamAsInputStream() {
			iis = null;
		}
		
		public WrapImageInputStreamAsInputStream(ImageInputStream iis) {
			this.iis = iis;
		}
		
		public final int available() { return 0; }	// no such method in ImageInputStream
		
		public final void close() throws IOException { iis.close(); }
		
		public final void mark(int readlimit) { iis.mark(); }		// ImageInputStream has no readlimit
		
		public final boolean markSupported() { return true; }		// always supported
		
		public final int read() throws IOException { return iis.read(); }
		
		public final int read(byte[] b) throws IOException { return iis.read(b); }
		
		public final int read(byte[] b, int off, int len) throws IOException { return iis.read(b,off,len); }
		
		public final void reset() throws IOException { iis.reset(); }
		
		public final long skip(long n) throws IOException { return iis.skipBytes(n); }
	}
	
	public void readEverything() throws IIOException {
		if (gotEverything) {
			return;
		}
		gotEverything = true;
		
		if (stream == null) {
			throw new IllegalStateException("No input stream");
		}
		decompressedOutput = new Parse.DecompressedOutput();		// allocation to byte or short, and setting of correct size, will be done by com.pixelmed.codec.jpeg.Parse
		try {
			Parse.MarkerSegmentsFoundDuringParse markerSegments = Parse.parse(new WrapImageInputStreamAsInputStream(stream),null,null,decompressedOutput);
			MarkerSegmentSOF sof = markerSegments != null ? markerSegments.getSOF() : null;
			if (sof != null) {
				if (sof.getNComponentsInFrame() != 1 && sof.getNComponentsInFrame() != 3) {
					throw new IIOException("Error reading JPEG stream - only single component (grayscale) or three component supported)");
				}
				width = sof.getNSamplesPerLine();
				height = sof.getNLines();
				bitDepth = sof.getSamplePrecision();
			}
			else {
				throw new IIOException("Error reading JPEG stream - no SOS or SOF marker segment parsed");
			}
		}
		catch (Exception e) {
			throw new IIOException("Error reading JPEG stream",e);
		}
	}

	public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException {
		checkIndex(imageIndex);
		readEverything();
		
		BufferedImage image = null;
		
		OutputArrayOrStream[] decompressedOutputPerComponent = decompressedOutput.getDecompressedOutputPerComponent();
		
		ComponentColorModel cm = null;
		ComponentSampleModel sm = null;
		DataBuffer buf = null;
		if (decompressedOutputPerComponent.length == 1) {
			if (bitDepth <= 8) {
				// copied from com.pixelmed.display.SourceImage.createByteGrayscaleImage() ...
				cm=new ComponentColorModel(
										   ColorSpace.getInstance(ColorSpace.CS_GRAY),
										   new int[] {8},
										   false,		// has alpha
										   false,		// alpha premultipled
										   Transparency.OPAQUE,
										   DataBuffer.TYPE_BYTE
										   );
				sm = new ComponentSampleModel(
											  DataBuffer.TYPE_BYTE,
											  width,
											  height,
											  1,
											  width,
											  new int[] {0}
											  );
				buf = new DataBufferByte(decompressedOutputPerComponent[0].getByteArray(),width,0);
			}
			else {
				// copied from com.pixelmed.display.SourceImage.createUnsignedShortGrayscaleImage() ...
				cm=new ComponentColorModel(
										   ColorSpace.getInstance(ColorSpace.CS_GRAY),
										   new int[] {16},
										   false,		// has alpha
										   false,		// alpha premultipled
										   Transparency.OPAQUE,
										   DataBuffer.TYPE_USHORT
										   );
				sm = new ComponentSampleModel(
											  DataBuffer.TYPE_USHORT,
											  width,
											  height,
											  1,
											  width,
											  new int[] {0}
											  );
				buf = new DataBufferUShort(decompressedOutputPerComponent[0].getShortArray(),width,0);
			}
		}
		else if (decompressedOutputPerComponent.length == 3) {
			// the decompressedOutput has separated the input into separate arrays, each of which we can use as a bank and use a band interleaved model
			if (bitDepth <= 8) {
				// copied from com.pixelmed.display.SourceImage.createBandInterleavedByteRGBImage(), except that we have three rather than one banks ...
				cm=new ComponentColorModel(
										   ColorSpace.getInstance(ColorSpace.CS_sRGB),	// lie if YCbCr (we don't know at this point) :(
										   new int[] {8,8,8},
										   false,		// has alpha
										   false,		// alpha premultipled
										   Transparency.OPAQUE,
										   DataBuffer.TYPE_BYTE
										   );
				sm = new ComponentSampleModel(
											  DataBuffer.TYPE_BYTE,
											  width,
											  height,
											  1/*pixelStride*/,
											  width/*scanlineStride*/,
											  new int[] {0,1,2}/*bankIndices*/,
											  new int[] {0,0,0}/*bandOffsets*/
											  );
				buf = new DataBufferByte(
					new byte[][] {
						decompressedOutputPerComponent[0].getByteArray(),
						decompressedOutputPerComponent[1].getByteArray(),
						decompressedOutputPerComponent[2].getByteArray(),
					},
					width*height);
			}
			else {
				// not really expecting to see > 8 bit color per channel, but no reason not to build it ... probably not tested yet though :(
				cm=new ComponentColorModel(
										   ColorSpace.getInstance(ColorSpace.CS_sRGB),	// lie if YCbCr (we don't know at this point) :(
										   new int[] {16,16,16},
										   false,		// has alpha
										   false,		// alpha premultipled
										   Transparency.OPAQUE,
										   DataBuffer.TYPE_USHORT
										   );
				sm = new ComponentSampleModel(
											  DataBuffer.TYPE_USHORT,
											  width,
											  height,
											  1/*pixelStride*/,
											  width/*scanlineStride*/,
											  new int[] {0,1,2}/*bankIndices*/,
											  new int[] {0,0,0}/*bandOffsets*/
											  );
				buf = new DataBufferUShort(
					new short[][] {
						decompressedOutputPerComponent[0].getShortArray(),
						decompressedOutputPerComponent[1].getShortArray(),
						decompressedOutputPerComponent[2].getShortArray(),
					},
					width*height);
			}
		}
		
		if (buf != null) {
			WritableRaster wr = Raster.createWritableRaster(sm,buf,new Point(0,0));
			image = new BufferedImage(cm,wr,true,null);	// no properties hash table
		}
		
		return image;
	}

	JPEGLosslessMetadata metadata = null;

	public IIOMetadata getStreamMetadata() throws IIOException {
		return null;
	}

	public IIOMetadata getImageMetadata(int imageIndex) throws IIOException {
		if (imageIndex != 0) {
			throw new IndexOutOfBoundsException("imageIndex != 0!");
		}
		readMetadata();
		return metadata;
	}
	
	public void readMetadata() throws IIOException {
		if (metadata != null) {
			return;
		}
		readEverything();
		this.metadata = new JPEGLosslessMetadata();
		//try {
		//}
		//catch (IOException e) {
		//	throw new IIOException("Exception reading metadata", e);
		//}
	}
}