/*
 *
 *  Copyright (C) 2002-2025, OFFIS e.V.
 *  All rights reserved.  See COPYRIGHT file for details.
 *
 *  This software and supporting documentation were developed by
 *
 *    OFFIS e.V.
 *    R&D Division Health
 *    Escherweg 2
 *    D-26121 Oldenburg, Germany
 *
 *
 *  Module:  dcmdata
 *
 *  Author:  Marco Eichelberg
 *
 *  Purpose: decoder codec class for RLE
 *
 */

#include "dcmtk/config/osconfig.h"
#include "dcmtk/dcmdata/dcrleccd.h"
#include "dcmtk/ofstd/ofstd.h"

// dcmdata includes
#include "dcmtk/dcmdata/dcrlecp.h"   /* for class DcmRLECodecParameter */
#include "dcmtk/dcmdata/dcrledec.h"  /* for class DcmRLEDecoder */
#include "dcmtk/dcmdata/dcdatset.h"  /* for class DcmDataset */
#include "dcmtk/dcmdata/dcdeftag.h"  /* for tag constants */
#include "dcmtk/dcmdata/dcpixseq.h"  /* for class DcmPixelSequence */
#include "dcmtk/dcmdata/dcpxitem.h"  /* for class DcmPixelItem */
#include "dcmtk/dcmdata/dcvrpobw.h"  /* for class DcmPolymorphOBOW */
#include "dcmtk/dcmdata/dcswap.h"    /* for swapIfNecessary() */
#include "dcmtk/dcmdata/dcuid.h"     /* for dcmGenerateUniqueIdentifer()*/


DcmRLECodecDecoder::DcmRLECodecDecoder()
: DcmCodec()
{
}


DcmRLECodecDecoder::~DcmRLECodecDecoder()
{
}


OFBool DcmRLECodecDecoder::canChangeCoding(
    const E_TransferSyntax oldRepType,
    const E_TransferSyntax newRepType) const
{
  E_TransferSyntax myXfer = EXS_RLELossless;
  DcmXfer newRep(newRepType);
  if (newRep.usesNativeFormat() && (oldRepType == myXfer))
    return OFTrue; // decompress requested

  // we don't support re-coding for now.
  return OFFalse;
}


Uint16 DcmRLECodecDecoder::decodedBitsAllocated(
    Uint16 bitsAllocated,
    Uint16 /* bitsStored */) const
{
    // The RLE decoder only supports images where BitsAllocated is a multiple of 8.
    if ((bitsAllocated < 8)||(bitsAllocated % 8 != 0)) return 0;

    // RLE cannot support more than 120 bits/sample (15 bands) in DICOM
    if (bitsAllocated > 120) return 0;

    return bitsAllocated;
}


