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}