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}