/* ============================================================
 *
 * This file is a part of digiKam project
 * https://www.digikam.org
 *
 * Date        : 2009-02-01
 * Description : Content aware resizing tool.
 *
 * Copyright (C) 2009-2010 by Julien Pontabry <julien dot pontabry at ulp dot u-strasbg dot fr>
 * Copyright (C) 2009-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
 * Copyright (C) 2009-2010 by Julien Narboux <julien at narboux dot fr>
 *
 * This program is free software; you can redistribute it
 * and/or modify it under the terms of the GNU General
 * Public License as published by the Free Software Foundation;
 * either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * ============================================================ */

#include "contentawareresizetool.h"

// C++ includes

#include <cstdio>

// Qt includes

#include <QButtonGroup>
#include <QCheckBox>
#include <QGridLayout>
#include <QLabel>
#include <QToolButton>
#include <QIcon>
#include <QStandardPaths>

// KDE includes

#include <klocalizedstring.h>
#include <ksharedconfig.h>
#include <kconfiggroup.h>

// Local includes

#include "dnuminput.h"
#include "dexpanderbox.h"
#include "editortoolsettings.h"
#include "dcombobox.h"
#include "imageiface.h"
#include "imageguidewidget.h"
#include "contentawarefilter.h"

namespace DigikamEditorContentAwareResizeToolPlugin
{

class Q_DECL_HIDDEN ContentAwareResizeTool::Private
{
public:

    enum MaskTool
    {
        redMask=0,
        greenMask,
        eraseMask
    };

public:

    explicit Private()
      : orgWidth            (0),
        orgHeight           (0),
        prevW               (0),
        prevH               (0),
        prevWP              (0.0),
        prevHP              (0.0),
        preserveRatioBox    (nullptr),
        weightMaskBox       (nullptr),
        preserveSkinTones   (nullptr),
        previewWidget       (nullptr),
        gboxSettings        (nullptr),
        wInput              (nullptr),
        hInput              (nullptr),
        stepInput           (nullptr),
        maskPenSize         (nullptr),
        sideSwitchInput     (nullptr),
        wpInput             (nullptr),
        hpInput             (nullptr),
        mixedRescaleInput   (nullptr),
        rigidityInput       (nullptr),
        funcInput           (nullptr),
        resizeOrderInput    (nullptr),
        expanderBox         (nullptr),
        redMaskTool         (nullptr),
        greenMaskTool       (nullptr),
        eraseMaskTool       (nullptr),
        maskGroup           (nullptr)
    {
    }

    static const QString configGroupName;
    static const QString configStepEntry;
    static const QString configSideSwitchEntry;
    static const QString configRigidityEntry;
    static const QString configFunctionEntry;
    static const QString configOrderEntry;
    static const QString configMixedRescaleValueEntry;
    static const QString configBrushSizeEntry;
    static const QString configPreserveTonesEntry;

    int                  orgWidth;
    int                  orgHeight;
    int                  prevW;
    int                  prevH;

    double               prevWP;
    double               prevHP;

    QCheckBox*           preserveRatioBox;
    QCheckBox*           weightMaskBox;
    QCheckBox*           preserveSkinTones;

    ImageGuideWidget*    previewWidget;

    EditorToolSettings*  gboxSettings;

    DIntNumInput*        wInput;
    DIntNumInput*        hInput;
    DIntNumInput*        stepInput;
    DIntNumInput*        maskPenSize;
    DIntNumInput*        sideSwitchInput;

    DDoubleNumInput*     wpInput;
    DDoubleNumInput*     hpInput;
    DDoubleNumInput*     mixedRescaleInput;
    DDoubleNumInput*     rigidityInput;

    DComboBox*           funcInput;
    DComboBox*           resizeOrderInput;

    DExpanderBox*        expanderBox;

    QToolButton*         redMaskTool;
    QToolButton*         greenMaskTool;
    QToolButton*         eraseMaskTool;