OFCondition DcmRLECodecDecoder::decode(
    const DcmRepresentationParameter * /* fromRepParam */,
    DcmPixelSequence * pixSeq,
    DcmPolymorphOBOW& uncompressedPixelData,
    const DcmCodecParameter * cp,
    const DcmStack& objStack,
    OFBool& /* removeOldRep */) const
{
  OFCondition result = EC_Normal;

  // assume we can cast the codec parameter to what we need
  const DcmRLECodecParameter *djcp = OFstatic_cast(const DcmRLECodecParameter *, cp);

  OFBool enableReverseByteOrder = djcp->getReverseDecompressionByteOrder();

  DcmStack localStack(objStack);
  (void)localStack.pop();             // pop pixel data element from stack
  DcmObject *dataset = localStack.pop(); // this is the item in which the pixel data is located
  if ((!dataset)||((dataset->ident()!= EVR_dataset) && (dataset->ident()!= EVR_item))) result = EC_InvalidTag;
  else
  {
    Uint16 imageSamplesPerPixel = 0;
    Uint16 imageRows = 0;
    Uint16 imageColumns = 0;
    Sint32 imageFrames = 1;
    Uint16 imageBitsAllocated = 0;
    Uint16 imageBytesAllocated = 0;
    Uint16 imagePlanarConfiguration = 0;
    Uint32 rleHeader[16];
    DcmItem *ditem = OFstatic_cast(DcmItem *, dataset);
    OFBool numberOfFramesPresent = OFFalse;

    if (result.good()) result = ditem->findAndGetUint16(DCM_SamplesPerPixel, imageSamplesPerPixel);
    if (result.good()) result = ditem->findAndGetUint16(DCM_Rows, imageRows);
    if (result.good()) result = ditem->findAndGetUint16(DCM_Columns, imageColumns);
    if (result.good()) result = ditem->findAndGetUint16(DCM_BitsAllocated, imageBitsAllocated);
    if (result.good())
    {
      imageBytesAllocated = OFstatic_cast(Uint16, imageBitsAllocated / 8);
      if ((imageBitsAllocated < 8)||(imageBitsAllocated % 8 != 0))
      {
        DCMDATA_ERROR("The RLE decoder only supports images where BitsAllocated is a multiple of 8.");
        result = EC_CannotChangeRepresentation;
      }
    }
    if (result.good() && (imageSamplesPerPixel > 1))
    {
      result = ditem->findAndGetUint16(DCM_PlanarConfiguration, imagePlanarConfiguration);
    }

    // number of frames is an optional attribute - we don't mind if it isn't present.
    if (result.good())
    {
      if (ditem->findAndGetSint32(DCM_NumberOfFrames, imageFrames).good()) numberOfFramesPresent = OFTrue;
    }

    if (imageFrames >= OFstatic_cast(Sint32, pixSeq->card()))
      imageFrames = OFstatic_cast(Sint32, pixSeq->card()) - 1; // limit number of frames to number of pixel items - 1
    if (imageFrames < 1)
      imageFrames = 1; // default in case the number of frames attribute is absent or contains garbage

    if (result.good())
    {
      DcmPixelItem *pixItem = NULL;
      Uint8 * rleData = NULL;
      const size_t bytesPerStripe = OFstatic_cast(size_t, imageColumns) * OFstatic_cast(size_t, imageRows);

      DcmRLEDecoder rledecoder(bytesPerStripe);
      if (rledecoder.fail()) result = EC_MemoryExhausted;  // RLE decoder failed to initialize
      else
      {
        // compute size of uncompressed frame, in bytes
        Uint32 frameSize = imageBytesAllocated * imageRows * imageColumns * imageSamplesPerPixel;

        // check for overflow
        if (imageRows != 0 && frameSize / imageRows != OFstatic_cast(Uint32, imageBytesAllocated) * imageColumns * imageSamplesPerPixel)
        {
          DCMDATA_WARN("DcmRLECodecDecoder: Cannot decompress image because uncompressed representation would exceed maximum possible size of PixelData attribute");
          return EC_ElemLengthExceeds32BitField;
        }

        Uint32 totalSize = frameSize * imageFrames;

        // check for overflow
        if (totalSize == 0xFFFFFFFF || (frameSize != 0 && totalSize / frameSize != OFstatic_cast(Uint32, imageFrames)))
        {
          DCMDATA_WARN("DcmRLECodecDecoder: Cannot decompress image because uncompressed representation would exceed maximum possible size of PixelData attribute");
          return EC_ElemLengthExceeds32BitField;
        }

        if (totalSize & 1) totalSize++; // align on 16-bit word boundary
        Uint16 *imageData16 = NULL;
        Sint32 currentFrame = 0;
        Uint32 currentItem = 1; // ignore offset table
        Uint32 numberOfStripes = 0;
        Uint32 fragmentLength = 0;

        result = uncompressedPixelData.createUint16Array(totalSize/sizeof(Uint16), imageData16);
        if (result.good())
        {
          Uint8 *imageData8 = OFreinterpret_cast(Uint8 *, imageData16);

          while ((currentFrame < imageFrames) && result.good())
          {
            DCMDATA_DEBUG("RLE decoder processes frame " << currentFrame);
            DCMDATA_DEBUG("RLE decoder processes pixel item " << currentItem);
            // get first pixel item of this frame
            result = pixSeq->getItem(pixItem, currentItem++);
            if (result.good())
            {
              fragmentLength = pixItem->getLength();
              result = pixItem->getUint8Array(rleData);
              if (result.good())
              {
                // we require that the RLE header must be completely
                // contained in the first fragment; otherwise bail out
                if (fragmentLength < 64)
                {
                  DCMDATA_ERROR("Pixel item shorter than 64 bytes, RLE header incomplete.");
                  result = EC_CannotChangeRepresentation;
                }
              }
            }

            if (result.good())
            {
              // copy RLE header to buffer and adjust byte order
              memcpy(rleHeader, rleData, 64);
              swapIfNecessary(gLocalByteOrder, EBO_LittleEndian, rleHeader, 16*OFstatic_cast(Uint32, sizeof(Uint32)), sizeof(Uint32));

              // determine number of stripes.
              numberOfStripes = rleHeader[0];

              // check that number of stripes in RLE header matches our expectation
              if ((numberOfStripes < 1) || (numberOfStripes > 15) ||
                  (numberOfStripes != OFstatic_cast(Uint32, imageBytesAllocated) * imageSamplesPerPixel))
              {
                  DCMDATA_ERROR("Number of stripes in RLE header incorrect: found " << numberOfStripes << ", expected " << (OFstatic_cast(Uint32, imageBytesAllocated) * imageSamplesPerPixel));
                  result = EC_CannotChangeRepresentation;
              }
            }

            if (result.good())
            {
              // this variable keeps the number of bytes we have processed
              // for the current frame in earlier pixel fragments
              Uint32 fragmentOffset = 0;

              // this variable keeps the current position within the current fragment
              Uint32 byteOffset = 0;

              OFBool lastStripe = OFFalse;
              OFBool lastStripeOfColor = OFFalse;
              Uint32 inputBytes = 0;

              // pointers for buffer copy operations
              Uint8 *outputBuffer = NULL;
              Uint8 *pixelPointer = NULL;

              // byte offset for first sample in frame
              Uint32 sampleOffset = 0;

              // byte offset between samples
              Uint32 offsetBetweenSamples = 0;

              // temporary variables
              Uint32 sample = 0;
              Uint32 byte = 0;
              Uint32 pixel = 0;

              // for each stripe in stripe set
              for (Uint32 stripeIndex = 0; (stripeIndex < numberOfStripes) && result.good(); ++stripeIndex)
              {
                // reset RLE codec
                rledecoder.clear();

                // adjust start point for RLE stripe, ignoring trailing garbage from the last run
                byteOffset = rleHeader[stripeIndex + 1];
                if (byteOffset < fragmentOffset)
                {
                    DCMDATA_ERROR("Byte offset in RLE header is wrong.");
                    result = EC_CannotChangeRepresentation;
                }
                else
                {
                  byteOffset -= fragmentOffset; // now byteOffset is correct but may point to next fragment
                  while ((byteOffset > fragmentLength) && result.good())
                  {
                    DCMDATA_DEBUG("RLE decoder processes pixel item " << currentItem);
                    result = pixSeq->getItem(pixItem, currentItem++);
                    if (result.good())
                    {
                      byteOffset -= fragmentLength;
                      fragmentOffset += fragmentLength;
                      fragmentLength = pixItem->getLength();
                      result = pixItem->getUint8Array(rleData);
                      if (result.bad())
                      {
                        DCMDATA_ERROR("Cannot access pixel fragment.");
                      }
                    }
                    else
                    {
                      DCMDATA_ERROR("Cannot access pixel fragment.");
                    }
                  }
                }

                // something went wrong; most likely the byte offset in the RLE header is incorrect.
                if (result.bad()) return EC_CannotChangeRepresentation;

                // byteOffset now points to the first byte of the new RLE stripe
                // check if the current stripe is the last one for this frame
                if (stripeIndex + 1 == numberOfStripes) lastStripe = OFTrue; else lastStripe = OFFalse;

                if (lastStripe)
                {
                  // the last stripe needs special handling because we cannot use the
                  // offset table to determine the number of bytes to feed to the codec
                  // if the RLE data is split in multiple fragments. We need to feed
                  // data fragment by fragment until the RLE codec has produced
                  // sufficient output.
                  while ((rledecoder.size() < bytesPerStripe) && result.good())
                  {
                    // feed complete remaining content of fragment to RLE codec and
                    // switch to next fragment
                    result = rledecoder.decompress(rleData + byteOffset, OFstatic_cast(size_t, fragmentLength - byteOffset));

                    // special handling for zero pad byte at the end of the RLE stream
                    // which results in an EC_StreamNotifyClient return code
                    // or trailing garbage data which results in EC_CorruptedData
                    if (rledecoder.size() == bytesPerStripe) result = EC_Normal;

                    // Check if we're already done. If yes, don't change fragment
                    if (result.good() || result == EC_StreamNotifyClient)
                    {
                      if (rledecoder.size() < bytesPerStripe)
                      {
                        DCMDATA_WARN("DcmRLECodecDecoder: Detected fragmented RLE compressed pixel data, which is not allowed in DICOM");
                        DCMDATA_DEBUG("RLE decoder processes pixel item " << currentItem);
                        result = pixSeq->getItem(pixItem, currentItem++);
                        if (result.good())
                        {
                          byteOffset = 0;
                          fragmentOffset += fragmentLength;
                          fragmentLength = pixItem->getLength();
                          result = pixItem->getUint8Array(rleData);
                        }
                      }
                      else byteOffset = fragmentLength;
                    }
                  } /* while */
                }
                else
                {
                  // not the last stripe. We can use the offset table to determine
                  // the number of bytes to feed to the RLE codec.
                  inputBytes = rleHeader[stripeIndex+2];
                  if (inputBytes < rleHeader[stripeIndex + 1])
                  {
                      DCMDATA_ERROR("Byte offset in RLE header is wrong.");
                      result = EC_CannotChangeRepresentation;
                  }
                  else
                  {
                    inputBytes -= rleHeader[stripeIndex + 1]; // number of bytes to feed to codec
                    while ((inputBytes > (fragmentLength - byteOffset)) && result.good())
                    {
                      // feed complete remaining content of fragment to RLE codec and
                      // switch to next fragment
                      result = rledecoder.decompress(rleData + byteOffset, OFstatic_cast(size_t, fragmentLength - byteOffset));

                      if (result.good() || result == EC_StreamNotifyClient)
                      {
                        DCMDATA_WARN("DcmRLECodecDecoder: Detected fragmented RLE compressed pixel data, which is not allowed in DICOM");
                        DCMDATA_DEBUG("RLE decoder processes pixel item " << currentItem);
                        result = pixSeq->getItem(pixItem, currentItem++);
                      }
                      if (result.good())
                      {
                        inputBytes -= fragmentLength - byteOffset;
                        byteOffset = 0;
                        fragmentOffset += fragmentLength;
                        fragmentLength = pixItem->getLength();
                        result = pixItem->getUint8Array(rleData);
                      }
                    } /* while */

                    // last fragment for this RLE stripe
                    if (inputBytes + byteOffset > fragmentLength)
                    {
                        DCMDATA_WARN("DcmRLECodecDecoder: Stream size in RLE header is wrong, adjusting from " << inputBytes << " to " << fragmentLength-byteOffset << " bytes");
                        inputBytes = fragmentLength-byteOffset;
                    }

                    result = rledecoder.decompress(rleData + byteOffset, OFstatic_cast(size_t, inputBytes));

                    // special handling for zero pad byte at the end of the RLE stream
                    // which results in an EC_StreamNotifyClient return code
                    // or trailing garbage data which results in EC_CorruptedData
                    if (rledecoder.size() == bytesPerStripe) result = EC_Normal;

                    byteOffset += inputBytes;
                  }
                }

                // copy the decoded stuff over to the buffer here...
                // make sure the RLE decoder has produced the right amount of data
                lastStripeOfColor = lastStripe || ((imagePlanarConfiguration == 1) && ((stripeIndex + 1) % imageBytesAllocated == 0));

                if (lastStripeOfColor && (rledecoder.size() < bytesPerStripe))
                {
                    // stripe ended prematurely? report a warning and continue
                    DCMDATA_WARN("DcmRLECodecDecoder: RLE decoder is finished but has produced insufficient data for this stripe, filling remaining pixels");
                    result = EC_Normal;
                }
                else if (rledecoder.size() != bytesPerStripe)
                {
                    DCMDATA_ERROR("RLE decoder is finished but has produced insufficient data for this stripe");
                    result = EC_CannotChangeRepresentation;
                }

                // distribute decompressed bytes into output image array
                if (result.good())
                {
                  // which sample and byte are we currently compressing?
                  sample = stripeIndex / imageBytesAllocated;
                  byte = stripeIndex % imageBytesAllocated;

                  // raw buffer containing bytesPerStripe bytes of uncompressed data
                  outputBuffer = OFstatic_cast(Uint8 *, rledecoder.getOutputBuffer());

                  // compute byte offsets
                  if (imagePlanarConfiguration == 0)
                  {
                     sampleOffset = sample * imageBytesAllocated;
                     offsetBetweenSamples = imageSamplesPerPixel * imageBytesAllocated;
                  }
                  else
                  {
                     sampleOffset = sample * imageBytesAllocated * imageColumns * imageRows;
                     offsetBetweenSamples = imageBytesAllocated;
                  }

                  // initialize pointer to output data
                  if (enableReverseByteOrder)
                  {
                    // assume incorrect LSB to MSB order of RLE segments as produced by some tools
                    pixelPointer = imageData8 + sampleOffset + byte;
                  }
                  else
                  {
                    pixelPointer = imageData8 + sampleOffset + imageBytesAllocated - byte - 1;
                  }

                  // loop through all pixels of the frame
                  for (pixel = 0; pixel < bytesPerStripe; ++pixel)
                  {
                    *pixelPointer = *outputBuffer++;
                    pixelPointer += offsetBetweenSamples;
                  }
                }
              } /* for */
            }

            // advance by one frame
            if (result.good())
            {
              currentFrame++;
              imageData8 += frameSize;
            }

          } /* while still frames to process */

          // adjust byte order for uncompressed image to little endian
          swapIfNecessary(EBO_LittleEndian, gLocalByteOrder, imageData16, OFstatic_cast(Uint32, totalSize), sizeof(Uint16));

          // Number of Frames might have changed in case the previous value was wrong
          if (result.good() && (numberOfFramesPresent || (imageFrames > 1)))
          {
            char numBuf[20];
            OFStandard::snprintf(numBuf, sizeof(numBuf), "%ld", OFstatic_cast(long, imageFrames));
            result = OFstatic_cast(DcmItem *, dataset)->putAndInsertString(DCM_NumberOfFrames, numBuf);
          }
        }
      }
    }

    // the following operations do not affect the Image Pixel Module
    // but other modules such as SOP Common.  We only perform these
    // changes if we're on the main level of the dataset,
    // which should always identify itself as dataset, not as item.
    if (dataset->ident() == EVR_dataset)
    {
        // create new SOP instance UID if codec parameters require so
        if (result.good() && djcp->getUIDCreation()) result =
          DcmCodec::newInstance(OFstatic_cast(DcmItem *, dataset), NULL, NULL, NULL);
    }
  }
  return result;
}


