/*
 *
 *  Copyright (C) 1998-2010, OFFIS e.V.
 *  All rights reserved.  See COPYRIGHT file for details.
 *
 *  This software and supporting documentation were developed by
 *
 *    OFFIS e.V.
 *    R&D Division Health
 *    Escherweg 2
 *    D-26121 Oldenburg, Germany
 *
 *
 *  Module: dcmpstat
 *
 *  Author: Marco Eichelberg
 *
 *  Purpose:
 *    classes: DVPresentationState
 *
 */

#include "dcmtk/config/osconfig.h"    /* make sure OS specific configuration is included first */

#include "dcmtk/ofstd/ofstream.h"
#include "dcmtk/dcmpstat/dvpstat.h"
#include "dcmtk/dcmpstat/dvpsdef.h"     /* for constants and macros */
#include "dcmtk/ofstd/ofstring.h"
#include "dcmtk/dcmimgle/dcmimage.h"    /* for DicomImage */
#include "dcmtk/dcmpstat/dvpscu.h"      /* for DVPSCurve */
#include "dcmtk/dcmpstat/dvpsvl.h"      /* for DVPSVOILUT */
#include "dcmtk/dcmpstat/dvpsvw.h"      /* for DVPSVOIWindow */
#include "dcmtk/dcmpstat/dvpsov.h"      /* for DVPSOverlay */
#include "dcmtk/dcmpstat/dvpsda.h"      /* for DVPSDisplayedArea */
#include "dcmtk/dcmpstat/dvpssv.h"      /* for DVPSSoftcopyVOI */
#include "dcmtk/dcmpstat/dvpshlp.h"     /* for class DVPSHelper */
#include "dcmtk/ofstd/ofstd.h"       /* for class OFStandard */

#include "dcmtk/dcmpstat/dvpsgl.h"      /* for DVPSGraphicLayer, needed by MSVC5 with STL */
#include "dcmtk/dcmpstat/dvpsrs.h"      /* for DVPSReferencedSeries, needed by MSVC5 with STL */
#include "dcmtk/dcmpstat/dvpsal.h"      /* for DVPSOverlayCurveActivationLayer, needed by MSVC5 with STL */
#include "dcmtk/dcmpstat/dvpsga.h"      /* for DVPSGraphicAnnotation, needed by MSVC5 with STL */
#include "dcmtk/dcmpstat/dvpsri.h"      /* for DVPSReferencedImage, needed by MSVC5 with STL */
#include "dcmtk/dcmpstat/dvpstx.h"      /* for DVPSTextObject, needed by MSVC5 with STL */
#include "dcmtk/dcmpstat/dvpsgr.h"      /* for DVPSGraphicObject, needed by MSVC5 with STL */

#define INCLUDE_CSTDLIB
#define INCLUDE_CSTDIO
#define INCLUDE_CSTRING
#define INCLUDE_CMATH
#define INCLUDE_CTIME
#define INCLUDE_LIBC
#define INCLUDE_UNISTD
#include "dcmtk/ofstd/ofstdinc.h"

/* --------------- class DVPresentationState --------------- */

DVPresentationState::DVPresentationState(
    DiDisplayFunction **dispFunction,
    unsigned long minPrintBitmapX,
    unsigned long minPrintBitmapY,
    unsigned long maxPrintBitmapX,
    unsigned long maxPrintBitmapY,
    unsigned long maxPreviewImageX,
    unsigned long maxPreviewImageY)
: DcmPresentationState()
, currentImageDataset(NULL)
, currentImageFileformat(NULL)
, currentImage(NULL)
, previewImage(NULL)
, currentImageWidth(0)
, currentImageHeight(0)
, renderedImageWidth(0)
, renderedImageHeight(0)
, renderedImageTop(0)
, renderedImageLeft(0)
, renderedImageBottom(0)
, renderedImageRight(0)
, currentImageSOPClassUID(NULL)
, currentImageSOPInstanceUID(NULL)
, currentImageSelectedFrame(0)
, currentImageOwned(OFFalse)
, currentImageVOIValid(OFFalse)
, currentImagePLUTValid(OFFalse)
, currentImageFlip(OFFalse)
, currentImageRotation(DVPSR_0_deg)
, currentImageOverlaysValid(0)
, currentImageCurveList()
, currentImageVOILUTList()
, currentImageVOIWindowList()
, currentImageModality(DCM_Modality)
, currentImageMonochrome1(OFFalse)
, displayTransform(DVPSD_GSDF)
, imageInverse(OFFalse)
, displayFunction(dispFunction)
, minimumPrintBitmapWidth(minPrintBitmapX)
, minimumPrintBitmapHeight(minPrintBitmapY)
, maximumPrintBitmapWidth(maxPrintBitmapX)
, maximumPrintBitmapHeight(maxPrintBitmapY)
, maximumPreviewImageWidth(maxPreviewImageX)
, maximumPreviewImageHeight(maxPreviewImageY)
{
  createInstanceUID();
}


DVPresentationState::~DVPresentationState()
{
  detachImage();
}

void DVPresentationState::detachImage()
{
  if (currentImage) delete currentImage;
  deletePreviewImage();
  if (currentImageOwned)
  {
    if (currentImageFileformat) delete currentImageFileformat;
    else if (currentImageDataset) delete currentImageDataset;
  }
  currentImage = NULL;
  currentImageFileformat = NULL;
  currentImageDataset = NULL;
  currentImageCurveList.clear();
  currentImageVOILUTList.clear();
  currentImageVOIWindowList.clear();
  currentImageModality.clear();
  currentImageMonochrome1 = OFFalse;

  // reset flags
  currentImageWidth = 0;
  currentImageHeight = 0;
  renderedImageWidth = 0;
  renderedImageHeight = 0;
  renderedImageTop = 0;
  renderedImageLeft = 0;
  renderedImageBottom = 0;
  renderedImageRight = 0;
  currentImageOwned = OFFalse;
  currentImageVOIValid = OFFalse;
  currentImagePLUTValid = OFFalse;
  currentImageFlip = OFFalse;
  currentImageRotation = DVPSR_0_deg;
  currentImageOverlaysValid = 0;
  currentImageSOPClassUID=NULL;
  currentImageSOPInstanceUID=NULL;
  currentImageSelectedFrame=0;
}


void DVPresentationState::clear()
{
  DcmPresentationState::clear();
  detachImage(); // clears all currentImageXX attributes
  // we do not change the display function
  displayTransform = DVPSD_GSDF;
  imageInverse = OFFalse;
  return;
}


OFCondition DVPresentationState::writeHardcopyImageAttributes(DcmItem &dset)
{
  OFCondition result = EC_Normal;
  DcmElement *delem=NULL;
  DcmSequenceOfItems *dseq=NULL;
  DcmItem *ditem=NULL;

  DVPSHelper::setDefault(result, patientName, DEFAULT_patientName);
  ADD_TO_DATASET(DcmPersonName, patientName)
  ADD_TO_DATASET(DcmLongString, patientID)
  ADD_TO_DATASET(DcmDate, patientBirthDate)
  ADD_TO_DATASET(DcmCodeString, patientSex)

  // write source image sequence
  if (result == EC_Normal)
  {
    dseq = new DcmSequenceOfItems(DCM_SourceImageSequence);
    if (dseq)
    {
      // first item references source image
      if (currentImageSOPClassUID && currentImageSOPInstanceUID)
      {
        ditem = new DcmItem();
        if (ditem)
        {
          delem = new DcmUniqueIdentifier(DCM_SOPClassUID);
          if (delem)
          {
            result = delem->putString(currentImageSOPClassUID);
            ditem->insert(delem, OFTrue /*replaceOld*/);
          } else result=EC_MemoryExhausted;
          delem = new DcmUniqueIdentifier(DCM_SOPInstanceUID);
          if (delem)
          {
            result = delem->putString(currentImageSOPInstanceUID);
            ditem->insert(delem, OFTrue /*replaceOld*/);
          } else result=EC_MemoryExhausted;
          if (EC_Normal == result) dseq->insert(ditem);  else delete ditem;
        } else result = EC_MemoryExhausted;
      }
      // second item references presentation state
      ditem = new DcmItem();
      if (ditem)
      {
        delem = new DcmUniqueIdentifier(sOPInstanceUID);
        if (delem) ditem->insert(delem, OFTrue /*replaceOld*/); else result=EC_MemoryExhausted;
        delem = new DcmUniqueIdentifier(DCM_SOPClassUID);
        if (delem)
        {
          result = delem->putString(UID_GrayscaleSoftcopyPresentationStateStorage);
          ditem->insert(delem, OFTrue /*replaceOld*/);
        } else result=EC_MemoryExhausted;
        if (EC_Normal == result) dseq->insert(ditem);  else delete ditem;
       }
    } else result = EC_MemoryExhausted;
    if (EC_Normal == result) dset.insert(dseq, OFTrue /*replaceOld*/); else delete dseq;
  }

  return result;
}

unsigned long DVPresentationState::getPrintBitmapSize()
{
  unsigned long result = 0;
  unsigned long width;
  unsigned long height;
  if (getPrintBitmapWidthHeight(width, height) == EC_Normal)
    result = width * height * 2;       // print bitmap: 12 bit stored, 16 bit allocated (2 bytes per pixel)
  return result;
}


OFCondition DVPresentationState::setMinimumPrintBitmapWidthHeight(unsigned long width,
                                                                  unsigned long height)
{
  OFCondition result = EC_IllegalCall;
  const unsigned long max = (width > height) ? width : height;
  if (((maximumPrintBitmapWidth == 0) || (maximumPrintBitmapWidth >= 2 * max)) &&
      ((maximumPrintBitmapHeight == 0) || (maximumPrintBitmapHeight >= 2 * max)))
  {
    minimumPrintBitmapWidth = width;
    minimumPrintBitmapHeight = height;
    result = EC_Normal;
  }
  return result;
}


