diff --git a/commands/imagelib/transforms/kpTransformResizeScaleCommand.cpp b/commands/imagelib/transforms/kpTransformResizeScaleCommand.cpp index 59295d35..ad7efbab 100644 --- a/commands/imagelib/transforms/kpTransformResizeScaleCommand.cpp +++ b/commands/imagelib/transforms/kpTransformResizeScaleCommand.cpp @@ -1,478 +1,477 @@ /* Copyright (c) 2003-2007 Clarence Dang All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define DEBUG_KP_TOOL_RESIZE_SCALE_COMMAND 0 #define DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG 0 #include "kpTransformResizeScaleCommand.h" #include "layers/selections/image/kpAbstractImageSelection.h" #include "environments/commands/kpCommandEnvironment.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "layers/selections/image/kpFreeFormImageSelection.h" #include "pixmapfx/kpPixmapFX.h" #include "layers/selections/image/kpRectangularImageSelection.h" #include "layers/selections/text/kpTextSelection.h" -#include #include #include #include #include #include #include #include "kpLogCategories.h" #include //-------------------------------------------------------------------------------- kpTransformResizeScaleCommand::kpTransformResizeScaleCommand (bool actOnSelection, int newWidth, int newHeight, Type type, kpCommandEnvironment *environ) : kpCommand (environ), m_actOnSelection (actOnSelection), m_type (type), m_backgroundColor (environ->backgroundColor ()), m_oldSelectionPtr (nullptr) { kpDocument *doc = document (); Q_ASSERT (doc); m_oldWidth = doc->width (m_actOnSelection); m_oldHeight = doc->height (m_actOnSelection); m_actOnTextSelection = (m_actOnSelection && doc->textSelection ()); resize (newWidth, newHeight); // If we have a selection _border_ (but not a floating selection), // then scale the selection with the document m_scaleSelectionWithImage = (!m_actOnSelection && (m_type == Scale || m_type == SmoothScale) && document ()->selection () && !document ()->selection ()->hasContent ()); } kpTransformResizeScaleCommand::~kpTransformResizeScaleCommand () { delete m_oldSelectionPtr; } // public virtual [base kpCommand] QString kpTransformResizeScaleCommand::name () const { if (m_actOnSelection) { if (m_actOnTextSelection) { if (m_type == Resize) return i18n ("Text: Resize Box"); } else { if (m_type == Scale) return i18n ("Selection: Scale"); else if (m_type == SmoothScale) return i18n ("Selection: Smooth Scale"); } } else { switch (m_type) { case Resize: return i18n ("Resize"); case Scale: return i18n ("Scale"); case SmoothScale: return i18n ("Smooth Scale"); } } return QString (); } // public virtual [base kpCommand] kpCommandSize::SizeType kpTransformResizeScaleCommand::size () const { return ImageSize (m_oldImage) + ImageSize (m_oldRightImage) + ImageSize (m_oldBottomImage) + SelectionSize (m_oldSelectionPtr); } // public int kpTransformResizeScaleCommand::newWidth () const { return m_newWidth; } // public void kpTransformResizeScaleCommand::setNewWidth (int width) { resize (width, newHeight ()); } // public int kpTransformResizeScaleCommand::newHeight () const { return m_newHeight; } // public void kpTransformResizeScaleCommand::setNewHeight (int height) { resize (newWidth (), height); } // public QSize kpTransformResizeScaleCommand::newSize () const { return QSize (newWidth (), newHeight ()); } // public virtual void kpTransformResizeScaleCommand::resize (int width, int height) { m_newWidth = width; m_newHeight = height; m_isLosslessScale = ((m_type == Scale) && (m_newWidth / m_oldWidth * m_oldWidth == m_newWidth) && (m_newHeight / m_oldHeight * m_oldHeight == m_newHeight)); } // public bool kpTransformResizeScaleCommand::scaleSelectionWithImage () const { return m_scaleSelectionWithImage; } // private void kpTransformResizeScaleCommand::scaleSelectionRegionWithDocument () { #if DEBUG_KP_TOOL_RESIZE_SCALE_COMMAND qCDebug(kpLogCommands) << "kpTransformResizeScaleCommand::scaleSelectionRegionWithDocument" << endl; #endif Q_ASSERT (m_oldSelectionPtr); Q_ASSERT (!m_oldSelectionPtr->hasContent ()); const double horizScale = double (m_newWidth) / double (m_oldWidth); const double vertScale = double (m_newHeight) / double (m_oldHeight); const int newX = static_cast (m_oldSelectionPtr->x () * horizScale); const int newY = static_cast (m_oldSelectionPtr->y () * vertScale); QPolygon currentPoints = m_oldSelectionPtr->calculatePoints (); currentPoints.translate (-currentPoints.boundingRect ().x (), -currentPoints.boundingRect ().y ()); // TODO: refactor into kpPixmapFX // TODO: Can we get to size 0x0 accidently? QMatrix scaleMatrix; scaleMatrix.scale (horizScale, vertScale); currentPoints = scaleMatrix.map (currentPoints); currentPoints.translate ( -currentPoints.boundingRect ().x () + newX, -currentPoints.boundingRect ().y () + newY); kpAbstractImageSelection *imageSel = dynamic_cast (m_oldSelectionPtr); kpTextSelection *textSel = dynamic_cast (m_oldSelectionPtr); if (imageSel) { document ()->setSelection ( kpFreeFormImageSelection (currentPoints, kpImage (), imageSel->transparency ())); } else if (textSel) { document ()->setSelection ( kpTextSelection (currentPoints.boundingRect (), textSel->textLines (), textSel->textStyle ())); } else Q_ASSERT (!"Unknown selection type"); environ ()->somethingBelowTheCursorChanged (); } // public virtual [base kpCommand] void kpTransformResizeScaleCommand::execute () { #if DEBUG_KP_TOOL_RESIZE_SCALE_COMMAND qCDebug(kpLogCommands) << "kpTransformResizeScaleCommand::execute() type=" << (int) m_type << " oldWidth=" << m_oldWidth << " oldHeight=" << m_oldHeight << " newWidth=" << m_newWidth << " newHeight=" << m_newHeight << endl; #endif if (m_oldWidth == m_newWidth && m_oldHeight == m_newHeight) return; if (m_type == Resize) { if (m_actOnSelection) { if (!m_actOnTextSelection) Q_ASSERT (!"kpTransformResizeScaleCommand::execute() resizing sel doesn't make sense"); QApplication::setOverrideCursor (Qt::WaitCursor); kpTextSelection *textSel = textSelection (); Q_ASSERT (textSel); kpTextSelection *newSel = textSel->resized (m_newWidth, m_newHeight); document ()->setSelection (*newSel); delete newSel; environ ()->somethingBelowTheCursorChanged (); QApplication::restoreOverrideCursor (); } else { QApplication::setOverrideCursor (Qt::WaitCursor); if (m_newWidth < m_oldWidth) { m_oldRightImage = document ()->getImageAt ( QRect (m_newWidth, 0, m_oldWidth - m_newWidth, m_oldHeight)); } if (m_newHeight < m_oldHeight) { m_oldBottomImage = document ()->getImageAt ( QRect (0, m_newHeight, m_newWidth, m_oldHeight - m_newHeight)); } document ()->resize (m_newWidth, m_newHeight, m_backgroundColor); QApplication::restoreOverrideCursor (); } } // Scale else { QApplication::setOverrideCursor (Qt::WaitCursor); kpImage oldImage = document ()->image (m_actOnSelection); if (!m_isLosslessScale) m_oldImage = oldImage; kpImage newImage = kpPixmapFX::scale (oldImage, m_newWidth, m_newHeight, m_type == SmoothScale); if (!m_oldSelectionPtr && document ()->selection ()) { // Save sel border m_oldSelectionPtr = document ()->selection ()->clone (); m_oldSelectionPtr->deleteContent (); } if (m_actOnSelection) { if (m_actOnTextSelection) Q_ASSERT (!"kpTransformResizeScaleCommand::execute() scaling text sel doesn't make sense"); Q_ASSERT (m_oldSelectionPtr); if ( !m_oldSelectionPtr ) // make coverity happy return; QRect newRect = QRect (m_oldSelectionPtr->x (), m_oldSelectionPtr->y (), newImage.width (), newImage.height ()); // Not possible to retain non-rectangular selection borders on scale // (think about e.g. a 45 deg line as part of the border & 2x scale) Q_ASSERT (dynamic_cast (m_oldSelectionPtr)); document ()->setSelection ( kpRectangularImageSelection (newRect, newImage, static_cast (m_oldSelectionPtr) ->transparency ())); environ ()->somethingBelowTheCursorChanged (); } else { document ()->setImage (newImage); if (m_scaleSelectionWithImage) { scaleSelectionRegionWithDocument (); } } QApplication::restoreOverrideCursor (); } } // public virtual [base kpCommand] void kpTransformResizeScaleCommand::unexecute () { #if DEBUG_KP_TOOL_RESIZE_SCALE_COMMAND qCDebug(kpLogCommands) << "kpTransformResizeScaleCommand::unexecute() type=" << m_type << endl; #endif if (m_oldWidth == m_newWidth && m_oldHeight == m_newHeight) return; kpDocument *doc = document (); Q_ASSERT (doc); if (m_type == Resize) { if (m_actOnSelection) { if (!m_actOnTextSelection) Q_ASSERT (!"kpTransformResizeScaleCommand::unexecute() resizing sel doesn't make sense"); QApplication::setOverrideCursor (Qt::WaitCursor); kpTextSelection *textSel = textSelection (); Q_ASSERT (textSel); kpTextSelection *newSel = textSel->resized (m_oldWidth, m_oldHeight); document ()->setSelection (*newSel); delete newSel; environ ()->somethingBelowTheCursorChanged (); QApplication::restoreOverrideCursor (); } else { QApplication::setOverrideCursor (Qt::WaitCursor); kpImage newImage (m_oldWidth, m_oldHeight, QImage::Format_ARGB32_Premultiplied); kpPixmapFX::setPixmapAt (&newImage, QPoint (0, 0), doc->image ()); if (m_newWidth < m_oldWidth) { kpPixmapFX::setPixmapAt (&newImage, QPoint (m_newWidth, 0), m_oldRightImage); } if (m_newHeight < m_oldHeight) { kpPixmapFX::setPixmapAt (&newImage, QPoint (0, m_newHeight), m_oldBottomImage); } doc->setImage (newImage); QApplication::restoreOverrideCursor (); } } // Scale else { QApplication::setOverrideCursor (Qt::WaitCursor); kpImage oldImage; if (!m_isLosslessScale) oldImage = m_oldImage; else oldImage = kpPixmapFX::scale (doc->image (m_actOnSelection), m_oldWidth, m_oldHeight); if (m_actOnSelection) { if (m_actOnTextSelection) Q_ASSERT (!"kpTransformResizeScaleCommand::unexecute() scaling text sel doesn't make sense"); Q_ASSERT (dynamic_cast (m_oldSelectionPtr)); kpAbstractImageSelection *oldImageSel = static_cast (m_oldSelectionPtr); kpAbstractImageSelection *oldSelection = oldImageSel->clone (); oldSelection->setBaseImage (oldImage); doc->setSelection (*oldSelection); delete oldSelection; environ ()->somethingBelowTheCursorChanged (); } else { doc->setImage (oldImage); if (m_scaleSelectionWithImage) { doc->setSelection (*m_oldSelectionPtr); environ ()->somethingBelowTheCursorChanged (); } } QApplication::restoreOverrideCursor (); } } diff --git a/dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp b/dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp index df1dcb06..d7188885 100644 --- a/dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp +++ b/dialogs/imagelib/transforms/kpTransformResizeScaleDialog.cpp @@ -1,828 +1,827 @@ /* Copyright (c) 2003-2007 Clarence Dang Copyright (c) 2011 Martin Koller All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG 0 #include "kpTransformResizeScaleDialog.h" -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kpLogCategories.h" #include #include #include #include "layers/selections/kpAbstractSelection.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "layers/selections/text/kpTextSelection.h" #include "tools/kpTool.h" #include "environments/dialogs/imagelib/transforms/kpTransformDialogEnvironment.h" //--------------------------------------------------------------------- #define kpSettingResizeScaleLastKeepAspect "Resize Scale - Last Keep Aspect" #define kpSettingResizeScaleScaleType "Resize Scale - ScaleType" //--------------------------------------------------------------------- #define SET_VALUE_WITHOUT_SIGNAL_EMISSION(knuminput_instance,value) \ { \ knuminput_instance->blockSignals (true); \ knuminput_instance->setValue (value); \ knuminput_instance->blockSignals (false); \ } #define IGNORE_KEEP_ASPECT_RATIO(cmd) \ { \ m_ignoreKeepAspectRatio++; \ cmd; \ m_ignoreKeepAspectRatio--; \ } //--------------------------------------------------------------------- kpTransformResizeScaleDialog::kpTransformResizeScaleDialog ( kpTransformDialogEnvironment *_env, QWidget *parent) : QDialog (parent), m_environ (_env), m_ignoreKeepAspectRatio (0), m_lastType(kpTransformResizeScaleCommand::Resize) { setWindowTitle (i18nc ("@title:window", "Resize / Scale")); QDialogButtonBox *buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); connect (buttons, &QDialogButtonBox::accepted, this, &kpTransformResizeScaleDialog::accept); connect (buttons, &QDialogButtonBox::rejected, this, &kpTransformResizeScaleDialog::reject); QWidget *baseWidget = new QWidget (this); QVBoxLayout *dialogLayout = new QVBoxLayout (this); dialogLayout->addWidget (baseWidget); dialogLayout->addWidget (buttons); QWidget *actOnBox = createActOnBox(baseWidget); QGroupBox *operationGroupBox = createOperationGroupBox(baseWidget); QGroupBox *dimensionsGroupBox = createDimensionsGroupBox(baseWidget); QVBoxLayout *baseLayout = new QVBoxLayout (baseWidget); baseLayout->setMargin(0); baseLayout->addWidget(actOnBox); baseLayout->addWidget(operationGroupBox); baseLayout->addWidget(dimensionsGroupBox); KConfigGroup cfg(KSharedConfig::openConfig(), kpSettingsGroupGeneral); setKeepAspectRatio(cfg.readEntry(kpSettingResizeScaleLastKeepAspect, false)); m_lastType = static_cast (cfg.readEntry(kpSettingResizeScaleScaleType, static_cast(kpTransformResizeScaleCommand::Resize))); slotActOnChanged (); m_newWidthInput->setFocus (); //enableButtonOk (!isNoOp ()); } //--------------------------------------------------------------------- // private kpDocument *kpTransformResizeScaleDialog::document () const { return m_environ->document (); } //--------------------------------------------------------------------- // private kpAbstractSelection *kpTransformResizeScaleDialog::selection () const { Q_ASSERT (document ()); return document ()->selection (); } //--------------------------------------------------------------------- // private kpTextSelection *kpTransformResizeScaleDialog::textSelection () const { Q_ASSERT (document ()); return document ()->textSelection (); } //--------------------------------------------------------------------- // private QWidget *kpTransformResizeScaleDialog::createActOnBox(QWidget *baseWidget) { QWidget *actOnBox = new QWidget (baseWidget); QLabel *actOnLabel = new QLabel (i18n ("Ac&t on:"), actOnBox); m_actOnCombo = new QComboBox (actOnBox); actOnLabel->setBuddy (m_actOnCombo); m_actOnCombo->insertItem (Image, i18n ("Entire Image")); if (selection ()) { QString selName = i18n ("Selection"); if (textSelection ()) selName = i18n ("Text Box"); m_actOnCombo->insertItem (Selection, selName); m_actOnCombo->setCurrentIndex (Selection); } else { actOnLabel->setEnabled (false); m_actOnCombo->setEnabled (false); } QHBoxLayout *lay = new QHBoxLayout (actOnBox); lay->setMargin (0); lay->addWidget (actOnLabel); lay->addWidget (m_actOnCombo, 1); connect (m_actOnCombo, static_cast(&QComboBox::activated), this, &kpTransformResizeScaleDialog::slotActOnChanged); return actOnBox; } //--------------------------------------------------------------------- static void toolButtonSetLook (QToolButton *button, const QString &iconName, const QString &name) { QPixmap icon = UserIcon (iconName); button->setIconSize (QSize (icon.width (), icon.height ())); button->setIcon (icon); button->setToolButtonStyle (Qt::ToolButtonTextUnderIcon); button->setText (name); button->setFocusPolicy (Qt::StrongFocus); button->setCheckable (true); } //--------------------------------------------------------------------- // private QGroupBox *kpTransformResizeScaleDialog::createOperationGroupBox (QWidget *baseWidget) { QGroupBox *operationGroupBox = new QGroupBox (i18n ("Operation"), baseWidget); operationGroupBox->setWhatsThis( i18n ("" "
    " "
  • Resize: The size of the picture will be" " increased" " by creating new areas to the right and/or bottom" " (filled in with the background color) or" " decreased by cutting" " it at the right and/or bottom.
  • " "
  • Scale: The picture will be expanded" " by duplicating pixels or squashed by dropping pixels.
  • " "
  • Smooth Scale: This is the same as" " Scale except that it blends neighboring" " pixels to produce a smoother looking picture.
  • " "
" "
")); m_resizeButton = new QToolButton (operationGroupBox); toolButtonSetLook (m_resizeButton, QLatin1String ("resize"), i18n ("&Resize")); m_scaleButton = new QToolButton (operationGroupBox); toolButtonSetLook (m_scaleButton, QLatin1String ("scale"), i18n ("&Scale")); m_smoothScaleButton = new QToolButton (operationGroupBox); toolButtonSetLook (m_smoothScaleButton, QLatin1String ("smooth_scale"), i18n ("S&mooth Scale")); QButtonGroup *resizeScaleButtonGroup = new QButtonGroup (baseWidget); resizeScaleButtonGroup->addButton (m_resizeButton); resizeScaleButtonGroup->addButton (m_scaleButton); resizeScaleButtonGroup->addButton (m_smoothScaleButton); QGridLayout *operationLayout = new QGridLayout (operationGroupBox ); operationLayout->addWidget (m_resizeButton, 0, 0, Qt::AlignCenter); operationLayout->addWidget (m_scaleButton, 0, 1, Qt::AlignCenter); operationLayout->addWidget (m_smoothScaleButton, 0, 2, Qt::AlignCenter); connect (m_resizeButton, &QToolButton::toggled, this, &kpTransformResizeScaleDialog::slotTypeChanged); connect (m_scaleButton, &QToolButton::toggled, this, &kpTransformResizeScaleDialog::slotTypeChanged); connect (m_smoothScaleButton, &QToolButton::toggled, this, &kpTransformResizeScaleDialog::slotTypeChanged); return operationGroupBox; } //--------------------------------------------------------------------- // private QGroupBox *kpTransformResizeScaleDialog::createDimensionsGroupBox(QWidget *baseWidget) { QGroupBox *dimensionsGroupBox = new QGroupBox (i18n ("Dimensions"), baseWidget); QLabel *widthLabel = new QLabel (i18n ("Width:"), dimensionsGroupBox); widthLabel->setAlignment (widthLabel->alignment () | Qt::AlignHCenter); QLabel *heightLabel = new QLabel (i18n ("Height:"), dimensionsGroupBox); heightLabel->setAlignment (heightLabel->alignment () | Qt::AlignHCenter); QLabel *originalLabel = new QLabel (i18n ("Original:"), dimensionsGroupBox); m_originalWidthInput = new QSpinBox; m_originalWidthInput->setRange(1, INT_MAX); m_originalWidthInput->setValue(document()->width(static_cast (selection()))); QLabel *xLabel0 = new QLabel (i18n ("x"), dimensionsGroupBox); m_originalHeightInput = new QSpinBox; m_originalHeightInput->setRange(1, INT_MAX); m_originalHeightInput->setValue(document()->height(static_cast (selection()))); QLabel *newLabel = new QLabel (i18n ("&New:"), dimensionsGroupBox); m_newWidthInput = new QSpinBox; m_newWidthInput->setRange(1, INT_MAX); QLabel *xLabel1 = new QLabel (i18n ("x"), dimensionsGroupBox); m_newHeightInput = new QSpinBox; m_newHeightInput->setRange(1, INT_MAX); QLabel *percentLabel = new QLabel (i18n ("&Percent:"), dimensionsGroupBox); m_percentWidthInput = new QDoubleSpinBox; m_percentWidthInput->setRange(0.01, 1000000); m_percentWidthInput->setValue(100); m_percentWidthInput->setSingleStep(1); m_percentWidthInput->setDecimals(2); m_percentWidthInput->setSuffix(i18n("%")); QLabel *xLabel2 = new QLabel (i18n ("x"), dimensionsGroupBox); m_percentHeightInput = new QDoubleSpinBox; m_percentHeightInput->setRange(0.01, 1000000); m_percentHeightInput->setValue(100); m_percentHeightInput->setSingleStep(1); m_percentHeightInput->setDecimals(2); m_percentHeightInput->setSuffix(i18n("%")); m_keepAspectRatioCheckBox = new QCheckBox (i18n ("Keep &aspect ratio"), dimensionsGroupBox); m_originalWidthInput->setEnabled (false); m_originalHeightInput->setEnabled (false); originalLabel->setBuddy (m_originalWidthInput); newLabel->setBuddy (m_newWidthInput); m_percentWidthInput->setValue (100); m_percentHeightInput->setValue (100); percentLabel->setBuddy (m_percentWidthInput); QGridLayout *dimensionsLayout = new QGridLayout (dimensionsGroupBox); dimensionsLayout->setColumnStretch (1/*column*/, 1); dimensionsLayout->setColumnStretch (3/*column*/, 1); dimensionsLayout->addWidget (widthLabel, 0, 1); dimensionsLayout->addWidget (heightLabel, 0, 3); dimensionsLayout->addWidget (originalLabel, 1, 0); dimensionsLayout->addWidget (m_originalWidthInput, 1, 1); dimensionsLayout->addWidget (xLabel0, 1, 2); dimensionsLayout->addWidget (m_originalHeightInput, 1, 3); dimensionsLayout->addWidget (newLabel, 2, 0); dimensionsLayout->addWidget (m_newWidthInput, 2, 1); dimensionsLayout->addWidget (xLabel1, 2, 2); dimensionsLayout->addWidget (m_newHeightInput, 2, 3); dimensionsLayout->addWidget (percentLabel, 3, 0); dimensionsLayout->addWidget (m_percentWidthInput, 3, 1); dimensionsLayout->addWidget (xLabel2, 3, 2); dimensionsLayout->addWidget (m_percentHeightInput, 3, 3); dimensionsLayout->addWidget (m_keepAspectRatioCheckBox, 4, 0, 1, 4); dimensionsLayout->setRowStretch (4/*row*/, 1); dimensionsLayout->setRowMinimumHeight (4/*row*/, dimensionsLayout->rowMinimumHeight (4) * 2); connect (m_newWidthInput, static_cast(&QSpinBox::valueChanged), this, &kpTransformResizeScaleDialog::slotWidthChanged); connect (m_newHeightInput, static_cast(&QSpinBox::valueChanged), this, &kpTransformResizeScaleDialog::slotHeightChanged); // COMPAT: KDoubleNumInput only fires valueChanged(double) once per // edit. It should either fire: // // 1. At the end of the edit (triggered by clicking or tabbing // away), like with KDE 3. // // OR // // 2. Once per keystroke. // // Bug in KDoubleNumInput. connect (m_percentWidthInput, static_cast(&QDoubleSpinBox::valueChanged), this, &kpTransformResizeScaleDialog::slotPercentWidthChanged); connect (m_percentHeightInput, static_cast(&QDoubleSpinBox::valueChanged), this, &kpTransformResizeScaleDialog::slotPercentHeightChanged); connect (m_keepAspectRatioCheckBox, &QCheckBox::toggled, this, &kpTransformResizeScaleDialog::setKeepAspectRatio); return dimensionsGroupBox; } //--------------------------------------------------------------------- // private void kpTransformResizeScaleDialog::widthFitHeightToAspectRatio () { if (m_keepAspectRatioCheckBox->isChecked () && !m_ignoreKeepAspectRatio) { // width / height = oldWidth / oldHeight // height = width * oldHeight / oldWidth const int newHeight = qRound (double (imageWidth ()) * double (originalHeight ()) / double (originalWidth ())); IGNORE_KEEP_ASPECT_RATIO (m_newHeightInput->setValue (newHeight)); } } //--------------------------------------------------------------------- // private void kpTransformResizeScaleDialog::heightFitWidthToAspectRatio () { if (m_keepAspectRatioCheckBox->isChecked () && !m_ignoreKeepAspectRatio) { // width / height = oldWidth / oldHeight // width = height * oldWidth / oldHeight const int newWidth = qRound (double (imageHeight ()) * double (originalWidth ()) / double (originalHeight ())); IGNORE_KEEP_ASPECT_RATIO (m_newWidthInput->setValue (newWidth)); } } //--------------------------------------------------------------------- // private bool kpTransformResizeScaleDialog::resizeEnabled () const { return (!actOnSelection () || (actOnSelection () && textSelection ())); } //--------------------------------------------------------------------- // private bool kpTransformResizeScaleDialog::scaleEnabled () const { return (!(actOnSelection () && textSelection ())); } //--------------------------------------------------------------------- // private bool kpTransformResizeScaleDialog::smoothScaleEnabled () const { return scaleEnabled (); } //--------------------------------------------------------------------- // public slot void kpTransformResizeScaleDialog::slotActOnChanged () { #if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 qCDebug(kpLogDialogs) << "kpTransformResizeScaleDialog::slotActOnChanged()"; #endif m_resizeButton->setEnabled (resizeEnabled ()); m_scaleButton->setEnabled (scaleEnabled ()); m_smoothScaleButton->setEnabled (smoothScaleEnabled ()); // TODO: somehow share logic with (resize|*scale)Enabled() if (actOnSelection ()) { if (textSelection ()) { m_resizeButton->setChecked (true); } else { if (m_lastType == kpTransformResizeScaleCommand::Scale) m_scaleButton->setChecked (true); else m_smoothScaleButton->setChecked (true); } } else { if (m_lastType == kpTransformResizeScaleCommand::Resize) m_resizeButton->setChecked (true); else if (m_lastType == kpTransformResizeScaleCommand::Scale) m_scaleButton->setChecked (true); else m_smoothScaleButton->setChecked (true); } m_originalWidthInput->setValue (originalWidth ()); m_originalHeightInput->setValue (originalHeight ()); m_newWidthInput->blockSignals (true); m_newHeightInput->blockSignals (true); m_newWidthInput->setMinimum (actOnSelection () ? selection ()->minimumWidth () : 1); m_newHeightInput->setMinimum (actOnSelection () ? selection ()->minimumHeight () : 1); m_newWidthInput->blockSignals (false); m_newHeightInput->blockSignals (false); IGNORE_KEEP_ASPECT_RATIO (slotPercentWidthChanged (m_percentWidthInput->value ())); IGNORE_KEEP_ASPECT_RATIO (slotPercentHeightChanged (m_percentHeightInput->value ())); setKeepAspectRatio (m_keepAspectRatioCheckBox->isChecked ()); } //--------------------------------------------------------------------- // public slot void kpTransformResizeScaleDialog::slotTypeChanged () { m_lastType = type (); } //--------------------------------------------------------------------- // public slot void kpTransformResizeScaleDialog::slotWidthChanged (int width) { #if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 qCDebug(kpLogDialogs) << "kpTransformResizeScaleDialog::slotWidthChanged(" << width << ")" << endl; #endif const double newPercentWidth = double (width) * 100 / double (originalWidth ()); SET_VALUE_WITHOUT_SIGNAL_EMISSION (m_percentWidthInput,newPercentWidth); widthFitHeightToAspectRatio (); //enableButtonOk (!isNoOp ()); } //--------------------------------------------------------------------- // public slot void kpTransformResizeScaleDialog::slotHeightChanged (int height) { #if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 qCDebug(kpLogDialogs) << "kpTransformResizeScaleDialog::slotHeightChanged(" << height << ")" << endl; #endif const double newPercentHeight = double (height) * 100 / double (originalHeight ()); SET_VALUE_WITHOUT_SIGNAL_EMISSION (m_percentHeightInput,newPercentHeight); heightFitWidthToAspectRatio (); //enableButtonOk (!isNoOp ()); } //--------------------------------------------------------------------- // public slot void kpTransformResizeScaleDialog::slotPercentWidthChanged (double percentWidth) { #if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 qCDebug(kpLogDialogs) << "kpTransformResizeScaleDialog::slotPercentWidthChanged(" << percentWidth << ")" << endl; #endif SET_VALUE_WITHOUT_SIGNAL_EMISSION (m_newWidthInput, qRound (percentWidth * originalWidth () / 100.0)); widthFitHeightToAspectRatio (); //enableButtonOk (!isNoOp ()); } //--------------------------------------------------------------------- // public slot void kpTransformResizeScaleDialog::slotPercentHeightChanged (double percentHeight) { #if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 qCDebug(kpLogDialogs) << "kpTransformResizeScaleDialog::slotPercentHeightChanged(" << percentHeight << ")" << endl; #endif SET_VALUE_WITHOUT_SIGNAL_EMISSION (m_newHeightInput, qRound (percentHeight * originalHeight () / 100.0)); heightFitWidthToAspectRatio (); //enableButtonOk (!isNoOp ()); } //--------------------------------------------------------------------- // public slot void kpTransformResizeScaleDialog::setKeepAspectRatio (bool on) { #if DEBUG_KP_TOOL_RESIZE_SCALE_DIALOG && 1 qCDebug(kpLogDialogs) << "kpTransformResizeScaleDialog::setKeepAspectRatio(" << on << ")" << endl; #endif if (on != m_keepAspectRatioCheckBox->isChecked ()) m_keepAspectRatioCheckBox->setChecked (on); if (on) widthFitHeightToAspectRatio (); } //--------------------------------------------------------------------- #undef IGNORE_KEEP_ASPECT_RATIO #undef SET_VALUE_WITHOUT_SIGNAL_EMISSION //--------------------------------------------------------------------- // private int kpTransformResizeScaleDialog::originalWidth () const { return document ()->width (actOnSelection ()); } //--------------------------------------------------------------------- // private int kpTransformResizeScaleDialog::originalHeight () const { return document ()->height (actOnSelection ()); } //--------------------------------------------------------------------- // public int kpTransformResizeScaleDialog::imageWidth () const { return m_newWidthInput->value (); } //--------------------------------------------------------------------- // public int kpTransformResizeScaleDialog::imageHeight () const { return m_newHeightInput->value (); } //--------------------------------------------------------------------- // public bool kpTransformResizeScaleDialog::actOnSelection () const { return (m_actOnCombo->currentIndex () == Selection); } //--------------------------------------------------------------------- // public kpTransformResizeScaleCommand::Type kpTransformResizeScaleDialog::type () const { if (m_resizeButton->isChecked ()) return kpTransformResizeScaleCommand::Resize; else if (m_scaleButton->isChecked ()) return kpTransformResizeScaleCommand::Scale; else return kpTransformResizeScaleCommand::SmoothScale; } //--------------------------------------------------------------------- // public bool kpTransformResizeScaleDialog::isNoOp () const { return (imageWidth () == originalWidth () && imageHeight () == originalHeight ()); } //--------------------------------------------------------------------- // private slot virtual [base QDialog] void kpTransformResizeScaleDialog::accept () { enum { eText, eSelection, eImage } actionTarget = eText; if (actOnSelection ()) { if (textSelection ()) { actionTarget = eText; } else { actionTarget = eSelection; } } else { actionTarget = eImage; } KLocalizedString message; QString caption, continueButtonText; // Note: If eText, can't Scale nor SmoothScale. // If eSelection, can't Resize. switch (type ()) { default: case kpTransformResizeScaleCommand::Resize: if (actionTarget == eText) { message = ki18n ("

