Coverage Report

Created: 2026-06-30 07:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dcmtk/dcmjpls/libsrc/djcodecd.cc
Line
Count
Source
1
/*
2
 *
3
 *  Copyright (C) 2007-2025, 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:  dcmjpls
15
 *
16
 *  Author:  Martin Willkomm, Marco Eichelberg, Uli Schlachter
17
 *
18
 *  Purpose: codec classes for JPEG-LS decoders.
19
 *
20
 */
21
22
#include "dcmtk/config/osconfig.h"
23
#include "dcmtk/dcmjpls/djcodecd.h"
24
25
#include "dcmtk/ofstd/ofstream.h"    /* for ofstream */
26
#include "dcmtk/ofstd/ofcast.h"      /* for casts */
27
#include "dcmtk/ofstd/offile.h"      /* for class OFFile */
28
#include "dcmtk/ofstd/ofstd.h"       /* for class OFStandard */
29
#include "dcmtk/dcmdata/dcdatset.h"  /* for class DcmDataset */
30
#include "dcmtk/dcmdata/dcdeftag.h"  /* for tag constants */
31
#include "dcmtk/dcmdata/dcpixseq.h"  /* for class DcmPixelSequence */
32
#include "dcmtk/dcmdata/dcpxitem.h"  /* for class DcmPixelItem */
33
#include "dcmtk/dcmdata/dcvrpobw.h"  /* for class DcmPolymorphOBOW */
34
#include "dcmtk/dcmdata/dcswap.h"    /* for swapIfNecessary() */
35
#include "dcmtk/dcmdata/dcuid.h"     /* for dcmGenerateUniqueIdentifer()*/
36
#include "dcmtk/dcmjpls/djcparam.h"  /* for class DJLSCodecParameter */
37
#include "djerror.h"                 /* for private class DJLSError */
38
39
// JPEG-LS library (CharLS) includes
40
#include "intrface.h"
41
42
E_TransferSyntax DJLSLosslessDecoder::supportedTransferSyntax() const
43
0
{
44
0
  return EXS_JPEGLSLossless;
45
0
}
46
47
E_TransferSyntax DJLSNearLosslessDecoder::supportedTransferSyntax() const
48
0
{
49
0
  return EXS_JPEGLSLossy;
50
0
}
51
52
// --------------------------------------------------------------------------
53
54
DJLSDecoderBase::DJLSDecoderBase()
55
2
: DcmCodec()
56
2
{
57
2
}
58
59
60
DJLSDecoderBase::~DJLSDecoderBase()
61
2
{
62
2
}
63
64
65
OFBool DJLSDecoderBase::canChangeCoding(
66
    const E_TransferSyntax oldRepType,
67
    const E_TransferSyntax newRepType) const
68
0
{
69
  // this codec only handles conversion from JPEG-LS to uncompressed.
70
71
0
  DcmXfer newRep(newRepType);
72
0
  if (newRep.usesNativeFormat() && (oldRepType == supportedTransferSyntax()))
73
0
     return OFTrue;
74
75
0
  return OFFalse;
76
0
}
77
78
79
Uint16 DJLSDecoderBase::decodedBitsAllocated(
80
    Uint16 /* bitsAllocated */,
81
    Uint16 bitsStored) const
82
0
{
83
  // this codec does not support images with less than 2 bits per sample
84
0
  if (bitsStored < 2) return 0;
85
86
  // for images with 2..8 bits per sample, BitsAllocated will be 8
87
0
  if (bitsStored <= 8) return 8;
88
89
  // for images with 9..16 bits per sample, BitsAllocated will be 16
90
0
  if (bitsStored <= 16) return 16;
91
92
  // this codec does not support images with more than 16 bits per sample
93
0
  return 0;
94
0
}
95
96
97
OFCondition DJLSDecoderBase::decode(
98
    const DcmRepresentationParameter * /* fromRepParam */,
99
    DcmPixelSequence * pixSeq,
100
    DcmPolymorphOBOW& uncompressedPixelData,
101
    const DcmCodecParameter * cp,
102
    const DcmStack& objStack,
103
    OFBool& removeOldRep) const