OFCondition DVPresentationState::setMaximumPrintBitmapWidthHeight(unsigned long width,
                                                                  unsigned long height)
{
  OFCondition result = EC_IllegalCall;
  const unsigned long min = (width < height) ? width : height;
  if (((minimumPrintBitmapWidth == 0) || (min >= 2 * minimumPrintBitmapWidth)) &&
      ((minimumPrintBitmapHeight == 0) || (min >= 2 * minimumPrintBitmapHeight)))
  {
    maximumPrintBitmapWidth = width;
    maximumPrintBitmapHeight = height;
    result = EC_Normal;
  }
  return result;
}


OFCondition DVPresentationState::getPrintBitmapWidthHeight(unsigned long &width,
                                                           unsigned long &height)
{
  OFCondition result = EC_Normal;
  if (currentImage)
  {
    renderPixelData(OFFalse);                                 // switch off display function
    width = renderedImageWidth;
    height = renderedImageHeight;
    if ((width > 0) && (height > 0))
    {
      width = (unsigned long)(renderedImageRight - renderedImageLeft + 1);
      height = (unsigned long)(renderedImageBottom - renderedImageTop + 1);

      if ((minimumPrintBitmapWidth > 0) && (width < minimumPrintBitmapWidth))
      {
        if ((minimumPrintBitmapHeight > 0) && (height < minimumPrintBitmapHeight))
        {
          const unsigned long xfactor = (unsigned long)((double)(minimumPrintBitmapWidth - 1) / (double)width) + 1;
          const unsigned long yfactor = (unsigned long)((double)(minimumPrintBitmapHeight - 1) / (double)height) + 1;
          if (xfactor > yfactor)
          {
            width *= xfactor;
            height *= xfactor;
          } else {
            width *= yfactor;
            height *= yfactor;
          }
        } else {
          const unsigned long factor = (unsigned long)((double)(minimumPrintBitmapWidth - 1) / (double)width) + 1;
          width *= factor;
          height *= factor;
        }
      }
      else if ((minimumPrintBitmapHeight > 0) && (height < minimumPrintBitmapHeight))
      {
        const unsigned long factor = (unsigned long)((double)(minimumPrintBitmapHeight - 1) / (double)height) + 1;
        width *= factor;
        height *= factor;
      }

      if ((maximumPrintBitmapWidth > 0) && (width > maximumPrintBitmapWidth))
      {
        if ((maximumPrintBitmapHeight > 0) && (height > maximumPrintBitmapHeight))
        {
          const unsigned long xdivisor = (unsigned long)((double)(width - 1) / (double)maximumPrintBitmapWidth) + 1;
          const unsigned long ydivisor = (unsigned long)((double)(height - 1) / (double)maximumPrintBitmapHeight) + 1;
          if (xdivisor > ydivisor)
          {
            width /= xdivisor;
            height /= xdivisor;
          } else {
            width /= ydivisor;
            height /= ydivisor;
          }
        } else {
          const unsigned long divisor = (unsigned long)((double)(width - 1) / (double)maximumPrintBitmapWidth) + 1;
          width /= divisor;
          height /= divisor;
        }
      }
      else if ((maximumPrintBitmapHeight > 0) && (height > maximumPrintBitmapHeight))
      {
        const unsigned long divisor = (unsigned long)((double)(height - 1) / (double)maximumPrintBitmapHeight) + 1;
        width /= divisor;
        height /= divisor;
      }
    }
  } else {
    width = 0;
    height = 0;
    result = EC_IllegalCall;
  }
  return result;
}


OFCondition DVPresentationState::getPrintBitmapWidth(unsigned long &width)
{
  unsigned long dummy;
  return getPrintBitmapWidthHeight(width, dummy);
}


OFCondition DVPresentationState::getPrintBitmapHeight(unsigned long &height)
{
  unsigned long dummy;
  return getPrintBitmapWidthHeight(dummy, height);
}


double DVPresentationState::getPrintBitmapPixelAspectRatio()
{
  double result = getDisplayedAreaPresentationPixelAspectRatio();
  if (result == 1.0) return result; // handle most frequent case quickly
  if (result == 0.0) result = 1.0; // should never happen

  DVPSRotationType rotation = getRotation();
  if ((rotation==DVPSR_90_deg)||(rotation==DVPSR_270_deg)) result = 1.0/result;
  return result;
}


OFCondition DVPresentationState::getPrintBitmap(void *bitmap,
                                                unsigned long size,
                                                OFBool inversePLUT)
{
  OFCondition result = EC_IllegalCall;
  if ((bitmap != NULL) && (size == getPrintBitmapSize()))       // check given buffer
  {
    if (currentImage)
    {
      renderPixelData(OFFalse);                                 // don't use current display function
      unsigned long width;
      unsigned long height;
      if (getPrintBitmapWidthHeight(width, height) == EC_Normal)
      {
        DicomImage *image = currentImage;

        /* we deactivate any presentation LUT at this point because
         * getPrintBitmapWidthHeight() calls renderPixelData().
         */
        if (presentationLUT.getType() == DVPSP_table)
        {
          if (inversePLUT)
          {
            if (currentImageMonochrome1) currentImage->setPolarity(EPP_Reverse);
            presentationLUT.activateInverseLUT(currentImage);
          } else {
            // we never render a presentation LUT into the print bitmap at this stage.
            if (currentImageMonochrome1) currentImage->setPresentationLutShape(ESP_Inverse);
            else currentImage->setPresentationLutShape(ESP_Identity);
          }
          // make sure the presentation LUT is re-activated for on-screen display
          currentImagePLUTValid = OFFalse;
        }

        /* clip to displayed area if necessary */
        if ((renderedImageLeft != 1) || (renderedImageRight != (signed long)renderedImageWidth) ||
            (renderedImageTop != 1) || (renderedImageBottom != (signed long)renderedImageHeight))
        {
          DicomImage *img = currentImage->createMonoOutputImage(currentImageSelectedFrame-1, 12 /*bits*/);
          if (img == NULL)
            img = currentImage;                                 // fall-back solution
          image = img->createClippedImage(renderedImageLeft - 1, renderedImageTop - 1, renderedImageRight - renderedImageLeft + 1,
            renderedImageBottom - renderedImageTop + 1, getShutterPresentationValue());
          if (img != currentImage)
            delete img;
        }
        /* scale up to minimum size or down to maximum size if necessary */
        if (((signed long)width != renderedImageRight - renderedImageLeft + 1) ||
           ((signed long)height != renderedImageBottom - renderedImageTop + 1))
        {
          DicomImage *img = image;
          image = img->createScaledImage(width, height, 0 /*no interpolation*/, 0 /*ignore aspect ratio*/);
          if (img != currentImage)
            delete img;
        }
        if (image != NULL)
        {
          if (image->getOutputData(bitmap, size, 12 /*bits*/, currentImageSelectedFrame-1)) result = EC_Normal;
        }
        if (image != currentImage) delete image;
      }
    }
  }
  return result;
}


OFCondition DVPresentationState::createPreviewImage(unsigned long maxWidth,
                                                    unsigned long maxHeight,
                                                    OFBool clipMode)
{
  OFCondition result = EC_IllegalCall;
  if ((currentImage != NULL) && (maxWidth > 0) && (maxHeight > 0))
  {
    deletePreviewImage();
    renderPixelData();
    unsigned long width = maxWidth;
    unsigned long height = maxHeight;
    double ratio = getPrintBitmapPixelAspectRatio();    // never 0 !
    if ((double)renderedImageWidth / (double)maxWidth * ratio < (double)renderedImageHeight / (double)maxHeight)
      width = 0;
    else
      height = 0;
    if (clipMode)
    {
      /* not yet implemented: clip preview image to current displayed area */
    }
    double oldRatio = currentImage->getWidthHeightRatio();
    currentImage->setWidthHeightRatio(ratio);
    previewImage = currentImage->createScaledImage(width, height, 1 /*interpolate*/, 1 /*aspect ratio*/);
    currentImage->setWidthHeightRatio(oldRatio);
    if (previewImage != NULL)
    {
      if (previewImage->getStatus() == EIS_Normal)
      {
        previewImage->removeAllOverlays();
        result = EC_Normal;
      } else {
        deletePreviewImage();
      }
    }
  }
  return result;
}


void DVPresentationState::deletePreviewImage()
{
  delete previewImage;
  previewImage = NULL;
}


unsigned long DVPresentationState::getPreviewImageSize()
{
  unsigned long result = 0;
  unsigned long width;
  unsigned long height;
  if (getPreviewImageWidthHeight(width, height) == EC_Normal)
      result = width * height;
  return result;
}


OFCondition DVPresentationState::getPreviewImageWidthHeight(unsigned long &width,
                                                            unsigned long &height)
{
  OFCondition result = EC_IllegalCall;
  if (previewImage != NULL)
  {
    width = previewImage->getWidth();
    height = previewImage->getHeight();
    if ((width > 0) && (height > 0))
      result = EC_Normal;
  } else {
    width = 0;
    height = 0;
  }
  return result;
}


OFCondition DVPresentationState::getPreviewImageWidth(unsigned long &width)
{
  unsigned long dummy;
  return getPreviewImageWidthHeight(width, dummy);
}


OFCondition DVPresentationState::getPreviewImageHeight(unsigned long &height)
{
  unsigned long dummy;
  return getPreviewImageWidthHeight(dummy, height);
}


OFCondition DVPresentationState::getPreviewImageBitmap(void *bitmap,
                                                       unsigned long size)
{
  OFCondition result = EC_IllegalCall;
  if ((previewImage != NULL) && (bitmap != NULL) && (size > 0))
  {
    renderPixelData();
    if (previewImage->getOutputData(bitmap, size, 8 /*bits*/, currentImageSelectedFrame-1))
      result = EC_Normal;
  }
  return result;
}


