Blob Blame History Raw


#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);