104
0
{
105
106
  // this codec may modify the DICOM header such that the previous pixel
107
  // representation is not valid anymore, e.g. in the case of color images
108
  // where the planar configuration can change. Indicate this to the caller
109
  // to trigger removal.
110
0
  removeOldRep = OFTrue;
111
112
  // retrieve pointer to dataset from parameter stack
113
0
  DcmStack localStack(objStack);
114
0
  (void)localStack.pop();  // pop pixel data element from stack
115
0
  DcmObject *dobject = localStack.pop(); // this is the item in which the pixel data is located
116
0
  if ((!dobject)||((dobject->ident()!= EVR_dataset) && (dobject->ident()!= EVR_item))) return EC_InvalidTag;
117
0
  DcmItem *dataset = OFstatic_cast(DcmItem *, dobject);
118
0
  OFBool numberOfFramesPresent = OFFalse;
119
120
  // determine properties of uncompressed dataset
121
0
  Uint16 imageSamplesPerPixel = 0;
122
0
  if (dataset->findAndGetUint16(DCM_SamplesPerPixel, imageSamplesPerPixel).bad()) return EC_TagNotFound;
123
124
  // we only handle one or three samples per pixel
125
0
  if ((imageSamplesPerPixel != 3) && (imageSamplesPerPixel != 1)) return EC_InvalidTag;
126
127
0
  Uint16 imageRows = 0;
128
0
  if (dataset->findAndGetUint16(DCM_Rows, imageRows).bad()) return EC_TagNotFound;
129
0
  if (imageRows < 1) return EC_InvalidTag;
130
131
0
  Uint16 imageColumns = 0;
132
0
  if (dataset->findAndGetUint16(DCM_Columns, imageColumns).bad()) return EC_TagNotFound;
133
0
  if (imageColumns < 1) return EC_InvalidTag;
134
135
  // number of frames is an optional attribute - we don't mind if it isn't present.
136
0
  Sint32 imageFrames = 0;
137
0
  if (dataset->findAndGetSint32(DCM_NumberOfFrames, imageFrames).good()) numberOfFramesPresent = OFTrue;
138
139
0
  if (imageFrames >= OFstatic_cast(Sint32, pixSeq->card()))
140
0
    imageFrames = pixSeq->card() - 1; // limit number of frames to number of pixel items - 1
141
0
  if (imageFrames < 1)
142
0
    imageFrames = 1; // default in case the number of frames attribute is absent or contains garbage
143
144
0
  Uint16 imageBitsStored = 0;
145
0
  if (dataset->findAndGetUint16(DCM_BitsStored, imageBitsStored).bad()) return EC_TagNotFound;
146
147
0
  Uint16 imageBitsAllocated = 0;
148
0
  if (dataset->findAndGetUint16(DCM_BitsAllocated, imageBitsAllocated).bad()) return EC_TagNotFound;
149
150
0
  Uint16 imageHighBit = 0;
151
0
  if (dataset->findAndGetUint16(DCM_HighBit, imageHighBit).bad()) return EC_TagNotFound;
152
153
  //we only support up to 16 bits per sample
154
0
  if ((imageBitsStored < 1) || (imageBitsStored > 16)) return EC_JLSUnsupportedBitDepth;
155
156
  // determine the number of bytes per sample (bits allocated) for the de-compressed object.
157
0
  Uint16 bytesPerSample = 1;
158
0
  if (imageBitsStored > 8) bytesPerSample = 2;
159
0
  else if (imageBitsAllocated > 8) bytesPerSample = 2;
160
161
  // compute size of uncompressed frame, in bytes
162
0
  Uint32 frameSize = bytesPerSample * imageRows * imageColumns * imageSamplesPerPixel;
163
164
  // check for overflow
165
0
  if (imageRows != 0 && frameSize / imageRows != (OFstatic_cast(Uint32, bytesPerSample) * imageColumns * imageSamplesPerPixel))
166
0
  {
167
0
    DCMJPLS_WARN("cannot decompress image because uncompressed representation would exceed maximum possible size of PixelData attribute");
168
0
    return EC_ElemLengthExceeds32BitField;
169
0
  }
170
171
  // compute size of pixel data attribute, in bytes
172
0
  Uint32 totalSize = frameSize * imageFrames;
173
174
  // check for overflow
175
0
  if (totalSize == 0xFFFFFFFF || (frameSize != 0 && totalSize / frameSize != OFstatic_cast(Uint32, imageFrames)))
176
0
  {
177
0
    DCMJPLS_WARN("cannot decompress image because uncompressed representation would exceed maximum possible size of PixelData attribute");
178
0
    return EC_ElemLengthExceeds32BitField;
179
0
  }
180
181
0
  if (totalSize & 1) totalSize++; // align on 16-bit word boundary
182
183
  // assume we can cast the codec parameter to what we need
184
0
  const DJLSCodecParameter *djcp = OFreinterpret_cast(const DJLSCodecParameter *, cp);
185
186
  // determine planar configuration for uncompressed data
187
0
  OFString imageSopClass;
188
0
  OFString imagePhotometricInterpretation;
189
0
  dataset->findAndGetOFString(DCM_SOPClassUID, imageSopClass);
190
0
  dataset->findAndGetOFString(DCM_PhotometricInterpretation, imagePhotometricInterpretation);
191
192
  // allocate space for uncompressed pixel data element
193
0
  Uint16 *pixeldata16 = NULL;
194
0
  OFCondition result = uncompressedPixelData.createUint16Array(totalSize/sizeof(Uint16), pixeldata16);
195
0
  if (result.bad()) return result;
196
197
0
  Uint8 *pixeldata8 = OFreinterpret_cast(Uint8 *, pixeldata16);
198
0
  Sint32 currentFrame = 0;
199
0
  Uint32 currentItem = 1; // item 0 contains the offset table
200
0
  OFBool done = OFFalse;
201
0
  OFBool forceSingleFragmentPerFrame = djcp->getForceSingleFragmentPerFrame();
202
203
0
  while (result.good() && !done)
204
0
  {
205
0
      DCMJPLS_DEBUG("JPEG-LS decoder processes frame " << (currentFrame+1));
206
207
0
      result = decodeFrameNoSwap(pixSeq, djcp, dataset, currentFrame, currentItem, pixeldata8, frameSize,
208
0
          imageFrames, imageColumns, imageRows, imageSamplesPerPixel, bytesPerSample);
209
210
      // check if we should enforce "one fragment per frame" while
211
      // decompressing a multi-frame image even if stream suspension occurs
212
0
      if ((result == EC_JLSInvalidCompressedData) && forceSingleFragmentPerFrame)
213
0
      {
214
        // frame is incomplete. Nevertheless skip to next frame.
215
        // This permits decompression of faulty multi-frame images.
216
0
        DCMJPLS_WARN("JPEG-LS bitstream invalid or incomplete, ignoring (but image is likely to be incomplete)");
217
0
        result = EC_Normal;
218
0
      }
219
220
0
      if (result.good())
221
0
      {
222
        // increment frame number, check if we're finished
223
0
        if (++currentFrame == imageFrames) done = OFTrue;
224
0
        pixeldata8 += frameSize;
225
0
      }
226
0
  }
227
228
  // Number of Frames might have changed in case the previous value was wrong
229
0
  if (result.good() && (numberOfFramesPresent || (imageFrames > 1)))
230
0
  {
231
0
    char numBuf[20];
232
0
    OFStandard::snprintf(numBuf, sizeof(numBuf), "%ld", OFstatic_cast(long, imageFrames));
233
0
    result = ((DcmItem *)dataset)->putAndInsertString(DCM_NumberOfFrames, numBuf);
234
0
  }
235
236
0
  if (result.good())
237
0
  {
238
    // decompression is complete, finally adjust byte order if necessary
239
0
    if (bytesPerSample == 1) // we're writing bytes into words
240
0
    {
241
0
      result = swapIfNecessary(gLocalByteOrder, EBO_LittleEndian, pixeldata16, OFstatic_cast(Uint32, totalSize), sizeof(Uint16));
242
0
    }
243
0
  }
244
245
0
  if (result.good() && (dataset->ident() == EVR_dataset))
246
0
  {
247
0
    DcmItem *ditem = OFreinterpret_cast(DcmItem*, dataset);
248
249
    // the following operations do not affect the Image Pixel Module
250
    // but other modules such as SOP Common.  We only perform these
251
    // changes if we're on the main level of the dataset,
252
    // which should always identify itself as dataset, not as item.
253
0
    if (djcp->getUIDCreation() == EJLSUC_always)
254
0
    {
255
        // create new SOP instance UID
256
0
        result = DcmCodec::newInstance(ditem, NULL, NULL, NULL);
257
0
    }
258
259
    // set Lossy Image Compression to "01" (see DICOM part 3, C.7.6.1.1.5)
260
0
    if (result.good() && (supportedTransferSyntax() == EXS_JPEGLSLossy)) result = ditem->putAndInsertString(DCM_LossyImageCompression, "01");
261
262
0
  }
263
264
0
  return result;
265
0
}
266
267
268
OFCondition DJLSDecoderBase::decodeFrame(
269
    const DcmRepresentationParameter * /* fromParam */,
270
    DcmPixelSequence *fromPixSeq,
271
    const DcmCodecParameter *cp,
272
    DcmItem *dataset,
273
    Uint32 frameNo,
274
    Uint32& currentItem,
275
    void * buffer,
276
    Uint32 bufSize,
277
    OFString& decompressedColorModel) const
