#include "FractoriumPch.h" #include "Fractorium.h" #include "PaletteTableWidgetItem.h" #define PALETTE_CELL_HEIGHT 16 /// /// 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.PaletteRandomSelect, SIGNAL(clicked(bool)), this, SLOT(OnPaletteRandomSelectButtonClicked(bool)), Qt::QueuedConnection); connect(ui.PaletteRandomAdjust, SIGNAL(clicked(bool)), this, SLOT(OnPaletteRandomAdjustButtonClicked(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 string& s) { QDirIterator it(s.c_str(), QStringList() << "*.xml", QDir::Files, QDirIterator::FollowSymlinks); m_PaletteList.Clear(); m_Fractorium->ui.PaletteFilenameCombo->clear(); m_Fractorium->ui.PaletteFilenameCombo->setProperty("path", QString::fromStdString(s)); while (it.hasNext()) { auto path = it.next().toStdString(); auto qfilename = it.fileName(); if (m_PaletteList.Add(path)) m_Fractorium->ui.PaletteFilenameCombo->addItem(qfilename); } 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 to 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 = m_Fractorium->ui.PaletteFilenameCombo->property("path").toString().toStdString() + "/" + s; if (int paletteSize = int(m_PaletteList.Size(m_CurrentPaletteFilePath))) { paletteTable->clear(); paletteTable->blockSignals(true); paletteTable->setRowCount(paletteSize); //Headers get removed when clearing, so must re-create here. auto nameHeader = new QTableWidgetItem("Name"); auto paletteHeader = new QTableWidgetItem("Palette"); nameHeader->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); paletteHeader->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); paletteTable->setHorizontalHeaderItem(0, nameHeader); paletteTable->setHorizontalHeaderItem(1, paletteHeader); //Palette list table. for (auto i = 0; i < paletteSize; i++) { if (auto p = m_PaletteList.GetPalette(m_CurrentPaletteFilePath, i)) { auto v = p->MakeRgbPaletteBlock(PALETTE_CELL_HEIGHT); auto nameCol = new QTableWidgetItem(p->m_Name.c_str()); nameCol->setToolTip(p->m_Name.c_str()); paletteTable->setItem(i, 0, nameCol); QImage image(v.data(), int(p->Size()), PALETTE_CELL_HEIGHT, QImage::Format_RGB888); auto paletteItem = new PaletteTableWidgetItem(p); paletteItem->setData(Qt::DecorationRole, QPixmap::fromImage(image)); paletteTable->setItem(i, 1, paletteItem); } } paletteTable->blockSignals(false); 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."); } } return false; } void Fractorium::OnPaletteFilenameComboChanged(const QString& text) { m_Controller->FillPaletteTable(text.toStdString()); 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 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.GetPalette(m_CurrentPaletteFilePath, row)) { m_TempPalette = *palette;//Deep copy. ApplyPaletteToEmber();//Copy temp palette to ember palette and apply adjustments. UpdateAdjustedPaletteGUI(m_Ember.m_Palette);//Show the adjusted 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); } /// /// 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. /// /// The curve index, 0-1/ 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