001/* ***** BEGIN LICENSE BLOCK *****
002 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
003 *
004 * The contents of this file are subject to the Mozilla Public License Version
005 * 1.1 (the "License"); you may not use this file except in compliance with
006 * the License. You may obtain a copy of the License at
007 * http://www.mozilla.org/MPL/
008 *
009 * Software distributed under the License is distributed on an "AS IS" basis,
010 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
011 * for the specific language governing rights and limitations under the
012 * License.
013 *
014 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in
015 * Java(TM), hosted at https://github.com/gunterze/dcm4che.
016 *
017 * The Initial Developer of the Original Code is
018 * Agfa Healthcare.
019 * Portions created by the Initial Developer are Copyright (C) 2013
020 * the Initial Developer. All Rights Reserved.
021 *
022 * Contributor(s):
023 * See @authors listed below
024 *
025 * Alternatively, the contents of this file may be used under the terms of
026 * either the GNU General Public License Version 2 or later (the "GPL"), or
027 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
028 * in which case the provisions of the GPL or the LGPL are applicable instead
029 * of those above. If you wish to allow use of your version of this file only
030 * under the terms of either the GPL or the LGPL, and not to allow others to
031 * use your version of this file under the terms of the MPL, indicate your
032 * decision by deleting the provisions above and replace them with the notice
033 * and other provisions required by the GPL or the LGPL. If you do not delete
034 * the provisions above, a recipient may use your version of this file under
035 * the terms of any one of the MPL, the GPL or the LGPL.
036 *
037 * ***** END LICENSE BLOCK ***** */
038
039package org.dcm4che3.imageio.plugins.rle;
040
041import java.awt.image.BufferedImage;
042import java.awt.image.DataBuffer;
043import java.awt.image.DataBufferByte;
044import java.awt.image.DataBufferShort;
045import java.awt.image.DataBufferUShort;
046import java.awt.image.Raster;
047import java.awt.image.SampleModel;
048import java.awt.image.WritableRaster;
049import java.io.EOFException;
050import java.io.IOException;
051import java.util.Iterator;
052
053import javax.imageio.ImageReadParam;
054import javax.imageio.ImageReader;
055import javax.imageio.ImageTypeSpecifier;
056import javax.imageio.metadata.IIOMetadata;
057import javax.imageio.spi.ImageReaderSpi;
058import javax.imageio.stream.ImageInputStream;
059
060import org.dcm4che3.util.ByteUtils;
061import org.slf4j.Logger;
062import org.slf4j.LoggerFactory;
063
064/**
065 * @author Gunter Zeilinger <gunterze@gmail.com>
066 *
067 */
068public class RLEImageReader extends ImageReader {
069
070    private static Logger LOG = LoggerFactory.getLogger(RLEImageReader.class);
071
072    private static final String UNKNOWN_IMAGE_TYPE =
073            "RLE Image Reader needs ImageReadParam.destination or "
074            + "ImageReadParam.destinationType specified";
075    private static final String UNSUPPORTED_DATA_TYPE =
076            "Unsupported Data Type of ImageReadParam.destination or "
077            + "ImageReadParam.destinationType: ";
078    private static final String MISMATCH_NUM_RLE_SEGMENTS =
079            "Number of RLE Segments does not match image type: ";
080
081    private final int[] header = new int[16];
082
083    private final byte[] buf = new byte[8192];
084
085    private long headerPos;
086
087    private long bufOff;
088
089    private int bufPos;
090
091    private int bufLen;
092
093    private ImageInputStream iis;
094
095    private int width;
096
097    private int height;
098
099    protected RLEImageReader(ImageReaderSpi originatingProvider) {
100        super(originatingProvider);
101    }
102
103    @Override
104    public void setInput(Object input, boolean seekForwardOnly,
105            boolean ignoreMetadata) {
106        super.setInput(input, seekForwardOnly, ignoreMetadata);
107        resetInternalState();
108        iis = (ImageInputStream) input;
109    }
110
111    private void resetInternalState() {
112        width = 0;
113        height = 0;
114    }
115
116    @Override
117    public int getNumImages(boolean allowSearch) throws IOException {
118        return 1;
119    }
120
121    @Override
122    public int getWidth(int imageIndex) throws IOException {
123        return width;
124    }
125
126    @Override
127    public int getHeight(int imageIndex) throws IOException {
128        return height;
129    }
130
131    @Override
132    public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex)
133            throws IOException {
134        return null;
135    }
136
137    @Override
138    public IIOMetadata getStreamMetadata() throws IOException {
139        return null;
140    }
141
142    @Override
143    public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
144        return null;
145    }
146
147
148    @Override
149    public boolean canReadRaster() {
150        return true;
151    }
152
153    @Override
154    public Raster readRaster(int imageIndex, ImageReadParam param)
155            throws IOException {
156        checkIndex(imageIndex);
157
158        WritableRaster raster = getDestinationRaster(param);
159        read(raster.getDataBuffer());
160        return raster;
161    }
162
163    @Override
164    public BufferedImage read(int imageIndex, ImageReadParam param)
165            throws IOException {
166        checkIndex(imageIndex);
167
168        BufferedImage bi = getDestination(param);
169        read(bi.getRaster().getDataBuffer());
170        return bi;
171    }
172
173    private void checkIndex(int imageIndex) {
174        if (imageIndex != 0)
175            throw new IndexOutOfBoundsException("imageIndex: " + imageIndex);
176    }
177
178    private BufferedImage getDestination(ImageReadParam param) {
179        if (param == null)
180            throw new IllegalArgumentException(UNKNOWN_IMAGE_TYPE);
181
182        BufferedImage bi = param.getDestination();
183        if (bi != null) {
184            width = bi.getWidth();
185            height = bi.getHeight();
186            return bi;
187        }
188        
189        ImageTypeSpecifier imageType = param.getDestinationType();
190        if (imageType != null) {
191            SampleModel sm = imageType.getSampleModel();
192            width = sm.getWidth();
193            height = sm.getHeight();
194            return imageType.createBufferedImage(width, height);
195        }
196        throw new IllegalArgumentException(UNKNOWN_IMAGE_TYPE);
197    }
198
199    private WritableRaster getDestinationRaster(ImageReadParam param) {
200        if (param == null)
201            throw new IllegalArgumentException(UNKNOWN_IMAGE_TYPE);
202
203        BufferedImage bi = param.getDestination();
204        if (bi != null) {
205            width = bi.getWidth();
206            height = bi.getHeight();
207            return bi.getRaster();
208        }
209
210        ImageTypeSpecifier imageType = param.getDestinationType();
211        if (imageType != null) {
212            SampleModel sm = imageType.getSampleModel();
213            width = sm.getWidth();
214            height = sm.getHeight();
215            return Raster.createWritableRaster(sm, null);
216        }
217        throw new IllegalArgumentException(UNKNOWN_IMAGE_TYPE);
218    }
219
220    private void read(DataBuffer db) throws IOException {
221        switch (db.getDataType()) {
222        case DataBuffer.TYPE_BYTE:
223            read(((DataBufferByte) db).getBankData());
224            break;
225        case DataBuffer.TYPE_USHORT:
226            read(((DataBufferUShort) db).getData());
227            break;
228        case DataBuffer.TYPE_SHORT:
229            read(((DataBufferShort) db).getData());
230            break;
231        default:
232            throw new IllegalArgumentException(
233                    UNSUPPORTED_DATA_TYPE + db.getDataType());
234        }
235    }
236
237    private void read(byte[][] bands) throws IOException {
238        readRLEHeader(bands.length);
239        for (int i = 0; i < bands.length; i++)
240            unrle(i+1, bands[i]);
241    }
242
243    private void read(short[] data) throws IOException {
244        readRLEHeader(2);
245        unrle(1, data);
246        unrle(2, data);
247    }
248
249    private void seekSegment(int seg) throws IOException {
250        long streamPos = headerPos + (header[seg] & 0xffffffffL);
251        int bufPos = (int) (streamPos - bufOff);
252        if (bufPos >= 0 && bufPos <= bufLen)
253            this.bufPos = bufPos;
254        else {
255            iis.seek(streamPos);
256            this.bufPos = bufLen; // force fillBuffer on nextByte()
257        }
258    }
259
260
261    private void readRLEHeader(int numSegments) throws IOException {
262        fillBuffer();
263        if (bufLen < 64)
264            throw new EOFException();
265        for (int i = 0, off = 0; i < header.length; i++, off += 4)
266            header[i] = ByteUtils.bytesToIntLE(buf, off);
267        bufPos = 64;
268        if (header[0] != numSegments)
269            throw new IOException(MISMATCH_NUM_RLE_SEGMENTS + header[0]);
270    }
271
272    private void unrle(int seg, byte[] data) throws IOException {
273        seekSegment(seg);
274        int pos = 0;
275        try {
276            int n;
277            int end;
278            byte val;
279            while (pos < data.length) {
280                n = nextByte();
281                if (n >= 0) {
282                    read(data, pos, ++n);
283                    pos += n;
284                } else if (n != -128) {
285                    end = pos + 1 - n;
286                    val = nextByte();
287                    while (pos < end)
288                        data[pos++] = val;
289                }
290            }
291        } catch (EOFException e) {
292            LOG.info("RLE Segment #{} too short, set missing {} bytes to 0",
293                    seg, data.length - pos);
294        } catch (IndexOutOfBoundsException e) {
295            LOG.info("RLE Segment #{} too long, truncate surplus bytes", seg);
296        }
297    }
298
299    private void read(byte[] data, int pos, int len) throws IOException {
300        int remaining = len;
301        int n;
302        while (remaining > 0) {
303            n = bufLen - bufPos;
304            if (n <= 0) {
305                fillBuffer();
306                n = bufLen - bufPos;
307            }
308            if ((remaining -= n) < 0)
309                n += remaining;
310            System.arraycopy(buf, bufPos, data, pos, n);
311            bufPos += n;
312            pos += n;
313        }
314    }
315
316    private void unrle(int seg, short[] data) throws IOException {
317        seekSegment(seg);
318        int pos = 0;
319        try {
320            int shift = seg == 1 ? 8 : 0;
321            int n;
322            int end;
323            int val;
324            while (pos < data.length) {
325                n = nextByte();
326                if (n >= 0) {
327                    read(data, pos, ++n, shift);
328                    pos += n;
329                } else if (n != -128) {
330                    end = pos + 1 - n;
331                    val = (nextByte() & 0xff) << shift;
332                    while (pos < end)
333                        data[pos++] |= val;
334                }
335            }
336        } catch (EOFException e) {
337            LOG.info("RLE Segment #{} too short, set missing {} bytes to 0",
338                    seg, data.length - pos);
339        } catch (IndexOutOfBoundsException e) {
340            LOG.info("RLE Segment #{} to long, truncate surplus bytes", seg);
341        }
342    }
343
344    private void read(short[] data, int pos, int len, int shift) throws IOException {
345        int remaining = len;
346        int n;
347        while (remaining > 0) {
348            n = bufLen - bufPos;
349            if (n <= 0) {
350                fillBuffer();
351                n = bufLen - bufPos;
352            }
353            if ((remaining -= n) < 0)
354                n += remaining;
355            while (n-- > 0)
356                data[pos++] |= (buf[bufPos++] & 0xff) << shift;
357        }
358    }
359
360    private void fillBuffer() throws IOException {
361        bufOff = iis.getStreamPosition();
362        bufPos = 0;
363        bufLen = iis.read(buf);
364        if (bufLen <= 0)
365            throw new EOFException();
366    }
367
368    private byte nextByte() throws IOException {
369        if (bufPos >= bufLen)
370            fillBuffer();
371
372        return buf[bufPos++];
373    }
374
375}