278
0
{
279
0
  OFCondition result = EC_Normal;
280
281
  // assume we can cast the codec parameter to what we need
282
0
  const DJLSCodecParameter *djcp = OFreinterpret_cast(const DJLSCodecParameter *, cp);
283
284
  // determine properties of uncompressed dataset
285
0
  Uint16 imageSamplesPerPixel = 0;
286
0
  if (dataset->findAndGetUint16(DCM_SamplesPerPixel, imageSamplesPerPixel).bad()) return EC_TagNotFound;
287
  // we only handle one or three samples per pixel
288
0
  if ((imageSamplesPerPixel != 3) && (imageSamplesPerPixel != 1)) return EC_InvalidTag;
289
290
0
  Uint16 imageRows = 0;
291
0
  if (dataset->findAndGetUint16(DCM_Rows, imageRows).bad()) return EC_TagNotFound;
292
0
  if (imageRows < 1) return EC_InvalidTag;
293
294
0
  Uint16 imageColumns = 0;
295
0
  if (dataset->findAndGetUint16(DCM_Columns, imageColumns).bad()) return EC_TagNotFound;
296
0
  if (imageColumns < 1) return EC_InvalidTag;
297
298
0
  Uint16 imageBitsStored = 0;
299
0
  if (dataset->findAndGetUint16(DCM_BitsStored, imageBitsStored).bad()) return EC_TagNotFound;
300
301
0
  Uint16 imageBitsAllocated = 0;
302
0
  if (dataset->findAndGetUint16(DCM_BitsAllocated, imageBitsAllocated).bad()) return EC_TagNotFound;
303
304
  //we only support up to 16 bits per sample
305
0
  if ((imageBitsStored < 1) || (imageBitsStored > 16)) return EC_JLSUnsupportedBitDepth;
306
307
  // determine the number of bytes per sample (bits allocated) for the de-compressed object.
308
0
  Uint16 bytesPerSample = 1;
309
0
  if (imageBitsStored > 8) bytesPerSample = 2;
310
0
  else if (imageBitsAllocated > 8) bytesPerSample = 2;
311
312
  // number of frames is an optional attribute - we don't mind if it isn't present.
313
0
  Sint32 imageFrames = 0;
314
0
  dataset->findAndGetSint32(DCM_NumberOfFrames, imageFrames).good();
315
316
0
  if (imageFrames >= OFstatic_cast(Sint32, fromPixSeq->card()))
317
0
    imageFrames = fromPixSeq->card() - 1; // limit number of frames to number of pixel items - 1
318
0
  if (imageFrames < 1)
319
0
    imageFrames = 1; // default in case the number of frames attribute is absent or contains garbage
320
321
  // if the user has provided this information, we trust him.
322
  // If the user has passed a zero, try to find out ourselves.
323
0
  if (currentItem == 0)
324
0
  {
325
0
    result = determineStartFragment(frameNo, imageFrames, fromPixSeq, currentItem);
326
0
  }
327
328
0
  if (result.good())
329
0
  {
330
    // We got all the data we need from the dataset, let's start decoding
331
0
    DCMJPLS_DEBUG("starting to decode frame " << frameNo << " with fragment " << currentItem);
332
0
    result = decodeFrame(fromPixSeq, djcp, dataset, frameNo, currentItem, buffer, bufSize,
333
0
        imageFrames, imageColumns, imageRows, imageSamplesPerPixel, bytesPerSample);
334
0
  }
335
336
0
  if (result.good())
337
0
  {
338
    // retrieve color model from given dataset
339
0
    result = dataset->findAndGetOFString(DCM_PhotometricInterpretation, decompressedColorModel);
340
0
  }
341
342
0
  return result;
343
0
}
344
345
OFCondition DJLSDecoderBase::decodeFrameNoSwap(
346
    DcmPixelSequence * fromPixSeq,
347
    const DJLSCodecParameter *cp,
348
    DcmItem *dataset,
349
    Uint32 frameNo,
350
    Uint32& currentItem,
351
    void * buffer,
352
    Uint32 bufSize,
353
    Sint32 imageFrames,
354
    Uint16 imageColumns,
355
    Uint16 imageRows,
356
    Uint16 imageSamplesPerPixel,
357
    Uint16 bytesPerSample)
