diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | 47d455dd55be855e4cc691c32f687f723d9247ee (patch) | |
tree | 52e236aaa2576bdb3840ebede26619692fed6d7d /kolourpaint/pixmapfx | |
download | tdegraphics-47d455dd55be855e4cc691c32f687f723d9247ee.tar.gz tdegraphics-47d455dd55be855e4cc691c32f687f723d9247ee.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdegraphics@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kolourpaint/pixmapfx')
21 files changed, 6084 insertions, 0 deletions
diff --git a/kolourpaint/pixmapfx/Makefile.am b/kolourpaint/pixmapfx/Makefile.am new file mode 100644 index 00000000..dfa1d697 --- /dev/null +++ b/kolourpaint/pixmapfx/Makefile.am @@ -0,0 +1,19 @@ +INCLUDES = -I$(srcdir)/.. -I$(srcdir)/../cursors -I$(srcdir)/../interfaces \ + -I$(srcdir)/../pixmapfx \ + -I$(srcdir)/../tools \ + -I$(srcdir)/../views \ + -I$(srcdir)/../widgets $(all_includes) + +noinst_LTLIBRARIES = libkolourpaintpixmapfx.la +libkolourpaintpixmapfx_la_SOURCES = kpcoloreffect.cpp \ + kpeffectbalance.cpp \ + kpeffectblursharpen.cpp \ + kpeffectemboss.cpp \ + kpeffectflatten.cpp \ + kpeffectinvert.cpp \ + kpeffectreducecolors.cpp \ + kpeffectsdialog.cpp \ + kpfloodfill.cpp \ + kppixmapfx.cpp + +METASOURCES = AUTO diff --git a/kolourpaint/pixmapfx/kpcoloreffect.cpp b/kolourpaint/pixmapfx/kpcoloreffect.cpp new file mode 100644 index 00000000..1660c1fa --- /dev/null +++ b/kolourpaint/pixmapfx/kpcoloreffect.cpp @@ -0,0 +1,168 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <[email protected]> + 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 <kpcoloreffect.h> + +#include <qapplication.h> +#include <qpixmap.h> + +#include <kdialog.h> +#include <klocale.h> + +#include <kpdefs.h> +#include <kpdocument.h> +#include <kpmainwindow.h> +#include <kpselection.h> + + +kpColorEffectCommand::kpColorEffectCommand (const QString &name, + bool actOnSelection, + kpMainWindow *mainWindow) + : kpCommand (mainWindow), + m_name (name), + m_actOnSelection (actOnSelection), + m_oldPixmapPtr (0) +{ +} + +kpColorEffectCommand::~kpColorEffectCommand () +{ + delete m_oldPixmapPtr; m_oldPixmapPtr = 0; +} + + +// public virtual [base kpCommand] +QString kpColorEffectCommand::name () const +{ + if (m_actOnSelection) + return i18n ("Selection: %1").arg (m_name); + else + return m_name; +} + + +// public virtual [base kpCommand] +int kpColorEffectCommand::size () const +{ + return kpPixmapFX::pixmapSize (m_oldPixmapPtr); +} + + +// public virtual [base kpCommand] +void kpColorEffectCommand::execute () +{ + kpDocument *doc = document (); + if (!doc) + return; + + QApplication::setOverrideCursor (Qt::waitCursor); + + + const QPixmap oldPixmap = *doc->pixmap (m_actOnSelection); + + if (!isInvertible ()) + { + m_oldPixmapPtr = new QPixmap (); + *m_oldPixmapPtr = oldPixmap; + } + + + QPixmap newPixmap = /*pure virtual*/applyColorEffect (oldPixmap); + + doc->setPixmap (m_actOnSelection, newPixmap); + + + QApplication::restoreOverrideCursor (); +} + +// public virtual [base kpCommand] +void kpColorEffectCommand::unexecute () +{ + kpDocument *doc = document (); + if (!doc) + return; + + QApplication::setOverrideCursor (Qt::waitCursor); + + + QPixmap newPixmap; + + if (!isInvertible ()) + { + newPixmap = *m_oldPixmapPtr; + } + else + { + newPixmap = /*pure virtual*/applyColorEffect (*doc->pixmap (m_actOnSelection)); + } + + doc->setPixmap (m_actOnSelection, newPixmap); + + + delete m_oldPixmapPtr; m_oldPixmapPtr = 0; + + + QApplication::restoreOverrideCursor (); +} + + +kpColorEffectWidget::kpColorEffectWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, const char *name) + : QWidget (parent, name), + m_actOnSelection (actOnSelection), + m_mainWindow (mainWindow) +{ +} + +kpColorEffectWidget::~kpColorEffectWidget () +{ +} + + +// public +QString kpColorEffectWidget::caption () const +{ + return QString::null; +} + + +// protected +int kpColorEffectWidget::marginHint () const +{ + return 0; +} + +// protected +int kpColorEffectWidget::spacingHint () const +{ + return KDialog::spacingHint (); +} + + +#include <kpcoloreffect.moc> diff --git a/kolourpaint/pixmapfx/kpcoloreffect.h b/kolourpaint/pixmapfx/kpcoloreffect.h new file mode 100644 index 00000000..8b3dfd09 --- /dev/null +++ b/kolourpaint/pixmapfx/kpcoloreffect.h @@ -0,0 +1,111 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <[email protected]> + 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_COLOR_EFFECT_H +#define KP_COLOR_EFFECT_H + +#include <qstring.h> +#include <qwidget.h> + +#include <kpcommandhistory.h> + +class QPixmap; + +class kpDocument; +class kpMainWindow; + + +class kpColorEffectCommand : public kpCommand +{ +public: + kpColorEffectCommand (const QString &name, + bool actOnSelection, + kpMainWindow *mainWindow); + virtual ~kpColorEffectCommand (); + + virtual QString name () const; + virtual int size () const; + +public: + virtual void execute (); + virtual void unexecute (); + +public: + // Return true if applyColorEffect(applyColorEffect(pixmap)) == pixmap + // to avoid storing the old pixmap, saving memory. + virtual bool isInvertible () const { return false; } + +protected: + virtual QPixmap applyColorEffect (const QPixmap &pixmap) = 0; + +private: + QString m_name; + bool m_actOnSelection; + + QPixmap *m_oldPixmapPtr; +}; + + +class kpColorEffectWidget : public QWidget +{ +Q_OBJECT + +public: + kpColorEffectWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, const char *name = 0); + virtual ~kpColorEffectWidget (); + +signals: + void settingsChangedNoWaitCursor (); + + void settingsChanged (); + + // (same as settingsChanged() but preview doesn't update until there + // has been no activity for a while - used for sliders in slow effects) + void settingsChangedDelayed (); + +public: + virtual QString caption () const; + + virtual bool isNoOp () const = 0; + virtual QPixmap applyColorEffect (const QPixmap &pixmap) = 0; + + virtual kpColorEffectCommand *createCommand () const = 0; + +protected: + int marginHint () const; + int spacingHint () const; + +protected: + bool m_actOnSelection; + kpMainWindow *m_mainWindow; +}; + + +#endif // KP_COLOR_EFFECT_H diff --git a/kolourpaint/pixmapfx/kpeffectbalance.cpp b/kolourpaint/pixmapfx/kpeffectbalance.cpp new file mode 100644 index 00000000..f4494d29 --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectbalance.cpp @@ -0,0 +1,517 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <[email protected]> + 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 <math.h> + +#include <qfontmetrics.h> +#include <qimage.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qpixmap.h> +#include <qpushbutton.h> + +#include <kcombobox.h> +#include <kdebug.h> +#include <kimageeffect.h> +#include <klocale.h> +#include <knuminput.h> + +#include <kppixmapfx.h> + + +#if DEBUG_KP_EFFECT_BALANCE + #include <qdatetime.h> +#endif + + +kpEffectBalanceCommand::kpEffectBalanceCommand (int channels, + int brightness, int contrast, int gamma, + bool actOnSelection, + kpMainWindow *mainWindow) + : kpColorEffectCommand (i18n ("Balance"), actOnSelection, mainWindow), + m_channels (channels), + m_brightness (brightness), m_contrast (contrast), m_gamma (gamma) +{ +} + +kpEffectBalanceCommand::~kpEffectBalanceCommand () +{ +} + + +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)))); +} + + +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 & kpEffectBalanceCommand::Red) + red = brightnessContrastGamma (red, brightness, contrast, gamma); + if (channels & kpEffectBalanceCommand::Green) + green = brightnessContrastGamma (green, brightness, contrast, gamma); + if (channels & kpEffectBalanceCommand::Blue) + blue = brightnessContrastGamma (blue, brightness, contrast, gamma); + + + return qRgba (red, green, blue, qAlpha (rgb)); +} + + +// public static +QPixmap kpEffectBalanceCommand::applyColorEffect (const QPixmap &pixmap, + int channels, + int brightness, int contrast, int gamma) +{ +#if DEBUG_KP_EFFECT_BALANCE + kdDebug () << "kpEffectBalanceCommand::applyColorEffect(" + << "channels=" << channels + << ",brightness=" << brightness + << ",contrast=" << contrast + << ",gamma=" << gamma + << ")" << endl; + QTime timer; timer.start (); +#endif + + QImage image = kpPixmapFX::convertToImage (pixmap); +#if DEBUG_KP_EFFECT_BALANCE + kdDebug () << "\tconvertToImage=" << timer.restart () << endl; +#endif + + + Q_UINT8 transformRed [256], + transformGreen [256], + transformBlue [256]; + + for (int i = 0; i < 256; i++) + { + Q_UINT8 applied = (Q_UINT8) brightnessContrastGamma (i, brightness, contrast, gamma); + + if (channels & kpEffectBalanceCommand::Red) + transformRed [i] = applied; + else + transformRed [i] = i; + + if (channels & kpEffectBalanceCommand::Green) + transformGreen [i] = applied; + else + transformGreen [i] = i; + + if (channels & kpEffectBalanceCommand::Blue) + transformBlue [i] = applied; + else + transformBlue [i] = i; + } + +#if DEBUG_KP_EFFECT_BALANCE + kdDebug () << "\tbuild lookup=" << timer.restart () << endl; +#endif + + + if (image.depth () > 8) + { + for (int y = 0; y < image.height (); y++) + { + for (int x = 0; x < image.width (); x++) + { + const QRgb rgb = image.pixel (x, y); + + const Q_UINT8 red = (Q_UINT8) qRed (rgb); + const Q_UINT8 green = (Q_UINT8) qGreen (rgb); + const Q_UINT8 blue = (Q_UINT8) qBlue (rgb); + const Q_UINT8 alpha = (Q_UINT8) qAlpha (rgb); + + image.setPixel (x, y, + qRgba (transformRed [red], + transformGreen [green], + transformBlue [blue], + alpha)); + + #if 0 + image.setPixel (x, y, + brightnessContrastGammaForRGB (image.pixel (x, y), + channels, + brightness, contrast, gamma)); + #endif + } + } + } + else + { + for (int i = 0; i < image.numColors (); i++) + { + const QRgb rgb = image.color (i); + + const Q_UINT8 red = (Q_UINT8) qRed (rgb); + const Q_UINT8 green = (Q_UINT8) qGreen (rgb); + const Q_UINT8 blue = (Q_UINT8) qBlue (rgb); + const Q_UINT8 alpha = (Q_UINT8) qAlpha (rgb); + + image.setColor (i, + qRgba (transformRed [red], + transformGreen [green], + transformBlue [blue], + alpha)); + + #if 0 + image.setColor (i, + brightnessContrastGammaForRGB (image.color (i), + channels, + brightness, contrast, gamma)); + #endif + } + + } +#if DEBUG_KP_EFFECT_BALANCE + kdDebug () << "\teffect=" << timer.restart () << endl; +#endif + + const QPixmap retPixmap = kpPixmapFX::convertToPixmap (image); +#if DEBUG_KP_EFFECT_BALANCE + kdDebug () << "\tconvertToPixmap=" << timer.restart () << endl; +#endif + + return retPixmap; +} + +// protected virtual [base kpColorEffectCommand] +QPixmap kpEffectBalanceCommand::applyColorEffect (const QPixmap &pixmap) +{ + return applyColorEffect (pixmap, m_channels, + m_brightness, m_contrast, m_gamma); +} + + + +kpEffectBalanceWidget::kpEffectBalanceWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, const char *name) + : kpColorEffectWidget (actOnSelection, mainWindow, parent, name) +{ + QGridLayout *lay = new QGridLayout (this, 5, 5, marginHint (), spacingHint ()); + + + QLabel *brightnessLabel = new QLabel (i18n ("&Brightness:"), this); + m_brightnessInput = new KIntNumInput (0/*value*/, this); + m_brightnessInput->setRange (-50, 50, 1/*step*/, true/*slider*/); + QPushButton *brightnessResetPushButton = new QPushButton (i18n ("Re&set"), this); + + QLabel *contrastLabel = new QLabel (i18n ("Co&ntrast:"), this); + m_contrastInput = new KIntNumInput (0/*value*/, this); + m_contrastInput->setRange (-50, 50, 1/*step*/, true/*slider*/); + QPushButton *contrastResetPushButton = new QPushButton (i18n ("&Reset"), this); + + QLabel *gammaLabel = new QLabel (i18n ("&Gamma:"), this); + m_gammaInput = new KIntNumInput (0/*value*/, this); + m_gammaInput->setRange (-50, 50, 1/*step*/, true/*slider*/); + // 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 (" 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, spacingHint ()); + + + QLabel *channelLabel = new QLabel (i18n ("C&hannels:"), this); + m_channelsComboBox = new KComboBox (this); + m_channelsComboBox->insertItem (i18n ("All")); + m_channelsComboBox->insertItem (i18n ("Red")); + m_channelsComboBox->insertItem (i18n ("Green")); + m_channelsComboBox->insertItem (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->addMultiCellWidget (m_brightnessInput, 0, 0, 1, 2); + lay->addWidget (brightnessResetPushButton, 0, 4); + + lay->addWidget (contrastLabel, 1, 0); + lay->addMultiCellWidget (m_contrastInput, 1, 1, 1, 2); + lay->addWidget (contrastResetPushButton, 1, 4); + + lay->addWidget (gammaLabel, 2, 0); + lay->addMultiCellWidget (m_gammaInput, 2, 2, 1, 2); + lay->addWidget (m_gammaLabel, 2, 3); + lay->addWidget (gammaResetPushButton, 2, 4); + + lay->addMultiCellWidget (spaceWidget, 3, 3, 0, 4); + lay->addMultiCellWidget (resetPushButton, 4, 4, 2, 4, Qt::AlignRight); + + lay->addWidget (channelLabel, 4, 0); + lay->addWidget (m_channelsComboBox, 4, 1, Qt::AlignLeft); + //lay->addWidget (resetPushButton, 4, 2, Qt::AlignRight); + + lay->setColStretch (1, 1); + + + // (no need for settingsChangedDelayed() since BCG effect is so fast :)) + connect (m_brightnessInput, SIGNAL (valueChanged (int)), + this, SIGNAL (settingsChangedNoWaitCursor ())); + connect (m_contrastInput, SIGNAL (valueChanged (int)), + this, SIGNAL (settingsChangedNoWaitCursor ())); + + connect (m_gammaInput, SIGNAL (valueChanged (int)), + this, SLOT (recalculateGammaLabel ())); + connect (m_gammaInput, SIGNAL (valueChanged (int)), + this, SIGNAL (settingsChangedNoWaitCursor ())); + + connect (m_channelsComboBox, SIGNAL (activated (int)), + this, SIGNAL (settingsChanged ())); + + connect (brightnessResetPushButton, SIGNAL (clicked ()), + this, SLOT (resetBrightness ())); + connect (contrastResetPushButton, SIGNAL (clicked ()), + this, SLOT (resetContrast ())); + connect (gammaResetPushButton, SIGNAL (clicked ()), + this, SLOT (resetGamma ())); + + connect (resetPushButton, SIGNAL (clicked ()), + this, SLOT (resetAll ())); + + + recalculateGammaLabel (); +} + +kpEffectBalanceWidget::~kpEffectBalanceWidget () +{ +} + + +// public virtual [base kpColorEffectWidget] +QString kpEffectBalanceWidget::caption () const +{ + return i18n ("Settings"); +} + + +// public virtual [base kpColorEffectWidget] +bool kpEffectBalanceWidget::isNoOp () const +{ + return (brightness () == 0 && contrast () == 0 && gamma () == 0); +} + +// public virtual [base kpColorEffectWidget] +QPixmap kpEffectBalanceWidget::applyColorEffect (const QPixmap &pixmap) +{ + return kpEffectBalanceCommand::applyColorEffect (pixmap, + channels (), brightness (), contrast (), gamma ()); +} + +// public virtual [base kpColorEffectWidget] +kpColorEffectCommand *kpEffectBalanceWidget::createCommand () const +{ + return new kpEffectBalanceCommand (channels (), + brightness (), contrast (), gamma (), + m_actOnSelection, + m_mainWindow); +} + + +// protected +int kpEffectBalanceWidget::channels () const +{ + switch (m_channelsComboBox->currentItem ()) + { + default: + case 0: + return kpEffectBalanceCommand::RGB; + + case 1: + return kpEffectBalanceCommand::Red; + + case 2: + return kpEffectBalanceCommand::Green; + + case 3: + return kpEffectBalanceCommand::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 ( + " " + + QString::number (pow (10, gamma () / 50.0), + 'f'/*[-]9.9*/, + 2/*precision*/) + + " "); + 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 (); +} + + +#include <kpeffectbalance.moc> diff --git a/kolourpaint/pixmapfx/kpeffectbalance.h b/kolourpaint/pixmapfx/kpeffectbalance.h new file mode 100644 index 00000000..b045159f --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectbalance.h @@ -0,0 +1,116 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <[email protected]> + 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_EFFECT_BALANCE_H +#define KP_EFFECT_BALANCE_H + +#include <kpcoloreffect.h> + + +class QLabel; + +class KComboBox; +class KIntNumInput; + +class kpMainWindowe; + + +class kpEffectBalanceCommand : public kpColorEffectCommand +{ +public: + enum Channel + { + None = 0, + Red = 1, Green = 2, Blue = 4, + RGB = Red | Green | Blue + }; + + // <brightness>, <contrast> & <gamma> are from -50 to 50 + + kpEffectBalanceCommand (int channels, + int brightness, int contrast, int gamma, + bool actOnSelection, + kpMainWindow *mainWindow); + virtual ~kpEffectBalanceCommand (); + + static QPixmap applyColorEffect (const QPixmap &pixmap, + int channels, + int brightness, int contrast, int gamma); + +protected: + virtual QPixmap applyColorEffect (const QPixmap &pixmap); + +protected: + int m_channels; + int m_brightness, m_contrast, m_gamma; +}; + + +class kpEffectBalanceWidget : public kpColorEffectWidget +{ +Q_OBJECT + +public: + kpEffectBalanceWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, const char *name = 0); + virtual ~kpEffectBalanceWidget (); + + virtual QString caption () const; + + virtual bool isNoOp () const; + virtual QPixmap applyColorEffect (const QPixmap &pixmap); + + virtual kpColorEffectCommand *createCommand () const; + +protected: + int channels () const; + + int brightness () const; + int contrast () const; + int gamma () const; + +protected slots: + void recalculateGammaLabel (); + + void resetBrightness (); + void resetContrast (); + void resetGamma (); + + void resetAll (); + +protected: + KIntNumInput *m_brightnessInput, + *m_contrastInput, + *m_gammaInput; + QLabel *m_gammaLabel; + KComboBox *m_channelsComboBox; +}; + + +#endif // KP_EFFECT_BALANCE_H diff --git a/kolourpaint/pixmapfx/kpeffectblursharpen.cpp b/kolourpaint/pixmapfx/kpeffectblursharpen.cpp new file mode 100644 index 00000000..50c0b27d --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectblursharpen.cpp @@ -0,0 +1,291 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <[email protected]> + 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_BLUR_SHARPEN 0 + + +#include <kpeffectblursharpen.h> + +#include <qimage.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qpixmap.h> +#include <qpushbutton.h> + +#include <kdebug.h> +#include <kimageeffect.h> +#include <klocale.h> +#include <knuminput.h> + +#include <kpmainwindow.h> +#include <kppixmapfx.h> + + +static QString nameForType (kpEffectBlurSharpenCommand::Type type) +{ + if (type == kpEffectBlurSharpenCommand::Blur) + return i18n ("Soften"); + else if (type == kpEffectBlurSharpenCommand::Sharpen) + return i18n ("Sharpen"); + else + return QString::null; +} + + +kpEffectBlurSharpenCommand::kpEffectBlurSharpenCommand (Type type, + double radius, double sigma, + int repeat, + bool actOnSelection, + kpMainWindow *mainWindow) + : kpColorEffectCommand (::nameForType (type), actOnSelection, mainWindow), + m_type (type), + m_radius (radius), m_sigma (sigma), + m_repeat (repeat) +{ +} + +kpEffectBlurSharpenCommand::~kpEffectBlurSharpenCommand () +{ +} + + +// public static +QPixmap kpEffectBlurSharpenCommand::apply (const QPixmap &pixmap, + Type type, double radius, double sigma, + int repeat) +{ +#if DEBUG_KP_EFFECT_BLUR_SHARPEN + kdDebug () << "kpEffectBlurSharpenCommand::apply(type=" + << int (type) + << " radius=" << radius + << " sigma=" << sigma + << " repeat=" << repeat + << ")" + << endl; +#endif + + // (KImageEffect::(blur|sharpen)() ignores mask) + QPixmap usePixmap = kpPixmapFX::pixmapWithDefinedTransparentPixels ( + pixmap, + Qt::white/*arbitrarily chosen*/); + + + QImage image = kpPixmapFX::convertToImage (usePixmap); + + for (int i = 0; i < repeat; i++) + { + if (type == Blur) + image = KImageEffect::blur (image, radius, sigma); + else if (type == Sharpen) + image = KImageEffect::sharpen (image, radius, sigma); + } + + QPixmap retPixmap = kpPixmapFX::convertToPixmap (image); + + + // KImageEffect::(blur|sharpen)() nukes mask - restore it + if (usePixmap.mask ()) + retPixmap.setMask (*usePixmap.mask ()); + + + return retPixmap; +} + +// protected virtual [base kpColorEffectCommand] +QPixmap kpEffectBlurSharpenCommand::applyColorEffect (const QPixmap &pixmap) +{ + return apply (pixmap, m_type, m_radius, m_sigma, m_repeat); +} + + + +kpEffectBlurSharpenWidget::kpEffectBlurSharpenWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, const char *name) + : kpColorEffectWidget (actOnSelection, mainWindow, parent, name) +{ + QGridLayout *lay = new QGridLayout (this, 4, 2, marginHint (), spacingHint ()); + + + QLabel *amountLabel = new QLabel (i18n ("&Amount:"), this); + m_amountInput = new KIntNumInput (this); + m_amountInput->setRange (-10, 10, 1/*step*/, true/*slider*/); + + m_typeLabel = new QLabel (this); + + + amountLabel->setBuddy (m_amountInput); + + + lay->addWidget (amountLabel, 0, 0); + lay->addWidget (m_amountInput, 0, 1); + + lay->addMultiCellWidget (m_typeLabel, 1, 1, 0, 1, Qt::AlignCenter); + + lay->setColStretch (1, 1); + + + connect (m_amountInput, SIGNAL (valueChanged (int)), + this, SIGNAL (settingsChangedDelayed ())); + + connect (m_amountInput, SIGNAL (valueChanged (int)), + this, SLOT (slotUpdateTypeLabel ())); +} + +kpEffectBlurSharpenWidget::~kpEffectBlurSharpenWidget () +{ +} + + +// public virtual [base kpColorEffectWidget] +QString kpEffectBlurSharpenWidget::caption () const +{ + return QString::null; +} + + +// public virtual [base kpColorEffectWidget] +bool kpEffectBlurSharpenWidget::isNoOp () const +{ + return (type () == kpEffectBlurSharpenCommand::None); +} + +// public virtual [base kpColorEffectWidget] +QPixmap kpEffectBlurSharpenWidget::applyColorEffect (const QPixmap &pixmap) +{ + return kpEffectBlurSharpenCommand::apply (pixmap, + type (), radius (), sigma (), repeat ()); +} + +// public virtual [base kpColorEffectWidget] +kpColorEffectCommand *kpEffectBlurSharpenWidget::createCommand () const +{ + return new kpEffectBlurSharpenCommand (type (), radius (), sigma (), repeat (), + m_actOnSelection, + m_mainWindow); +} + + +// protected slot +void kpEffectBlurSharpenWidget::slotUpdateTypeLabel () +{ + QString text = ::nameForType (type ()); + +#if DEBUG_KP_EFFECT_BLUR_SHARPEN + kdDebug () << "kpEffectBlurSharpenWidget::slotUpdateTypeLabel() text=" + << text << endl; +#endif + m_typeLabel->setText (text); +} + + +// protected +kpEffectBlurSharpenCommand::Type kpEffectBlurSharpenWidget::type () const +{ + if (m_amountInput->value () == 0) + return kpEffectBlurSharpenCommand::None; + else if (m_amountInput->value () < 0) + return kpEffectBlurSharpenCommand::Blur; + else + return kpEffectBlurSharpenCommand::Sharpen; +} + +// The numbers that follow were picked by experimentation. +// I still have no idea what "radius" and "sigma" mean +// (even after reading the API). + +// protected +double kpEffectBlurSharpenWidget::radius () const +{ + if (m_amountInput->value () == 0) + return 0; + + if (m_amountInput->value () < 0) + { + return 8; + } + else + { + const double SharpenMin = .1; + const double SharpenMax = 2.5; + + return SharpenMin + + (m_amountInput->value () - 1) * + (SharpenMax - SharpenMin) / + (m_amountInput->maxValue () - 1); + } +} + +// protected +double kpEffectBlurSharpenWidget::sigma () const +{ + if (m_amountInput->value () == 0) + return 0; + + if (m_amountInput->value () < 0) + { + const double BlurMin = .5; + const double BlurMax = 4; + + return BlurMin + + (-m_amountInput->value () - 1) * + (BlurMax - BlurMin) / + (-m_amountInput->minValue () - 1); + } + else + { + const double SharpenMin = .5; + const double SharpenMax = 3.0; + + return SharpenMin + + (m_amountInput->value () - 1) * + (SharpenMax - SharpenMin) / + (m_amountInput->maxValue () - 1); + } +} + +// protected +int kpEffectBlurSharpenWidget::repeat () const +{ + if (m_amountInput->value () == 0) + return 0; + + if (m_amountInput->value () < 0) + return 1; + else + { + const double SharpenMin = 1; + const double SharpenMax = 2; + + return qRound (SharpenMin + + (m_amountInput->value () - 1) * + (SharpenMax - SharpenMin) / + (m_amountInput->maxValue () - 1)); + } +} + +#include <kpeffectblursharpen.moc> diff --git a/kolourpaint/pixmapfx/kpeffectblursharpen.h b/kolourpaint/pixmapfx/kpeffectblursharpen.h new file mode 100644 index 00000000..3b12def1 --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectblursharpen.h @@ -0,0 +1,105 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <[email protected]> + 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_EFFECT_BLUR_SHARPEN_H +#define KP_EFFECT_BLUR_SHARPEN_H + + +#include <kpcolor.h> + +#include <kpcoloreffect.h> + + +class QLabel; + +class KIntNumInput; + +class kpMainWindow; + + +class kpEffectBlurSharpenCommand : public kpColorEffectCommand +{ +public: + enum Type + { + None = 0, Blur, Sharpen + }; + + kpEffectBlurSharpenCommand (Type type, + double radius, double sigma, + int repeat, + bool actOnSelection, + kpMainWindow *mainWindow); + virtual ~kpEffectBlurSharpenCommand (); + + static QPixmap apply (const QPixmap &pixmap, + Type type, double radius, double sigma, + int repeat); + +protected: + virtual QPixmap applyColorEffect (const QPixmap &pixmap); + +protected: + Type m_type; + double m_radius, m_sigma; + int m_repeat; +}; + + +class kpEffectBlurSharpenWidget : public kpColorEffectWidget +{ +Q_OBJECT + +public: + kpEffectBlurSharpenWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, const char *name = 0); + virtual ~kpEffectBlurSharpenWidget (); + + virtual QString caption () const; + + virtual bool isNoOp () const; + virtual QPixmap applyColorEffect (const QPixmap &pixmap); + + virtual kpColorEffectCommand *createCommand () const; + +protected slots: + void slotUpdateTypeLabel (); + +protected: + kpEffectBlurSharpenCommand::Type type () const; + double radius () const; + double sigma () const; + int repeat () const; + + KIntNumInput *m_amountInput; + QLabel *m_typeLabel; +}; + + +#endif // KP_EFFECT_BLUR_SHARPEN_H diff --git a/kolourpaint/pixmapfx/kpeffectemboss.cpp b/kolourpaint/pixmapfx/kpeffectemboss.cpp new file mode 100644 index 00000000..e33f3a42 --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectemboss.cpp @@ -0,0 +1,228 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <[email protected]> + 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_EMBOSS 0 + + +#include <kpeffectemboss.h> + +#include <qcheckbox.h> +#include <qimage.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qpixmap.h> +#include <qpushbutton.h> + +#include <kdebug.h> +#include <kimageeffect.h> +#include <klocale.h> +#include <knuminput.h> + +#include <kpmainwindow.h> +#include <kppixmapfx.h> + + +kpEffectEmbossCommand::kpEffectEmbossCommand (double radius, double sigma, + int repeat, + bool actOnSelection, + kpMainWindow *mainWindow) + : kpColorEffectCommand (i18n ("Emboss"), actOnSelection, mainWindow), + m_radius (radius), m_sigma (sigma), + m_repeat (repeat) +{ +} + +kpEffectEmbossCommand::~kpEffectEmbossCommand () +{ +} + + +// public static +QPixmap kpEffectEmbossCommand::apply (const QPixmap &pixmap, + double radius, double sigma, + int repeat) +{ +#if DEBUG_KP_EFFECT_EMBOSS + kdDebug () << "kpEffectEmbossCommand::apply()" + << " radius=" << radius + << " sigma=" << sigma + << " repeat=" << repeat + << ")" + << endl; +#endif + + // (KImageEffect::emboss() ignores mask) + QPixmap usePixmap = kpPixmapFX::pixmapWithDefinedTransparentPixels ( + pixmap, + Qt::white/*arbitrarily chosen*/); + + + QImage image = kpPixmapFX::convertToImage (usePixmap); + + for (int i = 0; i < repeat; i++) + { + image = KImageEffect::emboss (image, radius, sigma); + } + + QPixmap retPixmap = kpPixmapFX::convertToPixmap (image); + + + // KImageEffect::emboss() nukes mask - restore it + if (usePixmap.mask ()) + retPixmap.setMask (*usePixmap.mask ()); + + + return retPixmap; +} + +// protected virtual [base kpColorEffectCommand] +QPixmap kpEffectEmbossCommand::applyColorEffect (const QPixmap &pixmap) +{ + return apply (pixmap, m_radius, m_sigma, m_repeat); +} + + + +kpEffectEmbossWidget::kpEffectEmbossWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, const char *name) + : kpColorEffectWidget (actOnSelection, mainWindow, parent, name) +{ + QGridLayout *lay = new QGridLayout (this, 4, 2, marginHint (), spacingHint ()); + + +#if 0 + QLabel *amountLabel = new QLabel (i18n ("&Amount:"), this); + m_amountInput = new KIntNumInput (this); + m_amountInput->setRange (0, 10, 1/*step*/, true/*slider*/); + m_amountInput->setSpecialValueText (i18n ("None")); + + + amountLabel->setBuddy (m_amountInput); + + + lay->addWidget (amountLabel, 0, 0); + lay->addWidget (m_amountInput, 0, 1); + + lay->setColStretch (1, 1); + + + connect (m_amountInput, SIGNAL (valueChanged (int)), + this, SIGNAL (settingsChanged ())); +#endif + + m_enableCheckBox = new QCheckBox (i18n ("E&nable"), this); + + + lay->addMultiCellWidget (m_enableCheckBox, 0, 0, 0, 1, Qt::AlignCenter); + + + // (settingsChangedDelayed() instead of settingsChanged() so that the + // user can quickly press OK to apply effect to document directly and + // not have to wait for the also slow preview) + connect (m_enableCheckBox, SIGNAL (toggled (bool)), + this, SIGNAL (settingsChangedDelayed ())); +} + +kpEffectEmbossWidget::~kpEffectEmbossWidget () +{ +} + + +// public virtual [base kpColorEffectWidget] +QString kpEffectEmbossWidget::caption () const +{ + return QString::null; +} + + +// public virtual [base kpColorEffectWidget] +bool kpEffectEmbossWidget::isNoOp () const +{ + //return (m_amountInput->value () == 0); + return !m_enableCheckBox->isChecked (); +} + +// public virtual [base kpColorEffectWidget] +QPixmap kpEffectEmbossWidget::applyColorEffect (const QPixmap &pixmap) +{ + if (isNoOp ()) + return pixmap; + + return kpEffectEmbossCommand::apply (pixmap, radius (), sigma (), repeat ()); +} + +// public virtual [base kpColorEffectWidget] +kpColorEffectCommand *kpEffectEmbossWidget::createCommand () const +{ + return new kpEffectEmbossCommand (radius (), sigma (), repeat (), + m_actOnSelection, + m_mainWindow); +} + + +// The numbers that follow were picked by experimentation. +// I still have no idea what "radius" and "sigma" mean +// (even after reading the API). + +// protected +double kpEffectEmbossWidget::radius () const +{ + //if (m_amountInput->value () == 0) + // return 0; + + return 0; +} + + +// protected +double kpEffectEmbossWidget::sigma () const +{ +#if 0 + if (m_amountInput->value () == 0) + return 0; + + const double Min = 1; + const double Max = 1.2; + + return Min + + (m_amountInput->maxValue () - m_amountInput->value ()) * + (Max - Min) / + (m_amountInput->maxValue () - 1); +#endif + + return 1; +} + +// protected +int kpEffectEmbossWidget::repeat () const +{ + return 1; +} + + +#include <kpeffectemboss.moc> diff --git a/kolourpaint/pixmapfx/kpeffectemboss.h b/kolourpaint/pixmapfx/kpeffectemboss.h new file mode 100644 index 00000000..0234627f --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectemboss.h @@ -0,0 +1,93 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <[email protected]> + 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_EFFECT_EMBOSS_H +#define KP_EFFECT_EMBOSS_H + + +#include <kpcolor.h> + +#include <kpcoloreffect.h> + + +class QCheckBox; +class KIntNumInput; + +class kpMainWindow; + + +class kpEffectEmbossCommand : public kpColorEffectCommand +{ +public: + kpEffectEmbossCommand (double radius, double sigma, + int repeat, + bool actOnSelection, + kpMainWindow *mainWindow); + virtual ~kpEffectEmbossCommand (); + + static QPixmap apply (const QPixmap &pixmap, + double radius, double sigma, + int repeat); + +protected: + virtual QPixmap applyColorEffect (const QPixmap &pixmap); + +protected: + double m_radius, m_sigma; + int m_repeat; +}; + + +class kpEffectEmbossWidget : public kpColorEffectWidget +{ +Q_OBJECT + +public: + kpEffectEmbossWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, const char *name = 0); + virtual ~kpEffectEmbossWidget (); + + virtual QString caption () const; + + virtual bool isNoOp () const; + virtual QPixmap applyColorEffect (const QPixmap &pixmap); + + virtual kpColorEffectCommand *createCommand () const; + +protected: + double radius () const; + double sigma () const; + int repeat () const; + + //KIntNumInput *m_amountInput; + QCheckBox *m_enableCheckBox; +}; + + +#endif // KP_EFFECT_EMBOSS_H diff --git a/kolourpaint/pixmapfx/kpeffectflatten.cpp b/kolourpaint/pixmapfx/kpeffectflatten.cpp new file mode 100644 index 00000000..6a81bca0 --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectflatten.cpp @@ -0,0 +1,266 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <[email protected]> + 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_FLATTEN 0 + + +#include <kpeffectflatten.h> + +#include <qcheckbox.h> +#include <qimage.h> +#include <qlayout.h> +#include <qpixmap.h> +#include <qvbox.h> + +#include <kcolorbutton.h> +#include <kconfig.h> +#include <kdialog.h> +#include <kdebug.h> +#include <kglobal.h> +#include <kimageeffect.h> +#include <klocale.h> + +#include <kpdefs.h> +#include <kppixmapfx.h> + + +// +// kpEffectFlattenCommand +// + +kpEffectFlattenCommand::kpEffectFlattenCommand (const QColor &color1, + const QColor &color2, + bool actOnSelection, + kpMainWindow *mainWindow) + : kpColorEffectCommand (i18n ("Flatten"), actOnSelection, mainWindow), + m_color1 (color1), m_color2 (color2) +{ +} + +kpEffectFlattenCommand::~kpEffectFlattenCommand () +{ +} + + +// public static +void kpEffectFlattenCommand::apply (QPixmap *destPixmapPtr, + const QColor &color1, const QColor &color2) +{ + if (!destPixmapPtr) + return; + + QImage image = kpPixmapFX::convertToImage (*destPixmapPtr); + apply (&image, color1, color2); + *destPixmapPtr = kpPixmapFX::convertToPixmap (image); +} + +// public static +QPixmap kpEffectFlattenCommand::apply (const QPixmap &pm, + const QColor &color1, const QColor &color2) +{ + QImage image = kpPixmapFX::convertToImage (pm); + apply (&image, color1, color2); + return kpPixmapFX::convertToPixmap (image); +} + +// public static +void kpEffectFlattenCommand::apply (QImage *destImagePtr, + const QColor &color1, const QColor &color2) +{ + if (!destImagePtr) + return; + + KImageEffect::flatten (*destImagePtr/*ref*/, color1, color2); +} + +// public static +QImage kpEffectFlattenCommand::apply (const QImage &img, + const QColor &color1, const QColor &color2) +{ + QImage retImage = img; + apply (&retImage, color1, color2); + return retImage; +} + + +// +// kpEffectFlattenCommand implements kpColorEffectCommand interface +// + +// protected virtual [base kpColorEffectCommand] +QPixmap kpEffectFlattenCommand::applyColorEffect (const QPixmap &pixmap) +{ + return apply (pixmap, m_color1, m_color2); +} + + +// +// kpEffectFlattenWidget +// + +// public static +// Don't initialise globally when we probably don't have a colour +// allocation context. This way, the colours aren't sometimes invalid +// (e.g. at 8-bit). +QColor kpEffectFlattenWidget::s_lastColor1; +QColor kpEffectFlattenWidget::s_lastColor2; + +kpEffectFlattenWidget::kpEffectFlattenWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, + const char *name) + : kpColorEffectWidget (actOnSelection, mainWindow, parent, name) +{ + if (!s_lastColor1.isValid () || !s_lastColor2.isValid ()) + { + KConfigGroupSaver cfgGroupSaver (KGlobal::config (), kpSettingsGroupFlattenEffect); + KConfigBase *cfg = cfgGroupSaver.config (); + + s_lastColor1 = cfg->readColorEntry (kpSettingFlattenEffectColor1); + if (!s_lastColor1.isValid ()) + s_lastColor1 = Qt::red; + + s_lastColor2 = cfg->readColorEntry (kpSettingFlattenEffectColor2); + if (!s_lastColor2.isValid ()) + s_lastColor2 = Qt::blue; + } + + + m_enableCheckBox = new QCheckBox (i18n ("E&nable"), this); + + QVBox *colorButtonContainer = new QVBox (this); + colorButtonContainer->setMargin (KDialog::marginHint () / 2); + colorButtonContainer->setSpacing (spacingHint ()); + m_color1Button = new KColorButton (s_lastColor1, colorButtonContainer); + m_color2Button = new KColorButton (s_lastColor2, colorButtonContainer); + + + m_color1Button->setEnabled (false); + m_color2Button->setEnabled (false); + + + QVBoxLayout *lay = new QVBoxLayout (this, marginHint (), spacingHint ()); + lay->addWidget (m_enableCheckBox); + lay->addWidget (colorButtonContainer); + + + connect (m_enableCheckBox, SIGNAL (toggled (bool)), + this, SLOT (slotEnableChanged (bool))); + + connect (m_color1Button, SIGNAL (changed (const QColor &)), + this, SIGNAL (settingsChanged ())); + connect (m_color2Button, SIGNAL (changed (const QColor &)), + this, SIGNAL (settingsChanged ())); +} + +kpEffectFlattenWidget::~kpEffectFlattenWidget () +{ + s_lastColor1 = color1 (); + s_lastColor2 = color2 (); + + + KConfigGroupSaver cfgGroupSaver (KGlobal::config (), kpSettingsGroupFlattenEffect); + KConfigBase *cfg = cfgGroupSaver.config (); + + cfg->writeEntry (kpSettingFlattenEffectColor1, s_lastColor1); + cfg->writeEntry (kpSettingFlattenEffectColor2, s_lastColor2); + cfg->sync (); +} + + +// public +QColor kpEffectFlattenWidget::color1 () const +{ + return m_color1Button->color (); +} + +// public +QColor kpEffectFlattenWidget::color2 () const +{ + return m_color2Button->color (); +} + + +// +// kpEffectFlattenWidget implements kpColorEffectWidget interface +// + +// public virtual [base kpColorEffectWidget] +QString kpEffectFlattenWidget::caption () const +{ + return i18n ("Colors"); +} + + +// public virtual [base kpColorEffectWidget] +bool kpEffectFlattenWidget::isNoOp () const +{ + return !m_enableCheckBox->isChecked (); +} + +// public virtual [base kpColorEffectWidget] +QPixmap kpEffectFlattenWidget::applyColorEffect (const QPixmap &pixmap) +{ +#if DEBUG_KP_EFFECT_FLATTEN + kdDebug () << "kpEffectFlattenWidget::applyColorEffect() nop=" + << isNoOp () << endl; +#endif + + if (isNoOp ()) + return pixmap; + + return kpEffectFlattenCommand::apply (pixmap, color1 (), color2 ()); +} + + +// public virtual [base kpColorEffectWidget] +kpColorEffectCommand *kpEffectFlattenWidget::createCommand () const +{ + return new kpEffectFlattenCommand (color1 (), color2 (), + m_actOnSelection, + m_mainWindow); +} + + +// protected slot: +void kpEffectFlattenWidget::slotEnableChanged (bool enable) +{ +#if DEBUG_KP_EFFECT_FLATTEN + kdDebug () << "kpEffectFlattenWidget::slotEnableChanged(" << enable + << ") enableButton=" << m_enableCheckBox->isChecked () + << endl; +#endif + + m_color1Button->setEnabled (enable); + m_color2Button->setEnabled (enable); + + emit settingsChanged (); +} + + +#include <kpeffectflatten.moc> + diff --git a/kolourpaint/pixmapfx/kpeffectflatten.h b/kolourpaint/pixmapfx/kpeffectflatten.h new file mode 100644 index 00000000..79c9bbaf --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectflatten.h @@ -0,0 +1,115 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <[email protected]> + 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_EFFECT_FLATTEN_H +#define KP_EFFECT_FLATTEN_H + + +#include <qcolor.h> + +#include <kpcoloreffect.h> + + +class QCheckBox; +class QImage; + +class KColorButton; + +class kpMainWindow; + + +class kpEffectFlattenCommand : public kpColorEffectCommand +{ +public: + kpEffectFlattenCommand (const QColor &color1, const QColor &color2, + bool actOnSelection, + kpMainWindow *mainWindow); + virtual ~kpEffectFlattenCommand (); + + + static void apply (QPixmap *destPixmapPtr, + const QColor &color1, const QColor &color2); + static QPixmap apply (const QPixmap &pm, + const QColor &color1, const QColor &color2); + static void apply (QImage *destImagePtr, + const QColor &color1, const QColor &color2); + static QImage apply (const QImage &img, + const QColor &color1, const QColor &color2); + + + // + // kpColorEffectCommand interface + // + +protected: + virtual QPixmap applyColorEffect (const QPixmap &pixmap); + + QColor m_color1, m_color2; +}; + + +class kpEffectFlattenWidget : public kpColorEffectWidget +{ +Q_OBJECT + +public: + kpEffectFlattenWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, const char *name = 0); + virtual ~kpEffectFlattenWidget (); + + + static QColor s_lastColor1, s_lastColor2; + + + QColor color1 () const; + QColor color2 () const; + + + // + // kpColorEffectWidget interface + // + + virtual QString caption () const; + + virtual bool isNoOp () const; + virtual QPixmap applyColorEffect (const QPixmap &pixmap); + + virtual kpColorEffectCommand *createCommand () const; + +protected slots: + void slotEnableChanged (bool enable); + +protected: + QCheckBox *m_enableCheckBox; + KColorButton *m_color1Button, *m_color2Button; +}; + + + +#endif // KP_EFFECT_FLATTEN_H diff --git a/kolourpaint/pixmapfx/kpeffectinvert.cpp b/kolourpaint/pixmapfx/kpeffectinvert.cpp new file mode 100644 index 00000000..b9bb00a8 --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectinvert.cpp @@ -0,0 +1,315 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <[email protected]> + 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_INVERT 0 + + +#include <kpeffectinvert.h> + +#include <qcheckbox.h> +#include <qimage.h> +#include <qlayout.h> +#include <qpixmap.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <kppixmapfx.h> + + +// +// kpEffectInvertCommand +// + +kpEffectInvertCommand::kpEffectInvertCommand (int channels, + bool actOnSelection, + kpMainWindow *mainWindow) + : kpColorEffectCommand (channels == RGB ? + i18n ("Invert Colors") : i18n ("Invert"), + actOnSelection, mainWindow), + m_channels (channels) +{ +} + +kpEffectInvertCommand::kpEffectInvertCommand (bool actOnSelection, + kpMainWindow *mainWindow) + : kpColorEffectCommand (i18n ("Invert Colors"), actOnSelection, mainWindow), + m_channels (RGB) +{ +} + +kpEffectInvertCommand::~kpEffectInvertCommand () +{ +} + + +// public static +void kpEffectInvertCommand::apply (QPixmap *destPixmapPtr, int channels) +{ + QImage image = kpPixmapFX::convertToImage (*destPixmapPtr); + apply (&image, channels); + *destPixmapPtr = kpPixmapFX::convertToPixmap (image); +} + +// public static +QPixmap kpEffectInvertCommand::apply (const QPixmap &pm, int channels) +{ + QImage image = kpPixmapFX::convertToImage (pm); + apply (&image, channels); + return kpPixmapFX::convertToPixmap (image); +} + +// public static +void kpEffectInvertCommand::apply (QImage *destImagePtr, int channels) +{ + QRgb mask = qRgba ((channels & Red) ? 0xFF : 0, + (channels & Green) ? 0xFF : 0, + (channels & Blue) ? 0xFF : 0, + 0/*don't invert alpha*/); +#if DEBUG_KP_EFFECT_INVERT + kdDebug () << "kpEffectInvertCommand::apply(channels=" << channels + << ") mask=" << (int *) mask + << endl; +#endif + + if (destImagePtr->depth () > 8) + { + #if 0 + // SYNC: TODO: Qt BUG - invertAlpha argument is inverted!!! + destImagePtr->invertPixels (true/*no invert alpha (Qt 3.2)*/); + #else + // Above version works for Qt 3.2 at least. + // But this version will always work (slower, though) and supports + // inverting particular channels. + for (int y = 0; y < destImagePtr->height (); y++) + { + for (int x = 0; x < destImagePtr->width (); x++) + { + destImagePtr->setPixel (x, y, destImagePtr->pixel (x, y) ^ mask); + } + } + #endif + } + else + { + for (int i = 0; i < destImagePtr->numColors (); i++) + { + destImagePtr->setColor (i, destImagePtr->color (i) ^ mask); + } + } +} + +// public static +QImage kpEffectInvertCommand::apply (const QImage &img, int channels) +{ + QImage retImage = img; + apply (&retImage, channels); + return retImage; +} + + +// +// kpEffectInvertCommand implements kpColorEffectCommand interface +// + +// protected virtual [base kpColorEffectCommand] +QPixmap kpEffectInvertCommand::applyColorEffect (const QPixmap &pixmap) +{ + return apply (pixmap, m_channels); +} + + +// +// kpEffectInvertWidget +// + +kpEffectInvertWidget::kpEffectInvertWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, + const char *name) + : kpColorEffectWidget (actOnSelection, mainWindow, parent, name) +{ + QVBoxLayout *topLevelLay = new QVBoxLayout (this, marginHint (), spacingHint ()); + + + QWidget *centerWidget = new QWidget (this); + topLevelLay->addWidget (centerWidget, 0/*stretch*/, Qt::AlignCenter); + + + QVBoxLayout *centerWidgetLay = new QVBoxLayout (centerWidget, + 0/*margin*/, + spacingHint ()); + + + m_redCheckBox = new QCheckBox (i18n ("&Red"), centerWidget); + m_greenCheckBox = new QCheckBox (i18n ("&Green"), centerWidget); + m_blueCheckBox = new QCheckBox (i18n ("&Blue"), centerWidget); + + QWidget *spaceWidget = new QWidget (centerWidget); + spaceWidget->setFixedSize (1, spacingHint ()); + + m_allCheckBox = new QCheckBox (i18n ("&All"), centerWidget); + + + m_redCheckBox->setChecked (false); + m_greenCheckBox->setChecked (false); + m_blueCheckBox->setChecked (false); + + m_allCheckBox->setChecked (false); + + + centerWidgetLay->addWidget (m_redCheckBox); + centerWidgetLay->addWidget (m_greenCheckBox); + centerWidgetLay->addWidget (m_blueCheckBox); + + centerWidgetLay->addWidget (spaceWidget); + + centerWidgetLay->addWidget (m_allCheckBox); + + + m_inSignalHandler = false; + connect (m_redCheckBox, SIGNAL (toggled (bool)), + this, SLOT (slotRGBCheckBoxToggled ())); + connect (m_greenCheckBox, SIGNAL (toggled (bool)), + this, SLOT (slotRGBCheckBoxToggled ())); + connect (m_blueCheckBox, SIGNAL (toggled (bool)), + this, SLOT (slotRGBCheckBoxToggled ())); + + connect (m_allCheckBox, SIGNAL (toggled (bool)), + this, SLOT (slotAllCheckBoxToggled ())); +} + +kpEffectInvertWidget::~kpEffectInvertWidget () +{ +} + + +// public +int kpEffectInvertWidget::channels () const +{ +#if DEBUG_KP_EFFECT_INVERT + kdDebug () << "kpEffectInvertWidget::channels()" + << " isChecked: r=" << m_redCheckBox->isChecked () + << " g=" << m_greenCheckBox->isChecked () + << " b=" << m_blueCheckBox->isChecked () + << endl; +#endif + + int channels = 0; + + + if (m_redCheckBox->isChecked ()) + channels |= kpEffectInvertCommand::Red; + + if (m_greenCheckBox->isChecked ()) + channels |= kpEffectInvertCommand::Green; + + if (m_blueCheckBox->isChecked ()) + channels |= kpEffectInvertCommand::Blue; + + +#if DEBUG_KP_EFFECT_INVERT + kdDebug () << "\treturning channels=" << (int *) channels << endl; +#endif + return channels; +} + + +// +// kpEffectInvertWidget implements kpColorEffectWidget interface +// + +// public virtual [base kpColorEffectWidget] +QString kpEffectInvertWidget::caption () const +{ + return i18n ("Channels"); +} + + +// public virtual [base kpColorEffectWidget] +bool kpEffectInvertWidget::isNoOp () const +{ + return (channels () == kpEffectInvertCommand::None); +} + +// public virtual [base kpColorEffectWidget] +QPixmap kpEffectInvertWidget::applyColorEffect (const QPixmap &pixmap) +{ + return kpEffectInvertCommand::apply (pixmap, channels ()); +} + + +// public virtual [base kpColorEffectWidget] +kpColorEffectCommand *kpEffectInvertWidget::createCommand () const +{ + return new kpEffectInvertCommand (channels (), + m_actOnSelection, + m_mainWindow); +} + + +// protected slots +void kpEffectInvertWidget::slotRGBCheckBoxToggled () +{ + if (m_inSignalHandler) + return; + + m_inSignalHandler = true; + + //blockSignals (true); + m_allCheckBox->setChecked (m_redCheckBox->isChecked () && + m_blueCheckBox->isChecked () && + m_greenCheckBox->isChecked ()); + //blockSignals (false); + + emit settingsChanged (); + + m_inSignalHandler = false; +} + +// protected slot +void kpEffectInvertWidget::slotAllCheckBoxToggled () +{ + if (m_inSignalHandler) + return; + + m_inSignalHandler = true; + + //blockSignals (true); + m_redCheckBox->setChecked (m_allCheckBox->isChecked ()); + m_greenCheckBox->setChecked (m_allCheckBox->isChecked ()); + m_blueCheckBox->setChecked (m_allCheckBox->isChecked ()); + //blockSignals (false); + + emit settingsChanged (); + + m_inSignalHandler = false; +} + + +#include <kpeffectinvert.moc> + diff --git a/kolourpaint/pixmapfx/kpeffectinvert.h b/kolourpaint/pixmapfx/kpeffectinvert.h new file mode 100644 index 00000000..61d6cfda --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectinvert.h @@ -0,0 +1,130 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <[email protected]> + 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_EFFECT_INVERT_H +#define KP_EFFECT_INVERT_H + + +#include <kpcoloreffect.h> + + +class QCheckBox; +class QImage; + +class kpMainWindow; + + +class kpEffectInvertCommand : public kpColorEffectCommand +{ +public: + enum Channel + { + None = 0, + Red = 1, Green = 2, Blue = 4, + RGB = Red | Green | Blue + }; + + kpEffectInvertCommand (int channels, + bool actOnSelection, + kpMainWindow *mainWindow); + kpEffectInvertCommand (bool actOnSelection, + kpMainWindow *mainWindow); + virtual ~kpEffectInvertCommand (); + + + // + // Inverts the colours of each pixel in the given image. + // These functions differ from QImage::invertPixels() in the following ways: + // + // 1. for 8-bit images, it inverts the colours of the Colour Table + // (this means that you would get visually similar results to inversion + // at higher bit depths - rather than a "random-looking" inversion + // depending on the contents of the Colour Table) + // 2. never inverts the Alpha Buffer + // + + static void apply (QPixmap *destPixmapPtr, int channels = RGB); + static QPixmap apply (const QPixmap &pm, int channels = RGB); + static void apply (QImage *destImagePtr, int channels = RGB); + static QImage apply (const QImage &img, int channels = RGB); + + + // + // kpColorEffectCommand interface + // + +public: + virtual bool isInvertible () const { return true; } + +protected: + virtual QPixmap applyColorEffect (const QPixmap &pixmap); + + int m_channels; +}; + + +class kpEffectInvertWidget : public kpColorEffectWidget +{ +Q_OBJECT + +public: + kpEffectInvertWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, const char *name = 0); + virtual ~kpEffectInvertWidget (); + + + int channels () const; + + + // + // kpColorEffectWidget interface + // + + virtual QString caption () const; + + virtual bool isNoOp () const; + virtual QPixmap applyColorEffect (const QPixmap &pixmap); + + virtual kpColorEffectCommand *createCommand () const; + +protected slots: + void slotRGBCheckBoxToggled (); + void slotAllCheckBoxToggled (); + +protected: + QCheckBox *m_redCheckBox, *m_greenCheckBox, *m_blueCheckBox, + *m_allCheckBox; + + // blockSignals() didn't seem to work + bool m_inSignalHandler; +}; + + + +#endif // KP_EFFECT_INVERT_H diff --git a/kolourpaint/pixmapfx/kpeffectreducecolors.cpp b/kolourpaint/pixmapfx/kpeffectreducecolors.cpp new file mode 100644 index 00000000..b6eb7a42 --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectreducecolors.cpp @@ -0,0 +1,446 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <[email protected]> + 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_REDUCE_COLORS 0 + + +#include <kpeffectreducecolors.h> + +#include <qbuttongroup.h> +#include <qcheckbox.h> +#include <qimage.h> +#include <qlayout.h> +#include <qpixmap.h> +#include <qradiobutton.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <kppixmapfx.h> + + +QImage convertImageDepth (const QImage &image, int depth, bool dither) +{ +#if DEBUG_KP_EFFECT_REDUCE_COLORS + kdDebug () << "::convertImageDepth() changing image (w=" << image.width () + << ",h=" << image.height () + << ") depth from " << image.depth () + << " to " << depth + << " (dither=" << dither << ")" + << endl; +#endif + + if (image.isNull ()) + return image; + + if (depth == image.depth ()) + return image; + + +#if DEBUG_KP_EFFECT_REDUCE_COLORS && 0 + for (int y = 0; y < image.height (); y++) + { + for (int x = 0; x < image.width (); x++) + { + fprintf (stderr, " %08X", image.pixel (x, y)); + } + fprintf (stderr, "\n"); + } +#endif + + + // Hack around Qt's braindead QImage::convertDepth(1, ...) (with + // dithering off) which produces pathetic results with an image that + // only has 2 colours - sometimes it just gives a completely black + // result. Instead, we simply preserve the 2 colours. One use case + // is resaving a "colour monochrome" image (<= 2 colours but not + // necessarily black & white). + if (depth == 1 && !dither) + { + #if DEBUG_KP_EFFECT_REDUCE_COLORS + kdDebug () << "\tinvoking convert-to-depth 1 hack" << endl; + #endif + QRgb color0, color1; + bool color0Valid = false, color1Valid = false; + + bool moreThan2Colors = false; + + QImage monoImage (image.width (), image.height (), + 1/*depth*/, 2/*numColors*/, QImage::LittleEndian); + #if DEBUG_KP_EFFECT_REDUCE_COLORS + kdDebug () << "\t\tinitialising output image w=" << monoImage.width () + << ",h=" << monoImage.height () + << ",d=" << monoImage.depth () + << endl; + #endif + for (int y = 0; y < image.height (); y++) + { + for (int x = 0; x < image.width (); x++) + { + QRgb imagePixel = image.pixel (x, y); + + if (color0Valid && imagePixel == color0) + monoImage.setPixel (x, y, 0); + else if (color1Valid && imagePixel == color1) + monoImage.setPixel (x, y, 1); + else if (!color0Valid) + { + color0 = imagePixel; + color0Valid = true; + monoImage.setPixel (x, y, 0); + #if DEBUG_KP_EFFECT_REDUCE_COLORS + kdDebug () << "\t\t\tcolor0=" << (int *) color0 + << " at x=" << x << ",y=" << y << endl; + #endif + } + else if (!color1Valid) + { + color1 = imagePixel; + color1Valid = true; + monoImage.setPixel (x, y, 1); + #if DEBUG_KP_EFFECT_REDUCE_COLORS + kdDebug () << "\t\t\tcolor1=" << (int *) color1 + << " at x=" << x << ",y=" << y << endl; + #endif + } + else + { + #if DEBUG_KP_EFFECT_REDUCE_COLORS + kdDebug () << "\t\t\timagePixel=" << (int *) imagePixel + << " at x=" << x << ",y=" << y + << " moreThan2Colors - abort hack" << endl; + #endif + moreThan2Colors = true; + + // Dijkstra, this is clearer than double break'ing or + // a check in both loops + goto exit_loop; + } + } + } + exit_loop: + + if (!moreThan2Colors) + { + monoImage.setColor (0, color0Valid ? color0 : 0xFFFFFF); + monoImage.setColor (1, color1Valid ? color1 : 0x000000); + return monoImage; + } + } + + + QImage retImage = image.convertDepth (depth, + Qt::AutoColor | + (dither ? Qt::DiffuseDither : Qt::ThresholdDither) | + Qt::ThresholdAlphaDither | + (dither ? Qt::PreferDither : Qt::AvoidDither)); + +#if DEBUG_KP_EFFECT_REDUCE_COLORS && 0 + kdDebug () << "After colour reduction:" << endl; + for (int y = 0; y < image.height (); y++) + { + for (int x = 0; x < image.width (); x++) + { + fprintf (stderr, " %08X", image.pixel (x, y)); + } + fprintf (stderr, "\n"); + } +#endif + + return retImage; +} + + +// +// kpEffectReduceColorsCommand +// + +kpEffectReduceColorsCommand::kpEffectReduceColorsCommand (int depth, bool dither, + bool actOnSelection, + kpMainWindow *mainWindow) + : kpColorEffectCommand (commandName (depth, dither), actOnSelection, mainWindow), + m_depth (depth), m_dither (dither) +{ +} + +kpEffectReduceColorsCommand::~kpEffectReduceColorsCommand () +{ +} + + +// public +QString kpEffectReduceColorsCommand::commandName (int depth, int dither) const +{ + if (depth == 1) + { + if (dither) + return i18n ("Reduce to Monochrome (Dithered)"); + else + return i18n ("Reduce to Monochrome"); + } + else if (depth == 8) + { + if (dither) + return i18n ("Reduce to 256 Color (Dithered)"); + else + return i18n ("Reduce to 256 Color"); + } + else + { + return QString::null; + } +} + + +// public static +void kpEffectReduceColorsCommand::apply (QPixmap *destPixmapPtr, int depth, bool dither) +{ + if (!destPixmapPtr) + return; + + if (depth != 1 && depth != 8) + return; + + + QImage image = kpPixmapFX::convertToImage (*destPixmapPtr); + + + image = ::convertImageDepth (image, depth, dither); + + if (image.isNull ()) + return; + + + QPixmap pixmap = kpPixmapFX::convertToPixmap (image, false/*no dither*/); + + + // HACK: The above "image.convertDepth()" erases the Alpha Channel + // (at least for monochrome). + // qpixmap.html says "alpha masks on monochrome images are ignored." + // + // Put the mask back. + // + if (destPixmapPtr->mask ()) + pixmap.setMask (*destPixmapPtr->mask ()); + + *destPixmapPtr = pixmap; +} + +// public static +QPixmap kpEffectReduceColorsCommand::apply (const QPixmap &pm, int depth, bool dither) +{ + QPixmap ret = pm; + apply (&ret, depth, dither); + return ret; +} + + +// +// kpEffectReduceColorsCommand implements kpColorEffectCommand interface +// + +// protected virtual [base kpColorEffectCommand] +QPixmap kpEffectReduceColorsCommand::applyColorEffect (const QPixmap &pixmap) +{ + return apply (pixmap, m_depth, m_dither); +} + + +// +// kpEffectReduceColorsWidget +// + +kpEffectReduceColorsWidget::kpEffectReduceColorsWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, + const char *name) + : kpColorEffectWidget (actOnSelection, mainWindow, parent, name) +{ + QVBoxLayout *lay = new QVBoxLayout (this, marginHint (), spacingHint ()); + + + m_blackAndWhiteRadioButton = + new QRadioButton (i18n ("&Monochrome"), this); + + m_blackAndWhiteDitheredRadioButton = + new QRadioButton (i18n ("Mo&nochrome (dithered)"), this); + + m_8BitRadioButton = new QRadioButton (i18n ("256 co&lor"), this); + + m_8BitDitheredRadioButton = new QRadioButton (i18n ("256 colo&r (dithered)"), this); + + m_24BitRadioButton = new QRadioButton (i18n ("24-&bit color"), this); + + + QButtonGroup *buttonGroup = new QButtonGroup (this); + buttonGroup->hide (); + + buttonGroup->insert (m_blackAndWhiteRadioButton); + buttonGroup->insert (m_blackAndWhiteDitheredRadioButton); + buttonGroup->insert (m_8BitRadioButton); + buttonGroup->insert (m_8BitDitheredRadioButton); + buttonGroup->insert (m_24BitRadioButton); + + + const int screenDepth = QPixmap::defaultDepth (); +#if DEBUG_KP_EFFECT_REDUCE_COLORS + kdDebug () << "kpEffectReduceColorsWidget::<ctor> screenDepth=" + << screenDepth + << endl; +#endif + + // Note that everything is disabled for a 1-bit screen since there + // would be no effect. I won't support 2-bit or 4-bit screens either :) + m_blackAndWhiteRadioButton->setEnabled (screenDepth >= 8); + m_blackAndWhiteDitheredRadioButton->setEnabled (screenDepth >= 8); + m_8BitRadioButton->setEnabled (screenDepth >= 8); + // (not enabled if screenDepth==8 as m_8BitRadioButton already serves + // as NOP default) + m_8BitDitheredRadioButton->setEnabled (screenDepth > 8); + // (not "screenDepth >= 24" as we need a NOP default for 15/16-bit + // screens) + m_24BitRadioButton->setEnabled (screenDepth > 8); + + + m_defaultRadioButton = 0; + + if (m_24BitRadioButton->isEnabled ()) + { + #if DEBUG_KP_EFFECT_REDUCE_COLORS + kdDebug () << "\tdefault is 24-bit button" << endl; + #endif + m_defaultRadioButton = m_24BitRadioButton; + } + else if (m_8BitRadioButton->isEnabled ()) + { + #if DEBUG_KP_EFFECT_REDUCE_COLORS + kdDebug () << "\tdefault is 8-bit button" << endl; + #endif + m_defaultRadioButton = m_8BitRadioButton; + } + else + { + #if DEBUG_KP_EFFECT_REDUCE_COLORS + kdDebug () << "\tuser must have a 1-bit screen - no default" << endl; + #endif + } + + + if (m_defaultRadioButton) + m_defaultRadioButton->setChecked (true); + + + lay->addWidget (m_blackAndWhiteRadioButton); + lay->addWidget (m_blackAndWhiteDitheredRadioButton); + lay->addWidget (m_8BitRadioButton); + lay->addWidget (m_8BitDitheredRadioButton); + lay->addWidget (m_24BitRadioButton); + + + connect (m_blackAndWhiteRadioButton, SIGNAL (toggled (bool)), + this, SIGNAL (settingsChanged ())); + connect (m_blackAndWhiteDitheredRadioButton, SIGNAL (toggled (bool)), + this, SIGNAL (settingsChanged ())); + connect (m_8BitRadioButton, SIGNAL (toggled (bool)), + this, SIGNAL (settingsChanged ())); + connect (m_8BitDitheredRadioButton, SIGNAL (toggled (bool)), + this, SIGNAL (settingsChanged ())); + connect (m_24BitRadioButton, SIGNAL (toggled (bool)), + this, SIGNAL (settingsChanged ())); +} + +kpEffectReduceColorsWidget::~kpEffectReduceColorsWidget () +{ +} + + +// public +int kpEffectReduceColorsWidget::depth () const +{ + if (m_blackAndWhiteRadioButton->isChecked () || + m_blackAndWhiteDitheredRadioButton->isChecked ()) + { + return 1; + } + else if (m_8BitRadioButton->isChecked () || + m_8BitDitheredRadioButton->isChecked ()) + { + return 8; + } + else if (m_24BitRadioButton->isChecked ()) + { + return 24; + } + else + { + return 0; + } +} + +// public +bool kpEffectReduceColorsWidget::dither () const +{ + return (m_blackAndWhiteDitheredRadioButton->isChecked () || + m_8BitDitheredRadioButton->isChecked ()); +} + + +// +// kpEffectReduceColorsWidget implements kpColorEffectWidget interface +// + +// public virtual [base kpColorEffectWidget] +QString kpEffectReduceColorsWidget::caption () const +{ + return i18n ("Reduce To"); +} + + +// public virtual [base kpColorEffectWidget] +bool kpEffectReduceColorsWidget::isNoOp () const +{ + return (!m_defaultRadioButton || m_defaultRadioButton->isChecked ()); +} + +// public virtual [base kpColorEffectWidget] +QPixmap kpEffectReduceColorsWidget::applyColorEffect (const QPixmap &pixmap) +{ + return kpEffectReduceColorsCommand::apply (pixmap, depth (), dither ()); +} + + +// public virtual [base kpColorEffectWidget] +kpColorEffectCommand *kpEffectReduceColorsWidget::createCommand () const +{ + return new kpEffectReduceColorsCommand (depth (), dither (), + m_actOnSelection, + m_mainWindow); +} + + +#include <kpeffectreducecolors.moc> + diff --git a/kolourpaint/pixmapfx/kpeffectreducecolors.h b/kolourpaint/pixmapfx/kpeffectreducecolors.h new file mode 100644 index 00000000..a14cffc7 --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectreducecolors.h @@ -0,0 +1,110 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <[email protected]> + 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_EFFECT_REDUCE_COLORS_H +#define KP_EFFECT_REDUCE_COLORS_H + + +#include <kpcoloreffect.h> + + +class QRadioButton; +class QImage; + +class kpMainWindow; + + +QImage convertImageDepth (const QImage &image, int depth, bool dither); + + +class kpEffectReduceColorsCommand : public kpColorEffectCommand +{ +public: + // depth must be 1 or 8 + kpEffectReduceColorsCommand (int depth, bool dither, + bool actOnSelection, + kpMainWindow *mainWindow); + virtual ~kpEffectReduceColorsCommand (); + + QString commandName (int depth, int dither) const; + + // (always preserves mask) + static void apply (QPixmap *destPixmapPtr, int depth, bool dither); + static QPixmap apply (const QPixmap &pm, int depth, bool dither); + + + // + // kpColorEffectCommand interface + // + +protected: + virtual QPixmap applyColorEffect (const QPixmap &pixmap); + + int m_depth; + bool m_dither; +}; + + +class kpEffectReduceColorsWidget : public kpColorEffectWidget +{ +Q_OBJECT + +public: + kpEffectReduceColorsWidget (bool actOnSelection, + kpMainWindow *mainWindow, + QWidget *parent, const char *name = 0); + virtual ~kpEffectReduceColorsWidget (); + + + int depth () const; + bool dither () const; + + + // + // kpColorEffectWidget interface + // + + virtual QString caption () const; + + virtual bool isNoOp () const; + virtual QPixmap applyColorEffect (const QPixmap &pixmap); + + virtual kpColorEffectCommand *createCommand () const; + +protected: + QRadioButton *m_blackAndWhiteRadioButton, + *m_blackAndWhiteDitheredRadioButton, + *m_8BitRadioButton, + *m_8BitDitheredRadioButton, + *m_24BitRadioButton; + QRadioButton *m_defaultRadioButton; +}; + + + +#endif // KP_EFFECT_REDUCE_COLORS_H diff --git a/kolourpaint/pixmapfx/kpeffectsdialog.cpp b/kolourpaint/pixmapfx/kpeffectsdialog.cpp new file mode 100644 index 00000000..666f81cf --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectsdialog.cpp @@ -0,0 +1,369 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <[email protected]> + 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_EFFECTS_DIALOG 0 + + +#include <kpeffectsdialog.h> + +#include <qgroupbox.h> +#include <qhbox.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qtimer.h> + +#include <kapplication.h> +#include <kcombobox.h> +#include <kconfig.h> +#include <kdebug.h> +#include <klocale.h> + +#include <kpdefs.h> +#include <kpdocument.h> +#include <kpeffectbalance.h> +#include <kpeffectblursharpen.h> +#include <kpeffectemboss.h> +#include <kpeffectflatten.h> +#include <kpeffectinvert.h> +#include <kpeffectreducecolors.h> +#include <kppixmapfx.h> + + +// protected static +int kpEffectsDialog::s_lastWidth = 640; +int kpEffectsDialog::s_lastHeight = 620; + + +kpEffectsDialog::kpEffectsDialog (bool actOnSelection, + kpMainWindow *parent, + const char *name) + : kpToolPreviewDialog (kpToolPreviewDialog::Preview, + true/*reserve top row*/, + QString::null/*caption*/, + QString::null/*afterActionText (no Dimensions Group Box)*/, + actOnSelection, + parent, + name), + m_delayedUpdateTimer (new QTimer (this)), + m_effectsComboBox (0), + m_settingsGroupBox (0), + m_settingsLayout (0), + m_colorEffectWidget (0) +{ +#if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "kpEffectsDialog::kpEffectsDialog()" << endl; +#endif + + if (actOnSelection) + setCaption (i18n ("More Image Effects (Selection)")); + else + setCaption (i18n ("More Image Effects")); + + + connect (m_delayedUpdateTimer, SIGNAL (timeout ()), + this, SLOT (slotUpdateWithWaitCursor ())); + + + QHBox *effectContainer = new QHBox (mainWidget ()); + effectContainer->setSpacing (spacingHint () * 4 + /*need more space for QGroupBox titles*/); + effectContainer->setMargin (0); + + QLabel *label = new QLabel (i18n ("&Effect:"), effectContainer); + + m_effectsComboBox = new KComboBox (effectContainer); + m_effectsComboBox->insertItem (i18n ("Balance")); + m_effectsComboBox->insertItem (i18n ("Emboss")); + m_effectsComboBox->insertItem (i18n ("Flatten")); + m_effectsComboBox->insertItem (i18n ("Invert")); + m_effectsComboBox->insertItem (i18n ("Reduce Colors")); + m_effectsComboBox->insertItem (i18n ("Soften & Sharpen")); + + label->setBuddy (m_effectsComboBox); + effectContainer->setStretchFactor (m_effectsComboBox, 1); + + addCustomWidgetToFront (effectContainer); + + + m_settingsGroupBox = new QGroupBox (mainWidget ()); + m_settingsLayout = new QVBoxLayout (m_settingsGroupBox, + marginHint () * 2, + spacingHint ()); + addCustomWidgetToBack (m_settingsGroupBox); + + + connect (m_effectsComboBox, SIGNAL (activated (int)), + this, SLOT (selectEffect (int))); + selectEffect (0); + + + resize (s_lastWidth, s_lastHeight); + + +#if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "\tabout to slotUpdate()" << endl; +#endif + slotUpdate (); +} + +kpEffectsDialog::~kpEffectsDialog () +{ + s_lastWidth = width (); + s_lastHeight = height (); +} + + +// public virtual [base kpToolPreviewDialog] +bool kpEffectsDialog::isNoOp () const +{ + if (!m_colorEffectWidget) + return true; + + return m_colorEffectWidget->isNoOp (); +} + +// public +kpColorEffectCommand *kpEffectsDialog::createCommand () const +{ + if (!m_colorEffectWidget) + return 0; + + return m_colorEffectWidget->createCommand (); +} + + +// protected virtual [base kpToolPreviewDialog] +QSize kpEffectsDialog::newDimensions () const +{ + kpDocument *doc = document (); + if (!doc) + return QSize (); + + return QSize (doc->width (m_actOnSelection), + doc->height (m_actOnSelection)); +} + +// protected virtual [base kpToolPreviewDialog] +QPixmap kpEffectsDialog::transformPixmap (const QPixmap &pixmap, + int targetWidth, int targetHeight) const +{ + QPixmap pixmapWithEffect; + + if (m_colorEffectWidget) + pixmapWithEffect = m_colorEffectWidget->applyColorEffect (pixmap); + else + pixmapWithEffect = pixmap; + + return kpPixmapFX::scale (pixmapWithEffect, targetWidth, targetHeight); +} + + +// public +int kpEffectsDialog::selectedEffect () const +{ + return m_effectsComboBox->currentItem (); +} + +// public slot +void kpEffectsDialog::selectEffect (int which) +{ +#if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "kpEffectsDialog::selectEffect(" << which << ")" << endl; +#endif + + if (which < 0 || + which >= m_effectsComboBox->count ()) + { + return; + } + + if (which != m_effectsComboBox->currentItem ()) + m_effectsComboBox->setCurrentItem (which); + + + delete m_colorEffectWidget; + m_colorEffectWidget = 0; + + + m_settingsGroupBox->setCaption (QString::null); + +#define CREATE_EFFECT_WIDGET(name) \ + m_colorEffectWidget = new name (m_actOnSelection, \ + m_mainWindow, \ + m_settingsGroupBox) + switch (which) + { + case 0: + CREATE_EFFECT_WIDGET (kpEffectBalanceWidget); + break; + + case 1: + CREATE_EFFECT_WIDGET (kpEffectEmbossWidget); + break; + + case 2: + CREATE_EFFECT_WIDGET (kpEffectFlattenWidget); + break; + + case 3: + CREATE_EFFECT_WIDGET (kpEffectInvertWidget); + break; + + case 4: + CREATE_EFFECT_WIDGET (kpEffectReduceColorsWidget); + break; + + case 5: + CREATE_EFFECT_WIDGET (kpEffectBlurSharpenWidget); + break; + } +#undef CREATE_EFFECT_WIDGET + + + if (m_colorEffectWidget) + { + #if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "\twidget exists for effect #" << endl; + #endif + m_settingsGroupBox->setTitle (m_colorEffectWidget->caption ()); + + + // Don't resize the preview when showing the widget: + // TODO: actually work + + QSize previewGroupBoxMinSize = m_previewGroupBox->minimumSize (); + QSize previewGroupBoxMaxSize = m_previewGroupBox->maximumSize (); + QLayout::ResizeMode previewGroupBoxResizeMode = + m_previewGroupBox->layout () ? + m_previewGroupBox->layout ()->resizeMode () : + QLayout::Auto; + #if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "\tpreviewGroupBox: minSize=" << previewGroupBoxMinSize + << " maxSize=" << previewGroupBoxMaxSize + << " size=" << m_previewGroupBox->size () + << " layout=" << m_previewGroupBox->layout () + << " resizeMode=" << previewGroupBoxResizeMode + << endl; + #endif + + if (m_previewGroupBox->layout ()) + m_previewGroupBox->layout ()->setResizeMode (QLayout::FreeResize); + #if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "\tafter set resizeMode, previewGroupBox.size=" + << m_previewGroupBox->size () << endl; + #endif + m_previewGroupBox->setFixedSize (m_previewGroupBox->size ()); + #if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "\tafter set fixedSize, previewGroupBox.size=" + << m_previewGroupBox->size () << endl; + #endif + + // Show widget + m_settingsLayout->addWidget (m_colorEffectWidget); + #if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "\tafter addWidget, previewGroupBox.size=" + << m_previewGroupBox->size () << endl; + #endif + m_colorEffectWidget->show (); + #if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "\tafter addWidget show, previewGroupBox.size=" + << m_previewGroupBox->size () << endl; + #endif + + m_previewGroupBox->setMinimumSize (previewGroupBoxMinSize); + m_previewGroupBox->setMaximumSize (previewGroupBoxMaxSize); + #if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "\tafter set fixedSize, previewGroupBox.size=" + << m_previewGroupBox->size () << endl; + #endif + if (m_previewGroupBox->layout ()) + m_previewGroupBox->layout ()->setResizeMode (previewGroupBoxResizeMode); + #if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "\tafter restore resizeMode, previewGroupBox.size=" + << m_previewGroupBox->size () << endl; + #endif + + + connect (m_colorEffectWidget, SIGNAL (settingsChangedNoWaitCursor ()), + this, SLOT (slotUpdate ())); + connect (m_colorEffectWidget, SIGNAL (settingsChanged ()), + this, SLOT (slotUpdateWithWaitCursor ())); + connect (m_colorEffectWidget, SIGNAL (settingsChangedDelayed ()), + this, SLOT (slotDelayedUpdate ())); + slotUpdateWithWaitCursor (); + #if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "\tafter slotUpdateWithWaitCursor, previewGroupBox.size=" + << m_previewGroupBox->size () << endl; + #endif + } +} + + +// protected slot virtual [base kpToolPreviewDialog] +void kpEffectsDialog::slotUpdate () +{ +#if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "kpEffectsDialog::slotUpdate()" + << " timerActive=" << m_delayedUpdateTimer->isActive () + << endl; +#endif + + m_delayedUpdateTimer->stop (); + + kpToolPreviewDialog::slotUpdate (); +} + +// protected slot virtual [base kpToolPreviewDialog] +void kpEffectsDialog::slotUpdateWithWaitCursor () +{ +#if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "kpEffectsDialog::slotUpdateWithWaitCursor()" + << " timerActive=" << m_delayedUpdateTimer->isActive () + << endl; +#endif + + m_delayedUpdateTimer->stop (); + + kpToolPreviewDialog::slotUpdateWithWaitCursor (); +} + + +// protected slot +void kpEffectsDialog::slotDelayedUpdate () +{ +#if DEBUG_KP_EFFECTS_DIALOG + kdDebug () << "kpEffectsDialog::slotDelayedUpdate()" + << " timerActive=" << m_delayedUpdateTimer->isActive () + << endl; +#endif + m_delayedUpdateTimer->stop (); + + m_delayedUpdateTimer->start (400/*ms*/, true/*single shot*/); +} + + +#include <kpeffectsdialog.moc> diff --git a/kolourpaint/pixmapfx/kpeffectsdialog.h b/kolourpaint/pixmapfx/kpeffectsdialog.h new file mode 100644 index 00000000..fe7265cc --- /dev/null +++ b/kolourpaint/pixmapfx/kpeffectsdialog.h @@ -0,0 +1,90 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <[email protected]> + 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_EFFECTS_DIALOG_H +#define KP_EFFECTS_DIALOG_H + + +#include <kptoolpreviewdialog.h> + + +class QGroupBox; +class QStringList; +class QTimer; +class QVBoxLayout; + +class KComboBox; + +class kpColorEffectCommand; +class kpColorEffectWidget; +class kpMainWindow; + + +class kpEffectsDialog : public kpToolPreviewDialog +{ +Q_OBJECT + +public: + kpEffectsDialog (bool actOnSelection, + kpMainWindow *parent, + const char *name = 0); + virtual ~kpEffectsDialog (); + + virtual bool isNoOp () const; + kpColorEffectCommand *createCommand () const; + +protected: + virtual QSize newDimensions () const; + virtual QPixmap transformPixmap (const QPixmap &pixmap, + int targetWidth, int targetHeight) const; + +public: + int selectedEffect () const; +public slots: + void selectEffect (int which); + +protected slots: + virtual void slotUpdate (); + virtual void slotUpdateWithWaitCursor (); + + void slotDelayedUpdate (); + +protected: + static int s_lastWidth, s_lastHeight; + + QTimer *m_delayedUpdateTimer; + + KComboBox *m_effectsComboBox; + QGroupBox *m_settingsGroupBox; + QVBoxLayout *m_settingsLayout; + + kpColorEffectWidget *m_colorEffectWidget; +}; + + +#endif // KP_EFFECTS_DIALOG_H diff --git a/kolourpaint/pixmapfx/kpfloodfill.cpp b/kolourpaint/pixmapfx/kpfloodfill.cpp new file mode 100644 index 00000000..602e8acf --- /dev/null +++ b/kolourpaint/pixmapfx/kpfloodfill.cpp @@ -0,0 +1,362 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <[email protected]> + 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_FLOOD_FILL 0 + + +#include <kpfloodfill.h> + +#include <qapplication.h> +#include <qbitmap.h> +#include <qpainter.h> +#include <qpixmap.h> + +#include <kdebug.h> +#include <kpdefs.h> + +#include <kppixmapfx.h> +#include <kptool.h> + + +kpFloodFill::kpFloodFill (QPixmap *pixmap, int x, int y, + const kpColor &color, int processedColorSimilarity) + : m_pixmapPtr (pixmap), m_x (x), m_y (y), + m_color (color), m_processedColorSimilarity (processedColorSimilarity), + m_initState (0) +{ +} + +kpFloodFill::~kpFloodFill () +{ +} + + +// private +int kpFloodFill::fillLinesListSize (const QValueList <kpFloodFill::FillLine> &fillLines) const +{ + return (fillLines.size () * kpFloodFill::FillLine::size ()); +} + +// public +int kpFloodFill::size () const +{ + int fillLinesCacheSize = 0; + for (QValueVector < QValueList <kpFloodFill::FillLine > >::const_iterator it = m_fillLinesCache.begin (); + it != m_fillLinesCache.end (); + it++) + { + fillLinesCacheSize += fillLinesListSize (*it); + } + + return fillLinesListSize (m_fillLines) + + kpPixmapFX::imageSize (m_image) + + fillLinesCacheSize; +} + + +QRect kpFloodFill::boundingRect () const +{ + return m_boundingRect; +} + +bool kpFloodFill::fill () +{ + if (m_initState < 2 && !prepare ()) + { + kdError () << "kpFloodFill:fill() could not prepare()!" << endl; + return false; + } + + // not trying to do a NOP fill + if (m_boundingRect.isValid ()) + { + QApplication::setOverrideCursor (Qt::waitCursor); + + QPainter painter, maskPainter; + QBitmap maskBitmap; + + if (m_pixmapPtr->mask () || m_color.isTransparent ()) + { + maskBitmap = kpPixmapFX::getNonNullMask (*m_pixmapPtr); + maskPainter.begin (&maskBitmap); + maskPainter.setPen (m_color.maskColor ()); + } + + if (m_color.isOpaque ()) + { + painter.begin (m_pixmapPtr); + painter.setPen (m_color.toQColor ()); + } + + const QValueList <FillLine>::ConstIterator fillLinesEnd = m_fillLines.end (); + for (QValueList <FillLine>::ConstIterator it = m_fillLines.begin (); + it != fillLinesEnd; + it++) + { + QPoint p1 = QPoint ((*it).m_x1, (*it).m_y); + QPoint p2 = QPoint ((*it).m_x2, (*it).m_y); + + if (painter.isActive ()) + painter.drawLine (p1, p2); + + if (maskPainter.isActive ()) + maskPainter.drawLine (p1, p2); + } + + if (painter.isActive ()) + painter.end (); + + if (maskPainter.isActive ()) + maskPainter.end (); + + if (!maskBitmap.isNull ()) + m_pixmapPtr->setMask (maskBitmap); + + QApplication::restoreOverrideCursor (); + } + else + { + #if DEBUG_KP_FLOOD_FILL && 1 + kdDebug () << "kpFloodFill::fill() performing NOP fill" << endl; + #endif + } + + return true; +} + +bool kpFloodFill::prepareColorToChange () +{ +#if DEBUG_KP_FLOOD_FILL && 1 + kdDebug () << "kpFloodFill::prepareColorToChange" << endl; +#endif + + m_colorToChange = kpPixmapFX::getColorAtPixel (*m_pixmapPtr, QPoint (m_x, m_y)); + + if (m_colorToChange.isOpaque ()) + { + #if DEBUG_KP_FLOOD_FILL && 1 + kdDebug () << "\tcolorToChange: r=" << m_colorToChange.red () + << ", b=" << m_colorToChange.blue () + << ", g=" << m_colorToChange.green () + << endl; + #endif + } + else + { + #if DEBUG_KP_FLOOD_FILL && 1 + kdDebug () << "\tcolorToChange: transparent" << endl; + #endif + } + + m_initState = 1; + return true; +} + +// Derived from the zSprite2 Graphics Engine + +bool kpFloodFill::prepare () +{ +#if DEBUG_KP_FLOOD_FILL && 1 + kdDebug () << "kpFloodFill::prepare()" << endl; +#endif + m_boundingRect = QRect (); + + if (m_initState < 1 && !prepareColorToChange ()) + { + kdError () << "kpFloodFill:prepare() could not prepareColorToChange()!" << endl; + return false; + } + +#if DEBUG_KP_FLOOD_FILL && 1 + kdDebug () << "\tperforming NOP check" << endl; +#endif + + // get the color we need to replace + if (m_processedColorSimilarity == 0 && m_color == m_colorToChange) + { + // need to do absolutely nothing (this is a significant optimisation + // for people who randomly click a lot over already-filled areas) + m_initState = 2; // sync with all "return true"'s + return true; + } + +#if DEBUG_KP_FLOOD_FILL && 1 + kdDebug () << "\tconverting to image" << endl; +#endif + + // is this the only way to read pixels? + m_image = kpPixmapFX::convertToImage (*m_pixmapPtr); + if (m_image.isNull ()) + { + kdError () << "kpFloodFill::prepare() could not convert to QImage" << endl; + return false; + } + +#if DEBUG_KP_FLOOD_FILL && 1 + kdDebug () << "\tcreating fillLinesCache" << endl; +#endif + + // ready cache + m_fillLinesCache.resize (m_pixmapPtr->height ()); + +#if DEBUG_KP_FLOOD_FILL && 1 + kdDebug () << "\tcreating fill lines" << endl; +#endif + + // draw initial line + addLine (m_y, findMinX (m_y, m_x), findMaxX (m_y, m_x)); + + for (QValueList <FillLine>::ConstIterator it = m_fillLines.begin (); + it != m_fillLines.end (); + it++) + { + #if DEBUG_KP_FLOOD_FILL && 0 + kdDebug () << "Expanding from y=" << (*it).m_y + << " x1=" << (*it).m_x1 + << " x2=" << (*it).m_x2 + << endl; + #endif + + // make more lines above and below current line + findAndAddLines (*it, -1); + findAndAddLines (*it, +1); + } + +#if DEBUG_KP_FLOOD_FILL && 1 + kdDebug () << "\tfinalising memory usage" << endl; +#endif + + // finalize memory usage + m_image.reset (); + m_fillLinesCache.clear (); + + m_initState = 2; // sync with all "return true"'s + return true; +} + +void kpFloodFill::addLine (int y, int x1, int x2) +{ +#if DEBUG_KP_FLOOD_FILL && 0 + kdDebug () << "kpFillCommand::fillAddLine (" << y << "," << x1 << "," << x2 << ")" << endl; +#endif + + m_fillLines.append (FillLine (y, x1, x2)); + m_fillLinesCache [y].append (FillLine (y /* OPT */, x1, x2)); + m_boundingRect = m_boundingRect.unite (QRect (QPoint (x1, y), QPoint (x2, y))); +} + +kpColor kpFloodFill::pixelColor (int x, int y, bool *beenHere) const +{ + if (beenHere) + *beenHere = false; + + if (y >= (int) m_fillLinesCache.count ()) + { + kdError () << "kpFloodFill::pixelColor(" + << x << "," + << y << ") y out of range=" << m_pixmapPtr->height () << endl; + return kpColor::invalid; + } + + const QValueList <FillLine>::ConstIterator theEnd = m_fillLinesCache [y].end (); + for (QValueList <FillLine>::ConstIterator it = m_fillLinesCache [y].begin (); + it != theEnd; + it++) + { + if (x >= (*it).m_x1 && x <= (*it).m_x2) + { + if (beenHere) + *beenHere = true; + return m_color; + } + } + + return kpPixmapFX::getColorAtPixel (m_image, QPoint (x, y)); +} + +bool kpFloodFill::shouldGoTo (int x, int y) const +{ + bool beenThere; + const kpColor col = pixelColor (x, y, &beenThere); + + return (!beenThere && col.isSimilarTo (m_colorToChange, m_processedColorSimilarity)); +} + +void kpFloodFill::findAndAddLines (const FillLine &fillLine, int dy) +{ + // out of bounds? + if (fillLine.m_y + dy < 0 || fillLine.m_y + dy >= m_pixmapPtr->height ()) + return; + + for (int xnow = fillLine.m_x1; xnow <= fillLine.m_x2; xnow++) + { + // At current position, right colour? + if (shouldGoTo (xnow, fillLine.m_y + dy)) + { + // Find minimum and maximum x values + int minxnow = findMinX (fillLine.m_y + dy, xnow); + int maxxnow = findMaxX (fillLine.m_y + dy, xnow); + + // Draw line + addLine (fillLine.m_y + dy, minxnow, maxxnow); + + // Move x pointer + xnow = maxxnow; + } + } +} + +// finds the minimum x value at a certain line to be filled +int kpFloodFill::findMinX (int y, int x) const +{ + for (;;) + { + if (x < 0) + return 0; + + if (shouldGoTo (x, y)) + x--; + else + return x + 1; + } +} + +// finds the maximum x value at a certain line to be filled +int kpFloodFill::findMaxX (int y, int x) const +{ + for (;;) + { + if (x > m_pixmapPtr->width () - 1) + return m_pixmapPtr->width () - 1; + + if (shouldGoTo (x, y)) + x++; + else + return x - 1; + } +} diff --git a/kolourpaint/pixmapfx/kpfloodfill.h b/kolourpaint/pixmapfx/kpfloodfill.h new file mode 100644 index 00000000..5c0d8001 --- /dev/null +++ b/kolourpaint/pixmapfx/kpfloodfill.h @@ -0,0 +1,106 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <[email protected]> + 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 __kpfloodfill_h__ +#define __kpfloodfill_h__ + +#include <qimage.h> +#include <qvaluelist.h> +#include <qvaluevector.h> + +#include <kpcolor.h> + +class QPixmap; + +class kpFloodFill +{ +public: + kpFloodFill (QPixmap *pixmap, int x, int y, + const kpColor &color, + int processedColorSimilarity); + ~kpFloodFill (); + + int size () const; + + kpColor color () const { return m_color; } + int processedColorSimilarity () const { return m_processedColorSimilarity; } + + // you should call [prepareColorToChange(),[prepare(),[fill()]]] + bool prepareColorToChange (); + + // (only valid after prepareColorToChange()) + kpColor colorToChange () const { return m_colorToChange; }; + + bool prepare (); + QRect boundingRect () const; // only valid after prepare() + + bool fill (); + +private: + QPixmap *m_pixmapPtr; + int m_x, m_y; + kpColor m_color; + int m_processedColorSimilarity; + + int m_initState; + + QRect m_boundingRect; + + struct FillLine + { + FillLine (int y = -1, int x1 = -1, int x2 = -1) + : m_y (y), m_x1 (x1), m_x2 (x2) + { + } + + static int size () + { + return sizeof (FillLine); + } + + int m_y, m_x1, m_x2; + }; + + int fillLinesListSize (const QValueList <kpFloodFill::FillLine> &fillLines) const; + + void addLine (int y, int x1, int x2); + kpColor pixelColor (int x, int y, bool *beenHere = 0) const; + bool shouldGoTo (int x, int y) const; + void findAndAddLines (const FillLine &fillLine, int dy); + int findMinX (int y, int x) const; + int findMaxX (int y, int x) const; + + QValueList <FillLine> m_fillLines; + + // Init info + QImage m_image; + QValueVector < QValueList <FillLine> > m_fillLinesCache; + kpColor m_colorToChange; +}; + +#endif // __kpfloodfill_h__ diff --git a/kolourpaint/pixmapfx/kppixmapfx.cpp b/kolourpaint/pixmapfx/kppixmapfx.cpp new file mode 100644 index 00000000..1bd0b173 --- /dev/null +++ b/kolourpaint/pixmapfx/kppixmapfx.cpp @@ -0,0 +1,1677 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <[email protected]> + 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 <math.h> + +#include <qapplication.h> +#include <qbitmap.h> +#include <qdatetime.h> +#include <qimage.h> +#include <qpainter.h> +#include <qpixmap.h> +#include <qpoint.h> +#include <qpointarray.h> +#include <qrect.h> + +#include <kconfig.h> +#include <kdebug.h> +#include <klocale.h> +#include <kmessagebox.h> + +#include <kpcolor.h> +#include <kpdefs.h> +#include <kpselection.h> +#include <kptool.h> + + +// +// Overflow Resistant Arithmetic: +// + +// public static +int kpPixmapFX::addDimensions (int lhs, int rhs) +{ + if (lhs < 0 || rhs < 0 || + lhs > INT_MAX - rhs) + { + return INT_MAX; + } + + return lhs + rhs; +} + +// public static +int kpPixmapFX::multiplyDimensions (int lhs, int rhs) +{ + if (rhs == 0) + return 0; + + if (lhs < 0 || rhs < 0 || + lhs > INT_MAX / rhs) + { + return INT_MAX; + } + + return lhs * rhs; +} + + +// +// QPixmap Statistics +// + +// public static +int kpPixmapFX::pixmapArea (const QPixmap &pixmap) +{ + return kpPixmapFX::pixmapArea (pixmap.width (), pixmap.height ()); +} + +// public static +int kpPixmapFX::pixmapArea (const QPixmap *pixmap) +{ + return (pixmap ? kpPixmapFX::pixmapArea (*pixmap) : 0); +} + +// public static +int kpPixmapFX::pixmapArea (int width, int height) +{ + return multiplyDimensions (width, height); +} + + +// public static +int kpPixmapFX::pixmapSize (const QPixmap &pixmap) +{ + return kpPixmapFX::pixmapSize (pixmap.width (), pixmap.height (), + pixmap.depth ()); +} + +// public static +int kpPixmapFX::pixmapSize (const QPixmap *pixmap) +{ + return (pixmap ? kpPixmapFX::pixmapSize (*pixmap) : 0); +} + +// public static +int kpPixmapFX::pixmapSize (int width, int height, int depth) +{ + // handle 15bpp + int roundedDepth = (depth > 8 ? (depth + 7) / 8 * 8 : depth); + +#if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "kpPixmapFX::pixmapSize() w=" << width + << " h=" << height + << " d=" << depth + << " roundedDepth=" << roundedDepth + << " ret=" + << multiplyDimensions (kpPixmapFX::pixmapArea (width, height), roundedDepth) / 8 + << endl; +#endif + return multiplyDimensions (kpPixmapFX::pixmapArea (width, height), roundedDepth) / 8; +} + + +// public static +int kpPixmapFX::imageSize (const QImage &image) +{ + return kpPixmapFX::imageSize (image.width (), image.height (), image.depth ()); +} + +// public static +int kpPixmapFX::imageSize (const QImage *image) +{ + return (image ? kpPixmapFX::imageSize (*image) : 0); +} + +// public static +int kpPixmapFX::imageSize (int width, int height, int depth) +{ + // handle 15bpp + int roundedDepth = (depth > 8 ? (depth + 7) / 8 * 8 : depth); + +#if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "kpPixmapFX::imageSize() w=" << width + << " h=" << height + << " d=" << depth + << " roundedDepth=" << roundedDepth + << " ret=" + << multiplyDimensions (multiplyDimensions (width, height), roundedDepth) / 8 + << endl; +#endif + + return multiplyDimensions (multiplyDimensions (width, height), roundedDepth) / 8; +} + + +// public static +int kpPixmapFX::selectionSize (const kpSelection &sel) +{ + return sel.size (); +} + +// public static +int kpPixmapFX::selectionSize (const kpSelection *sel) +{ + return (sel ? sel->size () : 0); +} + + +// public static +int kpPixmapFX::stringSize (const QString &string) +{ +#if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "kpPixmapFX::stringSize(" << string << ")" + << " len=" << string.length () + << " sizeof(QChar)=" << sizeof (QChar) + << endl; +#endif + return string.length () * sizeof (QChar); +} + + +// public static +int kpPixmapFX::pointArraySize (const QPointArray &points) +{ +#if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "kpPixmapFX::pointArraySize() points.size=" + << points.size () + << " sizeof(QPoint)=" << sizeof (QPoint) + << endl; +#endif + + return (points.size () * sizeof (QPoint)); +} + + +// +// QPixmap/QImage Conversion Functions +// + +// public static +QImage kpPixmapFX::convertToImage (const QPixmap &pixmap) +{ + if (pixmap.isNull ()) + return QImage (); + + return pixmap.convertToImage (); +} + + +// Returns true if <image> contains translucency (rather than just transparency) +// QPixmap::hasAlphaChannel() appears to give incorrect results +static bool imageHasAlphaChannel (const QImage &image) +{ + if (image.depth () < 32) + return false; + + for (int y = 0; y < image.height (); y++) + { + for (int x = 0; x < image.width (); x++) + { + const QRgb rgb = image.pixel (x, y); + + if (qAlpha (rgb) > 0 && qAlpha (rgb) < 255) + return true; + } + } + + return false; +} + +static int imageNumColorsUpTo (const QImage &image, int max) +{ + QMap <QRgb, bool> rgbMap; + + if (image.depth () <= 8) + { + for (int i = 0; i < image.numColors () && (int) rgbMap.size () < max; i++) + { + rgbMap.insert (image.color (i), true); + } + } + else + { + for (int y = 0; y < image.height () && (int) rgbMap.size () < max; y++) + { + for (int x = 0; x < image.width () && (int) rgbMap.size () < max; x++) + { + rgbMap.insert (image.pixel (x, y), true); + } + } + } + + return rgbMap.size (); +} + +static void convertToPixmapWarnAboutLoss (const QImage &image, + const kpPixmapFX::WarnAboutLossInfo &wali) +{ + if (!wali.isValid ()) + return; + + + const QString colorDepthTranslucencyDontAskAgain = + wali.m_dontAskAgainPrefix + "_ColorDepthTranslucency"; + const QString colorDepthDontAskAgain = + wali.m_dontAskAgainPrefix + "_ColorDepth"; + const QString translucencyDontAskAgain = + wali.m_dontAskAgainPrefix + "_Translucency"; + +#if DEBUG_KP_PIXMAP_FX && 1 + QTime timer; + timer.start (); +#endif + + bool hasAlphaChannel = + (KMessageBox::shouldBeShownContinue (translucencyDontAskAgain) && + imageHasAlphaChannel (image)); + +#if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\twarnAboutLoss - check hasAlphaChannel took " + << timer.restart () << "msec" << endl; +#endif + + bool moreColorsThanDisplay = + (KMessageBox::shouldBeShownContinue (colorDepthDontAskAgain) && + image.depth () > QColor::numBitPlanes () && + QColor::numBitPlanes () < 24); // 32 indicates alpha channel + + int screenDepthNeeded = 0; + + if (moreColorsThanDisplay) + screenDepthNeeded = QMIN (24, image.depth ()); + +#if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\ttranslucencyShouldBeShown=" + << KMessageBox::shouldBeShownContinue (translucencyDontAskAgain) + << endl + << "\thasAlphaChannel=" << hasAlphaChannel + << endl + << "\tcolorDepthShownBeShown=" + << KMessageBox::shouldBeShownContinue (colorDepthDontAskAgain) + << endl + << "\timage.depth()=" << image.depth () + << endl + << "\tscreenDepth=" << QColor::numBitPlanes () + << endl + << "\tmoreColorsThanDisplay=" << moreColorsThanDisplay + << endl + << "\tneedDepth=" << screenDepthNeeded + << endl; +#endif + + + QApplication::setOverrideCursor (Qt::arrowCursor); + + if (moreColorsThanDisplay && hasAlphaChannel) + { + KMessageBox::information (wali.m_parent, + wali.m_moreColorsThanDisplayAndHasAlphaChannelMessage + .arg (screenDepthNeeded), + QString::null, // or would you prefer "Low Screen Depth and Image Contains Transparency"? :) + colorDepthTranslucencyDontAskAgain); + + if (!KMessageBox::shouldBeShownContinue (colorDepthTranslucencyDontAskAgain)) + { + KMessageBox::saveDontShowAgainContinue (colorDepthDontAskAgain); + KMessageBox::saveDontShowAgainContinue (translucencyDontAskAgain); + } + } + else if (moreColorsThanDisplay) + { + KMessageBox::information (wali.m_parent, + wali.m_moreColorsThanDisplayMessage + .arg (screenDepthNeeded), + i18n ("Low Screen Depth"), + colorDepthDontAskAgain); + } + else if (hasAlphaChannel) + { + KMessageBox::information (wali.m_parent, + wali.m_hasAlphaChannelMessage, + i18n ("Image Contains Translucency"), + translucencyDontAskAgain); + } + + QApplication::restoreOverrideCursor (); +} + +// public static +QPixmap kpPixmapFX::convertToPixmap (const QImage &image, bool pretty, + const WarnAboutLossInfo &wali) +{ +#if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "kpPixmapFX::convertToPixmap(image,pretty=" << pretty + << ",warnAboutLossInfo.isValid=" << wali.isValid () + << ")" << endl; + QTime timer; + timer.start (); +#endif + + if (image.isNull ()) + return QPixmap (); + + + QPixmap destPixmap; + + if (!pretty) + { + destPixmap.convertFromImage (image, + Qt::ColorOnly/*always display depth*/ | + Qt::ThresholdDither/*no dither*/ | + Qt::ThresholdAlphaDither/*no dither alpha*/| + Qt::AvoidDither); + } + else + { + destPixmap.convertFromImage (image, + Qt::ColorOnly/*always display depth*/ | + Qt::DiffuseDither/*hi quality dither*/ | + Qt::ThresholdAlphaDither/*no dither alpha*/ | + Qt::PreferDither/*(dither even if <256 colours)*/); + } + +#if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\tconversion took " << timer.elapsed () << "msec" << endl; +#endif + + kpPixmapFX::ensureNoAlphaChannel (&destPixmap); + + + if (wali.isValid ()) + convertToPixmapWarnAboutLoss (image, wali); + + + return destPixmap; +} + +// TODO: don't dup convertToPixmap() code +// public static +QPixmap kpPixmapFX::convertToPixmapAsLosslessAsPossible (const QImage &image, + const WarnAboutLossInfo &wali) +{ +#if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "kpPixmapFX::convertToPixmapAsLosslessAsPossible(image depth=" + << image.depth () + << ",warnAboutLossInfo.isValid=" << wali.isValid () + << ") screenDepth=" << QPixmap::defaultDepth () + << " imageNumColorsUpTo257=" << imageNumColorsUpTo (image, 257) + << endl; + QTime timer; + timer.start (); +#endif + + if (image.isNull ()) + return QPixmap (); + + + const int screenDepth = (QPixmap::defaultDepth () >= 24 ? + 32 : + QPixmap::defaultDepth ()); + + QPixmap destPixmap; + int ditherFlags = 0; + + if (image.depth () <= screenDepth) + { + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\timage depth <= screen depth - don't dither" + << " (AvoidDither | ThresholdDither)" << endl; + #endif + + ditherFlags = (Qt::AvoidDither | Qt::ThresholdDither); + } + // PRE: image.depth() > screenDepth + // ASSERT: screenDepth < 32 + else if (screenDepth <= 8) + { + const int screenNumColors = (1 << screenDepth); + + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\tscreen depth <= 8; imageNumColorsUpTo" + << (screenNumColors + 1) + << "=" << imageNumColorsUpTo (image, screenNumColors + 1) + << endl; + #endif + + if (imageNumColorsUpTo (image, screenNumColors + 1) <= screenNumColors) + { + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\t\tcolors fit on screen - don't dither" + << " (AvoidDither | ThresholdDither)" << endl; + #endif + ditherFlags = (Qt::AvoidDither | Qt::ThresholdDither); + } + else + { + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\t\tcolors don't fit on screen - dither" + << " (PreferDither | DiffuseDither)" << endl; + #endif + ditherFlags = (Qt::PreferDither | Qt::DiffuseDither); + } + } + // PRE: image.depth() > screenDepth && + // screenDepth > 8 + // ASSERT: screenDepth < 32 + else + { + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\tscreen depth > 8 - read config" << endl; + #endif + + int configDitherIfNumColorsGreaterThan = 323; + + KConfigGroupSaver cfgGroupSaver (KGlobal::config (), + kpSettingsGroupGeneral); + KConfigBase *cfg = cfgGroupSaver.config (); + + if (cfg->hasKey (kpSettingDitherOnOpen)) + { + configDitherIfNumColorsGreaterThan = cfg->readNumEntry (kpSettingDitherOnOpen); + } + else + { + cfg->writeEntry (kpSettingDitherOnOpen, configDitherIfNumColorsGreaterThan); + cfg->sync (); + } + + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\t\tcfg=" << configDitherIfNumColorsGreaterThan + << " image=" << imageNumColorsUpTo (image, configDitherIfNumColorsGreaterThan + 1) + << endl; + #endif + + if (imageNumColorsUpTo (image, configDitherIfNumColorsGreaterThan + 1) > + configDitherIfNumColorsGreaterThan) + { + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\t\t\talways dither (PreferDither | DiffuseDither)" + << endl; + #endif + ditherFlags = (Qt::PreferDither | Qt::DiffuseDither); + } + else + { + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\t\t\tdon't dither (AvoidDither | ThresholdDither)" + << endl; + #endif + ditherFlags = (Qt::AvoidDither | Qt::ThresholdDither); + } + } + + + destPixmap.convertFromImage (image, + Qt::ColorOnly/*always display depth*/ | + Qt::ThresholdAlphaDither/*no dither alpha*/ | + ditherFlags); + +#if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\tconversion took " << timer.elapsed () << "msec" << endl; +#endif + + kpPixmapFX::ensureNoAlphaChannel (&destPixmap); + + + if (wali.isValid ()) + convertToPixmapWarnAboutLoss (image, wali); + + + return destPixmap; +} + + +// public static +QPixmap kpPixmapFX::pixmapWithDefinedTransparentPixels (const QPixmap &pixmap, + const QColor &transparentColor) +{ + if (!pixmap.mask ()) + return pixmap; + + QPixmap retPixmap (pixmap.width (), pixmap.height ()); + retPixmap.fill (transparentColor); + + QPainter p (&retPixmap); + p.drawPixmap (QPoint (0, 0), pixmap); + p.end (); + + retPixmap.setMask (*pixmap.mask ()); + return retPixmap; +} + + +// +// Get/Set Parts of Pixmap +// + + +// public static +QPixmap kpPixmapFX::getPixmapAt (const QPixmap &pm, const QRect &rect) +{ + QPixmap retPixmap (rect.width (), rect.height ()); + +#if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "kpPixmapFX::getPixmapAt(pm.hasMask=" + << (pm.mask () ? 1 : 0) + << ",rect=" + << rect + << ")" + << endl; +#endif + + const QRect validSrcRect = pm.rect ().intersect (rect); + const bool wouldHaveUndefinedPixels = (validSrcRect != rect); + + if (wouldHaveUndefinedPixels) + { + #if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "\tret would contain undefined pixels - setting them to transparent" << endl; + #endif + QBitmap transparentMask (rect.width (), rect.height ()); + transparentMask.fill (Qt::color0/*transparent*/); + retPixmap.setMask (transparentMask); + } + + if (validSrcRect.isEmpty ()) + { + #if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "\tsilly case - completely invalid rect - ret transparent pixmap" << endl; + #endif + return retPixmap; + } + + + const QPoint destTopLeft = validSrcRect.topLeft () - rect.topLeft (); + + // copy data _and_ mask (if avail) + copyBlt (&retPixmap, /* dest */ + destTopLeft.x (), destTopLeft.y (), /* dest pt */ + &pm, /* src */ + validSrcRect.x (), validSrcRect.y (), /* src pt */ + validSrcRect.width (), validSrcRect.height ()); + + if (wouldHaveUndefinedPixels && retPixmap.mask () && !pm.mask ()) + { + #if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "\tensure opaque in valid region" << endl; + #endif + kpPixmapFX::ensureOpaqueAt (&retPixmap, + QRect (destTopLeft.x (), destTopLeft.y (), + validSrcRect.width (), validSrcRect.height ())); + } + +#if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "\tretPixmap.hasMask=" + << (retPixmap.mask () ? 1 : 0) + << endl; +#endif + + return retPixmap; +} + + +// public static +void kpPixmapFX::setPixmapAt (QPixmap *destPixmapPtr, const QRect &destRect, + const QPixmap &srcPixmap) +{ + if (!destPixmapPtr) + return; + +#if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "kpPixmapFX::setPixmapAt(destPixmap->rect=" + << destPixmapPtr->rect () + << ",destPixmap->hasMask=" + << (destPixmapPtr->mask () ? 1 : 0) + << ",destRect=" + << destRect + << ",srcPixmap.rect=" + << srcPixmap.rect () + << ",srcPixmap.hasMask=" + << (srcPixmap.mask () ? 1 : 0) + << ")" + << endl; +#endif + +#if DEBUG_KP_PIXMAP_FX && 0 + if (destPixmapPtr->mask ()) + { + QImage image = kpPixmapFX::convertToImage (*destPixmapPtr); + int numTrans = 0; + + for (int y = 0; y < image.height (); y++) + { + for (int x = 0; x < image.width (); x++) + { + if (qAlpha (image.pixel (x, y)) == 0) + numTrans++; + } + } + + kdDebug () << "\tdestPixmapPtr numTrans=" << numTrans << endl; + } +#endif + +#if 0 + // TODO: why does undo'ing a single pen dot on a transparent pixel, + // result in a opaque image, except for that single transparent pixel??? + // Qt bug on boundary case? + + // copy data _and_ mask + copyBlt (destPixmapPtr, + destAt.x (), destAt.y (), + &srcPixmap, + 0, 0, + destRect.width (), destRect.height ()); +#else + bitBlt (destPixmapPtr, + destRect.x (), destRect.y (), + &srcPixmap, + 0, 0, + destRect.width (), destRect.height (), + Qt::CopyROP, + true/*ignore mask*/); + + if (srcPixmap.mask ()) + { + QBitmap mask = getNonNullMask (*destPixmapPtr); + bitBlt (&mask, + destRect.x (), destRect.y (), + srcPixmap.mask (), + 0, 0, + destRect.width (), destRect.height (), + Qt::CopyROP, + true/*ignore mask*/); + destPixmapPtr->setMask (mask); + } +#endif + + if (destPixmapPtr->mask () && !srcPixmap.mask ()) + { + #if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "\t\topaque'ing dest rect" << endl; + #endif + kpPixmapFX::ensureOpaqueAt (destPixmapPtr, destRect); + } + +#if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "\tdestPixmap->hasMask=" + << (destPixmapPtr->mask () ? 1 : 0) + << endl; + if (destPixmapPtr->mask ()) + { + QImage image = kpPixmapFX::convertToImage (*destPixmapPtr); + int numTrans = 0; + + for (int y = 0; y < image.height (); y++) + { + for (int x = 0; x < image.width (); x++) + { + if (qAlpha (image.pixel (x, y)) == 0) + numTrans++; + } + } + + kdDebug () << "\tdestPixmapPtr numTrans=" << numTrans << endl; + } +#endif +} + +// public static +void kpPixmapFX::setPixmapAt (QPixmap *destPixmapPtr, const QPoint &destAt, + const QPixmap &srcPixmap) +{ + kpPixmapFX::setPixmapAt (destPixmapPtr, + QRect (destAt.x (), destAt.y (), + srcPixmap.width (), srcPixmap.height ()), + srcPixmap); +} + +// public static +void kpPixmapFX::setPixmapAt (QPixmap *destPixmapPtr, int destX, int destY, + const QPixmap &srcPixmap) +{ + kpPixmapFX::setPixmapAt (destPixmapPtr, QPoint (destX, destY), srcPixmap); +} + + +// public static +void kpPixmapFX::paintPixmapAt (QPixmap *destPixmapPtr, const QPoint &destAt, + const QPixmap &srcPixmap) +{ + if (!destPixmapPtr) + return; + + // Copy src (masked by src's mask) on top of dest. + bitBlt (destPixmapPtr, /* dest */ + destAt.x (), destAt.y (), /* dest pt */ + &srcPixmap, /* src */ + 0, 0 /* src pt */); + + kpPixmapFX::ensureOpaqueAt (destPixmapPtr, destAt, srcPixmap); +} + +// public static +void kpPixmapFX::paintPixmapAt (QPixmap *destPixmapPtr, int destX, int destY, + const QPixmap &srcPixmap) +{ + kpPixmapFX::paintPixmapAt (destPixmapPtr, QPoint (destX, destY), srcPixmap); +} + + +// public static +kpColor kpPixmapFX::getColorAtPixel (const QPixmap &pm, const QPoint &at) +{ +#if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "kpToolColorPicker::colorAtPixel" << p << endl; +#endif + + if (at.x () < 0 || at.x () >= pm.width () || + at.y () < 0 || at.y () >= pm.height ()) + { + return kpColor::invalid; + } + + QPixmap pixmap = getPixmapAt (pm, QRect (at, at)); + QImage image = kpPixmapFX::convertToImage (pixmap); + if (image.isNull ()) + { + kdError () << "kpPixmapFX::getColorAtPixel(QPixmap) could not convert to QImage" << endl; + return kpColor::invalid; + } + + return getColorAtPixel (image, QPoint (0, 0)); +} + +// public static +kpColor kpPixmapFX::getColorAtPixel (const QPixmap &pm, int x, int y) +{ + return kpPixmapFX::getColorAtPixel (pm, QPoint (x, y)); +} + +// 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.x (), at.y ()); + return kpColor (rgba); +} + +// public static +kpColor kpPixmapFX::getColorAtPixel (const QImage &img, int x, int y) +{ + return kpPixmapFX::getColorAtPixel (img, QPoint (x, y)); +} + + +// +// Mask Operations +// + + +// public static +void kpPixmapFX::ensureNoAlphaChannel (QPixmap *destPixmapPtr) +{ + if (destPixmapPtr->hasAlphaChannel ()) + destPixmapPtr->setMask (kpPixmapFX::getNonNullMask/*just in case*/ (*destPixmapPtr)); +} + + +// public static +QBitmap kpPixmapFX::getNonNullMask (const QPixmap &pm) +{ + if (pm.mask ()) + return *pm.mask (); + else + { + QBitmap maskBitmap (pm.width (), pm.height ()); + maskBitmap.fill (Qt::color1/*opaque*/); + + return maskBitmap; + } +} + + +// public static +void kpPixmapFX::ensureTransparentAt (QPixmap *destPixmapPtr, const QRect &destRect) +{ + if (!destPixmapPtr) + return; + + QBitmap maskBitmap = getNonNullMask (*destPixmapPtr); + + QPainter p (&maskBitmap); + + p.setPen (Qt::color0/*transparent*/); + p.setBrush (Qt::color0/*transparent*/); + + p.drawRect (destRect); + + p.end (); + + destPixmapPtr->setMask (maskBitmap); +} + + +// public static +void kpPixmapFX::paintMaskTransparentWithBrush (QPixmap *destPixmapPtr, const QPoint &destAt, + const QPixmap &brushBitmap) +{ + if (!destPixmapPtr) + return; + + if (brushBitmap.depth () > 1) + { + kdError () << "kpPixmapFX::paintMaskTransparentWidthBrush() passed brushPixmap with depth > 1" << endl; + return; + } + + QBitmap destMaskBitmap = kpPixmapFX::getNonNullMask (*destPixmapPtr); + + // Src + // Dest Mask Brush Bitmap = Result + // ------------------------------------- + // 0 0 0 + // 0 1 0 + // 1 0 1 + // 1 1 0 + // + // Brush Bitmap value of 1 means "make transparent" + // 0 means "leave it as it is" + + bitBlt (&destMaskBitmap, + destAt.x (), destAt.y (), + &brushBitmap, + 0, 0, + brushBitmap.width (), brushBitmap.height (), + Qt::NotAndROP); + + destPixmapPtr->setMask (destMaskBitmap); +} + +// public static +void kpPixmapFX::paintMaskTransparentWithBrush (QPixmap *destPixmapPtr, int destX, int destY, + const QPixmap &brushBitmap) +{ + kpPixmapFX::paintMaskTransparentWithBrush (destPixmapPtr, + QPoint (destX, destY), + brushBitmap); +} + + +// public static +void kpPixmapFX::ensureOpaqueAt (QPixmap *destPixmapPtr, const QRect &destRect) +{ + if (!destPixmapPtr || !destPixmapPtr->mask ()/*already opaque*/) + return; + + QBitmap maskBitmap = *destPixmapPtr->mask (); + + QPainter p (&maskBitmap); + + p.setPen (Qt::color1/*opaque*/); + p.setBrush (Qt::color1/*opaque*/); + + p.drawRect (destRect); + + p.end (); + + destPixmapPtr->setMask (maskBitmap); +} + +// public static +void kpPixmapFX::ensureOpaqueAt (QPixmap *destPixmapPtr, const QPoint &destAt, + const QPixmap &srcPixmap) +{ + if (!destPixmapPtr || !destPixmapPtr->mask ()/*already opaque*/) + return; + + QBitmap destMask = *destPixmapPtr->mask (); + + if (srcPixmap.mask ()) + { + bitBlt (&destMask, /* dest */ + destAt, /* dest pt */ + srcPixmap.mask (), /* src */ + QRect (0, 0, srcPixmap.width (), srcPixmap.height ()), /* src rect */ + Qt::OrROP/*if either is opaque, it's opaque*/); + } + else + { + QPainter p (&destMask); + + p.setPen (Qt::color1/*opaque*/); + p.setBrush (Qt::color1/*opaque*/); + + p.drawRect (destAt.x (), destAt.y (), + srcPixmap.width (), srcPixmap.height ()); + + p.end (); + } + + destPixmapPtr->setMask (destMask); +} + +// public static +void kpPixmapFX::ensureOpaqueAt (QPixmap *destPixmapPtr, int destX, int destY, + const QPixmap &srcPixmap) +{ + kpPixmapFX::ensureOpaqueAt (destPixmapPtr, QPoint (destX, destY), srcPixmap); +} + + +// +// Effects +// + +// public static +void kpPixmapFX::convertToGrayscale (QPixmap *destPixmapPtr) +{ + QImage image = kpPixmapFX::convertToImage (*destPixmapPtr); + kpPixmapFX::convertToGrayscale (&image); + *destPixmapPtr = kpPixmapFX::convertToPixmap (image); +} + +// public static +QPixmap kpPixmapFX::convertToGrayscale (const QPixmap &pm) +{ + QImage image = kpPixmapFX::convertToImage (pm); + kpPixmapFX::convertToGrayscale (&image); + return kpPixmapFX::convertToPixmap (image); +} + +static QRgb toGray (QRgb rgb) +{ + // naive way that doesn't preserve brightness + // int gray = (qRed (rgb) + qGreen (rgb) + qBlue (rgb)) / 3; + + // over-exaggerates red & blue + // int gray = qGray (rgb); + + int gray = (212671 * qRed (rgb) + 715160 * qGreen (rgb) + 72169 * qBlue (rgb)) / 1000000; + return qRgba (gray, gray, gray, qAlpha (rgb)); +} + +// public static +void kpPixmapFX::convertToGrayscale (QImage *destImagePtr) +{ + if (destImagePtr->depth () > 8) + { + // hmm, why not just write to the pixmap directly??? + + for (int y = 0; y < destImagePtr->height (); y++) + { + for (int x = 0; x < destImagePtr->width (); x++) + { + destImagePtr->setPixel (x, y, toGray (destImagePtr->pixel (x, y))); + } + } + } + else + { + // 1- & 8- bit images use a color table + for (int i = 0; i < destImagePtr->numColors (); i++) + destImagePtr->setColor (i, toGray (destImagePtr->color (i))); + } +} + +// public static +QImage kpPixmapFX::convertToGrayscale (const QImage &img) +{ + QImage retImage = img; + kpPixmapFX::convertToGrayscale (&retImage); + return retImage; +} + + +// public static +void kpPixmapFX::fill (QPixmap *destPixmapPtr, const kpColor &color) +{ + if (!destPixmapPtr) + return; + + if (color.isOpaque ()) + { + destPixmapPtr->setMask (QBitmap ()); // no mask = opaque + destPixmapPtr->fill (color.toQColor ()); + } + else + { + kpPixmapFX::ensureTransparentAt (destPixmapPtr, destPixmapPtr->rect ()); + } +} + +// public static +QPixmap kpPixmapFX::fill (const QPixmap &pm, const kpColor &color) +{ + QPixmap ret = pm; + kpPixmapFX::fill (&ret, color); + return ret; +} + + +// public static +void kpPixmapFX::resize (QPixmap *destPixmapPtr, int w, int h, + const kpColor &backgroundColor, bool fillNewAreas) +{ +#if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "kpPixmapFX::resize()" << endl; +#endif + + if (!destPixmapPtr) + return; + + int oldWidth = destPixmapPtr->width (); + int oldHeight = destPixmapPtr->height (); + + if (w == oldWidth && h == oldHeight) + return; + + + destPixmapPtr->resize (w, h); + + if (fillNewAreas && (w > oldWidth || h > oldHeight)) + { + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\tfilling in new areas" << endl; + #endif + QBitmap maskBitmap; + QPainter painter, maskPainter; + + if (backgroundColor.isOpaque ()) + { + painter.begin (destPixmapPtr); + painter.setPen (backgroundColor.toQColor ()); + painter.setBrush (backgroundColor.toQColor ()); + } + + if (backgroundColor.isTransparent () || destPixmapPtr->mask ()) + { + maskBitmap = kpPixmapFX::getNonNullMask (*destPixmapPtr); + maskPainter.begin (&maskBitmap); + maskPainter.setPen (backgroundColor.maskColor ()); + maskPainter.setBrush (backgroundColor.maskColor ()); + } + + #define PAINTER_CALL(cmd) \ + { \ + if (painter.isActive ()) \ + painter . cmd ; \ + \ + if (maskPainter.isActive ()) \ + maskPainter . cmd ; \ + } + if (w > oldWidth) + PAINTER_CALL (drawRect (oldWidth, 0, w - oldWidth, oldHeight)); + + if (h > oldHeight) + PAINTER_CALL (drawRect (0, oldHeight, w, h - oldHeight)); + #undef PAINTER_CALL + + if (maskPainter.isActive ()) + maskPainter.end (); + + if (painter.isActive ()) + painter.end (); + + if (!maskBitmap.isNull ()) + destPixmapPtr->setMask (maskBitmap); + } +} + +// public static +QPixmap kpPixmapFX::resize (const QPixmap &pm, int w, int h, + const kpColor &backgroundColor, bool fillNewAreas) +{ + QPixmap ret = pm; + kpPixmapFX::resize (&ret, w, h, backgroundColor, fillNewAreas); + return ret; +} + + +// public static +void kpPixmapFX::scale (QPixmap *destPixmapPtr, int w, int h, bool pretty) +{ + if (!destPixmapPtr) + return; + + *destPixmapPtr = kpPixmapFX::scale (*destPixmapPtr, w, h, pretty); +} + +// public static +QPixmap kpPixmapFX::scale (const QPixmap &pm, int w, int h, bool pretty) +{ +#if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "kpPixmapFX::scale(oldRect=" << pm.rect () + << ",w=" << w + << ",h=" << h + << ",pretty=" << pretty + << ")" + << endl; +#endif + + if (w == pm.width () && h == pm.height ()) + return pm; + + if (pretty) + { + QImage image = kpPixmapFX::convertToImage (pm); + + #if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "\tBefore smooth scale:" << endl; + for (int y = 0; y < image.height (); y++) + { + for (int x = 0; x < image.width (); x++) + { + fprintf (stderr, " %08X", image.pixel (x, y)); + } + fprintf (stderr, "\n"); + } + #endif + + image = image.smoothScale (w, h); + + #if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "\tAfter smooth scale:" << endl; + for (int y = 0; y < image.height (); y++) + { + for (int x = 0; x < image.width (); x++) + { + fprintf (stderr, " %08X", image.pixel (x, y)); + } + fprintf (stderr, "\n"); + } + #endif + + return kpPixmapFX::convertToPixmap (image, false/*let's not smooth it again*/); + } + else + { + QWMatrix matrix; + + matrix.scale (double (w) / double (pm.width ()), + double (h) / double (pm.height ())); + + return pm.xForm (matrix); + } +} + + +// public static +double kpPixmapFX::AngleInDegreesEpsilon = + KP_RADIANS_TO_DEGREES (atan (1.0 / 10000.0)) + / (2.0/*max error allowed*/ * 2.0/*for good measure*/); + + +static QWMatrix matrixWithZeroOrigin (const QWMatrix &matrix, int width, int height) +{ +#if DEBUG_KP_PIXMAP_FX + kdDebug () << "matrixWithZeroOrigin(w=" << width << ",h=" << height << ")" << endl; + kdDebug () << "\tmatrix: m11=" << matrix.m11 () + << " m12=" << matrix.m12 () + << " m21=" << matrix.m21 () + << " m22=" << matrix.m22 () + << " dx=" << matrix.dx () + << " dy=" << matrix.dy () + << endl; +#endif + // TODO: Should we be using QWMatrix::Areas? + QRect newRect = matrix.mapRect (QRect (0, 0, width, height)); +#if DEBUG_KP_PIXMAP_FX + kdDebug () << "\tnewRect=" << newRect << endl; +#endif + + QWMatrix translatedMatrix (matrix.m11 (), matrix.m12 (), matrix.m21 (), matrix.m22 (), + matrix.dx () - newRect.left (), matrix.dy () - newRect.top ()); + + return translatedMatrix; +} + +static QPixmap xForm (const QPixmap &pm, const QWMatrix &transformMatrix_, + const kpColor &backgroundColor, + int targetWidth, int targetHeight) +{ + QWMatrix transformMatrix = transformMatrix_; + +#if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "kppixmapfx.cpp: xForm(pm.size=" << pm.size () + << ",targetWidth=" << targetWidth + << ",targetHeight=" << targetHeight + << ")" + << endl; +#endif + // TODO: Should we be using QWMatrix::Areas? + QRect newRect = transformMatrix.map (pm.rect ()); +#if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\tmappedRect=" << newRect << endl; + +#endif + + QWMatrix scaleMatrix; + if (targetWidth > 0 && targetWidth != newRect.width ()) + { + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\tadjusting for targetWidth" << endl; + #endif + scaleMatrix.scale (double (targetWidth) / double (newRect.width ()), 1); + } + + if (targetHeight > 0 && targetHeight != newRect.height ()) + { + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\tadjusting for targetHeight" << endl; + #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? + QWMatrix wrongMatrix = transformMatrix * scaleMatrix; + QWMatrix 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 ())); + QWMatrix 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); + QWMatrix correctMatrix = scaleMatrix * transformMatrix; + + kdDebug () << "\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.map (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.map (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.map (pm.rect ()) + << endl + << "\tsupposedlyCorrectMatrix: m11=" << correctMatrix.m11 () + << " m12=" << correctMatrix.m12 () + << " m21=" << correctMatrix.m21 () + << " m22=" << correctMatrix.m22 () + << " dx=" << correctMatrix.dx () + << " dy=" << correctMatrix.dy () + << " rect=" << correctMatrix.map (pm.rect ()) + << endl; + #endif + + transformMatrix = transformMatrix * scaleMatrix; + + // TODO: Should we be using QWMatrix::Areas? + newRect = transformMatrix.map (pm.rect ()); + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\tnewRect after targetWidth,targetHeight adjust=" << newRect << endl; + #endif + } + + + QPixmap newPixmap (targetWidth > 0 ? targetWidth : newRect.width (), + targetHeight > 0 ? targetHeight : newRect.height ()); + if ((targetWidth > 0 && targetWidth != newRect.width ()) || + (targetHeight > 0 && targetHeight != newRect.height ())) + { + #if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "kppixmapfx.cpp: xForm(pm.size=" << pm.size () + << ",targetWidth=" << targetWidth + << ",targetHeight=" << targetHeight + << ") newRect=" << newRect + << " (you are a victim of rounding error)" + << endl; + #endif + } + + QBitmap newBitmapMask; + + if (backgroundColor.isOpaque ()) + newPixmap.fill (backgroundColor.toQColor ()); + + if (backgroundColor.isTransparent () || pm.mask ()) + { + newBitmapMask.resize (newPixmap.width (), newPixmap.height ()); + newBitmapMask.fill (backgroundColor.maskColor ()); + } + + QPainter painter (&newPixmap); +#if DEBUG_KP_PIXMAP_FX && 1 + kdDebug () << "\tmatrix: m11=" << transformMatrix.m11 () + << " m12=" << transformMatrix.m12 () + << " m21=" << transformMatrix.m21 () + << " m22=" << transformMatrix.m22 () + << " dx=" << transformMatrix.dx () + << " dy=" << transformMatrix.dy () + << endl; + const QWMatrix trueMatrix = QPixmap::trueMatrix (transformMatrix, + pm.width (), pm.height ()); + kdDebug () << "\ttrue matrix: m11=" << trueMatrix.m11 () + << " m12=" << trueMatrix.m12 () + << " m21=" << trueMatrix.m21 () + << " m22=" << trueMatrix.m22 () + << " dx=" << trueMatrix.dx () + << " dy=" << trueMatrix.dy () + << endl; +#endif + painter.setWorldMatrix (transformMatrix); +#if DEBUG_KP_PIXMAP_FX && 0 + kdDebug () << "\ttranslate top=" << painter.xForm (QPoint (0, 0)) << endl; + kdDebug () << "\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 + painter.drawPixmap (QPoint (0, 0), pm); + painter.end (); + + if (!newBitmapMask.isNull ()) + { + QPainter maskPainter (&newBitmapMask); + maskPainter.setWorldMatrix (transformMatrix); + maskPainter.drawPixmap (QPoint (0, 0), kpPixmapFX::getNonNullMask (pm)); + maskPainter.end (); + newPixmap.setMask (newBitmapMask); + } + + return newPixmap; +} + +// public static +QWMatrix kpPixmapFX::skewMatrix (int width, int height, double hangle, double vangle) +{ + if (fabs (hangle - 0) < kpPixmapFX::AngleInDegreesEpsilon && + fabs (vangle - 0) < kpPixmapFX::AngleInDegreesEpsilon) + { + return QWMatrix (); + } + + + /* 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. + * + */ + + //QWMatrix matrix (1, tan (KP_DEGREES_TO_RADIANS (vangle)), tan (KP_DEGREES_TO_RADIANS (hangle)), 1, 0, 0); + // I think this is clearer than above :) + QWMatrix matrix; + matrix.shear (tan (KP_DEGREES_TO_RADIANS (hangle)), + tan (KP_DEGREES_TO_RADIANS (vangle))); + + return matrixWithZeroOrigin (matrix, width, height); +} + +// public static +QWMatrix kpPixmapFX::skewMatrix (const QPixmap &pixmap, double hangle, double vangle) +{ + return kpPixmapFX::skewMatrix (pixmap.width (), pixmap.height (), hangle, vangle); +} + + +// public static +void kpPixmapFX::skew (QPixmap *destPixmapPtr, double hangle, double vangle, + const kpColor &backgroundColor, + int targetWidth, int targetHeight) +{ + if (!destPixmapPtr) + return; + + *destPixmapPtr = kpPixmapFX::skew (*destPixmapPtr, hangle, vangle, + backgroundColor, + targetWidth, targetHeight); +} + +// public static +QPixmap kpPixmapFX::skew (const QPixmap &pm, double hangle, double vangle, + const kpColor &backgroundColor, + int targetWidth, int targetHeight) +{ +#if DEBUG_KP_PIXMAP_FX + kdDebug () << "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 && + (targetWidth <= 0 && targetHeight <= 0)/*don't want to scale?*/) + { + return pm; + } + + if (fabs (hangle) > 90 - kpPixmapFX::AngleInDegreesEpsilon || + fabs (vangle) > 90 - kpPixmapFX::AngleInDegreesEpsilon) + { + kdError () << "kpPixmapFX::skew() passed hangle and/or vangle out of range (-90 < x < 90)" << endl; + return pm; + } + + + QWMatrix matrix = skewMatrix (pm, hangle, vangle); + + return ::xForm (pm, matrix, backgroundColor, targetWidth, targetHeight); +} + + +// public static +QWMatrix kpPixmapFX::rotateMatrix (int width, int height, double angle) +{ + if (fabs (angle - 0) < kpPixmapFX::AngleInDegreesEpsilon) + { + return QWMatrix (); + } + + QWMatrix matrix; + matrix.translate (width / 2, height / 2); + matrix.rotate (angle); + + return matrixWithZeroOrigin (matrix, width, height); +} + +// public static +QWMatrix kpPixmapFX::rotateMatrix (const QPixmap &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 -= ((int) angle) / 90 * 90; + + // "Impossible" situation? + if (angle < 0 || angle > 90) + { + kdError () << "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 + kdDebug () << "kpPixmapFX::isLosslessRotation(" << angleIn << ")" + << " residual angle=" << angle + << " returning " << ret + << endl; +#endif + return ret; +} + + +// public static +void kpPixmapFX::rotate (QPixmap *destPixmapPtr, double angle, + const kpColor &backgroundColor, + int targetWidth, int targetHeight) +{ + if (!destPixmapPtr) + return; + + *destPixmapPtr = kpPixmapFX::rotate (*destPixmapPtr, angle, + backgroundColor, + targetWidth, targetHeight); +} + +// public static +QPixmap kpPixmapFX::rotate (const QPixmap &pm, double angle, + const kpColor &backgroundColor, + int targetWidth, int targetHeight) +{ + if (fabs (angle - 0) < kpPixmapFX::AngleInDegreesEpsilon && + (targetWidth <= 0 && targetHeight <= 0)/*don't want to scale?*/) + { + return pm; + } + + + QWMatrix matrix = rotateMatrix (pm, angle); + + return ::xForm (pm, matrix, backgroundColor, targetWidth, targetHeight); +} + + +// public static +QWMatrix kpPixmapFX::flipMatrix (int width, int height, bool horz, bool vert) +{ + if (width <= 0 || height <= 0) + { + kdError () << "kpPixmapFX::flipMatrix() passed invalid dimensions" << endl; + return QWMatrix (); + } + + return QWMatrix (horz ? -1 : +1, // m11 + 0, // m12 + 0, // m21 + vert ? -1 : +1, // m22 + horz ? (width - 1) : 0, // dx + vert ? (height - 1) : 0); // dy +} + +// public static +QWMatrix kpPixmapFX::flipMatrix (const QPixmap &pixmap, bool horz, bool vert) +{ + return kpPixmapFX::flipMatrix (pixmap.width (), pixmap.height (), + horz, vert); +} + + +// public static +void kpPixmapFX::flip (QPixmap *destPixmapPtr, bool horz, bool vert) +{ + if (!horz && !vert) + return; + + *destPixmapPtr = kpPixmapFX::flip (*destPixmapPtr, horz, vert); +} + +// public static +QPixmap kpPixmapFX::flip (const QPixmap &pm, bool horz, bool vert) +{ + if (!horz && !vert) + return pm; + + return pm.xForm (flipMatrix (pm, horz, vert)); +} + +// public static +void kpPixmapFX::flip (QImage *destImagePtr, bool horz, bool vert) +{ + if (!horz && !vert) + return; + + *destImagePtr = kpPixmapFX::flip (*destImagePtr, horz, vert); +} + +// public static +QImage kpPixmapFX::flip (const QImage &img, bool horz, bool vert) +{ + if (!horz && !vert) + return img; + + return img.mirror (horz, vert); +} diff --git a/kolourpaint/pixmapfx/kppixmapfx.h b/kolourpaint/pixmapfx/kppixmapfx.h new file mode 100644 index 00000000..c083ee43 --- /dev/null +++ b/kolourpaint/pixmapfx/kppixmapfx.h @@ -0,0 +1,450 @@ + +/* + Copyright (c) 2003,2004,2005 Clarence Dang <[email protected]> + 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_PIXMAP_FX_H +#define KP_PIXMAP_FX_H + + +#include <qstring.h> + + +class QBitmap; +class QColor; +class QImage; +class QPointArray; +class QPixmap; +class QPoint; +class QRect; +class QString; +class QWidget; +class QWMatrix; + +class kpColor; +class kpSelection; + + +class kpPixmapFX +{ +public: + + // + // Overflow Resistant Arithmetic: + // + // Returns INT_MAX if <lhs> or <rhs> < 0 or if would overflow. + static int addDimensions (int lhs, int rhs); + static int multiplyDimensions (int lhs, int rhs); + + + // + // QPixmap Statistics + // + + // Returns the width * height. + static int pixmapArea (const QPixmap &pixmap); + static int pixmapArea (const QPixmap *pixmap); + static int pixmapArea (int width, int height); + + // Returns the estimated size of <pixmap> in pixmap memory. + static int pixmapSize (const QPixmap &pixmap); + static int pixmapSize (const QPixmap *pixmap); + static int pixmapSize (int width, int height, int depth); + + static int imageSize (const QImage &image); + static int imageSize (const QImage *image); + static int imageSize (int width, int height, int depth); + + static int selectionSize (const kpSelection &sel); + static int selectionSize (const kpSelection *sel); + + static int stringSize (const QString &string); + + static int pointArraySize (const QPointArray &points); + + + // + // QPixmap/QImage Conversion Functions + // + + // + // Converts <pixmap> to a QImage and returns it. + // + // WARNING: On an 8-bit screen: + // + // QPixmap result = convertToPixmap (convertToImage (pixmap)); + // + // <result> is slightly differently colored to <pixmap>. + // + // KolourPaint needs to convert to QImage occasionally as + // QImage allows KolourPaint to read pixels and because the QImage + // methods give reliable results and pixel-identical results on + // all platforms. The QPixmap paint engine has no such guarantee + // and even depends on the quality of the video driver. + // + // As a result, KolourPaint should not be used on an 8-bit screen. + // HITODO: Add warning on startup, like in KolourPaint/KDE4. + // + // This bug will be fixed when KolourPaint gets a proper image library, + // where QPixmap -> QImage -> QPixmap transitions will be not be needed. + static QImage convertToImage (const QPixmap &pixmap); + + // + // Dialog info for warning about data loss with convertToPixmap(). + // + struct WarnAboutLossInfo + { + // <moreColorsThanDisplayAndHasAlphaChannelMessage>: + // + // i18n ("The (image \"example.jpg\"|image from the clipboard)" + // " may have more colors than the current screen mode." + // " In order to display it, some colors may be changed." + // " Try increasing your screen depth to at least %1bpp." + // + // "\nIt also" + // + // " contains translucency which is not fully" + // " supported. The translucency data will be" + // " approximated with a 1-bit transparency mask.") + // + // <moreColorsThanDisplayMessage>: + // i18n ("The (image \"example.jpg\"|image from the clipboard)" + // " may have more colors than the current screen mode." + // " In order to display it, some colors may be changed." + // " Try increasing your screen depth to at least %1bpp.") + // + // <hasAlphaChannelMessage>: + // i18n ("The (image \"example.jpg\"|image from the clipboard)" + // " contains translucency which is not fully" + // " supported. The translucency data will be" + // " approximated with a 1-bit transparency mask.") + // + // <dontAskAgainPrefix>: + // + // Don'tAskAgain ID for dialog. + // + // <parent>: + // + // Dialog parent + // + WarnAboutLossInfo (const QString &moreColorsThanDisplayAndHasAlphaChannelMessage, + const QString &moreColorsThanDisplayMessage, + const QString &hasAlphaChannelMessage, + const QString &dontAskAgainPrefix, + QWidget *parent) + : + m_moreColorsThanDisplayAndHasAlphaChannelMessage ( + moreColorsThanDisplayAndHasAlphaChannelMessage), + m_moreColorsThanDisplayMessage ( + moreColorsThanDisplayMessage), + m_hasAlphaChannelMessage ( + hasAlphaChannelMessage), + m_dontAskAgainPrefix ( + dontAskAgainPrefix), + m_parent (parent), + m_isValid (true) + { + } + + WarnAboutLossInfo () + : m_parent (0), + m_isValid (false) + { + } + + ~WarnAboutLossInfo () + { + } + + + bool isValid () const { return m_isValid; } + + + QString m_moreColorsThanDisplayAndHasAlphaChannelMessage, + m_moreColorsThanDisplayMessage, + m_hasAlphaChannelMessage; + QString m_dontAskAgainPrefix; + QWidget *m_parent; + bool m_isValid; + }; + + // + // Converts <image> to a QPixmap of the current display's depth and + // returns it. + // + // If the flag <pretty> is set, it will dither the image making the + // returned pixmap look better but if the image has few colours + // (less than the screen can handle), this will be at the expense of + // exactness of conversion. + // + // This will automatically call ensureNoAlphaChannel(). + // + // Never use a foreign QPixmap that is offered to you - always get the + // foreign QImage and use this function to convert it to a sane QPixmap. + // + // <wali>, if specified, describes parameters for the dialog that comes + // up warning the user of data loss if the <image> contains translucency + // and/or more colors than the current display. + // + static QPixmap convertToPixmap (const QImage &image, bool pretty = false, + const WarnAboutLossInfo &wali = WarnAboutLossInfo ()); + + // Same as convertToPixmap() but tries as hard as possible to make the + // pixmap look like the original <image> - when in doubt, reads the + // config to see whether or not to dither (default: on). + // + // If you know for sure that <image> can be displayed losslessly on + // the screen, you should call convertToPixmap() with <pretty> = false + // instead. If you know for sure that <image> cannot be displayed + // losslessly, then call convertToPixmap() with <pretty> = true. + // + static QPixmap convertToPixmapAsLosslessAsPossible (const QImage &image, + const WarnAboutLossInfo &wali = WarnAboutLossInfo ()); + + + // Sets the RGB values of the pixels where <pixmap> is transparent to + // <transparentColor>. This has visually no effect on the <pixmap> + // unless the mask is lost. + static QPixmap pixmapWithDefinedTransparentPixels (const QPixmap &pixmap, + const QColor &transparentColor); + + + // + // Get/Set Parts of Pixmap + // + + + // + // Returns the pixel and mask data found at the <rect> in <pm>. + // + static QPixmap getPixmapAt (const QPixmap &pm, const QRect &rect); + + // + // Sets the pixel and mask data at <destRect> in <*destPixmapPtr> + // to <srcPixmap>. + // + static void setPixmapAt (QPixmap *destPixmapPtr, const QRect &destRect, + const QPixmap &srcPixmap); + + // + // Sets the pixel and mask data at the rectangle in <*destPixmapPtr>, + // with the top-left <destAt> and dimensions <srcPixmap.rect()>, + // to <srcPixmap>. + // + static void setPixmapAt (QPixmap *destPixmapPtr, const QPoint &destAt, + const QPixmap &srcPixmap); + static void setPixmapAt (QPixmap *destPixmapPtr, int destX, int destY, + const QPixmap &srcPixmap); + + // + // Draws <srcPixmap> on top of <*destPixmapPtr> at <destAt>. + // The mask of <*destPixmapPtr> is adjusted so that all opaque + // pixels in <srcPixmap> will be opaque in <*destPixmapPtr>. + // + static void paintPixmapAt (QPixmap *destPixmapPtr, const QPoint &destAt, + const QPixmap &srcPixmap); + static void paintPixmapAt (QPixmap *destPixmapPtr, int destX, int destY, + const QPixmap &srcPixmap); + + // + // Returns the colour of the pixel at <at> in <pm>. + // If the pixel is transparent, a value is returned such that + // kpTool::isColorTransparent(<return_value>) will return true. + // + static kpColor getColorAtPixel (const QPixmap &pm, const QPoint &at); + static kpColor getColorAtPixel (const QPixmap &pm, int x, int y); + + // + // Returns the color of the pixel at <at> in <img>. + // If the pixel is transparent, a value is returned such that + // kpTool::isColorTransparent(<return_value>) will return true. + // + static kpColor getColorAtPixel (const QImage &img, const QPoint &at); + static kpColor getColorAtPixel (const QImage &img, int x, int y); + + + // + // Mask Operations + // + + + // + // Removes <*destPixmapPtr>'s Alpha Channel and attempts to convert it + // to a mask. KolourPaint - and QPixmap to a great extent - does not + // support Alpha Channels - only masks. Call this whenever you get + // a pixmap from a foreign source; else all KolourPaint code will + // exhibit "undefined behaviour". + // + static void ensureNoAlphaChannel (QPixmap *destPixmapPtr); + + // + // Returns <pm>'s mask or a fully opaque mask (with <pm>'s dimensions) + // if <pm> does not have a mask. + // + static QBitmap getNonNullMask (const QPixmap &pm); + + // + // Ensures that <*destPixmapPtr> is transparent at <rect>. + // + static void ensureTransparentAt (QPixmap *destPixmapPtr, const QRect &destRect); + + // + // Sets the mask of <*destPixmapPtr> at the rectangle, with the + // top-left <destAt> and dimensions <srcMaskBitmap.rect()>, + // to transparent where <brushBitmap> is opaque. + // + // <brushPixmap> must be a QPixmap of depth 1 (or a QBitmap). + // + static void paintMaskTransparentWithBrush (QPixmap *destPixmapPtr, const QPoint &destAt, + const QPixmap &brushBitmap); + static void paintMaskTransparentWithBrush (QPixmap *destPixmapPtr, int destX, int destY, + const QPixmap &brushBitmap); + + // + // Ensures that <*destPixmapPtr> is opaque at <rect>. + // + static void ensureOpaqueAt (QPixmap *destPixmapPtr, const QRect &destRect); + + // + // Ensures that <srcPixmap>'s opaque pixels will be opaque if + // painted onto <*destPixmapPtr> at <destAt>. + // + static void ensureOpaqueAt (QPixmap *destPixmapPtr, const QPoint &destAt, + const QPixmap &srcPixmap); + static void ensureOpaqueAt (QPixmap *destPixmapPtr, int destX, int destY, + const QPixmap &srcPixmap); + + + // + // Effects + // + + + // + // Converts the image to grayscale. + // + static void convertToGrayscale (QPixmap *destPixmapPtr); + static QPixmap convertToGrayscale (const QPixmap &pm); + static void convertToGrayscale (QImage *destImagePtr); + static QImage convertToGrayscale (const QImage &img); + + // + // Fills an image in the given color. + // + static void fill (QPixmap *destPixmapPtr, const kpColor &color); + static QPixmap fill (const QPixmap &pm, const kpColor &color); + + // + // Resizes an image to the given width and height, + // filling any new areas with <backgroundColor> if <fillNewAreas> is set. + // + static void resize (QPixmap *destPixmapPtr, int w, int h, + const kpColor &backgroundColor, bool fillNewAreas = true); + static QPixmap resize (const QPixmap &pm, int w, int h, + const kpColor &backgroundColor, bool fillNewAreas = true); + + // + // Scales an image to the given width and height. + // If <pretty> is true, a smooth scale will be used. + // + static void scale (QPixmap *destPixmapPtr, int w, int h, bool pretty = false); + static QPixmap scale (const QPixmap &pm, int w, int h, bool pretty = false); + + + // The minimum difference between 2 angles (in degrees) such that they are + // considered different. This gives you at least enough precision to + // rotate an image whose width <= 10000 such that its height increases + // by just 1 (and similarly with height <= 10000 and width). + // + // Currently used for skew & rotate operations. + static double AngleInDegreesEpsilon; + + + // + // Skews an image. + // + // <hangle> horizontal angle clockwise (-90 < x < 90) + // <vangle> vertical angle clockwise (-90 < x < 90) + // <backgroundColor> color to fill new areas with + // <targetWidth> if > 0, the desired width of the resultant pixmap + // <targetHeight> if > 0, the desired height of the resultant pixmap + // + // Using <targetWidth> & <targetHeight> to generate preview pixmaps is + // significantly more efficient than skewing and then scaling yourself. + // + static QWMatrix skewMatrix (int width, int height, double hangle, double vangle); + static QWMatrix skewMatrix (const QPixmap &pixmap, double hangle, double vangle); + + static void skew (QPixmap *destPixmapPtr, double hangle, double vangle, + const kpColor &backgroundColor, + int targetWidth = -1, int targetHeight = -1); + static QPixmap skew (const QPixmap &pm, double hangle, double vangle, + const kpColor &backgroundColor, + int targetWidth = -1, int targetHeight = -1); + + // + // Rotates an image. + // + // <angle> clockwise angle to rotate by + // <backgroundColor> color to fill new areas with + // <targetWidth> if > 0, the desired width of the resultant pixmap + // <targetHeight> if > 0, the desired height of the resultant pixmap + // + // Using <targetWidth> & <targetHeight> to generate preview pixmaps is + // significantly more efficient than rotating and then scaling yourself. + // + static QWMatrix rotateMatrix (int width, int height, double angle); + static QWMatrix rotateMatrix (const QPixmap &pixmap, double angle); + + static bool isLosslessRotation (double angle); + + static void rotate (QPixmap *destPixmapPtr, double angle, + const kpColor &backgroundColor, + int targetWidth = -1, int targetHeight = -1); + static QPixmap rotate (const QPixmap &pm, double angle, + const kpColor &backgroundColor, + int targetWidth = -1, int targetHeight = -1); + + + // + // Flips an image in the given directions. + // + static QWMatrix flipMatrix (int width, int height, bool horz, bool vert); + static QWMatrix flipMatrix (const QPixmap &pixmap, bool horz, bool vert); + + // TODO: this kind of overloading is error prone + // e.g. QPixmap pixmap; + // kpPixmapFX::flip (pixmap, false, true); + // looks like it will flip vertically but does absolutely nothing! + // (should be &pixmap) + static void flip (QPixmap *destPixmapPtr, bool horz, bool vert); + static QPixmap flip (const QPixmap &pm, bool horz, bool vert); + static void flip (QImage *destImagePtr, bool horz, bool vert); + static QImage flip (const QImage &img, bool horz, bool vert); +}; + + +#endif // KP_PIXMAP_FX_H |