OFCondition DcmRLECodecDecoder::decodeFrame(
    const DcmRepresentationParameter * /* fromParam */,
    DcmPixelSequence * fromPixSeq,
    const DcmCodecParameter * cp,
    DcmItem *dataset,
    Uint32 frameNo,
    Uint32& startFragment,
    void *buffer,
    Uint32 bufSize,
    OFString& decompressedColorModel) const
{
    OFCondition result = EC_Normal;

    // assume we can cast the codec parameter to what we need
    const DcmRLECodecParameter *djcp = OFstatic_cast(const DcmRLECodecParameter *, cp);

    OFBool enableReverseByteOrder = djcp->getReverseDecompressionByteOrder();

    if ((!dataset)||((dataset->ident()!= EVR_dataset) && (dataset->ident()!= EVR_item))) return EC_InvalidTag;

    Uint16 imageSamplesPerPixel = 0;
    Uint16 imageRows = 0;
    Uint16 imageColumns = 0;
    Sint32 imageFrames = 1;
    Uint16 imageBitsAllocated = 0;
    Uint32 imageBytesAllocated = 0;
    Uint16 imagePlanarConfiguration = 0;
    Uint32 rleHeader[16];
    OFString photometricInterpretation;
    DcmItem *ditem = OFstatic_cast(DcmItem *, dataset);

    if (result.good()) result = ditem->findAndGetUint16(DCM_SamplesPerPixel, imageSamplesPerPixel);
    if (result.good()) result = ditem->findAndGetUint16(DCM_Rows, imageRows);
    if (result.good()) result = ditem->findAndGetUint16(DCM_Columns, imageColumns);
    if (result.good()) result = ditem->findAndGetUint16(DCM_BitsAllocated, imageBitsAllocated);
    if (result.good()) result = dataset->findAndGetOFString(DCM_PhotometricInterpretation, photometricInterpretation);
    if (result.good())
    {
        imageBytesAllocated = OFstatic_cast(Uint32, imageBitsAllocated / 8);
        if ((imageBitsAllocated < 8)||(imageBitsAllocated % 8 != 0))
        {
          DCMDATA_ERROR("The RLE decoder only supports images where BitsAllocated is a multiple of 8.");
          return EC_CannotChangeRepresentation;
        }
    }
    if (result.good() && (imageSamplesPerPixel > 1))
    {
        result = ditem->findAndGetUint16(DCM_PlanarConfiguration, imagePlanarConfiguration);
    }

    // number of frames is an optional attribute - we don't mind if it isn't present.
    if (result.good()) (void) ditem->findAndGetSint32(DCM_NumberOfFrames, imageFrames);
    if (imageFrames < 1) imageFrames = 1; // default in case this attribute contains garbage

    if (result.bad())
       return result;

    DcmPixelItem *pixItem = NULL;
    Uint8 * rleData = NULL;
    const size_t bytesPerStripe = OFstatic_cast(size_t, imageColumns) * OFstatic_cast(size_t, imageRows);
    Uint32 numberOfStripes = 0;
    Uint32 fragmentLength = 0;
    Uint32 frameSize = imageBytesAllocated * OFstatic_cast(Uint32, imageRows)
                       * OFstatic_cast(Uint32, imageColumns) * OFstatic_cast(Uint32, imageSamplesPerPixel);

    // check for overflow
    if (imageRows != 0 && frameSize / imageRows != (imageBytesAllocated * imageColumns * imageSamplesPerPixel))
    {
      DCMDATA_WARN("DcmRLECodecDecoder: Cannot decompress image because uncompressed representation would exceed maximum possible size of PixelData attribute");
      return EC_ElemLengthExceeds32BitField;
    }

    if (frameSize > bufSize) return EC_IllegalCall;

    DcmRLEDecoder rledecoder(bytesPerStripe);
    if (rledecoder.fail()) return EC_MemoryExhausted;  // RLE decoder failed to initialize

    DCMDATA_DEBUG("RLE decoder processes frame " << frameNo);

    // determine the corresponding item (first fragment) for this frame
    Uint32 currentItem = startFragment;

    // if the user has provided this information, we trust him.
    // If the user has passed a zero, try to find out ourselves.
    if (currentItem == 0 && result.good())
    {
        result = determineStartFragment(frameNo, imageFrames, fromPixSeq, currentItem);
        if (result.bad())
            return result;
    }

    DCMDATA_DEBUG("RLE decoder processes pixel item " << currentItem);
    // now access and decompress the frame starting at the item we have identified
    result = fromPixSeq->getItem(pixItem, currentItem);
    if (result.bad())
       return result;

    fragmentLength = pixItem->getLength();
    result = pixItem->getUint8Array(rleData);
    if (result.bad())
       return result;

    // copy RLE header to buffer and adjust byte order
    memcpy(rleHeader, rleData, 64);
    swapIfNecessary(gLocalByteOrder, EBO_LittleEndian, rleHeader, OFstatic_cast(Uint32, 16*sizeof(Uint32)), sizeof(Uint32));

    // determine number of stripes.
    numberOfStripes = rleHeader[0];

    // check that number of stripes in RLE header matches our expectation
    if ((numberOfStripes < 1) || (numberOfStripes > 15) || (numberOfStripes != OFstatic_cast(Uint32, imageBytesAllocated) * imageSamplesPerPixel))
    {
        DCMDATA_ERROR("Number of stripes in RLE header incorrect: found " << numberOfStripes << ", expected " << (OFstatic_cast(Uint32, imageBytesAllocated) * imageSamplesPerPixel));
        return EC_CannotChangeRepresentation;
    }

    // this variable keeps the current position within the current fragment
    Uint32 byteOffset = 0;

    OFBool lastStripe = OFFalse;
    OFBool lastStripeOfColor = OFFalse;
    Uint32 inputBytes = 0;

    // pointers for buffer copy operations
    Uint8 *outputBuffer = NULL;
    Uint8 *pixelPointer = NULL;
    Uint16 *imageData16 = OFreinterpret_cast(Uint16 *, buffer);
    Uint8 *imageData8 = OFreinterpret_cast(Uint8 *, buffer);

    // byte offset for first sample in frame
    Uint32 sampleOffset = 0;

    // byte offset between samples
    Uint32 offsetBetweenSamples = 0;

    // temporary variables
    Uint32 sample = 0;
    Uint32 byte = 0;
    Uint32 pixel = 0;
    size_t bytesToDecode;

    // for each stripe in stripe set
    for (Uint32 stripeIndex = 0; stripeIndex < numberOfStripes; ++stripeIndex)
    {
        // reset RLE codec
        rledecoder.clear();

        // adjust start point for RLE stripe
        byteOffset = rleHeader[stripeIndex + 1];

        // byteOffset now points to the first byte of the new RLE stripe
        // check if the current stripe is the last one for this frame
        if (stripeIndex + 1 == numberOfStripes) lastStripe = OFTrue; else lastStripe = OFFalse;

        if (lastStripe)
        {
            // the last stripe needs special handling because we cannot use the
            // offset table to determine the number of bytes to feed to the codec
            // if the RLE data is split in multiple fragments. We need to feed
            // data fragment by fragment until the RLE codec has produced
            // sufficient output.
            if (fragmentLength < byteOffset)
            {
              DCMDATA_ERROR("Byte offset in RLE header is wrong.");
              return EC_CannotChangeRepresentation;
            }
            bytesToDecode = OFstatic_cast(size_t, fragmentLength - byteOffset);
        }
        else
        {
            // not the last stripe. We can use the offset table to determine
            // the number of bytes to feed to the RLE codec.
            inputBytes = rleHeader[stripeIndex+2];
            if (inputBytes < rleHeader[stripeIndex + 1])
            {
              DCMDATA_ERROR("Byte offset in RLE header is wrong.");
              return EC_CannotChangeRepresentation;
            }

            inputBytes -= rleHeader[stripeIndex + 1]; // number of bytes to feed to codec

            bytesToDecode = OFstatic_cast(size_t, inputBytes);
        }

        // make sure we don't overshoot the buffer size in case of an incorrect byte offset
        if (fragmentLength < byteOffset + bytesToDecode)
        {
          DCMDATA_ERROR("Byte offset in RLE header is wrong.");
          return EC_CannotChangeRepresentation;
        }

        result = rledecoder.decompress(rleData + byteOffset, bytesToDecode);

        // special handling for zero pad byte at the end of the RLE stream
        // which results in an EC_StreamNotifyClient return code
        // or trailing garbage data which results in EC_CorruptedData
        if (rledecoder.size() == bytesPerStripe) result = EC_Normal;

        byteOffset += inputBytes;

        // copy the decoded stuff over to the buffer here...
        // make sure the RLE decoder has produced the right amount of data
        lastStripeOfColor = lastStripe || ((imagePlanarConfiguration == 1) && ((stripeIndex + 1) % imageBytesAllocated == 0));
        if (lastStripeOfColor && (rledecoder.size() < bytesPerStripe))
        {
            // stripe ended prematurely? report a warning and continue
            DCMDATA_WARN("RLE decoder is finished but has produced insufficient data for this stripe, filling remaining pixels");
            result = EC_Normal;
        }
        else if (rledecoder.size() != bytesPerStripe)
        {
            DCMDATA_ERROR("RLE decoder is finished but has produced insufficient data for this stripe");
            return EC_CannotChangeRepresentation;
        }

        // distribute decompressed bytes into output image array
        // which sample and byte are we currently decompressing?
        sample = stripeIndex / imageBytesAllocated;
        byte = stripeIndex % imageBytesAllocated;

        // raw buffer containing bytesPerStripe bytes of uncompressed data
        outputBuffer = OFstatic_cast(Uint8 *, rledecoder.getOutputBuffer());

        // compute byte offsets
        if (imagePlanarConfiguration == 0)
        {
            sampleOffset = sample * imageBytesAllocated;
            offsetBetweenSamples = imageSamplesPerPixel * imageBytesAllocated;
        }
        else
        {
            sampleOffset = sample * imageBytesAllocated * imageColumns * imageRows;
            offsetBetweenSamples = imageBytesAllocated;
        }

        // initialize pointer to output data
        if (enableReverseByteOrder)
        {
            // assume incorrect LSB to MSB order of RLE segments as produced by some tools
            pixelPointer = imageData8 + sampleOffset + byte;
        }
        else
        {
            pixelPointer = imageData8 + sampleOffset + imageBytesAllocated - byte - 1;
        }

        // copy the pixel data that was decoded
        const size_t decoderSize = rledecoder.size();
        for (pixel = 0; pixel < decoderSize; ++pixel)
        {
            *pixelPointer = *outputBuffer++;
            pixelPointer += offsetBetweenSamples;
        }
        // and fill the remainder of the image with copies of the last decoded pixel
        const Uint8 lastPixelValue = *(outputBuffer - 1);
        for (pixel = OFstatic_cast(Uint32, decoderSize); pixel < bytesPerStripe; ++pixel)
        {
            *pixelPointer = lastPixelValue;
            pixelPointer += offsetBetweenSamples;
        }
    }

    /* remove used fragment from memory */
    pixItem->compact(); // there should only be one...

    if (result.good())
    {
      // compression was successful. Now update output parameters
      startFragment = currentItem + 1;
      decompressedColorModel = photometricInterpretation;
    }

    // adjust byte order for uncompressed image to little endian
    if ((gLocalByteOrder == EBO_BigEndian) && (frameSize & 1))
    {
      DCMDATA_WARN("Size of frame buffer is odd, cannot correct byte order for last pixel value");
    }
    swapIfNecessary(EBO_LittleEndian, gLocalByteOrder, imageData16, frameSize, sizeof(Uint16));

    return result;
}