358
0
{
359
0
  DcmPixelItem *pixItem = NULL;
360
0
  Uint8 * jlsData = NULL;
361
0
  Uint8 * jlsFragmentData = NULL;
362
0
  Uint32 fragmentLength = 0;
363
0
  size_t compressedSize = 0;
364
0
  Uint32 fragmentsForThisFrame = 0;
365
0
  OFCondition result = EC_Normal;
366
0
  OFBool ignoreOffsetTable = cp->ignoreOffsetTable();
367
368
  // compute the number of JPEG-LS fragments we need in order to decode the next frame
369
0
  fragmentsForThisFrame = computeNumberOfFragments(imageFrames, frameNo, currentItem, ignoreOffsetTable, fromPixSeq);
370
0
  if (fragmentsForThisFrame == 0) result = EC_JLSCannotComputeNumberOfFragments;
371
372
  // determine planar configuration for uncompressed data
373
0
  OFString imageSopClass;
374
0
  OFString imagePhotometricInterpretation;
375
0
  dataset->findAndGetOFString(DCM_SOPClassUID, imageSopClass);
376
0
  dataset->findAndGetOFString(DCM_PhotometricInterpretation, imagePhotometricInterpretation);
377
0
  Uint16 imagePlanarConfiguration = 0; // 0 is color-by-pixel, 1 is color-by-plane
378
379
0
  if (imageSamplesPerPixel > 1)
380
0
  {
381
    // get planar configuration from dataset
382
0
    imagePlanarConfiguration = 2; // invalid value
383
    // warn on invalid value; should we also warn on missing attribute or value?
384
0
    if (dataset->findAndGetUint16(DCM_PlanarConfiguration, imagePlanarConfiguration).good() && (imagePlanarConfiguration != 0))
385
0
      DCMJPLS_WARN("invalid value for PlanarConfiguration " << DCM_PlanarConfiguration << ", should be \"0\"");
386
387
0
    switch (cp->getPlanarConfiguration())
388
0
    {
389
0
      case EJLSPC_restore:
390
        // determine auto default if not found or invalid
391
0
        if (imagePlanarConfiguration > 1)
392
0
          imagePlanarConfiguration = determinePlanarConfiguration(imageSopClass, imagePhotometricInterpretation);
393
0
        break;
394
0
      case EJLSPC_auto:
395
0
        imagePlanarConfiguration = determinePlanarConfiguration(imageSopClass, imagePhotometricInterpretation);
396
0
        break;
397
0
      case EJLSPC_colorByPixel:
398
0
        imagePlanarConfiguration = 0;
399
0
        break;
400
0
      case EJLSPC_colorByPlane:
401
0
        imagePlanarConfiguration = 1;
402
0
        break;
403
0
    }
404
0
  }
405
406
  // get the size of all the fragments
407
0
  if (result.good())
408
0
  {
409
    // Don't modify the original values for now
410
0
    Uint32 fragmentsForThisFrame2 = fragmentsForThisFrame;
411
0
    Uint32 currentItem2 = currentItem;
412
413
0
    while (result.good() && fragmentsForThisFrame2--)
414
0
    {
415
0
      result = fromPixSeq->getItem(pixItem, currentItem2++);
416
0
      if (result.good() && pixItem)
417
0
      {
418
0
        fragmentLength = pixItem->getLength();
419
0
        if (result.good())
420
0
          compressedSize += fragmentLength;
421
0
      }
422
0
    } /* while */
423
0
  }
424
425
  // get the compressed data
426
0
  if (result.good())
427
0
  {
428
0
    Uint32 offset = 0;
429
0
    jlsData = new Uint8[compressedSize];
430
431
0
    while (result.good() && fragmentsForThisFrame--)
432
0
    {
433
0
      result = fromPixSeq->getItem(pixItem, currentItem++);
434
0
      if (result.good() && pixItem)
435
0
      {
436
0
        fragmentLength = pixItem->getLength();
437
0
        result = pixItem->getUint8Array(jlsFragmentData);
438
0
        if (result.good() && jlsFragmentData)
439
0
        {
440
0
          memcpy(&jlsData[offset], jlsFragmentData, fragmentLength);
441
0
          offset += fragmentLength;
442
0
        }
443
0
      }
444
0
    } /* while */
445
0
  }
446
447
0
  if (result.good())
448
0
  {
449
0
    JlsParameters params;
450
0
    JLS_ERROR err;
451
452
0
    err = JpegLsReadHeader(jlsData, compressedSize, &params);
453
0
    result = DJLSError::convert(err);
454
455
0
    if (result.good())
456
0
    {
457
0
      if (params.width != imageColumns) result = EC_JLSImageDataMismatch;
458
0
      else if (params.height != imageRows) result = EC_JLSImageDataMismatch;
459
0
      else if (params.components != imageSamplesPerPixel) result = EC_JLSImageDataMismatch;
460
0
      else if ((bytesPerSample == 1) && (params.bitspersample > 8)) result = EC_JLSImageDataMismatch;
461
0
      else if ((bytesPerSample == 2) && (params.bitspersample <= 8)) result = EC_JLSImageDataMismatch;
462
0
    }
463
464
0
    if (!result.good())
465
0
    {
466
0
      delete[] jlsData;
467
0
    }
468
0
    else
469
0
    {
470
0
      err = JpegLsDecode(buffer, bufSize, jlsData, compressedSize, &params);
471
0
      result = DJLSError::convert(err);
472
0
      delete[] jlsData;
473
474
0
      if (result.good() && imageSamplesPerPixel == 3)
475
0
      {
476
0
        if (params.colorTransform != 0)
477
0
        {
478
0
          DCMJPLS_WARN("Color Transformation " << params.colorTransform << " is a non-standard HP/JPEG-LS extension");
479
0
        }
480
0
        if (imagePlanarConfiguration == 1 && params.ilv != ILV_NONE)
481
0
        {
482
          // The dataset says this should be planarConfiguration == 1, but
483
          // it isn't -> convert it.
484
0
          DCMJPLS_DEBUG("different planar configuration in JPEG-LS bitstream, converting to \"1\"");
485
0
          if (bytesPerSample == 1)
486
0
            result = createPlanarConfiguration1Byte(OFreinterpret_cast(Uint8*, buffer), imageColumns, imageRows);
487
0
          else
488
0
            result = createPlanarConfiguration1Word(OFreinterpret_cast(Uint16*, buffer), imageColumns, imageRows);
489
0
        }
490
0
        else if (imagePlanarConfiguration == 0 && params.ilv != ILV_SAMPLE && params.ilv != ILV_LINE)
491
0
        {
492
          // The dataset says this should be planarConfiguration == 0, but
493
          // it isn't -> convert it.
494
0
          DCMJPLS_DEBUG("different planar configuration in JPEG-LS bitstream, converting to \"0\"");
495
0
          if (bytesPerSample == 1)
496
0
            result = createPlanarConfiguration0Byte(OFreinterpret_cast(Uint8*, buffer), imageColumns, imageRows);
497
0
          else
498
0
            result = createPlanarConfiguration0Word(OFreinterpret_cast(Uint16*, buffer), imageColumns, imageRows);
499
0
        }
500
0
      }
501
502
      // update planar configuration if we are decoding a color image
503
0
      if (result.good() && (imageSamplesPerPixel > 1))
504
0
      {
505
0
        dataset->putAndInsertUint16(DCM_PlanarConfiguration, imagePlanarConfiguration);
506
0
      }
507
0
    }
508
0
  }
509
510
0
  return result;
511
0
}
512
513
OFCondition DJLSDecoderBase::decodeFrame(
514
    DcmPixelSequence * fromPixSeq,
515
    const DJLSCodecParameter *cp,
516
    DcmItem *dataset,
517
    Uint32 frameNo,
518
    Uint32& currentItem,
519
    void * buffer,
520
    Uint32 bufSize,
521
    Sint32 imageFrames,
522
    Uint16 imageColumns,
523
    Uint16 imageRows,
524
    Uint16 imageSamplesPerPixel,
525
    Uint16 bytesPerSample)
