mirror of
https://bitbucket.org/mfeemster/fractorium.git
synced 2025-01-21 21:20:07 -05:00
8086cfa731
--User changes -Allow users to set the Exp value when using the Exp temporal filter type. -Set the default temporal filter type to be Box, which does not alter the palette values at all during animation. This is done to avoid confusion when using Gaussian or Exp which can produce darkened images. --Bug fixes -Sending a sequence to the final render dialog when the keyframes had non zero rotate and center Y values would produce off center animations when rendered. -Temporal filters were being unnecessarily recreated many times when rendering or generating sequences. -Exp filter was always treated like a Box filter. --Code changes -Add a new member function SaveCurrentAsXml(QString filename = "") to the controllers which is only used for testing. -Modernize some C++ code.
741 lines
32 KiB
C++
741 lines
32 KiB
C++
#include "FractoriumPch.h"
|
|
#include "Fractorium.h"
|
|
#include "PaletteTableWidgetItem.h"
|
|
|
|
/// <summary>
|
|
/// Initialize the palette UI.
|
|
/// </summary>
|
|
void Fractorium::InitPaletteUI()
|
|
{
|
|
int spinHeight = 20, row = 0;
|
|
auto paletteTable = ui.PaletteListTable;
|
|
auto palettePreviewTable = ui.PalettePreviewTable;
|
|
connect(ui.PaletteFilenameCombo, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(OnPaletteFilenameComboChanged(const QString&)), Qt::QueuedConnection);
|
|
connect(paletteTable, SIGNAL(cellClicked(int, int)), this, SLOT(OnPaletteCellClicked(int, int)), Qt::QueuedConnection);
|
|
connect(paletteTable, SIGNAL(cellDoubleClicked(int, int)), this, SLOT(OnPaletteCellDoubleClicked(int, int)), Qt::QueuedConnection);
|
|
connect(palettePreviewTable, SIGNAL(MouseDragged(const QPointF&, const QPoint&)), this, SLOT(OnPreviewPaletteMouseDragged(const QPointF&, const QPoint&)), Qt::QueuedConnection);
|
|
connect(palettePreviewTable, SIGNAL(MouseReleased()), this, SLOT(OnPreviewPaletteMouseReleased()), Qt::QueuedConnection);
|
|
connect(palettePreviewTable, SIGNAL(cellDoubleClicked(int, int)), this, SLOT(OnPreviewPaletteCellDoubleClicked(int, int)), Qt::QueuedConnection);
|
|
connect(palettePreviewTable, SIGNAL(cellPressed(int, int)), this, SLOT(OnPreviewPaletteCellPressed(int, int)), Qt::QueuedConnection);
|
|
//Palette adjustment table.
|
|
auto table = ui.PaletteAdjustTable;
|
|
table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);//Split width over all columns evenly.
|
|
SetupSpinner<SpinBox, int>(table, this, row, 1, m_PaletteHueSpin, spinHeight, -180, 180, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0);
|
|
SetupSpinner<SpinBox, int>(table, this, row, 1, m_PaletteSaturationSpin, spinHeight, -100, 100, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0);
|
|
SetupSpinner<SpinBox, int>(table, this, row, 1, m_PaletteBrightnessSpin, spinHeight, -255, 255, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0);
|
|
row = 0;
|
|
SetupSpinner<SpinBox, int>(table, this, row, 3, m_PaletteContrastSpin, spinHeight, -100, 100, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0);
|
|
SetupSpinner<SpinBox, int>(table, this, row, 3, m_PaletteBlurSpin, spinHeight, 0, 127, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0);
|
|
SetupSpinner<SpinBox, int>(table, this, row, 3, m_PaletteFrequencySpin, spinHeight, 1, 10, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 1, 1, 1);
|
|
connect(ui.PaletteRandomSelectButton, SIGNAL(clicked(bool)), this, SLOT(OnPaletteRandomSelectButtonClicked(bool)), Qt::QueuedConnection);
|
|
connect(ui.PaletteRandomAdjustButton, SIGNAL(clicked(bool)), this, SLOT(OnPaletteRandomAdjustButtonClicked(bool)), Qt::QueuedConnection);
|
|
//Palette editor.
|
|
connect(ui.PaletteEditorButton, SIGNAL(clicked(bool)), this, SLOT(OnPaletteEditorButtonClicked(bool)), Qt::QueuedConnection);
|
|
//Preview table.
|
|
palettePreviewTable->setRowCount(1);
|
|
palettePreviewTable->setColumnWidth(1, 260);//256 plus small margin on each side.
|
|
auto previewNameCol = new QTableWidgetItem("");
|
|
palettePreviewTable->setItem(0, 0, previewNameCol);
|
|
auto previewPaletteItem = new QTableWidgetItem();
|
|
palettePreviewTable->setItem(0, 1, previewPaletteItem);
|
|
connect(ui.PaletteFilterLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(OnPaletteFilterLineEditTextChanged(const QString&)));
|
|
connect(ui.PaletteFilterClearButton, SIGNAL(clicked(bool)), this, SLOT(OnPaletteFilterClearButtonClicked(bool)));
|
|
paletteTable->setColumnWidth(1, 260);//256 plus small margin on each side.
|
|
paletteTable->horizontalHeader()->setSectionsClickable(true);
|
|
connect(paletteTable->horizontalHeader(), SIGNAL(sectionClicked(int)), this, SLOT(OnPaletteHeaderSectionClicked(int)), Qt::QueuedConnection);
|
|
connect(ui.ResetCurvesButton, SIGNAL(clicked(bool)), this, SLOT(OnResetCurvesButtonClicked(bool)), Qt::QueuedConnection);
|
|
connect(ui.CurvesView, SIGNAL(PointChangedSignal(int, int, const QPointF&)), this, SLOT(OnCurvesPointChanged(int, int, const QPointF&)), Qt::QueuedConnection);
|
|
connect(ui.CurvesView, SIGNAL(PointAddedSignal(size_t, const QPointF&)), this, SLOT(OnCurvesPointAdded(size_t, const QPointF&)), Qt::QueuedConnection);
|
|
connect(ui.CurvesView, SIGNAL(PointRemovedSignal(size_t, int)), this, SLOT(OnCurvesPointRemoved(size_t, int)), Qt::QueuedConnection);
|
|
connect(ui.CurvesAllRadio, SIGNAL(toggled(bool)), this, SLOT(OnCurvesAllRadioButtonToggled(bool)), Qt::QueuedConnection);
|
|
connect(ui.CurvesRedRadio, SIGNAL(toggled(bool)), this, SLOT(OnCurvesRedRadioButtonToggled(bool)), Qt::QueuedConnection);
|
|
connect(ui.CurvesGreenRadio, SIGNAL(toggled(bool)), this, SLOT(OnCurvesGreenRadioButtonToggled(bool)), Qt::QueuedConnection);
|
|
connect(ui.CurvesBlueRadio, SIGNAL(toggled(bool)), this, SLOT(OnCurvesBlueRadioButtonToggled(bool)), Qt::QueuedConnection);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read all palette Xml files in the specified folder and populate the palette list with the contents.
|
|
/// This will clear any previous contents.
|
|
/// Called upon initialization, or controller type change.
|
|
/// </summary>
|
|
/// <param name="s">The full path to the palette files folder</param>
|
|
/// <returns>The number of palettes successfully added</returns>
|
|
template <typename T>
|
|
size_t FractoriumEmberController<T>::InitPaletteList(const QString& s)
|
|
{
|
|
QDirIterator it(s, QStringList() << "*.xml" << "*.ugr" << "*.gradient" << "*.gradients", QDir::Files, QDirIterator::FollowSymlinks);
|
|
|
|
while (it.hasNext())
|
|
{
|
|
auto path = it.next();
|
|
auto qfilename = it.fileName();
|
|
|
|
try
|
|
{
|
|
if (QFile::exists(path) && m_PaletteList->Add(path.toStdString()))
|
|
m_Fractorium->ui.PaletteFilenameCombo->addItem(qfilename);
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
QMessageBox::critical(nullptr, "Palette Parsing Error", QString::fromStdString(e.what()));
|
|
}
|
|
catch (const char* e)
|
|
{
|
|
QMessageBox::critical(nullptr, "Palette Parsing Error", e);
|
|
}
|
|
}
|
|
|
|
m_Fractorium->ui.PaletteFilenameCombo->model()->sort(0);
|
|
return m_PaletteList->Size();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read a palette Xml file and populate the palette table with the contents.
|
|
/// This will clear any previous contents.
|
|
/// Called upon initialization, palette combo index change, and controller type change.
|
|
/// </summary>
|
|
/// <param name="s">The name of the palette file without the path</param>
|
|
/// <returns>True if successful, else false.</returns>
|
|
template <typename T>
|
|
bool FractoriumEmberController<T>::FillPaletteTable(const string& s)
|
|
{
|
|
if (!s.empty())//This occasionally seems to get called with an empty string for reasons unknown.
|
|
{
|
|
auto paletteTable = m_Fractorium->ui.PaletteListTable;
|
|
m_CurrentPaletteFilePath = s;
|
|
|
|
if (::FillPaletteTable(m_CurrentPaletteFilePath, paletteTable, m_PaletteList))
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
vector<string> errors = m_PaletteList->ErrorReport();
|
|
m_Fractorium->ErrorReportToQTextEdit(errors, m_Fractorium->ui.InfoFileOpeningTextEdit);
|
|
m_Fractorium->ShowCritical("Palette Read Error", "Could not load palette file, all images will be black. See info tab for details.");
|
|
m_PaletteList->ClearErrorReport();
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fill the palette table with the passed in string.
|
|
/// Called when the palette name combo box changes.
|
|
/// </summary>
|
|
/// <param name="text">The full path to the palette file</param>
|
|
void Fractorium::OnPaletteFilenameComboChanged(const QString& text)
|
|
{
|
|
auto s = text.toStdString();
|
|
m_Controller->FillPaletteTable(s);
|
|
auto fullname = m_Controller->m_PaletteList->GetFullPathFromFilename(s);
|
|
ui.PaletteFilenameCombo->setToolTip(QString::fromStdString(fullname));
|
|
ui.PaletteListTable->sortItems(0, m_PaletteSortMode == 0 ? Qt::AscendingOrder : Qt::DescendingOrder);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply adjustments to the current ember's palette.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::ApplyPaletteToEmber()
|
|
{
|
|
const uint blur = m_Fractorium->m_PaletteBlurSpin->value();
|
|
const uint freq = m_Fractorium->m_PaletteFrequencySpin->value();
|
|
const auto sat = m_Fractorium->m_PaletteSaturationSpin->value() / 100.0;
|
|
const auto brightness = m_Fractorium->m_PaletteBrightnessSpin->value() / 255.0;
|
|
const auto contrast = m_Fractorium->m_PaletteContrastSpin->value() > 0 ? m_Fractorium->m_PaletteContrastSpin->value() * 2.0 : m_Fractorium->m_PaletteContrastSpin->value() / 100.0;
|
|
const auto hue = m_Fractorium->m_PaletteHueSpin->value() / 360.0;
|
|
//Use the temp palette as the base and apply the adjustments gotten from the GUI and save the result in the ember palette.
|
|
m_TempPalette.MakeAdjustedPalette(m_Ember.m_Palette, m_Fractorium->m_PreviewPaletteRotation, hue, sat, brightness, contrast, blur, freq);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Use adjusted palette to update all related GUI controls with new color values.
|
|
/// Resets the rendering process.
|
|
/// </summary>
|
|
/// <param name="palette">The palette to use</param>
|
|
/// <param name="paletteName">Name of the palette</param>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::UpdateAdjustedPaletteGUI(Palette<float>& palette)
|
|
{
|
|
const auto xform = CurrentXform();
|
|
const auto palettePreviewTable = m_Fractorium->ui.PalettePreviewTable;
|
|
const auto paletteName = QString::fromStdString(m_Ember.m_Palette.m_Name);
|
|
auto previewPaletteItem = palettePreviewTable->item(0, 1);
|
|
|
|
if (previewPaletteItem)//This can be null if the palette file was moved or corrupted.
|
|
{
|
|
//Use the adjusted palette to fill the preview palette control so the user can see the effects of applying the adjustements.
|
|
vector<byte> v = palette.MakeRgbPaletteBlock(PALETTE_CELL_HEIGHT);//Make the palette repeat for PALETTE_CELL_HEIGHT rows.
|
|
m_FinalPaletteImage = QImage(int(palette.Size()), PALETTE_CELL_HEIGHT, QImage::Format_RGB888);//Create a QImage out of it.
|
|
memcpy(m_FinalPaletteImage.scanLine(0), v.data(), v.size() * sizeof(v[0]));//Memcpy the data in.
|
|
QPixmap pixmap(QPixmap::fromImage(m_FinalPaletteImage));//Create a QPixmap out of the QImage.
|
|
previewPaletteItem->setData(Qt::DecorationRole, pixmap.scaled(QSize(pixmap.width(), palettePreviewTable->rowHeight(0) + 2), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));//Set the pixmap on the palette tab.
|
|
m_Fractorium->SetPaletteTableItem(&pixmap, m_Fractorium->ui.XformPaletteRefTable, m_Fractorium->m_PaletteRefItem, 0, 0);//Set the palette ref table on the xforms | color tab.
|
|
|
|
if (auto previewNameItem = palettePreviewTable->item(0, 0))
|
|
{
|
|
previewNameItem->setText(paletteName);//Finally, set the name of the palette to be both the text and the tooltip.
|
|
previewNameItem->setToolTip(paletteName);
|
|
}
|
|
}
|
|
|
|
//Update the current xform's color and reset the rendering process.
|
|
//Update all controls to be safe.
|
|
if (xform)
|
|
XformColorIndexChanged(xform->m_ColorX, true, true, true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply all adjustments to the selected palette, show it
|
|
/// and assign it to the current ember.
|
|
/// Called when any adjustment spinner is modified.
|
|
/// Resets the rendering process.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::PaletteAdjust()
|
|
{
|
|
Update([&]()
|
|
{
|
|
ApplyPaletteToEmber();
|
|
UpdateAdjustedPaletteGUI(m_Ember.m_Palette);
|
|
});
|
|
}
|
|
|
|
void Fractorium::OnPaletteAdjust(int d) { m_Controller->PaletteAdjust(); }
|
|
|
|
/// <summary>
|
|
/// Set the passed in palette as the current one,
|
|
/// applying any adjustments previously specified.
|
|
/// Resets the rendering process.
|
|
/// </summary>
|
|
/// <param name="palette">The palette to assign to the temporary palette</param>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::SetBasePaletteAndAdjust(const Palette<float>& palette)
|
|
{
|
|
//The temp palette is assigned the palette read when the file was parsed/saved. The user can apply adjustments on the GUI later.
|
|
//These adjustments will be applied to the temp palette, then assigned back to m_Ember.m_Palette.
|
|
m_TempPalette = palette;//Deep copy.
|
|
ApplyPaletteToEmber();//Copy temp palette to ember palette and apply adjustments.
|
|
UpdateAdjustedPaletteGUI(m_Ember.m_Palette);//Show the adjusted palette.
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the selected palette as the current one,
|
|
/// applying any adjustments previously specified.
|
|
/// Called when a palette cell is clicked. Unfortunately,
|
|
/// this will get called twice on a double click when moving
|
|
/// from one palette to another. It happens quickly so it shouldn't
|
|
/// be too much of a problem.
|
|
/// Resets the rendering process.
|
|
/// </summary>
|
|
/// <param name="row">The table row clicked</param>
|
|
/// <param name="col">The table column clicked</param>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::PaletteCellClicked(int row, int col)
|
|
{
|
|
if (const auto palette = m_PaletteList->GetPaletteByFilename(m_CurrentPaletteFilePath, row))
|
|
SetBasePaletteAndAdjust(*palette);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Map the palette in the clicked row index to the index
|
|
/// in the palette list, then pass that index to PaletteCellClicked().
|
|
/// This resolves the case where the sort order of the palette table
|
|
/// is different than the internal order of the palette list.
|
|
/// </summary>
|
|
/// <param name="row">The table row clicked</param>
|
|
/// <param name="col">The table column clicked, ignored</param>
|
|
void Fractorium::OnPaletteCellClicked(int row, int col)
|
|
{
|
|
if (const auto item = dynamic_cast<PaletteTableWidgetItem*>(ui.PaletteListTable->item(row, 1)))
|
|
{
|
|
const auto index = int(item->Index());
|
|
|
|
if (m_PreviousPaletteRow != index)
|
|
{
|
|
m_Controller->PaletteCellClicked(index, col);
|
|
m_PreviousPaletteRow = index;//Save for comparison on next click.
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when the mouse has been moved while pressed on the palette preview table.
|
|
/// Computes the difference between where the mouse was clicked and where it is now, then
|
|
/// uses that difference as a rotation value to pass into the palette adjustment.
|
|
/// Updates the palette and resets the rendering process.
|
|
/// </summary>
|
|
/// <param name="local">The local mouse coordinates relative to the palette preview table</param>
|
|
/// <param name="global">The global mouse coordinates</param>
|
|
void Fractorium::OnPreviewPaletteMouseDragged(const QPointF& local, const QPoint& global)
|
|
{
|
|
if (m_PreviewPaletteMouseDown)
|
|
{
|
|
m_PreviewPaletteRotation = m_PreviewPaletteMouseDownRotation + (global.x() - m_PreviewPaletteMouseDownPosition.x());
|
|
//qDebug() << "Palette preview table drag reached main window event: " << local.x() << ' ' << local.y() << ", global: " << global.x() << ' ' << global.y() << ", final: " << m_PreviewPaletteRotation;
|
|
m_Controller->PaletteAdjust();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when the mouse has been released over the palette preview table.
|
|
/// Does nothing but set the dragging state to false.
|
|
/// </summary>
|
|
void Fractorium::OnPreviewPaletteMouseReleased()
|
|
{
|
|
m_PreviewPaletteMouseDown = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the palette rotation to zero.
|
|
/// Updates the palette and resets the rendering process.
|
|
/// </summary>
|
|
/// <param name="row">Ignored</param>
|
|
/// <param name="col">Ignored</param>
|
|
void Fractorium::OnPreviewPaletteCellDoubleClicked(int row, int col)
|
|
{
|
|
m_PreviewPaletteRotation = m_PreviewPaletteMouseDownRotation = 0;
|
|
m_PreviewPaletteMouseDown = false;
|
|
m_Controller->PaletteAdjust();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when the mouse has been pressed on the palette preview table.
|
|
/// Subsequent mouse movements will compute a rotation value to pass into the palette adjustment, based on the location
|
|
/// of the mouse when this slot is called.
|
|
/// </summary>
|
|
/// <param name="row">Ignored</param>
|
|
/// <param name="col">Ignored</param>
|
|
void Fractorium::OnPreviewPaletteCellPressed(int row, int col)
|
|
{
|
|
m_PreviewPaletteMouseDown = true;
|
|
m_PreviewPaletteMouseDownPosition = QCursor::pos();//Get the global mouse position.
|
|
m_PreviewPaletteMouseDownRotation = m_PreviewPaletteRotation;
|
|
//qDebug() << "Mouse down with initial pos: " << m_PreviewPaletteMouseDownPosition.x() << " and initial rotation: " << m_PreviewPaletteMouseDownRotation;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the selected palette as the current one,
|
|
/// resetting any adjustments previously specified.
|
|
/// Called when a palette cell is double clicked.
|
|
/// Resets the rendering process.
|
|
/// </summary>
|
|
/// <param name="row">The table row clicked</param>
|
|
/// <param name="col">The table column clicked</param>
|
|
void Fractorium::OnPaletteCellDoubleClicked(int row, int col)
|
|
{
|
|
ResetPaletteControls();
|
|
m_PreviousPaletteRow = -1;
|
|
OnPaletteCellClicked(row, col);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the selected palette to a randomly selected one,
|
|
/// applying any adjustments previously specified if the checked parameter is true.
|
|
/// Called when the Random Palette button is clicked.
|
|
/// Resets the rendering process.
|
|
/// </summary>
|
|
/// <param name="checked">True to clear the current adjustments, else leave current adjustments and apply them to the newly selected palette.</param>
|
|
void Fractorium::OnPaletteRandomSelectButtonClicked(bool checked)
|
|
{
|
|
uint i = 0;
|
|
const auto rowCount = ui.PaletteListTable->rowCount();
|
|
|
|
if (rowCount > 1)//If only one palette in the current palette file, just use it.
|
|
while (((i = QTIsaac<ISAAC_SIZE, ISAAC_INT>::LockedRand(rowCount)) == uint(m_PreviousPaletteRow)) || i >= static_cast<uint>(rowCount));
|
|
|
|
if (checked)
|
|
OnPaletteCellDoubleClicked(i, 1);//Will clear the adjustments.
|
|
else
|
|
OnPaletteCellClicked(i, 1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply random adjustments to the selected palette.
|
|
/// Called when the Random Adjustment button is clicked.
|
|
/// Resets the rendering process.
|
|
/// </summary>
|
|
void Fractorium::OnPaletteRandomAdjustButtonClicked(bool checked)
|
|
{
|
|
m_PaletteHueSpin->setValue(-180 + QTIsaac<ISAAC_SIZE, ISAAC_INT>::LockedRand(361));
|
|
m_PaletteSaturationSpin->setValue(-50 + QTIsaac<ISAAC_SIZE, ISAAC_INT>::LockedRand(101));//Full range of these leads to bad palettes, so clamp range.
|
|
m_PaletteBrightnessSpin->setValue(-50 + QTIsaac<ISAAC_SIZE, ISAAC_INT>::LockedRand(101));
|
|
m_PaletteContrastSpin->setValue(-50 + QTIsaac<ISAAC_SIZE, ISAAC_INT>::LockedRand(101));
|
|
|
|
//Doing frequency and blur together gives bad palettes that are just a solid color.
|
|
if (QTIsaac<ISAAC_SIZE, ISAAC_INT>::LockedRandBit())
|
|
{
|
|
m_PaletteBlurSpin->setValue(QTIsaac<ISAAC_SIZE, ISAAC_INT>::LockedRand(21));
|
|
m_PaletteFrequencySpin->setValue(1);
|
|
}
|
|
else
|
|
{
|
|
m_PaletteBlurSpin->setValue(0);
|
|
m_PaletteFrequencySpin->setValue(1 + QTIsaac<ISAAC_SIZE, ISAAC_INT>::LockedRand(10));
|
|
}
|
|
|
|
OnPaletteAdjust(0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Open the palette editor dialog.
|
|
/// Called when the palette editor button is clicked.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::PaletteEditorButtonClicked()
|
|
{
|
|
size_t i = 0;
|
|
const auto ed = m_Fractorium->m_PaletteEditor.get();
|
|
map<size_t, float> colorIndices;
|
|
const auto forceFinal = m_Fractorium->HaveFinal();
|
|
m_PreviousTempPalette = m_TempPalette; // it's necessary because m_TempPalette is changed when the user make changes in palette editor
|
|
ed->SetPalette(m_TempPalette);
|
|
|
|
while (auto xform = m_Ember.GetTotalXform(i, forceFinal))
|
|
colorIndices[i++] = xform->m_ColorX;
|
|
|
|
ed->SetColorIndices(colorIndices);
|
|
ed->SetPreviousColorIndices(colorIndices); // also necessary because the colors are changed in palette editor
|
|
ed->SetPaletteFile(m_CurrentPaletteFilePath);
|
|
#ifdef __linux__
|
|
ed->show();
|
|
#else
|
|
SyncPalette(ed->exec() == QDialog::Accepted);
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Slot called when the palette editor changes the palette and the Sync checkbox is checked.
|
|
/// </summary>
|
|
bool Fractorium::PaletteChanged()
|
|
{
|
|
return m_PaletteChanged;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Open the palette editor dialog.
|
|
/// This creates the palette editor dialog if it has not been created at least once.
|
|
/// Called when the palette editor button is clicked.
|
|
/// </summary>
|
|
/// <param name="checked">Ignored</param>
|
|
void Fractorium::OnPaletteEditorButtonClicked(bool checked)
|
|
{
|
|
if (!m_PaletteEditor.get())
|
|
{
|
|
m_PaletteEditor = std::make_unique<PaletteEditor>(this);
|
|
connect(m_PaletteEditor.get(), SIGNAL(PaletteChanged()), this, SLOT(OnPaletteEditorColorChanged()), Qt::QueuedConnection);
|
|
connect(m_PaletteEditor.get(), SIGNAL(PaletteFileChanged()), this, SLOT(OnPaletteEditorFileChanged()), Qt::QueuedConnection);
|
|
connect(m_PaletteEditor.get(), SIGNAL(ColorIndexChanged(size_t, float)), this, SLOT(OnPaletteEditorColorIndexChanged(size_t, float)), Qt::QueuedConnection);
|
|
#ifdef __linux__
|
|
connect(m_PaletteEditor.get(), SIGNAL(finished(int)), this, SLOT(OnPaletteEditorFinished(int)), Qt::QueuedConnection);
|
|
#endif
|
|
}
|
|
|
|
m_PaletteChanged = false;
|
|
m_PaletteFileChanged = false;
|
|
m_Controller->PaletteEditorButtonClicked();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Slot called when palette editor window is closed.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::SyncPalette(bool accepted)
|
|
{
|
|
const auto ed = m_Fractorium->m_PaletteEditor.get();
|
|
Palette<float> edPal;
|
|
Palette<float> prevPal = m_PreviousTempPalette;
|
|
map<size_t, float> colorIndices;
|
|
const auto forceFinal = m_Fractorium->HaveFinal();
|
|
|
|
if (accepted)
|
|
{
|
|
//Copy all just to be safe, because they may or may not have synced.
|
|
colorIndices = ed->GetColorIndices();
|
|
|
|
for (auto& index : colorIndices)
|
|
if (auto xform = m_Ember.GetTotalXform(index.first, forceFinal))
|
|
xform->m_ColorX = index.second;
|
|
|
|
edPal = ed->GetPalette(static_cast<int>(prevPal.Size()));
|
|
SetBasePaletteAndAdjust(edPal);//This will take care of updating the color index controls.
|
|
|
|
if (edPal.m_Filename.get() && !edPal.m_Filename->empty())
|
|
m_Fractorium->SetPaletteFileComboIndex(*edPal.m_Filename);
|
|
}
|
|
else if (m_Fractorium->PaletteChanged())//They clicked cancel, but synced at least once, restore the previous palette.
|
|
{
|
|
colorIndices = ed->GetPreviousColorIndices();
|
|
|
|
for (auto& index : colorIndices)
|
|
if (auto xform = m_Ember.GetTotalXform(index.first, forceFinal))
|
|
xform->m_ColorX = index.second;
|
|
|
|
SetBasePaletteAndAdjust(prevPal);//This will take care of updating the color index controls.
|
|
}
|
|
|
|
//Whether the current palette file was changed or not, if it's modifiable then reload it just to be safe (even though it might be overkill).
|
|
if (m_PaletteList->IsModifiable(m_CurrentPaletteFilePath))
|
|
m_Fractorium->OnPaletteFilenameComboChanged(QString::fromStdString(m_CurrentPaletteFilePath));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Slot called every time a color is changed in the palette editor.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::PaletteEditorColorChanged()
|
|
{
|
|
SetBasePaletteAndAdjust(m_Fractorium->m_PaletteEditor->GetPalette(static_cast<int>(m_TempPalette.Size())));
|
|
}
|
|
|
|
void Fractorium::OnPaletteEditorColorChanged()
|
|
{
|
|
m_PaletteChanged = true;
|
|
m_Controller->PaletteEditorColorChanged();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Slot called every time a palette file is changed in the palette editor.
|
|
/// </summary>
|
|
void Fractorium::OnPaletteEditorFileChanged()
|
|
{
|
|
m_PaletteFileChanged = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Slot called every time an xform color index is changed in the palette editor.
|
|
/// If a special value of size_t max is passed for index, it means update all color indices.
|
|
/// </summary>
|
|
/// <param name="index">The index of the xform whose color index has been changed. Special value of size_t max to update all</param>
|
|
/// <param name="value">The value of the color index</param>
|
|
void Fractorium::OnPaletteEditorColorIndexChanged(size_t index, float value)
|
|
{
|
|
if (index == std::numeric_limits<size_t>::max())//Update all in this case.
|
|
{
|
|
auto indices = m_PaletteEditor->GetColorIndices();
|
|
|
|
for (auto& it : indices)
|
|
OnXformColorIndexChanged(it.second, true, true, true, eXformUpdate::UPDATE_SPECIFIC, it.first);
|
|
}
|
|
else//Only update the xform index that was selected and dragged inside of the palette editor.
|
|
OnXformColorIndexChanged(value, true, true, true, eXformUpdate::UPDATE_SPECIFIC, index);
|
|
}
|
|
|
|
/// Slot called after EditPallete is closed.
|
|
/// </summary>
|
|
/// <param name="result">Cancel/OK action</param>
|
|
void Fractorium::OnPaletteEditorFinished(int result)
|
|
{
|
|
m_Controller->SyncPalette(result == QDialog::Accepted);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply the text in the palette filter text box to only show palettes whose names
|
|
/// contain the substring.
|
|
/// Called when the user types in the palette filter text box.
|
|
/// </summary>
|
|
/// <param name="text">The text to filter on</param>
|
|
void Fractorium::OnPaletteFilterLineEditTextChanged(const QString& text)
|
|
{
|
|
auto table = ui.PaletteListTable;
|
|
table->setUpdatesEnabled(false);
|
|
|
|
for (int i = 0; i < table->rowCount(); i++)
|
|
{
|
|
if (auto item = table->item(i, 0))
|
|
{
|
|
if (!item->text().contains(text, Qt::CaseInsensitive))
|
|
table->hideRow(i);
|
|
else
|
|
table->showRow(i);
|
|
}
|
|
}
|
|
|
|
ui.PaletteListTable->sortItems(0, m_PaletteSortMode == 0 ? Qt::AscendingOrder : Qt::DescendingOrder);//Must re-sort every time the filter changes.
|
|
table->setUpdatesEnabled(true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear the palette name filter, which will display all palettes.
|
|
/// Called when clear palette filter button is clicked.
|
|
/// </summary>
|
|
/// <param name="checked">Ignored</param>
|
|
void Fractorium::OnPaletteFilterClearButtonClicked(bool checked)
|
|
{
|
|
ui.PaletteFilterLineEdit->clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Change the sorting to be either ascending or descending.
|
|
/// Called when user clicks the table headers.
|
|
/// </summary>
|
|
/// <param name="col">Column index of the header clicked, ignored.</param>
|
|
void Fractorium::OnPaletteHeaderSectionClicked(int col)
|
|
{
|
|
m_PaletteSortMode = !m_PaletteSortMode;
|
|
ui.PaletteListTable->sortItems(0, m_PaletteSortMode == 0 ? Qt::AscendingOrder : Qt::DescendingOrder);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reset the palette controls.
|
|
/// Usually in response to a palette cell double click.
|
|
/// </summary>
|
|
void Fractorium::ResetPaletteControls()
|
|
{
|
|
m_PreviewPaletteRotation = m_PreviewPaletteMouseDownRotation = 0;
|
|
m_PaletteHueSpin->SetValueStealth(0);
|
|
m_PaletteSaturationSpin->SetValueStealth(0);
|
|
m_PaletteBrightnessSpin->SetValueStealth(0);
|
|
m_PaletteContrastSpin->SetValueStealth(0);
|
|
m_PaletteBlurSpin->SetValueStealth(0);
|
|
m_PaletteFrequencySpin->SetValueStealth(1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the index of the palette file combo box.
|
|
/// This is for display purposes only so the user can see which file, if any,
|
|
/// the current palette came from.
|
|
/// For embedded palettes with no filename, this will have no effect.
|
|
/// </summary>
|
|
/// <param name="filename">The string to set the index to</param>
|
|
void Fractorium::SetPaletteFileComboIndex(const string& filename)
|
|
{
|
|
if (!filename.empty())
|
|
ui.PaletteFilenameCombo->setCurrentText(QFileInfo(QString::fromStdString(filename)).fileName());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reset the color curve values for the selected curve in the current ember to their default state and also update the curves control.
|
|
/// Called when ResetCurvesButton is clicked.
|
|
/// Note if they click Reset Curves when the ctrl is pressed, then it clears all curves.
|
|
/// Resets the rendering process at either ACCUM_ONLY by default, or FILTER_AND_ACCUM when using early clip.
|
|
/// </summary>
|
|
/// <param name="i">The index of the curve to be cleared, 0 to clear all.</param>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::ClearColorCurves(int i)
|
|
{
|
|
UpdateAll([&](Ember<T>& ember, bool isMain)
|
|
{
|
|
if (i < 0)
|
|
ember.m_Curves.Init();
|
|
else
|
|
ember.m_Curves.Init(i);
|
|
}, true, m_Renderer->EarlyClip() ? eProcessAction::FILTER_AND_ACCUM : eProcessAction::ACCUM_ONLY, m_Fractorium->ApplyAll());
|
|
FillCurvesControl();
|
|
}
|
|
|
|
void Fractorium::OnResetCurvesButtonClicked(bool checked)
|
|
{
|
|
if (!QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier))
|
|
{
|
|
if (ui.CurvesAllRadio->isChecked())
|
|
m_Controller->ClearColorCurves(0);
|
|
else if (ui.CurvesRedRadio->isChecked())
|
|
m_Controller->ClearColorCurves(1);
|
|
else if (ui.CurvesGreenRadio->isChecked())
|
|
m_Controller->ClearColorCurves(2);
|
|
else if (ui.CurvesBlueRadio->isChecked())
|
|
m_Controller->ClearColorCurves(3);
|
|
else
|
|
m_Controller->ClearColorCurves(0);
|
|
}
|
|
else
|
|
{
|
|
m_Controller->ClearColorCurves(-1);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the coordinate of the curve point.
|
|
/// Called when the position of any of the points in the curves editor is is changed.
|
|
/// Resets the rendering process at either ACCUM_ONLY by default, or FILTER_AND_ACCUM when using early clip.
|
|
/// </summary>
|
|
/// <param name="curveIndex">The curve index, 0-3/</param>
|
|
/// <param name="pointIndex">The point index within the selected curve, 1-2.</param>
|
|
/// <param name="point">The new coordinate of the point in terms of the curves control rect.</param>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::ColorCurveChanged(int curveIndex, int pointIndex, const QPointF& point)
|
|
{
|
|
Update([&]
|
|
{
|
|
m_Ember.m_Curves.m_Points[curveIndex][pointIndex].x = point.x();
|
|
m_Ember.m_Curves.m_Points[curveIndex][pointIndex].y = point.y();
|
|
}, true, m_Renderer->EarlyClip() ? eProcessAction::FILTER_AND_ACCUM : eProcessAction::ACCUM_ONLY);
|
|
}
|
|
|
|
void Fractorium::OnCurvesPointChanged(int curveIndex, int pointIndex, const QPointF& point) { m_Controller->ColorCurveChanged(curveIndex, pointIndex, point); }
|
|
|
|
/// <summary>
|
|
/// Remove curve point.
|
|
/// Called when right clicking on a color curve point.
|
|
/// Resets the rendering process at either ACCUM_ONLY by default, or FILTER_AND_ACCUM when using early clip.
|
|
/// </summary>
|
|
/// <param name="curveIndex">The curve index./</param>
|
|
/// <param name="pointIndex">The point index within the selected curve.</param>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::ColorCurvesPointRemoved(size_t curveIndex, int pointIndex)
|
|
{
|
|
Update([&]
|
|
{
|
|
if (m_Ember.m_Curves.m_Points[curveIndex].size() > 2)
|
|
{
|
|
m_Ember.m_Curves.m_Points[curveIndex].erase(m_Ember.m_Curves.m_Points[curveIndex].begin() + pointIndex);
|
|
std::sort(m_Ember.m_Curves.m_Points[curveIndex].begin(), m_Ember.m_Curves.m_Points[curveIndex].end(), [&](auto & lhs, auto & rhs) { return lhs.x < rhs.x; });
|
|
}
|
|
}, true, m_Renderer->EarlyClip() ? eProcessAction::FILTER_AND_ACCUM : eProcessAction::ACCUM_ONLY);
|
|
FillCurvesControl();
|
|
}
|
|
|
|
void Fractorium::OnCurvesPointRemoved(size_t curveIndex, int pointIndex) { m_Controller->ColorCurvesPointRemoved(curveIndex, pointIndex); }
|
|
|
|
/// <summary>
|
|
/// Add a curve point.
|
|
/// Called when clicking in between points on a color curve.
|
|
/// Resets the rendering process at either ACCUM_ONLY by default, or FILTER_AND_ACCUM when using early clip.
|
|
/// </summary>
|
|
/// <param name="curveIndex">The curve index./</param>
|
|
/// <param name="pointIndex">The point to add to the selected curve.</param>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::ColorCurvesPointAdded(size_t curveIndex, const QPointF& point)
|
|
{
|
|
Update([&]
|
|
{
|
|
m_Ember.m_Curves.m_Points[curveIndex].push_back({ point.x(), point.y() });
|
|
std::sort(m_Ember.m_Curves.m_Points[curveIndex].begin(), m_Ember.m_Curves.m_Points[curveIndex].end(), [&](auto & lhs, auto & rhs) { return lhs.x < rhs.x; });
|
|
}, true, m_Renderer->EarlyClip() ? eProcessAction::FILTER_AND_ACCUM : eProcessAction::ACCUM_ONLY);
|
|
FillCurvesControl();
|
|
}
|
|
|
|
void Fractorium::OnCurvesPointAdded(size_t curveIndex, const QPointF& point) { m_Controller->ColorCurvesPointAdded(curveIndex, point); }
|
|
|
|
/// <summary>
|
|
/// Set the top most points in the curves control, which makes it easier to
|
|
/// select a point by putting it on top of all the others.
|
|
/// Called when the any of the curve color radio buttons are toggled.
|
|
/// </summary>
|
|
/// <param name="checked">Ignored</param>
|
|
void Fractorium::OnCurvesAllRadioButtonToggled(bool checked) { if (checked) ui.CurvesView->SetTop(CurveIndex::ALL); }
|
|
void Fractorium::OnCurvesRedRadioButtonToggled(bool checked) { if (checked) ui.CurvesView->SetTop(CurveIndex::RED); }
|
|
void Fractorium::OnCurvesGreenRadioButtonToggled(bool checked) { if (checked) ui.CurvesView->SetTop(CurveIndex::GREEN); }
|
|
void Fractorium::OnCurvesBlueRadioButtonToggled(bool checked) { if (checked) ui.CurvesView->SetTop(CurveIndex::BLUE); }
|
|
|
|
/// <summary>
|
|
/// Set the points in the curves control to the values of the curve points in the current ember.
|
|
/// </summary>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::FillCurvesControl()
|
|
{
|
|
m_Fractorium->ui.CurvesView->blockSignals(true);
|
|
m_Fractorium->ui.CurvesView->Set(m_Ember.m_Curves);
|
|
m_Fractorium->ui.CurvesView->blockSignals(false);
|
|
m_Fractorium->ui.CurvesView->update();
|
|
}
|
|
|
|
template class FractoriumEmberController<float>;
|
|
|
|
#ifdef DO_DOUBLE
|
|
template class FractoriumEmberController<double>;
|
|
#endif
|