diff --git a/Source/Ember/Ember.cpp b/Source/Ember/Ember.cpp index f0aa11a..5e30cc0 100644 --- a/Source/Ember/Ember.cpp +++ b/Source/Ember/Ember.cpp @@ -46,8 +46,8 @@ template<> unique_ptr> QTIsaac; #define EXPORT_SINGLE_TYPE_EMBER(T) \ - template<> bool PaletteList::m_Init = false; \ - template<> vector> PaletteList::m_Palettes = vector>(); \ + template<> const char* PaletteList::m_DefaultFilename = "flam3-palettes.xml"; \ + template<> map>> PaletteList::m_Palettes = map>>(); \ template<> bool XmlToEmber::m_Init = false; \ template<> vector XmlToEmber::m_FlattenNames = vector(); \ template<> vector> XmlToEmber::m_BadParamNames = vector>(); \ diff --git a/Source/Ember/EmberPch.h b/Source/Ember/EmberPch.h index 3005fba..650afbe 100644 --- a/Source/Ember/EmberPch.h +++ b/Source/Ember/EmberPch.h @@ -40,6 +40,7 @@ #else #include #endif +#include #include #include #include diff --git a/Source/Ember/Isaac.h b/Source/Ember/Isaac.h index 804ba1f..ad5c4fa 100644 --- a/Source/Ember/Isaac.h +++ b/Source/Ember/Isaac.h @@ -41,6 +41,13 @@ namespace EmberNs { + +union UintBytes +{ + unsigned char Bytes[4]; + uint Uint; +}; + /// /// QTIsaac class which allows using ISAAC in an OOP manner. /// @@ -49,6 +56,8 @@ class EMBER_API QTIsaac { public: enum { N = (1 << ALPHA) }; + UintBytes m_Cache; + int m_LastIndex; /// /// Global ISAAC RNG to be used from anywhere. This is not thread safe, so take caution to only @@ -83,6 +92,28 @@ public: QTIsaac(T a = 0, T b = 0, T c = 0, T* s = nullptr) { Srand(a, b, c, s); + m_LastIndex = 0; + m_Cache.Uint = Rand(); + T temp = RandByte();//Need to call at least once so other libraries can link. + } + + /// + /// Return the next random integer in the range of 0-255. + /// If only a byte is needed, this is a more efficient way because + /// it only calls rand 1/4 of the time. + /// + /// The next random integer in the range of 0-255 + inline T RandByte() + { + T ret = m_Cache.Bytes[m_LastIndex++]; + + if (m_LastIndex == 4) + { + m_LastIndex = 0; + m_Cache.Uint = Rand(); + } + + return ret; } /// @@ -261,7 +292,7 @@ public: if (s == nullptr)//Default to using time plus index as the seed if s was nullptr. { for (int i = 0; i < N; i++) - m_Rc.randrsl[i] = static_cast(time(nullptr)) + i; + m_Rc.randrsl[i] = static_cast(NowMs()) + i; } else { @@ -272,9 +303,9 @@ public: #ifndef ISAAC_FLAM3_DEBUG if (a == 0 && b == 0 && c == 0) { - m_Rc.randa = static_cast(time(nullptr)); - m_Rc.randb = static_cast(time(nullptr)) * static_cast(time(nullptr)); - m_Rc.randc = static_cast(time(nullptr)) * static_cast(time(nullptr)) * static_cast(time(nullptr)); + m_Rc.randa = static_cast(NowMs()); + m_Rc.randb = static_cast(NowMs()) * static_cast(NowMs()); + m_Rc.randc = static_cast(NowMs()) * static_cast(NowMs()) * static_cast(NowMs()); } else #endif diff --git a/Source/Ember/PaletteList.h b/Source/Ember/PaletteList.h index 13461bb..02bfaf2 100644 --- a/Source/Ember/PaletteList.h +++ b/Source/Ember/PaletteList.h @@ -18,11 +18,14 @@ template class EMBER_API PaletteList : public EmberReport { public: + static const char* m_DefaultFilename; + /// - /// Empty constructor which does nothing. + /// Empty constructor which initializes the palette map with the default palette file. /// PaletteList() { + Add(string(m_DefaultFilename)); } /// @@ -31,17 +34,16 @@ public: /// /// The full path to the file to read /// If true, override the initialization state and force a read, else observe the initialization state. - /// The initialization state - bool Init(const string& filename, bool force = false) + /// Whether anything was read + bool Add(const string& filename, bool force = false) { - if (!m_Init || force) - { - const char* loc = __FUNCTION__; + bool added = false; + auto& palettes = m_Palettes[filename]; - m_Init = false; - m_Palettes.clear(); - m_ErrorReport.clear(); + if (palettes.empty() || force) + { string buf; + const char* loc = __FUNCTION__; if (ReadFile(filename.c_str(), buf)) { @@ -51,10 +53,11 @@ public: { xmlNode* rootNode = xmlDocGetRootElement(doc); - m_Palettes.reserve(buf.size() / 2048);//Roughly what it takes per palette. - ParsePalettes(rootNode); + palettes.clear(); + palettes.reserve(buf.size() / 2048);//Roughly what it takes per palette. + ParsePalettes(rootNode, palettes); xmlFreeDoc(doc); - m_Init = m_ErrorReport.empty(); + added = true; } else { @@ -67,54 +70,81 @@ public: } } - return m_Init; + return added; } /// - /// Gets the palette at a specified index. + /// Get the palette at a random index in a random file in the map. /// - /// The index of the palette to read. A value of -1 indicates a random palette. - /// A pointer to the requested palette if the index was in range, else nullptr. - Palette* GetPalette(int i) + Palette* GetRandomPalette() { - if (!m_Palettes.empty()) + auto p = m_Palettes.begin(); + int i = 0, paletteFileIndex = QTIsaac::GlobalRand->Rand() % Size(); + + while (i < paletteFileIndex && p != m_Palettes.end()) { - if (i == -1) - return &m_Palettes[QTIsaac::GlobalRand->Rand() % Size()]; - else if (i < int(m_Palettes.size())) - return &m_Palettes[i]; + ++i; + ++p; + } + + if (i < Size()) + { + int paletteIndex = QTIsaac::GlobalRand->Rand() % p->second.size(); + + if (paletteIndex < p->second.size()) + return &p->second[paletteIndex]; } return nullptr; } /// - /// Gets a pointer to a palette with a specified name. + /// Get the palette at a specified index in the specified file in the map. /// - /// The name of the palette to retrieve - /// A pointer to the palette if found, else nullptr - Palette* GetPaletteByName(const string&& name) + /// The filename of the palette to retrieve + /// The index of the palette to read. A value of -1 indicates a random palette. + /// A pointer to the requested palette if the index was in range, else nullptr. + Palette* GetPalette(const string& filename, int i) { - for (uint i = 0; i < Size(); i++) - if (m_Palettes[i].m_Name == name) - return &m_Palettes[i]; + auto& palettes = m_Palettes[filename]; + + if (!palettes.empty() && i < int(palettes.size())) + return &palettes[i]; return nullptr; } /// - /// Gets a copy of the palette at a specified index with its hue adjusted by the specified amount. + /// Get a pointer to a palette with a specified name in the specified file in the map. /// - /// The index of the palette to read. A value of -1 indicates a random palette. + /// The filename of the palette to retrieve + /// The name of the palette to retrieve + /// A pointer to the palette if found, else nullptr + Palette* GetPaletteByName(const string& filename, const string& name) + { + for (auto& palettes : m_Palettes) + if (palettes.first == filename) + for (auto& palette : palettes.second) + if (palette.m_Name == name) + return &palette; + + return nullptr; + } + + /// + /// Get a copy of the palette at a specified index in the specified file in the map + /// with its hue adjusted by the specified amount. + /// + /// The filename of the palette to retrieve + /// The index of the palette to read. /// The hue adjustment to apply /// The palette to store the output /// True if successful, else false. - bool GetHueAdjustedPalette(int i, T hue, Palette& palette) + bool GetHueAdjustedPalette(const string& filename, int i, T hue, Palette& palette) { bool b = false; - Palette* unadjustedPal = GetPalette(i); - if (unadjustedPal) + if (Palette* unadjustedPal = GetPalette(filename, i)) { unadjustedPal->MakeHueAdjustedPalette(palette, hue); b = true; @@ -129,23 +159,72 @@ public: void Clear() { m_Palettes.clear(); - m_Init = false; } /// - /// Accessors. + /// Get the size of the palettes map. + /// This will be the number of files read. /// - bool Init() { return m_Init; } + /// The size of the palettes map size_t Size() { return m_Palettes.size(); } + /// + /// Get the size of specified palette vector in the palettes map. + /// + /// The index of the palette in the map to retrieve + /// The size of the palette vector at the specified index in the palettes map + size_t Size(size_t index) + { + size_t i = 0; + auto p = m_Palettes.begin(); + + while (i < index && p != m_Palettes.end()) + { + ++i; + ++p; + } + + return p->second.size(); + } + + /// + /// Get the size of specified palette vector in the palettes map. + /// + /// The filename of the palette in the map to retrieve + /// The size of the palette vector at the specified index in the palettes map + size_t Size(const string& s) + { + return m_Palettes[s].size(); + } + + /// + /// Get the name of specified palette in the palettes map. + /// + /// The index of the palette in the map to retrieve + /// The name of the palette vector at the specified index in the palettes map + const string& Name(size_t index) + { + size_t i = 0; + auto p = m_Palettes.begin(); + + while (i < index && p != m_Palettes.end()) + { + ++i; + ++p; + } + + return p->first; + } + private: /// - /// Parses an Xml node for all palettes present and stores in the palette list. + /// Parses an Xml node for all palettes present and store in the passed in palette vector. /// Note that although the Xml color values are expected to be 0-255, they are converted and /// stored as normalized colors, with values from 0-1. /// /// The parent note of all palettes in the Xml file. - void ParsePalettes(xmlNode* node) + /// The vector to store the paresed palettes associated with this file in. + void ParsePalettes(xmlNode* node, vector>& palettes) { bool hexError = false; char* val; @@ -208,19 +287,18 @@ private: if (!hexError) { - m_Palettes.push_back(palette); + palettes.push_back(palette); } } else { - ParsePalettes(node->children); + ParsePalettes(node->children, palettes); } node = node->next; } } - static bool m_Init;//Initialized to false in Ember.cpp, and will be set to true upon successful reading of an Xml palette file. - static vector> m_Palettes;//The vector that stores the palettes. + static map>> m_Palettes;//The map of filenames to vectors that store the palettes. }; } diff --git a/Source/Ember/SheepTools.h b/Source/Ember/SheepTools.h index a9efc9e..64a7bf3 100644 --- a/Source/Ember/SheepTools.h +++ b/Source/Ember/SheepTools.h @@ -72,7 +72,7 @@ public: m_OffsetX = 0; m_OffsetY = 0; - m_PaletteList.Init(palettePath); + m_PaletteList.Add(palettePath); m_StandardIterator = unique_ptr>(new StandardIterator()); m_XaosIterator = unique_ptr>(new XaosIterator()); m_Renderer = unique_ptr>(renderer); @@ -99,8 +99,8 @@ public: ember.AddXform(xform2); ember.AddXform(xform3); - if (m_PaletteList.Init()) - ember.m_Palette = *m_PaletteList.GetPalette(-1); + if (m_PaletteList.Size()) + ember.m_Palette = *m_PaletteList.GetRandomPalette(); return ember; } @@ -386,8 +386,8 @@ public: { Palette palette; - if (m_PaletteList.Init()) - palette = *m_PaletteList.GetPalette(-1); + if (m_PaletteList.Size()) + palette = *m_PaletteList.GetRandomPalette(); palette.MakeHueAdjustedPalette(ember.m_Palette, ember.m_Hue); @@ -640,8 +640,8 @@ public: ember.Clear(); ember.m_Hue = (m_Rand.Rand() & 7) ? 0 : m_Rand.Frand01(); - if (m_PaletteList.Init()) - palette = *m_PaletteList.GetPalette(-1); + if (m_PaletteList.Size()) + palette = *m_PaletteList.GetRandomPalette(); palette.MakeHueAdjustedPalette(ember.m_Palette, ember.m_Hue); ember.m_Time = 0; @@ -926,8 +926,8 @@ public: ember.m_Hue = 0.0; - if (m_PaletteList.Init()) - palette = m_PaletteList.GetPalette(-1); + if (m_PaletteList.Size()) + palette = m_PaletteList.GetRandomPalette(); if (palette) { diff --git a/Source/Ember/Utils.h b/Source/Ember/Utils.h index dbfa5f0..1310b7b 100644 --- a/Source/Ember/Utils.h +++ b/Source/Ember/Utils.h @@ -44,6 +44,13 @@ static inline size_t SizeOf(vector& vec) return sizeof(vec[0]) * vec.size(); } +/// +/// Thin wrapper around getting the current time in milliseconds. +/// +static inline size_t NowMs() +{ + return duration_cast(Clock::now().time_since_epoch()).count(); +} /// /// After a run completes, information about what was run can be saved as strings to the comments /// section of a jpg or png file. This class is just a container for those values. diff --git a/Source/Ember/XmlToEmber.h b/Source/Ember/XmlToEmber.h index 93ebbaa..5008088 100644 --- a/Source/Ember/XmlToEmber.h +++ b/Source/Ember/XmlToEmber.h @@ -344,7 +344,7 @@ public: string buf; //Ensure palette list is setup first. - if (!m_PaletteList.Init()) + if (!m_PaletteList.Size()) { m_ErrorReport.push_back(string(loc) + " : Palette list must be initialized before parsing embers."); return false; @@ -515,7 +515,7 @@ private: if (currentEmber.PaletteIndex() != -1) { - if (!m_PaletteList.GetHueAdjustedPalette(currentEmber.PaletteIndex(), currentEmber.m_Hue, currentEmber.m_Palette)) + if (!m_PaletteList.GetHueAdjustedPalette(PaletteList::m_DefaultFilename, currentEmber.PaletteIndex(), currentEmber.m_Hue, currentEmber.m_Palette)) { m_ErrorReport.push_back(string(loc) + " : Error assigning palette with index " + Itos(currentEmber.PaletteIndex())); } @@ -834,8 +834,6 @@ private: //Make sure BOTH are not specified, otherwise either are ok. int numColors = 0; int numBytes = 0; - bool oldFormat = false; - bool newFormat = false; int index0, index1; T hue0, hue1; T blend = 0.5; @@ -855,40 +853,12 @@ private: { attStr = reinterpret_cast(xmlGetProp(childNode, curAtt->name)); - if (!Compare(curAtt->name, "index0")) + if (!Compare(curAtt->name, "count")) { - oldFormat = true; - Atoi(attStr, index0); - } - else if (!Compare(curAtt->name, "index1")) - { - oldFormat = true; - Atoi(attStr, index1); - } - else if (!Compare(curAtt->name, "hue0")) - { - oldFormat = true; - Atof(attStr, hue0); - } - else if (!Compare(curAtt->name, "hue1")) - { - oldFormat = true; - Atof(attStr, hue1); - } - else if (!Compare(curAtt->name, "blend")) - { - oldFormat = true; - Atof(attStr, blend); - } - else if (!Compare(curAtt->name, "count")) - { - newFormat = true; Atoi(attStr, numColors); } else if (!Compare(curAtt->name, "format")) { - newFormat = true; - if (!_stricmp(attStr, "RGB")) numBytes = 3; else if (!_stricmp(attStr, "RGBA")) @@ -907,29 +877,16 @@ private: xmlFree(attStr); } - //Old or new format? - if (newFormat && oldFormat) + //Removing support for whatever "old format" was in flam3. + //Read formatted string from contents of tag. + char* palStr = CX(xmlNodeGetContent(childNode)); + + if (!ParseHexColors(palStr, currentEmber, numColors, numBytes)) { - oldFormat = false; - m_ErrorReport.push_back(string(loc) + " : Mixing of old and new palette tag syntax not allowed, defaulting to new"); + m_ErrorReport.push_back(string(loc) + " : Problem reading hexadecimal color data in palette"); } - if (oldFormat) - { - InterpolateCmap(currentEmber.m_Palette, blend, index0, hue0, index1, hue1); - } - else - { - //Read formatted string from contents of tag. - char* palStr = CX(xmlNodeGetContent(childNode)); - - if (!ParseHexColors(palStr, currentEmber, numColors, numBytes)) - { - m_ErrorReport.push_back(string(loc) + " : Problem reading hexadecimal color data in palette"); - } - - xmlFree(palStr); - } + xmlFree(palStr); } else if (!Compare(childNode->name, "symmetry")) { @@ -1436,51 +1393,6 @@ private: return ok; } - /// - /// Interpolate the palette. - /// Used with older formats, deprecated. - /// - /// The palette to interpolate - /// The blend - /// The first index - /// The first hue - /// The second index - /// The second hue/param> - void InterpolateCmap(Palette& palette, T blend, int index0, T hue0, int index1, T hue1) - { - int i, j; - const char* loc = __FUNCTION__; - Palette adjustedPal0, adjustedPal1; - - if (m_PaletteList.GetHueAdjustedPalette(index0, hue0, adjustedPal0) && - m_PaletteList.GetHueAdjustedPalette(index1, hue1, adjustedPal1)) - { - v4T* hueAdjusted0 = adjustedPal0.m_Entries.data(); - v4T* hueAdjusted1 = adjustedPal1.m_Entries.data(); - - for (i = 0; i < 256; i++) - { - T t[4], s[4]; - - Palette::RgbToHsv(glm::value_ptr(hueAdjusted0[i]), s); - Palette::RgbToHsv(glm::value_ptr(hueAdjusted1[i]), t); - - s[3] = hueAdjusted0[i][3]; - t[3] = hueAdjusted1[i][3]; - - for (j = 0; j < 4; j++) - t[j] = ((1 - blend) * s[j]) + (blend * t[j]); - - Palette::HsvToRgb(t, glm::value_ptr(palette.m_Entries[i])); - palette.m_Entries[i][3] = t[3]; - } - } - else - { - m_ErrorReport.push_back(string(loc) + " : Unable to retrieve palettes"); - } - } - /// /// Wrapper to parse a floating point Xml value and convert it to float. /// diff --git a/Source/EmberCommon/EmberCommon.h b/Source/EmberCommon/EmberCommon.h index 9f63626..3670e2f 100644 --- a/Source/EmberCommon/EmberCommon.h +++ b/Source/EmberCommon/EmberCommon.h @@ -112,7 +112,9 @@ static bool InitPaletteList(const string& filename) { PaletteList paletteList;//Even though this is local, the members are static so they will remain. - if (!paletteList.Init(filename)) + paletteList.Add(filename); + + if (!paletteList.Size()) { cout << "Error parsing palette file " << filename << ". Reason: " << endl; cout << paletteList.ErrorReportString() << endl << "Returning without executing." << endl; diff --git a/Source/EmberCommon/EmberCommonPch.h b/Source/EmberCommon/EmberCommonPch.h index 85a74bc..242476e 100644 --- a/Source/EmberCommon/EmberCommonPch.h +++ b/Source/EmberCommon/EmberCommonPch.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include diff --git a/Source/EmberTester/EmberTester.cpp b/Source/EmberTester/EmberTester.cpp index a66ca13..66b1ddc 100644 --- a/Source/EmberTester/EmberTester.cpp +++ b/Source/EmberTester/EmberTester.cpp @@ -102,7 +102,7 @@ void MakeTestAllVarsRegPrePost(vector>& embers) PaletteList paletteList; QTIsaac rand; - paletteList.Init("flam3-palettes.xml"); + paletteList.Add("flam3-palettes.xml"); Timing t; @@ -131,7 +131,7 @@ void MakeTestAllVarsRegPrePost(vector>& embers) ss << "NoVars"; emberNoVars.m_Name = ss.str(); ss.str(""); - emberNoVars.m_Palette = *paletteList.GetPalette(0); + emberNoVars.m_Palette = *paletteList.GetPalette(paletteList.m_DefaultFilename, 0); embers.push_back(emberNoVars); while (index < varList.RegSize()) @@ -196,7 +196,7 @@ void MakeTestAllVarsRegPrePost(vector>& embers) ss << index << "_" << regVar->Name(); ember1.m_Name = ss.str(); ss.str(""); - ember1.m_Palette = *paletteList.GetPalette(index % paletteList.Size()); + ember1.m_Palette = *paletteList.GetRandomPalette(); index++; embers.push_back(ember1); } @@ -1142,7 +1142,7 @@ void TestXformsInOutPoints() PaletteList paletteList; QTIsaac rand; - paletteList.Init("flam3-palettes.xml"); + paletteList.Add("flam3-palettes.xml"); while (index < varList.RegSize()) { @@ -1882,8 +1882,15 @@ int _tmain(int argc, _TCHAR* argv[]) { //int i; Timing t(4); - QTIsaac rand; - double d = 1; + QTIsaac rand(1, 2, 3); + mt19937 meow(1729); + + PaletteList palf; + Palette* pal = palf.GetRandomPalette(); + + cout << pal->Size() << endl; + + /*double d = 1; for (int i = 0; i < 10; i++) { @@ -1891,7 +1898,32 @@ int _tmain(int argc, _TCHAR* argv[]) d *= 10; } - return 0; + return 0;*/ + /* + uint i, iters = (uint)10e7; + size_t total = 0; + + t.Tic(); + for (i = 0; i < iters; i++) + { + total += rand.RandByte(); + total += rand.Rand(); + } + t.Toc("Isaac sum"); + + cout << "Isaac total = " << total << " for " << i << " iters." << endl; + + total = 0; + + t.Tic(); + for (i = 0; i < iters; i++) + { + total += meow(); + } + t.Toc("Mt sum"); + + cout << "Mt total = " << total << " for " << i << " iters." << endl; + */ //glm::vec2 solution, src[4]; //double bezT = 1, w[4]; //BezierPoints curvePoints[4]; diff --git a/Source/Fractorium/Fractorium.h b/Source/Fractorium/Fractorium.h index 77c38ca..70b3ed5 100644 --- a/Source/Fractorium/Fractorium.h +++ b/Source/Fractorium/Fractorium.h @@ -243,11 +243,11 @@ public slots: //Xforms Xaos. void OnXaosChanged(double d); - void OnXaosFromToToggled(bool checked); void OnClearXaosButtonClicked(bool checked); void OnRandomXaosButtonClicked(bool checked); //Palette. + void OnPaletteFilenameComboChanged(const QString& text); void OnPaletteAdjust(int d); void OnPaletteCellClicked(int row, int col); void OnPaletteCellDoubleClicked(int row, int col); diff --git a/Source/Fractorium/Fractorium.ui b/Source/Fractorium/Fractorium.ui index d3db396..3f633e4 100644 --- a/Source/Fractorium/Fractorium.ui +++ b/Source/Fractorium/Fractorium.ui @@ -2953,8 +2953,8 @@ SpinBox 0 0 - 118 - 618 + 245 + 747 @@ -4561,7 +4561,10 @@ SpinBox Qt::ScrollBarAsNeeded - Qt::ScrollBarAlwaysOff + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustToContents false @@ -4603,29 +4606,32 @@ SpinBox false - 2 + 3 false - false + true - 110 + 40 false - 27 + 15 - true + false false + + true + 22 @@ -4640,17 +4646,22 @@ SpinBox - Default Row + - Field + F1 - + F2 + + + + + F3 @@ -4663,56 +4674,11 @@ SpinBox - - - - - - - 0 - 0 - - - - Direction - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - false - - - - 6 + + + - - 2 - - - 6 - - - 6 - - - - - To - - - true - - - - - - - From - - - - + @@ -4752,7 +4718,7 @@ SpinBox Palette - + QLayout::SetDefaultConstraint @@ -4771,7 +4737,7 @@ SpinBox 6 - + @@ -4853,7 +4819,7 @@ SpinBox - + @@ -4986,7 +4952,7 @@ SpinBox - + Qt::StrongFocus @@ -5060,7 +5026,7 @@ SpinBox - + 4 @@ -5117,6 +5083,9 @@ SpinBox + + + @@ -6064,8 +6033,6 @@ SpinBox PreAffineGroupBox scrollArea_4 InfoBoundsGroupBox - XaosFromRadio - XaosToRadio diff --git a/Source/Fractorium/FractoriumEmberController.cpp b/Source/Fractorium/FractoriumEmberController.cpp index ff5c89e..4fb3b8b 100644 --- a/Source/Fractorium/FractoriumEmberController.cpp +++ b/Source/Fractorium/FractoriumEmberController.cpp @@ -76,7 +76,11 @@ FractoriumEmberController::FractoriumEmberController(Fractorium* fractorium) m_GLController = unique_ptr>(new GLEmberController(fractorium, fractorium->ui.GLDisplay, this)); m_PreviewRenderer = unique_ptr>(new EmberNs::Renderer()); SetupVariationTree(); - InitPaletteTable("flam3-palettes.xml"); + + //Initial combo change event to fill the palette table will be called automatically later. + if (!InitPaletteList("./")) + throw "No palettes found, exiting."; + BackgroundChanged(QColor(0, 0, 0));//Default to black. ClearUndo(); diff --git a/Source/Fractorium/FractoriumEmberController.h b/Source/Fractorium/FractoriumEmberController.h index 4969aab..937936a 100644 --- a/Source/Fractorium/FractoriumEmberController.h +++ b/Source/Fractorium/FractoriumEmberController.h @@ -177,14 +177,15 @@ public: virtual void VariationSpinBoxValueChanged(double d) { } //Xforms Xaos. - virtual void FillXaosWithCurrentXform() { } + virtual void FillXaos() { } virtual QString MakeXaosNameString(uint i) { return ""; } virtual void XaosChanged(DoubleSpinBox* sender) { } virtual void ClearXaos() { } virtual void RandomXaos() { } //Palette. - virtual bool InitPaletteTable(const string& s) { return false; } + virtual int InitPaletteList(const string& s) { return 0; } + virtual bool FillPaletteTable(const string& s) { return false; } virtual void ApplyPaletteToEmber() { } virtual void PaletteAdjust() { } virtual QRgb GetQRgbFromPaletteIndex(uint i) { return QRgb(); } @@ -235,6 +236,7 @@ protected: QImage m_FinalPaletteImage; QString m_LastSaveAll; QString m_LastSaveCurrent; + string m_CurrentPaletteFilePath; CriticalSection m_Cs; std::thread m_WriteThread; vector m_FinalImage[2]; @@ -405,14 +407,15 @@ public: void FillVariationTreeWithXform(Xform* xform); //Xforms Xaos. - virtual void FillXaosWithCurrentXform() override; + virtual void FillXaos() override; virtual QString MakeXaosNameString(uint i) override; virtual void XaosChanged(DoubleSpinBox* sender) override; virtual void ClearXaos() override; virtual void RandomXaos() override; //Palette. - virtual bool InitPaletteTable(const string& s) override; + virtual int InitPaletteList(const string& s) override; + virtual bool FillPaletteTable(const string& s) override; virtual void ApplyPaletteToEmber() override; virtual void PaletteAdjust() override; virtual QRgb GetQRgbFromPaletteIndex(uint i) override { return QRgb(); } diff --git a/Source/Fractorium/FractoriumMenus.cpp b/Source/Fractorium/FractoriumMenus.cpp index 3a68655..d97b960 100644 --- a/Source/Fractorium/FractoriumMenus.cpp +++ b/Source/Fractorium/FractoriumMenus.cpp @@ -98,7 +98,7 @@ void FractoriumEmberController::NewEmptyFlameInCurrentFile() xform.m_Weight = T(0.25); xform.m_ColorX = m_Rand.Frand01(); ember.AddXform(xform); - ember.m_Palette = *m_PaletteList.GetPalette(-1); + ember.m_Palette = *m_PaletteList.GetRandomPalette(); ember.m_Name = EmberFile::DefaultEmberName(m_EmberFile.Size() + 1).toStdString(); ember.m_Index = m_EmberFile.Size(); m_EmberFile.m_Embers.push_back(ember);//Will invalidate the pointers contained in the EmberTreeWidgetItems, UpdateLibraryTree() will resync. diff --git a/Source/Fractorium/FractoriumPalette.cpp b/Source/Fractorium/FractoriumPalette.cpp index 5455da7..fa1c7ac 100644 --- a/Source/Fractorium/FractoriumPalette.cpp +++ b/Source/Fractorium/FractoriumPalette.cpp @@ -12,6 +12,8 @@ void Fractorium::InitPaletteUI() QTableWidget* paletteTable = ui.PaletteListTable; QTableWidget* 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); @@ -30,52 +32,83 @@ void Fractorium::InitPaletteUI() 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. + QTableWidgetItem* previewNameCol = new QTableWidgetItem(""); + palettePreviewTable->setItem(0, 0, previewNameCol); + QTableWidgetItem* previewPaletteItem = new QTableWidgetItem(); + palettePreviewTable->setItem(0, 1, previewPaletteItem); + + paletteTable->setColumnWidth(1, 260);//256 plus small margin on each side. + paletteTable->horizontalHeader()->setSectionsClickable(false); +} + +/// +/// 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 +int 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, or controller type change. +/// Called upon initialization, palette combo index change, and controller type change. /// -/// The full path to the palette file +/// The name to the palette file without the path /// True if successful, else false. template -bool FractoriumEmberController::InitPaletteTable(const string& s) +bool FractoriumEmberController::FillPaletteTable(const string& s) { QTableWidget* paletteTable = m_Fractorium->ui.PaletteListTable; QTableWidget* palettePreviewTable = m_Fractorium->ui.PalettePreviewTable; - paletteTable->clear(); - - if (m_PaletteList.Init(s))//Default to this, but add an option later.//TODO + m_CurrentPaletteFilePath = m_Fractorium->ui.PaletteFilenameCombo->property("path").toString().toStdString() + s; + size_t paletteSize = m_PaletteList.Size(m_CurrentPaletteFilePath); + + if (paletteSize) { - //Preview table. - palettePreviewTable->setRowCount(1); - palettePreviewTable->setColumnWidth(1, 260);//256 plus small margin on each side. - QTableWidgetItem* previewNameCol = new QTableWidgetItem(""); - palettePreviewTable->setItem(0, 0, previewNameCol); - QTableWidgetItem* previewPaletteItem = new QTableWidgetItem(); - palettePreviewTable->setItem(0, 1, previewPaletteItem); - - //Palette list table. - paletteTable->setRowCount(m_PaletteList.Size()); - paletteTable->setColumnWidth(1, 260);//256 plus small margin on each side. - paletteTable->horizontalHeader()->setSectionsClickable(false); - + paletteTable->clear(); + paletteTable->blockSignals(true); + paletteTable->setRowCount(paletteSize); + //Headers get removed when clearing, so must re-create here. QTableWidgetItem* nameHeader = new QTableWidgetItem("Name"); QTableWidgetItem* paletteHeader = new QTableWidgetItem("Palette"); - nameHeader->setTextAlignment(Qt::AlignLeft|Qt::AlignVCenter); - paletteHeader->setTextAlignment(Qt::AlignLeft|Qt::AlignVCenter); + nameHeader->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); + paletteHeader->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); paletteTable->setHorizontalHeaderItem(0, nameHeader); paletteTable->setHorizontalHeaderItem(1, paletteHeader); //Palette list table. - for (size_t i = 0; i < m_PaletteList.Size(); i++) + for (size_t i = 0; i < paletteSize; i++) { - Palette* p = m_PaletteList.GetPalette(i); + Palette* p = m_PaletteList.GetPalette(m_CurrentPaletteFilePath, i); vector v = p->MakeRgbPaletteBlock(PALETTE_CELL_HEIGHT); QTableWidgetItem* nameCol = new QTableWidgetItem(p->m_Name.c_str()); @@ -89,6 +122,8 @@ bool FractoriumEmberController::InitPaletteTable(const string& s) paletteTable->setItem(i, 1, paletteItem); } + paletteTable->blockSignals(false); + m_Fractorium->OnPaletteRandomSelectButtonClicked(true); return true; } else @@ -102,6 +137,8 @@ bool FractoriumEmberController::InitPaletteTable(const string& s) return false; } +void Fractorium::OnPaletteFilenameComboChanged(const QString& text) { m_Controller->FillPaletteTable(text.toStdString()); } + /// /// Apply adjustments to the current ember's palette. /// @@ -188,7 +225,7 @@ void Fractorium::OnPaletteAdjust(int d) { m_Controller->PaletteAdjust(); } template void FractoriumEmberController::PaletteCellClicked(int row, int col) { - Palette* palette = m_PaletteList.GetPalette(row); + Palette* palette = m_PaletteList.GetPalette(m_CurrentPaletteFilePath, row); QTableWidgetItem* nameItem = m_Fractorium->ui.PaletteListTable->item(row, 0); if (palette) @@ -225,11 +262,11 @@ void Fractorium::OnPaletteCellDoubleClicked(int row, int col) /// /// Set the selected palette to a randomly selected one, -/// applying any adjustments previously specified. +/// applying any adjustments previously specified if the checked parameter is true. /// Called when the Random Palette button is clicked. /// Resets the rendering process. /// -/// The current row selected +/// True to clear the current adjustments, else leave current adjustments. void Fractorium::OnPaletteRandomSelectButtonClicked(bool checked) { uint i = 0; @@ -237,7 +274,10 @@ void Fractorium::OnPaletteRandomSelectButtonClicked(bool checked) while ((i = QTIsaac::GlobalRand->Rand(rowCount)) == uint(m_PreviousPaletteRow)); - OnPaletteCellClicked(i, 1); + if (checked) + OnPaletteCellDoubleClicked(i, 1);//Will clear the adjustments. + else + OnPaletteCellClicked(i, 1); } /// diff --git a/Source/Fractorium/FractoriumParams.cpp b/Source/Fractorium/FractoriumParams.cpp index 2e988f7..101df9d 100644 --- a/Source/Fractorium/FractoriumParams.cpp +++ b/Source/Fractorium/FractoriumParams.cpp @@ -568,6 +568,9 @@ void FractoriumEmberController::FillParamTablesAndPalette() m_TempPalette = m_Ember.m_Palette; UpdateAdjustedPaletteGUI(m_Ember.m_Palette);//Will clear name string since embedded palettes have no name. This will trigger a full render. } + + //Xaos. + FillXaos(); } /// diff --git a/Source/Fractorium/FractoriumXforms.cpp b/Source/Fractorium/FractoriumXforms.cpp index aa66dae..3381fca 100644 --- a/Source/Fractorium/FractoriumXforms.cpp +++ b/Source/Fractorium/FractoriumXforms.cpp @@ -267,11 +267,11 @@ void FractoriumEmberController::XformNameChanged(int row, int col) xform->m_Name = m_Fractorium->ui.XformWeightNameTable->item(row, col)->text().toStdString(); - if (index != -1) - { - if (QTableWidgetItem* xformNameItem = m_Fractorium->ui.XaosTable->item(index, 0)) - xformNameItem->setText(MakeXaosNameString(index)); - } + //if (index != -1) + //{ + // if (QTableWidgetItem* xformNameItem = m_Fractorium->ui.XaosTable->item(index, 0)) + // xformNameItem->setText(MakeXaosNameString(index)); + //} }, false); } @@ -298,7 +298,6 @@ void FractoriumEmberController::FillWithXform(Xform* xform) FillColorWithXform(xform); FillAffineWithXform(xform, true); FillAffineWithXform(xform, false); - FillXaosWithCurrentXform(); } /// diff --git a/Source/Fractorium/FractoriumXformsXaos.cpp b/Source/Fractorium/FractoriumXformsXaos.cpp index f0d07df..848f6d7 100644 --- a/Source/Fractorium/FractoriumXformsXaos.cpp +++ b/Source/Fractorium/FractoriumXformsXaos.cpp @@ -6,42 +6,24 @@ /// void Fractorium::InitXformsXaosUI() { - connect(ui.XaosToRadio, SIGNAL(toggled(bool)), this, SLOT(OnXaosFromToToggled(bool)), Qt::QueuedConnection); connect(ui.ClearXaosButton, SIGNAL(clicked(bool)), this, SLOT(OnClearXaosButtonClicked(bool)), Qt::QueuedConnection); connect(ui.RandomXaosButton, SIGNAL(clicked(bool)), this, SLOT(OnRandomXaosButtonClicked(bool)), Qt::QueuedConnection); } /// -/// Fill the xaos table with the values from the current xform. +/// Fill the xaos table with the values from the ember. /// template -void FractoriumEmberController::FillXaosWithCurrentXform() +void FractoriumEmberController::FillXaos() { - Xform* currXform = CurrentXform(); - - if (!IsFinal(currXform)) + for (int i = 0, count = int(XformCount()); i < count; i++) { - for (int i = 0; i < m_Ember.XformCount(); i++) - { - DoubleSpinBox* spinBox = dynamic_cast(m_Fractorium->ui.XaosTable->cellWidget(i, 1)); + auto* xform = m_Ember.GetXform(i); - //Fill in values column. - if (m_Fractorium->ui.XaosToRadio->isChecked())//"To": Single xform, advance index. - spinBox->SetValueStealth(currXform->Xaos(i)); - else//"From": Advance xforms, single index. - if ((currXform = m_Ember.GetXform(i))) - spinBox->SetValueStealth(currXform->Xaos(m_Fractorium->ui.CurrentXformCombo->currentIndex())); - - //Fill in name column. - Xform* xform = m_Ember.GetXform(i); - QTableWidgetItem* xformNameItem = m_Fractorium->ui.XaosTable->item(i, 0); - - if (xform && xformNameItem) - xformNameItem->setText(MakeXaosNameString(i)); - } + for (int j = 0; j < count; j++) + if (auto* spinBox = dynamic_cast(m_Fractorium->ui.XaosTable->cellWidget(i, j))) + spinBox->SetValueStealth(xform->Xaos(j)); } - - m_Fractorium->ui.XaosTable->setEnabled(!IsFinal(currXform));//Disable if final, else enable. } /// @@ -55,22 +37,22 @@ QString FractoriumEmberController::MakeXaosNameString(uint i) Xform* xform = m_Ember.GetXform(i); QString name; - if (xform) - { - int indexPlus1 = m_Ember.GetXformIndex(xform) + 1;//GUI is 1 indexed to avoid confusing the user. - int curr = m_Fractorium->ui.CurrentXformCombo->currentIndex() + 1; - - if (indexPlus1 != -1) - { - if (m_Fractorium->ui.XaosToRadio->isChecked()) - name = QString("From ") + ToString(curr) + QString(" To ") + ToString(indexPlus1); - else - name = QString("From ") + ToString(indexPlus1) + QString(" To ") + ToString(curr); - - //if (xform->m_Name != "") - // name = name + " (" + QString::fromStdString(xform->m_Name) + ")"; - } - } + //if (xform) + //{ + // int indexPlus1 = m_Ember.GetXformIndex(xform) + 1;//GUI is 1 indexed to avoid confusing the user. + // int curr = m_Fractorium->ui.CurrentXformCombo->currentIndex() + 1; + // + // if (indexPlus1 != -1) + // { + // if (m_Fractorium->ui.XaosToRadio->isChecked()) + // name = QString("From ") + ToString(curr) + QString(" To ") + ToString(indexPlus1); + // else + // name = QString("From ") + ToString(indexPlus1) + QString(" To ") + ToString(curr); + // + // //if (xform->m_Name != "") + // // name = name + " (" + QString::fromStdString(xform->m_Name) + ")"; + // } + //} return name; } @@ -78,109 +60,93 @@ QString FractoriumEmberController::MakeXaosNameString(uint i) /// /// Set the xaos value. /// Called when any xaos spinner is changed. -/// Different action taken based on the state of to/from radio button. /// Resets the rendering process. /// /// The DoubleSpinBox that triggered this event template void FractoriumEmberController::XaosChanged(DoubleSpinBox* sender) { - UpdateCurrentXform([&] (Xform* xform) - { - QTableWidget* xaosTable = m_Fractorium->ui.XaosTable; + auto p = sender->property("tableindex").toPoint(); - if (!IsFinal(xform))//This should never get called for the final xform because the table will be disabled, but check just to be safe. - { - for (int i = 0; i < xaosTable->rowCount(); i++)//Find the spin box that triggered the event. - { - DoubleSpinBox* spinBox = dynamic_cast(xaosTable->cellWidget(i, 1)); - - if (spinBox == sender) - { - if (m_Fractorium->ui.XaosToRadio->isChecked())//"To": Single xform, advance index. - { - xform->SetXaos(i, spinBox->value()); - } - else//"From": Advance xforms, single index. - { - if ((xform = m_Ember.GetXform(i)))//Single = is intentional. - xform->SetXaos(m_Fractorium->ui.CurrentXformCombo->currentIndex(), spinBox->value()); - } - - break; - } - } - } - }); + if (auto* xform = m_Ember.GetXform(p.x())) + Update([&] { xform->SetXaos(p.y(), sender->value()); }); } void Fractorium::OnXaosChanged(double d) { - if (DoubleSpinBox* senderSpinBox = dynamic_cast(this->sender())) + if (auto* senderSpinBox = dynamic_cast(this->sender())) m_Controller->XaosChanged(senderSpinBox); } /// -/// Update xaos display to use either "to" or "from" logic. -/// Called when xaos to/from radio buttons checked. -/// -/// Ignored -void Fractorium::OnXaosFromToToggled(bool checked) -{ - m_Controller->FillXaosWithCurrentXform(); -} - -/// -/// Clear xaos table, recreate all spinners based on the xaos used by the current xform in the current ember. -/// Called every time the current xform changes. +/// Clear xaos table, recreate all spinners based on the xaos used in the current ember. /// void Fractorium::FillXaosTable() { int spinHeight = 20; + int count = int(m_Controller->XformCount()); QWidget* w = nullptr; - ui.XaosTable->setRowCount(m_Controller->XformCount());//This will grow or shrink the number of rows and call the destructor for previous DoubleSpinBoxes. + QString lbl("lbl"); - for (int i = 0; i < int(m_Controller->XformCount()); i++) + ui.XaosTable->setRowCount(count);//This will grow or shrink the number of rows and call the destructor for previous DoubleSpinBoxes. + ui.XaosTable->setColumnCount(count); + + ui.XaosTable->verticalHeader()->setVisible(true); + ui.XaosTable->horizontalHeader()->setVisible(true); + ui.XaosTable->verticalHeader()->setSectionsClickable(false); + ui.XaosTable->horizontalHeader()->setSectionsClickable(false); + + for (int i = 0; i < count; i++) { - DoubleSpinBox* spinBox = new DoubleSpinBox(ui.XaosTable, spinHeight, 0.1); - QTableWidgetItem* xformNameItem = new QTableWidgetItem(m_Controller->MakeXaosNameString(i)); + for (int j = 0; j < count; j++) + { + QPoint p(i, j); + DoubleSpinBox* spinBox = new DoubleSpinBox(ui.XaosTable, spinHeight, 0.1); - spinBox->DoubleClick(true); - spinBox->DoubleClickZero(1); - spinBox->DoubleClickNonZero(0); - ui.XaosTable->setItem(i, 0, xformNameItem); - ui.XaosTable->setCellWidget(i, 1, spinBox); - connect(spinBox, SIGNAL(valueChanged(double)), this, SLOT(OnXaosChanged(double)), Qt::QueuedConnection); + spinBox->setFixedWidth(35); + spinBox->DoubleClick(true); + spinBox->DoubleClickZero(1); + spinBox->DoubleClickNonZero(0); + spinBox->setProperty("tableindex", p); + ui.XaosTable->setCellWidget(i, j, spinBox); + + auto wp = ui.XaosTable->item(i, j); - if (i > 0) - w = SetTabOrder(this, w, spinBox); - else - w = spinBox; + if (wp) + wp->setTextAlignment(Qt::AlignCenter); + + connect(spinBox, SIGNAL(valueChanged(double)), this, SLOT(OnXaosChanged(double)), Qt::QueuedConnection); + + if (i == 0 && j == 0) + w = spinBox; + else + w = SetTabOrder(this, w, spinBox); + } + } + + for (int i = 0; i < count; i++) + { + ui.XaosTable->setHorizontalHeaderItem(i, new QTableWidgetItem("F" + QString::number(i + 1))); + ui.XaosTable->setVerticalHeaderItem(i, new QTableWidgetItem("T" + QString::number(i + 1))); + ui.XaosTable->horizontalHeader()->setSectionResizeMode(i, QHeaderView::ResizeToContents); + ui.XaosTable->verticalHeader()->setSectionResizeMode(i, QHeaderView::ResizeToContents); } - w = SetTabOrder(this, w, ui.XaosToRadio); - w = SetTabOrder(this, w, ui.XaosFromRadio); + ui.XaosTable->resizeRowsToContents(); + ui.XaosTable->resizeColumnsToContents(); + w = SetTabOrder(this, w, ui.ClearXaosButton); w = SetTabOrder(this, w, ui.RandomXaosButton); } /// /// Clear all xaos from the current ember. -/// Called when xaos to/from radio buttons checked. /// -/// Ignored template void FractoriumEmberController::ClearXaos() { - UpdateCurrentXform([&] (Xform* xform) - { - m_Ember.ClearXaos(); - }); - - //Can't just call FillXaosWithCurrentXform() because the current xform might the final. - for (int i = 0; i < m_Ember.XformCount(); i++) - if (DoubleSpinBox* spinBox = dynamic_cast(m_Fractorium->ui.XaosTable->cellWidget(i, 1))) - spinBox->SetValueStealth(1.0); + Update([&] { m_Ember.ClearXaos(); }); + FillXaos(); } void Fractorium::OnClearXaosButtonClicked(bool checked) { m_Controller->ClearXaos(); } @@ -195,25 +161,24 @@ void Fractorium::OnClearXaosButtonClicked(bool checked) { m_Controller->ClearXao template void FractoriumEmberController::RandomXaos() { - UpdateCurrentXform([&](Xform* xform) + Update([&] { for (size_t i = 0; i < m_Ember.XformCount(); i++) { - if (Xform* localXform = m_Ember.GetXform(i)) + if (auto* xform = m_Ember.GetXform(i)) { for (size_t j = 0; j < m_Ember.XformCount(); j++) { if (m_Rand.RandBit()) - localXform->SetXaos(j, T(m_Rand.RandBit())); + xform->SetXaos(j, T(m_Rand.RandBit())); else - localXform->SetXaos(j, m_Rand.Frand(0, 3)); + xform->SetXaos(j, m_Rand.Frand(0, 3)); } } } }); - - //If current is final, it will update when they change to a non-final xform. - FillXaosWithCurrentXform(); + + FillXaos(); } void Fractorium::OnRandomXaosButtonClicked(bool checked) { m_Controller->RandomXaos(); }