|
|
|
/*
|
|
|
|
* dlg_colorrange.cc - part of KimageShop^WKrayon^WChalk
|
|
|
|
*
|
|
|
|
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
#include <tqapplication.h>
|
|
|
|
#include <tqpushbutton.h>
|
|
|
|
#include <tqcheckbox.h>
|
|
|
|
#include <tqslider.h>
|
|
|
|
#include <tqcombobox.h>
|
|
|
|
#include <tqpixmap.h>
|
|
|
|
#include <tqimage.h>
|
|
|
|
#include <tqlabel.h>
|
|
|
|
#include <tqcolor.h>
|
|
|
|
#include <tqradiobutton.h>
|
|
|
|
|
|
|
|
#include <knuminput.h>
|
|
|
|
#include <klocale.h>
|
|
|
|
#include <kdebug.h>
|
|
|
|
#include <kaction.h>
|
|
|
|
|
|
|
|
#include <kis_canvas_subject.h>
|
|
|
|
#include <kis_iterators_pixel.h>
|
|
|
|
#include <kis_layer.h>
|
|
|
|
#include <kis_paint_device.h>
|
|
|
|
#include <kis_selection.h>
|
|
|
|
#include <kis_selection_manager.h>
|
|
|
|
#include <kis_types.h>
|
|
|
|
#include <kis_undo_adapter.h>
|
|
|
|
#include <kis_view.h>
|
|
|
|
#include <kis_colorspace.h>
|
|
|
|
#include <kis_profile.h>
|
|
|
|
#include <kis_color_conversions.h>
|
|
|
|
#include <kis_selected_transaction.h>
|
|
|
|
#include <kis_cursor.h>
|
|
|
|
|
|
|
|
#include "dlg_colorrange.h"
|
|
|
|
#include "wdg_colorrange.h"
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
// XXX: Poynton says: hsv/hls is not what one ought to use for colour calculations.
|
|
|
|
// Unfortunately, I don't know enough to be able to use anything else.
|
|
|
|
|
|
|
|
bool isReddish(int h)
|
|
|
|
{
|
|
|
|
return ((h > 330 && h < 360) || ( h > 0 && h < 40));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isYellowish(int h)
|
|
|
|
{
|
|
|
|
return (h> 40 && h < 65);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isGreenish(int h)
|
|
|
|
{
|
|
|
|
return (h > 70 && h < 155);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isCyanish(int h)
|
|
|
|
{
|
|
|
|
return (h > 150 && h < 190);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isBlueish(int h)
|
|
|
|
{
|
|
|
|
return (h > 185 && h < 270);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isMagentaish(int h)
|
|
|
|
{
|
|
|
|
return (h > 265 && h < 330);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isHighlight(int v)
|
|
|
|
{
|
|
|
|
return (v > 200);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isMidTone(int v)
|
|
|
|
{
|
|
|
|
return (v > 100 && v < 200);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isShadow(int v)
|
|
|
|
{
|
|
|
|
return (v < 100);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
TQ_UINT32 matchColors(const TQColor & c, enumAction action)
|
|
|
|
{
|
|
|
|
int r = c.red();
|
|
|
|
int g = c.green();
|
|
|
|
int b = c.blue();
|
|
|
|
|
|
|
|
int h, s, v;
|
|
|
|
rgb_to_hsv(r, g, b, &h, &s, &v);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// XXX: Map the degree in which the colors conform to the requirement
|
|
|
|
// to a range of selectedness between 0 and 255
|
|
|
|
|
|
|
|
// XXX: Implement out-of-gamut using lcms
|
|
|
|
|
|
|
|
switch(action) {
|
|
|
|
|
|
|
|
case REDS:
|
|
|
|
if (isReddish(h))
|
|
|
|
return MAX_SELECTED;
|
|
|
|
else
|
|
|
|
return MIN_SELECTED;
|
|
|
|
case YELLOWS:
|
|
|
|
if (isYellowish(h)) {
|
|
|
|
return MAX_SELECTED;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return MIN_SELECTED;
|
|
|
|
case GREENS:
|
|
|
|
if (isGreenish(h))
|
|
|
|
return MAX_SELECTED;
|
|
|
|
else
|
|
|
|
return MIN_SELECTED;
|
|
|
|
case CYANS:
|
|
|
|
if (isCyanish(h))
|
|
|
|
return MAX_SELECTED;
|
|
|
|
else
|
|
|
|
return MIN_SELECTED;
|
|
|
|
case BLUES:
|
|
|
|
if (isBlueish(h))
|
|
|
|
return MAX_SELECTED;
|
|
|
|
else
|
|
|
|
return MIN_SELECTED;
|
|
|
|
case MAGENTAS:
|
|
|
|
if (isMagentaish(h))
|
|
|
|
return MAX_SELECTED;
|
|
|
|
else
|
|
|
|
return MIN_SELECTED;
|
|
|
|
case HIGHLIGHTS:
|
|
|
|
if (isHighlight(v))
|
|
|
|
return MAX_SELECTED;
|
|
|
|
else
|
|
|
|
return MIN_SELECTED;
|
|
|
|
case MIDTONES:
|
|
|
|
if (isMidTone(v))
|
|
|
|
return MAX_SELECTED;
|
|
|
|
else
|
|
|
|
return MIN_SELECTED;
|
|
|
|
case SHADOWS:
|
|
|
|
if (isShadow(v))
|
|
|
|
return MAX_SELECTED;
|
|
|
|
else
|
|
|
|
return MIN_SELECTED;
|
|
|
|
};
|
|
|
|
|
|
|
|
return MIN_SELECTED;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DlgColorRange::DlgColorRange( KisView * view, KisPaintDeviceSP dev, TQWidget * tqparent, const char * name)
|
|
|
|
: super (tqparent, name, true, i18n("Color Range"), Ok | Cancel, Ok)
|
|
|
|
{
|
|
|
|
m_dev = dev;
|
|
|
|
m_view = view;
|
|
|
|
|
|
|
|
m_subject = view->canvasSubject();
|
|
|
|
|
|
|
|
m_page = new WdgColorRange(this, "color_range");
|
|
|
|
Q_CHECK_PTR(m_page);
|
|
|
|
|
|
|
|
setCaption(i18n("Color Range"));
|
|
|
|
setMainWidget(m_page);
|
|
|
|
resize(m_page->tqsizeHint());
|
|
|
|
|
|
|
|
if (m_dev->image()->undo()) m_transaction = new KisSelectedTransaction(i18n("Select by Color Range"), m_dev);
|
|
|
|
|
|
|
|
if(! m_dev->hasSelection())
|
|
|
|
m_dev->selection()->clear();
|
|
|
|
m_selection = m_dev->selection();
|
|
|
|
|
|
|
|
updatePreview();
|
|
|
|
|
|
|
|
m_invert = false;
|
|
|
|
m_mode = SELECTION_ADD;
|
|
|
|
m_currentAction = REDS;
|
|
|
|
|
|
|
|
connect(this, TQT_SIGNAL(okClicked()),
|
|
|
|
this, TQT_SLOT(okClicked()));
|
|
|
|
|
|
|
|
connect(this, TQT_SIGNAL(cancelClicked()),
|
|
|
|
this, TQT_SLOT(cancelClicked()));
|
|
|
|
|
|
|
|
connect(m_page->chkInvert, TQT_SIGNAL(clicked()),
|
|
|
|
this, TQT_SLOT(slotInvertClicked()));
|
|
|
|
|
|
|
|
connect(m_page->cmbSelect, TQT_SIGNAL(activated(int)),
|
|
|
|
this, TQT_SLOT(slotSelectionTypeChanged(int)));
|
|
|
|
|
|
|
|
connect (m_page->radioAdd, TQT_SIGNAL(toggled(bool)),
|
|
|
|
this, TQT_SLOT(slotAdd(bool)));
|
|
|
|
|
|
|
|
connect (m_page->radioSubtract, TQT_SIGNAL(toggled(bool)),
|
|
|
|
this, TQT_SLOT(slotSubtract(bool)));
|
|
|
|
|
|
|
|
connect (m_page->bnSelect, TQT_SIGNAL(clicked()),
|
|
|
|
this, TQT_SLOT(slotSelectClicked()));
|
|
|
|
|
|
|
|
connect (m_page->bnDeselect, TQT_SIGNAL(clicked()),
|
|
|
|
this, TQT_SLOT(slotDeselectClicked()));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
DlgColorRange::~DlgColorRange()
|
|
|
|
{
|
|
|
|
delete m_page;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DlgColorRange::updatePreview()
|
|
|
|
{
|
|
|
|
if (!m_selection) return;
|
|
|
|
|
|
|
|
TQ_INT32 x, y, w, h;
|
|
|
|
m_dev->exactBounds(x, y, w, h);
|
|
|
|
TQPixmap pix = TQPixmap(m_selection->maskImage().smoothScale(350, 350, TQ_ScaleMin));
|
|
|
|
m_subject->canvasController()->updateCanvas();
|
|
|
|
m_page->pixSelection->setPixmap(pix);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DlgColorRange::okClicked()
|
|
|
|
{
|
|
|
|
m_dev->setDirty();
|
|
|
|
m_dev->emitSelectionChanged();
|
|
|
|
|
|
|
|
if (m_dev->image()->undo()) m_subject->undoAdapter()->addCommand(m_transaction);
|
|
|
|
accept();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DlgColorRange::cancelClicked()
|
|
|
|
{
|
|
|
|
if (m_dev->image()->undo()) m_transaction->unexecute();
|
|
|
|
|
|
|
|
m_subject->canvasController()->updateCanvas();
|
|
|
|
reject();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DlgColorRange::slotInvertClicked()
|
|
|
|
{
|
|
|
|
m_invert = m_page->chkInvert->isChecked();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DlgColorRange::slotSelectionTypeChanged(int index)
|
|
|
|
{
|
|
|
|
m_currentAction = (enumAction)index;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DlgColorRange::slotSubtract(bool on)
|
|
|
|
{
|
|
|
|
if (on)
|
|
|
|
m_mode = SELECTION_SUBTRACT;
|
|
|
|
}
|
|
|
|
void DlgColorRange::slotAdd(bool on)
|
|
|
|
{
|
|
|
|
if (on)
|
|
|
|
m_mode = SELECTION_ADD;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DlgColorRange::slotSelectClicked()
|
|
|
|
{
|
|
|
|
TQApplication::setOverrideCursor(KisCursor::waitCursor());
|
|
|
|
// XXX: Multithread this!
|
|
|
|
TQ_INT32 x, y, w, h;
|
|
|
|
m_dev->exactBounds(x, y, w, h);
|
|
|
|
KisColorSpace * cs = m_dev->colorSpace();
|
|
|
|
TQ_UINT8 opacity;
|
|
|
|
for (int y2 = y; y2 < h - y; ++y2) {
|
|
|
|
KisHLineIterator hiter = m_dev->createHLineIterator(x, y2, w, false);
|
|
|
|
KisHLineIterator selIter = m_selection ->createHLineIterator(x, y2, w, true);
|
|
|
|
while (!hiter.isDone()) {
|
|
|
|
TQColor c;
|
|
|
|
|
|
|
|
cs->toTQColor(hiter.rawData(), &c, &opacity);
|
|
|
|
// Don't try to select transparent pixels.
|
|
|
|
if (opacity > OPACITY_TRANSPARENT) {
|
|
|
|
TQ_UINT8 match = matchColors(c, m_currentAction);
|
|
|
|
|
|
|
|
if (match) {
|
|
|
|
// Personally, I think the invert option a bit silly. But it's possible I don't quite understand it. BSAR.
|
|
|
|
if (!m_invert) {
|
|
|
|
if (m_mode == SELECTION_ADD) {
|
|
|
|
*(selIter.rawData()) = match;
|
|
|
|
}
|
|
|
|
else if (m_mode == SELECTION_SUBTRACT) {
|
|
|
|
TQ_UINT8 selectedness = *(selIter.rawData());
|
|
|
|
if (match < selectedness) {
|
|
|
|
*(selIter.rawData()) = selectedness - match;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
*(selIter.rawData()) = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (m_mode == SELECTION_ADD) {
|
|
|
|
TQ_UINT8 selectedness = *(selIter.rawData());
|
|
|
|
if (match < selectedness) {
|
|
|
|
*(selIter.rawData()) = selectedness - match;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
*(selIter.rawData()) = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (m_mode == SELECTION_SUBTRACT) {
|
|
|
|
*(selIter.rawData()) = match;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
++hiter;
|
|
|
|
++selIter;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
updatePreview();
|
|
|
|
TQApplication::restoreOverrideCursor();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DlgColorRange::slotDeselectClicked()
|
|
|
|
{
|
|
|
|
m_dev->selection()->clear();
|
|
|
|
updatePreview();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#include "dlg_colorrange.moc"
|