OFCondition DVPresentationState::attachImage(DcmDataset *dataset, OFBool transferOwnership)
{
  if (!dataset) return EC_IllegalCall;

  // select the right DicomImage constructor depending on the Modality LUT
  DicomImage *image = NULL;
  if (useModalityRescale)
  {
    Float64 slope = 1.0;
    Float64 intercept = 0.0;

    if (EC_Normal != rescaleSlope.getFloat64(slope, 0))
    {
      DCMPSTAT_WARN("unable to evaluate Modality Rescale Slope, ignoring.");
      slope = 1.0;
    }
    if (EC_Normal != rescaleIntercept.getFloat64(intercept, 0))
    {
      DCMPSTAT_WARN("unable to evaluate Modality Rescale Slope, ignoring.");
      intercept = 0.0;
    }
    image = new DicomImage(dataset, dataset->getOriginalXfer(),
      slope, intercept, CIF_UsePresentationState);
  }
  else if (useModalityLUT)
  {
    image = new DicomImage(dataset, dataset->getOriginalXfer(),
      modalityLUTData, modalityLUTDescriptor, &modalityLUTExplanation, CIF_UsePresentationState);
  }
  else
  {
    image = new DicomImage(dataset, dataset->getOriginalXfer(), CIF_UsePresentationState);
  }

  OFCondition result = EC_Normal;
  if (image)
  {
    if (EIS_Normal == image->getStatus())
    {
      OFString aString;
      DcmStack stack;
      detachImage();
      currentImage = image;
      currentImageWidth = image->getWidth();
      currentImageHeight = image->getHeight();
      currentImageDataset = dataset;
      currentImageOwned = transferOwnership;
      currentImageSelectedFrame = 1; // default: first frame

      // get Modality
      if (EC_Normal == dataset->search(DCM_Modality, stack, ESM_fromHere, OFFalse))
      {
        currentImageModality = *((DcmCodeString *)(stack.top()));
      }
      stack.clear();

      // determine default Presentation LUT Shape
      if (EC_Normal == dataset->search(DCM_PhotometricInterpretation, stack, ESM_fromHere, OFFalse))
      {
         DcmCodeString *photometricInterpretation = (DcmCodeString *)(stack.top());
         if (photometricInterpretation->getVM() == 1)
         {
           aString.clear();
           photometricInterpretation->getOFString(aString,0);
           if ((aString == "MONOCHROME1")||(aString == "MONOCHROME 1")) currentImageMonochrome1 = OFTrue;
         }
      }
      stack.clear();

      // get SOP class UID and SOP instance UID.
      if ((EC_Normal == result)&&(EC_Normal == dataset->search(DCM_SOPClassUID, stack, ESM_fromHere, OFFalse)))
      {
        result = ((DcmUniqueIdentifier *)(stack.top()))->getString(currentImageSOPClassUID);
      }
      stack.clear();
      if ((EC_Normal == result)&&(EC_Normal == dataset->search(DCM_SOPInstanceUID, stack, ESM_fromHere, OFFalse)))
      {
        result = ((DcmUniqueIdentifier *)(stack.top()))->getString(currentImageSOPInstanceUID);
      }
      if (EC_Normal==result) result = currentImageCurveList.read(*dataset);
      if (EC_Normal==result) result = currentImageVOILUTList.read(*dataset);
      if (EC_Normal==result) result = currentImageVOIWindowList.read(*dataset);

      createPreviewImage(maximumPreviewImageWidth, maximumPreviewImageHeight);
    } else {
      delete image;
      result = EC_IllegalCall;
    }
  } else result = EC_MemoryExhausted;
  return result;
}

OFCondition DVPresentationState::getImageWidth(unsigned long &width)
{
  OFCondition result=EC_Normal;
  if (currentImage) width = currentImageWidth;
  else
  {
    width = 0;
    result = EC_IllegalCall;
  }
  return result;
}

OFCondition DVPresentationState::getImageHeight(unsigned long &height)
{
  OFCondition result=EC_Normal;
  if (currentImage) height = currentImageHeight;
  else
  {
    height = 0;
    result = EC_IllegalCall;
  }
  return result;
}


OFCondition DVPresentationState::attachImage(DcmFileFormat *fileformat, OFBool transferOwnership)
{
  if (fileformat == NULL) return EC_IllegalCall;
  OFCondition result = attachImage(fileformat->getDataset(), transferOwnership);
  if (EC_Normal == result) currentImageFileformat = fileformat;
  return result;
}


OFCondition DVPresentationState::addImageReferenceAttached(
    const char *aetitle,
    const char *filesetID,
    const char *filesetUID)
{
  if (currentImageDataset) return addImageReference(*currentImageDataset, aetitle, filesetID, filesetUID);
  else return EC_IllegalCall;
}


OFCondition DVPresentationState::removeImageReferenceAttached()
{
  if (currentImageDataset) return removeImageReference(*currentImageDataset);
  else return EC_IllegalCall;
}



OFCondition DVPresentationState::setCurrentPresentationLUT(DVPSPresentationLUTType newType)
{
  OFCondition result = presentationLUT.setType(newType);
  currentImagePLUTValid = OFFalse; // PLUT has changed
  imageInverse = presentationLUT.isInverse();
  return result;
}

OFCondition DVPresentationState::setDefaultPresentationLUTShape()
{
  OFCondition result = EC_Normal;
  if (currentImageMonochrome1) result=presentationLUT.setType(DVPSP_inverse); else result=presentationLUT.setType(DVPSP_identity);
  currentImagePLUTValid = OFFalse; // PLUT has changed
  imageInverse = presentationLUT.isInverse();
  return result;
}

OFCondition DVPresentationState::setPresentationLookupTable(
    DcmUnsignedShort& lutDescriptor,
    DcmUnsignedShort& lutData,
    DcmLongString& lutExplanation)
{
  OFCondition result = presentationLUT.setLUT(lutDescriptor, lutData, lutExplanation);
  currentImagePLUTValid = OFFalse; // PLUT has changed
  OFBool wasInverse = imageInverse;
  imageInverse = presentationLUT.isInverse();

  // keep inverse/normal status as before
  if ((wasInverse && (! imageInverse))||(imageInverse && (! wasInverse))) result = invertImage();
  return result;
}

OFCondition DVPresentationState::setPresentationLookupTable(DcmItem &dset)
{
  OFCondition result = presentationLUT.read(dset, OFFalse);
  if (EC_Normal != result) presentationLUT.setType(DVPSP_identity); // set to well-defined default in case of error
  currentImagePLUTValid = OFFalse; // PLUT has changed
  OFBool wasInverse = imageInverse;
  imageInverse = presentationLUT.isInverse();

  // keep inverse/normal status as before
  if ((wasInverse && (! imageInverse))||(imageInverse && (! wasInverse))) result = invertImage();
  return result;
}


void DVPresentationState::removeShutter(DVPSShutterType type)
{
  switch (type)
  {
    case DVPSU_rectangular:
      useShutterRectangular = OFFalse;
      break;
    case DVPSU_circular:
      useShutterCircular = OFFalse;
      break;
    case DVPSU_polygonal:
      useShutterPolygonal = OFFalse;
      break;
    case DVPSU_bitmap:
      if (useShutterBitmap) currentImageOverlaysValid = 1; // invalid but nothing added
      useShutterBitmap = OFFalse;
      break;
  }
  return;
}


OFCondition DVPresentationState::setRectShutter(Sint32 lv, Sint32 rv, Sint32 uh, Sint32 lh)
{
  OFCondition result=EC_Normal;
  char buf[80];

  sprintf(buf, "%ld", (long)lv);
  result = shutterLeftVerticalEdge.putString(buf);
  sprintf(buf, "%ld", (long)rv);
  if (EC_Normal==result) result = shutterRightVerticalEdge.putString(buf);
  sprintf(buf, "%ld", (long)uh);
  if (EC_Normal==result) result = shutterUpperHorizontalEdge.putString(buf);
  sprintf(buf, "%ld", (long)lh);
  if (EC_Normal==result) result = shutterLowerHorizontalEdge.putString(buf);
  if ((EC_Normal==result)&&(shutterPresentationValue.getLength()==0))
      result = shutterPresentationValue.putUint16(0,0);
  if (EC_Normal==result)
  {
    useShutterRectangular = OFTrue;
    if (useShutterBitmap) currentImageOverlaysValid = 1; // invalid but nothing added
    useShutterBitmap = OFFalse;
  }
  return result;
}


OFCondition DVPresentationState::setCircularShutter(Sint32 centerX, Sint32 centerY, Sint32 radius)
{
  OFCondition result=EC_Normal;
  char buf[80];

  sprintf(buf, "%ld\\%ld", (long)centerY, (long)centerX);
  result = centerOfCircularShutter.putString(buf);
  sprintf(buf, "%ld", (long)radius);
  if (EC_Normal==result) result = radiusOfCircularShutter.putString(buf);
  if ((EC_Normal==result)&&(shutterPresentationValue.getLength()==0))
      result = shutterPresentationValue.putUint16(0,0);
  if (EC_Normal==result)
  {
    useShutterCircular = OFTrue;
    if (useShutterBitmap) currentImageOverlaysValid = 1; // invalid but nothing added
    useShutterBitmap = OFFalse;
  }
  return result;
}


OFCondition DVPresentationState::addPolyShutterVertex(Sint32 x, Sint32 y)
{
  if (verticesOfThePolygonalShutter.getLength()==0) return EC_IllegalCall;
  OFString aString;
  OFCondition result = verticesOfThePolygonalShutter.getOFStringArray(aString,OFTrue);
  if (result==EC_Normal)
  {
    char buf[80];
    sprintf(buf, "\\%ld\\%ld", (long)y, (long)x);
    aString += buf;
    result = verticesOfThePolygonalShutter.putOFStringArray(aString);
  }

  if (result==EC_Normal)
  {
    Sint32 xp0 = 0;
    Sint32 yp0 = 0;
    result = getPolyShutterVertex(0, xp0, yp0);
    if (result==EC_Normal)
    {
      if ((xp0==x)&&(yp0==y)) // polygon is closed now, activate.
      {
        if (shutterPresentationValue.getLength()==0)
           result = shutterPresentationValue.putUint16(0,0);
        if (EC_Normal==result)
        {
          useShutterPolygonal = OFTrue;
          if (useShutterBitmap) currentImageOverlaysValid = 1; // invalid but nothing added
          useShutterBitmap = OFFalse;
        }
      }
    }
  }
  return result;
}