OFCondition DcmRLECodecDecoder::encode(
    const Uint16 * /* pixelData */,
    const Uint32 /* length */,
    const DcmRepresentationParameter * /* toRepParam */,
    DcmPixelSequence * & /* pixSeq */,
    const DcmCodecParameter * /* cp */,
    DcmStack & /* objStack */,
    OFBool& /* removeOldRep */) const
{
  // we are a decoder only
  return EC_IllegalCall;
}


OFCondition DcmRLECodecDecoder::encode(
    const E_TransferSyntax /* fromRepType */,
    const DcmRepresentationParameter * /* fromRepParam */,
    DcmPixelSequence * /* fromPixSeq */,
    const DcmRepresentationParameter * /* toRepParam */,
    DcmPixelSequence * & /* toPixSeq */,
    const DcmCodecParameter * /* cp */,
    DcmStack & /* objStack */,
    OFBool& /* removeOldRep */) const
{
  // we don't support re-coding for now.
  return EC_IllegalCall;
}


OFCondition DcmRLECodecDecoder::determineDecompressedColorModel(
    const DcmRepresentationParameter * /* fromParam */,
    DcmPixelSequence * /* fromPixSeq */,
    const DcmCodecParameter * /* cp */,
    DcmItem *dataset,
    OFString &decompressedColorModel) const
{
    OFCondition result = EC_IllegalParameter;
    if (dataset != NULL )
    {
        if ((dataset->ident() == EVR_dataset) || (dataset->ident() == EVR_item))
        {
            // retrieve color model from given dataset
            result = dataset->findAndGetOFString(DCM_PhotometricInterpretation, decompressedColorModel);
            if (result == EC_TagNotFound)
            {
                DCMDATA_WARN("DcmRLECodecDecoder: Mandatory element PhotometricInterpretation " << DCM_PhotometricInterpretation << " is missing");
                result = EC_MissingAttribute;
            }
            else if (result.bad())
            {
                DCMDATA_WARN("DcmRLECodecDecoder: Cannot retrieve value of element PhotometricInterpretation " << DCM_PhotometricInterpretation << ": " << result.text());
            }
            else if (decompressedColorModel.empty())
            {
                DCMDATA_WARN("DcmRLECodecDecoder: No value for mandatory element PhotometricInterpretation " << DCM_PhotometricInterpretation);
                result = EC_MissingValue;
            }
        } else
            result = EC_CorruptedData;
    }
    return result;
}