Resizing the text box to %1x%2" " may take a substantial amount of memory." " This can reduce system" " responsiveness and cause other application resource" " problems.

" "

Are you sure you want to resize the text box?

"); caption = i18nc ("@title:window", "Resize Text Box?"); continueButtonText = i18n ("R&esize Text Box"); } else if (actionTarget == eImage) { message = ki18n ("

Resizing the image to %1x%2" " may take a substantial amount of memory." " This can reduce system" " responsiveness and cause other application resource" " problems.

" "

Are you sure you want to resize the image?

"); caption = i18nc ("@title:window", "Resize Image?"); continueButtonText = i18n ("R&esize Image"); } break; case kpTransformResizeScaleCommand::Scale: if (actionTarget == eImage) { message = ki18n ("

Scaling the image to %1x%2" " may take a substantial amount of memory." " This can reduce system" " responsiveness and cause other application resource" " problems.

" "

Are you sure you want to scale the image?

"); caption = i18nc ("@title:window", "Scale Image?"); continueButtonText = i18n ("Scal&e Image"); } else if (actionTarget == eSelection) { message = ki18n ("

Scaling the selection to %1x%2" " may take a substantial amount of memory." " This can reduce system" " responsiveness and cause other application resource" " problems.

" "

Are you sure you want to scale the selection?

"); caption = i18nc ("@title:window", "Scale Selection?"); continueButtonText = i18n ("Scal&e Selection"); } break; case kpTransformResizeScaleCommand::SmoothScale: if (actionTarget == eImage) { message = ki18n ("

Smooth Scaling the image to %1x%2" " may take a substantial amount of memory." " This can reduce system" " responsiveness and cause other application resource" " problems.

" "

Are you sure you want to smooth scale the image?

"); caption = i18nc ("@title:window", "Smooth Scale Image?"); continueButtonText = i18n ("Smooth Scal&e Image"); } else if (actionTarget == eSelection) { message = ki18n ("

Smooth Scaling the selection to %1x%2" " may take a substantial amount of memory." " This can reduce system" " responsiveness and cause other application resource" " problems.

" "

Are you sure you want to smooth scale the selection?

"); caption = i18nc ("@title:window", "Smooth Scale Selection?"); continueButtonText = i18n ("Smooth Scal&e Selection"); } break; } if (kpTool::warnIfBigImageSize (originalWidth (), originalHeight (), imageWidth (), imageHeight (), message.subs (imageWidth ()).subs (imageHeight ()).toString (), caption, continueButtonText, this)) { QDialog::accept (); } // store settings KConfigGroup cfg(KSharedConfig::openConfig(), kpSettingsGroupGeneral); cfg.writeEntry(kpSettingResizeScaleLastKeepAspect, m_keepAspectRatioCheckBox->isChecked()); cfg.writeEntry(kpSettingResizeScaleScaleType, static_cast(m_lastType)); cfg.sync(); } //--------------------------------------------------------------------- diff --git a/document/kpDocument.cpp b/document/kpDocument.cpp index fe5a19a7..32727c2f 100644 --- a/document/kpDocument.cpp +++ b/document/kpDocument.cpp @@ -1,459 +1,458 @@ /* Copyright (c) 2003-2007 Clarence Dang All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define DEBUG_KP_DOCUMENT 0 #include "kpDocument.h" #include "kpDocumentPrivate.h" #include "layers/selections/kpAbstractSelection.h" #include "layers/selections/image/kpAbstractImageSelection.h" #include "imagelib/kpColor.h" #include "widgets/toolbars/kpColorToolBar.h" #include "kpDefs.h" #include "environments/document/kpDocumentEnvironment.h" #include "document/kpDocumentSaveOptions.h" #include "imagelib/kpDocumentMetaInfo.h" #include "imagelib/effects/kpEffectReduceColors.h" #include "pixmapfx/kpPixmapFX.h" #include "tools/kpTool.h" #include "widgets/toolbars/kpToolToolBar.h" #include "lgpl/generic/kpUrlFormatter.h" -#include #include "kpLogCategories.h" #include // kdelibs4support #include #include #include #include #include #include #include #include #include #include #include #include //--------------------------------------------------------------------- kpDocument::kpDocument (int w, int h, kpDocumentEnvironment *environ) : QObject (), m_constructorWidth (w), m_constructorHeight (h), m_isFromURL (false), m_savedAtLeastOnceBefore (false), m_saveOptions (new kpDocumentSaveOptions ()), m_metaInfo (new kpDocumentMetaInfo ()), m_modified (false), m_selection (nullptr), m_oldWidth (-1), m_oldHeight (-1), d (new kpDocumentPrivate ()) { #if DEBUG_KP_DOCUMENT && 0 qCDebug(kpLogDocument) << "kpDocument::kpDocument (" << w << "," << h << ")"; #endif m_image = new kpImage(w, h, QImage::Format_ARGB32_Premultiplied); m_image->fill(QColor(Qt::white).rgb()); d->environ = environ; } //--------------------------------------------------------------------- kpDocument::~kpDocument () { delete d; delete m_image; delete m_saveOptions; delete m_metaInfo; delete m_selection; } //--------------------------------------------------------------------- // public kpDocumentEnvironment *kpDocument::environ () const { return d->environ; } //--------------------------------------------------------------------- // public void kpDocument::setEnviron (kpDocumentEnvironment *environ) { d->environ = environ; } //--------------------------------------------------------------------- // public bool kpDocument::savedAtLeastOnceBefore () const { return m_savedAtLeastOnceBefore; } //--------------------------------------------------------------------- // public QUrl kpDocument::url () const { return m_url; } //--------------------------------------------------------------------- // public void kpDocument::setURL (const QUrl &url, bool isFromURL) { m_url = url; m_isFromURL = isFromURL; } //--------------------------------------------------------------------- // public bool kpDocument::isFromURL (bool checkURLStillExists) const { if (!m_isFromURL) return false; if (!checkURLStillExists) return true; return (!m_url.isEmpty () && KIO::NetAccess::exists (m_url, KIO::NetAccess::SourceSide/*open*/, d->environ->dialogParent ())); } //--------------------------------------------------------------------- // public QString kpDocument::prettyUrl () const { return kpUrlFormatter::PrettyUrl (m_url); } //--------------------------------------------------------------------- // public QString kpDocument::prettyFilename () const { return kpUrlFormatter::PrettyFilename (m_url); } //--------------------------------------------------------------------- // public const kpDocumentSaveOptions *kpDocument::saveOptions () const { return m_saveOptions; } //--------------------------------------------------------------------- // public void kpDocument::setSaveOptions (const kpDocumentSaveOptions &saveOptions) { *m_saveOptions = saveOptions; } //--------------------------------------------------------------------- // public const kpDocumentMetaInfo *kpDocument::metaInfo () const { return m_metaInfo; } //--------------------------------------------------------------------- // public void kpDocument::setMetaInfo (const kpDocumentMetaInfo &metaInfo) { *m_metaInfo = metaInfo; } //--------------------------------------------------------------------- /* * Properties */ void kpDocument::setModified (bool yes) { if (yes == m_modified) return; m_modified = yes; if (yes) emit documentModified (); } //--------------------------------------------------------------------- bool kpDocument::isModified () const { return m_modified; } //--------------------------------------------------------------------- bool kpDocument::isEmpty () const { return url ().isEmpty () && !isModified (); } //--------------------------------------------------------------------- int kpDocument::constructorWidth () const { return m_constructorWidth; } //--------------------------------------------------------------------- int kpDocument::width (bool ofSelection) const { if (ofSelection && m_selection) return m_selection->width (); else return m_image->width (); } //--------------------------------------------------------------------- int kpDocument::oldWidth () const { return m_oldWidth; } //--------------------------------------------------------------------- void kpDocument::setWidth (int w, const kpColor &backgroundColor) { resize (w, height (), backgroundColor); } //--------------------------------------------------------------------- int kpDocument::constructorHeight () const { return m_constructorHeight; } //--------------------------------------------------------------------- int kpDocument::height (bool ofSelection) const { if (ofSelection && m_selection) return m_selection->height (); else return m_image->height (); } //--------------------------------------------------------------------- int kpDocument::oldHeight () const { return m_oldHeight; } //--------------------------------------------------------------------- void kpDocument::setHeight (int h, const kpColor &backgroundColor) { resize (width (), h, backgroundColor); } //--------------------------------------------------------------------- QRect kpDocument::rect (bool ofSelection) const { if (ofSelection && m_selection) return m_selection->boundingRect (); else return m_image->rect (); } //--------------------------------------------------------------------- // public kpImage kpDocument::getImageAt (const QRect &rect) const { return kpPixmapFX::getPixmapAt (*m_image, rect); } //--------------------------------------------------------------------- // public void kpDocument::setImageAt (const kpImage &image, const QPoint &at) { #if DEBUG_KP_DOCUMENT && 0 qCDebug(kpLogDocument) << "kpDocument::setImageAt (image (w=" << image.width () << ",h=" << image.height () << "), x=" << at.x () << ",y=" << at.y () << endl; #endif kpPixmapFX::setPixmapAt (m_image, at, image); slotContentsChanged (QRect (at.x (), at.y (), image.width (), image.height ())); } //--------------------------------------------------------------------- // public kpImage kpDocument::image (bool ofSelection) const { kpImage ret; if (ofSelection) { kpAbstractImageSelection *imageSel = imageSelection (); Q_ASSERT (imageSel); ret = imageSel->baseImage (); } else ret = *m_image; return ret; } //--------------------------------------------------------------------- // public kpImage *kpDocument::imagePointer () const { return m_image; } //--------------------------------------------------------------------- // public void kpDocument::setImage (const kpImage &image) { m_oldWidth = width (); m_oldHeight = height (); *m_image = image; if (m_oldWidth == width () && m_oldHeight == height ()) slotContentsChanged (image.rect ()); else slotSizeChanged (QSize (width (), height ())); } //--------------------------------------------------------------------- // public void kpDocument::setImage (bool ofSelection, const kpImage &image) { if (ofSelection) { kpAbstractImageSelection *imageSel = imageSelection (); // Have to have an image selection in order to set its pixmap. Q_ASSERT (imageSel); imageSel->setBaseImage (image); } else setImage (image); } //--------------------------------------------------------------------- void kpDocument::fill (const kpColor &color) { #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "kpDocument::fill ()"; #endif m_image->fill(color.toQRgb()); slotContentsChanged (m_image->rect ()); } //--------------------------------------------------------------------- void kpDocument::resize (int w, int h, const kpColor &backgroundColor) { #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "kpDocument::resize (" << w << "," << h << ")"; #endif m_oldWidth = width (); m_oldHeight = height (); #if DEBUG_KP_DOCUMENT && 1 qCDebug(kpLogDocument) << "\toldWidth=" << m_oldWidth << " oldHeight=" << m_oldHeight << endl; #endif if (w == m_oldWidth && h == m_oldHeight) return; kpPixmapFX::resize (m_image, w, h, backgroundColor); slotSizeChanged (QSize (width (), height ())); } //--------------------------------------------------------------------- void kpDocument::slotContentsChanged (const QRect &rect) { setModified (); emit contentsChanged (rect); } //--------------------------------------------------------------------- void kpDocument::slotSizeChanged (const QSize &newSize) { setModified (); emit sizeChanged (newSize.width(), newSize.height()); emit sizeChanged (newSize); } //--------------------------------------------------------------------- diff --git a/document/kpDocument_Open.cpp b/document/kpDocument_Open.cpp index 0aa21009..c9c35d5b 100644 --- a/document/kpDocument_Open.cpp +++ b/document/kpDocument_Open.cpp @@ -1,240 +1,239 @@ /* Copyright (c) 2003-2007 Clarence Dang All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define DEBUG_KP_DOCUMENT 0 #include "kpDocument.h" #include "kpDocumentPrivate.h" #include "imagelib/kpColor.h" #include "widgets/toolbars/kpColorToolBar.h" #include "kpDefs.h" #include "environments/document/kpDocumentEnvironment.h" #include "document/kpDocumentSaveOptions.h" #include "imagelib/kpDocumentMetaInfo.h" #include "imagelib/effects/kpEffectReduceColors.h" #include "pixmapfx/kpPixmapFX.h" #include "tools/kpTool.h" #include "lgpl/generic/kpUrlFormatter.h" #include "views/manager/kpViewManager.h" -#include #include #include #include #include #include "kpLogCategories.h" #include // kdelibs4support #include #include //--------------------------------------------------------------------- void kpDocument::getDataFromImage(const QImage &image, kpDocumentSaveOptions &saveOptions, kpDocumentMetaInfo &metaInfo) { saveOptions.setColorDepth(image.depth()); saveOptions.setDither(false); // avoid double dithering when saving metaInfo.setDotsPerMeterX(image.dotsPerMeterX()); metaInfo.setDotsPerMeterY(image.dotsPerMeterY()); metaInfo.setOffset(image.offset()); QStringList keys = image.textKeys(); for (int i = 0; i < keys.count(); i++) metaInfo.setText(keys[i], image.text(keys[i])); } //--------------------------------------------------------------------- // public static QImage kpDocument::getPixmapFromFile(const QUrl &url, bool suppressDoesntExistDialog, QWidget *parent, kpDocumentSaveOptions *saveOptions, kpDocumentMetaInfo *metaInfo) { #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "kpDocument::getPixmapFromFile(" << url << "," << parent << ")"; #endif if (saveOptions) *saveOptions = kpDocumentSaveOptions (); if (metaInfo) *metaInfo = kpDocumentMetaInfo (); QString tempFile; if (url.isEmpty () || !KIO::NetAccess::download (url, tempFile, parent)) { if (!suppressDoesntExistDialog) { // TODO: Use "Cannot" instead of "Could not" in all dialogs in KolourPaint. // Or at least choose one consistently. // // TODO: Have captions for all dialogs in KolourPaint. KMessageBox::sorry (parent, i18n ("Could not open \"%1\".", kpUrlFormatter::PrettyFilename (url))); } return QImage (); } QMimeDatabase db; QMimeType mimeType = db.mimeTypeForFile(tempFile); if (saveOptions) saveOptions->setMimeType(mimeType.name()); #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\ttempFile=" << tempFile; qCDebug(kpLogDocument) << "\tmimetype=" << mimeType.name(); qCDebug(kpLogDocument) << "\tsrc=" << url.path (); #endif QImageReader reader(tempFile); reader.setAutoTransform(true); reader.setDecideFormatFromContent(true); QImage image = reader.read(); KIO::NetAccess::removeTempFile(tempFile); if (image.isNull ()) { KMessageBox::sorry (parent, i18n ("Could not open \"%1\" - unsupported image format.\n" "The file may be corrupt.", kpUrlFormatter::PrettyFilename (url))); return QImage (); } #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\tpixmap: depth=" << image.depth () << " hasAlphaChannel=" << image.hasAlphaChannel () << endl; #endif if ( saveOptions && metaInfo ) getDataFromImage(image, *saveOptions, *metaInfo); // make sure we always have Format_ARGB32_Premultiplied as this is the fastest to draw on // and Qt can not draw onto Format_Indexed8 (Qt-4.7) if ( image.format() != QImage::Format_ARGB32_Premultiplied ) image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); return image; } //--------------------------------------------------------------------- void kpDocument::openNew (const QUrl &url) { #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "kpDocument::openNew (" << url << ")"; #endif m_image->fill(QColor(Qt::white).rgb()); setURL (url, false/*not from url*/); *m_saveOptions = kpDocumentSaveOptions (); if ( !url.isEmpty() ) { // guess the mimetype from url's filename extension. // // That way "kolourpaint doesnotexist.bmp" automatically // selects the BMP file format when the save dialog comes up for // the first time. QMimeDatabase mimeDb; m_saveOptions->setMimeType(mimeDb.mimeTypeForUrl(url).name()); } *m_metaInfo = kpDocumentMetaInfo (); m_modified = false; emit documentOpened (); } //--------------------------------------------------------------------- bool kpDocument::open (const QUrl &url, bool newDocSameNameIfNotExist) { #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "kpDocument::open (" << url << ")"; #endif kpDocumentSaveOptions newSaveOptions; kpDocumentMetaInfo newMetaInfo; QImage newPixmap = kpDocument::getPixmapFromFile (url, newDocSameNameIfNotExist/*suppress "doesn't exist" dialog*/, d->environ->dialogParent (), &newSaveOptions, &newMetaInfo); if (!newPixmap.isNull ()) { delete m_image; m_image = new kpImage (newPixmap); setURL (url, true/*is from url*/); *m_saveOptions = newSaveOptions; *m_metaInfo = newMetaInfo; m_modified = false; emit documentOpened (); return true; } if (newDocSameNameIfNotExist) { if (!url.isEmpty () && // not just a permission error? !KIO::NetAccess::exists (url, KIO::NetAccess::SourceSide/*open*/, d->environ->dialogParent ())) { openNew (url); } else { openNew (QUrl ()); } return true; } else { return false; } } //--------------------------------------------------------------------- diff --git a/document/kpDocument_Save.cpp b/document/kpDocument_Save.cpp index d211ed55..d7ee8305 100644 --- a/document/kpDocument_Save.cpp +++ b/document/kpDocument_Save.cpp @@ -1,514 +1,513 @@ /* Copyright (c) 2003-2007 Clarence Dang All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define DEBUG_KP_DOCUMENT 0 #include "kpDocument.h" #include "kpDocumentPrivate.h" -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kpLogCategories.h" #include // kdelibs4support #include // kdelibs4support #include #include #include "imagelib/kpColor.h" #include "widgets/toolbars/kpColorToolBar.h" #include "kpDefs.h" #include "environments/document/kpDocumentEnvironment.h" #include "document/kpDocumentSaveOptions.h" #include "imagelib/kpDocumentMetaInfo.h" #include "imagelib/effects/kpEffectReduceColors.h" #include "pixmapfx/kpPixmapFX.h" #include "tools/kpTool.h" #include "widgets/toolbars/kpToolToolBar.h" #include "lgpl/generic/kpUrlFormatter.h" #include "views/manager/kpViewManager.h" bool kpDocument::save (bool overwritePrompt, bool lossyPrompt) { #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "kpDocument::save(" << "overwritePrompt=" << overwritePrompt << ",lossyPrompt=" << lossyPrompt << ") url=" << m_url << " savedAtLeastOnceBefore=" << savedAtLeastOnceBefore () << endl; #endif // TODO: check feels weak if (m_url.isEmpty () || m_saveOptions->mimeType ().isEmpty ()) { KMessageBox::detailedError (d->environ->dialogParent (), i18n ("Could not save image - insufficient information."), i18n ("URL: %1\n" "Mimetype: %2", prettyUrl (), m_saveOptions->mimeType ().isEmpty () ? i18n ("") : m_saveOptions->mimeType ()), i18nc ("@title:window", "Internal Error")); return false; } return saveAs (m_url, *m_saveOptions, overwritePrompt, lossyPrompt); } //--------------------------------------------------------------------- // public static bool kpDocument::lossyPromptContinue (const QImage &pixmap, const kpDocumentSaveOptions &saveOptions, QWidget *parent) { #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "kpDocument::lossyPromptContinue()"; #endif #define QUIT_IF_CANCEL(messageBoxCommand) \ { \ if (messageBoxCommand != KMessageBox::Continue) \ { \ return false; \ } \ } const int lossyType = saveOptions.isLossyForSaving (pixmap); if (lossyType & (kpDocumentSaveOptions::MimeTypeMaximumColorDepthLow | kpDocumentSaveOptions::Quality)) { QMimeDatabase db; QUIT_IF_CANCEL ( KMessageBox::warningContinueCancel (parent, i18n ("