OFCondition DVPresentationState::removeGraphicLayer(size_t idx)
{
  const char *name = graphicLayerList.getGraphicLayerName(idx);
  if (name==NULL) return EC_IllegalCall;
  activationLayerList.removeLayer(name);
  currentImageOverlaysValid = 1; // invalid but nothing added
  graphicAnnotationList.removeLayer(name);
  return graphicLayerList.removeGraphicLayer(idx);
}

size_t DVPresentationState::getNumberOfTextObjects(size_t layer)
{
  if (!currentImage) return 0;
  return graphicAnnotationList.getNumberOfTextObjects(graphicLayerList.getGraphicLayerName(layer), currentImageSOPInstanceUID, currentImageSelectedFrame);
}

DVPSTextObject *DVPresentationState::getTextObject(size_t layer, size_t idx)
{
  if (!currentImage) return NULL;
  return graphicAnnotationList.getTextObject(graphicLayerList.getGraphicLayerName(layer), currentImageSOPInstanceUID, currentImageSelectedFrame, idx);
}

DVPSTextObject *DVPresentationState::addTextObject(size_t layer, DVPSObjectApplicability applicability)
{
  if (!currentImage) return NULL;
  return graphicAnnotationList.addTextObject(graphicLayerList.getGraphicLayerName(layer), currentImageSOPClassUID,
    currentImageSOPInstanceUID, currentImageSelectedFrame, applicability);
}

OFCondition DVPresentationState::removeTextObject(size_t layer, size_t idx)
{
  if (!currentImage) return EC_IllegalCall;
  return graphicAnnotationList.removeTextObject(graphicLayerList.getGraphicLayerName(layer), currentImageSOPInstanceUID, currentImageSelectedFrame, idx);
}

OFCondition DVPresentationState::moveTextObject(size_t old_layer, size_t idx, size_t new_layer, DVPSObjectApplicability applicability)
{
  if (!currentImage) return EC_IllegalCall;
  if (old_layer==new_layer) return EC_Normal;
  return graphicAnnotationList.moveTextObject(
    graphicLayerList.getGraphicLayerName(old_layer),
    currentImageSOPClassUID,
    currentImageSOPInstanceUID,
    currentImageSelectedFrame,
    idx,
    applicability,
    graphicLayerList.getGraphicLayerName(new_layer));
}

size_t DVPresentationState::getNumberOfGraphicObjects(size_t layer)
{
  if (!currentImage) return 0;
  return graphicAnnotationList.getNumberOfGraphicObjects(graphicLayerList.getGraphicLayerName(layer), currentImageSOPInstanceUID, currentImageSelectedFrame);
}

DVPSGraphicObject *DVPresentationState::getGraphicObject(size_t layer, size_t idx)
{
  if (!currentImage) return NULL;
  return graphicAnnotationList.getGraphicObject(graphicLayerList.getGraphicLayerName(layer), currentImageSOPInstanceUID, currentImageSelectedFrame, idx);
}

DVPSGraphicObject *DVPresentationState::addGraphicObject(size_t layer, DVPSObjectApplicability applicability)
{
  if (!currentImage) return NULL;
  return graphicAnnotationList.addGraphicObject(graphicLayerList.getGraphicLayerName(layer), currentImageSOPClassUID,
    currentImageSOPInstanceUID, currentImageSelectedFrame, applicability);
}

OFCondition DVPresentationState::removeGraphicObject(size_t layer, size_t idx)
{
  if (!currentImage) return EC_IllegalCall;
  return graphicAnnotationList.removeGraphicObject(graphicLayerList.getGraphicLayerName(layer), currentImageSOPInstanceUID, currentImageSelectedFrame, idx);
}

OFCondition DVPresentationState::moveGraphicObject(size_t old_layer, size_t idx, size_t new_layer, DVPSObjectApplicability applicability)
{
  if (!currentImage) return EC_IllegalCall;
  if (old_layer==new_layer) return EC_Normal;
  return graphicAnnotationList.moveGraphicObject(
    graphicLayerList.getGraphicLayerName(old_layer),
    currentImageSOPClassUID,
    currentImageSOPInstanceUID,
    currentImageSelectedFrame,
    idx,
    applicability,
    graphicLayerList.getGraphicLayerName(new_layer));
}


DVPSCurve *DVPresentationState::getCurve(size_t layer, size_t idx)
{
  Uint16 rgroup = activationLayerList.getActivationGroup(
    graphicLayerList.getGraphicLayerName(layer), idx, OFTrue);
  if (rgroup==0) return NULL;
  else return currentImageCurveList.getCurveGroup(rgroup);
}

size_t DVPresentationState::getNumberOfCurvesInImage()
{
  return currentImageCurveList.size();
}

DVPSCurve *DVPresentationState::getCurveInImage(size_t idx)
{
  return currentImageCurveList.getCurve(idx);
}

OFCondition DVPresentationState::addCurve(size_t layer, size_t curveidxinimage)
{
  const char *lname = graphicLayerList.getGraphicLayerName(layer);
  DVPSCurve *curve = currentImageCurveList.getCurve(curveidxinimage);
  if ((curve==NULL)||(lname==NULL)) return EC_IllegalCall;
  return activationLayerList.setActivation(0x5000 + curve->getCurveGroup(), lname);
}

OFBool DVPresentationState::haveActiveVOIWindow()
{
  DVPSSoftcopyVOI *voi = getCurrentSoftcopyVOI();
  if (voi)
  {
    if (voi->haveLUT()) return OFFalse; else return OFTrue;
  }
  return OFFalse;
}

OFBool DVPresentationState::haveActiveVOILUT()
{
  DVPSSoftcopyVOI *voi = getCurrentSoftcopyVOI();
  if (voi) return voi->haveLUT();
  return OFFalse;
}

const char *DVPresentationState::getCurrentVOIDescription()
{
  DVPSSoftcopyVOI *voi = getCurrentSoftcopyVOI();
  if (voi) return voi->getCurrentVOIDescription();
  return NULL;
}

OFCondition DVPresentationState::getCurrentWindowWidth(double &w)
{
  DVPSSoftcopyVOI *voi = getCurrentSoftcopyVOI();
  if (voi) return voi->getCurrentWindowWidth(w);
  return EC_IllegalCall;
}

OFCondition DVPresentationState::getCurrentWindowCenter(double &c)
{
  DVPSSoftcopyVOI *voi = getCurrentSoftcopyVOI();
  if (voi) return voi->getCurrentWindowCenter(c);
  return EC_IllegalCall;
}

size_t DVPresentationState::getNumberOfVOILUTsInImage()
{
  return currentImageVOILUTList.size();
}

size_t DVPresentationState::getNumberOfVOIWindowsInImage()
{
  return currentImageVOIWindowList.size();
}

const char *DVPresentationState::getDescriptionOfVOILUTsInImage(size_t idx)
{
  DVPSVOILUT *lut = currentImageVOILUTList.getVOILUT(idx);
  if (lut==NULL) return NULL;
  return lut->getExplanation();
}

const char *DVPresentationState::getDescriptionOfVOIWindowsInImage(size_t idx)
{
  DVPSVOIWindow *window = currentImageVOIWindowList.getVOIWindow(idx);
  if (window==NULL) return NULL;
  return window->getExplanation();
}

OFCondition DVPresentationState::setVOILUTFromImage(size_t idx,
    DVPSObjectApplicability applicability)
{
  if (currentImage == NULL) return EC_IllegalCall;

  DVPSVOILUT *lut = currentImageVOILUTList.getVOILUT(idx);
  if (lut)
  {
    currentImageVOIValid = OFFalse; // VOI has changed

    DVPSSoftcopyVOI *voi = softcopyVOIList.createSoftcopyVOI(
      referencedSeriesList, currentImageSOPClassUID, currentImageSOPInstanceUID,
      currentImageSelectedFrame, currentImage->getFrameCount(), applicability);
    if (voi) return lut->assign(*voi);
  }
  return EC_IllegalCall;
}

OFCondition DVPresentationState::setVOIWindowFromImage(size_t idx,
    DVPSObjectApplicability applicability)
{
  currentImageVOIValid = OFFalse; // VOI has changed
  DVPSVOIWindow *window = currentImageVOIWindowList.getVOIWindow(idx);
  if (window)
  {
    return setVOIWindow(window->getWindowCenter(),
      window->getWindowWidth(), window->getExplanation(), applicability);
  }
  return EC_IllegalCall;
}

OFCondition DVPresentationState::setVOIWindow(double wCenter, double wWidth, const char *description,
    DVPSObjectApplicability applicability)
{
  if (currentImage == NULL) return EC_IllegalCall;
  currentImageVOIValid = OFFalse; // VOI has changed
  DVPSSoftcopyVOI *voi = softcopyVOIList.createSoftcopyVOI(
      referencedSeriesList, currentImageSOPClassUID, currentImageSOPInstanceUID,
      currentImageSelectedFrame, currentImage->getFrameCount(), applicability);
  if (voi) return voi->setVOIWindow(wCenter, wWidth, description);
  return EC_IllegalCall;
}

OFCondition DVPresentationState::setVOILUT(
    DcmUnsignedShort& lutDescriptor,
    DcmUnsignedShort& lutData,
    DcmLongString& lutExplanation,
    DVPSObjectApplicability applicability)
{
  if (lutData.getLength() == 0) return EC_IllegalCall;
  if (lutDescriptor.getVM() != 3) return EC_IllegalCall;
  if (currentImage == NULL) return EC_IllegalCall;

  currentImageVOIValid = OFFalse; // VOI has changed
  DVPSSoftcopyVOI *voi = softcopyVOIList.createSoftcopyVOI(
      referencedSeriesList, currentImageSOPClassUID, currentImageSOPInstanceUID,
      currentImageSelectedFrame, currentImage->getFrameCount(), applicability);
  if (voi) return voi->setVOILUT(lutDescriptor, lutData, lutExplanation);
  return EC_IllegalCall;
}