526
0
{
527
0
    OFCondition result = decodeFrameNoSwap(fromPixSeq, cp, dataset, frameNo, currentItem, buffer, bufSize, imageFrames, imageColumns, imageRows, imageSamplesPerPixel, bytesPerSample);
528
0
    if (result.good())
529
0
    {
530
        // decompression is complete, finally adjust byte order if necessary
531
0
        if (bytesPerSample == 1) // we're writing bytes into words
532
0
        {
533
0
            if ((gLocalByteOrder == EBO_BigEndian) && (bufSize & 1))
534
0
            {
535
0
              DCMJPLS_WARN("Size of frame buffer is odd, cannot correct byte order for last pixel value");
536
0
            }
537
0
            result = swapIfNecessary(gLocalByteOrder, EBO_LittleEndian, buffer, bufSize, sizeof(Uint16));
538
0
        }
539
0
    }
540
0
    return result;
541
0
}
542
543
OFCondition DJLSDecoderBase::encode(
544
    const Uint16 * /* pixelData */,
545
    const Uint32 /* length */,
546
    const DcmRepresentationParameter * /* toRepParam */,
547
    DcmPixelSequence * & /* pixSeq */,
548
    const DcmCodecParameter * /* cp */,
549
    DcmStack & /* objStack */,
550
    OFBool& /* removeOldRep */) const
