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.stream; 040 041import java.io.ByteArrayInputStream; 042import java.io.IOException; 043import java.util.Arrays; 044 045import javax.imageio.stream.ImageInputStream; 046import javax.imageio.stream.ImageInputStreamImpl; 047import javax.imageio.stream.MemoryCacheImageInputStream; 048 049import org.dcm4che3.data.BulkData; 050import org.dcm4che3.data.Fragments; 051import org.dcm4che3.data.Tag; 052import org.dcm4che3.util.ByteUtils; 053 054/** 055 * @author Gunter Zeilinger <gunterze@gmail.com> 056 * 057 */ 058public class SegmentedImageInputStream extends ImageInputStreamImpl { 059 060 private final ImageInputStream stream; 061 private final boolean autoExtend; 062 private long[] segmentPositionsList; 063 private int[] segmentLengths; 064 private int curSegment; 065 private long curSegmentEnd; 066 private byte[] header = new byte[8]; 067 068 public SegmentedImageInputStream(ImageInputStream stream, 069 long[] segmentPositionsList, int[] segmentLengths) 070 throws IOException { 071 this.stream = stream; 072 this.segmentPositionsList = segmentPositionsList.clone(); 073 this.segmentLengths = segmentLengths.clone(); 074 this.autoExtend = false; 075 seek(0); 076 } 077 078 public SegmentedImageInputStream(ImageInputStream stream, long pos, int len, boolean autoExtend) 079 throws IOException { 080 this.stream = stream; 081 this.segmentPositionsList = new long[]{ pos }; 082 this.segmentLengths = new int[]{ len }; 083 this.autoExtend = autoExtend; 084 seek(0); 085 } 086 087 public static SegmentedImageInputStream ofFrame(ImageInputStream iis, Fragments fragments, int index, int frames) 088 throws IOException { 089 if (frames > 1) { 090 if (fragments.size() != frames +1) 091 throw new UnsupportedOperationException( 092 "Number of Fragments [" + fragments.size() 093 + "] != Number of Frames [" + frames + "] + 1"); 094 Object fragment = fragments.get(index+1); 095 if (fragment instanceof BulkData) { 096 BulkData bulkData = (BulkData) fragment; 097 return new SegmentedImageInputStream(iis, bulkData.offset(), bulkData.length(), false); 098 } else if (fragment instanceof byte[]) { 099 // If the fragments contain byte arrays, we can just make a new ImageInputStream 100 // with the fragment data, instead of trying to slice it out. 101 byte[] byteFragment = (byte[]) fragment; 102 return new SegmentedImageInputStream( 103 new MemoryCacheImageInputStream( 104 new ByteArrayInputStream(byteFragment)), 0, byteFragment.length, false); 105 } 106 } 107 int n = fragments.size() - 1; 108 long[] offsets = new long[n]; 109 int[] lengths = new int[n]; 110 for (int i = 0; i < n; i++) { 111 BulkData bulkData = (BulkData) fragments.get(i+1); 112 offsets[i] = bulkData.offset(); 113 lengths[i] = bulkData.length(); 114 } 115 return new SegmentedImageInputStream(iis, offsets, lengths); 116 } 117 118 public long getLastSegmentEnd() { 119 int i = segmentPositionsList.length - 1; 120 return segmentPositionsList[i] + segmentLengths[i]; 121 } 122 123 private int offsetOf(int segment) { 124 int pos = 0; 125 for (int i = 0; i < segment; ++i) 126 pos += segmentLengths[i]; 127 return pos; 128 } 129 130 @Override 131 public void seek(long pos) throws IOException { 132 super.seek(pos); 133 for (int i = 0, off = 0; i < segmentLengths.length; i++) { 134 int end = off + segmentLengths[i]; 135 if (pos < end) { 136 stream.seek(segmentPositionsList[i] + pos - off); 137 curSegment = i; 138 curSegmentEnd = end; 139 return; 140 } 141 off = end; 142 } 143 curSegment = -1; 144 } 145 146 @Override 147 public int read() throws IOException { 148 if (!prepareRead()) 149 return -1; 150 151 bitOffset = 0; 152 int val = stream.read(); 153 if (val != -1) { 154 ++streamPos; 155 } 156 return val; 157 } 158 159 private boolean prepareRead() throws IOException { 160 if (curSegment < 0) 161 return false; 162 163 if (streamPos < curSegmentEnd) 164 return true; 165 166 if (curSegment+1 >= segmentPositionsList.length) { 167 if (!autoExtend) 168 return false; 169 170 stream.mark(); 171 stream.readFully(header); 172 stream.reset(); 173 if (ByteUtils.bytesToTagLE(header, 0) != Tag.Item) 174 return false; 175 176 addSegment(getLastSegmentEnd() + 8, ByteUtils.bytesToIntLE(header, 4)); 177 } 178 179 seek(offsetOf(curSegment+1)); 180 return true; 181 } 182 183 private void addSegment(long pos, int len) { 184 int i = segmentPositionsList.length; 185 segmentPositionsList = Arrays.copyOf(segmentPositionsList, i + 1); 186 segmentLengths = Arrays.copyOf(segmentLengths, i+1); 187 segmentPositionsList[i] = pos; 188 segmentLengths[i] = len; 189 } 190 191 @Override 192 public int read(byte[] b, int off, int len) throws IOException { 193 if (!prepareRead()) 194 return -1; 195 196 bitOffset = 0; 197 int nbytes = stream.read(b, off, 198 Math.min(len, (int) (curSegmentEnd-streamPos))); 199 if (nbytes != -1) { 200 streamPos += nbytes; 201 } 202 return nbytes; 203 } 204}