1 /*
2  *
3  *  Copyright (C) 2001-2020, OFFIS e.V.
4  *  All rights reserved.  See COPYRIGHT file for details.
5  *
6  *  This software and supporting documentation were developed by
7  *
8  *    OFFIS e.V.
9  *    R&D Division Health
10  *    Escherweg 2
11  *    D-26121 Oldenburg, Germany
12  *
13  *
14  *  Module:  dcmjpeg
15  *
16  *  Author:  Marco Eichelberg, Norbert Olges
17  *
18  *  Purpose: abstract codec class for JPEG encoders.
19  *
20  */
21 
22 #include "dcmtk/config/osconfig.h"
23 #include "dcmtk/dcmjpeg/djcodece.h"
24 
25 // ofstd includes
26 #include "dcmtk/ofstd/oflist.h"
27 #include "dcmtk/ofstd/ofstd.h"
28 
29 // dcmdata includes
30 #include "dcmtk/dcmdata/dcdatset.h"   /* for class DcmDataset */
31 #include "dcmtk/dcmdata/dcdeftag.h"   /* for tag constants */
32 #include "dcmtk/dcmdata/dcovlay.h"    /* for class DcmOverlayData */
33 #include "dcmtk/dcmdata/dcpixseq.h"   /* for class DcmPixelSequence */
34 #include "dcmtk/dcmdata/dcpxitem.h"   /* for class DcmPixelItem */
35 #include "dcmtk/dcmdata/dcuid.h"      /* for dcmGenerateUniqueIdentifer()*/
36 #include "dcmtk/dcmdata/dcvrcs.h"     /* for class DcmCodeString */
37 #include "dcmtk/dcmdata/dcvrds.h"     /* for class DcmDecimalString */
38 #include "dcmtk/dcmdata/dcvrlt.h"     /* for class DcmLongText */
39 #include "dcmtk/dcmdata/dcvrst.h"     /* for class DcmShortText */
40 #include "dcmtk/dcmdata/dcvrus.h"     /* for class DcmUnsignedShort */
41 #include "dcmtk/dcmdata/dcswap.h"     /* for swapIfNecessary */
42 
43 // dcmjpeg includes
44 #include "dcmtk/dcmjpeg/djcparam.h"   /* for class DJCodecParameter */
45 #include "dcmtk/dcmjpeg/djencabs.h"   /* for class DJEncoder */
46 
47 // dcmimgle includes
48 #include "dcmtk/dcmimgle/dcmimage.h"  /* for class DicomImage */
49 
50 #define INCLUDE_CMATH
51 #include "dcmtk/ofstd/ofstdinc.h"
52 
53 
DJCodecEncoder()54 DJCodecEncoder::DJCodecEncoder()
55 : DcmCodec()
56 {
57 }
58 
59 
~DJCodecEncoder()60 DJCodecEncoder::~DJCodecEncoder()
61 {
62 }
63 
64 
canChangeCoding(const E_TransferSyntax oldRepType,const E_TransferSyntax newRepType) const65 OFBool DJCodecEncoder::canChangeCoding(
66   const E_TransferSyntax oldRepType,
67   const E_TransferSyntax newRepType) const
68 {
69   E_TransferSyntax myXfer = supportedTransferSyntax();
70   DcmXfer newRep(newRepType);
71   DcmXfer oldRep(oldRepType);
72   if (oldRep.isNotEncapsulated() && (newRepType == myXfer)) return OFTrue; // compress requested
73 
74   // we don't support re-coding for now
75   return OFFalse;
76 }
77 
78 
decode(const DcmRepresentationParameter *,DcmPixelSequence *,DcmPolymorphOBOW &,const DcmCodecParameter *,const DcmStack &,OFBool &) const79 OFCondition DJCodecEncoder::decode(
80   const DcmRepresentationParameter * /* fromRepParam */,
81   DcmPixelSequence * /* pixSeq */,
82   DcmPolymorphOBOW& /* uncompressedPixelData */,
83   const DcmCodecParameter * /* cp */,
84   const DcmStack& /* objStack */,
85   OFBool& /* removeOldRep */ ) const
86 {
87   // we are an encoder only
88   return EC_IllegalCall;
89 }
90 
91 
decodeFrame(const DcmRepresentationParameter *,DcmPixelSequence *,const DcmCodecParameter *,DcmItem *,Uint32,Uint32 &,void *,Uint32,OFString &) const92 OFCondition DJCodecEncoder::decodeFrame(
93   const DcmRepresentationParameter * /* fromParam */ ,
94   DcmPixelSequence * /* fromPixSeq */ ,
95   const DcmCodecParameter * /* cp */ ,
96   DcmItem * /* dataset */ ,
97   Uint32 /* frameNo */ ,
98   Uint32& /* startFragment */ ,
99   void * /* buffer */ ,
100   Uint32 /* bufSize */ ,
101   OFString& /* decompressedColorModel */ ) const
102 {
103   // we are an encoder only
104   return EC_IllegalCall;
105 }
106 
107 
encode(const E_TransferSyntax,const DcmRepresentationParameter *,DcmPixelSequence *,const DcmRepresentationParameter *,DcmPixelSequence * &,const DcmCodecParameter *,DcmStack &,OFBool &) const108 OFCondition DJCodecEncoder::encode(
109   const E_TransferSyntax /* fromRepType */,
110   const DcmRepresentationParameter * /* fromRepParam */,
111   DcmPixelSequence * /* fromPixSeq */,
112   const DcmRepresentationParameter * /* toRepParam */,
113   DcmPixelSequence * & /* toPixSeq */,
114   const DcmCodecParameter * /* cp */,
115   DcmStack & /* objStack */,
116   OFBool& /* removeOldRep */) const
117 {
118   // we don't support re-coding for now
119   return EC_IllegalCall;
120 }
121 
122 
encode(const Uint16 *,const Uint32,const DcmRepresentationParameter * toRepParam,DcmPixelSequence * & pixSeq,const DcmCodecParameter * cp,DcmStack & objStack,OFBool & removeOldRep) const123 OFCondition DJCodecEncoder::encode(
124   const Uint16 * /* pixelData */,
125   const Uint32 /* length */,
126   const DcmRepresentationParameter * toRepParam,
127   DcmPixelSequence * & pixSeq,
128   const DcmCodecParameter *cp,
129   DcmStack & objStack,
130   OFBool& removeOldRep) const
131 {
132   OFCondition result = EC_Normal;
133 
134   // this codec may modify the DICOM header such that the previous pixel
135   // representation is not valid anymore. Indicate this to the caller
136   // to trigger removal.
137   removeOldRep = OFTrue;
138 
139   // assume we can cast the codec parameter to what we need
140   const DJCodecParameter *djcp = OFreinterpret_cast(const DJCodecParameter*, cp);
141 
142   // if true lossless mode is enabled, and we're supposed to do lossless compression,
143   // call the "true lossless encoding"-engine
144   if (isLosslessProcess() && (djcp->getTrueLosslessMode()))
145     return encodeTrueLossless(toRepParam, pixSeq, cp, objStack);
146 
147   DcmStack localStack(objStack);
148   (void)localStack.pop();                // pop pixel data element from stack
149   DcmObject *dataset = localStack.pop(); // this is the item in which the pixel data is located
150 
151   if ((!dataset)||((dataset->ident()!= EVR_dataset) && (dataset->ident()!= EVR_item))) result = EC_InvalidTag;
152   else
153   {
154     double compressionRatio = 0.0;
155     EP_Interpretation photometricInterpretation = DcmJpegHelper::getPhotometricInterpretation(OFreinterpret_cast(DcmItem*, dataset));
156     switch (photometricInterpretation)
157     {
158       case EPI_Monochrome1:
159       case EPI_Monochrome2:
160         // monochrome image
161         result = encodeMonochromeImage(OFreinterpret_cast(DcmItem*, dataset), toRepParam, pixSeq, djcp, compressionRatio);
162         break;
163       case EPI_PaletteColor:
164       case EPI_RGB:
165       case EPI_HSV:
166       case EPI_ARGB:
167       case EPI_CMYK:
168       case EPI_YBR_Partial_422:
169         // color image except YCbCr which receives special treatment
170         result = encodeColorImage(OFFalse, OFreinterpret_cast(DcmItem*, dataset), toRepParam, pixSeq, djcp, compressionRatio);
171         break;
172       case EPI_YBR_Full:
173       case EPI_YBR_Full_422:
174         // YCbCr color image
175         result = encodeColorImage(OFTrue, OFreinterpret_cast(DcmItem*, dataset), toRepParam, pixSeq, djcp, compressionRatio);
176         break;
177       case EPI_Unknown:
178         // unknown color model - bail out
179         result = EJ_UnsupportedPhotometricInterpretation;
180         break;
181       case EPI_Missing:
182         // photometric interpretation missing. If ACR-NEMA compatibility is activated, we treat this as MONOCHOME2, otherwise we report an error
183         if (djcp->getAcrNemaCompatibility())
184             result = encodeMonochromeImage(OFreinterpret_cast(DcmItem*, dataset), toRepParam, pixSeq, djcp, compressionRatio);
185             else result = EJ_UnsupportedPhotometricInterpretation;
186         break;
187     }
188 
189     // the following operations do not affect the Image Pixel Module
190     // but other modules such as SOP Common.  We only perform these
191     // changes if we're on the main level of the dataset,
192     // which should always identify itself as dataset, not as item.
193     if (dataset->ident() == EVR_dataset)
194     {
195 
196       // update image type
197       if (result.good()) result = DcmCodec::updateImageType(OFreinterpret_cast(DcmItem*, dataset));
198 
199       // determine compressed bit depth passed to JPEG codec
200       Uint16 compressedBits = OFstatic_cast(Uint16, djcp->getForcedBitDepth());
201       if (result.good())
202       {
203         if (compressedBits == 0)
204         {
205           result = OFreinterpret_cast(DcmItem*, dataset)->findAndGetUint16(DCM_BitsStored, compressedBits);
206         }
207       }
208 
209       // update derivation description
210       if (result.good()) result = updateDerivationDescription(OFreinterpret_cast(DcmItem*, dataset), toRepParam,
211         djcp, OFstatic_cast(Uint8, compressedBits), compressionRatio);
212 
213       if (result.good())
214       {
215         // lossy process - create new UID unless mode is EUC_never and we're not converting to Secondary Capture
216         // (pseudo-lossless mode may also result in lossy compression, so treat it the same way)
217         if (djcp->getConvertToSC() || (djcp->getUIDCreation() != EUC_never))
218           result = DcmCodec::newInstance(OFreinterpret_cast(DcmItem*, dataset), "DCM", "121320", "Uncompressed predecessor");
219 
220         // update lossy compression ratio
221         if (result.good()) result = updateLossyCompressionRatio(OFreinterpret_cast(DcmItem*, dataset), compressionRatio);
222       }
223 
224       // convert to Secondary Capture if requested by user.
225       // This method creates a new SOP class UID, so it should be executed
226       // after the call to newInstance() which creates a Source Image Sequence.
227       if (result.good() && djcp->getConvertToSC()) result = DcmCodec::convertToSecondaryCapture(OFreinterpret_cast(DcmItem*, dataset));
228     }
229   }
230   return result;
231 }
232 
233 
determineDecompressedColorModel(const DcmRepresentationParameter *,DcmPixelSequence *,const DcmCodecParameter *,DcmItem *,OFString &) const234 OFCondition DJCodecEncoder::determineDecompressedColorModel(
235     const DcmRepresentationParameter * /* fromParam */,
236     DcmPixelSequence * /* fromPixSeq */,
237     const DcmCodecParameter * /* cp */,
238     DcmItem * /* dataset */,
239     OFString & /* decompressedColorModel */) const
240 {
241     return EC_IllegalCall;
242 }
243 
244 
encodeColorImage(OFBool YBRmode,DcmItem * dataset,const DcmRepresentationParameter * toRepParam,DcmPixelSequence * & pixSeq,const DJCodecParameter * cp,double & compressionRatio) const245 OFCondition DJCodecEncoder::encodeColorImage(
246   OFBool YBRmode,
247   DcmItem *dataset,
248   const DcmRepresentationParameter * toRepParam,
249   DcmPixelSequence * & pixSeq,
250   const DJCodecParameter *cp,
251   double& compressionRatio) const
252 {
253   OFCondition result = EC_Normal;
254   DcmOffsetList offsetList;
255   DcmPixelSequence *pixelSequence = NULL;
256   DcmPixelItem *offsetTable = NULL;
257   unsigned short bitsPerSample = 0;
258   compressionRatio = 0.0; // initialize if something goes wrong
259   size_t compressedSize = 0;
260   double uncompressedSize = 0.0;
261   Uint16 compressedBits = OFstatic_cast(Uint16, cp->getForcedBitDepth());
262 
263   // Check if image is continuous-tone, bail out otherwise.
264   // We check the value of BitsStored, which is not affected by any transformation such as MLUT.
265   Uint16 bitsStored = 0;
266   result = dataset->findAndGetUint16(DCM_BitsStored, bitsStored);
267   if (result.bad()) return result;
268 
269   if ((bitsStored > 16) && isLosslessProcess())
270   {
271     DCMJPEG_WARN("Cannot lossless compress image with " << bitsStored << " bits/sample: JPEG supports max. 16 bits.");
272     return EJ_UnsupportedBitDepth;
273   }
274 
275   if (bitsStored < 2)
276   {
277     DCMJPEG_WARN("Cannot compress image with " << bitsStored << " bit/sample: JPEG requires at least 2 bits.");
278     return EJ_UnsupportedBitDepth;
279   }
280 
281   // initialize settings with defaults for RGB mode
282   OFBool monochromeMode = OFFalse;
283   unsigned long flags = 0; // flags for initialization of DicomImage
284   EP_Interpretation interpr = EPI_RGB;
285   Uint16 samplesPerPixel = 3;
286   const char *photometricInterpretation = "RGB";
287   if ((cp->getCompressionColorSpaceConversion() == ECC_lossyYCbCr) && (!isLosslessProcess()))
288   {
289     if (cp->getWriteYBR422()) photometricInterpretation = "YBR_FULL_422";
290     else photometricInterpretation = "YBR_FULL";
291   }
292 
293   // check mode and adjust settings
294   if (cp->getCompressionColorSpaceConversion() == ECC_monochrome)
295   {
296     monochromeMode = OFTrue;
297     flags = 0;
298     interpr = EPI_Monochrome2;
299     samplesPerPixel = 1;
300     photometricInterpretation = "MONOCHROME2";
301   }
302   else if (YBRmode)
303   {
304     monochromeMode = OFFalse;
305     flags = CIF_KeepYCbCrColorModel; // keep YCbCr color model
306     interpr = EPI_YBR_Full;
307     if (cp->getWriteYBR422()) photometricInterpretation = "YBR_FULL_422";
308     else photometricInterpretation = "YBR_FULL";
309   }
310 
311   // integrate DicomImage flags transported by DJCodecParameter into "flags"-variable
312   if (cp->getAcceptWrongPaletteTags())
313     flags |= CIF_WrongPaletteAttributeTags;
314   if (cp->getAcrNemaCompatibility())
315     flags |= CIF_AcrNemaCompatibility;
316 
317   // create dcmimage object. Will fail if dcmimage has not been activated in main().
318   // transfer syntax can be any uncompressed one.
319   DicomImage *dimage = NULL;
320   if (monochromeMode)
321   {
322     DicomImage colorimage(dataset, EXS_LittleEndianImplicit, flags); // read all frames
323     if (colorimage.getStatus() == EIS_Normal) dimage = colorimage.createMonochromeImage();
324     if (dimage) dimage->setNoVoiTransformation();
325   }
326   else
327   {
328     dimage = new DicomImage(dataset, EXS_LittleEndianImplicit, flags); // read all frames
329   }
330 
331   if (dimage == NULL) result = EC_MemoryExhausted;
332   else if (dimage->getStatus() != EIS_Normal)
333   {
334     DCMJPEG_WARN("Color encoder: " << DicomImage::getString(dimage->getStatus()));
335     result = EC_IllegalCall;
336   }
337 
338   // don't render overlays
339   if (result.good())
340   {
341     dimage->hideAllOverlays();
342   }
343 
344   // create initial pixel sequence
345   if (result.good())
346   {
347     pixelSequence = new DcmPixelSequence(DCM_PixelSequenceTag);
348     if (pixelSequence == NULL) result = EC_MemoryExhausted;
349     else
350     {
351       // create empty offset table
352       offsetTable = new DcmPixelItem(DCM_PixelItemTag);
353       if (offsetTable == NULL) result = EC_MemoryExhausted;
354       else pixelSequence->insert(offsetTable);
355     }
356   }
357 
358   // select bit depth
359   if (result.good())
360   {
361     if (compressedBits == 0)
362     {
363       result = OFreinterpret_cast(DcmItem*, dataset)->findAndGetUint16(DCM_BitsStored, compressedBits);
364     }
365   }
366 
367   // create codec instance
368   if (result.good())
369   {
370     DJEncoder *jpeg = createEncoderInstance(toRepParam, cp, OFstatic_cast(Uint8, compressedBits));
371     if (jpeg)
372     {
373       // render and compress each frame
374       bitsPerSample = jpeg->bitsPerSample();
375       size_t frameCount = dimage->getFrameCount();
376       unsigned short bytesPerSample = jpeg->bytesPerSample();
377       unsigned short columns = OFstatic_cast(unsigned short, dimage->getWidth());
378       unsigned short rows = OFstatic_cast(unsigned short, dimage->getHeight());
379       Uint8 *jpegData = NULL;
380       Uint32 jpegLen  = 0;
381       const void *frame = NULL;
382 
383       // compute original image size in bytes, ignoring any padding bits.
384       uncompressedSize = OFstatic_cast(double, columns * rows * dimage->getDepth() * frameCount * samplesPerPixel) / 8.0;
385       for (unsigned long i=0; (i<frameCount) && (result.good()); i++)
386       {
387         frame = dimage->getOutputData(bitsPerSample, i, 0);
388         if (frame == NULL) result = EC_MemoryExhausted;
389         else
390         {
391           // compress frame
392           jpegData = NULL;
393           if (bytesPerSample == 1)
394           {
395             result = jpeg->encode(columns, rows, interpr, samplesPerPixel, OFreinterpret_cast(Uint8*, OFconst_cast(void*, frame)), jpegData, jpegLen);
396           } else {
397             result = jpeg->encode(columns, rows, interpr, samplesPerPixel, OFreinterpret_cast(Uint16*, OFconst_cast(void*, frame)), jpegData, jpegLen);
398           }
399 
400           // store frame
401           if (result.good())
402           {
403             result = pixelSequence->storeCompressedFrame(offsetList, jpegData, jpegLen, cp->getFragmentSize());
404           }
405 
406           // delete block of JPEG data
407           delete[] jpegData;
408           compressedSize += jpegLen;
409         }
410       }
411       delete jpeg;
412     } else result = EC_MemoryExhausted;
413   }
414 
415   // store pixel sequence if everything was successful
416   if (result.good()) pixSeq = pixelSequence;
417   else
418   {
419     delete pixelSequence;
420     pixSeq = NULL;
421   }
422 
423   if ((result.good()) && (cp->getCreateOffsetTable()))
424   {
425     // create offset table
426     result = offsetTable->createOffsetTable(offsetList);
427   }
428 
429   if (result.good())
430   {
431     // adapt attributes in image pixel module
432     if (result.good()) result = dataset->putAndInsertUint16(DCM_SamplesPerPixel, samplesPerPixel);
433     if (result.good()) result = dataset->putAndInsertString(DCM_PhotometricInterpretation, photometricInterpretation);
434     if (result.good())
435     {
436       if (bitsPerSample > 8)
437         result = dataset->putAndInsertUint16(DCM_BitsAllocated, 16);
438       else
439         result = dataset->putAndInsertUint16(DCM_BitsAllocated, 8);
440     }
441     if (result.good()) result = dataset->putAndInsertUint16(DCM_BitsStored, bitsPerSample);
442     if (result.good()) result = dataset->putAndInsertUint16(DCM_HighBit, OFstatic_cast(Uint16, bitsPerSample-1));
443     if (result.good()) result = dataset->putAndInsertUint16(DCM_PixelRepresentation, 0);
444     if (result.good())
445     {
446       if (monochromeMode) delete dataset->remove(DCM_PlanarConfiguration);
447       else result = dataset->putAndInsertUint16(DCM_PlanarConfiguration, 0);
448     }
449     delete dataset->remove(DCM_SmallestImagePixelValue);
450     delete dataset->remove(DCM_LargestImagePixelValue);
451     delete dataset->remove(DCM_RedPaletteColorLookupTableDescriptor);
452     delete dataset->remove(DCM_GreenPaletteColorLookupTableDescriptor);
453     delete dataset->remove(DCM_BluePaletteColorLookupTableDescriptor);
454     delete dataset->remove(DCM_RedPaletteColorLookupTableData);
455     delete dataset->remove(DCM_GreenPaletteColorLookupTableData);
456     delete dataset->remove(DCM_BluePaletteColorLookupTableData);
457     delete dataset->remove(DCM_PixelPaddingValue);
458     delete dataset->remove(DCM_PixelPaddingRangeLimit);
459     delete dataset->remove(DCM_SmallestPixelValueInSeries);
460     delete dataset->remove(DCM_LargestPixelValueInSeries);
461     delete dataset->remove(DCM_PaletteColorLookupTableUID);
462     delete dataset->remove(DCM_SegmentedRedPaletteColorLookupTableData);
463     delete dataset->remove(DCM_SegmentedGreenPaletteColorLookupTableData);
464     delete dataset->remove(DCM_SegmentedBluePaletteColorLookupTableData);
465   }
466   if (compressedSize > 0) compressionRatio = OFstatic_cast(double, uncompressedSize) / OFstatic_cast(double, compressedSize);
467 
468   delete dimage;
469   return result;
470 }
471 
472 
encodeTrueLossless(const DcmRepresentationParameter * toRepParam,DcmPixelSequence * & pixSeq,const DcmCodecParameter * cp,DcmStack & objStack) const473 OFCondition DJCodecEncoder::encodeTrueLossless(
474   const DcmRepresentationParameter * toRepParam,
475   DcmPixelSequence * & pixSeq,
476   const DcmCodecParameter *cp,
477   DcmStack & objStack) const
478 {
479   OFCondition result = EC_Normal;
480   // assume we can cast the codec parameter to what we need
481   DJCodecParameter *djcp = OFreinterpret_cast(DJCodecParameter*, OFconst_cast(DcmCodecParameter*, cp));
482   // get dataset from stack
483   DcmStack localStack(objStack);
484   (void)localStack.pop();
485   DcmObject *dataset = localStack.pop();
486 
487   // check whether dataset was on top of the stack
488   if ((!dataset)||((dataset->ident()!= EVR_dataset) && (dataset->ident()!= EVR_item)))
489     return EC_InvalidTag;
490   else
491   {
492     DcmItem *datsetItem = OFreinterpret_cast(DcmItem*, dataset);
493     double compressionRatio = 0.0;
494     const Uint16* pixelData;
495     size_t length = 0;
496     Uint16 bitsAllocated = 0;
497     Uint16 bitsStored = 0;
498     Uint16 bytesAllocated = 0;
499     Uint16 samplesPerPixel = 0;
500     Uint16 planarConfiguration = 0;
501     Uint16 columns = 0;
502     Uint16 rows = 0;
503     Sint32 numberOfFrames = 1;
504     EP_Interpretation interpr = EPI_Unknown;
505     Uint8 *jpegData = NULL;
506     Uint32 jpegLen  = 0;
507     OFBool byteSwapped = OFFalse;      // true if we have byte-swapped the original pixel data
508     OFBool planConfSwitched = OFFalse; // true if planar configuration was toggled
509     DcmOffsetList offsetList;
510     OFString photometricInterpretation;
511     DcmElement *dummyElem;
512 
513     // get relevant attributes for encoding from dataset
514     result = datsetItem->findAndGetUint16(DCM_BitsStored, bitsStored);
515     if (result.good()) result = datsetItem->findAndGetUint16(DCM_BitsAllocated, bitsAllocated);
516     if (result.good()) result = datsetItem->findAndGetUint16(DCM_SamplesPerPixel, samplesPerPixel);
517     if (result.good()) result = datsetItem->findAndGetUint16(DCM_Columns, columns);
518     if (result.good()) result = datsetItem->findAndGetUint16(DCM_Rows, rows);
519     if (result.good()) result = datsetItem->findAndGetOFString(DCM_PhotometricInterpretation, photometricInterpretation);
520     if (result.good()) result = datsetItem->findAndGetUint16Array(DCM_PixelData, pixelData, NULL, OFFalse);
521     if (result.good()) result = datsetItem->findAndGetElement(DCM_PixelData, dummyElem);
522     if (result.good()) length = dummyElem->getLength();
523     if (result.good())
524     {
525       result = datsetItem->findAndGetSint32(DCM_NumberOfFrames, numberOfFrames);
526       if (result.bad() || numberOfFrames < 1) numberOfFrames = 1;
527       result = EC_Normal;
528     }
529     if (result.bad())
530     {
531       DCMJPEG_ERROR("True lossless encoder: Unable to get relevant attributes from dataset");
532       return result;
533     }
534 
535     // check, whether bit depth is supported
536     if (bitsAllocated == 8)
537       bytesAllocated = 1;
538     else if (bitsAllocated == 16)
539       bytesAllocated = 2;
540     else
541     {
542       DCMJPEG_ERROR("True lossless encoder: Only 8 or 16 bits allocated supported");
543       return EC_IllegalParameter;
544     }
545 
546     // make sure that all the descriptive attributes have sensible values
547     if ((columns < 1)||(rows < 1)||(samplesPerPixel < 1))
548     {
549       DCMJPEG_ERROR("True lossless encoder: Invalid attribute values in pixel module");
550       return EC_CannotChangeRepresentation;
551     }
552 
553     /* Set and check photometric interpretation (up to now: EPI_RGB)
554      * Only photometric interpretations, that are explicitly "supported" by the
555      * IJG lib are set. For all others "unknown" is set. Some are even rejected here.
556      */
557     if (photometricInterpretation == "MONOCHROME1")
558       interpr = EPI_Monochrome1;
559     else if (photometricInterpretation == "MONOCHROME2")
560       interpr = EPI_Monochrome2;
561     else if (photometricInterpretation == "YBR_FULL")
562       interpr = EPI_YBR_Full;
563     // some photometric interpretations are not supported:
564     else if ( (photometricInterpretation == "YBR_FULL_422")    ||
565               (photometricInterpretation == "YBR_PARTIAL_422") ||
566               (photometricInterpretation == "YBR_PARTIAL_420") ||
567               (photometricInterpretation == "YBR_ICT")         ||
568               (photometricInterpretation == "YBR_RCT") )
569     {
570       DCMJPEG_ERROR("True lossless encoder: Photometric interpretation not supported: " << photometricInterpretation);
571       return EC_IllegalParameter;
572     }
573     else    // Palette, HSV, ARGB, CMYK
574       interpr = EPI_Unknown;
575 
576     // IJG libs need "color by pixel", transform if required
577     if (result.good() && (samplesPerPixel > 1) )
578     {
579       result = datsetItem->findAndGetUint16(DCM_PlanarConfiguration, planarConfiguration);
580       if ( result.good() && (planarConfiguration == 1) )
581       {
582         if (bytesAllocated == 1)
583           result = togglePlanarConfiguration8(OFreinterpret_cast(Uint8*, OFconst_cast(Uint16*, pixelData)), length, samplesPerPixel, OFstatic_cast(Uint16, 1) /* switch to "by pixel"*/);
584         else
585           result = togglePlanarConfiguration16(OFconst_cast(Uint16*, pixelData), length/2 /*16 bit*/, samplesPerPixel, OFstatic_cast(Uint16, 1) /* switch to "by pixel"*/);
586         planConfSwitched = OFTrue;
587       }
588     }
589     if (result.bad())
590     {
591         DCMJPEG_ERROR("True lossless encoder: Unable to change Planar Configuration from 'by plane' to 'by pixel' for encoding");
592         return result;
593     }
594 
595     // check whether enough raw data is available for encoding
596     if (bytesAllocated * samplesPerPixel * columns * rows * OFstatic_cast(size_t,numberOfFrames) > length)
597     {
598       DCMJPEG_ERROR("True lossless encoder: Cannot change representation, not enough data");
599       return EC_CannotChangeRepresentation;
600     }
601 
602     // byte swap pixel data to little endian if bits allocated is 8
603     if ((gLocalByteOrder == EBO_BigEndian) && (bitsAllocated == 8))
604     {
605       result = swapIfNecessary(EBO_LittleEndian, gLocalByteOrder, OFconst_cast(Uint16*, pixelData), OFstatic_cast(Uint32, length), sizeof(Uint16));
606       if ( result.bad() )
607       {
608         DCMJPEG_ERROR("True lossless encoder: Unable to swap bytes to respect local byte ordering");
609         return EC_CannotChangeRepresentation;
610       }
611       byteSwapped = OFTrue;
612     }
613 
614     // create initial pixel sequence with empty offset table
615     DcmPixelSequence *pixelSequence = NULL;
616     DcmPixelItem *offsetTable = NULL;
617     if (result.good())
618     {
619       pixelSequence = new DcmPixelSequence(DCM_PixelSequenceTag);
620       if (pixelSequence == NULL) result = EC_MemoryExhausted;
621       else
622       {
623         // create empty offset table
624         offsetTable = new DcmPixelItem(DCM_PixelItemTag);
625         if (offsetTable == NULL) result = EC_MemoryExhausted;
626         else pixelSequence->insert(offsetTable);
627       }
628     }
629 
630     // prepare some variables for encoding
631     size_t frameCount = OFstatic_cast(size_t, numberOfFrames);
632     size_t frameSize = columns * rows * samplesPerPixel * bytesAllocated;
633     const Uint8 *framePointer = OFreinterpret_cast(const Uint8 *, pixelData);
634     size_t compressedSize = 0;
635 
636     // create encoder corresponding to bit depth (8 or 16 bit)
637     DJEncoder *jpeg = createEncoderInstance(toRepParam, djcp, OFstatic_cast(Uint8, bitsAllocated));
638     if (jpeg)
639     {
640       // main loop for compression: compress each frame
641       for (unsigned int i=0; i<frameCount && result.good(); i++)
642       {
643         if (bitsAllocated == 8)
644         {
645           jpeg->encode(columns, rows, interpr, samplesPerPixel, OFconst_cast(Uint8*, framePointer), jpegData, jpegLen);
646         }
647         else if (bitsAllocated == 16)
648         {
649           jpeg->encode(columns, rows, interpr, samplesPerPixel, OFreinterpret_cast(Uint16*, OFconst_cast(Uint8*, framePointer)), jpegData, jpegLen);
650         }
651         // update variables
652         compressedSize+=jpegLen;
653         framePointer+=frameSize;
654         if (jpegLen == 0)
655         {
656           DCMJPEG_ERROR("True lossless encoder: Error encoding frame");
657           result = EC_CannotChangeRepresentation;
658         }
659         else
660         {
661           result = pixelSequence->storeCompressedFrame(offsetList, jpegData, jpegLen, djcp->getFragmentSize());
662         }
663         // free memory
664         delete[] jpegData;
665       }
666     }
667     else
668     {
669       DCMJPEG_ERROR("True lossless encoder: Cannot allocate encoder instance");
670       result = EC_IllegalCall;
671     }
672     if (result.good())
673     {
674       compressionRatio = OFstatic_cast(double, bytesAllocated * samplesPerPixel * columns * rows * numberOfFrames) / OFstatic_cast(double, compressedSize);
675       pixSeq = pixelSequence;
676     }
677     else
678       delete pixelSequence;
679     delete jpeg; // encoder no longer in use
680 
681     if ((result.good()) && (djcp->getCreateOffsetTable()))
682     {
683       // create offset table
684       result = offsetTable->createOffsetTable(offsetList);
685     }
686 
687     // the following operations do not affect the Image Pixel Module
688     // but other modules such as SOP Common.  We only perform these
689     // changes if we're on the main level of the datsetItem,
690     // which should always identify itself as datsetItem, not as item.
691 
692     // update derivation description reflecting the JPEG compression applied
693     result = updateDerivationDescription(datsetItem, toRepParam, djcp, OFstatic_cast(Uint8, bitsAllocated), compressionRatio);
694 
695     if ( (datsetItem->ident() == EVR_dataset) && result.good() )
696     {
697       // convert to Secondary Capture if requested by user.
698       // This method creates a new SOP class UID, so it should be executed
699       // after the call to newInstance() which creates a Source Image Sequence.
700       if ( djcp->getConvertToSC() || (djcp->getUIDCreation() == EUC_always) )
701       {
702         if (djcp->getConvertToSC())
703         {
704           result = DcmCodec::convertToSecondaryCapture(datsetItem);
705         }
706         // update image type (set to DERIVED)
707         if (result.good())
708           result = DcmCodec::updateImageType(datsetItem);
709         if (result.good())
710           result = DcmCodec::newInstance(OFreinterpret_cast(DcmItem*, datsetItem), "DCM", "121320", "Uncompressed predecessor");
711       }
712     }
713     // switch _original_ pixel data back to "color by plane", if required
714     if (planConfSwitched)
715     {
716       if (bytesAllocated == 1)
717         result = togglePlanarConfiguration8(OFreinterpret_cast(Uint8*, OFconst_cast(Uint16*, pixelData)), length, samplesPerPixel, OFstatic_cast(Uint16, 0) /*switch to "by plane"*/);
718       else
719         result = togglePlanarConfiguration16(OFconst_cast(Uint16*, pixelData), length/2, samplesPerPixel, OFstatic_cast(Uint16, 0) /*switch to "by plane"*/);
720       if (result.good())
721       {
722         // update Planar Configuration in dataset
723         result = updatePlanarConfiguration(datsetItem, 0 /* update to "by pixel" */);
724       }
725       else
726       {
727         DCMJPEG_ERROR("True lossless encoder: Cannot switch back to original planar configuration of the pixel data");
728         result = EC_CannotChangeRepresentation;
729       }
730     }
731     // byte swap pixel data back to local endian if necessary
732     if (byteSwapped)
733       swapIfNecessary(gLocalByteOrder, EBO_LittleEndian, OFconst_cast(Uint16*, pixelData), OFstatic_cast(Uint32, length), sizeof(Uint16));
734   }
735   return result;
736 }
737 
738 
appendCompressionRatio(OFString & arg,double ratio)739 void DJCodecEncoder::appendCompressionRatio(
740   OFString& arg,
741   double ratio)
742 {
743   char buf[64];
744   OFStandard::ftoa(buf, sizeof(buf), ratio, OFStandard::ftoa_uppercase, 0, 5);
745   arg += buf;
746 }
747 
748 
updateLossyCompressionRatio(DcmItem * dataset,double ratio) const749 OFCondition DJCodecEncoder::updateLossyCompressionRatio(
750   DcmItem *dataset,
751   double ratio) const
752 {
753   if (dataset == NULL) return EC_IllegalCall;
754 
755   // set Lossy Image Compression to "01" (see DICOM part 3, C.7.6.1.1.5)
756   OFCondition result = dataset->putAndInsertString(DCM_LossyImageCompression, "01");
757   if (result.bad()) return result;
758 
759   // set Lossy Image Compression Ratio
760   OFString s;
761   const char *oldRatio = NULL;
762   if ((dataset->findAndGetString(DCM_LossyImageCompressionRatio, oldRatio)).good() && oldRatio)
763   {
764     s = oldRatio;
765     s += "\\";
766   }
767   appendCompressionRatio(s, ratio);
768 
769   result = dataset->putAndInsertString(DCM_LossyImageCompressionRatio, s.c_str());
770   if (result.bad()) return result;
771 
772   // count VM of lossy image compression ratio
773   size_t i;
774   size_t s_vm = 0;
775   size_t s_sz = s.size();
776   for (i = 0; i < s_sz; ++i)
777     if (s[i] == '\\') ++s_vm;
778 
779   // set Lossy Image Compression Method
780   const char *oldMethod = NULL;
781   OFString m;
782   if ((dataset->findAndGetString(DCM_LossyImageCompressionMethod, oldMethod)).good() && oldMethod)
783   {
784     m = oldMethod;
785     m += "\\";
786   }
787 
788   // count VM of lossy image compression method
789   size_t m_vm = 0;
790   size_t m_sz = m.size();
791   for (i = 0; i < m_sz; ++i)
792     if (m[i] == '\\') ++m_vm;
793 
794   // make sure that VM of Compression Method is not smaller than  VM of Compression Ratio
795   while (m_vm++ < s_vm) m += "\\";
796 
797   m += "ISO_10918_1";
798   return dataset->putAndInsertString(DCM_LossyImageCompressionMethod, m.c_str());
799 }
800 
801 
updateDerivationDescription(DcmItem * dataset,const DcmRepresentationParameter * toRepParam,const DJCodecParameter * cp,Uint8 bitsPerSample,double ratio) const802 OFCondition DJCodecEncoder::updateDerivationDescription(
803   DcmItem *dataset,
804   const DcmRepresentationParameter * toRepParam,
805   const DJCodecParameter *cp,
806   Uint8 bitsPerSample,
807   double ratio) const
808 {
809   OFString derivationDescription;
810 
811   // create new Derivation Description
812   createDerivationDescription(toRepParam, cp, bitsPerSample, ratio, derivationDescription);
813 
814   // append old Derivation Description, if any
815   const char *oldDerivation = NULL;
816   if ((dataset->findAndGetString(DCM_DerivationDescription, oldDerivation)).good() && oldDerivation)
817   {
818     derivationDescription += " [";
819     derivationDescription += oldDerivation;
820     derivationDescription += "]";
821     if (derivationDescription.length() > 1024)
822     {
823       // ST is limited to 1024 characters, cut off tail
824       derivationDescription.erase(1020);
825       derivationDescription += "...]";
826     }
827   }
828 
829   OFCondition result = dataset->putAndInsertString(DCM_DerivationDescription, derivationDescription.c_str());
830   if (result.good())
831   {
832     // assume we can cast the codec parameter to what we need
833     DJCodecParameter *djcp = OFconst_cast(DJCodecParameter*, cp);
834 
835     if (!isLosslessProcess() || !djcp->getTrueLosslessMode())
836       result = DcmCodec::insertCodeSequence(dataset, DCM_DerivationCodeSequence, "DCM", "113040", "Lossy Compression");
837 
838   }
839   return result;
840 }
841 
842 
adjustOverlays(DcmItem * dataset,DicomImage & image) const843 OFCondition DJCodecEncoder::adjustOverlays(
844   DcmItem *dataset,
845   DicomImage& image) const
846 {
847   if (dataset == NULL) return EC_IllegalCall;
848 
849   unsigned int overlayCount = image.getOverlayCount();
850   if (overlayCount > 0)
851   {
852     Uint16 group = 0;
853     DcmStack stack;
854     size_t bytesAllocated = 0;
855     Uint8 *buffer = NULL;
856     unsigned int width = 0;
857     unsigned int height = 0;
858     long unsigned int frames = 0;
859     DcmElement *elem = NULL;
860     OFCondition result = EC_Normal;
861 
862     // adjust overlays (prior to grayscale compression)
863     for (unsigned int i=0; i < overlayCount; i++)
864     {
865       // check if current overlay is embedded in pixel data
866       group = OFstatic_cast(Uint16, image.getOverlayGroupNumber(i));
867       stack.clear();
868       if ((dataset->search(DcmTagKey(group, 0x3000), stack, ESM_fromHere, OFFalse)).bad())
869       {
870         // separate Overlay Data not found. Assume overlay is embedded.
871         bytesAllocated = image.create6xxx3000OverlayData(buffer, i, width, height, frames);
872         if (bytesAllocated > 0)
873         {
874           elem = new DcmOverlayData(DcmTagKey(group, 0x3000)); // DCM_OverlayData
875           if (elem)
876           {
877             result = elem->putUint8Array(buffer, OFstatic_cast(Uint32, bytesAllocated));
878             delete[] buffer;
879             if (result.good())
880             {
881               dataset->insert(elem, OFTrue /*replaceOld*/);
882               // DCM_OverlayBitsAllocated
883               result = dataset->putAndInsertUint16(DcmTagKey(group, 0x0100), 1);
884               // DCM_OverlayBitPosition
885               if (result.good()) result = dataset->putAndInsertUint16(DcmTagKey(group, 0x0102), 0);
886             }
887             else
888             {
889               delete elem;
890               return result;
891             }
892           }
893           else
894           {
895             delete[] buffer;
896             return EC_MemoryExhausted;
897           }
898         }
899         else return EC_IllegalCall;
900       }
901     }
902   }
903   return EC_Normal;
904 }
905 
906 
encodeMonochromeImage(DcmItem * dataset,const DcmRepresentationParameter * toRepParam,DcmPixelSequence * & pixSeq,const DJCodecParameter * cp,double & compressionRatio) const907 OFCondition DJCodecEncoder::encodeMonochromeImage(
908   DcmItem *dataset,
909   const DcmRepresentationParameter * toRepParam,
910   DcmPixelSequence * & pixSeq,
911   const DJCodecParameter *cp,
912   double& compressionRatio) const
913 {
914   OFCondition result = EC_Normal;
915   DcmOffsetList offsetList;
916   DcmPixelSequence *pixelSequence = NULL;
917   DcmPixelItem *offsetTable = NULL;
918   unsigned short bitsPerSample = 0;
919   compressionRatio = 0.0; // initialize if something goes wrong
920   size_t compressedSize = 0;
921   double uncompressedSize = 0.0;
922   unsigned long flags = 0; // flags for initialization of DicomImage
923 
924   // variables needed if VOI mode is 0
925   double minRange = 0.0;
926   double maxRange = 0.0;
927   double minUsed  = 0.0;
928   double maxUsed  = 0.0;
929   double rescaleSlope = 1.0;
930   double rescaleIntercept = 0.0;
931   double voiFactor = 1.0;
932   double voiOffset = 0.0;
933   double windowCenter = 0.0;
934   double windowWidth = 0.0;
935   OFBool deleteVOILUT = OFFalse;
936   OFBool mode_XA = OFFalse; // true if the current SOP class uses the X-Ray Image Module
937   OFBool mode_CT = OFFalse; // true if the current SOP class uses the CT Image Module
938 
939   // Modes of operation (if VOI mode is 0)
940   OFBool mode_usePixelValues = cp->getUsePixelValues();
941   OFBool mode_useModalityRescale = cp->getUseModalityRescale();
942 
943   //create flags for DicomImage corresponding to DJCodecParameter options
944   if (cp->getAcceptWrongPaletteTags())
945     flags |= CIF_WrongPaletteAttributeTags;
946   if (cp->getAcrNemaCompatibility())
947     flags |= CIF_AcrNemaCompatibility;
948 
949   // Check if image is continuous-tone, bail out otherwise.
950   // We check the value of BitsStored, which is not affected by any transformation such as MLUT.
951   Uint16 bitsStored = 0;
952   result = dataset->findAndGetUint16(DCM_BitsStored, bitsStored);
953   if (result.bad()) return result;
954 
955   if ((bitsStored > 16) && isLosslessProcess())
956   {
957     DCMJPEG_WARN("Cannot lossless compress image with " << bitsStored << " bits/sample: JPEG supports max. 16 bits.");
958     return EJ_UnsupportedBitDepth;
959   }
960 
961   if (bitsStored < 2)
962   {
963     DCMJPEG_WARN("Cannot compress image with " << bitsStored << " bit/sample: JPEG requires at least 2 bits.");
964     return EJ_UnsupportedBitDepth;
965   }
966 
967   // create DicomImage object. Will fail if dcmimage has not been activated in main().
968   // transfer syntax can be any uncompressed one.
969   DicomImage dimage(dataset, EXS_LittleEndianImplicit, flags); // read all frames
970   if (dimage.getStatus() != EIS_Normal)
971   {
972     DCMJPEG_WARN("Monochrome encoder: " << DicomImage::getString(dimage.getStatus()));
973     result = EC_IllegalCall;
974   }
975 
976   // don't render overlays
977   dimage.hideAllOverlays();
978 
979   // actual pixel depth of source image which can be different from Bits Stored
980   // e. g. when Modality LUT (if enabled) shifts pixel values to a smaller
981   // range or if the pixel values itself do not make use of the "Bits Stored"
982   // full range available.
983   int pixelDepth = dimage.getDepth();
984 
985   // create overlay data for embedded overlays
986   if (result.good()) result = adjustOverlays(dataset, dimage);
987 
988   // VOI transformations should only be applied on the dataset level, not
989   // in nested items such as the Icon Image Sequence where we don't expect
990   // a VOI window or LUT to be present
991   size_t windowType = 0;
992   if (dataset->ident() == EVR_dataset)
993   {
994     windowType = cp->getWindowType();
995   }
996 
997   // set VOI transformation
998   if (result.good())
999   {
1000     switch (windowType)
1001     {
1002       case 0: // no VOI transformation
1003         {
1004           // disable correction of polarity or any other presentation LUT transformation
1005           dimage.setPresentationLutShape(ESP_Identity);
1006 
1007           // disable VOI transformation
1008           dimage.setNoVoiTransformation();
1009 
1010           // look up SOP Class UID, if any
1011           const char *classUID = NULL;
1012           dataset->findAndGetString(DCM_SOPClassUID, classUID);
1013 
1014           // SOP Class specifics.
1015           if (classUID && ! cp->getConvertToSC())
1016           {
1017             // these three SOP classes use the X-Ray Image Module in which the meaning
1018             // of the Modality LUT transformation is "inversed" and, therefore,
1019             // needs special handling.  This is not an issue if we're converting to
1020             // secondary capture anyway.
1021             if ((0 == strcmp(classUID, UID_XRayAngiographicImageStorage)) ||
1022                 (0 == strcmp(classUID, UID_XRayRadiofluoroscopicImageStorage)) ||
1023                 (0 == strcmp(classUID, UID_RETIRED_XRayAngiographicBiPlaneImageStorage)))
1024             {
1025               mode_XA = OFTrue;
1026               mode_useModalityRescale = OFFalse; // inverse definition of Modality LUT Module
1027             }
1028 
1029             // CT is also a special case because the Modality LUT is required here
1030             // to convert to Hounsfield units. Again, this is not an issue if we're
1031             // converting to SC anyway.
1032             if (0 == strcmp(classUID, UID_CTImageStorage))
1033             {
1034               mode_CT = OFTrue;
1035               mode_useModalityRescale = OFTrue; // required for Hounsfield units
1036             }
1037           }
1038 
1039           // query image range and extreme values
1040           if (result.good())
1041           {
1042             // technically possible min/max values
1043             if (! dimage.getMinMaxValues(minRange, maxRange, 1)) result = EC_IllegalCall;
1044             if (maxRange <= minRange) result = EC_IllegalCall;
1045           }
1046 
1047           if (result.good())
1048           {
1049             // actually present min/max values in pixel data
1050             if (! dimage.getMinMaxValues(minUsed, maxUsed, 0)) result = EC_IllegalCall;
1051             if (maxUsed < minUsed) result = EC_IllegalCall;
1052           }
1053         }
1054         break;
1055       case 1: // use the n-th VOI window from the image file
1056         {
1057           size_t windowParameter = cp->getWindowParameter();
1058           if ((windowParameter < 1) || (windowParameter > dimage.getWindowCount())) result = EC_IllegalCall;
1059           if (!dimage.setWindow(OFstatic_cast(unsigned long, windowParameter - 1))) result = EC_IllegalCall;
1060         }
1061         break;
1062       case 2: // use the n-th VOI look up table from the image file
1063         {
1064           size_t windowParameter = cp->getWindowParameter();
1065           if ((windowParameter < 1) || (windowParameter > dimage.getVoiLutCount())) result = EC_IllegalCall;
1066           if (!dimage.setVoiLut(OFstatic_cast(unsigned long, windowParameter - 1))) result = EC_IllegalCall;
1067         }
1068         break;
1069       case 3: // Compute VOI window using min-max algorithm
1070         if (!dimage.setMinMaxWindow(0)) result = EC_IllegalCall;
1071         break;
1072       case 4: // Compute VOI window using Histogram algorithm, ignoring n percent
1073         {
1074           size_t windowParameter = cp->getWindowParameter();
1075           if (!dimage.setHistogramWindow(OFstatic_cast(double, windowParameter)/100.0)) result = EC_IllegalCall;
1076         }
1077         break;
1078       case 5: // Compute VOI window using center r and width s
1079         {
1080           double winCenter=0.0, winWidth=0.0;
1081           cp->getVOIWindow(winCenter, winWidth);
1082           if (!dimage.setWindow(winCenter, winWidth)) result = EC_IllegalCall;
1083         }
1084         break;
1085       case 6: // Compute VOI window using min-max algorithm ignoring extremes
1086         if (!dimage.setMinMaxWindow(1)) result = EC_IllegalCall;
1087         break;
1088       case 7: // Compute region of interest VOI window
1089         {
1090          size_t left_pos=0, top_pos=0, width=0, height=0;
1091          cp->getROI(left_pos, top_pos, width, height);
1092           if (!dimage.setRoiWindow(OFstatic_cast(unsigned long, left_pos), OFstatic_cast(unsigned long, top_pos),
1093              OFstatic_cast(unsigned long, width), OFstatic_cast(unsigned long, height))) result = EC_IllegalCall;
1094         }
1095         break;
1096       default: // includes case 0, which must not occur here
1097         result = EC_IllegalCall;
1098         break;
1099     }
1100   }
1101 
1102   // create initial pixel sequence
1103   if (result.good())
1104   {
1105     pixelSequence = new DcmPixelSequence(DCM_PixelSequenceTag);
1106     if (pixelSequence == NULL) result = EC_MemoryExhausted;
1107     else
1108     {
1109       // create empty offset table
1110       offsetTable = new DcmPixelItem(DCM_PixelItemTag);
1111       if (offsetTable == NULL) result = EC_MemoryExhausted;
1112       else pixelSequence->insert(offsetTable);
1113     }
1114   }
1115 
1116   // select bit depth
1117   Uint16 compressedBits = OFstatic_cast(Uint16, cp->getForcedBitDepth());
1118   if (result.good())
1119   {
1120     if (compressedBits == 0)
1121     {
1122       result = OFreinterpret_cast(DcmItem*, dataset)->findAndGetUint16(DCM_BitsStored, compressedBits);
1123     }
1124   }
1125 
1126   // create codec instance
1127   if (result.good())
1128   {
1129     DJEncoder *jpeg = createEncoderInstance(toRepParam, cp, OFstatic_cast(Uint8, compressedBits));
1130     if (jpeg)
1131     {
1132       bitsPerSample = jpeg->bitsPerSample();
1133 
1134       if (windowType == 0)
1135       {
1136         // perform image computations
1137         if (mode_usePixelValues)
1138         {
1139           double z = minUsed;
1140           if (z >= 0) z = floor(z); else z = ceil(z-1023.0);
1141           double rangeUsed = maxUsed - minUsed + 1; // range that must be covered by window
1142 
1143           // compute optimum window width
1144           if (pixelDepth < bitsPerSample) windowWidth  = 1 << pixelDepth;
1145              else windowWidth  = 1 << bitsPerSample;
1146           while (windowWidth < rangeUsed) windowWidth *= 2;
1147 
1148           windowCenter = windowWidth * 0.5;
1149           if ((z + windowWidth) > maxUsed) windowCenter += z; else windowCenter += minUsed;
1150           dimage.setWindow(windowCenter, windowWidth);
1151         }
1152 
1153         // perform image computations
1154         if (mode_useModalityRescale)
1155         {
1156           // Use Rescale Slope/Intercept to map the decompressed image data
1157           // back to the original value range, keeping all VOI transformations valid.
1158           if (mode_usePixelValues)
1159           {
1160             rescaleSlope = (windowWidth-1)/((1<<bitsPerSample)-1);
1161             rescaleIntercept = windowCenter - (windowWidth * 0.5);
1162           }
1163           else
1164           {
1165             rescaleSlope = (maxRange - minRange)/((1<<bitsPerSample)-1);
1166             rescaleIntercept = minRange;
1167           }
1168         }
1169         else
1170         {
1171           // Don't use Rescale Slope/Intercept (remove or encode as identity transformation).
1172           // Modify window centers/widths encoded in image, remove VOI LUTs.
1173           deleteVOILUT = OFTrue;
1174           if (mode_usePixelValues)
1175           {
1176             voiFactor = ((1<<bitsPerSample)-1)/(windowWidth-1); // 1/rescaleSlope
1177             voiOffset = (windowWidth * 0.5) - windowCenter; // - rescaleIntercept
1178           }
1179           else
1180           {
1181             voiFactor = ((1<<bitsPerSample)-1)/(maxRange - minRange); // 1/rescaleSlope
1182             voiOffset = -minRange; // - rescaleIntercept
1183           }
1184         }
1185       }
1186 
1187       // render and compress each frame
1188       size_t frameCount = dimage.getFrameCount();
1189       unsigned short bytesPerSample = jpeg->bytesPerSample();
1190       unsigned short columns = OFstatic_cast(unsigned short, dimage.getWidth());
1191       unsigned short rows = OFstatic_cast(unsigned short, dimage.getHeight());
1192       Uint8 *jpegData = NULL;
1193       Uint32 jpegLen  = 0;
1194       const void *frame = NULL;
1195 
1196       // compute original image size in bytes, ignoring any padding bits.
1197       Uint16 samplesPerPixel = 0;
1198       if ((dataset->findAndGetUint16(DCM_SamplesPerPixel, samplesPerPixel)).bad()) samplesPerPixel = 1;
1199       uncompressedSize = OFstatic_cast(double, columns * rows * pixelDepth * frameCount * samplesPerPixel) / 8.0;
1200       for (size_t i=0; (i<frameCount) && (result.good()); i++)
1201       {
1202         frame = dimage.getOutputData(bitsPerSample, OFstatic_cast(unsigned long, i), 0);
1203         if (frame == NULL) result = EC_MemoryExhausted;
1204         else
1205         {
1206           // compress frame
1207           jpegData = NULL;
1208           if (bytesPerSample == 1)
1209           {
1210             result = jpeg->encode(columns, rows, EPI_Monochrome2, 1, OFreinterpret_cast(Uint8*, OFconst_cast(void*, frame)), jpegData, jpegLen);
1211           } else {
1212             result = jpeg->encode(columns, rows, EPI_Monochrome2, 1, OFreinterpret_cast(Uint16*, OFconst_cast(void*, frame)), jpegData, jpegLen);
1213           }
1214 
1215           // store frame
1216           if (result.good())
1217           {
1218             result = pixelSequence->storeCompressedFrame(offsetList, jpegData, jpegLen, cp->getFragmentSize());
1219           }
1220 
1221           // delete block of JPEG data
1222           delete[] jpegData;
1223           compressedSize += jpegLen;
1224         }
1225       }
1226       delete jpeg;
1227     } else result = EC_MemoryExhausted;
1228   }
1229 
1230   // store pixel sequence if everything went well.
1231   if (result.good()) pixSeq = pixelSequence;
1232   else
1233   {
1234     delete pixelSequence;
1235     pixSeq = NULL;
1236   }
1237 
1238   if ((result.good()) && (cp->getCreateOffsetTable()))
1239   {
1240     // create offset table
1241     result = offsetTable->createOffsetTable(offsetList);
1242   }
1243 
1244   if (result.good())
1245   {
1246     // adapt attributes in image pixel module
1247     if (result.good()) result = dataset->putAndInsertUint16(DCM_SamplesPerPixel, 1);
1248     if (result.good() && (windowType != 0)) result = dataset->putAndInsertString(DCM_PhotometricInterpretation, "MONOCHROME2");
1249     if (result.good())
1250     {
1251       if (bitsPerSample > 8)
1252         result = dataset->putAndInsertUint16(DCM_BitsAllocated, 16);
1253       else
1254         result = dataset->putAndInsertUint16(DCM_BitsAllocated, 8);
1255     }
1256     if (result.good()) result = dataset->putAndInsertUint16(DCM_BitsStored, bitsPerSample);
1257     if (result.good()) result = dataset->putAndInsertUint16(DCM_HighBit, OFstatic_cast(Uint16, bitsPerSample-1));
1258     if (result.good()) result = dataset->putAndInsertUint16(DCM_PixelRepresentation, 0);
1259     delete dataset->remove(DCM_PlanarConfiguration);
1260     delete dataset->remove(DCM_SmallestImagePixelValue);
1261     delete dataset->remove(DCM_LargestImagePixelValue);
1262     delete dataset->remove(DCM_RedPaletteColorLookupTableDescriptor);
1263     delete dataset->remove(DCM_GreenPaletteColorLookupTableDescriptor);
1264     delete dataset->remove(DCM_BluePaletteColorLookupTableDescriptor);
1265     delete dataset->remove(DCM_RedPaletteColorLookupTableData);
1266     delete dataset->remove(DCM_GreenPaletteColorLookupTableData);
1267     delete dataset->remove(DCM_BluePaletteColorLookupTableData);
1268     delete dataset->remove(DCM_PixelPaddingValue);
1269     delete dataset->remove(DCM_SmallestPixelValueInSeries);
1270     delete dataset->remove(DCM_LargestPixelValueInSeries);
1271     delete dataset->remove(DCM_PaletteColorLookupTableUID);
1272     delete dataset->remove(DCM_SegmentedRedPaletteColorLookupTableData);
1273     delete dataset->remove(DCM_SegmentedGreenPaletteColorLookupTableData);
1274     delete dataset->remove(DCM_SegmentedBluePaletteColorLookupTableData);
1275 
1276     // check if either Modality LUT Sequence or Rescale Slope are present
1277     DcmStack stack;
1278     OFBool foundModalityLUT = OFFalse;
1279     if ((dataset->search(DCM_ModalityLUTSequence, stack, ESM_fromHere, OFFalse)).good()) foundModalityLUT = OFTrue;
1280     if (! foundModalityLUT)
1281     {
1282       stack.clear();
1283       if ((dataset->search(DCM_RescaleSlope, stack, ESM_fromHere, OFFalse)).good()) foundModalityLUT = OFTrue;
1284     }
1285 
1286     // delete old Modality transformation
1287     delete dataset->remove(DCM_ModalityLUTSequence);
1288     delete dataset->remove(DCM_RescaleIntercept);
1289     delete dataset->remove(DCM_RescaleSlope);
1290     delete dataset->remove(DCM_RescaleType);
1291 
1292     // update Modality LUT Module and Pixel Intensity Relationship
1293     if (windowType == 0)
1294     {
1295       if (mode_XA) // XA needs special handling
1296       {
1297         // XA Mode: set Pixel Intensity Relationship to "DISP", no Modality LUT
1298         if (result.good()) result = dataset->putAndInsertString(DCM_PixelIntensityRelationship, "DISP");
1299       }
1300       /* else if we had a modality LUT before, a LUT is inserted again.
1301          or if specific rescale slope/intercept has been computed, use that in image
1302        */
1303       else if (foundModalityLUT || rescaleSlope != 1.0 || rescaleIntercept != 0.0)
1304       {
1305         char buf[64];
1306         OFStandard::ftoa(buf, sizeof(buf), rescaleIntercept, OFStandard::ftoa_uppercase, 0, 6);
1307         if (result.good()) result = dataset->putAndInsertString(DCM_RescaleIntercept, buf);
1308         OFStandard::ftoa(buf, sizeof(buf), rescaleSlope, OFStandard::ftoa_uppercase, 0, 6);
1309         if (result.good()) result = dataset->putAndInsertString(DCM_RescaleSlope, buf);
1310         if (result.good())
1311         {
1312           if (mode_CT) result = dataset->putAndInsertString(DCM_RescaleType, "HU"); // Hounsfield units
1313           else result =         dataset->putAndInsertString(DCM_RescaleType, "US"); // unspecified
1314         }
1315       }
1316     }
1317     else
1318     {
1319       // if we had found a Modality LUT Transformation, create a identity LUT transformation
1320       if (foundModalityLUT)
1321       {
1322         if (result.good()) result = dataset->putAndInsertString(DCM_RescaleIntercept, "0");
1323         if (result.good()) result = dataset->putAndInsertString(DCM_RescaleSlope, "1");
1324         if (result.good()) result = dataset->putAndInsertString(DCM_RescaleType, "US"); // unspecified
1325       }
1326 
1327       // Adjust Pixel Intensity Relationship (needed for XA). If present, set value to "DISP".
1328       stack.clear();
1329       if ((dataset->search(DCM_PixelIntensityRelationship, stack, ESM_fromHere, OFFalse)).good())
1330       {
1331         if (result.good()) result = dataset->putAndInsertString(DCM_PixelIntensityRelationship, "DISP");
1332       }
1333     }
1334 
1335     // Adjust VOI LUT and Presentation LUT transformation
1336     if (windowType == 0)
1337     {
1338       if (deleteVOILUT) delete dataset->remove(DCM_VOILUTSequence);
1339 
1340       // Adjust window center/width
1341       if (result.good()) result = correctVOIWindows(dataset, voiOffset, voiFactor);
1342 
1343       // Don't modify Presentation LUT transformations (if any)
1344     }
1345     else
1346     {
1347       delete dataset->remove(DCM_VOILUTSequence);
1348       delete dataset->remove(DCM_WindowCenter);
1349       delete dataset->remove(DCM_WindowWidth);
1350       delete dataset->remove(DCM_WindowCenterWidthExplanation);
1351 
1352       // Adjust Presentation LUT Transformation.
1353       stack.clear();
1354       OFBool foundPresentationLUT = OFFalse;
1355       if ((dataset->search(DCM_PresentationLUTSequence, stack, ESM_fromHere, OFFalse)).good()) foundPresentationLUT = OFTrue;
1356       if (! foundPresentationLUT)
1357       {
1358         stack.clear();
1359         if ((dataset->search(DCM_PresentationLUTShape, stack, ESM_fromHere, OFFalse)).good()) foundPresentationLUT = OFTrue;
1360       }
1361 
1362       // delete old Presentation LUT transformation
1363       delete dataset->remove(DCM_PresentationLUTSequence);
1364       delete dataset->remove(DCM_PresentationLUTShape);
1365 
1366       // if we had found a Presentation LUT Transformation, create a new identity transformation
1367       if (foundPresentationLUT)
1368       {
1369         if (result.good()) result = dataset->putAndInsertString(DCM_PresentationLUTShape, "IDENTITY");
1370       }
1371     }
1372   }
1373 
1374   // determine compression ratio
1375   if (compressedSize > 0) compressionRatio = OFstatic_cast(double, uncompressedSize) / OFstatic_cast(double, compressedSize);
1376   return result;
1377 }
1378 
1379 
correctVOIWindows(DcmItem * dataset,double voiOffset,double voiFactor)1380 OFCondition DJCodecEncoder::correctVOIWindows(
1381   DcmItem *dataset,
1382   double voiOffset,
1383   double voiFactor)
1384 {
1385   if (voiOffset == 0.0 && voiFactor == 1.0) return EC_Normal;
1386 
1387   OFCondition result = EC_Normal;
1388   DcmElement *center = NULL;
1389   DcmElement *width = NULL;
1390   DcmElement *explanation = NULL;
1391 
1392   DcmStack stack;
1393   if ((dataset->search(DCM_WindowCenter, stack, ESM_fromHere, OFFalse)).good())
1394   {
1395     center = OFreinterpret_cast(DcmElement*, stack.top());
1396   }
1397   stack.clear();
1398   if ((dataset->search(DCM_WindowWidth, stack, ESM_fromHere, OFFalse)).good())
1399   {
1400     width = OFreinterpret_cast(DcmElement*, stack.top());
1401   }
1402   stack.clear();
1403   if ((dataset->search(DCM_WindowCenterWidthExplanation, stack, ESM_fromHere, OFFalse)).good())
1404   {
1405     explanation = OFreinterpret_cast(DcmElement*, stack.top());
1406   }
1407 
1408   OFString newCenter;
1409   OFString newWidth;
1410   OFString newExplanation;
1411   Float64 currentCenter = 0.0;
1412   Float64 currentWidth = 0.0;
1413   OFString currentExplanation;
1414   double tempCenter = 0.0;
1415   double tempWidth = 0.0;
1416   char buf[64];
1417 
1418   if (center && width)
1419   {
1420     size_t numWindows = center->getVM();
1421     // iterate over all defined VOI windows
1422     for (size_t i=0; i<numWindows; i++)
1423     {
1424       if (((center->getFloat64(currentCenter,OFstatic_cast(Uint32, i))).good()) && ((width->getFloat64(currentWidth,OFstatic_cast(Uint32, i))).good()))
1425       {
1426         // found one pair of values, adapt them to value range shifted pixel data
1427         tempCenter = (currentCenter+voiOffset)*voiFactor;
1428         tempWidth = currentWidth * voiFactor;
1429         // add this window to the attribute values that are later replacing old windows
1430         OFStandard::ftoa(buf, sizeof(buf), tempCenter, OFStandard::ftoa_uppercase, 0, 6);
1431         if (!newCenter.empty()) newCenter += "\\";
1432         newCenter += buf;
1433         OFStandard::ftoa(buf, sizeof(buf), tempWidth, OFStandard::ftoa_uppercase, 0, 6);
1434         if (!newWidth.empty()) newWidth += "\\";
1435         newWidth += buf;
1436 
1437         if (!newExplanation.empty()) newExplanation += "\\";
1438         if (explanation && ((explanation->getOFString(currentExplanation,OFstatic_cast(Uint32, i))).good()))
1439           newExplanation += currentExplanation;
1440           else
1441           {
1442             newExplanation += "center=";
1443             OFStandard::ftoa(buf, sizeof(buf), tempCenter, OFStandard::ftoa_uppercase, 0, 6);
1444             newExplanation += buf;
1445             newExplanation += "/width=";
1446             OFStandard::ftoa(buf, sizeof(buf), tempWidth, OFStandard::ftoa_uppercase, 0, 6);
1447             newExplanation += buf;
1448           }
1449       }
1450     }
1451   }
1452 
1453   // remove old windows
1454   delete dataset->remove(DCM_WindowCenter);
1455   delete dataset->remove(DCM_WindowWidth);
1456   delete dataset->remove(DCM_WindowCenterWidthExplanation);
1457 
1458   // and insert newly computed ones if necessary
1459   if (!newCenter.empty())
1460   {
1461     if (result.good()) result = dataset->putAndInsertString(DCM_WindowCenter, newCenter.c_str());
1462     if (result.good()) result = dataset->putAndInsertString(DCM_WindowWidth, newWidth.c_str());
1463     if (result.good()) result = dataset->putAndInsertString(DCM_WindowCenterWidthExplanation, newExplanation.c_str());
1464   }
1465 
1466   return result;
1467 }
1468 
1469 
togglePlanarConfiguration8(Uint8 * pixelData,const size_t numValues,const Uint16 samplesPerPixel,const Uint16 oldPlanarConfig)1470 OFCondition DJCodecEncoder::togglePlanarConfiguration8(
1471   Uint8 *pixelData,
1472   const size_t numValues,
1473   const Uint16 samplesPerPixel,
1474   const Uint16 oldPlanarConfig)
1475 {
1476   if (pixelData == NULL)
1477     return EC_IllegalParameter;
1478   // allocate target buffer
1479   Uint8* px8 = new Uint8[numValues];
1480   if (!px8)
1481     return EC_MemoryExhausted;
1482   size_t numPixels = numValues / samplesPerPixel;
1483   if (oldPlanarConfig == 1)   // change from "by plane" to "by pixel"
1484   {
1485     for (size_t n=0; n < numPixels; n++)
1486     {
1487         for (Uint16 s=0; s < samplesPerPixel; s++)
1488           px8[n*samplesPerPixel+s]   = pixelData[n+numPixels*s];
1489     }
1490   }
1491   else  //change from "by pixel" to "by plane"
1492   {
1493     for (size_t n=0; n < numPixels; n++)
1494     {
1495         for (Uint16 s=0; s < samplesPerPixel; s++)
1496           px8[n+numPixels*s]   = pixelData[n*samplesPerPixel+s];
1497     }
1498   }
1499   // copy filled buffer to pixel data and free memory
1500   memcpy(pixelData, px8, OFstatic_cast(size_t, numValues));
1501   delete[] px8;
1502   return EC_Normal;
1503 }
1504 
1505 
togglePlanarConfiguration16(Uint16 * pixelData,const size_t numValues,const Uint16 samplesPerPixel,const Uint16 oldPlanarConfig)1506 OFCondition DJCodecEncoder::togglePlanarConfiguration16(
1507   Uint16 *pixelData,
1508   const size_t numValues, //number of 16-bit components
1509   const Uint16 samplesPerPixel,
1510   const Uint16 oldPlanarConfig)
1511 {
1512   if (pixelData == NULL)
1513     return EC_IllegalParameter;
1514   // allocate target buffer
1515   Uint16* px16 = new Uint16[numValues];
1516   if (!px16)
1517     return EC_MemoryExhausted;
1518   size_t numPixels = numValues / samplesPerPixel;
1519   if (oldPlanarConfig == 1)   // change from "by plane" to "by pixel"
1520   {
1521     for (size_t n=0; n < numPixels; n++)
1522     {
1523         for (Uint16 s=0; s < samplesPerPixel; s++)
1524           px16[n*samplesPerPixel+s]   = pixelData[n+numPixels*s];
1525     }
1526   }
1527   else  //change from "by pixel" to "by plane"
1528   {
1529     for (size_t n=0; n < numPixels; n++)
1530     {
1531         for (Uint16 s=0; s < samplesPerPixel; s++)
1532           px16[n+numPixels*s]   = pixelData[n*samplesPerPixel+s];
1533     }
1534   }
1535   // copy filled buffer to pixel data and free memory
1536   memcpy(pixelData, px16, OFstatic_cast(size_t, numValues*2));
1537   delete[] px16;
1538   return EC_Normal;
1539 }
1540 
1541 
updatePlanarConfiguration(DcmItem * item,const Uint16 newPlanConf) const1542 OFCondition DJCodecEncoder::updatePlanarConfiguration(
1543   DcmItem *item,
1544   const Uint16 newPlanConf) const
1545 {
1546   if ( (item == NULL) || (newPlanConf) > 1)
1547     return EC_IllegalParameter;
1548   return item->putAndInsertUint16(DCM_PlanarConfiguration, newPlanConf);
1549 }
1550