void DVPresentationState::deactivateVOI(DVPSObjectApplicability applicability)
{
  if (currentImage == NULL) return;
  currentImageVOIValid = OFFalse; // VOI has changed
  softcopyVOIList.removeSoftcopyVOI(
      referencedSeriesList, currentImageSOPInstanceUID,
      currentImageSelectedFrame, currentImage->getFrameCount(), applicability);
  return;
}

OFCondition DVPresentationState::setGammaVOILUT(double gammaValue, DVPSObjectApplicability applicability)
{
  if (currentImage == NULL) return EC_IllegalCall;

  OFCondition status = EC_IllegalCall;
  const unsigned int numberOfBits = 16;
  unsigned long numberOfEntries = 0;
  signed long firstMapped = 0;
  if (haveActiveVOIWindow())    // use active VOI window to specify the LUT descriptor
  {
    double ww, wc;
    if ((getCurrentWindowWidth(ww) == EC_Normal) && (getCurrentWindowCenter(wc) == EC_Normal))
    {
      if (ww <= 65536)
      {
        numberOfEntries = (unsigned long)ww;
        firstMapped = (signed long)(wc - ww / 2);
      }
    }
  }
  if (numberOfEntries == 0)     // no valid VOI window, use whole pixel range
  {
    double min, max;
    if (getImageMinMaxPixelRange(min, max) == EC_Normal)
    {
      if (max - min < 65536.0)
      {
        numberOfEntries = (unsigned long)(max - min + 1.0);
        firstMapped = (signed long)min;
      }
    }
  }

  if ((numberOfEntries > 0) && (numberOfEntries <= 65536) &&
     (((firstMapped >= -32768) && (firstMapped <= 32767)) || ((firstMapped >= 0) && (firstMapped <= 65535))))
  {
    Uint16 *data = new Uint16[numberOfEntries];
    if (data != NULL)
    {
      /* calculate gamma curve */
      const Uint16 maxValue = 0xFFFF >> (16 - numberOfBits);
      double step = (double)maxValue / ((double)numberOfEntries - 1.0);
      double gammaExp = 1.0 / gammaValue;
      double factor = (double)maxValue / pow((double)maxValue, gammaExp);
      unsigned long i;
      for (i = 0; i < numberOfEntries; i++)
        data[i]= (Uint16)(factor * pow(i * step, gammaExp));

      Uint16 numEntries16 = 0;
      if (numberOfEntries < 65536)
        numEntries16 = (Uint16)numberOfEntries;

      /* LUT Descriptor */
      DcmElement *lutDescriptor = NULL;
      if (firstMapped < 0)
      {
        // LUT Descriptor is SS
        lutDescriptor = new DcmSignedShort(DcmTag(DCM_LUTDescriptor, EVR_SS));
        if (lutDescriptor != NULL)
        {
            status = lutDescriptor->putSint16((Sint16)numEntries16, 0);
            if (EC_Normal == status)
              status = lutDescriptor->putSint16((Sint16)firstMapped, 1);
            if (EC_Normal == status)
              status = lutDescriptor->putSint16((Sint16)numberOfBits, 2);
        } else
          status = EC_MemoryExhausted;
      } else {
        // LUT Descriptor is US
        lutDescriptor = new DcmUnsignedShort(DcmTag(DCM_LUTDescriptor, EVR_US));
        if (lutDescriptor != NULL)
        {
            status = lutDescriptor->putUint16(numEntries16, 0);
            if (EC_Normal == status)
              status = lutDescriptor->putUint16((Uint16)firstMapped, 1);
            if (EC_Normal == status)
              status = lutDescriptor->putUint16((Uint16)numberOfBits, 2);
        } else
            status = EC_MemoryExhausted;
      }

      /* LUT Data */
      DcmElement *lutData = NULL;
      if (status == EC_Normal)
      {
        // LUT Data as OW, because of max size = 64K
        lutData = new DcmOtherByteOtherWord(DcmTag(DCM_LUTData, EVR_OW));
        if (lutData != NULL)
          status = lutData->putUint16Array(data, numberOfEntries);
        else
          status = EC_MemoryExhausted;
      }

      /* LUT Explanation */
      DcmLongString *lutExplanation = NULL;
      if (status == EC_Normal)
      {
        char explanation[100];
        char gammabuf[16];
        OFStandard::ftoa(gammabuf, sizeof(gammabuf), gammaValue, OFStandard::ftoa_format_f, 3, 1);

        sprintf(explanation, "LUT with gamma %s, descriptor %u/%ld/%u", gammabuf,
               (numberOfEntries < 65536) ? (Uint16)numberOfEntries : 0, firstMapped, numberOfBits);

        lutExplanation = new DcmLongString(DCM_LUTExplanation);
        if (lutExplanation != NULL)
          status = lutExplanation->putString(explanation);
        else
          status = EC_MemoryExhausted;
      }

      /* set VOI LUT */
      if (status == EC_Normal)
      {
        if ((lutDescriptor != NULL) && (lutData != NULL) && (lutExplanation !=  NULL))
          status = setVOILUT(*(DcmUnsignedShort *)lutDescriptor, *(DcmUnsignedShort *)lutData, *lutExplanation, applicability);
      }

      /* delete temporary dcmtk structures */
      delete lutDescriptor;
      delete lutData;
      delete lutExplanation;
    } else
      status = EC_MemoryExhausted;
    delete[] data;
  }
  return status;
}


const char *DVPresentationState::getActiveOverlayLabel(size_t layer, size_t idx)
{
  Uint16 group = getActiveOverlayGroup(layer, idx);
  if (group==0) return NULL;
  DVPSOverlay *internalOverlay = overlayList.getOverlayGroup(group);
  if (internalOverlay) return internalOverlay->getOverlayLabel();
  if (currentImage) return currentImage->getOverlayLabel(group);
  return NULL;
}

const char *DVPresentationState::getActiveOverlayDescription(size_t layer, size_t idx)
{
  Uint16 group = getActiveOverlayGroup(layer, idx);
  if (group==0) return NULL;
  DVPSOverlay *internalOverlay = overlayList.getOverlayGroup(group);
  if (internalOverlay) return internalOverlay->getOverlayDescription();
  if (currentImage) return currentImage->getOverlayDescription(group);
  return NULL;
}

OFBool DVPresentationState::activeOverlayIsROI(size_t layer, size_t idx)
{
  Uint16 group = getActiveOverlayGroup(layer, idx);
  if (group==0) return OFFalse;
  DVPSOverlay *internalOverlay = overlayList.getOverlayGroup(group);
  if (internalOverlay) return internalOverlay->isROI();
  if ((currentImage)&&(EMO_RegionOfInterest == currentImage->getOverlayMode(group))) return OFTrue;
  return OFFalse;
}

size_t DVPresentationState::getNumberOfOverlaysInImage()
{
  if (currentImage == NULL) return 0;

  unsigned int numOverlays=currentImage->getOverlayCount();
  size_t result = (size_t) numOverlays;
  Uint16 group;
  for (unsigned int i=0; i<numOverlays; i++)
  {
    // ignore all overlays which are shadowed by the presentation state
    group = (Uint16)(currentImage->getOverlayGroupNumber(i));
    if ((group==0)||(overlayList.haveOverlayGroup(group))) result--;
  }
  return result;
}


Uint16 DVPresentationState::getOverlayInImageGroup(size_t idx)
{
  if (currentImage == NULL) return 0;

  size_t currentIndex = 0;
  Uint16 group;
  do
  {
    group = (Uint16) (currentImage->getOverlayGroupNumber(currentIndex++));
    if (!overlayList.haveOverlayGroup(group))
    {
      // group is not shadowed by the presentation state
      if (idx==0) return group; else idx--;
    }
  } while (group != 0);
  return 0;
}


const char *DVPresentationState::getOverlayInImageLabel(size_t idx)
{
  Uint16 group = getOverlayInImageGroup(idx); // returns 0 if currentImage==NULL
  if (group==0) return NULL; else return currentImage->getOverlayLabel(group);
}


const char *DVPresentationState::getOverlayInImageDescription(size_t idx)
{
  Uint16 group = getOverlayInImageGroup(idx); // returns 0 if currentImage==NULL
  if (group==0) return NULL; else return currentImage->getOverlayDescription(group);
}


size_t DVPresentationState::getOverlayInImageActivationLayer(size_t idx)
{
  Uint16 group = getOverlayInImageGroup(idx); // returns 0 if currentImage==NULL
  if (group==0) return DVPS_IDX_NONE;
  const char *layerName = activationLayerList.getActivationLayer(group);
  if (layerName) return graphicLayerList.getGraphicLayerIndex(layerName);
  else return DVPS_IDX_NONE;
}


OFBool DVPresentationState::overlayInImageIsROI(size_t idx)
{
  Uint16 group = getOverlayInImageGroup(idx); // returns 0 if currentImage==NULL
  if (group==0) return OFFalse;
  if (EMO_RegionOfInterest == currentImage->getOverlayMode(group)) return OFTrue;
  else return OFFalse;
}

OFCondition DVPresentationState::removeOverlayFromPresentationState(size_t idx)
{
  Uint16 group = getOverlayInPresentationStateGroup(idx);
  if (group != 0)
  {
    activationLayerList.removeActivation(group);
    currentImageOverlaysValid = 1; // invalid but nothing added
    return overlayList.removeOverlay(idx);
  }
  return EC_IllegalCall; // overlay does not exist
}


Uint16 DVPresentationState::findOverlayGroup(Uint16 currentGroup)
{
  int allocated[16];
  size_t i, max;
  Uint16 group = 0;

  for (i=0; i<16; i++) allocated[i]=0;
  max = overlayList.size();
  for (i=0; i<max; i++)
  { // mark all group numbers used in presentation state
    group = getOverlayInPresentationStateGroup(i);
    if ((group >= 0x6000)&&(group <= 0x601F)) allocated[(Uint16)(group - 0x6000) >> 1] = 2;
  }
  max = getNumberOfOverlaysInImage();
  for (i=0; i<max; i++)
  { // mark all group numbers used in image
    group = getOverlayInImageGroup(i);
    if ((group >= 0x6000)&&(group <= 0x601F)) allocated[(Uint16)(group - 0x6000) >> 1] += 1;
  }
  // now we have 0=unused, 1=used in image, 2=used in pstate, 3=used in both.

  // check if the current group can be left unchanged
  if ((currentGroup >= 0x6000)&&(currentGroup <= 0x601F))
  {
    if (allocated[(Uint16)(group - 0x6000) >> 1] == 2) return currentGroup;
  }
  // find a free group
  for (i=0; i<16; i++) if (allocated[i]==0) return 0x6000+(i<<1);
  // find a group not used in the presentation state
  for (i=0; i<16; i++) if (allocated[i]<2) return 0x6000+(i<<1);
  // not found.
  return 0;
}

