/*
 *
 *  Copyright (C) 2002-2021, OFFIS e.V.
 *  All rights reserved.  See COPYRIGHT file for details.
 *
 *  This software and supporting documentation were developed by
 *
 *    OFFIS e.V.
 *    R&D Division Health
 *    Escherweg 2
 *    D-26121 Oldenburg, Germany
 *
 *
 *  Module:  dcmimage
 *
 *  Authors: Joerg Riesmeier
 *
 *  Purpose: Scale DICOM images
 *
 */


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

#include "dcmtk/dcmdata/dctk.h"          /* for various dcmdata headers */
#include "dcmtk/dcmdata/cmdlnarg.h"      /* for prepareCmdLineArgs */
#include "dcmtk/dcmdata/dcuid.h"         /* for dcmtk version name */

#include "dcmtk/ofstd/ofconapp.h"        /* for OFConsoleApplication */
#include "dcmtk/ofstd/ofcmdln.h"         /* for OFCommandLine */

#include "dcmtk/oflog/oflog.h"           /* for OFLogger */

#include "dcmtk/dcmimgle/dcmimage.h"     /* for DicomImage */
#include "dcmtk/dcmimage/diregist.h"     /* include to support color images */
#include "dcmtk/dcmdata/dcrledrg.h"      /* for DcmRLEDecoderRegistration */

#ifdef BUILD_DCMSCALE_AS_DCMJSCAL
#include "dcmtk/dcmjpeg/djdecode.h"      /* for dcmjpeg decoders */
#include "dcmtk/dcmjpeg/dipijpeg.h"      /* for dcmimage JPEG plugin */
#endif

#ifdef WITH_ZLIB
#include <zlib.h>          /* for zlibVersion() */
#endif

#define OFFIS_CONSOLE_DESCRIPTION "Scale DICOM images"

#ifdef BUILD_DCMSCALE_AS_DCMJSCAL
#define OFFIS_CONSOLE_APPLICATION "dcmjscal"
#else
#define OFFIS_CONSOLE_APPLICATION "dcmscale"
#endif

static OFLogger dcmscaleLogger = OFLog::getLogger("dcmtk.apps." OFFIS_CONSOLE_APPLICATION);

static char rcsid[] = "$dcmtk: " OFFIS_CONSOLE_APPLICATION " v"
  OFFIS_DCMTK_VERSION " " OFFIS_DCMTK_RELEASEDATE " $";

#define SHORTCOL 4
#define LONGCOL 21


// ********************************************

