/* * * Copyright (C) 1994-2021, 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: Gerd Ehlers, Andreas Barth * * Purpose: Implementation of class DcmPixelItem * */ #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ #include "dcmtk/ofstd/ofstream.h" #include "dcmtk/dcmdata/dcpxitem.h" #include "dcmtk/dcmdata/dcswap.h" #include "dcmtk/ofstd/ofstring.h" #include "dcmtk/ofstd/ofstd.h" #include "dcmtk/dcmdata/dcistrma.h" /* for class DcmInputStream */ #include "dcmtk/dcmdata/dcostrma.h" /* for class DcmOutputStream */ #include "dcmtk/dcmdata/dcwcache.h" /* for class DcmWriteCache */ // ******************************** DcmPixelItem::DcmPixelItem(const DcmTag &tag, const Uint32 len) : DcmOtherByteOtherWord(tag, len) { setTagVR(EVR_pixelItem); } DcmPixelItem::DcmPixelItem(const DcmPixelItem &old) : DcmOtherByteOtherWord(old) { } DcmPixelItem &DcmPixelItem::operator=(const DcmPixelItem &obj) { DcmOtherByteOtherWord::operator=(obj); return *this; } OFCondition DcmPixelItem::copyFrom(const DcmObject &rhs) { if (this != &rhs) { if (rhs.ident() != ident()) return EC_IllegalCall; *this = OFstatic_cast(const DcmPixelItem &, rhs); } return EC_Normal; } DcmPixelItem::~DcmPixelItem() { } // ******************************** OFBool DcmPixelItem::isNested() const { OFBool nested = OFFalse; if (getParent() != NULL) { // check for surrounding structure of a pixel sequence if ((getParent()->ident() == EVR_pixelSQ)) nested = OFTrue; } return nested; } DcmItem *DcmPixelItem::getParentItem() { DcmItem *parentItem = NULL; if (getParent() != NULL) { // make sure that the direct parent has the correct type if (getParent()->ident() == EVR_pixelSQ) { DcmObject *parent = getParent()->getParent(); if (parent != NULL) { // make sure that it is really a class derived from DcmItem switch (parent->ident()) { case EVR_metainfo: case EVR_dataset: case EVR_item: case EVR_dirRecord: parentItem = OFreinterpret_cast(DcmItem *, parent); break; default: DCMDATA_DEBUG("DcmPixelItem::getParentItem() Parent object has wrong class identifier: " << OFstatic_cast(int, parent->ident()) << " (" << DcmVR(parent->ident()).getVRName() << ")"); break; } } } else { DCMDATA_DEBUG("DcmPixelItem::getParentItem() Direct parent object is not a pixel sequence"); } } return parentItem; } // ******************************** Uint32 DcmPixelItem::calcElementLength(const E_TransferSyntax xfer, const E_EncodingType enctype) { /* silence unused arguments warnings */ OFstatic_cast(void, xfer); OFstatic_cast(void, enctype); /* get length of the pixel data */ Uint32 valueLength = getLengthField(); /* make sure the value did not overflow, clamp it otherwise. */ if (OFStandard::check32BitAddOverflow(valueLength, 8)) return OFnumeric_limits::max(); return valueLength + 8; } // ******************************** OFCondition DcmPixelItem::writeTagAndLength(DcmOutputStream &outStream, const E_TransferSyntax oxfer, Uint32 &writtenBytes) const { OFCondition l_error = outStream.status(); if (l_error.good()) { /* write tag information */ l_error = writeTag(outStream, getTag(), oxfer); writtenBytes = 4; /* prepare to write the value field */ Uint32 valueLength = getLengthField(); DcmXfer outXfer(oxfer); /* check byte-ordering */ const E_ByteOrder oByteOrder = outXfer.getByteOrder(); if (oByteOrder == EBO_unknown) { return EC_IllegalCall; } swapIfNecessary(oByteOrder, gLocalByteOrder, &valueLength, 4, 4); // availability of four bytes space in output buffer // has been checked by caller. writtenBytes += OFstatic_cast(Uint32, outStream.write(&valueLength, 4)); } else writtenBytes = 0; return l_error; } void DcmPixelItem::print(STD_NAMESPACE ostream &out, const size_t flags, const int level, const char *pixelFileName, size_t *pixelCounter) { /* call inherited method */ printPixel(out, flags, level, pixelFileName, pixelCounter); } OFCondition DcmPixelItem::createOffsetTable(const DcmOffsetList &offsetList) { OFCondition result = EC_Normal; size_t numEntries = offsetList.size(); if (numEntries > 0) { Uint32 current = 0; Uint32 *array = new Uint32[numEntries]; if (array) { DCMDATA_DEBUG("DcmPixelItem: creating offset table with " << numEntries << " entries"); OFListConstIterator(Uint32) first = offsetList.begin(); OFListConstIterator(Uint32) last = offsetList.end(); unsigned long idx = 0; OFBool overflow = OFFalse; while ((first != last) && result.good()) { // check for 32-bit unsigned integer overflow (during previous iteration) and report on this if (overflow) { DCMDATA_WARN("DcmPixelItem: offset value exceeds maximum (32-bit unsigned integer) for frame #" << (idx + 1) << ", cannot create offset table"); result = EC_InvalidBasicOffsetTable; } // check for odd offset values, should never happen at this point (if list was filled by an encoder) else if (current & 1) { DCMDATA_WARN("DcmPixelItem: odd offset value (" << current << ") for frame #" << (idx + 1) << ", cannot create offset table"); result = EC_InvalidBasicOffsetTable; } else { // value "current" is proven to be valid array[idx++] = current; // check for 32-bit unsigned integer overflow (but report only during next iteration) overflow = !OFStandard::safeAdd(current, *first, current); ++first; } } if (result.good()) { result = swapIfNecessary(EBO_LittleEndian, gLocalByteOrder, array, OFstatic_cast(Uint32, numEntries * sizeof(Uint32)), sizeof(Uint32)); if (result.good()) result = putUint8Array(OFreinterpret_cast(Uint8 *, array), OFstatic_cast(unsigned long, numEntries * sizeof(Uint32))); } delete[] array; } else result = EC_MemoryExhausted; } return result; } OFCondition DcmPixelItem::writeXML(STD_NAMESPACE ostream &out, const size_t flags) { if (flags & DCMTypes::XF_useNativeModel) { /* in Native DICOM Model, there is no concept of a "pixel item" */ return makeOFCondition(OFM_dcmdata, EC_CODE_CannotConvertToXML, OF_error, "Cannot convert Pixel Item to Native DICOM Model"); } else { /* XML start tag for "item" */ out << ""; /* write element value (if loaded) */ if (valueLoaded() && (flags & DCMTypes::XF_writeBinaryData)) { /* encode binary data as Base64 */ if (flags & DCMTypes::XF_encodeBase64) { /* pixel items always contain 8 bit data, therefore, byte swapping not required */ OFStandard::encodeBase64(out, OFstatic_cast(Uint8 *, getValue()), OFstatic_cast(size_t, getLengthField())); } else { /* get and check 8 bit data */ Uint8 *byteValues = NULL; if (getUint8Array(byteValues).good() && (byteValues != NULL)) { const unsigned long count = getLengthField(); out << STD_NAMESPACE hex << STD_NAMESPACE setfill('0'); /* print byte values in hex mode */ out << STD_NAMESPACE setw(2) << OFstatic_cast(int, *(byteValues++)); for (unsigned long i = 1; i < count; i++) out << "\\" << STD_NAMESPACE setw(2) << OFstatic_cast(int, *(byteValues++)); /* reset i/o manipulators */ out << STD_NAMESPACE dec << STD_NAMESPACE setfill(' '); } } } /* XML end tag for "item" */ out << "" << OFendl; /* always report success */ return EC_Normal; } } OFCondition DcmPixelItem::writeSignatureFormat( DcmOutputStream &outStream, const E_TransferSyntax oxfer, const E_EncodingType enctype, DcmWriteCache *wcache) { if (dcmEnableOldSignatureFormat.get()) { /* Old signature format as created by DCMTK releases previous to 3.5.4. * This is non-conformant because it includes the item length in pixel items. */ return DcmOtherByteOtherWord::writeSignatureFormat(outStream, oxfer, enctype, wcache); } else { DcmWriteCache wcache2; /* In case the transfer state is not initialized, this is an illegal call */ if (getTransferState() == ERW_notInitialized) errorFlag = EC_IllegalCall; else { /* if this is not an illegal call, we need to do something. First */ /* of all, check the error state of the stream that was passed */ /* only do something if the error state of the stream is ok */ errorFlag = outStream.status(); if (errorFlag.good()) { /* create an object that represents the transfer syntax */ DcmXfer outXfer(oxfer); /* pointer to element value if value resides in memory or old-style * write behaviour is active (i.e. everything loaded into memory upon write */ Uint8 *value = NULL; OFBool accessPossible = OFFalse; /* check that we actually do have access to the element's value. * If the element is unaccessible (which would mean that the value resides * in file and access to the file fails), write an empty element with * zero length. */ if (getLengthField() > 0) { if (valueLoaded()) { /* get this element's value. Mind the byte ordering (little */ /* or big endian) of the transfer syntax which shall be used */ value = OFstatic_cast(Uint8 *, getValue(outXfer.getByteOrder())); if (value) accessPossible = OFTrue; } else { /* Use local cache object if needed. This may cause those bytes * that are read but not written because the output stream stalls to * be read again, and the file from being re-opened next time. * Therefore, this case should be avoided. */ if (!wcache) wcache = &wcache2; /* initialize cache object. This is safe even if the object was already initialized */ wcache->init(this, getLengthField(), getTransferredBytes(), outXfer.getByteOrder()); /* access first block of element content */ errorFlag = wcache->fillBuffer(*this); /* check that everything worked and the buffer is non-empty now */ accessPossible = errorFlag.good() && (! wcache->bufferIsEmpty()); } } /* if this element's transfer state is ERW_init (i.e. it has not yet been written to */ /* the stream) and if the outstream provides enough space for tag and length information */ /* write tag and length information to it, do something */ if (getTransferState() == ERW_init) { /* first compare with DCM_TagInfoLength (12). If there is not enough space * in the buffer, check if the buffer is still sufficient for the requirements * of this element, which may need only 8 instead of 12 bytes. */ if (outStream.avail() >= 4) { /* if there is no value, Length (member variable) shall be set to 0 */ if (! accessPossible) setLengthField(0); /* write tag and length information (and possibly also data type information) to the stream, */ /* mind the transfer syntax and remember the amount of bytes that have been written */ errorFlag = writeTag(outStream, getTag(), oxfer); /* if the writing was successful, set this element's transfer */ /* state to ERW_inWork and the amount of transferred bytes to 0 */ if (errorFlag.good()) { setTransferState(ERW_inWork); setTransferredBytes(0); } } else errorFlag = EC_StreamNotifyClient; } /* if there is a value that has to be written to the stream */ /* and if this element's transfer state is ERW_inWork */ if (accessPossible && getTransferState() == ERW_inWork) { Uint32 len = 0; if (valueLoaded()) { /* write as many bytes as possible to the stream starting at value[getTransferredBytes()] */ /* (note that the bytes value[0] to value[getTransferredBytes()-1] have already been */ /* written to the stream) */ len = OFstatic_cast(Uint32, outStream.write(&value[getTransferredBytes()], getLengthField() - getTransferredBytes())); /* increase the amount of bytes which have been transfered correspondingly */ incTransferredBytes(len); /* see if there is something fishy with the stream */ errorFlag = outStream.status(); } else { Uint32 buflen = 0; OFBool done = getTransferredBytes() == getLengthField(); while (! done) { // re-fill buffer from file if empty errorFlag = wcache->fillBuffer(*this); buflen = wcache->contentLength(); if (errorFlag.good()) { // write as many bytes from cache buffer to stream as possible len = wcache->writeBuffer(outStream); /* increase the amount of bytes which have been transfered correspondingly */ incTransferredBytes(len); /* see if there is something fishy with the stream */ errorFlag = outStream.status(); } // stop writing if something went wrong, we were unable to send all of the buffer content // (which indicates that the output stream needs to be flushed, or everything was sent out. done = errorFlag.bad() || (len < buflen) || (getTransferredBytes() == getLengthField()); } } /* if the amount of transferred bytes equals the length of the element's value, the */ /* entire value has been written to the stream. Thus, this element's transfer state */ /* has to be set to ERW_ready. If this is not the case but the error flag still shows */ /* an ok value, there was no more space in the stream and a corresponding return value */ /* has to be set. (Isn't the "else if" part superfluous?!?) */ if (getLengthField() == getTransferredBytes()) setTransferState(ERW_ready); else if (errorFlag.good()) errorFlag = EC_StreamNotifyClient; } } } } /* return result value */ return errorFlag; }