OFCondition DVPresentationState::changeOverlayGroupInPresentationState(size_t idx, Uint16 newGroup)
{
  Uint16 group = getOverlayInPresentationStateGroup(idx);
  if (group != 0)
  {
    if (newGroup==0) newGroup = findOverlayGroup(group);
    if (newGroup==group) return EC_Normal; // shortcut - no change needed
    OFCondition result = overlayList.changeOverlayGroup(idx, newGroup);
    if (EC_Normal == result)
    {
      const char *layerName = activationLayerList.getActivationLayer(group);
      if (layerName)
      {
        activationLayerList.removeActivation(group);
        result = activationLayerList.setActivation(newGroup, layerName);
        currentImageOverlaysValid = 0; // invalid
      }
    }
    return result;
  }
  return EC_IllegalCall;
}

OFCondition DVPresentationState::addOverlayToPresentationState(DcmItem& overlayIOD, Uint16 groupInItem, Uint16 newGroup)
{
  if (newGroup==0) newGroup = findOverlayGroup();
  if (newGroup==0) return EC_IllegalCall; // no group number available
  return overlayList.addOverlay(overlayIOD, groupInItem, newGroup);
}

OFBool DVPresentationState::overlayIsSuitableAsBitmapShutter(size_t idx)
{
  if (currentImage)
  {
    DVPSOverlay *overlay = overlayList.getOverlay(idx);
    if (overlay)
    {
      return overlay->isSuitableAsShutter(currentImage->getWidth(), currentImage->getHeight());
    }
  }
  return OFFalse;
}

OFCondition DVPresentationState::activateOverlayInImage(size_t layer, size_t idx)
{
  Uint16 group = getOverlayInImageGroup(idx);
  if (group==0) return EC_IllegalCall;
  if (activationLayerList.getActivationLayer(group) != NULL) return EC_IllegalCall; //already activated

  const char *layerName = getGraphicLayerName(layer);
  if (layerName==NULL) return EC_IllegalCall;
  currentImageOverlaysValid = 1; // invalid but nothing (external) added
  return activationLayerList.setActivation(group, layerName);
}

OFCondition DVPresentationState::activateOverlayInPresentationState(size_t layer, size_t idx)
{
  Uint16 group = getOverlayInPresentationStateGroup(idx);
  if (group==0) return EC_IllegalCall;
  if (activationLayerList.getActivationLayer(group) != NULL) return EC_IllegalCall; //already activated
  if (haveShutter(DVPSU_bitmap))
  { // check if the overlay is used as the bitmap shutter
    Uint16 shutterGroup=0;
    shutterOverlayGroup.getUint16(shutterGroup,0);
    if (shutterGroup == group) return EC_IllegalCall; // used as bitmap shutter
  }
  const char *layerName = getGraphicLayerName(layer);
  if (layerName==NULL) return EC_IllegalCall;
  currentImageOverlaysValid = 0; // invalid
  return activationLayerList.setActivation(group, layerName);
}

OFCondition DVPresentationState::activateOverlayAsBitmapShutter(size_t idx)
{
  Uint16 group = getOverlayInPresentationStateGroup(idx);
  if (group==0) return EC_IllegalCall;
  if (activationLayerList.getActivationLayer(group) != NULL) return EC_IllegalCall; // activated as overlay
  if (overlayIsSuitableAsBitmapShutter(idx))
  {
    shutterOverlayGroup.clear();
    OFCondition result = shutterOverlayGroup.putUint16(group,0);
    if ((EC_Normal==result)&&(shutterPresentationValue.getLength()==0))
        result = shutterPresentationValue.putUint16(0,0);
    if (EC_Normal==result)
    {
      useShutterRectangular = OFFalse;
      useShutterCircular = OFFalse;
      useShutterPolygonal = OFFalse;
      useShutterBitmap = OFTrue;
      currentImageOverlaysValid = 0; // invalid
    }
    return result;
  }
  return EC_IllegalCall;
}

OFCondition DVPresentationState::deactivateOverlay(size_t layer, size_t idx)
{
  Uint16 group = activationLayerList.getActivationGroup(
    graphicLayerList.getGraphicLayerName(layer), idx, OFFalse);
  if (group == 0) return EC_IllegalCall;
  activationLayerList.removeActivation(group);
  currentImageOverlaysValid = 1; // invalid but nothing added
  return EC_Normal;
}

Uint16 DVPresentationState::convertPValueToDDL(Uint16 pvalue, unsigned int bits)
{
  Uint16 result = 0;
  if ((bits == 8) || (bits == 12))
  {
    if (currentImage && (bits == 8))
    {
      /* activate display transform */
      if (displayFunction && (displayTransform != DVPSD_none))
        currentImage->setDisplayFunction(displayFunction[displayTransform]);
      else
        currentImage->setNoDisplayFunction();
      currentImage->convertPValueToDDL(pvalue, result, bits /* 8 */);
    } else
      result = (pvalue >> (16 - bits));
  }
  return result;
}


