// NeL - MMORPG Framework
// Copyright (C) 2014 Jan BOON (jan.boon@kaetemi.be)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
#include
#include "panoply_preview.h"
// STL includes
// Qt includes
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// NeL includes
#include
#include
#include
#include
#include
#include
#include
// Project includes
#include "main_window.h"
#include "../panoply_maker/color_modifier.h"
using namespace std;
using namespace NLMISC;
namespace NLTOOLS {
class CColorThread : public NLMISC::IRunnable
{
public:
// Called when a thread is run.
virtual void run()
{
while (Running)
{
SettingsMutex.enter();
if (!Running)
{
SettingsMutex.leave();
return;
}
if (!Process)
{
SettingsMutex.leave();
nlSleep(10); // TODO: Should wait on an event signal...
continue;
}
// nldebug("Update color modifier");
m_ColorModifier.Hue = Hue;
m_ColorModifier.Lightness = Lightness;
m_ColorModifier.Saturation = Saturation;
m_ColorModifier.Luminosity = Luminosity;
m_ColorModifier.Contrast = Contrast;
Process = false;
SettingsMutex.leave();
BitmapMutex.enter();
if (!Running)
{
BitmapMutex.leave();
return;
}
if (!BitmapsOk)
{
nldebug("Bitmaps not ready");
BitmapMutex.leave();
nlSleep(500);
continue;
}
float retDeltaHue;
DestBitmap = ColorBitmap;
m_ColorModifier.convertBitmap(DestBitmap, ColorBitmap, MaskBitmap, retDeltaHue);
BitmapMutex.leave();
PanoplyPreview->displayBitmap(DestBitmap);
nlSleep(10); // TODO: Should wait on an event signal...
}
}
CColorThread() : PanoplyPreview(NULL), BitmapsOk(false), Hue(0), Lightness(0), Saturation(0), Luminosity(0), Contrast(0), Process(false), Running(true) { }
virtual ~CColorThread() { }
virtual void getName (std::string &result) const { result = "CColorThread"; }
private:
CColorModifier m_ColorModifier;
public:
CPanoplyPreview *PanoplyPreview;
NLMISC::CMutex BitmapMutex;
NLMISC::CBitmap ColorBitmap;
NLMISC::CBitmap MaskBitmap;
bool BitmapsOk;
NLMISC::CBitmap DestBitmap;
NLMISC::CMutex SettingsMutex;
float Hue;
float Lightness;
float Saturation;
float Luminosity;
float Contrast;
bool Process;
bool Running;
};
// *****************************************************************
CPanoplyPreview::CPanoplyPreview(CMainWindow *parent) : QWidget(parent)
{
connect(this, SIGNAL(tSigBitmap()), this, SLOT(tSlotBitmap()));
createDockWindows(parent);
m_Image = new QImage(512, 512, QImage::Format_RGB32);
m_Pixmap = new QPixmap(512, 512);
setMinimumWidth(512);
setMinimumHeight(512);
m_ColorThread = new CColorThread();
m_ColorThread->PanoplyPreview = this;
m_Thread = IThread::create(m_ColorThread);
m_Thread->start();
}
CPanoplyPreview::~CPanoplyPreview()
{
m_ColorThread->SettingsMutex.enter();
m_ColorThread->BitmapMutex.enter();
m_ColorThread->Running = false;
m_ColorThread->BitmapMutex.leave();
m_ColorThread->SettingsMutex.leave();
m_Thread->wait();
delete m_Thread;
delete m_ColorThread;
}
void CPanoplyPreview::paintEvent(QPaintEvent* e)
{
QPainter painter(this);
painter.drawPixmap(0, 0, *m_Pixmap);
}
void CPanoplyPreview::displayBitmap(const CBitmap &bitmap) // Called from thread!
{
// nldebug("received bitmap");
m_ColorThread->BitmapMutex.enter();
m_ImageMutex.enter();
const char *buffer = (const char *)&bitmap.getPixels()[0];
if (bitmap.getWidth() != m_Image->width() || bitmap.getHeight() != m_Image->height())
{
QImage *image = m_Image;
m_Image = new QImage(bitmap.getWidth(), bitmap.getHeight(), QImage::Format_RGB32);
delete image;
}
for (uint32 y = 0; y < bitmap.getHeight(); ++y)
{
uint8 *dst = (uint8 *)m_Image->scanLine(y);
const uint8 *src = (const uint8 *)&buffer[y * bitmap.getWidth() * sizeof(uint32)];
for (uint32 x = 0; x < bitmap.getWidth(); ++x)
{
uint32 xb = x * 4;
dst[xb + 0] = src[xb + 2];
dst[xb + 1] = src[xb + 1];
dst[xb + 2] = src[xb + 0];
dst[xb + 3] = src[xb + 3];
}
//memcpy(m_Image->scanLine(y), &buffer[y * bitmap.getWidth() * sizeof(uint32)], sizeof(uint32) * bitmap.getWidth());
}
m_ImageMutex.leave();
m_ColorThread->BitmapMutex.leave();
tSigBitmap();
}
void CPanoplyPreview::tSlotBitmap()
{
// nldebug("display bitmap");
m_ImageMutex.enter();
if (m_Image->width() != m_Pixmap->width()
|| m_Image->height() != m_Pixmap->height())
{
QPixmap *pixmap = m_Pixmap;
m_Pixmap = new QPixmap(m_Image->width(), m_Image->height());
setMinimumWidth(m_Pixmap->width());
setMinimumHeight(m_Pixmap->height());
delete pixmap;
}
m_Pixmap->convertFromImage(*m_Image);
repaint();
m_ImageMutex.leave();
}
void CPanoplyPreview::colorEdited(const QString &text)
{
m_ColorFile = text;
}
void CPanoplyPreview::maskEdited(const QString &text)
{
m_MaskFile = text;
}
void CPanoplyPreview::goPushed(bool)
{
// nldebug("push bitmaps");
m_ColorThread->SettingsMutex.enter();
m_ColorThread->BitmapMutex.enter();
m_ColorThread->BitmapsOk = false;
try
{
{
NLMISC::CIFile is;
if (!is.open(m_ColorFile.toLocal8Bit().data()))
throw NLMISC::Exception("Cannot open file '%s'", m_ColorFile.toLocal8Bit().data());
uint32 depth = m_ColorThread->ColorBitmap.load(is);
if (depth == 0 || m_ColorThread->ColorBitmap.getPixels().empty())
throw NLMISC::Exception("Failed to load bitmap '%s'", m_ColorFile.toLocal8Bit().data());
if (m_ColorThread->ColorBitmap.PixelFormat != NLMISC::CBitmap::RGBA)
m_ColorThread->ColorBitmap.convertToType(NLMISC::CBitmap::RGBA);
}
{
NLMISC::CIFile is;
if (!is.open(m_MaskFile.toLocal8Bit().data()))
throw NLMISC::Exception("Cannot open file '%s'", m_MaskFile.toLocal8Bit().data());
uint32 depth = m_ColorThread->MaskBitmap.load(is);
if (depth == 0 || m_ColorThread->MaskBitmap.getPixels().empty())
throw NLMISC::Exception("Failed to load bitmap '%s'", m_MaskFile.toLocal8Bit().data());
if (m_ColorThread->MaskBitmap.PixelFormat != NLMISC::CBitmap::Luminance)
m_ColorThread->MaskBitmap.convertToType(NLMISC::CBitmap::Luminance);
}
{
m_ColorThread->BitmapsOk = true;
m_ColorThread->Process = true;
}
}
catch (const NLMISC::Exception &e)
{
nlwarning("Exception: '%s'", e.what());
}
m_ColorThread->BitmapMutex.leave();
m_ColorThread->SettingsMutex.leave();
// nldebug("done pushing butmaps");
}
void CPanoplyPreview::hueChanged(int value)
{
float v = (float)value;
m_ColorThread->SettingsMutex.enter();
m_ColorThread->Hue = v;
m_ColorThread->Process = true;
m_ColorThread->SettingsMutex.leave();
}
void CPanoplyPreview::lightnessChanged(int value)
{
float v = (float)value * 0.01f;
m_ColorThread->SettingsMutex.enter();
m_ColorThread->Lightness = v;
m_ColorThread->Process = true;
m_ColorThread->SettingsMutex.leave();
}
void CPanoplyPreview::saturationChanged(int value)
{
float v = (float)value * 0.01f;
m_ColorThread->SettingsMutex.enter();
m_ColorThread->Saturation = v;
m_ColorThread->Process = true;
m_ColorThread->SettingsMutex.leave();
}
void CPanoplyPreview::luminosityChanged(int value)
{
float v = (float)value;
m_ColorThread->SettingsMutex.enter();
m_ColorThread->Luminosity = v;
m_ColorThread->Process = true;
m_ColorThread->SettingsMutex.leave();
}
void CPanoplyPreview::contrastChanged(int value)
{
float v = (float)value;
m_ColorThread->SettingsMutex.enter();
m_ColorThread->Contrast = v;
m_ColorThread->Process = true;
m_ColorThread->SettingsMutex.leave();
}
// *****************************************************************
CSliderTextEdit::CSliderTextEdit(QWidget *parent, QLineEdit *lineEdit, float scale) : QSlider(Qt::Horizontal, parent), m_LineEdit(lineEdit), m_Updating(false), m_Scale(scale)
{
connect(this, SIGNAL(valueChanged(int)), this, SLOT(sliderValueChanged(int)));
connect(lineEdit, SIGNAL(textEdited(const QString &)), this, SLOT(lineEditTextEdited(const QString &)));
}
CSliderTextEdit::~CSliderTextEdit()
{
}
void CSliderTextEdit::lineEditTextEdited(const QString &text)
{
if (!m_Updating)
{
m_Updating = true;
setValue((int)(text.toFloat() * m_Scale));
m_Updating = false;
}
}
void CSliderTextEdit::sliderValueChanged(int value)
{
if (!m_Updating)
{
m_Updating = true;
m_LineEdit->setText(QString::number((double)value / (double)m_Scale));
m_Updating = false;
}
}
// *****************************************************************
void CPanoplyPreview::createDockWindows(CMainWindow *mainWindow)
{
nlassert(mainWindow);
// Color Modifier
{
QDockWidget *dockWidget = new QDockWidget(mainWindow);
nlassert(dockWidget);
dockWidget->setWindowTitle(tr("Color Modifier"));
dockWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
QScrollArea *scrollArea = new QScrollArea(dockWidget);
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scrollArea->setWidgetResizable(true);
QWidget *widget = new QWidget(scrollArea);
QVBoxLayout *vboxLayout = new QVBoxLayout(widget);
// Input File Paths
{
QGroupBox *groupBox = new QGroupBox(widget);
groupBox->setTitle(tr("Input File Paths"));
QGridLayout *groupLayout = new QGridLayout(groupBox);
QLabel *colorLabel = new QLabel(groupBox);
colorLabel->setText(tr("Color: "));
groupLayout->addWidget(colorLabel, 0, 0);
QLineEdit *colorFile = new QLineEdit(groupBox);
colorFile->setText("W:\\database\\stuff\\fyros\\agents\\_textures\\actors\\fy_hof_armor00_arm01_c1.png");
groupLayout->addWidget(colorFile, 0, 1);
m_ColorFile = colorFile->text();
connect(colorFile, SIGNAL(textEdited(const QString &)), this, SLOT(colorEdited(const QString &)));
QLabel *maskLabel = new QLabel(groupBox);
maskLabel->setText(tr("Mask: "));
groupLayout->addWidget(maskLabel, 1, 0);
QLineEdit *maskFile = new QLineEdit(groupBox);
maskFile->setText("W:\\database\\stuff\\fyros\\agents\\_textures\\actors\\mask\\fy_hof_armor00_arm01_c1_skin.png");
groupLayout->addWidget(maskFile, 1, 1);
m_MaskFile = maskFile->text();
connect(maskFile, SIGNAL(textEdited(const QString &)), this, SLOT(maskEdited(const QString &)));
QPushButton *go = new QPushButton(groupBox);
go->setText(tr("Go"));
groupLayout->addWidget(go, 2, 0, 1, 2);
connect(go, SIGNAL(clicked(bool)), this, SLOT(goPushed(bool)));
groupBox->setLayout(groupLayout);
vboxLayout->addWidget(groupBox);
}
// Color Modifier
{
QGroupBox *groupBox = new QGroupBox(widget);
groupBox->setTitle(tr("Color Modifier"));
QGridLayout *groupLayout = new QGridLayout(groupBox);
QLabel *label;
QLineEdit *edit;
CSliderTextEdit *slider;
label = new QLabel(groupBox);
label->setText(tr("Hue [0, 360]: "));
groupLayout->addWidget(label, 0, 0);
edit = new QLineEdit(groupBox);
edit->setText("0");
groupLayout->addWidget(edit, 0, 1);
slider = new CSliderTextEdit(groupBox, edit, 1.0f);
slider->setMinimum(0);
slider->setMaximum(360);
slider->setValue(0);
groupLayout->addWidget(slider, 1, 0, 1, 2);
connect(slider, SIGNAL(valueChanged(int)), this, SLOT(hueChanged(int)));
label = new QLabel(groupBox);
label->setText(tr("Lightness [-1, 1]: "));
groupLayout->addWidget(label, 2, 0);
edit = new QLineEdit(groupBox);
edit->setText("0");
groupLayout->addWidget(edit, 2, 1);
slider = new CSliderTextEdit(groupBox, edit, 100.0f);
slider->setMinimum(-100);
slider->setMaximum(100);
slider->setValue(0);
groupLayout->addWidget(slider, 3, 0, 1, 2);
connect(slider, SIGNAL(valueChanged(int)), this, SLOT(lightnessChanged(int)));
label = new QLabel(groupBox);
label->setText(tr("Saturation [-1, 1]: "));
groupLayout->addWidget(label, 4, 0);
edit = new QLineEdit(groupBox);
edit->setText("0");
groupLayout->addWidget(edit, 4, 1);
slider = new CSliderTextEdit(groupBox, edit, 100.0f);
slider->setMinimum(-100);
slider->setMaximum(100);
slider->setValue(0);
groupLayout->addWidget(slider, 5, 0, 1, 2);
connect(slider, SIGNAL(valueChanged(int)), this, SLOT(saturationChanged(int)));
label = new QLabel(groupBox);
label->setText(tr("Luminosity [-100, 100]: "));
groupLayout->addWidget(label, 6, 0);
edit = new QLineEdit(groupBox);
edit->setText("0");
groupLayout->addWidget(edit, 6, 1);
slider = new CSliderTextEdit(groupBox, edit, 1.0f);
slider->setMinimum(-100);
slider->setMaximum(100);
slider->setValue(0);
groupLayout->addWidget(slider, 7, 0, 1, 2);
connect(slider, SIGNAL(valueChanged(int)), this, SLOT(luminosityChanged(int)));
label = new QLabel(groupBox);
label->setText(tr("Contrast [-100, 100]: "));
groupLayout->addWidget(label, 8, 0);
edit = new QLineEdit(groupBox);
edit->setText("0");
groupLayout->addWidget(edit, 8, 1);
slider = new CSliderTextEdit(groupBox, edit, 1.0f);
slider->setMinimum(-100);
slider->setMaximum(100);
slider->setValue(0);
groupLayout->addWidget(slider, 9, 0, 1, 2);
connect(slider, SIGNAL(valueChanged(int)), this, SLOT(contrastChanged(int)));
groupBox->setLayout(groupLayout);
vboxLayout->addWidget(groupBox);
}
vboxLayout->addStretch();
widget->setLayout(vboxLayout);
scrollArea->setWidget(widget);
dockWidget->setWidget(scrollArea);
mainWindow->addDockWidget(Qt::LeftDockWidgetArea, dockWidget);
mainWindow->widgetsMenu()->addAction(dockWidget->toggleViewAction());
}
}
} /* namespace NLTOOLS */
/* end of file */