The %1 format may not be able" " to preserve all of the image's color information.

" "

Are you sure you want to save in this format?

", db.mimeTypeForName(saveOptions.mimeType()).comment()), // TODO: caption misleading for lossless formats that have // low maximum colour depth i18nc ("@title:window", "Lossy File Format"), KStandardGuiItem::save (), KStandardGuiItem::cancel(), QLatin1String ("SaveInLossyMimeTypeDontAskAgain"))); } else if (lossyType & kpDocumentSaveOptions::ColorDepthLow) { QUIT_IF_CANCEL ( KMessageBox::warningContinueCancel (parent, i18n ("

Saving the image at the low color depth of %1-bit" " may result in the loss of color information." // TODO: It looks like 8-bit QImage's now support alpha. // Update kpDocumentSaveOptions::isLossyForSaving() // and change "might" to "will". " Any transparency might also be removed.

" "

Are you sure you want to save at this color depth?

", saveOptions.colorDepth ()), i18nc ("@title:window", "Low Color Depth"), KStandardGuiItem::save (), KStandardGuiItem::cancel(), QLatin1String ("SaveAtLowColorDepthDontAskAgain"))); } #undef QUIT_IF_CANCEL return true; } //--------------------------------------------------------------------- // public static bool kpDocument::savePixmapToDevice (const QImage &image, QIODevice *device, const kpDocumentSaveOptions &saveOptions, const kpDocumentMetaInfo &metaInfo, bool lossyPrompt, QWidget *parent, bool *userCancelled) { if (userCancelled) *userCancelled = false; QStringList types = KImageIO::typeForMime (saveOptions.mimeType ()); #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\ttypes=" << types; #endif if (types.isEmpty ()) return false; // It's safe to arbitrarily choose the 0th type as any type in the list // should invoke the same KImageIO image loader. const QString type = types [0]; #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\tmimeType=" << saveOptions.mimeType () << " type=" << type << endl; #endif if (lossyPrompt && !lossyPromptContinue (image, saveOptions, parent)) { if (userCancelled) *userCancelled = true; #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\treturning false because of lossyPrompt"; #endif return false; } // TODO: fix dup with kpDocumentSaveOptions::isLossyForSaving() const bool useSaveOptionsColorDepth = (saveOptions.mimeTypeHasConfigurableColorDepth () && !saveOptions.colorDepthIsInvalid ()); const bool useSaveOptionsQuality = (saveOptions.mimeTypeHasConfigurableQuality () && !saveOptions.qualityIsInvalid ()); // // Reduce colors if required // #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\tuseSaveOptionsColorDepth=" << useSaveOptionsColorDepth << "current image depth=" << image.depth () << "save options depth=" << saveOptions.colorDepth (); #endif QImage imageToSave(image); if (useSaveOptionsColorDepth && imageToSave.depth () != saveOptions.colorDepth ()) { // TODO: I think this erases the mask! // // I suspect this doesn't matter since this is only called to // reduce color depth and QImage's with depth < 32 don't // support masks anyway. // // Later: I think the mask is preserved for 8-bit since Qt4 // seems to support it for QImage. imageToSave = kpEffectReduceColors::convertImageDepth (imageToSave, saveOptions.colorDepth (), saveOptions.dither ()); } // // Write Meta Info // imageToSave.setDotsPerMeterX (metaInfo.dotsPerMeterX ()); imageToSave.setDotsPerMeterY (metaInfo.dotsPerMeterY ()); imageToSave.setOffset (metaInfo.offset ()); QList keyList = metaInfo.textKeys (); for (QList ::const_iterator it = keyList.constBegin (); it != keyList.constEnd (); ++it) { imageToSave.setText (*it, metaInfo.text (*it)); } // // Save at required quality // int quality = -1; // default if (useSaveOptionsQuality) quality = saveOptions.quality (); #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\tsaving"; #endif if (!imageToSave.save (device, type.toLatin1 (), quality)) { #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\tQImage::save() returned false"; #endif return false; } #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\tsave OK"; #endif return true; } //--------------------------------------------------------------------- static void CouldNotCreateTemporaryFileDialog (QWidget *parent) { KMessageBox::error (parent, i18n ("Could not save image - unable to create temporary file.")); } //--------------------------------------------------------------------- static void CouldNotSaveDialog (const QUrl &url, QWidget *parent) { // TODO: use file.errorString() KMessageBox::error (parent, i18n ("Could not save as \"%1\".", kpUrlFormatter::PrettyFilename (url))); } //--------------------------------------------------------------------- // public static bool kpDocument::savePixmapToFile (const QImage &pixmap, const QUrl &url, const kpDocumentSaveOptions &saveOptions, const kpDocumentMetaInfo &metaInfo, bool overwritePrompt, bool lossyPrompt, QWidget *parent) { // TODO: Use KIO::NetAccess:mostLocalURL() for accessing home:/ (and other // such local URLs) for efficiency and because only local writes // are atomic. #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "kpDocument::savePixmapToFile (" << url << ",overwritePrompt=" << overwritePrompt << ",lossyPrompt=" << lossyPrompt << ")" << endl; saveOptions.printDebug (QLatin1String ("\tsaveOptions")); metaInfo.printDebug (QLatin1String ("\tmetaInfo")); #endif if (overwritePrompt && KIO::NetAccess::exists (url, KIO::NetAccess::DestinationSide/*write*/, parent)) { int result = KMessageBox::warningContinueCancel (parent, i18n ("A document called \"%1\" already exists.\n" "Do you want to overwrite it?", kpUrlFormatter::PrettyFilename (url)), QString(), KStandardGuiItem::overwrite ()); if (result != KMessageBox::Continue) { #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\tuser doesn't want to overwrite"; #endif return false; } } if (lossyPrompt && !lossyPromptContinue (pixmap, saveOptions, parent)) { #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\treturning false because of lossyPrompt"; #endif return false; } // Local file? if (url.isLocalFile ()) { const QString filename = url.toLocalFile (); // sync: All failure exit paths _must_ call QSaveFile::cancelWriting() or // else, the QSaveFile destructor will overwrite the file, // , despite the failure. QSaveFile atomicFileWriter (filename); { if (!atomicFileWriter.open (QIODevice::WriteOnly)) { // We probably don't need this as has not been // opened. atomicFileWriter.cancelWriting (); #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\treturning false because could not open QSaveFile" << " error=" << atomicFileWriter.error () << endl; #endif ::CouldNotCreateTemporaryFileDialog (parent); return false; } // Write to local temporary file. if (!savePixmapToDevice (pixmap, &atomicFileWriter, saveOptions, metaInfo, false/*no lossy prompt*/, parent)) { atomicFileWriter.cancelWriting (); #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\treturning false because could not save pixmap to device" << endl; #endif ::CouldNotSaveDialog (url, parent); return false; } // Atomically overwrite local file with the temporary file // we saved to. if (!atomicFileWriter.commit ()) { atomicFileWriter.cancelWriting (); #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\tcould not close QSaveFile"; #endif ::CouldNotSaveDialog (url, parent); return false; } } // sync QSaveFile.cancelWriting() } // Remote file? else { // Create temporary file that is deleted when the variable goes // out of scope. QTemporaryFile tempFile; if (!tempFile.open ()) { #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\treturning false because could not open tempFile"; #endif ::CouldNotCreateTemporaryFileDialog (parent); return false; } // Write to local temporary file. if (!savePixmapToDevice (pixmap, &tempFile, saveOptions, metaInfo, false/*no lossy prompt*/, parent)) { #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\treturning false because could not save pixmap to device" << endl; #endif ::CouldNotSaveDialog (url, parent); return false; } // Collect name of temporary file now, as QTemporaryFile::fileName() // stops working after close() is called. const QString tempFileName = tempFile.fileName (); #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\ttempFileName='" << tempFileName << "'"; #endif Q_ASSERT (!tempFileName.isEmpty ()); tempFile.close (); if (tempFile.error () != QFile::NoError) { #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\treturning false because could not close"; #endif ::CouldNotSaveDialog (url, parent); return false; } // Copy local temporary file to overwrite remote. // TODO: No one seems to know how to do this atomically // [http://lists.kde.org/?l=kde-core-devel&m=117845162728484&w=2]. // At least, fish:// (ssh) is definitely not atomic. if (!KIO::NetAccess::upload (tempFileName, url, parent)) { #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "\treturning false because could not upload"; #endif KMessageBox::error (parent, i18n ("Could not save image - failed to upload.")); return false; } } return true; } //--------------------------------------------------------------------- bool kpDocument::saveAs (const QUrl &url, const kpDocumentSaveOptions &saveOptions, bool overwritePrompt, bool lossyPrompt) { #if DEBUG_KP_DOCUMENT qCDebug(kpLogDocument) << "kpDocument::saveAs (" << url << "," << saveOptions.mimeType () << ")" << endl; #endif if (kpDocument::savePixmapToFile (imageWithSelection (), url, saveOptions, *metaInfo (), overwritePrompt, lossyPrompt, d->environ->dialogParent ())) { setURL (url, true/*is from url*/); *m_saveOptions = saveOptions; m_modified = false; m_savedAtLeastOnceBefore = true; emit documentSaved (); return true; } else { return false; } } //--------------------------------------------------------------------- diff --git a/document/kpDocument_Selection.cpp b/document/kpDocument_Selection.cpp index e83bf647..e565eeb9 100644 --- a/document/kpDocument_Selection.cpp +++ b/document/kpDocument_Selection.cpp @@ -1,340 +1,339 @@ /* Copyright (c) 2003-2007 Clarence Dang All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define DEBUG_KP_DOCUMENT 0 #include "kpDocument.h" #include "kpDocumentPrivate.h" -#include #include #include #include #include "kpLogCategories.h" #include #include "imagelib/kpColor.h" #include "kpDefs.h" #include "environments/document/kpDocumentEnvironment.h" #include "layers/selections/kpAbstractSelection.h" #include "layers/selections/image/kpAbstractImageSelection.h" #include "layers/selections/text/kpTextSelection.h" // public kpAbstractSelection *kpDocument::selection () const { return m_selection; } //--------------------------------------------------------------------- // public kpAbstractImageSelection *kpDocument::imageSelection () const { return dynamic_cast (m_selection); } //--------------------------------------------------------------------- // public kpTextSelection *kpDocument::textSelection () const { return dynamic_cast (m_selection); } //--------------------------------------------------------------------- // public void kpDocument::setSelection (const kpAbstractSelection &selection) { #if DEBUG_KP_DOCUMENT && 1 qCDebug(kpLogDocument) << "kpDocument::setSelection() sel boundingRect=" << selection.boundingRect () << endl; #endif d->environ->setQueueViewUpdates (); { const bool hadSelection = static_cast (m_selection); kpAbstractSelection *oldSelection = m_selection; // (must be called before giving the document a new selection, to // avoid a potential mess where switchToCompatibleTool() ends // the current selection tool, killing the new selection) bool isTextChanged = false; d->environ->switchToCompatibleTool (selection, &isTextChanged); Q_ASSERT (m_selection == oldSelection); m_selection = selection.clone (); // There's no need to uninitialize the old selection // (e.g. call disconnect()) since we delete it later. connect (m_selection, &kpAbstractSelection::changed, this, &kpDocument::slotContentsChanged); // // Now all kpDocument state has been set. // We can _only_ change the environment after that, as the environment // may access the document. Exception is above with // switchToCompatibleTool(). // d->environ->assertMatchingUIState (selection); // // Now all kpDocument and environment state has been set. // We can _only_ fire signals after that, as the signal receivers (the // "wider environment") may access the document and the environment. // #if DEBUG_KP_DOCUMENT && 1 qCDebug(kpLogDocument) << "\tcheck sel " << (int *) m_selection << " boundingRect=" << m_selection->boundingRect () << endl; #endif if (oldSelection) { if (oldSelection->hasContent ()) slotContentsChanged (oldSelection->boundingRect ()); else emit contentsChanged (oldSelection->boundingRect ()); delete oldSelection; oldSelection = nullptr; } if (m_selection->hasContent ()) slotContentsChanged (m_selection->boundingRect ()); else emit contentsChanged (m_selection->boundingRect ()); if (!hadSelection) emit selectionEnabled (true); if (isTextChanged) emit selectionIsTextChanged (textSelection ()); } d->environ->restoreQueueViewUpdates (); #if DEBUG_KP_DOCUMENT && 1 qCDebug(kpLogDocument) << "\tkpDocument::setSelection() ended"; #endif } //--------------------------------------------------------------------- // public kpImage kpDocument::getSelectedBaseImage () const { kpAbstractImageSelection *imageSel = imageSelection (); Q_ASSERT (imageSel); // Easy if we already have it :) const kpImage image = imageSel->baseImage (); if (!image.isNull ()) return image; const QRect boundingRect = imageSel->boundingRect (); Q_ASSERT (boundingRect.isValid ()); // OPT: This is very slow. Image / More Effects ... calls us twice // unnecessarily. return imageSel->givenImageMaskedByShape (getImageAt (boundingRect)); } //--------------------------------------------------------------------- // public void kpDocument::imageSelectionPullFromDocument (const kpColor &backgroundColor) { kpAbstractImageSelection *imageSel = imageSelection (); Q_ASSERT (imageSel); // Should not already have an image or we would not be pulling. Q_ASSERT (!imageSel->hasContent ()); const QRect boundingRect = imageSel->boundingRect (); Q_ASSERT (boundingRect.isValid ()); // // Get selection image from document // kpImage selectedImage = getSelectedBaseImage (); d->environ->setQueueViewUpdates (); imageSel->setBaseImage (selectedImage); // // Fill opaque bits of the hole in the document // #if !defined (QT_NO_DEBUG) && !defined (NDEBUG) if (imageSel->transparency ().isTransparent ()) { Q_ASSERT (backgroundColor == imageSel->transparency ().transparentColor ()); } else { // If this method is begin called by a tool, the assert does not // have to hold since transparentColor() might not be defined in Opaque // Mode. // // If we were called by a tricky sequence of undo/redo commands, the assert // does not have to hold for additional reason, which is that // kpMainWindow::setImageSelectionTransparency() does not have to // set in Opaque Mode. // // In practice, it probably does hold but I wouldn't bet on it. } #endif kpImage eraseImage(boundingRect.size(), QImage::Format_ARGB32_Premultiplied); eraseImage.fill(backgroundColor.toQRgb()); // only paint the region of the shape of the selection QPainter painter(m_image); painter.setClipRegion(imageSel->shapeRegion()); painter.setCompositionMode(QPainter::CompositionMode_Source); painter.drawImage(boundingRect.topLeft(), eraseImage); slotContentsChanged(boundingRect); d->environ->restoreQueueViewUpdates (); } //--------------------------------------------------------------------- // public void kpDocument::selectionDelete () { if ( !m_selection ) return; const QRect boundingRect = m_selection->boundingRect (); Q_ASSERT (boundingRect.isValid ()); const bool selectionHadContent = m_selection->hasContent (); delete m_selection; m_selection = nullptr; // HACK to prevent document from being modified when // user cancels dragging out a new selection // REFACTOR: Extract this out into a method. if (selectionHadContent) slotContentsChanged (boundingRect); else emit contentsChanged (boundingRect); emit selectionEnabled (false); } //--------------------------------------------------------------------- // public void kpDocument::selectionCopyOntoDocument (bool applySelTransparency) { // Empty selection, just doing nothing if ( !m_selection || !m_selection->hasContent() ) return; const QRect boundingRect = m_selection->boundingRect (); Q_ASSERT (boundingRect.isValid ()); if (imageSelection ()) { if (applySelTransparency) imageSelection ()->paint (m_image, rect ()); else imageSelection ()->paintWithBaseImage (m_image, rect ()); } else { // (for antialiasing with background) m_selection->paint (m_image, rect ()); } slotContentsChanged (boundingRect); } //--------------------------------------------------------------------- // public void kpDocument::selectionPushOntoDocument (bool applySelTransparency) { selectionCopyOntoDocument (applySelTransparency); selectionDelete (); } //--------------------------------------------------------------------- // public kpImage kpDocument::imageWithSelection () const { #if DEBUG_KP_DOCUMENT && 1 qCDebug(kpLogDocument) << "kpDocument::imageWithSelection()"; #endif // Have selection? // // It need not have any content because e.g. a text box with an opaque // background, but no content, is still visually there. if (m_selection) { #if DEBUG_KP_DOCUMENT && 1 qCDebug(kpLogDocument) << "\tselection @ " << m_selection->boundingRect (); #endif kpImage output = *m_image; // (this is a NOP for image selections without content) m_selection->paint (&output, rect ()); return output; } else { #if DEBUG_KP_DOCUMENT && 1 qCDebug(kpLogDocument) << "\tno selection"; #endif return *m_image; } } //--------------------------------------------------------------------- diff --git a/imagelib/effects/kpEffectBalance.cpp b/imagelib/effects/kpEffectBalance.cpp index 8e199c97..7a816d1f 100644 --- a/imagelib/effects/kpEffectBalance.cpp +++ b/imagelib/effects/kpEffectBalance.cpp @@ -1,213 +1,213 @@ /* Copyright (c) 2003-2007 Clarence Dang All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define DEBUG_KP_EFFECT_BALANCE 0 #include "kpEffectBalance.h" #include #include #include "kpLogCategories.h" #include "pixmapfx/kpPixmapFX.h" #if DEBUG_KP_EFFECT_BALANCE #include #endif static inline int between0And255 (int val) { if (val < 0) return 0; else if (val > 255) return 255; else return val; } static inline int brightness (int base, int strength) { return between0And255 (base + strength * 255 / 50); } static inline int contrast (int base, int strength) { return between0And255 ((base - 127) * (strength + 50) / 50 + 127); } static inline int gamma (int base, int strength) { - return between0And255 (qRound (255.0 * pow (base / 255.0, 1.0 / pow (10., strength / 50.0)))); + return between0And255 (qRound (255.0 * std::pow (base / 255.0, 1.0 / std::pow (10., strength / 50.0)))); } static inline int brightnessContrastGamma (int base, int newBrightness, int newContrast, int newGamma) { return gamma (contrast (brightness (base, newBrightness), newContrast), newGamma); } static inline QRgb brightnessContrastGammaForRGB (QRgb rgb, int channels, int brightness, int contrast, int gamma) { int red = qRed (rgb); int green = qGreen (rgb); int blue = qBlue (rgb); if (channels & kpEffectBalance::Red) red = brightnessContrastGamma (red, brightness, contrast, gamma); if (channels & kpEffectBalance::Green) green = brightnessContrastGamma (green, brightness, contrast, gamma); if (channels & kpEffectBalance::Blue) blue = brightnessContrastGamma (blue, brightness, contrast, gamma); return qRgba (red, green, blue, qAlpha (rgb)); } // public static kpImage kpEffectBalance::applyEffect (const kpImage &image, int channels, int brightness, int contrast, int gamma) { #if DEBUG_KP_EFFECT_BALANCE qCDebug(kpLogImagelib) << "kpEffectBalance::applyEffect(" << "channels=" << channels << ",brightness=" << brightness << ",contrast=" << contrast << ",gamma=" << gamma << ")" << endl; QTime timer; timer.start (); #endif QImage qimage = image; #if DEBUG_KP_EFFECT_BALANCE qCDebug(kpLogImagelib) << "\tconvertToImage=" << timer.restart (); #endif quint8 transformRed [256], transformGreen [256], transformBlue [256]; for (int i = 0; i < 256; i++) { quint8 applied = static_cast (brightnessContrastGamma (i, brightness, contrast, gamma)); if (channels & kpEffectBalance::Red) transformRed [i] = applied; else transformRed [i] = static_cast (i); if (channels & kpEffectBalance::Green) transformGreen [i] = applied; else transformGreen [i] = static_cast (i); if (channels & kpEffectBalance::Blue) transformBlue [i] = applied; else transformBlue [i] = static_cast (i); } #if DEBUG_KP_EFFECT_BALANCE qCDebug(kpLogImagelib) << "\tbuild lookup=" << timer.restart (); #endif if (qimage.depth () > 8) { for (int y = 0; y < qimage.height (); y++) { for (int x = 0; x < qimage.width (); x++) { const QRgb rgb = qimage.pixel (x, y); const quint8 red = static_cast (qRed (rgb)); const quint8 green = static_cast (qGreen (rgb)); const quint8 blue = static_cast (qBlue (rgb)); const quint8 alpha = static_cast (qAlpha (rgb)); qimage.setPixel (x, y, qRgba (transformRed [red], transformGreen [green], transformBlue [blue], alpha)); #if 0 qimage.setPixel (x, y, brightnessContrastGammaForRGB (qimage.pixel (x, y), channels, brightness, contrast, gamma)); #endif } } } else { for (int i = 0; i < qimage.colorCount (); i++) { const QRgb rgb = qimage.color (i); const quint8 red = static_cast (qRed (rgb)); const quint8 green = static_cast (qGreen (rgb)); const quint8 blue = static_cast (qBlue (rgb)); const quint8 alpha = static_cast (qAlpha (rgb)); qimage.setColor (i, qRgba (transformRed [red], transformGreen [green], transformBlue [blue], alpha)); #if 0 qimage.setColor (i, brightnessContrastGammaForRGB (qimage.color (i), channels, brightness, contrast, gamma)); #endif } } return qimage; } diff --git a/imagelib/effects/kpEffectHSV.cpp b/imagelib/effects/kpEffectHSV.cpp index 1aa0f10f..e3b36c77 100644 --- a/imagelib/effects/kpEffectHSV.cpp +++ b/imagelib/effects/kpEffectHSV.cpp @@ -1,190 +1,190 @@ /* Copyright (c) 2007 Mike Gashler All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // TODO: Clarence's code review #include "kpEffectHSV.h" #include #include #include #include "kpLogCategories.h" #include "pixmapfx/kpPixmapFX.h" static void ColorToHSV(unsigned int c, float* pHue, float* pSaturation, float* pValue) { int r = qRed(c); int g = qGreen(c); int b = qBlue(c); int min; if(b >= g && b >= r) { // Blue min = qMin(r, g); if(b != min) { *pHue = static_cast (r - g) / ((b - min) * 6) + static_cast (2) / 3; *pSaturation = 1.0f - static_cast (min) / static_cast (b); } else { *pHue = 0; *pSaturation = 0; } *pValue = static_cast (b) / 255; } else if(g >= r) { // Green min = qMin(b, r); if(g != min) { *pHue = static_cast (b - r) / ((g - min) * 6) + static_cast (1) / 3; *pSaturation = 1.0f - static_cast (min) / static_cast (g); } else { *pHue = 0; *pSaturation = 0; } *pValue = static_cast (g) / 255; } else { // Red min = qMin(g, b); if(r != min) { *pHue = static_cast (g - b) / ((r - min) * 6); if(*pHue < 0) (*pHue) += 1.0f; *pSaturation = 1.0f - static_cast (min) / static_cast (r); } else { *pHue = 0; *pSaturation = 0; } *pValue = static_cast (r) / 255; } } static unsigned int HSVToColor(int alpha, float hue, float saturation, float value) { //Q_ASSERT (hue >= 0 && hue <= 1 && saturation >= 0 && saturation <= 1 && value >= 0 && value <= 1); hue *= 5.999999f; int h = static_cast (hue); float f = hue - h; float p = value * (1.0f - saturation); float q = value * (1.0f - ((h & 1) == 0 ? 1.0f - f : f) * saturation); switch(h) { case 0: return qRgba(static_cast (value * 255.999999f), static_cast (q * 255.999999f), static_cast (p * 255.999999f), alpha); case 1: return qRgba(static_cast (q * 255.999999f), static_cast (value * 255.999999f), static_cast (p * 255.999999f), alpha); case 2: return qRgba(static_cast (p * 255.999999f), static_cast (value * 255.999999f), static_cast (q * 255.999999f), alpha); case 3: return qRgba(static_cast (p * 255.999999f), static_cast (q * 255.999999f), static_cast (value * 255.999999f), alpha); case 4: return qRgba(static_cast (q * 255.999999f), static_cast (p * 255.999999f), static_cast (value * 255.999999f), alpha); case 5: return qRgba(static_cast (value * 255.999999f), static_cast (p * 255.999999f), static_cast (q * 255.999999f), alpha); } return qRgba(0, 0, 0, alpha); } static QRgb AdjustHSVInternal (QRgb pix, double hueDiv360, double saturation, double value) { float h, s, v; ::ColorToHSV(pix, &h, &s, &v); const int alpha = qAlpha(pix); h += static_cast (hueDiv360); - h -= floor(h); + h -= std::floor(h); s = qMax(0.0f, qMin(1.0f, s + static_cast (saturation))); v = qMax(0.0f, qMin(1.0f, v + static_cast (value))); return ::HSVToColor(alpha, h, s, v); } static void AdjustHSV (QImage* pImage, double hue, double saturation, double value) { hue /= 360; if (pImage->depth () > 8) { for (int y = 0; y < pImage->height (); y++) { for (int x = 0; x < pImage->width (); x++) { QRgb pix = pImage->pixel (x, y); pix = ::AdjustHSVInternal (pix, hue, saturation, value); pImage->setPixel (x, y, pix); } } } else { for (int i = 0; i < pImage->colorCount (); i++) { QRgb pix = pImage->color (i); pix = ::AdjustHSVInternal (pix, hue, saturation, value); pImage->setColor (i, pix); } } } // public static kpImage kpEffectHSV::applyEffect (const kpImage &image, double hue, double saturation, double value) { QImage qimage(image); ::AdjustHSV (&qimage, hue, saturation, value); return qimage; } diff --git a/imagelib/kpDocumentMetaInfo.cpp b/imagelib/kpDocumentMetaInfo.cpp index 756b9929..d1b54c64 100644 --- a/imagelib/kpDocumentMetaInfo.cpp +++ b/imagelib/kpDocumentMetaInfo.cpp @@ -1,282 +1,282 @@ /* Copyright (c) 2003-2007 Clarence Dang All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "kpDocumentMetaInfo.h" #include #include #include #include "kpLogCategories.h" #include "kpDefs.h" // // Constants which "ought to be enough for anybody" // LOTODO: Maybe there are some QImage constants somewhere? // // public static // (round up to guarantee at least 1 dot per inch) const int kpDocumentMetaInfo::MinDotsPerMeter = - int (ceil (1/*single dot per inch - a very low DPI*/ * KP_INCHES_PER_METER) + 0.1); + int (std::ceil (1/*single dot per inch - a very low DPI*/ * KP_INCHES_PER_METER) + 0.1); const int kpDocumentMetaInfo::MaxDotsPerMeter = int ((600 * 100)/*a lot of DPI*/ * KP_INCHES_PER_METER); // public static const int kpDocumentMetaInfo::MaxOffset = (4000/*big image*/ * 100)/*a very big image*/; const int kpDocumentMetaInfo::MinOffset = -kpDocumentMetaInfo::MaxOffset; //--------------------------------------------------------------------- struct kpDocumentMetaInfoPrivate { int m_dotsPerMeterX, m_dotsPerMeterY; QPoint m_offset; QMap m_textMap; }; //--------------------------------------------------------------------- // public kpDocumentMetaInfo::kpDocumentMetaInfo () : d (new kpDocumentMetaInfoPrivate ()) { d->m_dotsPerMeterX = 0; d->m_dotsPerMeterY = 0; d->m_offset = QPoint (0, 0); } //--------------------------------------------------------------------- kpDocumentMetaInfo::kpDocumentMetaInfo (const kpDocumentMetaInfo &rhs) : d (new kpDocumentMetaInfoPrivate ()) { d->m_dotsPerMeterX = rhs.dotsPerMeterX (); d->m_dotsPerMeterY = rhs.dotsPerMeterY (); d->m_offset = rhs.offset (); d->m_textMap = rhs.textMap (); } //--------------------------------------------------------------------- // public kpDocumentMetaInfo::~kpDocumentMetaInfo () { delete d; } //--------------------------------------------------------------------- // public bool kpDocumentMetaInfo::operator== (const kpDocumentMetaInfo &rhs) const { return (d->m_dotsPerMeterX == rhs.d->m_dotsPerMeterX && d->m_dotsPerMeterY == rhs.d->m_dotsPerMeterY && d->m_offset == rhs.d->m_offset && d->m_textMap == rhs.d->m_textMap); } //--------------------------------------------------------------------- // public bool kpDocumentMetaInfo::operator!= (const kpDocumentMetaInfo &rhs) const { return !(*this == rhs); } //--------------------------------------------------------------------- // public kpDocumentMetaInfo &kpDocumentMetaInfo::operator= (const kpDocumentMetaInfo &rhs) { if (this == &rhs) return *this; d->m_dotsPerMeterX = rhs.dotsPerMeterX (); d->m_dotsPerMeterY = rhs.dotsPerMeterY (); d->m_offset = rhs.offset (); d->m_textMap = rhs.textMap (); return *this; } //--------------------------------------------------------------------- // public void kpDocumentMetaInfo::printDebug (const QString &prefix) const { const QString usedPrefix = !prefix.isEmpty() ? QString(prefix + QLatin1String(":")) : QString(); qCDebug(kpLogImagelib) << usedPrefix; qCDebug(kpLogImagelib) << "dotsPerMeter X=" << dotsPerMeterX () << " Y=" << dotsPerMeterY () << " offset=" << offset () << endl; QList keyList = textKeys (); for (QList ::const_iterator it = keyList.constBegin (); it != keyList.constEnd (); ++it) { qCDebug(kpLogImagelib) << "key=" << (*it) << " text=" << text (*it) << endl; } qCDebug(kpLogImagelib) << usedPrefix << "ENDS"; } //--------------------------------------------------------------------- // public kpCommandSize::SizeType kpDocumentMetaInfo::size () const { kpCommandSize::SizeType ret = 0; foreach (const QString &key, d->m_textMap.keys ()) { ret += kpCommandSize::StringSize (key) + kpCommandSize::StringSize (d->m_textMap [key]); } // We don't know what the QMap size overhead is so overestimate the size // rather than underestimating it. // LOTODO: Find the proper size in bytes. return ret * 3; } //--------------------------------------------------------------------- // public int kpDocumentMetaInfo::dotsPerMeterX () const { return d->m_dotsPerMeterX; } //--------------------------------------------------------------------- // public void kpDocumentMetaInfo::setDotsPerMeterX (int val) { // Unspecified resolution? if (val == 0) { d->m_dotsPerMeterX = 0; return; } d->m_dotsPerMeterX = qBound (MinDotsPerMeter, val, MaxDotsPerMeter); } //--------------------------------------------------------------------- // public int kpDocumentMetaInfo::dotsPerMeterY () const { return d->m_dotsPerMeterY; } //--------------------------------------------------------------------- // public void kpDocumentMetaInfo::setDotsPerMeterY (int val) { // Unspecified resolution? if (val == 0) { d->m_dotsPerMeterY = 0; return; } d->m_dotsPerMeterY = qBound (MinDotsPerMeter, val, MaxDotsPerMeter); } //--------------------------------------------------------------------- // public QPoint kpDocumentMetaInfo::offset () const { return d->m_offset; } //--------------------------------------------------------------------- // public void kpDocumentMetaInfo::setOffset (const QPoint &point) { const int x = qBound (MinOffset, point.x (), MaxOffset); const int y = qBound (MinOffset, point.y (), MaxOffset); d->m_offset = QPoint (x, y); } //--------------------------------------------------------------------- // public QMap kpDocumentMetaInfo::textMap () const { return d->m_textMap; } //--------------------------------------------------------------------- // public QList kpDocumentMetaInfo::textKeys () const { return d->m_textMap.keys (); } //--------------------------------------------------------------------- // public QString kpDocumentMetaInfo::text (const QString &key) const { if (key.isEmpty ()) return QString (); return d->m_textMap [key]; } //--------------------------------------------------------------------- // public void kpDocumentMetaInfo::setText (const QString &key, const QString &value) { if (key.isEmpty ()) return; d->m_textMap [key] = value; } //--------------------------------------------------------------------- diff --git a/kpDefs.h b/kpDefs.h index cb2bdd6d..f3ee9668 100644 --- a/kpDefs.h +++ b/kpDefs.h @@ -1,151 +1,144 @@ /* Copyright (c) 2003-2007 Clarence Dang All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef KP_DEFS_H #define KP_DEFS_H #include #include #include #include #include // approx. 2896x2896x32bpp or 3344x3344x24bpp (TODO: 24==32?) or 4096*4096x16bpp #define KP_BIG_IMAGE_SIZE (32 * 1048576) -#define KP_PI 3.141592653589793238462 - - -#define KP_DEGREES_TO_RADIANS(deg) ((deg) * KP_PI / 180.0) -#define KP_RADIANS_TO_DEGREES(rad) ((rad) * 180.0 / KP_PI) - - #define KP_INVALID_POINT QPoint (INT_MIN / 8, INT_MIN / 8) #define KP_INVALID_WIDTH (INT_MIN / 8) #define KP_INVALID_HEIGHT (INT_MIN / 8) #define KP_INVALID_SIZE QSize (INT_MIN / 8, INT_MIN / 8) #define KP_INCHES_PER_METER (100 / 2.54) #define KP_MILLIMETERS_PER_INCH 25.4 // // Settings // #define kpSettingsGroupRecentFiles "Recent Files" #define kpSettingsGroupGeneral "General Settings" #define kpSettingFirstTime "First Time" #define kpSettingShowGrid "Show Grid" #define kpSettingShowPath "Show Path" #define kpSettingDrawAntiAliased "Draw AntiAliased" #define kpSettingColorSimilarity "Color Similarity" #define kpSettingDitherOnOpen "Dither on Open if Screen is 15/16bpp and Image Num Colors More Than" #define kpSettingPrintImageCenteredOnPage "Print Image Centered On Page" #define kpSettingOpenImagesInSameWindow "Open Images in the Same Window" #define kpSettingsGroupFileSaveAs "File/Save As" #define kpSettingsGroupFileExport "File/Export" #define kpSettingsGroupEditCopyTo "Edit/Copy To" #define kpSettingForcedMimeType "Forced MimeType" #define kpSettingForcedColorDepth "Forced Color Depth" #define kpSettingForcedDither "Forced Dither" #define kpSettingForcedQuality "Forced Quality" #define kpSettingLastDocSize "Last Document Size" #define kpSettingMoreEffectsLastEffect "More Effects - Last Effect" #define kpSettingsGroupMimeTypeProperties "MimeType Properties Version 1.2-3" #define kpSettingMimeTypeMaximumColorDepth "Maximum Color Depth" #define kpSettingMimeTypeHasConfigurableColorDepth "Configurable Color Depth" #define kpSettingMimeTypeHasConfigurableQuality "Configurable Quality Setting" #define kpSettingsGroupUndoRedo "Undo/Redo Settings" #define kpSettingUndoMinLimit "Min Limit" #define kpSettingUndoMaxLimit "Max Limit" #define kpSettingUndoMaxLimitSizeLimit "Max Limit Size Limit" #define kpSettingsGroupThumbnail "Thumbnail Settings" #define kpSettingThumbnailShown "Shown" #define kpSettingThumbnailGeometry "Geometry" #define kpSettingThumbnailZoomed "Zoomed" #define kpSettingThumbnailShowRectangle "ShowRectangle" #define kpSettingsGroupPreviewSave "Save Preview Settings" #define kpSettingPreviewSaveGeometry "Geometry" #define kpSettingPreviewSaveUpdateDelay "Update Delay" #define kpSettingsGroupTools "Tool Settings" #define kpSettingLastTool "Last Used Tool" #define kpSettingToolBoxIconSize "Tool Box Icon Size" #define kpSettingsGroupText "Text Settings" #define kpSettingFontFamily "Font Family" #define kpSettingFontSize "Font Size" #define kpSettingBold "Bold" #define kpSettingItalic "Italic" #define kpSettingUnderline "Underline" #define kpSettingStrikeThru "Strike Thru" #define kpSettingsGroupFlattenEffect "Flatten Effect Settings" #define kpSettingFlattenEffectColor1 "Color1" #define kpSettingFlattenEffectColor2 "Color2" // // Session Restore Setting // // URL of the document in the main window. // // This key only exists if the document does. If it exists, it can be empty. // The URL need not point to a file that exists e.g. "kolourpaint doesnotexist.png". #define kpSessionSettingDocumentUrl QString::fromLatin1 ("Session Document Url") // The size of a document which is not from a URL e.g. "kolourpaint doesnotexist.png". // This key does not exist for documents from URLs. #define kpSessionSettingNotFromUrlDocumentSize QString::fromLatin1 ("Session Not-From-Url Document Size") #endif // KP_DEFS_H diff --git a/pixmapfx/kpPixmapFX_DrawShapes.cpp b/pixmapfx/kpPixmapFX_DrawShapes.cpp index 4276dbd4..48c14c6c 100644 --- a/pixmapfx/kpPixmapFX_DrawShapes.cpp +++ b/pixmapfx/kpPixmapFX_DrawShapes.cpp @@ -1,278 +1,277 @@ /* Copyright (c) 2003-2007 Clarence Dang All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define DEBUG_KP_PIXMAP_FX 0 #include "kpPixmapFX.h" -#include #include #include #include #include #include #include "kpLogCategories.h" #include "layers/selections/kpAbstractSelection.h" #include "imagelib/kpColor.h" #include "kpDefs.h" //--------------------------------------------------------------------- // Returns whether there is only 1 distinct point in . bool kpPixmapFX::Only1PixelInPointArray (const QPolygon &points) { if (points.count () == 0) return false; for (int i = 1; i < static_cast (points.count ()); i++) { if (points [i] != points [0]) return false; } return true; } //--------------------------------------------------------------------- // Warp the given from 1 to 0. // This is not always done (specifically if ) because // width 0 sometimes looks worse. // // Qt lines of width 1 look like they have a width between 1-2 i.e.: // // # // ## // # // # // // compared to Qt's special "width 0" which just means a "proper" width 1: // // # // # // # // # // static int WidthToQPenWidth (int width, bool drawingEllipse = false) { if (width == 1) { // 3x10 ellipse with Qt width 0 looks like rectangle. // Therefore, do not apply this 1 -> 0 transformations for ellipses. if (!drawingEllipse) { // Closer to looking width 1, for lines at least. return 0; } } return width; } //--------------------------------------------------------------------- static void QPainterSetPenWithStipple (QPainter *p, const kpColor &fColor, int penWidth, const kpColor &fStippleColor = kpColor::Invalid, bool isEllipseLike = false) { if (!fStippleColor.isValid ()) { p->setPen ( kpPixmapFX::QPainterDrawLinePen ( fColor.toQColor(), ::WidthToQPenWidth (penWidth, isEllipseLike))); } else { QPen usePen = kpPixmapFX::QPainterDrawLinePen ( fColor.toQColor(), ::WidthToQPenWidth (penWidth, isEllipseLike)); usePen.setStyle (Qt::DashLine); p->setPen (usePen); p->setBackground (fStippleColor.toQColor()); p->setBackgroundMode (Qt::OpaqueMode); } } //--------------------------------------------------------------------- // public static QPen kpPixmapFX::QPainterDrawRectPen (const QColor &color, int qtWidth) { return QPen (color, qtWidth, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin); } //--------------------------------------------------------------------- // public static QPen kpPixmapFX::QPainterDrawLinePen (const QColor &color, int qtWidth) { return QPen (color, qtWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); } //--------------------------------------------------------------------- // // drawPolyline() / drawLine() // // public static void kpPixmapFX::drawPolyline (QImage *image, const QPolygon &points, const kpColor &color, int penWidth, const kpColor &stippleColor) { QPainter painter(image); ::QPainterSetPenWithStipple(&painter, color, penWidth, stippleColor); // Qt bug: single point doesn't show up depending on penWidth. if (Only1PixelInPointArray(points)) { #if DEBUG_KP_PIXMAP_FX qCDebug(kpLogPixmapfx) << "\tinvoking single point hack"; #endif painter.drawPoint(points[0]); return; } painter.drawPolyline(points); } //--------------------------------------------------------------------- // // drawPolygon() // // public static void kpPixmapFX::drawPolygon (QImage *image, const QPolygon &points, const kpColor &fcolor, int penWidth, const kpColor &bcolor, bool isFinal, const kpColor &fStippleColor) { QPainter p(image); ::QPainterSetPenWithStipple (&p, fcolor, penWidth, fStippleColor); if (bcolor.isValid ()) p.setBrush (QBrush (bcolor.toQColor())); // HACK: seems to be needed if set_Pen_(Qt::color0) else fills with Qt::color0. else p.setBrush (Qt::NoBrush); // Qt bug: single point doesn't show up depending on penWidth. if (Only1PixelInPointArray (points)) { #if DEBUG_KP_PIXMAP_FX qCDebug(kpLogPixmapfx) << "\tinvoking single point hack"; #endif p.drawPoint(points [0]); return; } // TODO: why aren't the ends rounded? p.drawPolygon(points, Qt::OddEvenFill); if ( isFinal ) return; if ( points.count() <= 2 ) return; p.setCompositionMode(QPainter::RasterOp_SourceXorDestination); p.setPen(QPen(Qt::white)); p.drawLine(points[0], points[points.count() - 1]); } //--------------------------------------------------------------------- // public static void kpPixmapFX::fillRect (QImage *image, int x, int y, int width, int height, const kpColor &color, const kpColor &stippleColor) { QPainter painter(image); if (!stippleColor.isValid ()) { painter.fillRect (x, y, width, height, color.toQColor()); } else { const int StippleSize = 4; painter.setClipRect (x, y, width, height); for (int dy = 0; dy < height; dy += StippleSize) { for (int dx = 0; dx < width; dx += StippleSize) { const bool parity = ((dy + dx) / StippleSize) % 2; kpColor useColor; if (!parity) useColor = color; else useColor = stippleColor; painter.fillRect (x + dx, y + dy, StippleSize, StippleSize, useColor.toQColor()); } } } } //--------------------------------------------------------------------- void kpPixmapFX::drawStippleRect(QImage *image, int x, int y, int width, int height, const kpColor &fColor, const kpColor &fStippleColor) { QPainter painter(image); painter.setPen(QPen(fColor.toQColor(), 1, Qt::DashLine)); painter.setBackground(fStippleColor.toQColor()); painter.setBackgroundMode(Qt::OpaqueMode); painter.drawRect(x, y, width - 1, height - 1); } //--------------------------------------------------------------------- diff --git a/pixmapfx/kpPixmapFX_GetSetPixmapParts.cpp b/pixmapfx/kpPixmapFX_GetSetPixmapParts.cpp index 60a5b599..bc36a534 100644 --- a/pixmapfx/kpPixmapFX_GetSetPixmapParts.cpp +++ b/pixmapfx/kpPixmapFX_GetSetPixmapParts.cpp @@ -1,143 +1,142 @@ /* Copyright (c) 2003-2007 Clarence Dang All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define DEBUG_KP_PIXMAP_FX 0 #include "kpPixmapFX.h" -#include #include #include #include #include #include "kpLogCategories.h" #include "imagelib/kpColor.h" //--------------------------------------------------------------------- // public static QImage kpPixmapFX::getPixmapAt (const QImage &image, const QRect &rect) { return image.copy(rect); } //--------------------------------------------------------------------- // public static void kpPixmapFX::setPixmapAt(QImage *destPtr, const QRect &destRect, const QImage &src) { #if DEBUG_KP_PIXMAP_FX && 1 qCDebug(kpLogPixmapfx) << "kpPixmapFX::setPixmapAt(destPixmap->rect=" << destPtr->rect () << ",destRect=" << destRect << ",src.rect=" << src.rect () << ")" << endl; #endif Q_ASSERT (destPtr); // You cannot copy more than what you have. Q_ASSERT (destRect.width () <= src.width () && destRect.height () <= src.height ()); QPainter painter(destPtr); // destination shall be source only painter.setCompositionMode(QPainter::CompositionMode_Source); painter.drawImage(destRect.topLeft(), src, QRect(0, 0, destRect.width(), destRect.height())); } //--------------------------------------------------------------------- // public static void kpPixmapFX::setPixmapAt (QImage *destPtr, const QPoint &destAt, const QImage &src) { kpPixmapFX::setPixmapAt (destPtr, QRect (destAt.x (), destAt.y (), src.width (), src.height ()), src); } //--------------------------------------------------------------------- // public static void kpPixmapFX::setPixmapAt (QImage *destPtr, int destX, int destY, const QImage &src) { kpPixmapFX::setPixmapAt (destPtr, QPoint (destX, destY), src); } //--------------------------------------------------------------------- // public static void kpPixmapFX::paintPixmapAt (QImage *destPtr, const QPoint &destAt, const QImage &src) { // draw image with SourceOver composition mode QPainter painter(destPtr); painter.drawImage(destAt, src); } //--------------------------------------------------------------------- // public static void kpPixmapFX::paintPixmapAt (QImage *destPtr, int destX, int destY, const QImage &src) { kpPixmapFX::paintPixmapAt(destPtr, QPoint (destX, destY), src); } //--------------------------------------------------------------------- // public static kpColor kpPixmapFX::getColorAtPixel (const QImage &img, const QPoint &at) { if (!img.valid (at.x (), at.y ())) return kpColor::Invalid; QRgb rgba = img.pixel(at); return kpColor (rgba); } //--------------------------------------------------------------------- // public static kpColor kpPixmapFX::getColorAtPixel (const QImage &img, int x, int y) { return kpPixmapFX::getColorAtPixel (img, QPoint (x, y)); } //--------------------------------------------------------------------- diff --git a/pixmapfx/kpPixmapFX_Transforms.cpp b/pixmapfx/kpPixmapFX_Transforms.cpp index 5986ffed..8d2b936e 100644 --- a/pixmapfx/kpPixmapFX_Transforms.cpp +++ b/pixmapfx/kpPixmapFX_Transforms.cpp @@ -1,674 +1,674 @@ /* Copyright (c) 2003-2007 Clarence Dang All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define DEBUG_KP_PIXMAP_FX 0 #include "kpPixmapFX.h" -#include +#include #include #include #include #include #include "kpLogCategories.h" #include "layers/selections/kpAbstractSelection.h" #include "imagelib/kpColor.h" #include "kpDefs.h" //--------------------------------------------------------------------- // public static void kpPixmapFX::resize (QImage *destPtr, int w, int h, const kpColor &backgroundColor) { #if DEBUG_KP_PIXMAP_FX && 1 qCDebug(kpLogPixmapfx) << "kpPixmapFX::resize()"; #endif if (!destPtr) return; const int oldWidth = destPtr->width (); const int oldHeight = destPtr->height (); if (w == oldWidth && h == oldHeight) return; QImage newImage (w, h, QImage::Format_ARGB32_Premultiplied); // Would have new undefined areas? if (w > oldWidth || h > oldHeight) newImage.fill (backgroundColor.toQRgb ()); // Copy over old pixmap. QPainter painter(&newImage); painter.setCompositionMode(QPainter::CompositionMode_Source); painter.drawImage(0, 0, *destPtr); painter.end(); // Replace pixmap with new one. *destPtr = newImage; } //--------------------------------------------------------------------- // public static QImage kpPixmapFX::resize (const QImage &image, int w, int h, const kpColor &backgroundColor) { QImage ret = image; kpPixmapFX::resize (&ret, w, h, backgroundColor); return ret; } //--------------------------------------------------------------------- // public static void kpPixmapFX::scale (QImage *destPtr, int w, int h, bool pretty) { if (!destPtr) return; *destPtr = kpPixmapFX::scale (*destPtr, w, h, pretty); } //--------------------------------------------------------------------- // public static QImage kpPixmapFX::scale (const QImage &image, int w, int h, bool pretty) { #if DEBUG_KP_PIXMAP_FX && 0 qCDebug(kpLogPixmapfx) << "kpPixmapFX::scale(oldRect=" << image.rect () << ",w=" << w << ",h=" << h << ",pretty=" << pretty << ")" << endl; #endif if (w == image.width () && h == image.height ()) return image; return image.scaled(w, h, Qt::IgnoreAspectRatio, pretty ? Qt::SmoothTransformation : Qt::FastTransformation); } //--------------------------------------------------------------------- // public static const double kpPixmapFX::AngleInDegreesEpsilon = - KP_RADIANS_TO_DEGREES (atan (1.0 / 10000.0)) + qRadiansToDegrees (std::tan (1.0 / 10000.0)) / (2.0/*max error allowed*/ * 2.0/*for good measure*/); static void MatrixDebug (const QString matrixName, const QMatrix &matrix, int srcPixmapWidth = -1, int srcPixmapHeight = -1) { #if DEBUG_KP_PIXMAP_FX const int w = srcPixmapWidth, h = srcPixmapHeight; qCDebug(kpLogPixmapfx) << matrixName << "=" << matrix; // Sometimes this precision lets us see unexpected rounding errors. fprintf (stderr, "m11=%.24f m12=%.24f m21=%.24f m22=%.24f dx=%.24f dy=%.24f\n", matrix.m11 (), matrix.m12 (), matrix.m21 (), matrix.m22 (), matrix.dx (), matrix.dy ()); if (w > 0 && h > 0) { qCDebug(kpLogPixmapfx) << "(0,0) ->" << matrix.map (QPoint (0, 0)); qCDebug(kpLogPixmapfx) << "(w-1,0) ->" << matrix.map (QPoint (w - 1, 0)); qCDebug(kpLogPixmapfx) << "(0,h-1) ->" << matrix.map (QPoint (0, h - 1)); qCDebug(kpLogPixmapfx) << "(w-1,h-1) ->" << matrix.map (QPoint (w - 1, h - 1)); } #if 0 QMatrix trueMatrix = QPixmap::trueMatrix (matrix, w, h); qCDebug(kpLogPixmapfx) << matrixName << "trueMatrix=" << trueMatrix; if (w > 0 && h > 0) { qCDebug(kpLogPixmapfx) << "(0,0) ->" << trueMatrix.map (QPoint (0, 0)); qCDebug(kpLogPixmapfx) << "(w-1,0) ->" << trueMatrix.map (QPoint (w - 1, 0)); qCDebug(kpLogPixmapfx) << "(0,h-1) ->" << trueMatrix.map (QPoint (0, h - 1)); qCDebug(kpLogPixmapfx) << "(w-1,h-1) ->" << trueMatrix.map (QPoint (w - 1, h - 1)); } #endif #else Q_UNUSED (matrixName); Q_UNUSED (matrix); Q_UNUSED (srcPixmapWidth); Q_UNUSED (srcPixmapHeight); #endif // DEBUG_KP_PIXMAP_FX } //--------------------------------------------------------------------- // Theoretically, this should act the same as QPixmap::trueMatrix() but // it doesn't. As an example, if you rotate tests/transforms.png by 90 // degrees clockwise, this returns the correct of 26 but // QPixmap::trueMatrix() returns 27. // // You should use the returned matrix to map points accurately (e.g. selection // borders). For QPainter::drawPixmap()/drawImage() + setWorldMatrix() // rendering accuracy, pass the returned matrix through QPixmap::trueMatrix() // and use that. // // TODO: If you put the flipMatrix() of tests/transforms.png through this, // the output is the same as QPixmap::trueMatrix(): is one off // (dy=27 instead of 26). // SYNC: I bet this is a Qt4 bug. static QMatrix MatrixWithZeroOrigin (const QMatrix &matrix, int width, int height) { #if DEBUG_KP_PIXMAP_FX qCDebug(kpLogPixmapfx) << "matrixWithZeroOrigin(w=" << width << ",h=" << height << ")"; qCDebug(kpLogPixmapfx) << "\tmatrix: m11=" << matrix.m11 () << "m12=" << matrix.m12 () << "m21=" << matrix.m21 () << "m22=" << matrix.m22 () << "dx=" << matrix.dx () << "dy=" << matrix.dy (); #endif QRect mappedRect = matrix.mapRect (QRect (0, 0, width, height)); #if DEBUG_KP_PIXMAP_FX qCDebug(kpLogPixmapfx) << "\tmappedRect=" << mappedRect; #endif QMatrix translatedMatrix ( matrix.m11 (), matrix.m12 (), matrix.m21 (), matrix.m22 (), matrix.dx () - mappedRect.left (), matrix.dy () - mappedRect.top ()); #if DEBUG_KP_PIXMAP_FX qCDebug(kpLogPixmapfx) << "\treturning" << translatedMatrix; qCDebug(kpLogPixmapfx) << "(0,0) ->" << translatedMatrix.map (QPoint (0, 0)); qCDebug(kpLogPixmapfx) << "(w-1,0) ->" << translatedMatrix.map (QPoint (width - 1, 0)); qCDebug(kpLogPixmapfx) << "(0,h-1) ->" << translatedMatrix.map (QPoint (0, height - 1)); qCDebug(kpLogPixmapfx) << "(w-1,h-1) ->" << translatedMatrix.map (QPoint (width - 1, height - 1)); #endif return translatedMatrix; } //--------------------------------------------------------------------- static double TrueMatrixEpsilon = 0.000001; // An attempt to reverse tiny rounding errors introduced by QPixmap::trueMatrix() // when skewing tests/transforms.png by 45% horizontally (with TransformPixmap() // using a QPixmap painter, prior to the 2007-10-09 change -- did not test after // the change). // Unfortunately, this does not work enough to stop the rendering errors // that follow. But it was worth a try and might still help us given the // sometimes excessive aliasing QPainter::draw{Pixmap,Image}() gives us, when // QPainter::SmoothPixmapTransform is disabled. static double TrueMatrixFixInts (double x) { - if (fabs (x - qRound (x)) < TrueMatrixEpsilon) + if (std::fabs (x - qRound (x)) < TrueMatrixEpsilon) return qRound (x); else return x; } //--------------------------------------------------------------------- static QMatrix TrueMatrix (const QMatrix &matrix, int srcPixmapWidth, int srcPixmapHeight) { ::MatrixDebug ("TrueMatrix(): org", matrix); const QMatrix truMat = QPixmap::trueMatrix (matrix, srcPixmapWidth, srcPixmapHeight); ::MatrixDebug ("TrueMatrix(): passed through QPixmap::trueMatrix()", truMat); const QMatrix retMat ( ::TrueMatrixFixInts (truMat.m11 ()), ::TrueMatrixFixInts (truMat.m12 ()), ::TrueMatrixFixInts (truMat.m21 ()), ::TrueMatrixFixInts (truMat.m22 ()), ::TrueMatrixFixInts (truMat.dx ()), ::TrueMatrixFixInts (truMat.dy ())); ::MatrixDebug ("TrueMatrix(): fixed ints", retMat); return retMat; } //--------------------------------------------------------------------- // Like QPixmap::transformed() but fills new areas with // (unless is invalid) and works around internal QMatrix // floating point -> integer oddities, that would otherwise give fatally // incorrect results. If you don't believe me on this latter point, compare // QPixmap::transformed() to us using a flip matrix or a rotate-by-multiple-of-90 // matrix on tests/transforms.png -- QPixmap::transformed()'s output is 1 // pixel too high or low depending on whether the matrix is passed through // QPixmap::trueMatrix(). // // Use and to specify the intended output size // of the pixmap. -1 if don't care. static QImage TransformPixmap (const QImage &pm, const QMatrix &transformMatrix_, const kpColor &backgroundColor, int targetWidth, int targetHeight) { QMatrix transformMatrix = transformMatrix_; #if DEBUG_KP_PIXMAP_FX && 1 qCDebug(kpLogPixmapfx) << "kppixmapfx.cpp: TransformPixmap(pm.size=" << pm.size () << ",targetWidth=" << targetWidth << ",targetHeight=" << targetHeight << ")" << endl; #endif QRect newRect = transformMatrix.mapRect (pm.rect ()); #if DEBUG_KP_PIXMAP_FX && 1 qCDebug(kpLogPixmapfx) << "\tmappedRect=" << newRect; #endif QMatrix scaleMatrix; if (targetWidth > 0 && targetWidth != newRect.width ()) { #if DEBUG_KP_PIXMAP_FX && 1 qCDebug(kpLogPixmapfx) << "\tadjusting for targetWidth"; #endif scaleMatrix.scale (double (targetWidth) / double (newRect.width ()), 1); } if (targetHeight > 0 && targetHeight != newRect.height ()) { #if DEBUG_KP_PIXMAP_FX && 1 qCDebug(kpLogPixmapfx) << "\tadjusting for targetHeight"; #endif scaleMatrix.scale (1, double (targetHeight) / double (newRect.height ())); } if (!scaleMatrix.isIdentity ()) { #if DEBUG_KP_PIXMAP_FX && 1 // TODO: What is going on here??? Why isn't matrix * working properly? QMatrix wrongMatrix = transformMatrix * scaleMatrix; QMatrix oldHat = transformMatrix; if (targetWidth > 0 && targetWidth != newRect.width ()) oldHat.scale (double (targetWidth) / double (newRect.width ()), 1); if (targetHeight > 0 && targetHeight != newRect.height ()) oldHat.scale (1, double (targetHeight) / double (newRect.height ())); QMatrix altHat = transformMatrix; altHat.scale ((targetWidth > 0 && targetWidth != newRect.width ()) ? double (targetWidth) / double (newRect.width ()) : 1, (targetHeight > 0 && targetHeight != newRect.height ()) ? double (targetHeight) / double (newRect.height ()) : 1); QMatrix correctMatrix = scaleMatrix * transformMatrix; qCDebug(kpLogPixmapfx) << "\tsupposedlyWrongMatrix: m11=" << wrongMatrix.m11 () // <<<---- this is the correct matrix??? << " m12=" << wrongMatrix.m12 () << " m21=" << wrongMatrix.m21 () << " m22=" << wrongMatrix.m22 () << " dx=" << wrongMatrix.dx () << " dy=" << wrongMatrix.dy () << " rect=" << wrongMatrix.mapRect (pm.rect ()) << endl << "\ti_used_to_use_thisMatrix: m11=" << oldHat.m11 () << " m12=" << oldHat.m12 () << " m21=" << oldHat.m21 () << " m22=" << oldHat.m22 () << " dx=" << oldHat.dx () << " dy=" << oldHat.dy () << " rect=" << oldHat.mapRect (pm.rect ()) << endl << "\tabove but scaled at the same time: m11=" << altHat.m11 () << " m12=" << altHat.m12 () << " m21=" << altHat.m21 () << " m22=" << altHat.m22 () << " dx=" << altHat.dx () << " dy=" << altHat.dy () << " rect=" << altHat.mapRect (pm.rect ()) << endl << "\tsupposedlyCorrectMatrix: m11=" << correctMatrix.m11 () << " m12=" << correctMatrix.m12 () << " m21=" << correctMatrix.m21 () << " m22=" << correctMatrix.m22 () << " dx=" << correctMatrix.dx () << " dy=" << correctMatrix.dy () << " rect=" << correctMatrix.mapRect (pm.rect ()) << endl; #endif transformMatrix = transformMatrix * scaleMatrix; newRect = transformMatrix.mapRect (pm.rect ()); #if DEBUG_KP_PIXMAP_FX && 1 qCDebug(kpLogPixmapfx) << "\tnewRect after targetWidth,targetHeight adjust=" << newRect; #endif } ::MatrixDebug ("TransformPixmap(): before trueMatrix", transformMatrix, pm.width (), pm.height ()); #if DEBUG_KP_PIXMAP_FX && 1 QMatrix oldMatrix = transformMatrix; #endif // Translate the matrix to account for Qt rounding errors, // so that flipping (if it used this method) and rotating by a multiple // of 90 degrees actually work as expected (try tests/transforms.png). // // SYNC: This was not required with Qt3 so we are actually working // around a Qt4 bug/feature. // // COMPAT: Qt4's rendering with a matrix enabled is low quality anyway // but does this reduce quality even further? // // With or without it, skews by 45 degrees with the QImage // painter below look bad (with it, you get an extra transparent // line on the right; without, you get only about 1/4 of a source // line on the left). In Qt3, with TrueMatrix(), the source // image is translated 1 pixel off the destination image. // // Also, if you skew a rectangular selection, the skewed selection // border does not line up with the skewed image data. // TODO: do we need to pass through this new matrix? transformMatrix = ::TrueMatrix (transformMatrix, pm.width (), pm.height ()); #if DEBUG_KP_PIXMAP_FX && 1 qCDebug(kpLogPixmapfx) << "trueMatrix changed matrix?" << (oldMatrix == transformMatrix); #endif ::MatrixDebug ("TransformPixmap(): after trueMatrix", transformMatrix, pm.width (), pm.height ()); QImage newQImage (targetWidth > 0 ? targetWidth : newRect.width (), targetHeight > 0 ? targetHeight : newRect.height (), QImage::Format_ARGB32_Premultiplied); if ((targetWidth > 0 && targetWidth != newRect.width ()) || (targetHeight > 0 && targetHeight != newRect.height ())) { #if DEBUG_KP_PIXMAP_FX && 1 qCDebug(kpLogPixmapfx) << "kppixmapfx.cpp: TransformPixmap(pm.size=" << pm.size () << ",targetWidth=" << targetWidth << ",targetHeight=" << targetHeight << ") newRect=" << newRect << " (you are a victim of rounding error)" << endl; #endif } #if DEBUG_KP_PIXMAP_FX && 0 qCDebug(kpLogPixmapfx) << "\ttranslate top=" << painter.xForm (QPoint (0, 0)); qCDebug(kpLogPixmapfx) << "\tmatrix: m11=" << painter.worldMatrix ().m11 () << " m12=" << painter.worldMatrix ().m12 () << " m21=" << painter.worldMatrix ().m21 () << " m22=" << painter.worldMatrix ().m22 () << " dx=" << painter.worldMatrix ().dx () << " dy=" << painter.worldMatrix ().dy () << endl; #endif // Note: Do _not_ use "p.setRenderHints (QPainter::SmoothPixmapTransform);" // as the user does not want their image to get blurier every // time they e.g. rotate it (especially important for multiples // of 90 degrees but also true for every other angle). Being a // pixel-based program, we generally like to preserve RGB values // and avoid unnecessary blurs -- in the worst case, we'd rather // drop pixels, than blur. QPainter p (&newQImage); { // Make sure transparent pixels are drawn into the destination image. p.setCompositionMode (QPainter::CompositionMode_Source); // Fill the entire new image with the background color. if (backgroundColor.isValid ()) { p.fillRect (newQImage.rect (), backgroundColor.toQColor ()); } p.setMatrix (transformMatrix); p.drawImage (QPoint (0, 0), pm); } p.end (); #if DEBUG_KP_PIXMAP_FX && 1 qCDebug(kpLogPixmapfx) << "Done" << endl << endl; #endif return newQImage; } //--------------------------------------------------------------------- // public static QMatrix kpPixmapFX::skewMatrix (int width, int height, double hangle, double vangle) { - if (fabs (hangle - 0) < kpPixmapFX::AngleInDegreesEpsilon && - fabs (vangle - 0) < kpPixmapFX::AngleInDegreesEpsilon) + if (std::fabs (hangle - 0) < kpPixmapFX::AngleInDegreesEpsilon && + std::fabs (vangle - 0) < kpPixmapFX::AngleInDegreesEpsilon) { return QMatrix (); } /* Diagram for completeness :) * * |---------- w ----------| * (0,0) * _ _______________________ (w,0) * | |\~_ va | * | | \ ~_ | * | |ha\ ~__ | * | \ ~__ | dy * h | \ ~___ | * | \ ~___ | * | | \ ~___| (w,w*tan(va)=dy) * | | \ * \ * _ |________\________|_____|\ vertical shear factor * (0,h) dx ^~_ | \ | * | ~_ \________\________ General Point (x,y) V * | ~__ \ Skewed Point (x + y*tan(ha),y + x*tan(va)) * (h*tan(ha)=dx,h) ~__ \ ^ * ~___ \ | * ~___ \ horizontal shear factor * Key: ~___\ * ha = hangle (w + h*tan(ha)=w+dx,h + w*tan(va)=w+dy) * va = vangle * * Skewing really just twists a rectangle into a parallelogram. * */ //QMatrix matrix (1, tan (KP_DEGREES_TO_RADIANS (vangle)), tan (KP_DEGREES_TO_RADIANS (hangle)), 1, 0, 0); // I think this is clearer than above :) QMatrix matrix; - matrix.shear (tan (KP_DEGREES_TO_RADIANS (hangle)), - tan (KP_DEGREES_TO_RADIANS (vangle))); + matrix.shear (std::tan (qDegreesToRadians (hangle)), + std::tan (qDegreesToRadians (vangle))); return ::MatrixWithZeroOrigin (matrix, width, height); } //--------------------------------------------------------------------- // public static QMatrix kpPixmapFX::skewMatrix (const QImage &pixmap, double hangle, double vangle) { return kpPixmapFX::skewMatrix (pixmap.width (), pixmap.height (), hangle, vangle); } //--------------------------------------------------------------------- // public static void kpPixmapFX::skew (QImage *destPtr, double hangle, double vangle, const kpColor &backgroundColor, int targetWidth, int targetHeight) { if (!destPtr) return; *destPtr = kpPixmapFX::skew (*destPtr, hangle, vangle, backgroundColor, targetWidth, targetHeight); } //--------------------------------------------------------------------- // public static QImage kpPixmapFX::skew (const QImage &pm, double hangle, double vangle, const kpColor &backgroundColor, int targetWidth, int targetHeight) { #if DEBUG_KP_PIXMAP_FX qCDebug(kpLogPixmapfx) << "kpPixmapFX::skew() pm.width=" << pm.width () << " pm.height=" << pm.height () << " hangle=" << hangle << " vangle=" << vangle << " targetWidth=" << targetWidth << " targetHeight=" << targetHeight << endl; #endif - if (fabs (hangle - 0) < kpPixmapFX::AngleInDegreesEpsilon && - fabs (vangle - 0) < kpPixmapFX::AngleInDegreesEpsilon && + if (std::fabs (hangle - 0) < kpPixmapFX::AngleInDegreesEpsilon && + std::fabs (vangle - 0) < kpPixmapFX::AngleInDegreesEpsilon && (targetWidth <= 0 && targetHeight <= 0)/*don't want to scale?*/) { return pm; } - if (fabs (hangle) > 90 - kpPixmapFX::AngleInDegreesEpsilon || - fabs (vangle) > 90 - kpPixmapFX::AngleInDegreesEpsilon) + if (std::fabs (hangle) > 90 - kpPixmapFX::AngleInDegreesEpsilon || + std::fabs (vangle) > 90 - kpPixmapFX::AngleInDegreesEpsilon) { qCCritical(kpLogPixmapfx) << "kpPixmapFX::skew() passed hangle and/or vangle out of range (-90 < x < 90)" << endl; return pm; } QMatrix matrix = skewMatrix (pm, hangle, vangle); return ::TransformPixmap (pm, matrix, backgroundColor, targetWidth, targetHeight); } //--------------------------------------------------------------------- // public static QMatrix kpPixmapFX::rotateMatrix (int width, int height, double angle) { - if (fabs (angle - 0) < kpPixmapFX::AngleInDegreesEpsilon) + if (std::fabs (angle - 0) < kpPixmapFX::AngleInDegreesEpsilon) { return QMatrix (); } QMatrix matrix; matrix.translate (width / 2, height / 2); matrix.rotate (angle); return ::MatrixWithZeroOrigin (matrix, width, height); } //--------------------------------------------------------------------- // public static QMatrix kpPixmapFX::rotateMatrix (const QImage &pixmap, double angle) { return kpPixmapFX::rotateMatrix (pixmap.width (), pixmap.height (), angle); } //--------------------------------------------------------------------- // public static bool kpPixmapFX::isLosslessRotation (double angle) { const double angleIn = angle; // Reflect angle into positive if negative if (angle < 0) angle = -angle; // Remove multiples of 90 to make sure 0 <= angle <= 90 angle -= (static_cast (angle)) / 90 * 90; // "Impossible" situation? if (angle < 0 || angle > 90) { qCCritical(kpLogPixmapfx) << "kpPixmapFX::isLosslessRotation(" << angleIn << ") result=" << angle << endl; return false; // better safe than sorry } const bool ret = (angle < kpPixmapFX::AngleInDegreesEpsilon || 90 - angle < kpPixmapFX::AngleInDegreesEpsilon); #if DEBUG_KP_PIXMAP_FX qCDebug(kpLogPixmapfx) << "kpPixmapFX::isLosslessRotation(" << angleIn << ")" << " residual angle=" << angle << " returning " << ret << endl; #endif return ret; } //--------------------------------------------------------------------- // public static void kpPixmapFX::rotate (QImage *destPtr, double angle, const kpColor &backgroundColor, int targetWidth, int targetHeight) { if (!destPtr) return; *destPtr = kpPixmapFX::rotate (*destPtr, angle, backgroundColor, targetWidth, targetHeight); } //--------------------------------------------------------------------- // public static QImage kpPixmapFX::rotate (const QImage &pm, double angle, const kpColor &backgroundColor, int targetWidth, int targetHeight) { - if (fabs (angle - 0) < kpPixmapFX::AngleInDegreesEpsilon && + if (std::fabs (angle - 0) < kpPixmapFX::AngleInDegreesEpsilon && (targetWidth <= 0 && targetHeight <= 0)/*don't want to scale?*/) { return pm; } QMatrix matrix = rotateMatrix (pm, angle); return ::TransformPixmap (pm, matrix, backgroundColor, targetWidth, targetHeight); } //--------------------------------------------------------------------- diff --git a/tools/polygonal/kpToolPolygonalBase.cpp b/tools/polygonal/kpToolPolygonalBase.cpp index 3b61a2c8..985cadd9 100644 --- a/tools/polygonal/kpToolPolygonalBase.cpp +++ b/tools/polygonal/kpToolPolygonalBase.cpp @@ -1,493 +1,493 @@ /* Copyright (c) 2003-2007 Clarence Dang All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define DEBUG_KP_TOOL_POLYGON 0 #include "kpToolPolygonalBase.h" #include -#include +#include #include #include #include #include #include #include "kpLogCategories.h" #include "commands/kpCommandHistory.h" #include "document/kpDocument.h" #include "kpDefs.h" #include "imagelib/kpImage.h" #include "imagelib/kpPainter.h" #include "pixmapfx/kpPixmapFX.h" #include "layers/tempImage/kpTempImage.h" #include "environments/tools/kpToolEnvironment.h" #include "commands/tools/polygonal/kpToolPolygonalCommand.h" #include "widgets/toolbars/kpToolToolBar.h" #include "widgets/toolbars/options/kpToolWidgetLineWidth.h" #include "views/manager/kpViewManager.h" struct kpToolPolygonalBasePrivate { kpToolPolygonalBasePrivate () : drawShapeFunc(nullptr), toolWidgetLineWidth(nullptr), originatingMouseButton(-1) { } kpToolPolygonalBase::DrawShapeFunc drawShapeFunc; kpToolWidgetLineWidth *toolWidgetLineWidth; int originatingMouseButton; QPolygon points; }; //--------------------------------------------------------------------- kpToolPolygonalBase::kpToolPolygonalBase ( const QString &text, const QString &description, DrawShapeFunc drawShapeFunc, int key, kpToolEnvironment *environ, QObject *parent, const QString &name) : kpTool (text, description, key, environ, parent, name), d (new kpToolPolygonalBasePrivate ()) { d->drawShapeFunc = drawShapeFunc; d->toolWidgetLineWidth = nullptr; // (hopefully cause crash if we use it before initialising it) d->originatingMouseButton = -1; } //--------------------------------------------------------------------- kpToolPolygonalBase::~kpToolPolygonalBase () { delete d; } //--------------------------------------------------------------------- // virtual void kpToolPolygonalBase::begin () { kpToolToolBar *tb = toolToolBar (); Q_ASSERT (tb); #if DEBUG_KP_TOOL_POLYGON qCDebug(kpLogTools) << "kpToolPolygonalBase::begin() tb=" << tb; #endif d->toolWidgetLineWidth = tb->toolWidgetLineWidth (); connect (d->toolWidgetLineWidth, &kpToolWidgetLineWidth::lineWidthChanged, this, &kpToolPolygonalBase::updateShape); d->toolWidgetLineWidth->show (); viewManager ()->setCursor (QCursor (Qt::ArrowCursor)); d->originatingMouseButton = -1; setUserMessage (/*virtual*/haventBegunShapeUserMessage ()); } //--------------------------------------------------------------------- // virtual void kpToolPolygonalBase::end () { // TODO: needed? endShape (); disconnect (d->toolWidgetLineWidth, &kpToolWidgetLineWidth::lineWidthChanged, this, &kpToolPolygonalBase::updateShape); d->toolWidgetLineWidth = nullptr; viewManager ()->unsetCursor (); } void kpToolPolygonalBase::beginDraw () { #if DEBUG_KP_TOOL_POLYGON qCDebug(kpLogTools) << "kpToolPolygonalBase::beginDraw() d->points=" << d->points.toList () << ", startPoint=" << startPoint () << endl; #endif bool endedShape = false; // We now need to start with dragging out the initial line? if (d->points.count () == 0) { d->originatingMouseButton = mouseButton (); // The line starts and ends at the start point of the drag. // draw() will modify the last point in d->points to reflect the // mouse drag, as the drag proceeds. d->points.append (startPoint ()); d->points.append (startPoint ()); } // Already have control points - not dragging out initial line. else { // Clicking the other mouse button? if (mouseButton () != d->originatingMouseButton) { // Finish shape. TODO: I suspect we need to call endShapeInternal instead. endShape (); endedShape = true; } // Are we dragging out an extra control point? else { // Add another control point. d->points.append (startPoint ()); } } #if DEBUG_KP_TOOL_POLYGON qCDebug(kpLogTools) << "\tafterwards, d->points=" << d->points.toList (); #endif if (!endedShape) { // We've started dragging. Print instructions on how to cancel shape. setUserMessage (cancelUserMessage ()); } } // protected void kpToolPolygonalBase::applyModifiers () { const int count = d->points.count (); QPoint &lineStartPoint = d->points [count - 2]; QPoint &lineEndPoint = d->points [count - 1]; #if DEBUG_KP_TOOL_POLYGON && 1 qCDebug(kpLogTools) << "kpToolPolygonalBase::applyModifiers() #pts=" << count << " line: startPt=" << lineStartPoint << " endPt=" << lineEndPoint << " modifiers: shift=" << shiftPressed () << " alt=" << altPressed () << " ctrl=" << controlPressed () << endl; #endif // angles if (shiftPressed () || controlPressed ()) { int diffx = lineEndPoint.x () - lineStartPoint.x (); int diffy = lineEndPoint.y () - lineStartPoint.y (); double ratio; if (diffx == 0) ratio = DBL_MAX; else ratio = fabs (double (diffy) / double (diffx)); #if DEBUG_KP_TOOL_POLYGON && 1 qCDebug(kpLogTools) << "\tdiffx=" << diffx << " diffy=" << diffy << " ratio=" << ratio << endl; #endif // Shift = 0, 45, 90 // Ctrl = 0, 30, 60, 90 // Shift + Ctrl = 0, 30, 45, 60, 90 double angles [10]; // "ought to be enough for anybody" int numAngles = 0; angles [numAngles++] = 0; if (controlPressed ()) - angles [numAngles++] = KP_PI / 6; + angles [numAngles++] = M_PI / 6; if (shiftPressed ()) - angles [numAngles++] = KP_PI / 4; + angles [numAngles++] = M_PI / 4; if (controlPressed ()) - angles [numAngles++] = KP_PI / 3; - angles [numAngles++] = KP_PI / 2; + angles [numAngles++] = M_PI / 3; + angles [numAngles++] = M_PI / 2; Q_ASSERT (numAngles <= int (sizeof (angles) / sizeof (angles [0]))); double angle = angles [numAngles - 1]; for (int i = 0; i < numAngles - 1; i++) { - double acceptingRatio = tan ((angles [i] + angles [i + 1]) / 2.0); + double acceptingRatio = std::tan ((angles [i] + angles [i + 1]) / 2.0); if (ratio < acceptingRatio) { angle = angles [i]; break; } } // horizontal (dist from start not maintained) - if (fabs (KP_RADIANS_TO_DEGREES (angle) - 0) + if (std::fabs (qRadiansToDegrees (angle) - 0) < kpPixmapFX::AngleInDegreesEpsilon) { lineEndPoint = QPoint (lineEndPoint.x (), lineStartPoint.y ()); } // vertical (dist from start not maintained) - else if (fabs (KP_RADIANS_TO_DEGREES (angle) - 90) + else if (std::fabs (qRadiansToDegrees (angle) - 90) < kpPixmapFX::AngleInDegreesEpsilon) { lineEndPoint = QPoint (lineStartPoint.x (), lineEndPoint.y ()); } // diagonal (dist from start maintained) else { - const double dist = sqrt (static_cast (diffx * diffx + diffy * diffy)); + const double dist = std::sqrt (static_cast (diffx * diffx + diffy * diffy)); #define sgn(a) ((a)<0?-1:1) // Round distances _before_ adding to any coordinate // (ensures consistent rounding behaviour in x & y directions) const int newdx = qRound (dist * cos (angle) * sgn (diffx)); const int newdy = qRound (dist * sin (angle) * sgn (diffy)); #undef sgn lineEndPoint = QPoint (lineStartPoint.x () + newdx, lineStartPoint.y () + newdy); #if DEBUG_KP_TOOL_POLYGON && 1 qCDebug(kpLogTools) << "\t\tdiagonal line: dist=" << dist << " angle=" << (angle * 180 / KP_PI) << " endPoint=" << lineEndPoint << endl; #endif } } // if (shiftPressed () || controlPressed ()) { // centring if (altPressed () && 0/*ALT is unreliable*/) { // start = start - diff // = start - (end - start) // = start - end + start // = 2 * start - end if (count == 2) lineStartPoint += (lineStartPoint - lineEndPoint); else lineEndPoint += (lineEndPoint - lineStartPoint); } // if (altPressed ()) { } // protected QPolygon *kpToolPolygonalBase::points () const { return &d->points; } // protected int kpToolPolygonalBase::originatingMouseButton () const { Q_ASSERT (hasBegunShape ()); return d->originatingMouseButton; } // virtual void kpToolPolygonalBase::draw (const QPoint &, const QPoint &, const QRect &) { // A click of the other mouse button (to finish shape, instead of adding // another control point) would have caused endShape() to have been // called in kpToolPolygonalBase::beginDraw(). The points list would now // be empty. We are being called by kpTool::mouseReleaseEvent(). if (d->points.count () == 0) return; #if DEBUG_KP_TOOL_POLYGON qCDebug(kpLogTools) << "kpToolPolygonalBase::draw() d->points=" << d->points.toList () << ", endPoint=" << currentPoint () << endl; #endif // Update points() so that last point reflects current mouse position. const int count = d->points.count (); d->points [count - 1] = currentPoint (); #if DEBUG_KP_TOOL_POLYGON qCDebug(kpLogTools) << "\tafterwards, d->points=" << d->points.toList (); #endif // Are we drawing a line? if (/*virtual*/drawingALine ()) { // Adjust the line (end points given by the last 2 points of points()) // in response to keyboard modifiers. applyModifiers (); // Update the preview of the shape. updateShape (); // Inform the user that we're dragging out a line with 2 control points. setUserShapePoints (d->points [count - 2], d->points [count - 1]); } // We're modifying a point. else { // Update the preview of the shape. updateShape (); // Informs the user that we're just modifying a point (perhaps, a control // point of a Bezier). setUserShapePoints (d->points [count - 1]); } } // TODO: code dup with kpToolRectangle // private kpColor kpToolPolygonalBase::drawingForegroundColor () const { return color (originatingMouseButton ()); } // protected virtual kpColor kpToolPolygonalBase::drawingBackgroundColor () const { return kpColor::Invalid; } // TODO: code dup with kpToolRectangle // protected slot void kpToolPolygonalBase::updateShape () { if (d->points.count () == 0) return; const QRect boundingRect = kpTool::neededRect ( d->points.boundingRect (), d->toolWidgetLineWidth->lineWidth ()); #if DEBUG_KP_TOOL_POLYGON qCDebug(kpLogTools) << "kpToolPolygonalBase::updateShape() boundingRect=" << boundingRect << " lineWidth=" << d->toolWidgetLineWidth->lineWidth () << endl; #endif kpImage image = document ()->getImageAt (boundingRect); QPolygon pointsTranslated = d->points; pointsTranslated.translate (-boundingRect.x (), -boundingRect.y ()); (*d->drawShapeFunc) (&image, pointsTranslated, drawingForegroundColor (), d->toolWidgetLineWidth->lineWidth (), /*virtual*/drawingBackgroundColor (), false/*not final*/); kpTempImage newTempImage (false/*always display*/, kpTempImage::SetImage/*render mode*/, boundingRect.topLeft (), image); viewManager ()->setFastUpdates (); { viewManager ()->setTempImage (newTempImage); } viewManager ()->restoreFastUpdates (); } // virtual void kpToolPolygonalBase::cancelShape () { viewManager ()->invalidateTempImage (); d->points.resize (0); setUserMessage (i18n ("Let go of all the mouse buttons.")); } void kpToolPolygonalBase::releasedAllButtons () { if (!hasBegunShape ()) setUserMessage (/*virtual*/haventBegunShapeUserMessage ()); // --- else case already handled by endDraw() --- } // public virtual [base kpTool] void kpToolPolygonalBase::endShape (const QPoint &, const QRect &) { #if DEBUG_KP_TOOL_POLYGON qCDebug(kpLogTools) << "kpToolPolygonalBase::endShape() d->points=" << d->points.toList () << endl; #endif if (!hasBegunShape ()) return; viewManager ()->invalidateTempImage (); QRect boundingRect = kpTool::neededRect ( d->points.boundingRect (), d->toolWidgetLineWidth->lineWidth ()); commandHistory ()->addCommand ( new kpToolPolygonalCommand ( text (), d->drawShapeFunc, d->points, boundingRect, drawingForegroundColor (), d->toolWidgetLineWidth->lineWidth (), /*virtual*/drawingBackgroundColor (), environ ()->commandEnvironment ())); d->points.resize (0); setUserMessage (/*virtual*/haventBegunShapeUserMessage ()); } // public virtual [base kpTool] bool kpToolPolygonalBase::hasBegunShape () const { return (d->points.count () > 0); } // virtual protected slot [base kpTool] void kpToolPolygonalBase::slotForegroundColorChanged (const kpColor &) { updateShape (); } // virtual protected slot [base kpTool] void kpToolPolygonalBase::slotBackgroundColorChanged (const kpColor &) { updateShape (); } diff --git a/views/kpView.cpp b/views/kpView.cpp index 125dd7b4..0c2e8005 100644 --- a/views/kpView.cpp +++ b/views/kpView.cpp @@ -1,682 +1,681 @@ /* Copyright (c) 2003-2007 Clarence Dang Copyright (c) 2005 Kazuki Ohta Copyright (c) 2010 Tasuku Suzuki All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define DEBUG_KP_VIEW 0 #define DEBUG_KP_VIEW_RENDERER ((DEBUG_KP_VIEW && 1) || 0) #include "kpView.h" #include "kpViewPrivate.h" -#include #include #include #include #include #include #include #include "kpLogCategories.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "layers/selections/text/kpTextSelection.h" #include "tools/kpTool.h" #include "widgets/toolbars/kpToolToolBar.h" #include "views/manager/kpViewManager.h" #include "kpViewScrollableContainer.h" //--------------------------------------------------------------------- // public static const int kpView::MinZoomLevel = 1; const int kpView::MaxZoomLevel = 3200; //--------------------------------------------------------------------- kpView::kpView (kpDocument *document, kpToolToolBar *toolToolBar, kpViewManager *viewManager, kpView *buddyView, kpViewScrollableContainer *scrollableContainer, QWidget *parent) : QWidget (parent), d (new kpViewPrivate ()) { d->document = document; d->toolToolBar = toolToolBar; d->viewManager = viewManager; d->buddyView = buddyView; d->scrollableContainer = scrollableContainer; d->hzoom = 100; d->vzoom = 100; d->origin = QPoint (0, 0); d->showGrid = false; d->isBuddyViewScrollableContainerRectangleShown = false; // Don't waste CPU drawing default background since its overridden by // our fully opaque drawing. In reality, this seems to make no // difference in performance. setAttribute(Qt::WA_OpaquePaintEvent, true); setFocusPolicy (Qt::WheelFocus); setMouseTracking (true); // mouseMoveEvent's even when no mousebtn down setAttribute (Qt::WA_KeyCompression, true); } //--------------------------------------------------------------------- kpView::~kpView () { setHasMouse (false); delete d; } //--------------------------------------------------------------------- // public kpDocument *kpView::document () const { return d->document; } //--------------------------------------------------------------------- // protected kpAbstractSelection *kpView::selection () const { return document () ? document ()->selection () : nullptr; } //--------------------------------------------------------------------- // protected kpTextSelection *kpView::textSelection () const { return document () ? document ()->textSelection () : nullptr; } //--------------------------------------------------------------------- // public kpToolToolBar *kpView::toolToolBar () const { return d->toolToolBar; } // protected kpTool *kpView::tool () const { return toolToolBar () ? toolToolBar ()->tool () : nullptr; } // public kpViewManager *kpView::viewManager () const { return d->viewManager; } // public kpView *kpView::buddyView () const { return d->buddyView; } // public kpViewScrollableContainer *kpView::buddyViewScrollableContainer () const { return (buddyView () ? buddyView ()->scrollableContainer () : nullptr); } // public kpViewScrollableContainer *kpView::scrollableContainer () const { return d->scrollableContainer; } // public int kpView::zoomLevelX (void) const { return d->hzoom; } // public int kpView::zoomLevelY (void) const { return d->vzoom; } // public virtual void kpView::setZoomLevel (int hzoom, int vzoom) { hzoom = qBound (MinZoomLevel, hzoom, MaxZoomLevel); vzoom = qBound (MinZoomLevel, vzoom, MaxZoomLevel); if (hzoom == d->hzoom && vzoom == d->vzoom) return; d->hzoom = hzoom; d->vzoom = vzoom; if (viewManager ()) viewManager ()->updateView (this); emit zoomLevelChanged (hzoom, vzoom); } // public QPoint kpView::origin () const { return d->origin; } // public virtual void kpView::setOrigin (const QPoint &origin) { #if DEBUG_KP_VIEW qCDebug(kpLogViews) << "kpView(" << objectName () << ")::setOrigin" << origin; #endif if (origin == d->origin) { #if DEBUG_KP_VIEW qCDebug(kpLogViews) << "\tNOP"; #endif return; } d->origin = origin; if (viewManager ()) viewManager ()->updateView (this); emit originChanged (origin); } // public bool kpView::canShowGrid () const { // (minimum zoom level < 400% would probably be reported as a bug by // users who thought that the grid was a part of the image!) return ((zoomLevelX () >= 400 && zoomLevelX () % 100 == 0) && (zoomLevelY () >= 400 && zoomLevelY () % 100 == 0)); } // public bool kpView::isGridShown () const { return d->showGrid; } // public void kpView::showGrid (bool yes) { if (d->showGrid == yes) return; if (yes && !canShowGrid ()) return; d->showGrid = yes; if (viewManager ()) viewManager ()->updateView (this); } // public bool kpView::isBuddyViewScrollableContainerRectangleShown () const { return d->isBuddyViewScrollableContainerRectangleShown; } // public void kpView::showBuddyViewScrollableContainerRectangle (bool yes) { if (yes == d->isBuddyViewScrollableContainerRectangleShown) return; d->isBuddyViewScrollableContainerRectangleShown = yes; if (d->isBuddyViewScrollableContainerRectangleShown) { // Got these connect statements by analysing deps of // updateBuddyViewScrollableContainerRectangle() rect update code. connect (this, &kpView::zoomLevelChanged, this, &kpView::updateBuddyViewScrollableContainerRectangle); connect (this, &kpView::originChanged, this, &kpView::updateBuddyViewScrollableContainerRectangle); if (buddyViewScrollableContainer ()) { connect (buddyViewScrollableContainer (), &kpViewScrollableContainer::contentsMoved, this, &kpView::updateBuddyViewScrollableContainerRectangle); connect (buddyViewScrollableContainer (), &kpViewScrollableContainer::resized, this, &kpView::updateBuddyViewScrollableContainerRectangle); } if (buddyView ()) { connect (buddyView (), &kpView::zoomLevelChanged, this, &kpView::updateBuddyViewScrollableContainerRectangle); connect (buddyView (), &kpView::originChanged, this, &kpView::updateBuddyViewScrollableContainerRectangle); connect (buddyView (), static_cast(&kpView::sizeChanged), this, &kpView::updateBuddyViewScrollableContainerRectangle); } } else { disconnect (this, &kpView::zoomLevelChanged, this, &kpView::updateBuddyViewScrollableContainerRectangle); disconnect (this, &kpView::originChanged, this, &kpView::updateBuddyViewScrollableContainerRectangle); if (buddyViewScrollableContainer ()) { disconnect (buddyViewScrollableContainer (), &kpViewScrollableContainer::contentsMoved, this, &kpView::updateBuddyViewScrollableContainerRectangle); disconnect (buddyViewScrollableContainer (), &kpViewScrollableContainer::resized, this, &kpView::updateBuddyViewScrollableContainerRectangle); } if (buddyView ()) { disconnect (buddyView (), &kpView::zoomLevelChanged, this, &kpView::updateBuddyViewScrollableContainerRectangle); disconnect (buddyView (), &kpView::originChanged, this, &kpView::updateBuddyViewScrollableContainerRectangle); disconnect (buddyView (), static_cast(&kpView::sizeChanged), this, &kpView::updateBuddyViewScrollableContainerRectangle); } } updateBuddyViewScrollableContainerRectangle (); } // protected QRect kpView::buddyViewScrollableContainerRectangle () const { return d->buddyViewScrollableContainerRectangle; } // protected slot void kpView::updateBuddyViewScrollableContainerRectangle () { if (viewManager ()) viewManager ()->setQueueUpdates (); { if (d->buddyViewScrollableContainerRectangle.isValid ()) { if (viewManager ()) { // Erase last viewManager ()->updateViewRectangleEdges (this, d->buddyViewScrollableContainerRectangle); } } QRect newRect; if (isBuddyViewScrollableContainerRectangleShown () && buddyViewScrollableContainer () && buddyView ()) { QRect docRect = buddyView ()->transformViewToDoc ( QRect (buddyViewScrollableContainer ()->horizontalScrollBar()->value(), buddyViewScrollableContainer ()->verticalScrollBar()->value(), qMin (buddyView ()->width (), buddyViewScrollableContainer ()->viewport()->width ()), qMin (buddyView ()->height (), buddyViewScrollableContainer ()->viewport()->height ()))); QRect viewRect = this->transformDocToView (docRect); // (Surround the area of interest by moving outwards by 1 pixel in each // direction - don't overlap area) newRect = QRect (viewRect.x () - 1, viewRect.y () - 1, viewRect.width () + 2, viewRect.height () + 2); } else { newRect = QRect (); } if (newRect != d->buddyViewScrollableContainerRectangle) { // (must set before updateView() for paintEvent() to see new // rect) d->buddyViewScrollableContainerRectangle = newRect; if (newRect.isValid ()) { if (viewManager ()) { viewManager ()->updateViewRectangleEdges (this, d->buddyViewScrollableContainerRectangle); } } } } if (viewManager ()) viewManager ()->restoreQueueUpdates (); } //--------------------------------------------------------------------- // public double kpView::transformViewToDocX (double viewX) const { return (viewX - origin ().x ()) * 100.0 / zoomLevelX (); } //--------------------------------------------------------------------- // public double kpView::transformViewToDocY (double viewY) const { return (viewY - origin ().y ()) * 100.0 / zoomLevelY (); } //--------------------------------------------------------------------- // public QPoint kpView::transformViewToDoc (const QPoint &viewPoint) const { return QPoint (static_cast (transformViewToDocX (viewPoint.x ())), static_cast (transformViewToDocY (viewPoint.y ()))); } //--------------------------------------------------------------------- // public QRect kpView::transformViewToDoc (const QRect &viewRect) const { if (zoomLevelX () == 100 && zoomLevelY () == 100) { return QRect (viewRect.x () - origin ().x (), viewRect.y () - origin ().y (), viewRect.width (), viewRect.height ()); } else { const QPoint docTopLeft = transformViewToDoc (viewRect.topLeft ()); // (don't call transformViewToDoc[XY]() - need to round up dimensions) const int docWidth = qRound (double (viewRect.width ()) * 100.0 / double (zoomLevelX ())); const int docHeight = qRound (double (viewRect.height ()) * 100.0 / double (zoomLevelY ())); // (like QWMatrix::Areas) return QRect (docTopLeft.x (), docTopLeft.y (), docWidth, docHeight); } } //--------------------------------------------------------------------- // public double kpView::transformDocToViewX (double docX) const { return (docX * zoomLevelX () / 100.0) + origin ().x (); } // public double kpView::transformDocToViewY (double docY) const { return (docY * zoomLevelY () / 100.0) + origin ().y (); } // public QPoint kpView::transformDocToView (const QPoint &docPoint) const { return QPoint (static_cast (transformDocToViewX (docPoint.x ())), static_cast (transformDocToViewY (docPoint.y ()))); } // public QRect kpView::transformDocToView (const QRect &docRect) const { if (zoomLevelX () == 100 && zoomLevelY () == 100) { return QRect (docRect.x () + origin ().x (), docRect.y () + origin ().y (), docRect.width (), docRect.height ()); } else { const QPoint viewTopLeft = transformDocToView (docRect.topLeft ()); // (don't call transformDocToView[XY]() - need to round up dimensions) const int viewWidth = qRound (double (docRect.width ()) * double (zoomLevelX ()) / 100.0); const int viewHeight = qRound (double (docRect.height ()) * double (zoomLevelY ()) / 100.0); // (like QWMatrix::Areas) return QRect (viewTopLeft.x (), viewTopLeft.y (), viewWidth, viewHeight); } } // public QPoint kpView::transformViewToOtherView (const QPoint &viewPoint, const kpView *otherView) { if (this == otherView) return viewPoint; const double docX = transformViewToDocX (viewPoint.x ()); const double docY = transformViewToDocY (viewPoint.y ()); const double otherViewX = otherView->transformDocToViewX (docX); const double otherViewY = otherView->transformDocToViewY (docY); return QPoint (static_cast (otherViewX), static_cast (otherViewY)); } // public int kpView::zoomedDocWidth () const { return document () ? document ()->width () * zoomLevelX () / 100 : 0; } // public int kpView::zoomedDocHeight () const { return document () ? document ()->height () * zoomLevelY () / 100 : 0; } // public void kpView::setHasMouse (bool yes) { kpViewManager *vm = viewManager (); if (!vm) return; #if DEBUG_KP_VIEW && 0 qCDebug(kpLogViews) << "kpView(" << objectName () << ")::setHasMouse(" << yes << ") existing viewUnderCursor=" << (vm->viewUnderCursor () ? vm->viewUnderCursor ()->objectName () : "(none)") << endl; #endif if (yes && vm->viewUnderCursor () != this) vm->setViewUnderCursor (this); else if (!yes && vm->viewUnderCursor () == this) vm->setViewUnderCursor (nullptr); } //--------------------------------------------------------------------- // public void kpView::addToQueuedArea (const QRegion ®ion) { #if DEBUG_KP_VIEW && 0 qCDebug(kpLogViews) << "kpView(" << objectName () << ")::addToQueuedArea() already=" << d->queuedUpdateArea << " - plus - " << region << endl; #endif d->queuedUpdateArea += region; } //--------------------------------------------------------------------- // public void kpView::addToQueuedArea (const QRect &rect) { #if DEBUG_KP_VIEW && 0 qCDebug(kpLogViews) << "kpView(" << objectName () << ")::addToQueuedArea() already=" << d->queuedUpdateArea << " - plus - " << rect << endl; #endif d->queuedUpdateArea += rect; } //--------------------------------------------------------------------- // public void kpView::invalidateQueuedArea () { #if DEBUG_KP_VIEW && 0 qCDebug(kpLogViews) << "kpView::invalidateQueuedArea()"; #endif d->queuedUpdateArea = QRegion (); } //--------------------------------------------------------------------- // public void kpView::updateQueuedArea () { kpViewManager *vm = viewManager (); #if DEBUG_KP_VIEW && 0 qCDebug(kpLogViews) << "kpView(" << objectName () << ")::updateQueuedArea() vm=" << (bool) vm << " queueUpdates=" << (vm && vm->queueUpdates ()) << " fastUpdates=" << (vm && vm->fastUpdates ()) << " area=" << d->queuedUpdateArea << endl; #endif if (!vm) return; if (vm->queueUpdates ()) return; if (!d->queuedUpdateArea.isEmpty ()) vm->updateView (this, d->queuedUpdateArea); invalidateQueuedArea (); } //--------------------------------------------------------------------- // public QPoint kpView::mouseViewPoint (const QPoint &returnViewPoint) const { if (returnViewPoint != KP_INVALID_POINT) return returnViewPoint; else { // TODO: I don't think this is right for the main view since that's // inside the scrollview (which can scroll). return mapFromGlobal (QCursor::pos ()); } } //--------------------------------------------------------------------- // public virtual QVariant kpView::inputMethodQuery (Qt::InputMethodQuery query) const { #if DEBUG_KP_VIEW && 1 qCDebug(kpLogViews) << "kpView(" << objectName () << ")::inputMethodQuery()"; #endif QVariant ret; switch (query) { case Qt::ImMicroFocus: { QRect r = d->viewManager->textCursorRect (); r.setTopLeft (r.topLeft () + origin ()); r.setHeight (r.height() + 2); r = transformDocToView (r); ret = r; break; } case Qt::ImFont: { if (textSelection ()) { ret = textSelection ()->textStyle ().font (); } break; } default: break; } return ret; } //--------------------------------------------------------------------- diff --git a/widgets/colorSimilarity/kpColorSimilarityCubeRenderer.cpp b/widgets/colorSimilarity/kpColorSimilarityCubeRenderer.cpp index 80a04238..9151fa30 100644 --- a/widgets/colorSimilarity/kpColorSimilarityCubeRenderer.cpp +++ b/widgets/colorSimilarity/kpColorSimilarityCubeRenderer.cpp @@ -1,234 +1,234 @@ /* Copyright (c) 2003-2007 Clarence Dang All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define DEBUG_KP_COLOR_SIMILARITY_CUBE 0 #include "kpColorSimilarityCubeRenderer.h" -#include +#include #include #include #include "kpLogCategories.h" #include "widgets/colorSimilarity/kpColorSimilarityHolder.h" #include "kpDefs.h" //--------------------------------------------------------------------- static QColor Color (int redOrGreenOrBlue, int baseBrightness, double colorSimilarity, int similarityDirection, int highlight) { int brightness = int (baseBrightness + similarityDirection * 0.5 * colorSimilarity * kpColorSimilarityHolder::ColorCubeDiagonalDistance); if (brightness < 0) brightness = 0; else if (brightness > 255) brightness = 255; switch (redOrGreenOrBlue) { default: case 0: return QColor (brightness, highlight, highlight); case 1: return QColor (highlight, brightness, highlight); case 2: return QColor (highlight, highlight, brightness); } } //--------------------------------------------------------------------- static QPointF PointBetween(const QPointF &p, const QPointF &q) { return QPointF((p.x() + q.x()) / 2.0, (p.y() + q.y()) / 2.0); } //--------------------------------------------------------------------- static void DrawQuadrant(QPaintDevice *target, const QColor &col, const QPointF &p1, const QPointF &p2, const QPointF &p3, const QPointF &pointNotOnOutline) { QPolygonF points (4); points [0] = p1; points [1] = p2; points [2] = p3; points [3] = pointNotOnOutline; QPainter p(target); p.setRenderHints(QPainter::Antialiasing, true); // Polygon fill. p.setPen(col); p.setBrush(col); p.drawPolygon(points); // do not draw a black border. It looks ugly } //--------------------------------------------------------------------- static void DrawFace (QPaintDevice *target, double colorSimilarity, int redOrGreenOrBlue, const QPointF &tl, const QPointF &tr, const QPointF &bl, const QPointF &br, int highlight) { #if DEBUG_KP_COLOR_SIMILARITY_CUBE qCDebug(kpLogWidgets) << "kpColorSimilarityCubeRenderer.cpp:DrawFace(RorGorB=" << redOrGreenOrBlue << ",tl=" << tl << ",tr=" << tr << ",bl=" << bl << ",br=" << br << ")" << endl; #endif // tl --- tm --- tr // | | | // | | | // ml --- mm --- mr // | | | // | | | // bl --- bm --- br const QPointF tm (::PointBetween (tl, tr)); const QPointF bm (::PointBetween (bl, br)); const QPointF ml (::PointBetween (tl, bl)); const QPointF mr (::PointBetween (tr, br)); const QPointF mm (::PointBetween (ml, mr)); const int baseBrightness = qMax (127, 255 - int (kpColorSimilarityHolder::MaxColorSimilarity * kpColorSimilarityHolder::ColorCubeDiagonalDistance / 2)); QColor colors [2] = { ::Color (redOrGreenOrBlue, baseBrightness, colorSimilarity, -1, highlight), ::Color (redOrGreenOrBlue, baseBrightness, colorSimilarity, +1, highlight) }; #if DEBUG_KP_COLOR_SIMILARITY_CUBE qCDebug(kpLogWidgets) << "\tmaxColorSimilarity=" << kpColorSimilarityHolder::MaxColorSimilarity << " colorCubeDiagDist=" << kpColorSimilarityHolder::ColorCubeDiagonalDistance << endl << "\tbaseBrightness=" << baseBrightness << " color[0]=" << ((colors [0].rgba() & RGB_MASK) >> ((2 - redOrGreenOrBlue) * 8)) << " color[1]=" << ((colors [1].rgba() & RGB_MASK) >> ((2 - redOrGreenOrBlue) * 8)) << endl; #endif ::DrawQuadrant(target, colors [0], tm, tl, ml, mm); ::DrawQuadrant(target, colors [1], tm, tr, mr, mm); ::DrawQuadrant(target, colors [1], ml, bl, bm, mm); ::DrawQuadrant(target, colors [0], bm, br, mr, mm); } //--------------------------------------------------------------------- // public static void kpColorSimilarityCubeRenderer::Paint(QPaintDevice *target, int x, int y, int cubeRectSize, double colorSimilarity, int highlight) { Q_ASSERT (highlight >= 0 && highlight <= 255); // // P------- Q --- --- // / / | | | // /A / | side | // R-------S T --- cubeRectSize // | | / / | // S | | / side | // U-------V --- --- // |-------| // side // |-----------| // cubeRectSize // // - const double angle = KP_DEGREES_TO_RADIANS (45); + const double angle = qDegreesToRadians (45.0); // S + S sin A = cubeRectSize // (1 + sin A) x S = cubeRectSize - const double side = double(cubeRectSize) / (1.0 + sin(angle)); + const double side = double(cubeRectSize) / (1.0 + std::sin(angle)); - const QPointF pointP(x + (side * cos (angle)), + const QPointF pointP(x + (side * std::cos (angle)), y); const QPointF pointQ(x + cubeRectSize - 1, y); const QPointF pointR(x, - y + (side * sin (angle))); + y + (side * std::sin (angle))); const QPointF pointS(x + (side), - y + (side * sin (angle))); + y + (side * std::sin (angle))); const QPointF pointT(x + cubeRectSize - 1, y + (side)); const QPointF pointU(x, y + cubeRectSize - 1); const QPointF pointV(x + (side), y + cubeRectSize - 1); // Top Face ::DrawFace(target, colorSimilarity, 0/*red*/, pointP, pointQ, pointR, pointS, highlight); // Front Face ::DrawFace(target, colorSimilarity, 1/*green*/, pointR, pointS, pointU, pointV, highlight); // Right Face ::DrawFace(target, colorSimilarity, 2/*blue*/, pointS, pointQ, pointV, pointT, highlight); } //--------------------------------------------------------------------- diff --git a/widgets/colorSimilarity/kpColorSimilarityHolder.cpp b/widgets/colorSimilarity/kpColorSimilarityHolder.cpp index a2154395..a294e9bb 100644 --- a/widgets/colorSimilarity/kpColorSimilarityHolder.cpp +++ b/widgets/colorSimilarity/kpColorSimilarityHolder.cpp @@ -1,189 +1,189 @@ /* Copyright (c) 2003-2007 Clarence Dang All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define DEBUG_KP_COLOR_SIMILARITY_CUBE 0 #include "kpColorSimilarityHolder.h" #include "kpColorSimilarityCubeRenderer.h" #include "imagelib/kpColor.h" #include "kpDefs.h" #include #include #include #include #include "kpLogCategories.h" #include // public static const double kpColorSimilarityHolder::ColorCubeDiagonalDistance = - sqrt (255.0 * 255 * 3); + std::sqrt (255.0 * 255 * 3); // public static const double kpColorSimilarityHolder::MaxColorSimilarity = 0.30; kpColorSimilarityHolder::kpColorSimilarityHolder () : m_colorSimilarity (0) { } kpColorSimilarityHolder::~kpColorSimilarityHolder () { } // Don't cause the translators grief by appending strings etc. // - duplicate text with 2 cases // public static QString kpColorSimilarityHolder::WhatsThisWithClickInstructions () { return i18n ("" "