void DVPresentationState::renderPixelData(OFBool display)
{
  if (currentImage == NULL) return;
  int result=0;

  /* activate Barten transform */
  if (displayFunction && (displayTransform != DVPSD_none) && display)
  {
    currentImage->setDisplayFunction(displayFunction[displayTransform]);
    if (previewImage != NULL)
      previewImage->setDisplayFunction(displayFunction[displayTransform]);
  } else {
    currentImage->setNoDisplayFunction();
    if (previewImage != NULL)
      previewImage->setNoDisplayFunction();
  }

  if (! currentImageVOIValid)
  {
     currentImageVOIValid = OFTrue;

     /* set VOI transformation */
     DVPSSoftcopyVOI *voi = getCurrentSoftcopyVOI();
     if (voi)
     {
       if (voi->haveLUT())
       {
         result = currentImage->setVoiLut(voi->getLUTData(), voi->getLUTDescriptor());
         if (previewImage != NULL)
           previewImage->setVoiLut(voi->getLUTData(), voi->getLUTDescriptor());
       } else {
         double wc=0.0, ww=0.0;
         if ((EC_Normal == voi->getCurrentWindowCenter(wc)) &&
             (EC_Normal == voi->getCurrentWindowWidth(ww)))
         {
           result = currentImage->setWindow(wc, ww);
           if (previewImage != NULL)
             previewImage->setWindow(wc, ww);
         } else {
           result = currentImage->setNoVoiTransformation();
           if (previewImage != NULL)
             previewImage->setNoVoiTransformation();
         }
       }
     } else {
       result = currentImage->setNoVoiTransformation();
       if (previewImage != NULL)
         previewImage->setNoVoiTransformation();
     }
     if (!result)
       DCMPSTAT_WARN("unable to set VOI transformation, ignoring.");
  } /* VOI transform */

  if (! currentImagePLUTValid)
  {
     presentationLUT.activate(currentImage);
     if (previewImage != NULL)
       presentationLUT.activate(previewImage);
     currentImagePLUTValid = OFTrue;
  } /* Presentation LUT */

  Uint16 bitmapShutterGroup = 0;
  Uint16 bitmapShutterPValue = 0;
  if (useShutterBitmap)
  {
    if (EC_Normal != shutterOverlayGroup.getUint16(bitmapShutterGroup, 0)) bitmapShutterGroup=0;
    if (EC_Normal != shutterPresentationValue.getUint16(bitmapShutterPValue, 0)) bitmapShutterPValue=0;
  }

  if (currentImageOverlaysValid==1)
  {
    /* overlays are invalid but no external overlays have been added */
    /* remove all external overlays that are not active as overlay or bitmap shutter */
    for (unsigned int remgroup=0x6000; remgroup <= 0x601F; remgroup += 2)
    {
      if ((remgroup != bitmapShutterGroup)&&((! overlayList.haveOverlayGroup(remgroup))||
          (NULL == activationLayerList.getActivationLayer(remgroup))))
      {
         currentImage->removeOverlay(remgroup); // ignore return value.
      }
    }
    currentImageOverlaysValid = 2; // valid
  }
  else if (currentImageOverlaysValid==0)
  {
    /* overlays are invalid */

    /* since we might be required to add external overlays, we first
     * need to flip the image back to its original rotation and flip state
     */
    if (currentImageFlip)
    {
      result = currentImage->flipImage();
      if (previewImage != NULL) previewImage->flipImage();
      if (!result)
        DCMPSTAT_WARN("unable to flip image horizontally, ignoring.");
      currentImageFlip = OFFalse;
    }
    signed int srot=0;
    switch(currentImageRotation)
    {
      case DVPSR_0_deg:   break;
      case DVPSR_90_deg:  srot=270; break;
      case DVPSR_180_deg: srot=180; break;
      case DVPSR_270_deg: srot=90; break;
    }
    if (srot != 0)
    {
      result = currentImage->rotateImage(srot);
      if (previewImage != NULL)
        previewImage->rotateImage(srot);
      if (!result)
        DCMPSTAT_WARN("unable to rotate image by " << srot << " degrees, ignoring.");
    }
    currentImageRotation = DVPSR_0_deg;

    // deactivate all overlays first
    result = currentImage->removeAllOverlays();
    if (!result)
      DCMPSTAT_WARN("unable to disable external overlays, ignoring.");

    size_t numOverlays = overlayList.size();
    DVPSOverlay *overlay = NULL;
    Uint16 ovgroup;
    for (size_t ovidx=0; ovidx<numOverlays; ovidx++)
    {
      overlay = overlayList.getOverlay(ovidx);
      if (overlay)
      {
        ovgroup = overlay->getOverlayGroup()+ 0x6000;
        if (NULL != activationLayerList.getActivationLayer(ovgroup))
        {
          if (activateOverlayHelper(*overlay, *currentImage).bad())
          {
            if (!result)
              DCMPSTAT_WARN("unable to set external overlay group 0x"
                << STD_NAMESPACE hex << ovgroup << STD_NAMESPACE dec << ", ignoring.");
          }
        }
        else if ((useShutterBitmap)&&(ovgroup == bitmapShutterGroup))
        {
          //activate bitmap overlay
          if (activateOverlayHelper(*overlay, *currentImage, OFTrue, bitmapShutterPValue).bad())
          {
            if (!result)
              DCMPSTAT_WARN("unable to activate bitmap shutter 0x"
                << STD_NAMESPACE hex << ovgroup << STD_NAMESPACE dec << ", ignoring.");
          }
        }
      }
    }
    currentImageOverlaysValid = 2; // valid
  }

  OFBool pstateFlip = getFlip();
  DVPSRotationType pstateRotation = getRotation();

  // store image width and height after application of rotation
  if ((pstateRotation == DVPSR_90_deg) || (pstateRotation == DVPSR_270_deg))
  {
    renderedImageWidth = currentImageHeight;
    renderedImageHeight = currentImageWidth;
  } else {
    renderedImageWidth = currentImageWidth;
    renderedImageHeight = currentImageHeight;
  }

  // get coordinates of current displayed area and store values after the
  // 'virtual' anti-rotation has been applied
  Sint32 tlhcX = 1;
  Sint32 tlhcY = 1;
  Sint32 brhcX = currentImageWidth;
  Sint32 brhcY = currentImageHeight;
  getImageRelativeDisplayedArea(tlhcX, tlhcY, brhcX, brhcY);
  if (tlhcX > brhcX)
  {                                     // swap 'left' and 'right' if necessary
    Sint32 tmp = tlhcX;
    tlhcX = brhcX;
    brhcX = tmp;
  }
  if (tlhcY > brhcY)
  {                                     // swap 'top' and 'bottom' if necessary
    Sint32 tmp = tlhcY;
    tlhcY = brhcY;
    brhcY = tmp;
  }
  switch (pstateRotation)
  {
    case DVPSR_0_deg:
      renderedImageTop = tlhcY;
      renderedImageLeft = tlhcX;
      renderedImageBottom = brhcY;
      renderedImageRight = brhcX;
      break;
    case DVPSR_90_deg:
      renderedImageTop = tlhcX;
      renderedImageLeft = (signed long)currentImageHeight - brhcY + 1;
      renderedImageBottom = brhcX;
      renderedImageRight = (signed long)currentImageHeight - tlhcY + 1;
      break;
    case DVPSR_180_deg:
      renderedImageTop = (signed long)currentImageHeight - brhcY + 1;
      renderedImageLeft = (signed long)currentImageWidth - brhcX + 1;
      renderedImageBottom = (signed long)currentImageHeight - tlhcY + 1;
      renderedImageRight = (signed long)currentImageWidth - tlhcX + 1;
      break;
    case DVPSR_270_deg:
      renderedImageTop = (signed long)currentImageWidth - brhcX + 1;
      renderedImageLeft = tlhcY;
      renderedImageBottom = (signed long)currentImageWidth - tlhcX + 1;
      renderedImageRight = brhcY;
      break;
  }
  if (pstateFlip)
  {
    signed long tmp = renderedImageLeft;
    renderedImageLeft = (signed long)renderedImageWidth - renderedImageRight + 1;
    renderedImageRight = (signed long)renderedImageWidth - tmp + 1;
  }

  // we can always reach the final rotation/flip status with
  // at most one rotation and one flip. The following formula
  // derives the operations to perform.
  OFBool flp=OFFalse;
  if ((pstateFlip && !currentImageFlip)||(!pstateFlip && currentImageFlip)) flp=OFTrue; else flp=OFFalse;

  signed int rot=0;
  switch (pstateRotation)
  {
    case DVPSR_0_deg:
      switch(currentImageRotation)
      {
        case DVPSR_0_deg:   rot=0; break;
        case DVPSR_90_deg:  if (currentImageFlip) rot=90; else rot=270; break;
        case DVPSR_180_deg: rot=180; break;
        case DVPSR_270_deg: if (currentImageFlip) rot=270; else rot=90; break;
      }
      break;
    case DVPSR_90_deg:
      switch(currentImageRotation)
      {
        case DVPSR_0_deg:   if (currentImageFlip) rot=270; else rot=90; break;
        case DVPSR_90_deg:  rot=0; break;
        case DVPSR_180_deg: if (currentImageFlip) rot=90; else rot=270; break;
        case DVPSR_270_deg: rot=180; break;
      }
      break;
    case DVPSR_180_deg:
      switch(currentImageRotation)
      {
        case DVPSR_0_deg:   rot=180; break;
        case DVPSR_90_deg:  if (currentImageFlip) rot=270; else rot=90; break;
        case DVPSR_180_deg: rot=0; break;
        case DVPSR_270_deg: if (currentImageFlip) rot=90; else rot=270; break;
      }
      break;
    case DVPSR_270_deg:
      switch(currentImageRotation)
      {
        case DVPSR_0_deg:   if (currentImageFlip) rot=90; else rot=270; break;
        case DVPSR_90_deg:  rot=180; break;
        case DVPSR_180_deg: if (currentImageFlip) rot=270; else rot=90; break;
        case DVPSR_270_deg: rot=0; break;
      }
      break;
  }

  if (rot != 0)
  {
    result = currentImage->rotateImage(rot);
    if (previewImage != NULL) previewImage->rotateImage(rot);
    if (!result)
      DCMPSTAT_WARN("unable to rotate image by " << rot << " degrees, ignoring.");
  }

  if (flp)
  {
    result = currentImage->flipImage();
    if (previewImage != NULL) previewImage->flipImage();
    if (!result)
      DCMPSTAT_WARN("unable to flip image horizontally, ignoring.");
  }

  currentImageRotation = pstateRotation;
  currentImageFlip = pstateFlip;

  return;
}


OFCondition DVPresentationState::getOverlayData(
     size_t layer,
     size_t idx,
     const void *&overlayData,
     unsigned int &width,
     unsigned int &height,
     unsigned int &left_pos,
     unsigned int &top_pos,
     OFBool &isROI,
     Uint16 &fore,
     unsigned int bits)
{
   EM_Overlay mode = EMO_Default;
   if (currentImage && ((bits == 8) || (bits == 12)))
   {
     renderPixelData();
     Uint16 group = activationLayerList.getActivationGroup(graphicLayerList.getGraphicLayerName(layer), idx, OFFalse);
     if (group==0) return EC_IllegalCall;
     Uint16 back = 0;
     fore = (Uint16)DicomImageClass::maxval(bits);   /* 255 or 4095 */
     Uint16 pvalue = 65535;
     if (graphicLayerList.getGraphicLayerRecommendedDisplayValueGray(layer, pvalue) == EC_Normal)
         currentImage->convertPValueToDDL(pvalue, fore, bits);
     if (fore == 0)
         back = (Uint16)DicomImageClass::maxval(bits);
     const void *data = currentImage->getOverlayData((unsigned int)group, left_pos, top_pos, width, height, mode,
       currentImageSelectedFrame-1, bits, fore, back);
     if (EMO_RegionOfInterest == mode) isROI=OFTrue; else isROI=OFFalse;
     if (data) overlayData = data;
     else
     {
       overlayData = NULL;
       return EC_IllegalCall;
     }
   } else {
     overlayData = NULL;
     width = 0;
     height = 0;
     left_pos = 0;
     top_pos = 0;
     isROI = OFFalse;
     fore = 0;
     return EC_IllegalCall;
   }
   return EC_Normal;
}

OFBool DVPresentationState::isInverse()
{
    return imageInverse;
}

OFCondition DVPresentationState::invertImage()
{
  OFCondition status = presentationLUT.invert();
  currentImagePLUTValid = OFFalse; // PLUT has changed
  imageInverse = (imageInverse ? OFFalse : OFTrue);
  return status;
}


OFCondition DVPresentationState::getPixelData(
     const void *&pixelData,
     unsigned long &width,
     unsigned long &height)
{
   if (currentImage)
   {
     renderPixelData();
     width = currentImage->getWidth();
     height = currentImage->getHeight();
     pixelData = currentImage->getOutputData(8, currentImageSelectedFrame-1);
   } else {
     pixelData = NULL;
     width = 0;
     height = 0;
     return EC_IllegalCall;
   }
   return EC_Normal;
}

OFCondition DVPresentationState::getPixelData(
     void *pixelData,
     unsigned long size)
{
   if (currentImage)
   {
     renderPixelData();
     if (currentImage->getOutputData(pixelData, size, 8, currentImageSelectedFrame-1))
         return EC_Normal;
   }
   return EC_IllegalCall;
}

OFCondition DVPresentationState::getImageMinMaxPixelRange(double &minValue, double& maxValue)
{
  OFCondition result = EC_IllegalCall;
  if (currentImage)
  {
    if (currentImage->getMinMaxValues(minValue, maxValue, 1)) result = EC_Normal;
  }
  return result;
}

OFCondition DVPresentationState::getImageMinMaxPixelValue(double &minValue, double& maxValue)
{
  OFCondition result = EC_IllegalCall;
  if (currentImage)
  {
    if (currentImage->getMinMaxValues(minValue, maxValue, 0)) result = EC_Normal;
  }
  return result;
}

OFCondition DVPresentationState::getImageNumberOfFrames(unsigned long &frames)
{
  if (currentImage)
  {
    frames = (unsigned long)(currentImage->getFrameCount());
    return EC_Normal;
  }
  return EC_IllegalCall;
}

OFCondition DVPresentationState::selectImageFrameNumber(unsigned long frame)
{
  if ((frame > 0) && currentImage && (frame <= currentImage->getFrameCount()))
  {
    if (currentImageSelectedFrame != frame)
      currentImageVOIValid = OFFalse; // VOI might has changed
    currentImageSelectedFrame=frame;
    return EC_Normal;
  }
  return EC_IllegalCall;
}