551
0
{
552
  // we are a decoder only
553
0
  return EC_IllegalCall;
554
0
}
555
556
557
OFCondition DJLSDecoderBase::encode(
558
    const E_TransferSyntax /* fromRepType */,
559
    const DcmRepresentationParameter * /* fromRepParam */,
560
    DcmPixelSequence * /* fromPixSeq */,
561
    const DcmRepresentationParameter * /* toRepParam */,
562
    DcmPixelSequence * & /* toPixSeq */,
563
    const DcmCodecParameter * /* cp */,
564
    DcmStack & /* objStack */,
565
    OFBool& /* removeOldRep */) const
566
0
{
567
  // we don't support re-coding for now.
568
0
  return EC_IllegalCall;
569
0
}
570
571
572
OFCondition DJLSDecoderBase::determineDecompressedColorModel(
573
    const DcmRepresentationParameter * /* fromParam */,
574
    DcmPixelSequence * /* fromPixSeq */,
575
    const DcmCodecParameter * /* cp */,
576
    DcmItem * dataset,
577
    OFString & decompressedColorModel) const
578
0
{
579
0
  OFCondition result = EC_IllegalParameter;
580
0
  if (dataset != NULL)
581
0
  {
582
    // retrieve color model from given dataset
583
0
    result = dataset->findAndGetOFString(DCM_PhotometricInterpretation, decompressedColorModel);
584
0
    if (result == EC_TagNotFound)
585
0
    {
586
0
        DCMJPLS_WARN("mandatory element PhotometricInterpretation " << DCM_PhotometricInterpretation << " is missing");
587
0
        result = EC_MissingAttribute;
588
0
    }
589
0
    else if (result.bad())
590
0
    {
591
0
        DCMJPLS_WARN("cannot retrieve value of element PhotometricInterpretation " << DCM_PhotometricInterpretation << ": " << result.text());
592
0
    }
593
0
    else if (decompressedColorModel.empty())
594
0
    {
595
0
        DCMJPLS_WARN("no value for mandatory element PhotometricInterpretation " << DCM_PhotometricInterpretation);
596
0
        result = EC_MissingValue;
597
0
    }
598
0
  }
599
0
  return result;
600
0
}
601
602
603
Uint16 DJLSDecoderBase::determinePlanarConfiguration(
604
  const OFString& sopClassUID,
605
  const OFString& photometricInterpretation)
606
0
{
607
  // Hardcopy Color Image always requires color-by-plane
608
0
  if (sopClassUID == UID_RETIRED_HardcopyColorImageStorage) return 1;
609
610
  // The 1996 Ultrasound Image IODs require color-by-plane if color model is YBR_FULL.
611
0
  if (photometricInterpretation == "YBR_FULL")
612
0
  {
613
0
    if ((sopClassUID == UID_UltrasoundMultiframeImageStorage)
614
0
       ||(sopClassUID == UID_UltrasoundImageStorage)) return 1;
615
0
  }
616
617
  // default for all other cases
618
0
  return 0;
619
0
}
620
621
Uint32 DJLSDecoderBase::computeNumberOfFragments(
622
  Sint32 numberOfFrames,
623
  Uint32 currentFrame,
624
  Uint32 startItem,
625
  OFBool ignoreOffsetTable,
626
  DcmPixelSequence * pixSeq)