Color Similarity is how similar the colors of different pixels" " must be, for operations to consider them to be the same.

" "

If you set it to something other than Exact Match," " you can work more effectively with dithered" " images and photos, in a comparable manner to the \"Magic Wand\"" " feature of other paint programs.

" "

This feature applies to:

" "
    " "
  • Selections: In Transparent mode, any color in the" " selection that is similar to the background color will" " be made transparent.
  • " "
  • Flood Fill: For regions with similar - but not" " identical - colored pixels, a higher setting is likely to" " fill more pixels.
  • " "
  • Color Eraser: Any pixel whose color is similar" " to the foreground color will be replaced with the background" " color.
  • " "
  • Autocrop and Remove Internal Border: For" " borders with similar - but not identical - colored pixels," " a higher setting is more likely to crop the whole border.
  • " "
" "

Higher settings mean that operations consider an increased range" " of colors to be sufficiently similar so as to be the same. Therefore," " you should increase the setting if the above operations are not" " affecting pixels whose colors you consider to be similar enough.

" "

However, if they are having too much of an effect and are changing" " pixels whose colors you do not consider to be similar" " (e.g. if Flood Fill is changing too many pixels), you" " should decrease this setting.

" // sync: Compared to the other string below, we've added this line. "

To configure it, click on the cube.

