#include "FractoriumPch.h" #include "Fractorium.h" #include "PaletteTableWidgetItem.h" /// /// Initialize the palette UI. /// 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); //Palette adjustment table. auto table = ui.PaletteAdjustTable; table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);//Split width over all columns evenly. SetupSpinner(table, this, row, 1, m_PaletteHueSpin, spinHeight, -180, 180, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0); SetupSpinner(table, this, row, 1, m_PaletteSaturationSpin, spinHeight, -100, 100, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0); SetupSpinner(table, this, row, 1, m_PaletteBrightnessSpin, spinHeight, -255, 255, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0); row = 0; SetupSpinner(table, this, row, 3, m_PaletteContrastSpin, spinHeight, -100, 100, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0); SetupSpinner(table, this, row, 3, m_PaletteBlurSpin, spinHeight, 0, 127, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0); SetupSpinner(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.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); } /// /// 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. /// /// The full path to the palette files folder /// The number of palettes successfully added template size_t FractoriumEmberController::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(); } /// /// 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. /// /// The name of the palette file without the path /// True if successful, else false. template bool FractoriumEmberController::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 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; } /// /// Fill the palette table with the passed in string. /// Called when the palette name combo box changes. /// /// The full path to the palette file 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); } /// /// Apply adjustments to the current ember's palette. /// template void FractoriumEmberController::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, 0, hue, sat, brightness, contrast, blur, freq); } /// /// Use adjusted palette to update all related GUI controls with new color values. /// Resets the rendering process. /// /// The palette to use /// Name of the palette template void FractoriumEmberController::UpdateAdjustedPaletteGUI(Palette& 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 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. if (xform) XformColorIndexChanged(xform->m_ColorX, true); } /// /// 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. /// template void FractoriumEmberController::PaletteAdjust() { Update([&]() { ApplyPaletteToEmber(); UpdateAdjustedPaletteGUI(m_Ember.m_Palette); }); } void Fractorium::OnPaletteAdjust(int d) { m_Controller->PaletteAdjust(); } /// /// Set the passed in palette as the current one, /// applying any adjustments previously specified. /// Resets the rendering process. /// /// The palette to assign to the temporary palette template void FractoriumEmberController::SetBasePaletteAndAdjust(const Palette& 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. } /// /// 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. /// /// The table row clicked /// The table column clicked template void FractoriumEmberController::PaletteCellClicked(int row, int col) { if (auto palette = m_PaletteList.GetPaletteByFilename(m_CurrentPaletteFilePath, row)) SetBasePaletteAndAdjust(*palette); } /// /// 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. /// /// The table row clicked /// The table column clicked, ignored void Fractorium::OnPaletteCellClicked(int row, int col) { if (auto item = dynamic_cast(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. } } } /// /// 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. /// /// The table row clicked /// The table column clicked void Fractorium::OnPaletteCellDoubleClicked(int row, int col) { ResetPaletteControls(); m_PreviousPaletteRow = -1; OnPaletteCellClicked(row, col); } /// /// 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. /// /// True to clear the current adjustments, else leave current adjustments and apply them to the newly selected palette. void Fractorium::OnPaletteRandomSelectButtonClicked(bool checked) { uint i = 0; int rowCount = ui.PaletteListTable->rowCount() - 1; while ((i = QTIsaac::LockedRand(rowCount)) == uint(m_PreviousPaletteRow)); if (checked) OnPaletteCellDoubleClicked(i, 1);//Will clear the adjustments. else OnPaletteCellClicked(i, 1); } /// /// Apply random adjustments to the selected palette. /// Called when the Random Adjustment button is clicked. /// Resets the rendering process. /// void Fractorium::OnPaletteRandomAdjustButtonClicked(bool checked) { m_PaletteHueSpin->setValue(-180 + QTIsaac::LockedRand(361)); m_PaletteSaturationSpin->setValue(-50 + QTIsaac::LockedRand(101));//Full range of these leads to bad palettes, so clamp range. m_PaletteBrightnessSpin->setValue(-50 + QTIsaac::LockedRand(101)); m_PaletteContrastSpin->setValue(-50 + QTIsaac::LockedRand(101)); //Doing frequency and blur together gives bad palettes that are just a solid color. if (QTIsaac::LockedRandBit()) { m_PaletteBlurSpin->setValue(QTIsaac::LockedRand(21)); m_PaletteFrequencySpin->setValue(1); } else { m_PaletteBlurSpin->setValue(0); m_PaletteFrequencySpin->setValue(1 + QTIsaac::LockedRand(10)); } OnPaletteAdjust(0); } /// /// Open the palette editor dialog. /// Called when the palette editor button is clicked. /// template void FractoriumEmberController::PaletteEditorButtonClicked() { auto ed = m_Fractorium->m_PaletteEditor; Palette prevPal = m_TempPalette; ed->SetPalette(m_TempPalette); if (ed->exec() == QDialog::Accepted) { if (!m_Fractorium->PaletteChanged())//If the clicked ok, but never synced, set the palette now. SetBasePaletteAndAdjust(ed->GetPalette(int(256))); } else if (m_Fractorium->PaletteChanged())//They clicked cancel, but synced at least once, restore the previous palette. { SetBasePaletteAndAdjust(prevPal); } //If the palette was modifiable, and any palette was changed at least once if (m_Fractorium->m_PaletteFileChanged && m_PaletteList.IsModifiable(m_CurrentPaletteFilePath)) { if (!::FillPaletteTable(m_CurrentPaletteFilePath, m_Fractorium->ui.PaletteListTable, m_PaletteList)) { vector errors = m_PaletteList.ErrorReport(); m_Fractorium->ErrorReportToQTextEdit(errors, m_Fractorium->ui.InfoFileOpeningTextEdit); m_Fractorium->ShowCritical("Palette Read Error", "Could not re-load modified palette file, all images will be black. See info tab for details."); m_PaletteList.ClearErrorReport(); } } } /// /// Slot called when the palette editor changes the palette and the Sync checkbox is checked. /// bool Fractorium::PaletteChanged() { return m_PaletteChanged; } /// /// 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. /// /// Ignored void Fractorium::OnPaletteEditorButtonClicked(bool checked) { if (!m_PaletteEditor) { m_PaletteEditor = new PaletteEditor(m_Controller->m_PaletteList, this); connect(m_PaletteEditor, SIGNAL(PaletteChanged()), this, SLOT(OnPaletteEditorColorChanged()), Qt::QueuedConnection); connect(m_PaletteEditor, SIGNAL(PaletteFileChanged()), this, SLOT(OnPaletteEditorFileChanged()), Qt::QueuedConnection); } m_PaletteChanged = false; m_PaletteFileChanged = false; m_Controller->PaletteEditorButtonClicked(); } /// /// Slot called every time a color is changed in the palette editor. /// void Fractorium::OnPaletteEditorColorChanged() { m_PaletteChanged = true; m_Controller->SetBasePaletteAndAdjust(m_PaletteEditor->GetPalette(int(256))); } /// /// Slot called every time a palette file is changed in the palette editor. /// void Fractorium::OnPaletteEditorFileChanged() { m_PaletteFileChanged = true; } /// /// 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. /// /// The text to filter on 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); } /// /// Clear the palette name filter, which will display all palettes. /// Called when clear palette filter button is clicked. /// /// Ignored void Fractorium::OnPaletteFilterClearButtonClicked(bool checked) { ui.PaletteFilterLineEdit->clear(); } /// /// Change the sorting to be either ascending or descending. /// Called when user clicks the table headers. /// /// Column index of the header clicked, ignored. void Fractorium::OnPaletteHeaderSectionClicked(int col) { m_PaletteSortMode = !m_PaletteSortMode; ui.PaletteListTable->sortItems(0, m_PaletteSortMode == 0 ? Qt::AscendingOrder : Qt::DescendingOrder); } /// /// Reset the palette controls. /// Usually in response to a palette cell double click. /// void Fractorium::ResetPaletteControls() { m_PaletteHueSpin->SetValueStealth(0); m_PaletteSaturationSpin->SetValueStealth(0); m_PaletteBrightnessSpin->SetValueStealth(0); m_PaletteContrastSpin->SetValueStealth(0); m_PaletteBlurSpin->SetValueStealth(0); m_PaletteFrequencySpin->SetValueStealth(1); } /// /// 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. /// /// The string to set the index to void Fractorium::SetPaletteFileComboIndex(const string& filename) { if (!filename.empty()) ui.PaletteFilenameCombo->setCurrentText(QFileInfo(QString::fromStdString(filename)).fileName()); } /// /// Reset the color curve values in the current ember to their default state and also update the curves control. /// Called when ResetCurvesButton is clicked. /// Resets the rendering process at either ACCUM_ONLY by default, or FILTER_AND_ACCUM when using early clip. /// template void FractoriumEmberController::ClearColorCurves() { Update([&] { m_Ember.m_Curves.Init(); }, true, m_Renderer->EarlyClip() ? eProcessAction::FILTER_AND_ACCUM : eProcessAction::ACCUM_ONLY); FillCurvesControl(); } void Fractorium::OnResetCurvesButtonClicked(bool checked) { m_Controller->ClearColorCurves(); } /// /// 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. /// /// The curve index, 0-1/ /// The point index within the selected curve, 1-2. /// The new coordinate of the point in terms of the curves control rect. template void FractoriumEmberController::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); } /// /// 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. /// /// Ignored 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); } /// /// Set the points in the curves control to the values of the curve points in the current ember. /// template void FractoriumEmberController::FillCurvesControl() { m_Fractorium->ui.CurvesView->blockSignals(true); for (auto i = 0; i < 4; i++) { for (auto j = 1; j < 3; j++)//Only do middle points. { QPointF point(m_Ember.m_Curves.m_Points[i][j].x, m_Ember.m_Curves.m_Points[i][j].y); m_Fractorium->ui.CurvesView->Set(i, j, point); } } m_Fractorium->ui.CurvesView->blockSignals(false); m_Fractorium->ui.CurvesView->update(); } template class FractoriumEmberController; #ifdef DO_DOUBLE template class FractoriumEmberController; #endif