unsigned long DVPresentationState::getSelectedImageFrameNumber()
{
  if (currentImage)
  {
    if (currentImageSelectedFrame <= currentImage->getFrameCount())
      return currentImageSelectedFrame;
  }
  return 0;
}

DVPSDisplayedArea *DVPresentationState::getDisplayedAreaSelection()
{
  if (currentImage==NULL) return NULL;
  DVPSDisplayedArea * area = displayedAreaSelectionList.findDisplayedArea(currentImageSOPInstanceUID, currentImageSelectedFrame);
  if (area==NULL)
  {
      DCMPSTAT_WARN("no displayed area selection item for current image found, creating default.");
      if ((currentImageDataset)&&(EC_Normal == createDefaultDisplayedArea(*currentImageDataset)))
      {
        area = displayedAreaSelectionList.findDisplayedArea(currentImageSOPInstanceUID, currentImageSelectedFrame);
      }
  }
  return area;
}

DVPSPresentationSizeMode DVPresentationState::getDisplayedAreaPresentationSizeMode()
{
  DVPSDisplayedArea * area = getDisplayedAreaSelection();
  if (area) return area->getPresentationSizeMode(); else return DVPSD_scaleToFit;
}

double DVPresentationState::getDisplayedAreaPresentationPixelAspectRatio()
{
  DVPSDisplayedArea * area = getDisplayedAreaSelection();
  if (area) return area->getPresentationPixelAspectRatio(); else return 1.0;
}

OFCondition DVPresentationState::getStandardDisplayedArea(Sint32& tlhcX, Sint32& tlhcY, Sint32& brhcX, Sint32& brhcY)
{
  DVPSDisplayedArea * area = getDisplayedAreaSelection();
  if (area)
  {
    area->getDisplayedArea(tlhcX, tlhcY, brhcX, brhcY);
    return EC_Normal;
  }
  return EC_IllegalCall;
}

OFCondition DVPresentationState::getImageRelativeDisplayedArea(Sint32& tlhcX, Sint32& tlhcY, Sint32& brhcX, Sint32& brhcY)
{
  DVPSDisplayedArea * area = getDisplayedAreaSelection();
  if (area)
  {
    DVPSRotationType rotation = getRotation();
    OFBool flip = getFlip();
    area = area->clone(); // create temporary copy
    area->rotateAndFlip(rotation, flip, DVPSR_0_deg, OFFalse);
    area->getDisplayedArea(tlhcX, tlhcY, brhcX, brhcY);
    delete area;
    return EC_Normal;
  }
  return EC_IllegalCall;
}

OFCondition DVPresentationState::getDisplayedAreaPresentationPixelSpacing(double& x, double& y)
{
  DVPSDisplayedArea * area = getDisplayedAreaSelection();
  if (area) return area->getPresentationPixelSpacing(x, y); else return EC_IllegalCall;
}

OFCondition DVPresentationState::getDisplayedAreaPresentationPixelMagnificationRatio(double& magnification)
{
  DVPSDisplayedArea * area = getDisplayedAreaSelection();
  if (area) return area->getPresentationPixelMagnificationRatio(magnification); else return EC_IllegalCall;
}

OFBool DVPresentationState::canUseDisplayedAreaTrueSize()
{
  DVPSDisplayedArea * area = getDisplayedAreaSelection();
  if (area) return area->canUseTrueSize(); else return OFFalse;
}

OFCondition DVPresentationState::setStandardDisplayedArea(
     DVPSPresentationSizeMode sizeMode,
     Sint32 tlhcX, Sint32 tlhcY,
     Sint32 brhcX, Sint32 brhcY,
     double magnification,
     DVPSObjectApplicability applicability)
{
  if (currentImage == NULL) return EC_IllegalCall;

  // make sure that we have an old displayed area item that "knows" about pixel spacing/aspect ratio,
  // because we will only copy this data into the new item.
  DVPSDisplayedArea *area = getDisplayedAreaSelection();

  // find appropriate item, create new if necessary.
  area = displayedAreaSelectionList.createDisplayedArea(
      referencedSeriesList, currentImageSOPClassUID, currentImageSOPInstanceUID,
      currentImageSelectedFrame, currentImage->getFrameCount(), applicability);

  if (area) return area->setDisplayedArea(sizeMode, tlhcX, tlhcY, brhcX, brhcY, magnification);
  return EC_IllegalCall;
}

OFCondition DVPresentationState::setImageRelativeDisplayedArea(
     DVPSPresentationSizeMode sizeMode,
     Sint32 tlhcX, Sint32 tlhcY,
     Sint32 brhcX, Sint32 brhcY,
     double magnification,
     DVPSObjectApplicability applicability)
{
  if (currentImage == NULL) return EC_IllegalCall;

  // make sure that we have an old displayed area item that "knows" about pixel spacing/aspect ratio,
  // because we will only copy this data into the new item.
  DVPSDisplayedArea *area = getDisplayedAreaSelection();

  // find appropriate item, create new if necessary.
  area = displayedAreaSelectionList.createDisplayedArea(
      referencedSeriesList, currentImageSOPClassUID, currentImageSOPInstanceUID,
      currentImageSelectedFrame, currentImage->getFrameCount(), applicability);

  if (area)
  {
     // get current rotation and flip status
     DVPSRotationType rotation = getRotation();
     OFBool flip = getFlip();

     // force rotation and flip status back to unrotated/unflipped
     // because in this case standard displayed area and image relative displayed area are the same
     area->rotateAndFlip(rotation, flip, DVPSR_0_deg, OFFalse);

     // set displayed area
     OFCondition result = area->setDisplayedArea(sizeMode, tlhcX, tlhcY, brhcX, brhcY, magnification);

     // restore rotation and flip status
     area->rotateAndFlip(DVPSR_0_deg, OFFalse, rotation, flip);

     return result;
  }
  return EC_IllegalCall;
}


DVPSSoftcopyVOI *DVPresentationState::getCurrentSoftcopyVOI()
{
  if (currentImage==NULL) return NULL;
  return softcopyVOIList.findSoftcopyVOI(currentImageSOPInstanceUID, currentImageSelectedFrame);
}

OFCondition DVPresentationState::getPrintBitmapRequestedImageSize(OFString& requestedImageSize)
{
  requestedImageSize.clear();
  if ((currentImage)&&(getDisplayedAreaPresentationSizeMode()==DVPSD_trueSize))
  {
    double x=0.0, y=0.0;
    if (EC_Normal == getDisplayedAreaPresentationPixelSpacing(x, y))
    {
      char c[80];
      DVPSRotationType rotation = getRotation();
      if ((rotation==DVPSR_90_deg)||(rotation==DVPSR_270_deg))
      {
        x = y * currentImageHeight; // physical height of unrotated image in mm
      } else {
        x *= currentImageWidth;  // physical width of unrotated image in mm
      }
      OFStandard::ftoa(c, sizeof(c), x, OFStandard::ftoa_format_f);

      requestedImageSize = c;
      return EC_Normal;
    }
  }
  return EC_IllegalCall;
}

OFCondition DVPresentationState::writePresentationLUTforPrint(DcmItem &dset)
{
  OFCondition result = EC_Normal;
  if (currentImageMonochrome1)
  {
    // write inverted LUT because image is also converted to MONOCHROME2
    presentationLUT.invert();
    if (EC_Normal==result) result = presentationLUT.write(dset, OFFalse);
    presentationLUT.invert();
  } else result = presentationLUT.write(dset, OFFalse);
  return result;
}

const char *DVPresentationState::getCurrentImageModality()
{
  char *c = NULL;
  if (EC_Normal == currentImageModality.getString(c)) return c; else return NULL;
}

const char *DVPresentationState::getAttachedImageSOPClassUID()
{
  return currentImageSOPClassUID;
}


const char *DVPresentationState::getAttachedImageSOPInstanceUID()
{
  return currentImageSOPInstanceUID;
}


OFCondition DVPresentationState::activateOverlayHelper(DVPSOverlay& ovl, DicomImage &image, OFBool asShutter, Uint16 pvalue)
{
  Sint16 originX=0;
  Sint16 originY=0;
  Uint16 sizeX=0;
  Uint16 sizeY=0;
  unsigned int group = ovl.getOverlayGroup() + 0x6000;
  EM_Overlay mode=EMO_Graphic;
  if (asShutter) mode=EMO_BitmapShutter; else if (ovl.isROI()) mode=EMO_RegionOfInterest;

  OFCondition result = ovl.getValues(originX, originY, sizeX, sizeY);
  if (result.good())
  {
    signed int left_pos = (signed int) originX;
    signed int top_pos = (signed int) originY;
    unsigned int columns = (unsigned int)sizeX;
    unsigned int rows = (unsigned int)sizeY;
    if (0 == image.addOverlay(group, left_pos, top_pos, columns, rows,
      ovl.getData(), ovl.getLabel(), ovl.getDescription(), mode))
        result = EC_IllegalCall;
    if ((asShutter)&&(EC_Normal==result))
    {
      if (0 == image.showOverlay(group, pvalue)) result = EC_IllegalCall;
    }
  }
  return result;
}

OFCondition DVPresentationState::read(DcmItem &dset)
{
  OFCondition result = DcmPresentationState::read(dset);
  imageInverse = presentationLUT.isInverse();
  return result;
}

OFCondition DVPresentationState::createFromImage(
  DcmItem &dset,
  DVPSoverlayActivation overlayActivation,
  DVPSVOIActivation voiActivation,
  OFBool curveActivation,
  OFBool shutterActivation,
  OFBool presentationActivation,
  DVPSGraphicLayering layering,
  const char *aetitle,
  const char *filesetID,
  const char *filesetUID)
{
  OFCondition result = DcmPresentationState::createFromImage(
    dset, overlayActivation, voiActivation, curveActivation, shutterActivation,
    presentationActivation, layering, aetitle, filesetID, filesetUID);
  imageInverse = presentationLUT.isInverse();
  return result;
}