" "
"); } // public static QString kpColorSimilarityHolder::WhatsThis () { return i18n ("" "

Color Similarity is how similar the colors of different pixels" " must be, for operations to consider them to be the same.

" "

If you set it to something other than Exact Match," " you can work more effectively with dithered" " images and photos, in a comparable manner to the \"Magic Wand\"" " feature of other paint programs.

" "

This feature applies to:

" "
    " "
  • Selections: In Transparent mode, any color in the" " selection that is similar to the background color will" " be made transparent.
  • " "
  • Flood Fill: For regions with similar - but not" " identical - colored pixels, a higher setting is likely to" " fill more pixels.
  • " "
  • Color Eraser: Any pixel whose color is similar" " to the foreground color will be replaced with the background" " color.
  • " "
  • Autocrop and Remove Internal Border: For" " borders with similar - but not identical - colored pixels," " a higher setting is more likely to crop the whole border.
  • " "
" "

Higher settings mean that operations consider an increased range" " of colors to be sufficiently similar so as to be the same. Therefore," " you should increase the setting if the above operations are not" " affecting pixels whose colors you consider to be similar enough.

" "

However, if they are having too much of an effect and are changing" " pixels whose colors you do not consider to be similar" " (e.g. if Flood Fill is changing too many pixels), you" " should decrease this setting.