627
0
{
628
629
0
  unsigned long numItems = pixSeq->card();
630
0
  DcmPixelItem *pixItem = NULL;
631
632
  // We first check the simple cases, that is, a single-frame image,
633
  // the last frame of a multi-frame image and the standard case where we do have
634
  // a single fragment per frame.
635
0
  if ((numberOfFrames <= 1) || (currentFrame + 1 == OFstatic_cast(Uint32, numberOfFrames)))
636
0
  {
637
    // single-frame image or last frame. All remaining fragments belong to this frame
638
0
    return (numItems - startItem);
639
0
  }
640
0
  if (OFstatic_cast(Uint32, numberOfFrames + 1) == numItems)
641
0
  {
642
    // multi-frame image with one fragment per frame
643
0
    return 1;
644
0
  }
645
646
0
  OFCondition result = EC_Normal;
647
0
  if (! ignoreOffsetTable)
648
0
  {
649
    // We do have a multi-frame image with multiple fragments per frame, and we are
650
    // not working on the last frame. Let's check the offset table if present.
651
0
    result = pixSeq->getItem(pixItem, 0);
652
0
    if (result.good() && pixItem)
653
0
    {
654
0
      Uint32 offsetTableLength = pixItem->getLength();
655
0
      if (offsetTableLength == (OFstatic_cast(Uint32, numberOfFrames) * 4))
656
0
      {
657
        // offset table is non-empty and contains one entry per frame
658
0
        Uint8 *offsetData = NULL;
659
0
        result = pixItem->getUint8Array(offsetData);
660
0
        if (result.good() && offsetData)
661
0
        {
662
          // now we can access the offset table
663
0
          Uint32 *offsetData32 = OFreinterpret_cast(Uint32 *, offsetData);
664
665
          // extract the offset for the NEXT frame. This offset is guaranteed to exist
666
          // because the "last frame/single frame" case is handled above.
667
0
          Uint32 offset = offsetData32[currentFrame+1];
668
669
          // convert to local endian byte order (always little endian in file)
670
0
          swapIfNecessary(gLocalByteOrder, EBO_LittleEndian, &offset, sizeof(Uint32), sizeof(Uint32));
671
672
          // determine index of start fragment for next frame
673
0
          Uint32 byteCount = 0;
674
0
          Uint32 fragmentIndex = 1;
675
0
          while ((byteCount < offset) && (fragmentIndex < numItems))
676
0
          {
677
0
             pixItem = NULL;
678
0
             result = pixSeq->getItem(pixItem, fragmentIndex++);
679
0
             if (result.good() && pixItem)
680
0
             {
681
0
               byteCount += pixItem->getLength() + 8; // add 8 bytes for item tag and length
682
0
               if ((byteCount == offset) && (fragmentIndex > startItem))
683
0
               {
684
                 // bingo, we have found the offset for the next frame
685
0
                 return fragmentIndex - startItem;
686
0
               }
687
0
             }
688
0
             else break; /* something went wrong, break out of while loop */
689
0
          } /* while */
690
0
        }
691
0
      }
692
0
    }
693
0
  }
694
695
  // So we have a multi-frame image with multiple fragments per frame and the
696
  // offset table is empty or wrong. Our last chance is to peek into the JPEG-LS
697
  // bitstream and identify the start of the next frame.
698
0
  Uint32 nextItem = startItem;
699
0
  Uint8 *fragmentData = NULL;
700
0
  while (++nextItem < numItems)
701
0
  {
702
0
    pixItem = NULL;
703
0
    result = pixSeq->getItem(pixItem, nextItem);
704
0
    if (result.good() && pixItem)
705
0
    {
706
0
        fragmentData = NULL;
707
0
        result = pixItem->getUint8Array(fragmentData);
708
0
        if (result.good() && fragmentData && (pixItem->getLength() > 3))
709
0
        {
710
0
          if (isJPEGLSStartOfImage(fragmentData))
711
0
          {
712
            // found a JPEG-LS SOI marker. Assume that this is the start of the next frame.
713
0
            return (nextItem - startItem);
714
0
          }
715
0
        }
716
0
        else break; /* something went wrong, break out of while loop */
717
0
    }
718
0
    else break; /* something went wrong, break out of while loop */
719
0
  }
720
721
  // We're bust. No way to determine the number of fragments per frame.
722
0
  return 0;
723
0
}
724
725
726
OFBool DJLSDecoderBase::isJPEGLSStartOfImage(Uint8 *fragmentData)
727
0
{
728
  // A valid JPEG-LS bitstream will always start with an SOI marker FFD8, followed
729
  // by either an SOF55 (FFF7), COM (FFFE) or APPn (FFE0-FFEF) marker.
730
0
  if ((*fragmentData++) != 0xFF) return OFFalse;
731
0
  if ((*fragmentData++) != 0xD8) return OFFalse;
732
0
  if ((*fragmentData++) != 0xFF) return OFFalse;
733
0
  if ((*fragmentData == 0xF7)||(*fragmentData == 0xFE)||((*fragmentData & 0xF0) == 0xE0))
734
0
  {
735
0
    return OFTrue;
736
0
  }
737
0
  return OFFalse;
738
0
}
739
740
741
OFCondition DJLSDecoderBase::createPlanarConfiguration1Byte(
742
  Uint8 *imageFrame,
743
  Uint16 columns,
744
  Uint16 rows)