int main(int argc, char *argv[])
{
    OFConsoleApplication app(OFFIS_CONSOLE_APPLICATION, OFFIS_CONSOLE_DESCRIPTION, rcsid);
    OFCommandLine cmd;

    OFBool opt_uidCreation = OFTrue;
    E_FileReadMode opt_readMode = ERM_autoDetect;
    E_FileWriteMode opt_writeMode = EWM_createNewMeta;
    E_TransferSyntax opt_ixfer = EXS_Unknown;
    E_TransferSyntax opt_oxfer = EXS_Unknown;
    E_GrpLenEncoding opt_oglenc = EGL_recalcGL;
    E_EncodingType opt_oenctype = EET_ExplicitLength;
    E_PaddingEncoding opt_opadenc = EPD_noChange;
    OFCmdUnsignedInt opt_filepad = 0;
    OFCmdUnsignedInt opt_itempad = 0;

#ifdef BUILD_DCMSCALE_AS_DCMJSCAL
    // JPEG parameters, currently not used
# if 0
    OFCmdUnsignedInt opt_quality = 90;                 /* default: 90% JPEG quality */
    E_SubSampling opt_sampling = ESS_422;              /* default: 4:2:2 sub-sampling */
# endif
    E_DecompressionColorSpaceConversion opt_decompCSconversion = EDC_photometricInterpretation;
#endif

    int opt_useAspectRatio = 1;                        /* default: use aspect ratio for scaling */
    OFCmdUnsignedInt opt_useInterpolation = 1;         /* default: use interpolation method '1' for scaling */
    int opt_scaleType = 0;                             /* default: no scaling */
                                                       /* 1 = X-factor, 2 = Y-factor, 3=X-size, 4=Y-size */
    OFCmdFloat opt_scale_factor = 1.0;
    OFCmdUnsignedInt opt_scale_size = 1;

    OFBool           opt_useClip = OFFalse;            /* default: don't clip */
    OFCmdSignedInt   opt_left = 0, opt_top = 0;        /* clip region (origin) */
    OFCmdUnsignedInt opt_width = 0, opt_height = 0;    /* clip region (extension) */

    const char *opt_ifname = NULL;
    const char *opt_ofname = NULL;

    prepareCmdLineArgs(argc, argv, OFFIS_CONSOLE_APPLICATION);
    cmd.setOptionColumns(LONGCOL, SHORTCOL);

    cmd.addParam("dcmfile-in",  "DICOM input filename to be scaled");
    cmd.addParam("dcmfile-out", "DICOM output filename to be written");

    cmd.addGroup("general options:", LONGCOL, SHORTCOL + 2);
     cmd.addOption("--help",                "-h",       "print this help text and exit", OFCommandLine::AF_Exclusive);
     cmd.addOption("--version",                         "print version information and exit", OFCommandLine::AF_Exclusive);
     OFLog::addOptions(cmd);

    cmd.addGroup("input options:");
     cmd.addSubGroup("input file format:");
      cmd.addOption("--read-file",          "+f",       "read file format or data set (default)");
      cmd.addOption("--read-file-only",     "+fo",      "read file format only");
      cmd.addOption("--read-dataset",       "-f",       "read data set without file meta information");
     cmd.addSubGroup("input transfer syntax:");
      cmd.addOption("--read-xfer-auto",     "-t=",      "use TS recognition (default)");
      cmd.addOption("--read-xfer-detect",   "-td",      "ignore TS specified in the file meta header");
      cmd.addOption("--read-xfer-little",   "-te",      "read with explicit VR little endian TS");
      cmd.addOption("--read-xfer-big",      "-tb",      "read with explicit VR big endian TS");
      cmd.addOption("--read-xfer-implicit", "-ti",      "read with implicit VR little endian TS");

    cmd.addGroup("image processing and encoding options:");
#ifdef BUILD_DCMSCALE_AS_DCMJSCAL
     cmd.addSubGroup("color space conversion options (compressed images only):");
      cmd.addOption("--conv-photometric",   "+cp",      "convert if YCbCr photometric interpr. (default)");
      cmd.addOption("--conv-lossy",         "+cl",      "convert YCbCr to RGB if lossy JPEG");
      cmd.addOption("--conv-guess",         "+cg",      "convert to RGB if YCbCr is guessed by library");
      cmd.addOption("--conv-guess-lossy",   "+cgl",     "convert to RGB if lossy JPEG and YCbCr is\nguessed by the underlying JPEG library");
      cmd.addOption("--conv-always",        "+ca",      "always convert YCbCr to RGB");
      cmd.addOption("--conv-never",         "+cn",      "never convert color space");
#endif
     cmd.addSubGroup("scaling:");
      cmd.addOption("--recognize-aspect",    "+a",      "recognize pixel aspect ratio when scaling (def)");
      cmd.addOption("--ignore-aspect",       "-a",      "ignore pixel aspect ratio when scaling");
      cmd.addOption("--interpolate",         "+i",   1, "[n]umber of algorithm: integer",
                                                        "use interpolation when scaling (1..4, def: 1)");
      cmd.addOption("--no-interpolation",    "-i",      "no interpolation when scaling");
      cmd.addOption("--no-scaling",          "-S",      "no scaling, ignore pixel aspect ratio (default)");
      cmd.addOption("--scale-x-factor",      "+Sxf", 1, "[f]actor: float",
                                                        "scale x axis by factor, auto-compute y axis");
      cmd.addOption("--scale-y-factor",      "+Syf", 1, "[f]actor: float",
                                                        "scale y axis by factor, auto-compute x axis");
      cmd.addOption("--scale-x-size",        "+Sxv", 1, "[n]umber: integer",
                                                        "scale x axis to n pixels, auto-compute y axis");
      cmd.addOption("--scale-y-size",        "+Syv", 1, "[n]umber: integer",
                                                        "scale y axis to n pixels, auto-compute x axis");
     cmd.addSubGroup("other transformations:");
      cmd.addOption("--clip-region",         "+C",   4, "[l]eft [t]op [w]idth [h]eight: integer",
                                                        "clip rectangular image region (l, t, w, h)");
     cmd.addSubGroup("SOP Instance UID:");
      cmd.addOption("--uid-always",          "+ua",     "always assign new SOP Instance UID (default)");
      cmd.addOption("--uid-never",           "+un",     "never assign new SOP Instance UID");

  cmd.addGroup("output options:");
    cmd.addSubGroup("output file format:");
      cmd.addOption("--write-file",          "+F",      "write file format (default)");
      cmd.addOption("--write-dataset",       "-F",      "write data set without file meta information");
    cmd.addSubGroup("output transfer syntax:");
      cmd.addOption("--write-xfer-same",     "+t=",     "write with same TS as input (default)");
      cmd.addOption("--write-xfer-little",   "+te",     "write with explicit VR little endian TS");
      cmd.addOption("--write-xfer-big",      "+tb",     "write with explicit VR big endian TS");
      cmd.addOption("--write-xfer-implicit", "+ti",     "write with implicit VR little endian TS");
    cmd.addSubGroup("post-1993 value representations:");
      cmd.addOption("--enable-new-vr",       "+u",      "enable support for new VRs (UN/UT) (default)");
      cmd.addOption("--disable-new-vr",      "-u",      "disable support for new VRs, convert to OB");
    cmd.addSubGroup("group length encoding:");
      cmd.addOption("--group-length-recalc", "+g=",     "recalculate group lengths if present (default)");
      cmd.addOption("--group-length-create", "+g",      "always write with group length elements");
      cmd.addOption("--group-length-remove", "-g",      "always write without group length elements");
    cmd.addSubGroup("length encoding in sequences and items:");
      cmd.addOption("--length-explicit",     "+e",      "write with explicit lengths (default)");
      cmd.addOption("--length-undefined",    "-e",      "write with undefined lengths");
    cmd.addSubGroup("data set trailing padding (not with --write-dataset):");
      cmd.addOption("--padding-retain",      "-p=",     "do not change padding\n(default if not --write-dataset)");
      cmd.addOption("--padding-off",         "-p",      "no padding (implicit if --write-dataset)");
      cmd.addOption("--padding-create",      "+p",   2, "[f]ile-pad [i]tem-pad: integer",
                                                        "align file on multiple of f bytes\nand items on multiple of i bytes");

    if (app.parseCommandLine(cmd, argc, argv))
    {
      /* check exclusive options first */
      if (cmd.hasExclusiveOption())
      {
          if (cmd.findOption("--version"))
          {
              app.printHeader(OFTrue /*print host identifier*/);
              COUT << OFendl << "External libraries used:";
#if !defined(WITH_ZLIB) && !defined(BUILD_DCMSCALE_AS_DCMJSCAL)
              COUT << " none" << OFendl;
#else
              COUT << OFendl;
#endif
#ifdef WITH_ZLIB
              COUT << "- ZLIB, Version " << zlibVersion() << OFendl;
#endif
#ifdef BUILD_DCMSCALE_AS_DCMJSCAL
              COUT << "- " << DiJPEGPlugin::getLibraryVersionString() << OFendl;
#endif
              return 0;
          }
      }

      /* command line parameters */

      cmd.getParam(1, opt_ifname);
      cmd.getParam(2, opt_ofname);

      /* general options */

      OFLog::configureFromCommandLine(cmd, app);

      /* input options */

      cmd.beginOptionBlock();
      if (cmd.findOption("--read-file")) opt_readMode = ERM_autoDetect;
      if (cmd.findOption("--read-file-only")) opt_readMode = ERM_fileOnly;
      if (cmd.findOption("--read-dataset")) opt_readMode = ERM_dataset;
      cmd.endOptionBlock();

      cmd.beginOptionBlock();
      if (cmd.findOption("--read-xfer-auto"))
          opt_ixfer = EXS_Unknown;
      if (cmd.findOption("--read-xfer-detect"))
          dcmAutoDetectDatasetXfer.set(OFTrue);
      if (cmd.findOption("--read-xfer-little"))
      {
          app.checkDependence("--read-xfer-little", "--read-dataset", opt_readMode == ERM_dataset);
          opt_ixfer = EXS_LittleEndianExplicit;
      }
      if (cmd.findOption("--read-xfer-big"))
      {
          app.checkDependence("--read-xfer-big", "--read-dataset", opt_readMode == ERM_dataset);
          opt_ixfer = EXS_BigEndianExplicit;
      }
      if (cmd.findOption("--read-xfer-implicit"))
      {
          app.checkDependence("--read-xfer-implicit", "--read-dataset", opt_readMode == ERM_dataset);
          opt_ixfer = EXS_LittleEndianImplicit;
      }
      cmd.endOptionBlock();

      /* image processing options: color space conversion */

#ifdef BUILD_DCMSCALE_AS_DCMJSCAL
      cmd.beginOptionBlock();
      if (cmd.findOption("--conv-photometric"))
          opt_decompCSconversion = EDC_photometricInterpretation;
      if (cmd.findOption("--conv-lossy"))
          opt_decompCSconversion = EDC_lossyOnly;
      if (cmd.findOption("--conv-guess"))
          opt_decompCSconversion = EDC_guess;
      if (cmd.findOption("--conv-guess-lossy"))
          opt_decompCSconversion = EDC_guessLossyOnly;
      if (cmd.findOption("--conv-always"))
          opt_decompCSconversion = EDC_always;
      if (cmd.findOption("--conv-never"))
          opt_decompCSconversion = EDC_never;
      cmd.endOptionBlock();
#endif

      /* image processing options: scaling */

      cmd.beginOptionBlock();
      if (cmd.findOption("--recognize-aspect"))
          opt_useAspectRatio = 1;
      if (cmd.findOption("--ignore-aspect"))
          opt_useAspectRatio = 0;
      cmd.endOptionBlock();

      cmd.beginOptionBlock();
      if (cmd.findOption("--interpolate"))
          app.checkValue(cmd.getValueAndCheckMinMax(opt_useInterpolation, 1, 4));
      if (cmd.findOption("--no-interpolation"))
          opt_useInterpolation = 0;
      cmd.endOptionBlock();

      cmd.beginOptionBlock();
      if (cmd.findOption("--no-scaling"))
          opt_scaleType = 0;
      if (cmd.findOption("--scale-x-factor"))
      {
          opt_scaleType = 1;
          app.checkValue(cmd.getValueAndCheckMin(opt_scale_factor, 0.0, OFFalse));
      }
      if (cmd.findOption("--scale-y-factor"))
      {
          opt_scaleType = 2;
          app.checkValue(cmd.getValueAndCheckMin(opt_scale_factor, 0.0, OFFalse));
      }
      if (cmd.findOption("--scale-x-size"))
      {
          opt_scaleType = 3;
          app.checkValue(cmd.getValueAndCheckMin(opt_scale_size, 1));
      }
      if (cmd.findOption("--scale-y-size"))
      {
          opt_scaleType = 4;
          app.checkValue(cmd.getValueAndCheckMin(opt_scale_size, 1));
      }
      cmd.endOptionBlock();

      /* image processing options: other transformations */

      if (cmd.findOption("--clip-region"))
      {
          app.checkValue(cmd.getValue(opt_left));
          app.checkValue(cmd.getValue(opt_top));
          app.checkValue(cmd.getValue(opt_width));
          app.checkValue(cmd.getValue(opt_height));
          opt_useClip = OFTrue;
      }

      /* image processing options: SOP Instance UID options */

      cmd.beginOptionBlock();
      if (cmd.findOption("--uid-always")) opt_uidCreation = OFTrue;
      if (cmd.findOption("--uid-never")) opt_uidCreation = OFFalse;
      cmd.endOptionBlock();

      /* output options */

      cmd.beginOptionBlock();
      if (cmd.findOption("--write-file")) opt_writeMode = EWM_createNewMeta;
      if (cmd.findOption("--write-dataset")) opt_writeMode = EWM_dataset;
      cmd.endOptionBlock();

      cmd.beginOptionBlock();
      if (cmd.findOption("--write-xfer-same")) opt_oxfer = EXS_Unknown;
      if (cmd.findOption("--write-xfer-little")) opt_oxfer = EXS_LittleEndianExplicit;
      if (cmd.findOption("--write-xfer-big")) opt_oxfer = EXS_BigEndianExplicit;
      if (cmd.findOption("--write-xfer-implicit")) opt_oxfer = EXS_LittleEndianImplicit;
      cmd.endOptionBlock();

      cmd.beginOptionBlock();
      if (cmd.findOption("--enable-new-vr")) dcmEnableGenerationOfNewVRs();
      if (cmd.findOption("--disable-new-vr")) dcmDisableGenerationOfNewVRs();
      cmd.endOptionBlock();

      cmd.beginOptionBlock();
      if (cmd.findOption("--group-length-recalc")) opt_oglenc = EGL_recalcGL;
      if (cmd.findOption("--group-length-create")) opt_oglenc = EGL_withGL;
      if (cmd.findOption("--group-length-remove")) opt_oglenc = EGL_withoutGL;
      cmd.endOptionBlock();

      cmd.beginOptionBlock();
      if (cmd.findOption("--length-explicit")) opt_oenctype = EET_ExplicitLength;
      if (cmd.findOption("--length-undefined")) opt_oenctype = EET_UndefinedLength;
      cmd.endOptionBlock();

      cmd.beginOptionBlock();
      if (cmd.findOption("--padding-retain"))
      {
          app.checkConflict("--padding-retain", "--write-dataset", opt_writeMode == EWM_dataset);
          opt_opadenc = EPD_noChange;
      }
      if (cmd.findOption("--padding-off")) opt_opadenc = EPD_withoutPadding;
      if (cmd.findOption("--padding-create"))
      {
          app.checkConflict("--padding-create", "--write-dataset", opt_writeMode == EWM_dataset);
          app.checkValue(cmd.getValueAndCheckMin(opt_filepad, 0));
          app.checkValue(cmd.getValueAndCheckMin(opt_itempad, 0));
          opt_opadenc = EPD_withPadding;
      }
      cmd.endOptionBlock();
    }

    /* print resource identifier */
    OFLOG_DEBUG(dcmscaleLogger, rcsid << OFendl);

    /* make sure data dictionary is loaded */
    if (!dcmDataDict.isDictionaryLoaded())
    {
        OFLOG_WARN(dcmscaleLogger, "no data dictionary loaded, check environment variable: "
            << DCM_DICT_ENVIRONMENT_VARIABLE);
    }

    // register RLE decompression codec
    DcmRLEDecoderRegistration::registerCodecs();
#ifdef BUILD_DCMSCALE_AS_DCMJSCAL
    // register global decompression codecs
    DJDecoderRegistration::registerCodecs(opt_decompCSconversion);
#endif

    // ======================================================================
    // read input file

    if ((opt_ifname == NULL) || (strlen(opt_ifname) == 0))
    {
        OFLOG_FATAL(dcmscaleLogger, "invalid input filename: <empty string>");
        return 1;
    }

    /* no clipping/scaling */
    if (!opt_scaleType && !opt_useClip)
    {
        OFLOG_FATAL(dcmscaleLogger, "nothing to do");
        return 1;
    }

    OFLOG_INFO(dcmscaleLogger, "open input file " << opt_ifname);

    DcmFileFormat fileformat;
    DcmDataset *dataset = fileformat.getDataset();

    OFCondition error = fileformat.loadFile(opt_ifname, opt_ixfer, EGL_noChange, DCM_MaxReadLength, opt_readMode);
    if (error.bad())
    {
        OFLOG_FATAL(dcmscaleLogger, error.text() << ": reading file: " <<  opt_ifname);
        return 1;
    }

    OFLOG_INFO(dcmscaleLogger, "load all data into memory");

    /* make sure that pixel data is loaded before output file is created */
    dataset->loadAllDataIntoMemory();

    // select uncompressed output transfer syntax.
    // this will implicitly decompress the image if necessary.

    if (opt_oxfer == EXS_Unknown)
    {
        OFLOG_INFO(dcmscaleLogger, "set output transfer syntax to input transfer syntax");
        opt_oxfer = dataset->getOriginalXfer();
    }

    OFLOG_INFO(dcmscaleLogger, "check if new output transfer syntax is possible");

    DcmXfer opt_oxferSyn(opt_oxfer);
    if (dataset->chooseRepresentation(opt_oxfer, NULL).good() && dataset->canWriteXfer(opt_oxfer))
    {
        OFLOG_INFO(dcmscaleLogger, "output transfer syntax " << opt_oxferSyn.getXferName()
            << " can be written");
    } else {
        OFLOG_FATAL(dcmscaleLogger, "no conversion to transfer syntax " << opt_oxferSyn.getXferName()
            << " possible");
        return 1;
    }

    // ======================================================================
    // image processing starts here

    OFLOG_INFO(dcmscaleLogger, "preparing pixel data");

    const unsigned long flags = (opt_scaleType > 0) ? CIF_MayDetachPixelData : 0;
    // create DicomImage object
    DicomImage *di = new DicomImage(dataset, opt_oxfer, flags);
    if (di == NULL)
    {
        OFLOG_FATAL(dcmscaleLogger, "memory exhausted");
        return 1;
    }
    if (di->getStatus() != EIS_Normal)
    {
        OFLOG_FATAL(dcmscaleLogger, DicomImage::getString(di->getStatus()));
        return 1;
    }

    DicomImage *newimage = NULL;
    OFString derivationDescription;

    if (opt_useClip)
        OFLOG_INFO(dcmscaleLogger, "clipping image to (" << opt_left << "," << opt_top
            << "," << opt_width << "," << opt_height << ")");
    // perform clipping (without scaling)
    if (opt_scaleType <= 0)
    {
        if (opt_useClip)
        {
            newimage = di->createClippedImage(opt_left, opt_top, opt_width, opt_height);
            derivationDescription = "Clipped rectangular image region";
        }
    }
    // perform scaling (and possibly clipping)
    else if (opt_scaleType <= 4)
    {
        switch (opt_scaleType)
        {
            case 1:
                OFLOG_INFO(dcmscaleLogger, "scaling image, X factor=" << opt_scale_factor
                    << ", Interpolation=" << OFstatic_cast(int, opt_useInterpolation)
                    << ", Aspect Ratio=" << (opt_useAspectRatio ? "yes" : "no"));
                if (opt_useClip)
                    newimage = di->createScaledImage(opt_left, opt_top, opt_width, opt_height, opt_scale_factor, 0.0,
                        OFstatic_cast(int, opt_useInterpolation), opt_useAspectRatio);
                else
                    newimage = di->createScaledImage(opt_scale_factor, 0.0, OFstatic_cast(int, opt_useInterpolation),
                        opt_useAspectRatio);
                break;
            case 2:
                OFLOG_INFO(dcmscaleLogger, "scaling image, Y factor=" << opt_scale_factor
                    << ", Interpolation=" << OFstatic_cast(int, opt_useInterpolation)
                    << ", Aspect Ratio=" << (opt_useAspectRatio ? "yes" : "no"));
                if (opt_useClip)
                    newimage = di->createScaledImage(opt_left, opt_top, opt_width, opt_height, 0.0, opt_scale_factor,
                        OFstatic_cast(int, opt_useInterpolation), opt_useAspectRatio);
                else
                    newimage = di->createScaledImage(0.0, opt_scale_factor, OFstatic_cast(int, opt_useInterpolation),
                        opt_useAspectRatio);
                break;
            case 3:
                OFLOG_INFO(dcmscaleLogger, "scaling image, X size=" << opt_scale_size
                    << ", Interpolation=" << OFstatic_cast(int, opt_useInterpolation)
                    << ", Aspect Ratio=" << (opt_useAspectRatio ? "yes" : "no"));
                if (opt_useClip)
                    newimage = di->createScaledImage(opt_left, opt_top, opt_width, opt_height, opt_scale_size, 0,
                        OFstatic_cast(int, opt_useInterpolation), opt_useAspectRatio);
                else
                    newimage = di->createScaledImage(opt_scale_size, 0, OFstatic_cast(int, opt_useInterpolation),
                        opt_useAspectRatio);
                break;
            case 4:
                OFLOG_INFO(dcmscaleLogger, "scaling image, Y size=" << opt_scale_size
                    << ", Interpolation=" << OFstatic_cast(int, opt_useInterpolation)
                    << ", Aspect Ratio=" << (opt_useAspectRatio ? "yes" : "no"));
                if (opt_useClip)
                    newimage = di->createScaledImage(opt_left, opt_top, opt_width, opt_height, 0, opt_scale_size,
                        OFstatic_cast(int, opt_useInterpolation), opt_useAspectRatio);
                else
                    newimage = di->createScaledImage(0, opt_scale_size, OFstatic_cast(int, opt_useInterpolation),
                        opt_useAspectRatio);
                break;
            default:
                break;
        }
        if (opt_useClip)
            derivationDescription = "Scaled rectangular image region";
        else
            derivationDescription = "Scaled image";
    }
    if (opt_scaleType > 4)
        OFLOG_ERROR(dcmscaleLogger, "internal error: unknown scaling type");
    else if (newimage == NULL)
    {
        OFLOG_FATAL(dcmscaleLogger, "cannot create new image");
        return 1;
    }
    else if (newimage->getStatus() != EIS_Normal)
    {
        OFLOG_FATAL(dcmscaleLogger, DicomImage::getString(newimage->getStatus()));
        return 1;
    }
    /* write scaled image to dataset (update attributes of Image Pixel Module) */
    else if (!newimage->writeImageToDataset(*dataset))
    {
        OFLOG_FATAL(dcmscaleLogger, "cannot write new image to dataset");
        return 1;
    }
    delete newimage;

    /* cleanup original image */
    delete di;

    // ======================================================================
    // update some header attributes

    // update Derivation Description
    if (!derivationDescription.empty())
    {
        const char *oldDerivation = NULL;
        if (dataset->findAndGetString(DCM_DerivationDescription, oldDerivation).good() && oldDerivation)
        {
             // append old Derivation Description, if any
            derivationDescription += " [";
            derivationDescription += oldDerivation;
            derivationDescription += "]";
            if (derivationDescription.length() > 1024)
            {
                // ST is limited to 1024 characters, cut off tail
                derivationDescription.erase(1020);
                derivationDescription += "...]";
            }
        }
        dataset->putAndInsertString(DCM_DerivationDescription, derivationDescription.c_str());
    }

    // update Image Type
    OFString imageType = "DERIVED";
    const char *oldImageType = NULL;
    if (dataset->findAndGetString(DCM_ImageType, oldImageType).good())
    {
        if (oldImageType != NULL)
        {
            // append old image type information beginning with second entry
            const char *pos = strchr(oldImageType, '\\');
            if (pos != NULL)
                imageType += pos;
        }
    }
    dataset->putAndInsertString(DCM_ImageType, imageType.c_str());

    // update SOP Instance UID
    if (opt_uidCreation)
    {
        // add reference to source image
        DcmItem *ditem = NULL;
        const char *sopClassUID = NULL;
        const char *sopInstanceUID = NULL;
        if (dataset->findAndGetString(DCM_SOPClassUID, sopClassUID).good() &&
            dataset->findAndGetString(DCM_SOPInstanceUID, sopInstanceUID).good())
        {
            dataset->findAndDeleteElement(DCM_SourceImageSequence);
            if (dataset->findOrCreateSequenceItem(DCM_SourceImageSequence, ditem).good())
            {
                ditem->putAndInsertString(DCM_SOPClassUID, sopClassUID);
                ditem->putAndInsertString(DCM_SOPInstanceUID, sopInstanceUID);
            }
        }
        // create new SOP instance UID
        char new_uid[100];
        dataset->putAndInsertString(DCM_SOPInstanceUID, dcmGenerateUniqueIdentifier(new_uid));
    }

    // ======================================================================
    // write back output file

    OFLOG_INFO(dcmscaleLogger, "create output file " << opt_ofname);

    error = fileformat.saveFile(opt_ofname, opt_oxfer, opt_oenctype, opt_oglenc, opt_opadenc,
        OFstatic_cast(Uint32, opt_filepad), OFstatic_cast(Uint32, opt_itempad), opt_writeMode);

    if (error.bad())
    {
        OFLOG_FATAL(dcmscaleLogger, error.text() << ": writing file: " <<  opt_ofname);
        return 1;
    }

    OFLOG_INFO(dcmscaleLogger, "conversion successful");

    // deregister RLE decompression codec
    DcmRLEDecoderRegistration::cleanup();
#ifdef BUILD_DCMSCALE_AS_DCMJSCAL
    // deregister global decompression codecs
    DJDecoderRegistration::cleanup();
#endif

    return 0;
}
