/* * * Copyright (C) 2002-2010, OFFIS e.V. * All rights reserved. See COPYRIGHT file for details. * * This software and supporting documentation were developed by * * OFFIS e.V. * R&D Division Health * Escherweg 2 * D-26121 Oldenburg, Germany * * * Module: dcmdata * * Author: Marco Eichelberg * * Purpose: zlib compression filter for output streams * */ #include "dcmtk/config/osconfig.h" #ifdef WITH_ZLIB #include "dcmtk/dcmdata/dcostrmz.h" #include "dcmtk/dcmdata/dcerror.h" #define DCMZLIBOUTPUTFILTER_BUFSIZE 4096 /* taken from zutil.h */ #if MAX_MEM_LEVEL >= 8 #define DEF_MEM_LEVEL 8 #else #define DEF_MEM_LEVEL MAX_MEM_LEVEL #endif OFGlobal dcmZlibCompressionLevel(Z_DEFAULT_COMPRESSION); // helper method to fix old-style casts warnings BEGIN_EXTERN_C static int OFdeflateInit(z_stream* const stream, int level) { #ifdef ZLIB_ENCODE_RFC1950_HEADER /* create deflated ZLIB format instead of deflated bitstream format * (i.e. RFC 1950 instead of RFC 1951). * THE RESULTING BITSTREAM IS NOT DICOM COMPLIANT! * Use only for testing, and use with care. */ return deflateInit(stream, level); #else /* windowBits is passed < 0 to suppress zlib header */ return deflateInit2(stream, level, Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); #endif } END_EXTERN_C DcmZLibOutputFilter::DcmZLibOutputFilter() : DcmOutputFilter() , current_(NULL) , zstream_(new z_stream) , status_(EC_MemoryExhausted) , flushed_(OFFalse) , inputBuf_(new unsigned char[DCMZLIBOUTPUTFILTER_BUFSIZE]) , inputBufStart_(0) , inputBufCount_(0) , outputBuf_(new unsigned char[DCMZLIBOUTPUTFILTER_BUFSIZE]) , outputBufStart_(0) , outputBufCount_(0) { if (zstream_ && inputBuf_ && outputBuf_) { zstream_->zalloc = Z_NULL; zstream_->zfree = Z_NULL; zstream_->opaque = Z_NULL; if (Z_OK == OFdeflateInit(zstream_, dcmZlibCompressionLevel.get())) status_ = EC_Normal; else { OFString etext = "ZLib Error: "; if (zstream_->msg) etext += zstream_->msg; status_ = makeOFCondition(OFM_dcmdata, 16, OF_error, etext.c_str()); } } } DcmZLibOutputFilter::~DcmZLibOutputFilter() { if (zstream_) { deflateEnd(zstream_); // discards any unprocessed input and does not flush any pending output delete zstream_; } delete[] inputBuf_; delete[] outputBuf_; } OFBool DcmZLibOutputFilter::good() const { return status_.good(); } OFCondition DcmZLibOutputFilter::status() const { return status_; } OFBool DcmZLibOutputFilter::isFlushed() const { if (status_.bad() || (current_ == NULL)) return OFTrue; return (inputBufCount_ == 0) && (outputBufCount_ == 0) && flushed_ && current_->isFlushed(); } offile_off_t DcmZLibOutputFilter::avail() const { // compute number of bytes available in input buffer if (status_.good() ) return DCMZLIBOUTPUTFILTER_BUFSIZE - inputBufCount_; else return 0; } void DcmZLibOutputFilter::flushOutputBuffer() { if (outputBufCount_) { // flush from outputBufStart_ to end of data or end of buffer, whatever comes first offile_off_t numBytes = (outputBufStart_ + outputBufCount_ > DCMZLIBOUTPUTFILTER_BUFSIZE) ? (DCMZLIBOUTPUTFILTER_BUFSIZE - outputBufStart_) : outputBufCount_ ; offile_off_t written = current_->write(outputBuf_ + outputBufStart_, numBytes); // adjust counters outputBufCount_ -= written; outputBufStart_ += written; if (outputBufStart_ == DCMZLIBOUTPUTFILTER_BUFSIZE) { // wrapped around outputBufStart_ = 0; // now flush to end of data if (outputBufCount_ && written) { written = current_->write(outputBuf_, outputBufCount_); // adjust counters outputBufCount_ -= written; outputBufStart_ += written; } } // reset buffer start to make things faster if (outputBufCount_ == 0) outputBufStart_ = 0; } } offile_off_t DcmZLibOutputFilter::fillInputBuffer(const void *buf, offile_off_t buflen) { offile_off_t result = 0; if (buf && buflen && inputBufCount_ < DCMZLIBOUTPUTFILTER_BUFSIZE) { const unsigned char *data = OFstatic_cast(const unsigned char *, buf); // use first part of input buffer if (inputBufStart_ + inputBufCount_ < DCMZLIBOUTPUTFILTER_BUFSIZE) { result = DCMZLIBOUTPUTFILTER_BUFSIZE - (inputBufStart_ + inputBufCount_); if (result > buflen) result = buflen; memcpy(inputBuf_ + inputBufStart_ + inputBufCount_, data, OFstatic_cast(size_t, result)); inputBufCount_ += result; data += result; buflen -= result; } // use second part of input buffer if (buflen && (inputBufCount_ < DCMZLIBOUTPUTFILTER_BUFSIZE) && inputBufStart_ + inputBufCount_ >= DCMZLIBOUTPUTFILTER_BUFSIZE) { offile_off_t len = DCMZLIBOUTPUTFILTER_BUFSIZE - inputBufCount_; if (len > buflen) len = buflen; memcpy(inputBuf_ + (inputBufStart_ + inputBufCount_ - DCMZLIBOUTPUTFILTER_BUFSIZE), data, OFstatic_cast(size_t, len)); inputBufCount_ += len; result += len; } } return result; } void DcmZLibOutputFilter::compressInputBuffer(OFBool finalize) { if (inputBufCount_ || finalize) { // flush from inputBufStart_ to end of data or end of buffer, whatever comes first offile_off_t numBytes = (inputBufStart_ + inputBufCount_ > DCMZLIBOUTPUTFILTER_BUFSIZE) ? (DCMZLIBOUTPUTFILTER_BUFSIZE - inputBufStart_) : inputBufCount_ ; offile_off_t written = compress(inputBuf_ + inputBufStart_, numBytes, finalize); // adjust counters inputBufCount_ -= written; inputBufStart_ += written; if (inputBufStart_ == DCMZLIBOUTPUTFILTER_BUFSIZE) { // wrapped around inputBufStart_ = 0; // now flush to end of data if (inputBufCount_ && written) { written = compress(inputBuf_, inputBufCount_, finalize); // adjust counters inputBufCount_ -= written; inputBufStart_ += written; } } // reset buffer start to make things faster if (inputBufCount_ == 0) inputBufStart_ = 0; } } offile_off_t DcmZLibOutputFilter::compress(const void *buf, offile_off_t buflen, OFBool finalize) { offile_off_t result = 0; if (outputBufCount_ < DCMZLIBOUTPUTFILTER_BUFSIZE) { zstream_->next_in = OFstatic_cast(Bytef *, OFconst_cast(void *, buf)); zstream_->avail_in = OFstatic_cast(uInt, buflen); int zstatus; // use first part of output buffer if (outputBufStart_ + outputBufCount_ < DCMZLIBOUTPUTFILTER_BUFSIZE) { zstream_->next_out = OFstatic_cast(Bytef *, outputBuf_ + outputBufStart_ + outputBufCount_); zstream_->avail_out = OFstatic_cast(uInt, DCMZLIBOUTPUTFILTER_BUFSIZE - (outputBufStart_ + outputBufCount_)); zstatus = deflate(zstream_, (finalize ? Z_FINISH : 0)); if (zstatus == Z_OK || zstatus == Z_BUF_ERROR) { /* everything OK */ } else if (zstatus == Z_STREAM_END) flushed_ = OFTrue; else { OFString etext = "ZLib Error: "; if (zstream_->msg) etext += zstream_->msg; status_ = makeOFCondition(OFM_dcmdata, 16, OF_error, etext.c_str()); } outputBufCount_ = DCMZLIBOUTPUTFILTER_BUFSIZE - outputBufStart_ - OFstatic_cast(offile_off_t, zstream_->avail_out); } // use second part of output buffer if ((outputBufCount_ < DCMZLIBOUTPUTFILTER_BUFSIZE) && outputBufStart_ + outputBufCount_ >= DCMZLIBOUTPUTFILTER_BUFSIZE) { zstream_->next_out = OFstatic_cast(Bytef *, outputBuf_ + (outputBufStart_ + outputBufCount_ - DCMZLIBOUTPUTFILTER_BUFSIZE)); zstream_->avail_out = OFstatic_cast(uInt, DCMZLIBOUTPUTFILTER_BUFSIZE - outputBufCount_); zstatus = deflate(zstream_, (finalize ? Z_FINISH : 0)); if (zstatus == Z_OK || zstatus == Z_BUF_ERROR) { /* everything OK */ } else if (zstatus == Z_STREAM_END) flushed_ = OFTrue; else { OFString etext = "ZLib Error: "; if (zstream_->msg) etext += zstream_->msg; status_ = makeOFCondition(OFM_dcmdata, 16, OF_error, etext.c_str()); } outputBufCount_ = DCMZLIBOUTPUTFILTER_BUFSIZE - OFstatic_cast(offile_off_t, zstream_->avail_out); } result = (buflen - OFstatic_cast(offile_off_t, zstream_->avail_in)); } return result; } offile_off_t DcmZLibOutputFilter::write(const void *buf, offile_off_t buflen) { if (status_.bad() || (current_ == NULL)) return 0; // flush output buffer if necessary if (outputBufCount_ == DCMZLIBOUTPUTFILTER_BUFSIZE) flushOutputBuffer(); // compress pending input from input buffer while (status_.good() && inputBufCount_ > 0 && outputBufCount_ < DCMZLIBOUTPUTFILTER_BUFSIZE) { compressInputBuffer(OFFalse); if (outputBufCount_ == DCMZLIBOUTPUTFILTER_BUFSIZE) flushOutputBuffer(); } const unsigned char *data = OFstatic_cast(const unsigned char *, buf); offile_off_t result = 0; // compress user data only if input buffer is empty if (inputBufCount_ == 0) { while (status_.good() && (buflen > result) && outputBufCount_ < DCMZLIBOUTPUTFILTER_BUFSIZE) { result += compress(data+result, buflen-result, OFFalse); if (outputBufCount_ == DCMZLIBOUTPUTFILTER_BUFSIZE) flushOutputBuffer(); } } // finally stuff as much into the input buffer as possible result += fillInputBuffer(data+result, buflen-result); // total number of bytes consumed from input return result; } void DcmZLibOutputFilter::flush() { if (status_.good() && current_) { // flush output buffer first if (outputBufCount_ == DCMZLIBOUTPUTFILTER_BUFSIZE) flushOutputBuffer(); // compress pending input from input buffer while (status_.good() && inputBufCount_ > 0 && outputBufCount_ < DCMZLIBOUTPUTFILTER_BUFSIZE) { compressInputBuffer(OFTrue); if (outputBufCount_ == DCMZLIBOUTPUTFILTER_BUFSIZE) flushOutputBuffer(); } while (status_.good() && (! flushed_) && outputBufCount_ < DCMZLIBOUTPUTFILTER_BUFSIZE) { // create output from compression engine until end of compressed stream compress(NULL, 0, OFTrue); if (outputBufCount_ == DCMZLIBOUTPUTFILTER_BUFSIZE) flushOutputBuffer(); } // final attempt to flush output buffer if (outputBufCount_ > 0) flushOutputBuffer(); } } void DcmZLibOutputFilter::append(DcmConsumer& consumer) { current_ = &consumer; } #else /* WITH_ZLIB */ /* make sure that the object file is not completely empty if compiled * without zlib because some linkers might fail otherwise. */ void dcostrmz_dummy_function() { return; } #endif /* WITH_ZLIB */