745
0
{
746
0
  if (imageFrame == NULL) return EC_IllegalCall;
747
748
0
  unsigned long numPixels = columns * rows;
749
0
  if (numPixels == 0) return EC_IllegalCall;
750
751
0
  Uint8 *buf = new Uint8[3*numPixels + 3];
752
0
  if (buf)
753
0
  {
754
0
    memcpy(buf, imageFrame, (size_t)(3*numPixels));
755
0
    Uint8 *s = buf;                        // source
756
0
    Uint8 *r = imageFrame;                 // red plane
757
0
    Uint8 *g = imageFrame + numPixels;     // green plane
758
0
    Uint8 *b = imageFrame + (2*numPixels); // blue plane
759
0
    for (unsigned long i=numPixels; i; i--)
760
0
    {
761
0
      *r++ = *s++;
762
0
      *g++ = *s++;
763
0
      *b++ = *s++;
764
0
    }
765
0
    delete[] buf;
766
0
  } else return EC_MemoryExhausted;
767
0
  return EC_Normal;
768
0
}
769
770
771
OFCondition DJLSDecoderBase::createPlanarConfiguration1Word(
772
  Uint16 *imageFrame,
773
  Uint16 columns,
774
  Uint16 rows)
775
0
{
776
0
  if (imageFrame == NULL) return EC_IllegalCall;
777
778
0
  unsigned long numPixels = columns * rows;
779
0
  if (numPixels == 0) return EC_IllegalCall;
780
781
0
  Uint16 *buf = new Uint16[3*numPixels + 3];
782
0
  if (buf)
783
0
  {
784
0
    memcpy(buf, imageFrame, (size_t)(3*numPixels*sizeof(Uint16)));
785
0
    Uint16 *s = buf;                        // source
786
0
    Uint16 *r = imageFrame;                 // red plane
787
0
    Uint16 *g = imageFrame + numPixels;     // green plane
788
0
    Uint16 *b = imageFrame + (2*numPixels); // blue plane
789
0
    for (unsigned long i=numPixels; i; i--)
790
0
    {
791
0
      *r++ = *s++;
792
0
      *g++ = *s++;
793
0
      *b++ = *s++;
794
0
    }
795
0
    delete[] buf;
796
0
  } else return EC_MemoryExhausted;
797
0
  return EC_Normal;
798
0
}
799
800
OFCondition DJLSDecoderBase::createPlanarConfiguration0Byte(
801
  Uint8 *imageFrame,
802
  Uint16 columns,
803
  Uint16 rows)
804
0
{
805
0
  if (imageFrame == NULL) return EC_IllegalCall;
806
807
0
  unsigned long numPixels = columns * rows;
808
0
  if (numPixels == 0) return EC_IllegalCall;
809
810
0
  Uint8 *buf = new Uint8[3*numPixels + 3];
811
0
  if (buf)
812
0
  {
813
0
    memcpy(buf, imageFrame, (size_t)(3*numPixels));
814
0
    Uint8 *t = imageFrame;          // target
815
0
    Uint8 *r = buf;                 // red plane
816
0
    Uint8 *g = buf + numPixels;     // green plane
817
0
    Uint8 *b = buf + (2*numPixels); // blue plane
818
0
    for (unsigned long i=numPixels; i; i--)
819
0
    {
820
0
      *t++ = *r++;
821
0
      *t++ = *g++;
822
0
      *t++ = *b++;
823
0
    }
824
0
    delete[] buf;
825
0
  } else return EC_MemoryExhausted;
826
0
  return EC_Normal;
827
0
}
828
829
830
OFCondition DJLSDecoderBase::createPlanarConfiguration0Word(
831
  Uint16 *imageFrame,
832
  Uint16 columns,
833
  Uint16 rows)
834
0
{
835
0
  if (imageFrame == NULL) return EC_IllegalCall;
836
837
0
  unsigned long numPixels = columns * rows;
838
0
  if (numPixels == 0) return EC_IllegalCall;
839
840
0
  Uint16 *buf = new Uint16[3*numPixels + 3];
841
0
  if (buf)
842
0
  {
843
0
    memcpy(buf, imageFrame, (size_t)(3*numPixels*sizeof(Uint16)));
844
0
    Uint16 *t = imageFrame;          // target
845
0
    Uint16 *r = buf;                 // red plane
846
0
    Uint16 *g = buf + numPixels;     // green plane
847
0
    Uint16 *b = buf + (2*numPixels); // blue plane
848
0
    for (unsigned long i=numPixels; i; i--)
849
0
    {
850
0
      *t++ = *r++;
851
0
      *t++ = *g++;
852
0
      *t++ = *b++;
853
0
    }
854
0
    delete[] buf;
855
0
  } else return EC_MemoryExhausted;
856
0
  return EC_Normal;
857
0
}