" "
"); } // public double kpColorSimilarityHolder::colorSimilarity () const { return m_colorSimilarity; } // public virtual void kpColorSimilarityHolder::setColorSimilarity (double similarity) { #if DEBUG_KP_COLOR_SIMILARITY_CUBE qCDebug(kpLogWidgets) << "kpColorSimilarityHolder::setColorSimilarity(" << similarity << ")"; #endif if (m_colorSimilarity == similarity) return; if (similarity < 0) similarity = 0; else if (similarity > MaxColorSimilarity) similarity = MaxColorSimilarity; m_colorSimilarity = similarity; } diff --git a/widgets/imagelib/effects/kpEffectBalanceWidget.cpp b/widgets/imagelib/effects/kpEffectBalanceWidget.cpp index 40c2aa63..46f184aa 100644 --- a/widgets/imagelib/effects/kpEffectBalanceWidget.cpp +++ b/widgets/imagelib/effects/kpEffectBalanceWidget.cpp @@ -1,317 +1,317 @@ /* Copyright (c) 2003-2007 Clarence Dang All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define DEBUG_KP_EFFECT_BALANCE 0 #include "kpEffectBalanceWidget.h" #include "imagelib/effects/kpEffectBalance.h" #include "commands/imagelib/effects/kpEffectBalanceCommand.h" #include "pixmapfx/kpPixmapFX.h" #include "kpLogCategories.h" #include #include "kpNumInput.h" #include #include #include #include #include #include #if DEBUG_KP_EFFECT_BALANCE #include #endif kpEffectBalanceWidget::kpEffectBalanceWidget (bool actOnSelection, QWidget *parent) : kpEffectWidgetBase (actOnSelection, parent) { QGridLayout *lay = new QGridLayout (this); lay->setMargin (0); QLabel *brightnessLabel = new QLabel (i18n ("&Brightness:"), this); m_brightnessInput = new kpIntNumInput (0/*value*/, this); m_brightnessInput->setRange (-50, 50); QPushButton *brightnessResetPushButton = new QPushButton (i18n ("Re&set"), this); QLabel *contrastLabel = new QLabel (i18n ("Co&ntrast:"), this); m_contrastInput = new kpIntNumInput (0/*value*/, this); m_contrastInput->setRange (-50, 50); QPushButton *contrastResetPushButton = new QPushButton (i18n ("&Reset"), this); QLabel *gammaLabel = new QLabel (i18n ("&Gamma:"), this); m_gammaInput = new kpIntNumInput (0/*value*/, this); m_gammaInput->setRange (-50, 50); // TODO: This is what should be shown in the m_gammaInput spinbox m_gammaLabel = new QLabel (this); // TODO: This doesn't seem to be wide enough with some fonts so the // whole layout moves when we drag the gamma slider. m_gammaLabel->setMinimumWidth (m_gammaLabel->fontMetrics ().width (QLatin1String (" 10.00 "))); m_gammaLabel->setAlignment (m_gammaLabel->alignment () | Qt::AlignRight); QPushButton *gammaResetPushButton = new QPushButton (i18n ("Rese&t"), this); QWidget *spaceWidget = new QLabel (this); spaceWidget->setFixedSize (1, fontMetrics ().height () / 4); QLabel *channelLabel = new QLabel (i18n ("C&hannels:"), this); m_channelsComboBox = new QComboBox (this); m_channelsComboBox->addItem (i18n ("All")); m_channelsComboBox->addItem (i18n ("Red")); m_channelsComboBox->addItem (i18n ("Green")); m_channelsComboBox->addItem (i18n ("Blue")); QPushButton *resetPushButton = new QPushButton (i18n ("Reset &All Values"), this); brightnessLabel->setBuddy (m_brightnessInput); contrastLabel->setBuddy (m_contrastInput); gammaLabel->setBuddy (m_gammaInput); channelLabel->setBuddy (m_channelsComboBox); lay->addWidget (brightnessLabel, 0, 0); lay->addWidget (m_brightnessInput, 0, 1, 1, 2); lay->addWidget (brightnessResetPushButton, 0, 4); lay->addWidget (contrastLabel, 1, 0); lay->addWidget (m_contrastInput, 1, 1, 1, 2); lay->addWidget (contrastResetPushButton, 1, 4); lay->addWidget (gammaLabel, 2, 0); lay->addWidget (m_gammaInput, 2, 1, 1, 2); lay->addWidget (m_gammaLabel, 2, 3); lay->addWidget (gammaResetPushButton, 2, 4); lay->addWidget (spaceWidget, 3, 0, 1, 5); lay->addWidget (resetPushButton, 4, 2, 1, 3, Qt::AlignRight); lay->addWidget (channelLabel, 4, 0); lay->addWidget (m_channelsComboBox, 4, 1, Qt::AlignLeft); //lay->addWidget (resetPushButton, 4, 2, Qt::AlignRight); lay->setColumnStretch (1, 1); // (no need for settingsChangedDelayed() since BCG effect is so fast :)) connect (m_brightnessInput, &kpIntNumInput::valueChanged, this, &kpEffectBalanceWidget::settingsChangedNoWaitCursor); connect (m_contrastInput, &kpIntNumInput::valueChanged, this, &kpEffectBalanceWidget::settingsChangedNoWaitCursor); connect (m_gammaInput, &kpIntNumInput::valueChanged, this, &kpEffectBalanceWidget::recalculateGammaLabel); connect (m_gammaInput, &kpIntNumInput::valueChanged, this, &kpEffectBalanceWidget::settingsChangedNoWaitCursor); connect (m_channelsComboBox, static_cast(&QComboBox::activated), this, &kpEffectBalanceWidget::settingsChanged); connect (brightnessResetPushButton, &QPushButton::clicked, this, &kpEffectBalanceWidget::resetBrightness); connect (contrastResetPushButton, &QPushButton::clicked, this, &kpEffectBalanceWidget::resetContrast); connect (gammaResetPushButton, &QPushButton::clicked, this, &kpEffectBalanceWidget::resetGamma); connect (resetPushButton, &QPushButton::clicked, this, &kpEffectBalanceWidget::resetAll); recalculateGammaLabel (); } kpEffectBalanceWidget::~kpEffectBalanceWidget () { } // public virtual [base kpEffectWidgetBase] QString kpEffectBalanceWidget::caption () const { return i18n ("Settings"); } // public virtual [base kpEffectWidgetBase] bool kpEffectBalanceWidget::isNoOp () const { return (brightness () == 0 && contrast () == 0 && gamma () == 0); } // public virtual [base kpEffectWidgetBase] kpImage kpEffectBalanceWidget::applyEffect (const kpImage &image) { return kpEffectBalance::applyEffect (image, channels (), brightness (), contrast (), gamma ()); } // public virtual [base kpEffectWidgetBase] kpEffectCommandBase *kpEffectBalanceWidget::createCommand ( kpCommandEnvironment *cmdEnviron) const { return new kpEffectBalanceCommand (channels (), brightness (), contrast (), gamma (), m_actOnSelection, cmdEnviron); } // protected int kpEffectBalanceWidget::channels () const { switch (m_channelsComboBox->currentIndex ()) { default: case 0: return kpEffectBalance::RGB; case 1: return kpEffectBalance::Red; case 2: return kpEffectBalance::Green; case 3: return kpEffectBalance::Blue; } } // protected int kpEffectBalanceWidget::brightness () const { return m_brightnessInput->value (); } // protected int kpEffectBalanceWidget::contrast () const { return m_contrastInput->value (); } // protected int kpEffectBalanceWidget::gamma () const { return m_gammaInput->value (); } // protected slot void kpEffectBalanceWidget::recalculateGammaLabel () { m_gammaLabel->setText ( QLatin1String (" ") + - QString::number (pow (10, gamma () / 50.0), + QString::number (std::pow (10, gamma () / 50.0), 'f'/*[-]9.9*/, 2/*precision*/) + QLatin1String (" ")); m_gammaLabel->repaint (); } // protected slot void kpEffectBalanceWidget::resetBrightness () { if (brightness () == 0) return; bool sb = signalsBlocked (); if (!sb) blockSignals (true); m_brightnessInput->setValue (0); if (!sb) blockSignals (false); // Immediate update (if signals aren't blocked) emit settingsChanged (); } // protected slot void kpEffectBalanceWidget::resetContrast () { if (contrast () == 0) return; bool sb = signalsBlocked (); if (!sb) blockSignals (true); m_contrastInput->setValue (0); if (!sb) blockSignals (false); // Immediate update (if signals aren't blocked) emit settingsChanged (); } // protected slot void kpEffectBalanceWidget::resetGamma () { if (gamma () == 0) return; bool sb = signalsBlocked (); if (!sb) blockSignals (true); m_gammaInput->setValue (0); recalculateGammaLabel (); if (!sb) blockSignals (false); // Immediate update (if signals aren't blocked) emit settingsChanged (); } // protected slot void kpEffectBalanceWidget::resetAll () { if (isNoOp ()) return; // Prevent multiple settingsChanged() which would normally result in // redundant, expensive preview repaints blockSignals (true); resetBrightness (); resetContrast (); resetGamma (); recalculateGammaLabel (); blockSignals (false); emit settingsChanged (); } diff --git a/widgets/imagelib/effects/kpNumInput.cpp b/widgets/imagelib/effects/kpNumInput.cpp index 433a68c9..17771256 100644 --- a/widgets/imagelib/effects/kpNumInput.cpp +++ b/widgets/imagelib/effects/kpNumInput.cpp @@ -1,710 +1,710 @@ /* This file is part of the KDE libraries * Initial implementation: * Copyright (c) 1997 Patrick Dowler * Rewritten and maintained by: * Copyright (c) 2000 Dirk Mueller * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kpNumInput.h" #include #include #include #include #include #include #include #include #include #include static inline int calcDiffByTen(int x, int y) { // calculate ( x - y ) / 10 without overflowing ints: return (x / 10) - (y / 10) + (x % 10 - y % 10) / 10; } // ---------------------------------------------------------------------------- class kpNumInputPrivate { public: kpNumInputPrivate(kpNumInput *q) : q(q), column1Width(0), column2Width(0), label(nullptr), slider(nullptr), labelAlignment(nullptr) { } static kpNumInputPrivate *get(const kpNumInput *i) { return i->d; } kpNumInput *q; int column1Width, column2Width; QLabel *label; QSlider *slider; QSize sliderSize, labelSize; Qt::Alignment labelAlignment; }; #define K_USING_kpNumInput_P(_d) kpNumInputPrivate *_d = kpNumInputPrivate::get(this) kpNumInput::kpNumInput(QWidget *parent) : QWidget(parent), d(new kpNumInputPrivate(this)) { setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed)); setFocusPolicy(Qt::StrongFocus); KConfigDialogManager::changedMap()->insert("kpIntNumInput", SIGNAL(valueChanged(int))); KConfigDialogManager::changedMap()->insert("QSpinBox", SIGNAL(valueChanged(int))); KConfigDialogManager::changedMap()->insert("kpDoubleSpinBox", SIGNAL(valueChanged(double))); } kpNumInput::~kpNumInput() { delete d; } QSlider *kpNumInput::slider() const { return d->slider; } bool kpNumInput::showSlider() const { return d->slider; } void kpNumInput::setLabel(const QString &label, Qt::Alignment a) { if (label.isEmpty()) { delete d->label; d->label = nullptr; d->labelAlignment = nullptr; } else { if (!d->label) { d->label = new QLabel(this); } d->label->setText(label); d->label->setObjectName("kpNumInput::QLabel"); d->label->setAlignment(a); // if no vertical alignment set, use Top alignment if (!(a & (Qt::AlignTop | Qt::AlignBottom | Qt::AlignVCenter))) { a |= Qt::AlignTop; } d->labelAlignment = a; } layout(); } QString kpNumInput::label() const { return d->label ? d->label->text() : QString(); } void kpNumInput::layout() { // label sizeHint d->labelSize = (d->label ? d->label->sizeHint() : QSize(0, 0)); if (d->label && (d->labelAlignment & Qt::AlignVCenter)) { d->column1Width = d->labelSize.width() + 4; } else { d->column1Width = 0; } // slider sizeHint d->sliderSize = (d->slider ? d->slider->sizeHint() : QSize(0, 0)); doLayout(); } QSize kpNumInput::sizeHint() const { return minimumSizeHint(); } void kpNumInput::setSteps(int minor, int major) { if (d->slider) { d->slider->setSingleStep(minor); d->slider->setPageStep(major); } } // ---------------------------------------------------------------------------- class kpIntNumInput::kpIntNumInputPrivate { public: kpIntNumInput *q; QSpinBox *intSpinBox; QSize intSpinBoxSize; kpIntNumInputPrivate(kpIntNumInput *q) : q(q) {} }; kpIntNumInput::kpIntNumInput(QWidget *parent) : kpNumInput(parent) , d(new kpIntNumInputPrivate(this)) { initWidget(0); } kpIntNumInput::kpIntNumInput(int val, QWidget *parent) : kpNumInput(parent) , d(new kpIntNumInputPrivate(this)) { initWidget(val); } QSpinBox *kpIntNumInput::spinBox() const { return d->intSpinBox; } void kpIntNumInput::initWidget(int val) { d->intSpinBox = new QSpinBox(this); d->intSpinBox->setRange(INT_MIN, INT_MAX); d->intSpinBox->setSingleStep(1); d->intSpinBox->setValue(val); d->intSpinBox->setObjectName("kpIntNumInput::QSpinBox"); connect(d->intSpinBox, QOverload::of(&QSpinBox::valueChanged), this, &kpIntNumInput::spinValueChanged); setFocusProxy(d->intSpinBox); layout(); } void kpIntNumInput::spinValueChanged(int val) { K_USING_kpNumInput_P(priv); if (priv->slider) { priv->slider->setValue(val); } emit valueChanged(val); } void kpIntNumInput::setRange(int lower, int upper, int singleStep) { if (upper < lower || singleStep <= 0) { qDebug() << "WARNING: kpIntNumInput::setRange() called with bad arguments. Ignoring call..."; return; } d->intSpinBox->setMinimum(lower); d->intSpinBox->setMaximum(upper); d->intSpinBox->setSingleStep(singleStep); singleStep = d->intSpinBox->singleStep(); // maybe QRangeControl didn't like our lineStep? layout(); // update slider information K_USING_kpNumInput_P(priv); if (!priv->slider) { priv->slider = new QSlider(Qt::Horizontal, this); connect(priv->slider, &QSlider::valueChanged, d->intSpinBox, &QSpinBox::setValue); priv->slider->setTickPosition(QSlider::TicksBelow); layout(); } const int value = d->intSpinBox->value(); priv->slider->setRange(d->intSpinBox->minimum(), d->intSpinBox->maximum()); priv->slider->setPageStep(d->intSpinBox->singleStep()); priv->slider->setValue(value); // calculate (upper-lower)/10 without overflowing int's: const int major = calcDiffByTen(d->intSpinBox->maximum(), d->intSpinBox->minimum()); priv->slider->setSingleStep(d->intSpinBox->singleStep()); priv->slider->setPageStep(qMax(1, major)); priv->slider->setTickInterval(major); } void kpIntNumInput::setMinimum(int min) { setRange(min, d->intSpinBox->maximum(), d->intSpinBox->singleStep()); } int kpIntNumInput::minimum() const { return d->intSpinBox->minimum(); } void kpIntNumInput::setMaximum(int max) { setRange(d->intSpinBox->minimum(), max, d->intSpinBox->singleStep()); } int kpIntNumInput::maximum() const { return d->intSpinBox->maximum(); } int kpIntNumInput::singleStep() const { return d->intSpinBox->singleStep(); } void kpIntNumInput::setSingleStep(int singleStep) { d->intSpinBox->setSingleStep(singleStep); } void kpIntNumInput::setSuffix(const QString &suffix) { d->intSpinBox->setSuffix(suffix); layout(); } QString kpIntNumInput::suffix() const { return d->intSpinBox->suffix(); } void kpIntNumInput::setEditFocus(bool mark) { if (mark) { d->intSpinBox->setFocus(); } } QSize kpIntNumInput::minimumSizeHint() const { K_USING_kpNumInput_P(priv); ensurePolished(); int w; int h; h = qMax(d->intSpinBoxSize.height(), priv->sliderSize.height()); // if in extra row, then count it here if (priv->label && (priv->labelAlignment & (Qt::AlignBottom | Qt::AlignTop))) { h += 4 + priv->labelSize.height(); } else { // label is in the same row as the other widgets h = qMax(h, priv->labelSize.height() + 2); } const int spacingHint = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); w = priv->slider ? priv->slider->sizeHint().width() + spacingHint : 0; w += priv->column1Width + priv->column2Width; if (priv->labelAlignment & (Qt::AlignTop | Qt::AlignBottom)) { w = qMax(w, priv->labelSize.width() + 4); } return QSize(w, h); } void kpIntNumInput::doLayout() { K_USING_kpNumInput_P(priv); d->intSpinBoxSize = d->intSpinBox->sizeHint(); priv->column2Width = d->intSpinBoxSize.width(); if (priv->label) { priv->label->setBuddy(d->intSpinBox); } } void kpIntNumInput::resizeEvent(QResizeEvent *e) { K_USING_kpNumInput_P(priv); int w = priv->column1Width; int h = 0; const int spacingHint = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); if (priv->label && (priv->labelAlignment & Qt::AlignTop)) { priv->label->setGeometry(0, 0, e->size().width(), priv->labelSize.height()); h += priv->labelSize.height() + spacingHint; } if (priv->label && (priv->labelAlignment & Qt::AlignVCenter)) { priv->label->setGeometry(0, 0, w, d->intSpinBoxSize.height()); } if (qApp->layoutDirection() == Qt::RightToLeft) { d->intSpinBox->setGeometry(w, h, priv->slider ? priv->column2Width : qMax(priv->column2Width, e->size().width() - w), d->intSpinBoxSize.height()); w += priv->column2Width + spacingHint; if (priv->slider) { priv->slider->setGeometry(w, h, e->size().width() - w, d->intSpinBoxSize.height() + spacingHint); } } else if (priv->slider) { priv->slider->setGeometry(w, h, e->size().width() - (w + priv->column2Width + spacingHint), d->intSpinBoxSize.height() + spacingHint); d->intSpinBox->setGeometry(w + priv->slider->size().width() + spacingHint, h, priv->column2Width, d->intSpinBoxSize.height()); } else { d->intSpinBox->setGeometry(w, h, qMax(priv->column2Width, e->size().width() - w), d->intSpinBoxSize.height()); } h += d->intSpinBoxSize.height() + 2; if (priv->label && (priv->labelAlignment & Qt::AlignBottom)) { priv->label->setGeometry(0, h, priv->labelSize.width(), priv->labelSize.height()); } } kpIntNumInput::~kpIntNumInput() { delete d; } void kpIntNumInput::setValue(int val) { d->intSpinBox->setValue(val); // slider value is changed by spinValueChanged } int kpIntNumInput::value() const { return d->intSpinBox->value(); } void kpIntNumInput::setSpecialValueText(const QString &text) { d->intSpinBox->setSpecialValueText(text); layout(); } QString kpIntNumInput::specialValueText() const { return d->intSpinBox->specialValueText(); } void kpIntNumInput::setLabel(const QString &label, Qt::Alignment a) { K_USING_kpNumInput_P(priv); kpNumInput::setLabel(label, a); if (priv->label) { priv->label->setBuddy(d->intSpinBox); } } // ---------------------------------------------------------------------------- class kpDoubleNumInput::kpDoubleNumInputPrivate { public: kpDoubleNumInputPrivate() : spin(nullptr) {} QDoubleSpinBox *spin; QSize editSize; QString specialValue; }; kpDoubleNumInput::kpDoubleNumInput(QWidget *parent) : kpNumInput(parent) , d(new kpDoubleNumInputPrivate()) { initWidget(0.0, 0.0, 9999.0, 0.01, 2); } kpDoubleNumInput::kpDoubleNumInput(double lower, double upper, double value, QWidget *parent, double singleStep, int precision) : kpNumInput(parent) , d(new kpDoubleNumInputPrivate()) { initWidget(value, lower, upper, singleStep, precision); } kpDoubleNumInput::~kpDoubleNumInput() { delete d; } QString kpDoubleNumInput::specialValueText() const { return d->specialValue; } void kpDoubleNumInput::initWidget(double value, double lower, double upper, double singleStep, int precision) { d->spin = new QDoubleSpinBox(this); d->spin->setRange(lower, upper); d->spin->setSingleStep(singleStep); d->spin->setValue(value); d->spin->setDecimals(precision); d->spin->setObjectName("kpDoubleNumInput::QDoubleSpinBox"); setFocusProxy(d->spin); connect(d->spin, QOverload::of(&QDoubleSpinBox::valueChanged), this, &kpDoubleNumInput::valueChanged); layout(); } double kpDoubleNumInput::mapSliderToSpin(int val) const { K_USING_kpNumInput_P(priv); // map [slidemin,slidemax] to [spinmin,spinmax] const double spinmin = d->spin->minimum(); const double spinmax = d->spin->maximum(); const double slidemin = priv->slider->minimum(); // cast int to double to avoid const double slidemax = priv->slider->maximum(); // overflow in rel denominator const double rel = (double(val) - slidemin) / (slidemax - slidemin); return spinmin + rel * (spinmax - spinmin); } void kpDoubleNumInput::sliderMoved(int val) { d->spin->setValue(mapSliderToSpin(val)); } void kpDoubleNumInput::spinBoxChanged(double val) { K_USING_kpNumInput_P(priv); const double spinmin = d->spin->minimum(); const double spinmax = d->spin->maximum(); const double slidemin = priv->slider->minimum(); // cast int to double to avoid const double slidemax = priv->slider->maximum(); // overflow in rel denominator const double rel = (val - spinmin) / (spinmax - spinmin); if (priv->slider) { priv->slider->blockSignals(true); priv->slider->setValue(qRound(slidemin + rel * (slidemax - slidemin))); priv->slider->blockSignals(false); } } QSize kpDoubleNumInput::minimumSizeHint() const { K_USING_kpNumInput_P(priv); ensurePolished(); int w; int h; h = qMax(d->editSize.height(), priv->sliderSize.height()); // if in extra row, then count it here if (priv->label && (priv->labelAlignment & (Qt::AlignBottom | Qt::AlignTop))) { h += 4 + priv->labelSize.height(); } else { // label is in the same row as the other widgets h = qMax(h, priv->labelSize.height() + 2); } const int spacingHint = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); w = priv->slider ? priv->slider->sizeHint().width() + spacingHint : 0; w += priv->column1Width + priv->column2Width; if (priv->labelAlignment & (Qt::AlignTop | Qt::AlignBottom)) { w = qMax(w, priv->labelSize.width() + 4); } return QSize(w, h); } void kpDoubleNumInput::resizeEvent(QResizeEvent *e) { K_USING_kpNumInput_P(priv); int w = priv->column1Width; int h = 0; const int spacingHint = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); if (priv->label && (priv->labelAlignment & Qt::AlignTop)) { priv->label->setGeometry(0, 0, e->size().width(), priv->labelSize.height()); h += priv->labelSize.height() + 4; } if (priv->label && (priv->labelAlignment & Qt::AlignVCenter)) { priv->label->setGeometry(0, 0, w, d->editSize.height()); } if (qApp->layoutDirection() == Qt::RightToLeft) { d->spin->setGeometry(w, h, priv->slider ? priv->column2Width : e->size().width() - w, d->editSize.height()); w += priv->column2Width + spacingHint; if (priv->slider) { priv->slider->setGeometry(w, h, e->size().width() - w, d->editSize.height() + spacingHint); } } else if (priv->slider) { priv->slider->setGeometry(w, h, e->size().width() - (priv->column1Width + priv->column2Width + spacingHint), d->editSize.height() + spacingHint); d->spin->setGeometry(w + priv->slider->width() + spacingHint, h, priv->column2Width, d->editSize.height()); } else { d->spin->setGeometry(w, h, e->size().width() - w, d->editSize.height()); } h += d->editSize.height() + 2; if (priv->label && (priv->labelAlignment & Qt::AlignBottom)) { priv->label->setGeometry(0, h, priv->labelSize.width(), priv->labelSize.height()); } } void kpDoubleNumInput::doLayout() { K_USING_kpNumInput_P(priv); d->editSize = d->spin->sizeHint(); priv->column2Width = d->editSize.width(); } void kpDoubleNumInput::setValue(double val) { d->spin->setValue(val); } void kpDoubleNumInput::setRange(double lower, double upper, double singleStep) { K_USING_kpNumInput_P(priv); QDoubleSpinBox *spin = d->spin; d->spin->setRange(lower, upper); d->spin->setSingleStep(singleStep); const double range = spin->maximum() - spin->minimum(); - const double steps = range * pow(10.0, spin->decimals()); + const double steps = range * std::pow(10.0, spin->decimals()); if (!priv->slider) { priv->slider = new QSlider(Qt::Horizontal, this); priv->slider->setTickPosition(QSlider::TicksBelow); // feedback line: when one moves, the other moves, too: connect(priv->slider, &QSlider::valueChanged, this, &kpDoubleNumInput::sliderMoved); layout(); } if (steps > 1000 ) { priv->slider->setRange(0, 1000); priv->slider->setSingleStep(1); priv->slider->setPageStep(50); } else { const int singleSteps = qRound(steps); priv->slider->setRange(0, singleSteps); priv->slider->setSingleStep(1); const int pageSteps = qBound(1, singleSteps / 20, 10); priv->slider->setPageStep(pageSteps); } spinBoxChanged(spin->value()); connect(spin, QOverload::of(&QDoubleSpinBox::valueChanged), this, &kpDoubleNumInput::spinBoxChanged); layout(); } void kpDoubleNumInput::setMinimum(double min) { setRange(min, maximum(), d->spin->singleStep()); } double kpDoubleNumInput::minimum() const { return d->spin->minimum(); } void kpDoubleNumInput::setMaximum(double max) { setRange(minimum(), max, d->spin->singleStep()); } double kpDoubleNumInput::maximum() const { return d->spin->maximum(); } double kpDoubleNumInput::singleStep() const { return d->spin->singleStep(); } void kpDoubleNumInput::setSingleStep(double singleStep) { d->spin->setSingleStep(singleStep); } double kpDoubleNumInput::value() const { return d->spin->value(); } QString kpDoubleNumInput::suffix() const { return d->spin->suffix(); } void kpDoubleNumInput::setSuffix(const QString &suffix) { d->spin->setSuffix(suffix); layout(); } void kpDoubleNumInput::setDecimals(int decimals) { d->spin->setDecimals(decimals); layout(); } int kpDoubleNumInput::decimals() const { return d->spin->decimals(); } void kpDoubleNumInput::setSpecialValueText(const QString &text) { d->spin->setSpecialValueText(text); layout(); } void kpDoubleNumInput::setLabel(const QString &label, Qt::Alignment a) { K_USING_kpNumInput_P(priv); kpNumInput::setLabel(label, a); if (priv->label) { priv->label->setBuddy(d->spin); } } #include "moc_kpNumInput.cpp"