/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is part of dcm4che, an implementation of DICOM(TM) in
* Java(TM), hosted at https://github.com/gunterze/dcm4che.
*
* The Initial Developer of the Original Code is
* Agfa Healthcare.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* See @authors listed below
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.dcm4che3.io;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import org.dcm4che3.data.Attributes;
import org.dcm4che3.data.BulkData;
import org.dcm4che3.data.DatasetWithFMI;
import org.dcm4che3.data.SpecificCharacterSet;
import org.dcm4che3.data.Tag;
import org.dcm4che3.data.UID;
import org.dcm4che3.data.VR;
import org.dcm4che3.data.Value;
import org.dcm4che3.util.ByteUtils;
import org.dcm4che3.util.TagUtils;
/**
* @author Gunter Zeilinger <gunterze@gmail.com>
*/
public class DicomOutputStream extends FilterOutputStream {
private static final byte[] DICM = { 'D', 'I', 'C', 'M' };
private byte[] preamble = new byte[128];
private boolean explicitVR;
private boolean bigEndian;
private DicomEncodingOptions encOpts = DicomEncodingOptions.DEFAULT;
private final byte[] buf = new byte[12];
public DicomOutputStream(OutputStream out, String tsuid)
throws IOException {
super(out);
switchTransferSyntax(tsuid);
}
public DicomOutputStream(File file) throws IOException {
this(new BufferedOutputStream(new FileOutputStream(file)),
UID.ExplicitVRLittleEndian);
}
public final void setPreamble(byte[] preamble) {
if (preamble.length != 128)
throw new IllegalArgumentException(
"preamble.length=" + preamble.length);
this.preamble = preamble.clone();
}
public final boolean isExplicitVR() {
return explicitVR;
}
public final boolean isBigEndian() {
return bigEndian;
}
public final DicomEncodingOptions getEncodingOptions() {
return encOpts;
}
public final void setEncodingOptions(DicomEncodingOptions encOpts) {
if (encOpts == null)
throw new NullPointerException();
this.encOpts = encOpts;
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
out.write(b, off, len);
}
public void writeCommand(Attributes cmd) throws IOException {
if (explicitVR || bigEndian)
throw new IllegalStateException("explicitVR=" + explicitVR
+ ", bigEndian=" + bigEndian);
cmd.writeGroupTo(this, Tag.CommandGroupLength);
}
public void writeFileMetaInformation(Attributes fmi) throws IOException {
if (!explicitVR || bigEndian)
throw new IllegalStateException("explicitVR=" + explicitVR
+ ", bigEndian=" + bigEndian);
String tsuid = fmi.getString(Tag.TransferSyntaxUID, null);
write(preamble);
write(DICM);
fmi.writeGroupTo(this, Tag.FileMetaInformationGroupLength);
switchTransferSyntax(tsuid);
}
public void writeDataset(Attributes fmi, Attributes dataset)
throws IOException {
if (fmi != null)
writeFileMetaInformation(fmi);
if (dataset.bigEndian() != bigEndian
|| encOpts.groupLength
|| !encOpts.undefSequenceLength
|| !encOpts.undefItemLength)
dataset = new Attributes(dataset, bigEndian);
if (encOpts.groupLength)
dataset.calcLength(encOpts, explicitVR);
dataset.writeTo(this);
}
public void writeDatasetWithFMI(DatasetWithFMI datasetWithFMI) throws IOException {
writeDataset(datasetWithFMI.getFileMetaInformation(), datasetWithFMI.getDataset());
}
private void switchTransferSyntax(String tsuid) throws IOException {
bigEndian = tsuid.equals(UID.ExplicitVRBigEndianRetired);
explicitVR = !tsuid.equals(UID.ImplicitVRLittleEndian);
if (tsuid.equals(UID.DeflatedExplicitVRLittleEndian)
|| tsuid.equals(UID.JPIPReferencedDeflate)) {
super.out = new DeflaterOutputStream(super.out,
new Deflater(Deflater.DEFAULT_COMPRESSION, true));
}
}
public void writeHeader(int tag, VR vr, int len) throws IOException {
byte[] b = buf;
ByteUtils.tagToBytes(tag, b, 0, bigEndian);
int headerLen;
if (!TagUtils.isItem(tag) && explicitVR) {
ByteUtils.shortToBytesBE(vr.code(), b, 4);
if ((headerLen = vr.headerLength()) == 8) {
ByteUtils.shortToBytes(len, b, 6, bigEndian);
} else {
b[6] = b[7] = 0;
ByteUtils.intToBytes(len, b, 8, bigEndian);
}
} else {
ByteUtils.intToBytes(len, b, 4, bigEndian);
headerLen = 8;
}
out.write(b, 0, headerLen);
}
public void writeAttribute(int tag, VR vr, Object value,
SpecificCharacterSet cs) throws IOException {
if (value instanceof Value)
writeAttribute(tag, vr, (Value) value);
else
writeAttribute(tag, vr,
(value instanceof byte[])
? (byte[]) value
: vr.toBytes(value, cs));
}
public void writeAttribute(int tag, VR vr, byte[] val) throws IOException {
int padlen = val.length & 1;
writeHeader(tag, vr, val.length + padlen);
out.write(val);
if (padlen > 0)
out.write(vr.paddingByte());
}
public void writeAttribute(int tag, VR vr, Value val) throws IOException {
if (val instanceof BulkData
&& super.out instanceof ObjectOutputStream) {
writeHeader(tag, vr, BulkData.MAGIC_LEN);
((BulkData) val).serializeTo((ObjectOutputStream) super.out);
} else {
int length = val.getEncodedLength(encOpts, explicitVR, vr);
writeHeader(tag, vr, length);
val.writeTo(this, vr);
if (length == -1)
writeHeader(Tag.SequenceDelimitationItem, null, 0);
}
}
public void writeGroupLength(int tag, int len) throws IOException {
byte[] b = buf;
ByteUtils.tagToBytes(tag, b, 0, bigEndian);
if (explicitVR) {
ByteUtils.shortToBytesBE(VR.UL.code(), b, 4);
ByteUtils.shortToBytes(4, b, 6, bigEndian);
} else {
ByteUtils.intToBytes(4, b, 4, bigEndian);
}
ByteUtils.intToBytes(len, b, 8, bigEndian);
out.write(b, 0, 12);
}
public void finish() throws IOException {
if( out instanceof DeflaterOutputStream ) {
((DeflaterOutputStream) out).finish();
}
}
public void close() throws IOException {
try {
finish();
} catch (IOException ignored) {
}
super.close();
}
}