You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
447 lines
13 KiB
447 lines
13 KiB
|
|
/*
|
|
Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org>
|
|
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 <tqbuttongroup.h>
|
|
#include <tqcheckbox.h>
|
|
#include <tqimage.h>
|
|
#include <tqlayout.h>
|
|
#include <tqpixmap.h>
|
|
#include <tqradiobutton.h>
|
|
|
|
#include <kdebug.h>
|
|
#include <tdelocale.h>
|
|
|
|
#include <kppixmapfx.h>
|
|
|
|
|
|
TQImage convertImageDepth (const TQImage &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 TQt's braindead TQImage::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
|
|
TQRgb color0, color1;
|
|
bool color0Valid = false, color1Valid = false;
|
|
|
|
bool moreThan2Colors = false;
|
|
|
|
TQImage monoImage (image.width (), image.height (),
|
|
1/*depth*/, 2/*numColors*/, TQImage::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++)
|
|
{
|
|
TQRgb 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;
|
|
}
|
|
}
|
|
|
|
|
|
TQImage retImage = image.convertDepth (depth,
|
|
TQt::AutoColor |
|
|
(dither ? TQt::DiffuseDither : TQt::ThresholdDither) |
|
|
TQt::ThresholdAlphaDither |
|
|
(dither ? TQt::PreferDither : TQt::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
|
|
TQString 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 TQString();
|
|
}
|
|
}
|
|
|
|
|
|
// public static
|
|
void kpEffectReduceColorsCommand::apply (TQPixmap *destPixmapPtr, int depth, bool dither)
|
|
{
|
|
if (!destPixmapPtr)
|
|
return;
|
|
|
|
if (depth != 1 && depth != 8)
|
|
return;
|
|
|
|
|
|
TQImage image = kpPixmapFX::convertToImage (*destPixmapPtr);
|
|
|
|
|
|
image = ::convertImageDepth (image, depth, dither);
|
|
|
|
if (image.isNull ())
|
|
return;
|
|
|
|
|
|
TQPixmap pixmap = kpPixmapFX::convertToPixmap (image, false/*no dither*/);
|
|
|
|
|
|
// HACK: The above "image.convertDepth()" erases the Alpha Channel
|
|
// (at least for monochrome).
|
|
// tqpixmap.html says "alpha masks on monochrome images are ignored."
|
|
//
|
|
// Put the mask back.
|
|
//
|
|
if (destPixmapPtr->mask ())
|
|
pixmap.setMask (*destPixmapPtr->mask ());
|
|
|
|
*destPixmapPtr = pixmap;
|
|
}
|
|
|
|
// public static
|
|
TQPixmap kpEffectReduceColorsCommand::apply (const TQPixmap &pm, int depth, bool dither)
|
|
{
|
|
TQPixmap ret = pm;
|
|
apply (&ret, depth, dither);
|
|
return ret;
|
|
}
|
|
|
|
|
|
//
|
|
// kpEffectReduceColorsCommand implements kpColorEffectCommand interface
|
|
//
|
|
|
|
// protected virtual [base kpColorEffectCommand]
|
|
TQPixmap kpEffectReduceColorsCommand::applyColorEffect (const TQPixmap &pixmap)
|
|
{
|
|
return apply (pixmap, m_depth, m_dither);
|
|
}
|
|
|
|
|
|
//
|
|
// kpEffectReduceColorsWidget
|
|
//
|
|
|
|
kpEffectReduceColorsWidget::kpEffectReduceColorsWidget (bool actOnSelection,
|
|
kpMainWindow *mainWindow,
|
|
TQWidget *parent,
|
|
const char *name)
|
|
: kpColorEffectWidget (actOnSelection, mainWindow, parent, name)
|
|
{
|
|
TQVBoxLayout *lay = new TQVBoxLayout (this, marginHint (), spacingHint ());
|
|
|
|
|
|
m_blackAndWhiteRadioButton =
|
|
new TQRadioButton (i18n ("&Monochrome"), this);
|
|
|
|
m_blackAndWhiteDitheredRadioButton =
|
|
new TQRadioButton (i18n ("Mo&nochrome (dithered)"), this);
|
|
|
|
m_8BitRadioButton = new TQRadioButton (i18n ("256 co&lor"), this);
|
|
|
|
m_8BitDitheredRadioButton = new TQRadioButton (i18n ("256 colo&r (dithered)"), this);
|
|
|
|
m_24BitRadioButton = new TQRadioButton (i18n ("24-&bit color"), this);
|
|
|
|
|
|
TQButtonGroup *buttonGroup = new TQButtonGroup (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 = TQPixmap::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, TQ_SIGNAL (toggled (bool)),
|
|
this, TQ_SIGNAL (settingsChanged ()));
|
|
connect (m_blackAndWhiteDitheredRadioButton, TQ_SIGNAL (toggled (bool)),
|
|
this, TQ_SIGNAL (settingsChanged ()));
|
|
connect (m_8BitRadioButton, TQ_SIGNAL (toggled (bool)),
|
|
this, TQ_SIGNAL (settingsChanged ()));
|
|
connect (m_8BitDitheredRadioButton, TQ_SIGNAL (toggled (bool)),
|
|
this, TQ_SIGNAL (settingsChanged ()));
|
|
connect (m_24BitRadioButton, TQ_SIGNAL (toggled (bool)),
|
|
this, TQ_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]
|
|
TQString 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]
|
|
TQPixmap kpEffectReduceColorsWidget::applyColorEffect (const TQPixmap &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>
|
|
|