#include "adjustlevelspopup.h"
// Tnz6 includes
#include "tapp.h"
#include "cellselection.h"
#include "filmstripselection.h"
#include "menubarcommandids.h"
// TnzQt includes
#include "toonzqt/histogram.h"
#include "toonzqt/marksbar.h"
#include "toonzqt/menubarcommand.h"
#include "toonzqt/tselectionhandle.h"
#include "toonzqt/intfield.h"
#include "toonzqt/icongenerator.h"
// TnzLib includes
#include "toonz/txshcell.h"
#include "toonz/txshsimplelevel.h"
#include "toonz/tframehandle.h"
#include "toonz/txshlevelhandle.h"
#include "toonz/tcolumnhandle.h"
#include "toonz/txsheethandle.h"
// TnzCore includes
#include "trasterimage.h"
#include "trop.h"
#include "tundo.h"
#include "timagecache.h"
// Qt includes
#include <QPushButton>
#include <QSplitter>
#include <QScrollArea>
#include <QMainWindow>
//**************************************************************************
// Local namespace stuff
//**************************************************************************
namespace
{
void resetMarksBar(MarksBar *marksBar)
{
QVector<int> &values = marksBar->values();
values[0] = 0;
values[1] = 255;
}
//--------------------------------------------------------------
//Get the exact range for specified value
void getRange(const QVector<int> &values, int &min, int &max)
{
int size = values.size();
for (min = 0; min < size; ++min) {
if (values[min])
break;
}
for (max = size - 1; max >= 0; --max) {
if (values[max])
break;
}
}
//--------------------------------------------------------------
//Get the threshold-permissive range for values.
void getRange(const QVector<int> &values, int threshold, int &min, int &max)
{
int val, size = values.size(), count;
count = values[0]; //Always Consent 1 value cut
for (min = 1; min < size; ++min) {
val = values[min];
count += val;
if (val && count > threshold) //Then, stop before a positive value when thresh is exceeded
break;
}
count = values[size - 1];
for (max = size - 2; max >= 0; --max) {
val = values[max];
count += val;
if (val && count > threshold)
break;
}
}
} //namespace
//**************************************************************************
// Adjust Levels Swatch
//**************************************************************************
class AdjustLevelsPopup::Swatch : public PlaneViewer
{
TRasterP m_ras;
public:
Swatch(QWidget *parent = 0) : PlaneViewer(parent)
{
setBgColor(TPixel32::White, TPixel32::White);
}
TRasterP raster() const { return m_ras; }
TRasterP &raster() { return m_ras; }
void paintGL()
{
drawBackground();
if (m_ras) {
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
//Note GL_ONE instead of GL_SRC_ALPHA: it's needed since the input
//image is supposedly premultiplied - and it works because the
//viewer's background is opaque.
//See tpixelutils.h's overPixT function for comparison.
pushGLWorldCoordinates();
draw(m_ras);
popGLCoordinates();
glDisable(GL_BLEND);
}
}
};
//**************************************************************************
// EditableMarksBar implementation
//**************************************************************************
EditableMarksBar::EditableMarksBar(QWidget *parent)
: QFrame(parent)
{
QVBoxLayout *layout = new QVBoxLayout;
layout->setMargin(0);
layout->setSpacing(2);
setLayout(layout);
//Add MarksBar
m_marksBar = new MarksBar;
m_marksBar->setContentsMargins(5, 0, 6, 0);
layout->addWidget(m_marksBar);
//Customize it
m_marksBar->setRange(0, 256, 2);
QVector<int> &values = m_marksBar->values();
values.push_back(0);
values.push_back(256);
QVector<QColor> &colors = m_marksBar->colors();
colors.fill(Qt::black, 2);
//MarksBar dominates values change notifications
bool ret = connect(m_marksBar, SIGNAL(marksUpdated()), this, SLOT(updateFields()));
ret = ret && connect(m_marksBar, SIGNAL(marksUpdated()), this, SIGNAL(paramsChanged()));
//Add fields layout
QHBoxLayout *hLayout = new QHBoxLayout;
hLayout->setMargin(0);
hLayout->setContentsMargins(4, 0, 5, 0);
layout->addLayout(hLayout);
m_fields[0] = new DVGui::IntLineEdit;
hLayout->addWidget(m_fields[0]);
ret = ret && connect(m_fields[0], SIGNAL(editingFinished()), this, SLOT(onFieldEdited()));
hLayout->addStretch(1);
m_fields[1] = new DVGui::IntLineEdit;
hLayout->addWidget(m_fields[1]);
ret = ret && connect(m_fields[1], SIGNAL(editingFinished()), this, SLOT(onFieldEdited()));
updateFields();
}
//--------------------------------------------------------------
EditableMarksBar::~EditableMarksBar()
{
}
//--------------------------------------------------------------
void EditableMarksBar::getValues(int *values) const
{
const QVector<int> &marks = m_marksBar->values();
values[0] = marks[0];
values[1] = marks[1] - 1;
}
//--------------------------------------------------------------
void EditableMarksBar::updateFields()
{
const QVector<int> &values = m_marksBar->values();
m_fields[0]->setValue(values[0]);
m_fields[1]->setValue(values[1] - 1);
//No emission - as it's the marksbar that dominate signal emission
}
//--------------------------------------------------------------
void EditableMarksBar::onFieldEdited()
{
QVector<int> &values = m_marksBar->values();
//Copy the values to the marksBar
values[0] = m_fields[0]->getValue();
values[1] = m_fields[1]->getValue() + 1;
m_marksBar->conformValues();
emit paramsChanged();
}
//**************************************************************************
// Adjust Levels Popup implementation
//**************************************************************************
AdjustLevelsPopup::AdjustLevelsPopup()
: DVGui::Dialog(TApp::instance()->getMainWindow(), true, false, "AdjustLevels"), m_thresholdD(0.005) //0.5% of the image size
{
int i, j;
setWindowTitle(tr("Adjust Levels"));
setLabelWidth(0);
setModal(false);
setTopMargin(0);
setTopSpacing(0);
beginVLayout();
QSplitter *splitter = new QSplitter(Qt::Vertical);
splitter->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding));
addWidget(splitter);
endVLayout();
//------------------------- Top Layout --------------------------
QScrollArea *scrollArea = new QScrollArea(splitter);
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scrollArea->setWidgetResizable(true);
scrollArea->setMinimumWidth(450);
splitter->addWidget(scrollArea);
splitter->setStretchFactor(0, 1);
QFrame *topWidget = new QFrame(scrollArea);
scrollArea->setWidget(topWidget);
QVBoxLayout *topVLayout = new QVBoxLayout(topWidget); //Needed to justify at top
topWidget->setLayout(topVLayout);
QHBoxLayout *topLayout = new QHBoxLayout(topWidget);
topVLayout->addLayout(topLayout);
topVLayout->addStretch(1);
//------------------------- Histogram ---------------------------
m_histogram = new Histogram(topWidget);
topLayout->addWidget(m_histogram);
//------------------------- Mark Bars ---------------------------
MarksBar *histogramMarksBar, *colorBarMarksBar;
QVBoxLayout *histogramViewLayout;
for (i = 0; i < 5; ++i) {
HistogramView *view = m_histogram->getHistograms()->getHistogramView(i);
histogramViewLayout = static_cast<QVBoxLayout *>(view->layout());
//Don't draw channel numbers
view->channelBar()->setDrawNumbers(false);
for (j = 0; j < 2; ++j) {
EditableMarksBar *editableMarksBar = m_marksBar[j + (i << 1)] = new EditableMarksBar;
MarksBar *marksBar = editableMarksBar->marksBar();
//Set margins up to cover the histogram
editableMarksBar->layout()->setContentsMargins(6, 0, 5, 0);
connect(editableMarksBar, SIGNAL(paramsChanged()), this, SLOT(onParamsChanged()));
histogramViewLayout->insertWidget(1 + (j << 1), editableMarksBar);
}
}
//------------------------- View Widget -------------------------
//NOTE: It's IMPORTANT that parent widget is supplied. It's somewhat
//used by QSplitter to decide the initial widget sizes...
m_viewer = new Swatch(splitter);
m_viewer->setMinimumHeight(150);
m_viewer->setFocusPolicy(Qt::WheelFocus);
splitter->addWidget(m_viewer);
//--------------------------- Buttons ---------------------------
QVBoxLayout *buttonsLayout = new QVBoxLayout(topWidget);
topLayout->addLayout(buttonsLayout);
buttonsLayout->addSpacing(50);
QPushButton *clampRange = new QPushButton(tr("Clamp"), topWidget);
clampRange->setMinimumSize(65, 25);
buttonsLayout->addWidget(clampRange);
connect(clampRange, SIGNAL(clicked(bool)), this, SLOT(clampRange()));
QPushButton *autoAdjust = new QPushButton(tr("Auto"), topWidget);
autoAdjust->setMinimumSize(65, 25);
buttonsLayout->addWidget(autoAdjust);
connect(autoAdjust, SIGNAL(clicked(bool)), this, SLOT(autoAdjust()));
QPushButton *resetBtn = new QPushButton(tr("Reset"), topWidget);
resetBtn->setMinimumSize(65, 25);
buttonsLayout->addWidget(resetBtn);
connect(resetBtn, SIGNAL(clicked(bool)), this, SLOT(reset()));
buttonsLayout->addStretch(1);
m_okBtn = new QPushButton(tr("Apply"));
addButtonBarWidget(m_okBtn);
connect(m_okBtn, SIGNAL(clicked()), this, SLOT(apply()));
//Finally, acquire current selection
acquireRaster();
m_viewer->resize(0, 350);
resize(600, 700);
}
//--------------------------------------------------------------
void AdjustLevelsPopup::showEvent(QShowEvent *se)
{
TSelectionHandle *selectionHandle = TApp::instance()->getCurrentSelection();
connect(selectionHandle, SIGNAL(selectionChanged(TSelection *)),
this, SLOT(onSelectionChanged()));
acquireRaster();
}
//--------------------------------------------------------------
void AdjustLevelsPopup::hideEvent(QHideEvent *he)
{
Dialog::hideEvent(he);
TSelectionHandle *selectionHandle = TApp::instance()->getCurrentSelection();
disconnect(selectionHandle, SIGNAL(selectionChanged(TSelection *)),
this, SLOT(onSelectionChanged()));
m_inputRas = TRasterP();
m_viewer->raster() = TRasterP();
}
//--------------------------------------------------------------
void AdjustLevelsPopup::acquireRaster()
{
//Retrieve current selection
TApp *app = TApp::instance();
TSelection *selection = app->getCurrentSelection()->getSelection();
TCellSelection *cellSelection = dynamic_cast<TCellSelection *>(selection);
TFilmstripSelection *filmstripSelection = dynamic_cast<TFilmstripSelection *>(selection);
//Retrieve the input raster
m_inputRas = TRasterP();
if (cellSelection) {
TXsheet *xsh = app->getCurrentXsheet()->getXsheet();
TXshCell cell = xsh->getCell(app->getCurrentFrame()->getFrameIndex(), app->getCurrentColumn()->getColumnIndex());
TRasterImageP rasImage = cell.getImage(true);
if (rasImage && rasImage->getRaster())
m_inputRas = rasImage->getRaster();
} else if (filmstripSelection) {
TXshSimpleLevel *simpleLevel = app->getCurrentLevel()->getSimpleLevel();
if (simpleLevel) {
TRasterImageP rasImage = (TRasterImageP)simpleLevel->getFrame(app->getCurrentFrame()->getFid(), true);
if (rasImage && rasImage->getRaster())
m_inputRas = rasImage->getRaster();
}
}
if (m_inputRas) {
m_threshold = m_inputRas->getLx() * m_inputRas->getLy() * m_thresholdD;
m_okBtn->setEnabled(true);
} else {
m_inputRas = TRasterP();
m_okBtn->setEnabled(false);
}
//Build histograms
m_histogram->setRaster(m_inputRas);
//Update the corresponding processed image in the viewer
updateProcessedImage();
}
//--------------------------------------------------------------
void AdjustLevelsPopup::setThreshold(double t)
{
m_thresholdD = t;
if (m_inputRas)
m_threshold = m_inputRas->getLx() * m_inputRas->getLy() * m_thresholdD;
}
//--------------------------------------------------------------
void AdjustLevelsPopup::getParameters(int *in0, int *in1, int *out0, int *out1)
{
int p[2];
int i, j;
for (i = j = 0; i < 10; i += 2, ++j) {
m_marksBar[i]->getValues(p);
in0[j] = p[0], in1[j] = p[1];
m_marksBar[i + 1]->getValues(p);
out0[j] = p[0], out1[j] = p[1];
}
}
//--------------------------------------------------------------
void AdjustLevelsPopup::updateProcessedImage()
{
if (!m_inputRas) {
m_viewer->raster() = TRasterP();
m_viewer->update();
return;
}
//Allocate a conformant output, if necessary
TRasterP &outRas = m_viewer->raster();
if (!outRas ||
outRas->getPixelSize() != m_inputRas->getPixelSize() ||
outRas->getSize() != m_inputRas->getSize())
outRas = m_inputRas->create(m_inputRas->getLx(), m_inputRas->getLy());
//Perform the operation preview
int in0[5], in1[5], out0[5], out1[5];
getParameters(in0, in1, out0, out1);
TRop::rgbmAdjust(outRas, m_inputRas, in0, in1, out0, out1);
//Update the swatch
m_viewer->update();
}
//--------------------------------------------------------------
void AdjustLevelsPopup::onSelectionChanged()
{
acquireRaster();
}
//--------------------------------------------------------------
//Params were changed. Content must be updated.
void AdjustLevelsPopup::onParamsChanged()
{
updateProcessedImage();
}
//--------------------------------------------------------------
//Reset ALL channels
void AdjustLevelsPopup::reset()
{
int i;
for (i = 0; i < 10; ++i) {
EditableMarksBar *editableMarksBar = m_marksBar[i];
QVector<int> &marks = editableMarksBar->marksBar()->values();
marks[0] = 0, marks[1] = 256;
editableMarksBar->updateFields();
}
onParamsChanged();
update();
}
//--------------------------------------------------------------
void AdjustLevelsPopup::clampRange()
{
if (!m_inputRas)
return;
Histograms *histograms = m_histogram->getHistograms();
int channelIdx = histograms->currentIndex();
int inputBarIdx = (channelIdx << 1);
EditableMarksBar *editableMarksBar = m_marksBar[inputBarIdx];
int min, max;
//Clamp histogram
const QVector<int> &values = histograms->getHistogramView(channelIdx)->values();
::getRange(values, min, max);
QVector<int> &marks = editableMarksBar->marksBar()->values();
if (min < max)
marks[0] = min, marks[1] = max + 1;
else
marks[0] = 0, marks[1] = 256;
editableMarksBar->updateFields();
onParamsChanged();
update();
}
//--------------------------------------------------------------
void AdjustLevelsPopup::autoAdjust()
{
if (!m_inputRas)
return;
Histograms *histograms = m_histogram->getHistograms();
int channelIdx = histograms->currentIndex();
int inputBarIdx = (channelIdx << 1);
EditableMarksBar *editableMarksBar = m_marksBar[inputBarIdx];
int min, max;
//Clamp histogram
const QVector<int> &values = histograms->getHistogramView(channelIdx)->values();
if (channelIdx == 0) {
int minR, maxR, minG, maxG, minB, maxB;
::getRange(histograms->getHistogramView(1)->values(), m_threshold, minR, maxR);
::getRange(histograms->getHistogramView(2)->values(), m_threshold, minG, maxG);
::getRange(histograms->getHistogramView(3)->values(), m_threshold, minB, maxB);
min = tmin(minR, minG, minB);
max = tmax(maxR, maxG, maxB);
} else
::getRange(values, m_threshold, min, max);
QVector<int> &marks = editableMarksBar->marksBar()->values();
if (min < max)
marks[0] = min, marks[1] = max + 1;
else
marks[0] = 0, marks[1] = 256;
editableMarksBar->updateFields();
onParamsChanged();
update();
}
//**************************************************************************
// TGBMScale Undo
//**************************************************************************
class AdjustLevelsUndo : public TUndo
{
int m_in0[5], m_in1[5], m_out0[5], m_out1[5];
int m_r, m_c;
QString m_rasId;
int m_rasSize;
public:
AdjustLevelsUndo(int *in0, int *in1, int *out0, int *out1, int r, int c, TRasterP ras);
~AdjustLevelsUndo();
void undo() const;
void redo() const;
int getSize() const { return sizeof(*this) + m_rasSize; }
};
//--------------------------------------------------------------
AdjustLevelsUndo::AdjustLevelsUndo(int *in0, int *in1, int *out0, int *out1,
int r, int c, TRasterP ras)
: m_r(r), m_c(c), m_rasSize(ras->getLx() * ras->getLy() * ras->getPixelSize())
{
memcpy(m_in0, in0, sizeof(m_in0));
memcpy(m_in1, in1, sizeof(m_in1));
memcpy(m_out0, out0, sizeof(m_out0));
memcpy(m_out1, out1, sizeof(m_out1));
static int counter = 0;
m_rasId = QString("AdjustLevelsUndo") + QString::number(++counter);
TImageCache::instance()->add(m_rasId, TRasterImageP(ras));
}
//--------------------------------------------------------------
AdjustLevelsUndo::~AdjustLevelsUndo()
{
TImageCache::instance()->remove(m_rasId);
}
//--------------------------------------------------------------
void AdjustLevelsUndo::undo() const
{
TXsheet *xsheet = TApp::instance()->getCurrentXsheet()->getXsheet();
TXshCell cell = xsheet->getCell(m_r, m_c);
TRasterImageP rasImage = (TRasterImageP)cell.getImage(true);
if (!rasImage)
return; //...Should never happen, though...
rasImage->setRaster(((TRasterImageP)TImageCache::instance()->get(m_rasId, true))->getRaster()->clone());
TXshSimpleLevel *simpleLevel = cell.getSimpleLevel();
assert(simpleLevel);
simpleLevel->touchFrame(cell.getFrameId());
simpleLevel->setDirtyFlag(true);
IconGenerator::instance()->invalidate(simpleLevel, cell.getFrameId());
if (m_isLastInBlock)
TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
}
//--------------------------------------------------------------
void AdjustLevelsUndo::redo() const
{
TXsheet *xsheet = TApp::instance()->getCurrentXsheet()->getXsheet();
TXshCell cell = xsheet->getCell(m_r, m_c);
TRasterImageP rasImage = (TRasterImageP)cell.getImage(true);
if (!rasImage)
return;
TRasterP ras = rasImage->getRaster();
if (!ras)
return;
TRop::rgbmAdjust(ras, ras, m_in0, m_in1, m_out0, m_out1);
TXshSimpleLevel *simpleLevel = cell.getSimpleLevel();
assert(simpleLevel);
simpleLevel->touchFrame(cell.getFrameId());
simpleLevel->setDirtyFlag(true);
IconGenerator::instance()->invalidate(simpleLevel, cell.getFrameId());
if (m_isLastInBlock)
TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
}
//**************************************************************************
// Apply stuff
//**************************************************************************
void AdjustLevelsPopup::apply()
{
//Retrieve parameters
int in0[5], in1[5], out0[5], out1[5];
getParameters(in0, in1, out0, out1);
//Operate depending on the selection kind
TCellSelection *cellSelection = dynamic_cast<TCellSelection *>(TSelection::getCurrent());
if (cellSelection) {
std::set<TRasterImage *> images; //Multiple cells may yield the same image...
int r0, c0, r1, c1;
cellSelection->getSelectedCells(r0, c0, r1, c1);
TXsheet *xsheet = TApp::instance()->getCurrentXsheet()->getXsheet();
bool oneImageChanged = false;
TUndoManager::manager()->beginBlock();
{
int c, r;
for (c = c0; c <= c1; c++) {
for (r = r0; r <= r1; r++) {
const TXshCell &cell = xsheet->getCell(r, c);
TRasterImageP rasImage = (TRasterImageP)cell.getImage(true);
if (!rasImage)
continue;
if (images.find(rasImage.getPointer()) != images.end())
continue;
TRasterP ras = rasImage->getRaster();
if (!ras)
continue;
images.insert(rasImage.getPointer());
oneImageChanged = true;
TUndoManager::manager()->add(new AdjustLevelsUndo(in0, in1, out0, out1, r, c, ras->clone()));
TRop::rgbmAdjust(ras, ras, in0, in1, out0, out1);
TXshSimpleLevel *simpleLevel = cell.getSimpleLevel();
assert(simpleLevel);
simpleLevel->touchFrame(cell.getFrameId());
simpleLevel->setDirtyFlag(true);
IconGenerator::instance()->invalidate(simpleLevel, cell.getFrameId());
}
}
}
TUndoManager::manager()->endBlock();
if (oneImageChanged) {
close();
return;
}
}
TFilmstripSelection *filmstripSelection = dynamic_cast<TFilmstripSelection *>(TSelection::getCurrent());
if (filmstripSelection) {
TXshSimpleLevel *simpleLevel = TApp::instance()->getCurrentLevel()->getSimpleLevel();
if (simpleLevel) {
std::set<TFrameId> fids = filmstripSelection->getSelectedFids();
bool oneImageChanged = false;
std::set<TFrameId>::iterator it = fids.begin();
for (it; it != fids.end(); it++) {
TRasterImageP rasImage = (TRasterImageP)simpleLevel->getFrame(*it, true);
if (!rasImage)
continue;
TRasterP ras = rasImage->getRaster();
if (!ras)
continue;
oneImageChanged = true;
TRop::rgbmAdjust(ras, ras, in0, in1, out0, out1);
simpleLevel->touchFrame(*it);
simpleLevel->setDirtyFlag(true);
IconGenerator::instance()->invalidate(simpleLevel, *it);
}
if (oneImageChanged) {
close();
return;
}
}
}
DVGui::error(QObject::tr("The current selection is invalid."));
return;
}
//**************************************************************************
// Open Popup Command Handler instantiation
//**************************************************************************
OpenPopupCommandHandler<AdjustLevelsPopup> openAdjustLevelsPopup(MI_AdjustLevels);