    QButtonGroup*        maskGroup;
};

const QString ContentAwareResizeTool::Private::configGroupName(QLatin1String("liquidrescale Tool"));
const QString ContentAwareResizeTool::Private::configStepEntry(QLatin1String("Step"));
const QString ContentAwareResizeTool::Private::configSideSwitchEntry(QLatin1String("SideSwitch"));
const QString ContentAwareResizeTool::Private::configRigidityEntry(QLatin1String("Rigidity"));
const QString ContentAwareResizeTool::Private::configFunctionEntry(QLatin1String("Function"));
const QString ContentAwareResizeTool::Private::configOrderEntry(QLatin1String("Order"));
const QString ContentAwareResizeTool::Private::configMixedRescaleValueEntry(QLatin1String("MixedRescaleValue"));
const QString ContentAwareResizeTool::Private::configBrushSizeEntry(QLatin1String("BrushSize"));
const QString ContentAwareResizeTool::Private::configPreserveTonesEntry(QLatin1String("PreserveTones"));

// --------------------------------------------------------

ContentAwareResizeTool::ContentAwareResizeTool(QObject* const parent)
    : EditorToolThreaded(parent),
      d                 (new Private)
{
    setObjectName(QLatin1String("liquidrescale"));

    d->previewWidget = new ImageGuideWidget(nullptr, false, ImageGuideWidget::HVGuideMode);
    d->previewWidget->installEventFilter(this);
    setToolView(d->previewWidget);
    setPreviewModeMask(PreviewToolBar::PreviewToggleOnMouseOver);

    // -------------------------------------------------------------

    d->gboxSettings = new EditorToolSettings(nullptr);
    d->gboxSettings->setButtons(EditorToolSettings::Default|
                                EditorToolSettings::Ok|
                                EditorToolSettings::Try|
                                EditorToolSettings::Cancel);

    QGridLayout* const grid = new QGridLayout(d->gboxSettings->plainPage());

    // Initialize data

    ImageIface iface;
    d->orgWidth  = iface.originalSize().width();
    d->orgHeight = iface.originalSize().height();
    d->prevW     = d->orgWidth;
    d->prevH     = d->orgHeight;
    d->prevWP    = 100.0;
    d->prevHP    = 100.0;

    // -------------------------------------------------------------

    QWidget* const sizeSettingsContainer  = new QWidget;
    QGridLayout* const sizeSettingsLayout = new QGridLayout;

    d->preserveRatioBox = new QCheckBox(i18n("Maintain aspect ratio"), d->gboxSettings->plainPage());
    d->preserveRatioBox->setWhatsThis(i18n("Enable this option to maintain aspect ratio with new image sizes."));
    d->preserveRatioBox->setChecked(true);

    QLabel* const labelWidth = new QLabel(i18n("Width (px):"), d->gboxSettings->plainPage());
    d->wInput                = new DIntNumInput(d->gboxSettings->plainPage());
    d->wInput->setRange(1, 2*d->orgWidth, 1);
    d->wInput->setDefaultValue(d->orgWidth);
    d->wInput->setObjectName(QLatin1String("wInput"));
    d->wInput->setWhatsThis(i18n("Set here the new image width in pixels."));

    QLabel* const labelHeight = new QLabel(i18n("Height (px):"), d->gboxSettings->plainPage());
    d->hInput                 = new DIntNumInput(d->gboxSettings->plainPage());
    d->hInput->setRange(1, 2*d->orgHeight, 1);
    d->hInput->setDefaultValue(d->orgHeight);
    d->hInput->setObjectName(QLatin1String("hInput"));
    d->hInput->setWhatsThis(i18n("Set here the new image height in pixels."));

    QLabel* const labelWidthP = new QLabel(i18n("Width (%):"), d->gboxSettings->plainPage());
    d->wpInput                = new DDoubleNumInput(d->gboxSettings->plainPage());
    d->wpInput->setRange(1.0, 200.0, 1.0);
    d->wpInput->setDefaultValue(100.0);
    d->wpInput->setObjectName(QLatin1String("wpInput"));
    d->wpInput->setWhatsThis(i18n("New image width, as a percentage (%)."));

    QLabel* const labelHeightP = new QLabel(i18n("Height (%):"), d->gboxSettings->plainPage());
    d->hpInput                 = new DDoubleNumInput(d->gboxSettings->plainPage());
    d->hpInput->setRange(1.0, 200.0, 1.0);
    d->hpInput->setDefaultValue(100.0);
    d->hpInput->setObjectName(QLatin1String("hpInput"));
    d->hpInput->setWhatsThis(i18n("New image height, as a percentage (%)."));

    sizeSettingsLayout->addWidget(d->preserveRatioBox, 0, 0, 1, 3);
    sizeSettingsLayout->addWidget(labelWidth,          1, 0, 1, 1);
    sizeSettingsLayout->addWidget(d->wInput,           1, 1, 1, 4);
    sizeSettingsLayout->addWidget(labelHeight,         2, 0, 1, 1);
    sizeSettingsLayout->addWidget(d->hInput,           2, 1, 1, 4);
    sizeSettingsLayout->addWidget(labelWidthP,         3, 0, 1, 1);
    sizeSettingsLayout->addWidget(d->wpInput,          3, 1, 1, 4);
    sizeSettingsLayout->addWidget(labelHeightP,        4, 0, 1, 1);
    sizeSettingsLayout->addWidget(d->hpInput,          4, 1, 1, 4);

    sizeSettingsContainer->setLayout(sizeSettingsLayout);

    // -------------------------------------------------------------

    QWidget* const mixedRescaleContainer  = new QWidget;
    QGridLayout* const mixedRescaleLayout = new QGridLayout;

    d->mixedRescaleInput = new DDoubleNumInput(d->gboxSettings->plainPage());
    d->mixedRescaleInput->setRange(0.0, 100.0, 1.0);
    d->mixedRescaleInput->setDefaultValue(100.0);
    d->mixedRescaleInput->setObjectName(QLatin1String("mixedRescaleInput"));
    d->mixedRescaleInput->setWhatsThis(i18n("Specify here your desired content-aware rescaling percentage."));
    d->mixedRescaleInput->setEnabled(true);

    mixedRescaleLayout->addWidget(d->mixedRescaleInput,  0, 0, 1, 1);

    mixedRescaleContainer->setLayout(mixedRescaleLayout);

    // -------------------------------------------------------------

    QWidget* const maskSettingsContainer  = new QWidget;
    QGridLayout* const maskSettingsLayout = new QGridLayout;

    d->weightMaskBox  = new QCheckBox(i18n("Add weight masks"), d->gboxSettings->plainPage());
    d->weightMaskBox->setWhatsThis(i18n("Enable this option to add suppression and preservation masks."));
    d->weightMaskBox->setChecked(false);

    d->maskGroup = new QButtonGroup(d->gboxSettings->plainPage());
    d->maskGroup->setExclusive(true);

    QLabel* const labeRedMaskTool = new QLabel(i18n("Suppression weight mask:"), d->gboxSettings->plainPage());
    d->redMaskTool          = new QToolButton(d->gboxSettings->plainPage());
    d->redMaskTool->setIcon(QIcon(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/data/indicator-red.png"))));
    d->redMaskTool->setCheckable(true);
    d->redMaskTool->setChecked(true);
    d->redMaskTool->setToolTip(i18n("Draw a suppression mask"));
    d->redMaskTool->setWhatsThis(i18n("Click on this button to draw zones marking which areas of the "
                                      "image are less important.  These zones will be deleted when reducing "
                                      "the picture, or duplicated when enlarging the picture."));
    d->redMaskTool->setEnabled(false);
    d->maskGroup->addButton(d->redMaskTool, Private::redMask);

    QLabel* const labeGreenMaskTool = new QLabel(i18n("Preservation weight mask:"), d->gboxSettings->plainPage());
    d->greenMaskTool          = new QToolButton(d->gboxSettings->plainPage());
    d->greenMaskTool->setIcon(QIcon(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/data/indicator-green.png"))));
    d->greenMaskTool->setCheckable(true);
    d->greenMaskTool->setToolTip(i18n("Draw a preservation mask"));
    d->greenMaskTool->setWhatsThis(i18n("Click on this button to draw zones marking which areas of the "
                                        "image you want to preserve."));
    d->greenMaskTool->setEnabled(false);
    d->maskGroup->addButton(d->greenMaskTool, Private::greenMask);

    QLabel* const labeEraseMaskTool = new QLabel(i18n("Erase mask:"), d->gboxSettings->plainPage());
    d->eraseMaskTool          = new QToolButton(d->gboxSettings->plainPage());
    d->eraseMaskTool->setIcon(QIcon(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/data/indicator-gray.png"))));
    d->eraseMaskTool->setCheckable(true);
    d->eraseMaskTool->setToolTip(i18n("Erase mask"));
    d->eraseMaskTool->setWhatsThis(i18n("Click on this button to erase mask regions."));
    d->eraseMaskTool->setEnabled(false);
    d->maskGroup->addButton(d->eraseMaskTool, Private::eraseMask);

    QLabel* const labelMaskPenSize = new QLabel(i18n("Brush size:"), d->gboxSettings->plainPage());
    d->maskPenSize           = new DIntNumInput(d->gboxSettings->plainPage());
    d->maskPenSize->setRange(3, 64, 1);
    d->maskPenSize->setDefaultValue(10);
    d->maskPenSize->setObjectName(QLatin1String("maskPenSize"));
    d->maskPenSize->setWhatsThis(i18n("Specify here the size of the brush used to paint masks."));

    maskSettingsLayout->addWidget(d->weightMaskBox,  1, 0, 1, -1);
    maskSettingsLayout->addWidget(labeRedMaskTool,   2, 0, 1, 3);
    maskSettingsLayout->addWidget(d->redMaskTool,    2, 2, 1, 1);
    maskSettingsLayout->addWidget(labeGreenMaskTool, 3, 0, 1, 3);
    maskSettingsLayout->addWidget(d->greenMaskTool,  3, 2, 1, 1);
    maskSettingsLayout->addWidget(labeEraseMaskTool, 4, 0, 1, 3);
    maskSettingsLayout->addWidget(d->eraseMaskTool,  4, 2, 1, 1);
    maskSettingsLayout->addWidget(labelMaskPenSize,  5, 0, 1, 1);
    maskSettingsLayout->addWidget(d->maskPenSize,    5, 1, 1, 4);

    maskSettingsContainer->setLayout(maskSettingsLayout);

    // -------------------------------------------------------------

    QWidget* const energyFunctionsContainer  = new QWidget;
    QGridLayout* const energyFunctionsLayout = new QGridLayout;

    d->funcInput = new DComboBox(d->gboxSettings->plainPage());
    d->funcInput->addItem(i18n("Norm of brightness gradient"));
    d->funcInput->addItem(i18n("Sum of absolute values of brightness gradients"));
    d->funcInput->addItem(i18n("Absolute value of brightness gradient"));
    d->funcInput->addItem(i18n("Norm of luma gradient"));
    d->funcInput->addItem(i18n("Sum of absolute values of luma gradients"));
    d->funcInput->addItem(i18n("Absolute value of luma gradient"));

    d->funcInput->setDefaultIndex(2);
    d->funcInput->setWhatsThis(i18n("This option allows you to choose a gradient function. This function is used "
                                    "to determine which pixels should be removed or kept."));

    d->preserveSkinTones = new QCheckBox(i18n("Preserve Skin Tones"), d->gboxSettings->plainPage());
    d->preserveSkinTones->setWhatsThis(i18n("Enable this option to preserve pixels whose color is "
                                            "close to a skin tone."));
    d->preserveSkinTones->setChecked(false);

    energyFunctionsLayout->addWidget(d->funcInput,         1, 0, 1,-1);
    energyFunctionsLayout->addWidget(d->preserveSkinTones, 2, 0, 1, 3);

    energyFunctionsContainer->setLayout(energyFunctionsLayout);

    // -------------------------------------------------------------

    QWidget* const advancedSettingsContainer  = new QWidget;
    QGridLayout* const advancedSettingsLayout = new QGridLayout;

    QLabel* const labelRigidity = new QLabel(i18n("Overall rigidity of the seams:"), d->gboxSettings->plainPage());
    d->rigidityInput            = new DDoubleNumInput(d->gboxSettings->plainPage());
    d->rigidityInput->setRange(0.0, 10.0, 1.0);
    d->rigidityInput->setDefaultValue(0.0);
    d->rigidityInput->setWhatsThis(i18n("Use this value to give a negative bias to the seams which "
                                        "are not straight. May be useful to prevent distortions in "
                                        "some situations, or to avoid artifacts from pixel skipping "
                                        "(it is better to use low values in such case). This setting "
                                        "applies to the whole selected layer if no rigidity mask is used. "
                                        "Note: the bias is proportional to the difference in the transversal "
                                        "coordinate between each two successive points, elevated to the power "
                                        "of 1.5, and summed up for the whole seam."));

    QLabel* const labelSteps = new QLabel(i18n("Maximum number of transversal steps:"),d->gboxSettings->plainPage());
    d->stepInput             = new DIntNumInput(d->gboxSettings->plainPage());
    d->stepInput->setRange(1, 5, 1);
    d->stepInput->setDefaultValue(1);
    d->stepInput->setWhatsThis(i18n("This option lets you choose the maximum transversal step "
                                    "that the pixels in the seams can take. In the standard "
                                    "algorithm, corresponding to the default value step = 1, "
                                    "each pixel in a seam can be shifted by at most one pixel "
                                    "with respect to its neighbors. This implies that the seams "
                                    "can form an angle of at most 45 degrees with respect to their "
                                    "base line. Increasing the step value lets you overcome this "
                                    "limit, but may lead to the introduction of artifacts. In order "
                                    "to balance the situation, you can use the rigidity setting."));

    QLabel* const labelSideSwitch = new QLabel(i18n("Side switch frequency:"),d->gboxSettings->plainPage());
    d->sideSwitchInput            = new DIntNumInput(d->gboxSettings->plainPage());
    d->sideSwitchInput->setRange(1, 20, 1);
    d->sideSwitchInput->setDefaultValue(4);
    d->sideSwitchInput->setWhatsThis(i18n("During the carving process, at each step "
                                          "the optimal seam to be carved is chosen based on "
                                          "the relevance value for each pixel. However, in the case where two "
                                          "seams are equivalent (which may happen, for instance, when large portions "
                                          "of the image have the same color), the algorithm always "
                                          "chooses the seams from one side.  In some cases, this can pose "
                                          "problems, e.g. an object centered in the original image might not be "
                                          "centered in the resulting image. In order to overcome "
                                          "this effect, this setting allows the favored side to be switched "
                                          "automatically during rescaling, at the cost of slightly "
                                          "worse performance."));

    QLabel* const labelResizeOrder = new QLabel(i18n("Resize Order:"),d->gboxSettings->plainPage());
    d->resizeOrderInput            = new DComboBox(d->gboxSettings->plainPage());
    d->resizeOrderInput->addItem(i18n("Horizontally first"));
    d->resizeOrderInput->addItem(i18n("Vertically first"));
    d->resizeOrderInput->setDefaultIndex(0);
    d->resizeOrderInput->setWhatsThis(i18n("Here you can set whether to resize horizontally first or "
                                           "vertically first."));

    advancedSettingsLayout->addWidget(labelRigidity,       1, 0, 1, 4);
    advancedSettingsLayout->addWidget(d->rigidityInput,    2, 0, 1,-1);
    advancedSettingsLayout->addWidget(labelSteps,          3, 0, 1, 4);
    advancedSettingsLayout->addWidget(d->stepInput,        4, 0, 1,-1);
    advancedSettingsLayout->addWidget(labelSideSwitch,     5, 0, 1, 4);
    advancedSettingsLayout->addWidget(d->sideSwitchInput,  6, 0, 1,-1);
    advancedSettingsLayout->addWidget(labelResizeOrder,    7, 0, 1, 4);
    advancedSettingsLayout->addWidget(d->resizeOrderInput, 8, 0, 1,-1);

    advancedSettingsContainer->setLayout(advancedSettingsLayout);

    // -------------------------------------------------------------

    d->expanderBox = new DExpanderBox;
    d->expanderBox->setObjectName(QLatin1String("ContentAwareResizeTool Expander"));
    d->expanderBox->addItem(sizeSettingsContainer, QIcon::fromTheme(QLatin1String("transform-scale")), i18n("Target size"),
                            QLatin1String("SizeSettingsContainer"), true);
    d->expanderBox->addItem(mixedRescaleContainer, QIcon::fromTheme(QLatin1String("transform-scale")),
                            i18n("Content-aware rescale percentage"),
                            QLatin1String("MixedRescaleContainer"), true);
    d->expanderBox->addItem(maskSettingsContainer, QIcon::fromTheme(QLatin1String("transform-scale")), i18n("Mask Settings"),
                            QLatin1String("MaskSettingsContainer"), true);
    d->expanderBox->addItem(energyFunctionsContainer, QIcon::fromTheme(QLatin1String("transform-scale")), i18n("Energy function"),
                            QLatin1String("EnergyFunctionsContainer"), true);
    d->expanderBox->addItem(advancedSettingsContainer, QIcon::fromTheme(QLatin1String("system-run")), i18n("Advanced Settings"),
                            QLatin1String("AdvancedSettingsContainer"), true);
    d->expanderBox->addStretch();

    const int spacing = d->gboxSettings->spacingHint();

    grid->addWidget(d->expanderBox, 0, 0, 1, -1);
    grid->setRowStretch(0, 1);
    grid->setContentsMargins(spacing, spacing, spacing, spacing);
    grid->setSpacing(spacing);

    setToolSettings(d->gboxSettings);

    // -------------------------------------------------------------

    connect(d->wInput, SIGNAL(valueChanged(int)),
            this, SLOT(slotValuesChanged()));

    connect(d->hInput, SIGNAL(valueChanged(int)),
            this, SLOT(slotValuesChanged()));

    connect(d->wpInput, SIGNAL(valueChanged(double)),
            this, SLOT(slotValuesChanged()));

    connect(d->hpInput, SIGNAL(valueChanged(double)),
            this, SLOT(slotValuesChanged()));

    connect(d->mixedRescaleInput, SIGNAL(valueChanged(double)),
            this, SLOT(slotMixedRescaleValueChanged()));

    connect(d->weightMaskBox, SIGNAL(stateChanged(int)),
            this, SLOT(slotWeightMaskBoxStateChanged(int)));

    connect(d->maskGroup, SIGNAL(buttonClicked(int)),
            this, SLOT(slotMaskColorChanged(int)));

    connect(d->maskPenSize, SIGNAL(valueChanged(int)),
            this, SLOT(slotMaskPenSizeChanged(int)));
}

ContentAwareResizeTool::~ContentAwareResizeTool()
{
    delete d;
}

void ContentAwareResizeTool::readSettings()
{
    KSharedConfig::Ptr config = KSharedConfig::openConfig();
    KConfigGroup group        = config->group(d->configGroupName);

    blockWidgetSignals(true);

    // NOTE: size settings are not restored here because they depends of image size.
    d->stepInput->setValue(group.readEntry(d->configStepEntry,                      d->stepInput->defaultValue()));
    d->stepInput->setValue(group.readEntry(d->configSideSwitchEntry,                d->sideSwitchInput->defaultValue()));
    d->rigidityInput->setValue(group.readEntry(d->configRigidityEntry,              d->rigidityInput->defaultValue()));
    d->funcInput->setCurrentIndex(group.readEntry(d->configFunctionEntry,           d->funcInput->defaultIndex()));
    d->resizeOrderInput->setCurrentIndex(group.readEntry(d->configOrderEntry,       d->resizeOrderInput->defaultIndex()));
    d->mixedRescaleInput->setValue(group.readEntry(d->configMixedRescaleValueEntry, d->mixedRescaleInput->defaultValue()));
    d->maskPenSize->setValue(group.readEntry(d->configBrushSizeEntry,               d->maskPenSize->defaultValue()));
    d->preserveSkinTones->setChecked(group.readEntry(d->configPreserveTonesEntry,   false));

    d->expanderBox->readSettings(group);

    enableContentAwareSettings(d->mixedRescaleInput->value() > 0.0);

    blockWidgetSignals(false);
}

void ContentAwareResizeTool::writeSettings()
{
    KSharedConfig::Ptr config = KSharedConfig::openConfig();
    KConfigGroup group        = config->group(d->configGroupName);

    // NOTE: size settings are not saved here because they depends of image size.
    group.writeEntry(d->configStepEntry,              d->stepInput->value());
    group.writeEntry(d->configSideSwitchEntry,        d->sideSwitchInput->value());
    group.writeEntry(d->configRigidityEntry,          d->rigidityInput->value());
    group.writeEntry(d->configFunctionEntry,          d->funcInput->currentIndex());
    group.writeEntry(d->configOrderEntry,             d->resizeOrderInput->currentIndex());
    group.writeEntry(d->configMixedRescaleValueEntry, d->mixedRescaleInput->value());
    group.writeEntry(d->configBrushSizeEntry,         d->maskPenSize->value());
    group.writeEntry(d->configPreserveTonesEntry,     d->preserveSkinTones->isChecked());

    d->expanderBox->writeSettings(group);

    group.sync();
}

void ContentAwareResizeTool::slotResetSettings()
{
    blockWidgetSignals(true);

    d->preserveRatioBox->setChecked(true);
    d->wInput->slotReset();
    d->hInput->slotReset();
    d->wpInput->slotReset();
    d->hpInput->slotReset();
    d->mixedRescaleInput->slotReset();

    blockWidgetSignals(false);
}

void ContentAwareResizeTool::slotValuesChanged()
{
    blockWidgetSignals(true);

    QString s(sender()->objectName());

    if      (s == QLatin1String("wInput"))
    {
        double val  = d->wInput->value();
        double pval = val / (double)(d->orgWidth) * 100.0;

        d->wpInput->setValue(pval);

        if (d->preserveRatioBox->isChecked())
        {
            int h = (int)(pval * d->orgHeight / 100);

            d->hpInput->setValue(pval);
            d->hInput->setValue(h);
        }
    }
    else if (s == QLatin1String("hInput"))
    {
        double val  = d->hInput->value();
        double pval = val / (double)(d->orgHeight) * 100.0;

        d->hpInput->setValue(pval);

        if (d->preserveRatioBox->isChecked())
        {
            int w = (int)(pval * d->orgWidth / 100);

            d->wpInput->setValue(pval);
            d->wInput->setValue(w);
        }
    }
    else if (s == QLatin1String("wpInput"))
    {
        double val = d->wpInput->value();
        int w      = (int)(val * d->orgWidth / 100);

        d->wInput->setValue(w);

        if (d->preserveRatioBox->isChecked())
        {
            int h = (int)(val * d->orgHeight / 100);

            d->hpInput->setValue(val);
            d->hInput->setValue(h);
        }
    }
    else if (s == QLatin1String("hpInput"))
    {
        double val = d->hpInput->value();
        int h      = (int)(val * d->orgHeight / 100);

        d->hInput->setValue(h);

        if (d->preserveRatioBox->isChecked())
        {
            int w = (int)(val * d->orgWidth / 100);

            d->wpInput->setValue(val);
            d->wInput->setValue(w);
        }
    }

    d->prevW  = d->wInput->value();
    d->prevH  = d->hInput->value();
    d->prevWP = d->wpInput->value();
    d->prevHP = d->hpInput->value();

    blockWidgetSignals(false);
}

void ContentAwareResizeTool::enableMaskSettings(bool b)
{
    bool c = b && d->weightMaskBox->isChecked();
    d->weightMaskBox->setEnabled(b);
    d->redMaskTool  ->setEnabled(c);
    d->greenMaskTool->setEnabled(c);
    d->eraseMaskTool->setEnabled(c);
    d->maskPenSize  ->setEnabled(c);
}

void ContentAwareResizeTool::enableContentAwareSettings(bool b)
{
    d->stepInput->setEnabled(b);
    d->rigidityInput->setEnabled(b);
    d->sideSwitchInput->setEnabled(b);
    d->funcInput->setEnabled(b);
    d->preserveSkinTones->setEnabled(b);
    d->resizeOrderInput->setEnabled(b);
    enableMaskSettings(b);
}

void ContentAwareResizeTool::slotMixedRescaleValueChanged()
{
    blockWidgetSignals(true);
    enableContentAwareSettings(d->mixedRescaleInput->value()>0.0);
    blockWidgetSignals(false);
}

void ContentAwareResizeTool::disableSettings()
{
    d->preserveRatioBox->setEnabled(false);
    d->wInput->setEnabled(false);
    d->hInput->setEnabled(false);
    d->wpInput->setEnabled(false);
    d->hpInput->setEnabled(false);
    d->mixedRescaleInput->setEnabled(false);
    enableContentAwareSettings(false);
}

void ContentAwareResizeTool::contentAwareResizeCore(DImg* const image, int target_width, int target_height, const QImage& mask)
{
    ContentAwareContainer settings;
    settings.preserve_skin_tones = d->preserveSkinTones->isChecked();
    settings.width               = target_width;
    settings.height              = target_height;
    settings.step                = d->stepInput->value();
    settings.side_switch_freq    = d->sideSwitchInput->value();
    settings.rigidity            = d->rigidityInput->value();
    settings.mask                = mask;
    settings.func                = (ContentAwareContainer::EnergyFunction)d->funcInput->currentIndex();
    settings.resize_order        = (d->resizeOrderInput->currentIndex() == 0) ? Qt::Horizontal : Qt::Vertical;
    setFilter(new ContentAwareFilter(image, this, settings));
}

void ContentAwareResizeTool::preparePreview()
{
    if ((d->prevW  != d->wInput->value())  || (d->prevH  != d->hInput->value()) ||
        (d->prevWP != d->wpInput->value()) || (d->prevHP != d->hpInput->value()))
    {
        slotValuesChanged();
    }

    disableSettings();

    ImageIface* const iface = d->previewWidget->imageIface();
    int w                   = iface->previewSize().width();
    int h                   = iface->previewSize().height();
    DImg imTemp             = iface->original()->smoothScale(w, h, Qt::KeepAspectRatio);
    int new_w               = (int)(w*d->wpInput->value() / 100.0);
    int new_h               = (int)(h*d->hpInput->value() / 100.0);

    if (d->mixedRescaleInput->value()<100.0) // mixed rescale
    {
        double stdRescaleP = (100.0 - d->mixedRescaleInput->value()) / 100.0;
        int diff_w         = (int)(stdRescaleP * (w - new_w));
        int diff_h         = (int)(stdRescaleP * (h - new_h));

        imTemp.resize(imTemp.width() - diff_w, imTemp.height() - diff_h);
    }

    QImage mask;

    if (d->weightMaskBox->isChecked())
    {
        mask = d->previewWidget->getMask();
    }

    contentAwareResizeCore(&imTemp, new_w, new_h, mask);
}

void ContentAwareResizeTool::prepareFinal()
{
    if ((d->prevW  != d->wInput->value())  || (d->prevH  != d->hInput->value()) ||
        (d->prevWP != d->wpInput->value()) || (d->prevHP != d->hpInput->value()))
    {
        slotValuesChanged();
    }

    disableSettings();

    ImageIface iface;
    QImage     mask;

    if (d->mixedRescaleInput->value() < 100.0) // mixed rescale
    {
        double stdRescaleP = (100.0 - d->mixedRescaleInput->value()) / 100.0;
        int diff_w         = (int)(stdRescaleP * (iface.originalSize().width()  - d->wInput->value()));
        int diff_h         = (int)(stdRescaleP * (iface.originalSize().height() - d->hInput->value()));
        DImg image         = iface.original()->smoothScale(iface.originalSize().width()  - diff_w,
                             iface.originalSize().height() - diff_h,
                             Qt::IgnoreAspectRatio);

        if (d->weightMaskBox->isChecked())
        {
            mask = d->previewWidget->getMask().scaled(iface.originalSize().width()  - diff_w,
                    iface.originalSize().height() - diff_h);
        }

        contentAwareResizeCore(&image, d->wInput->value(), d->hInput->value(), mask);
    }
    else
    {
        if (d->weightMaskBox->isChecked())
        {
            mask = d->previewWidget->getMask().scaled(iface.originalSize());
        }

        contentAwareResizeCore(iface.original(), d->wInput->value(), d->hInput->value(), mask);
    }
}

void ContentAwareResizeTool::setPreviewImage()
{
    ImageIface* const iface = d->previewWidget->imageIface();
    int w                   = iface->previewSize().width();
    int h                   = iface->previewSize().height();
    DImg imTemp             = filter()->getTargetImage().smoothScale(w, h, Qt::KeepAspectRatio);
    DImg imDest(w, h, filter()->getTargetImage().sixteenBit(),
                filter()->getTargetImage().hasAlpha());

    QColor background = toolView()->backgroundRole();
    imDest.fill(DColor(background, filter()->getTargetImage().sixteenBit()));
    imDest.bitBltImage(&imTemp, (w-imTemp.width())/2, (h-imTemp.height())/2);

    iface->setPreview(imDest.smoothScale(iface->previewSize()));
    d->previewWidget->updatePreview();
}

void ContentAwareResizeTool::renderingFinished()
{
    d->preserveRatioBox->setEnabled(true);
    d->wInput->setEnabled(true);
    d->hInput->setEnabled(true);
    d->wpInput->setEnabled(true);
    d->hpInput->setEnabled(true);
    d->mixedRescaleInput->setEnabled(true);
    enableContentAwareSettings(true);
}

void ContentAwareResizeTool::setFinalImage()
{
    ImageIface iface;
    DImg targetImage = filter()->getTargetImage();
    iface.setOriginal(i18n("Liquid Rescale"), filter()->filterAction(), targetImage);
}

void ContentAwareResizeTool::blockWidgetSignals(bool b)
{
    d->preserveRatioBox->blockSignals(b);
    d->wInput->blockSignals(b);
    d->hInput->blockSignals(b);
    d->wpInput->blockSignals(b);
    d->hpInput->blockSignals(b);
    d->mixedRescaleInput->blockSignals(b);
    d->weightMaskBox->blockSignals(b);
    d->redMaskTool->blockSignals(b);
    d->greenMaskTool->blockSignals(b);
    d->eraseMaskTool->blockSignals(b);
}

void ContentAwareResizeTool::slotMaskColorChanged(int type)
{
    d->previewWidget->setEraseMode(type == Private::eraseMask);

    if      (type == Private::redMask)
    {
        d->previewWidget->setPaintColor(QColor(255, 0, 0));
    }
    else if (type == Private::greenMask)
    {
        d->previewWidget->setPaintColor(QColor(0, 255, 0));
    }
    else // erase
    {
        d->previewWidget->setPaintColor(QColor(255, 255, 0));
    }
}

void ContentAwareResizeTool::slotWeightMaskBoxStateChanged(int state)
{
    if (state == Qt::Unchecked)
    {
        d->redMaskTool->setEnabled(false);
        d->greenMaskTool->setEnabled(false);
        d->eraseMaskTool->setEnabled(false);
        d->maskPenSize->setEnabled(false);
        d->previewWidget->setMaskEnabled(false);
    }
    else    // Checked
    {
        d->redMaskTool->setEnabled(true);
        d->greenMaskTool->setEnabled(true);
        d->eraseMaskTool->setEnabled(true);
        d->maskPenSize->setEnabled(true);
        d->previewWidget->setMaskEnabled(true);

        if (d->redMaskTool->isChecked())
        {
            d->previewWidget->setPaintColor(QColor(255, 0, 0, 255));
        }
        else
        {
            d->previewWidget->setPaintColor(QColor(0, 255, 0, 255));
        }
    }
}

void ContentAwareResizeTool::slotMaskPenSizeChanged(int size)
{
    d->previewWidget->setMaskPenSize(size);
}

bool ContentAwareResizeTool::eventFilter(QObject* obj, QEvent* ev)
{
    if (d->weightMaskBox->isChecked())
    {
        if (obj == d->previewWidget)
        {
            if (ev->type() == QEvent::Wheel)
            {
                QWheelEvent* const wheel = static_cast<QWheelEvent *>(ev);

                if (wheel->angleDelta().y() >= 0)
                {
                    d->maskPenSize->setValue(d->maskPenSize->value()         +
                                             (wheel->angleDelta().y() / 120) *
                                             (wheel->angleDelta().y() / 120));
                }
                else
                {
                    d->maskPenSize->setValue(d->maskPenSize->value()         -
                                             (wheel->angleDelta().y() / 120) *
                                             (wheel->angleDelta().y() / 120));
                }

                d->previewWidget->setMaskCursor();
            }
        }
    }

    return false;
}

} // namespace DigikamEditorContentAwareResizeToolPlugin
