fractorium/Source/Fractorium/FractoriumPalette.cpp
Person 90ec5b8246 --User changes:
-Show common folder locations such as documents, downloads, pictures in the sidebar in all file dialogs.
 -Warning message about exceeding memory in final render dialog now suggests strips as the solution to the problem.
 -Strips now has a tooltip explaining what it does.
 -Allow more digits in the spinners on the color section the flame tab.
 -Add manually adjustable size spinners in the final render dialog. Percentage scale and absolute size are fully synced.
 -Default prefix in final render is now the filename when doing animations (coming from sequence section of the library tab).
 -Changed the elliptic variation back to using a less precise version for float, and a more precise version for double. The last release had it always using double.
 -New applied xaos table that shows a read-only view of actual weights by taking the base xform weights and multiplying them by the xaos values.
 -New table in the xaos tab that gives a graphical representation of the probability that each xform is chosen, with and without xaos.
 -Add button to transpose the xaos rows and columns.
 -Add support for importing .chaos files from Chaotica.
 --Pasting back to Chaotica will work for most, but not all, variations due to incompatible parameter names in some.
 -Curves are now splines instead of Bezier. This adds compatibility with Chaotica, but breaks it for Apophysis. Xmls are still pastable, but the color curves will look different.
 --The curve editor on the palette tab can now add points by clicking on the lines and remove points by clicking on the points themselves, just like Chaotica.
 --Splines are saved in four new xml fields: overall_curve, red_curve, green_curve and blue_curve.
 -Allow for specifying the percentage of a sub batch each thread should iterate through per kernel call when running with OpenCL. This gives a roughly 1% performance increase due to having to make less kernel calls while iterating.
 --This field is present for interactive editing (where it's not very useful) and in the final render dialog.
 --On the command line, this is specified as --sbpctth for EmberRender and EmberAnimate.
 -Allow double clicking to toggle the supersample field in the flame tab between 1 and 2 for easily checking the effect of the field.
 -When showing affine values as polar coordinates, show angles normalized to 360 to match Chaotica.
 -Fuse Count spinner now toggles between 15 and 100 when double clicking for easily checking the effect of the field.
 -Added field for limiting the range in the x and y direction that the initial points are chosen from.
 -Added a field called K2 which is an alternative way to set brightness, ignored when zero.
 --This has no effect for many variations, but hs a noticeable effect for some.
 -Added new variations:
 arcsech
 arcsech2
 arcsinh
 arctanh
 asteria
 block
 bwraps_rand
 circlecrop2
 coth_spiral
 crackle2
 depth_blur
 depth_blur2
 depth_gaussian
 depth_gaussian2
 depth_ngon
 depth_ngon2
 depth_sine
 depth_sine2
 dragonfire
 dspherical
 dust
 excinis
 exp2
 flipx
 flowerdb
 foci_p
 gaussian
 glynnia2
 glynnsim4
 glynnsim5
 henon
 henon
 hex_rand
 hex_truchet
 hypershift
 lazyjess
 lens
 lozi
 lozi
 modulusx
 modulusy
 oscilloscope2
 point_symmetry
 pointsymmetry
 projective
 pulse
 rotate
 scry2
 shift
 smartshape
 spher
 squares
 starblur2
 swirl3
 swirl3r
 tanh_spiral
 target0
 target2
 tile_hlp
 truchet_glyph
 truchet_inv
 truchet_knot
 unicorngaloshen
 vibration
 vibration2
 --hex_truchet, hex_rand should always use double. They are extremely sensitive.

--Bug fixes:
 -Bounds sign was flipped for x coordinate of world space when center was not zero.
 -Right clicking and dragging spinner showed menu on mouse up, even if it was very far away.
 -Text boxes for size in final render dialog were hard to type in. Same bug as xform weight used to be so fix the same way.
 -Fix spelling to be plural in toggle color speed box.
 -Stop using the blank user palette to generate flames. Either put colored palettes in it, or exclude it from randoms.
 -Clicking the random palette button for a palette file with only one palette in it would freeze the program.
 -Clicking none scale in final render did not re-render the preview.
 -Use less precision on random xaos. No need for 12 decimal places.
 -The term sub batch is overloaded in the options dialog. Change the naming and tooltip of those settings for cpu and opencl.
 --Also made clear in the tooltip for the default opencl quality setting that the value is per device.
 -The arrows spinner in palette editor appears like a read-only label. Made it look like a spinner.
 -Fix border colors for various spin boxes and table headers in the style sheet. Requires reload.
 -Fix a bug in the bwraps variation which would produce different results than Chaotica and Apophysis.
 -Synth was allowed to be selected for random flame generation when using an Nvidia card but it shouldn't have been because Nvidia has a hard time compiling synth.
 -A casting bug in the OpenCL kernels for log scaling and density filtering was preventing successful compilations on Intel iGPUs. Fixed even though we don't support anything other than AMD and Nvidia.
 -Palette rotation (click and drag) position was not being reset when loading a new flame.
 -When the xform circles were hidden, opening and closing the options dialog would improperly reshow them.
 -Double click toggle was broken on integer spin boxes.
 -Fixed tab order of some controls.
 -Creating a palette from a jpg in the palette editor only produced a single color.
 --Needed to package imageformats/qjpeg.dll with the Windows installer.
 -The basic memory benchmark test flame was not really testing memory. Make it more spread out.
 -Remove the temporal samples field from the flame tab, it was never used because it's only an animation parameter which is specified in the final render dialog or on the command line with EmberAnimate.

--Code changes:
 -Add IsEmpty() to Palette to determine if a palette is all black.
 -Attempt to avoid selecting a blank palette in PaletteList::GetRandomPalette().
 -Add function ScanForChaosNodes() and some associated helper functions in XmlToEmber.
 -Make variation param name correction be case insensitive in XmlToEmber.
 -Report error when assigning a variation param value in XmlToEmber.
 -Add SubBatchPercentPerThread() method to RendererCL.
 -Override enterEvent() and leaveEvent() in DoubleSpinBox and SpinBox to prevent the context menu from showing up on right mouse up after already leaving the spinner.
 -Filtering the mouse wheel event in TableWidget no longer appears to be needed. It was probably an old Qt bug that has been fixed.
 -Gui/ember syncing code in the final render dialog needed to be reworked to accommodate absolute sizes.
2019-04-13 19:00:46 -07:00

694 lines
30 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);
}
}
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()
{
uint blur = m_Fractorium->m_PaletteBlurSpin->value();
uint freq = m_Fractorium->m_PaletteFrequencySpin->value();
double sat = double(m_Fractorium->m_PaletteSaturationSpin->value() / 100.0);
double brightness = double(m_Fractorium->m_PaletteBrightnessSpin->value() / 255.0);
double contrast = double(m_Fractorium->m_PaletteContrastSpin->value() > 0 ? (m_Fractorium->m_PaletteContrastSpin->value() * 2) : m_Fractorium->m_PaletteContrastSpin->value()) / 100.0;
double hue = double(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)
{
auto xform = CurrentXform();
auto palettePreviewTable = m_Fractorium->ui.PalettePreviewTable;
auto previewPaletteItem = palettePreviewTable->item(0, 1);
auto paletteName = QString::fromStdString(m_Ember.m_Palette.m_Name);
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.
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 (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 (auto item = dynamic_cast<PaletteTableWidgetItem*>(ui.PaletteListTable->item(row, 1)))
{
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;
int 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 >= 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;
auto ed = m_Fractorium->m_PaletteEditor;
Palette<float> edPal;
Palette<float> prevPal = m_TempPalette;
map<size_t, float> colorIndices;
bool forceFinal = m_Fractorium->HaveFinal();
ed->SetPalette(m_TempPalette);
while (auto xform = m_Ember.GetTotalXform(i, forceFinal))
colorIndices[i++] = xform->m_ColorX;
ed->SetColorIndices(colorIndices);
ed->SetPaletteFile(m_CurrentPaletteFilePath);
//ed->setpal
if (ed->exec() == QDialog::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(int(256));
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.
{
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 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)
{
m_PaletteEditor = new PaletteEditor(this);
connect(m_PaletteEditor, SIGNAL(PaletteChanged()), this, SLOT(OnPaletteEditorColorChanged()), Qt::QueuedConnection);
connect(m_PaletteEditor, SIGNAL(PaletteFileChanged()), this, SLOT(OnPaletteEditorFileChanged()), Qt::QueuedConnection);
connect(m_PaletteEditor, SIGNAL(ColorIndexChanged(size_t, float)), this, SLOT(OnPaletteEditorColorIndexChanged(size_t, float)), Qt::QueuedConnection);
}
m_PaletteChanged = false;
m_PaletteFileChanged = false;
m_Controller->PaletteEditorButtonClicked();
}
/// <summary>
/// Slot called every time a color is changed in the palette editor.
/// </summary>
void Fractorium::OnPaletteEditorColorChanged()
{
m_PaletteChanged = true;
m_Controller->SetBasePaletteAndAdjust(m_PaletteEditor->GetPalette(int(256)));
}
/// <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);
}
/// <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 "All" radio button is selected, 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)
{
Update([&]
{
if (i)
m_Ember.m_Curves.Init(i);
else
m_Ember.m_Curves.Init(0);
}, true, m_Renderer->EarlyClip() ? eProcessAction::FILTER_AND_ACCUM : eProcessAction::ACCUM_ONLY);
FillCurvesControl();
}
void Fractorium::OnResetCurvesButtonClicked(bool checked)
{
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);
}
/// <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