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}