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) 2011
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.io;
040
041import java.io.BufferedOutputStream;
042import java.io.File;
043import java.io.FileOutputStream;
044import java.io.FilterOutputStream;
045import java.io.IOException;
046import java.io.ObjectOutputStream;
047import java.io.OutputStream;
048import java.util.zip.Deflater;
049import java.util.zip.DeflaterOutputStream;
050
051import org.dcm4che3.data.Attributes;
052import org.dcm4che3.data.BulkData;
053import org.dcm4che3.data.DatasetWithFMI;
054import org.dcm4che3.data.SpecificCharacterSet;
055import org.dcm4che3.data.Tag;
056import org.dcm4che3.data.UID;
057import org.dcm4che3.data.VR;
058import org.dcm4che3.data.Value;
059import org.dcm4che3.util.ByteUtils;
060import org.dcm4che3.util.TagUtils;
061
062/**
063 * @author Gunter Zeilinger <gunterze@gmail.com>
064 */
065public class DicomOutputStream extends FilterOutputStream {
066
067    private static final byte[] DICM = { 'D', 'I', 'C', 'M' };
068
069    private byte[] preamble = new byte[128];
070
071    private boolean explicitVR;
072    private boolean bigEndian;
073    private DicomEncodingOptions encOpts = DicomEncodingOptions.DEFAULT;
074
075    private final byte[] buf = new byte[12];
076
077    public DicomOutputStream(OutputStream out, String tsuid)
078            throws IOException {
079        super(out);
080        switchTransferSyntax(tsuid);
081    }
082
083    public DicomOutputStream(File file) throws IOException {
084        this(new BufferedOutputStream(new FileOutputStream(file)),
085                UID.ExplicitVRLittleEndian);
086    }
087
088    public final void setPreamble(byte[] preamble) {
089        if (preamble.length != 128)
090            throw new IllegalArgumentException(
091                    "preamble.length=" + preamble.length);
092        this.preamble = preamble.clone();
093    }
094
095    public final boolean isExplicitVR() {
096        return explicitVR;
097    }
098
099    public final boolean isBigEndian() {
100        return bigEndian;
101    }
102
103    public final DicomEncodingOptions getEncodingOptions() {
104        return encOpts;
105    }
106
107    public final void setEncodingOptions(DicomEncodingOptions encOpts) {
108        if (encOpts == null)
109            throw new NullPointerException();
110        this.encOpts = encOpts;
111    }
112
113    @Override
114    public void write(byte[] b, int off, int len) throws IOException {
115        out.write(b, off, len);
116    }
117
118    public void writeCommand(Attributes cmd) throws IOException {
119        if (explicitVR || bigEndian)
120            throw new IllegalStateException("explicitVR=" + explicitVR
121                    + ", bigEndian=" + bigEndian);
122        cmd.writeGroupTo(this, Tag.CommandGroupLength);
123    }
124
125    public void writeFileMetaInformation(Attributes fmi) throws IOException {
126        if (!explicitVR || bigEndian)
127            throw new IllegalStateException("explicitVR=" + explicitVR
128                    + ", bigEndian=" + bigEndian);
129        String tsuid = fmi.getString(Tag.TransferSyntaxUID, null);
130        write(preamble);
131        write(DICM);
132        fmi.writeGroupTo(this, Tag.FileMetaInformationGroupLength);
133        switchTransferSyntax(tsuid);
134    }
135
136    public void writeDataset(Attributes fmi, Attributes dataset)
137            throws IOException {
138        if (fmi != null)
139            writeFileMetaInformation(fmi);
140        if (dataset.bigEndian() != bigEndian
141                || encOpts.groupLength
142                || !encOpts.undefSequenceLength
143                || !encOpts.undefItemLength)
144            dataset = new Attributes(dataset, bigEndian);
145        if (encOpts.groupLength)
146            dataset.calcLength(encOpts, explicitVR);
147        dataset.writeTo(this);
148    }
149
150    public void writeDatasetWithFMI(DatasetWithFMI datasetWithFMI) throws IOException {
151        writeDataset(datasetWithFMI.getFileMetaInformation(), datasetWithFMI.getDataset());
152    }
153
154    private void switchTransferSyntax(String tsuid) throws IOException {
155        bigEndian = tsuid.equals(UID.ExplicitVRBigEndianRetired);
156        explicitVR = !tsuid.equals(UID.ImplicitVRLittleEndian);
157        if (tsuid.equals(UID.DeflatedExplicitVRLittleEndian)
158                        || tsuid.equals(UID.JPIPReferencedDeflate)) {
159                super.out = new DeflaterOutputStream(super.out,
160                        new Deflater(Deflater.DEFAULT_COMPRESSION, true));
161        }
162    }
163
164    public void writeHeader(int tag, VR vr, int len) throws IOException {
165        byte[] b = buf;
166        ByteUtils.tagToBytes(tag, b, 0, bigEndian);
167        int headerLen;
168        if (!TagUtils.isItem(tag) && explicitVR) {
169            ByteUtils.shortToBytesBE(vr.code(), b, 4);
170            if ((headerLen = vr.headerLength()) == 8) {
171                ByteUtils.shortToBytes(len, b, 6, bigEndian);
172            } else {
173                b[6] = b[7] = 0;
174                ByteUtils.intToBytes(len, b, 8, bigEndian);
175            }
176        } else {
177            ByteUtils.intToBytes(len, b, 4, bigEndian);
178            headerLen = 8;
179        }
180        out.write(b, 0, headerLen);
181    }
182
183
184    public void writeAttribute(int tag, VR vr, Object value,
185            SpecificCharacterSet cs)  throws IOException {
186        if (value instanceof Value)
187            writeAttribute(tag, vr, (Value) value);
188        else
189            writeAttribute(tag, vr,
190                    (value instanceof byte[])
191                            ? (byte[]) value
192                            : vr.toBytes(value, cs));
193    }
194
195    public void writeAttribute(int tag, VR vr, byte[] val) throws IOException {
196        int padlen = val.length & 1;
197        writeHeader(tag, vr, val.length + padlen);
198        out.write(val);
199        if (padlen > 0)
200            out.write(vr.paddingByte());
201    }
202
203    public void writeAttribute(int tag, VR vr, Value val) throws IOException {
204        if (val instanceof BulkData
205                && super.out instanceof ObjectOutputStream) {
206            writeHeader(tag, vr, BulkData.MAGIC_LEN);
207            ((BulkData) val).serializeTo((ObjectOutputStream) super.out);
208        } else {
209            int length = val.getEncodedLength(encOpts, explicitVR, vr);
210            writeHeader(tag, vr, length);
211            val.writeTo(this, vr);
212            if (length == -1)
213                writeHeader(Tag.SequenceDelimitationItem, null, 0);
214        }
215    }
216
217    public void writeGroupLength(int tag, int len) throws IOException {
218        byte[] b = buf;
219        ByteUtils.tagToBytes(tag, b, 0, bigEndian);
220        if (explicitVR) {
221            ByteUtils.shortToBytesBE(VR.UL.code(), b, 4);
222            ByteUtils.shortToBytes(4, b, 6, bigEndian);
223        } else {
224            ByteUtils.intToBytes(4, b, 4, bigEndian);
225        }
226        ByteUtils.intToBytes(len, b, 8, bigEndian);
227        out.write(b, 0, 12);
228    }
229
230    public void finish() throws IOException {
231        if( out instanceof DeflaterOutputStream ) {
232            ((DeflaterOutputStream) out).finish();
233        }
234    }
235
236    public void close() throws IOException {
237        try {
238            finish();
239        } catch (IOException ignored) {
240        }
241        super.close();
242    }
243}