#include "EmberPch.h" #include "PaletteList.h" namespace EmberNs { /// /// Empty constructor which initializes the palette map with the default palette file. /// template PaletteList::PaletteList() { } /// /// Destructor which saves any modifiable palettes to file, just in case they were modified. /// template PaletteList::~PaletteList() { for (auto& palFile : s_Palettes) { if (IsModifiable(palFile.first)) Save(palFile.first); } } /// /// Create a new palette file with the given name and vector of palettes, and save it. /// /// The full path to the file to add /// The list of palettes which comprise the file /// True if the file did not exist, was successfully added and saved, else false. template bool PaletteList::AddPaletteFile(const string& filename, const vector>& palettes) { if (!GetPaletteListByFullPath(filename)) { auto item = s_Palettes.insert(make_pair(filename, palettes)); Save(filename); return true; } return false; } /// /// Create an new empty palette file with the given name with a single modifiable palette in it. /// /// The full path to the file to add /// The list of palettes which comprise the file /// True if the file did not exist, was successfully added and saved, else false. template bool PaletteList::AddEmptyPaletteFile(const string& filename) { if (!GetPaletteListByFullPath(filename)) { auto item = s_Palettes.insert(make_pair(filename, vector>())); Palette p; p.m_Index = 0; p.m_Name = "empty-default"; p.m_Filename = make_shared(filename); p.m_SourceColors = map { { T(0), v4T(T(0), T(0), T(0), T(1)) }, { T(1), v4T(T(0), T(0), T(0), T(1)) } }; item.first->second.push_back(p); Save(filename); return true; } return false; } /// /// Add a new palette to an existing palette file and save the file. /// /// The full path to the existing palette file to add /// The new palette to add to the file /// True if the palette file existed, the palette was added, and the file was successfully saved, else false. template bool PaletteList::AddPaletteToFile(const string& filename, const Palette& palette) { if (auto p = GetPaletteListByFullPathOrFilename(filename)) { p->push_back(palette); p->back().m_Index = int(p->size()) - 1; Save(filename); return true; } return false; } /// /// Replace an existing palette in a palette file with a new one and save the file. /// The match is done based on palette name, so if there are duplicate names in /// the file, only the first one will be replaced. /// /// The full path to the existing palette file to replace a palette in /// The new palette to use to replace an existing one in the file /// True if the palette file existed, the palette was replaced, and the file was successfully saved, else false. template bool PaletteList::Replace(const string& filename, const Palette& palette) { if (auto p = GetPaletteListByFullPathOrFilename(filename)) { for (auto& pal : *p) { if (pal.m_Name == palette.m_Name) { auto index = pal.m_Index; pal = palette; pal.m_Index = index; Save(filename); return true; } } } return false; } /// /// Replace an existing palette in a palette file with a new one and save the file. /// The match is done based on the passed in index. /// /// The full path to the existing palette file to replace a palette in /// The new palette to use to replace an existing one in the file /// The 0-based index of the palette to replace /// True if the palette file existed, the palette was replaced, and the file was successfully saved, else false. template bool PaletteList::Replace(const string& filename, const Palette& palette, int index) { if (auto p = GetPaletteListByFullPathOrFilename(filename)) { if (index < p->size()) { (*p)[index] = palette; (*p)[index].m_Index = index; Save(filename); return true; } } return false; } /// /// Delete an existing palette from a palette file. /// The match is done based on the passed in index. /// /// The full path to the existing palette file to delete a palette from /// The 0-based index of the palette to delete /// True if the palette file existed, the palette was deleted, and the file was successfully saved, else false. template bool PaletteList::Delete(const string& filename, int index) { int i = 0; if (auto p = GetPaletteListByFullPathOrFilename(filename)) { if (index < p->size()) { p->erase(p->begin() + index); for (auto& pal : *p) pal.m_Index = i++; Save(filename); return true; } } return false; } /// /// Read an Xml palette file into memory. /// This must be called before any palette file usage. /// /// The full path to the file to read /// If true, override the initialization state and force a read, else observe the initialization state. /// Whether anything was read template bool PaletteList::Add(const string& filename, bool force) { bool added = true; bool contains = GetPaletteListByFullPathOrFilename(filename) != nullptr; auto filenameonly = GetFilename(filename); if (contains && !force)//Don't allow any palettes with the same name, even if they reside in different paths. return false; auto palettes = s_Palettes.insert(make_pair(filename, vector>())); if (force || palettes.second) { string buf; const char* loc = __FUNCTION__; if (ReadFile(filename.c_str(), buf)) { auto lower = ToLower(filename); auto pfilename = shared_ptr(new string(filename)); if (EndsWith(lower, ".xml")) { xmlDocPtr doc = xmlReadMemory(static_cast(buf.data()), int(buf.size()), filename.c_str(), nullptr, XML_PARSE_NONET); if (doc) { auto rootNode = xmlDocGetRootElement(doc); if (!Compare(rootNode->name, "palettes")) { palettes.first->second.clear(); palettes.first->second.reserve(buf.size() / 2048);//Roughly the size in bytes it takes to store the xml text of palette. ParsePalettes(rootNode, pfilename, palettes.first->second); xmlFreeDoc(doc); } if (palettes.first->second.empty()) { added = false;//Reading failed, likely not a valid palette file. s_Palettes.erase(filename); AddToReport(string(loc) + " : Couldn't parse xml doc"); } } else { added = false; s_Palettes.erase(filename); AddToReport(string(loc) + " : Couldn't load xml doc"); } } else if (EndsWith(lower, ".ugr") || EndsWith(lower, ".gradient") || EndsWith(lower, ".gradients")) { if (!ParsePalettes(buf, pfilename, palettes.first->second)) { added = false; s_Palettes.erase(filename); AddToReport(string(loc) + " : Couldn't read palette file " + filename); } } } else { added = false; s_Palettes.erase(filename); AddToReport(string(loc) + " : Couldn't read palette file " + filename); } } return added; } /// /// Get the palette at a random index in a random file in the map. /// /// A pointer to a random palette in a random file if successful, else nullptr. template Palette* PaletteList::GetRandomPalette() { auto p = s_Palettes.begin(); size_t i = 0, paletteFileIndex = QTIsaac::LockedRand() % Size(); //Move p forward i elements. while (i < paletteFileIndex && p != s_Palettes.end()) { ++i; ++p; } if (i < Size()) { size_t paletteIndex = QTIsaac::LockedRand() % p->second.size(); if (paletteIndex < p->second.size()) return &p->second[paletteIndex]; } return nullptr; } /// /// Get the palette at a specified index in the specified file in the map. /// /// 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. template Palette* PaletteList::GetPaletteByFilename(const string& filename, size_t i) { if (auto palettes = GetPaletteListByFilename(filename)) if (i < palettes->size()) return &(*palettes)[i]; return nullptr; } /// /// Get the palette at a specified index in the specified file in the map. /// /// The full path and 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. template Palette* PaletteList::GetPaletteByFullPath(const string& filename, size_t i) { if (auto palettes = GetPaletteListByFullPath(filename)) if (i < palettes->size()) return &(*palettes)[i]; return nullptr; } /// /// Get a pointer to a palette with a specified name in the specified full path and filename in the map. /// /// The filename of the palette to retrieve /// The name of the palette to retrieve /// A pointer to the requested palette if found, else nullptr. template Palette* PaletteList::GetPaletteByName(const string& filename, const string& name) { if (auto palettes = GetPaletteListByFullPathOrFilename(filename)) for (auto& palette : *palettes) if (palette.m_Name == name) return &palette; return nullptr; } /// /// Get the palette file with the specified filename in the map. /// /// The filename of the palette to retrieve /// A pointer to the requested palette if found, else nullptr. template vector>* PaletteList::GetPaletteListByFilename(const string& filename) { auto filenameonly = GetFilename(filename); for (auto& palettes : s_Palettes) if (GetFilename(palettes.first) == filenameonly) return &palettes.second; return nullptr; } /// /// Get the palette file with the specified full path and filename in the map. /// /// The full path and filename of the palette to retrieve /// A pointer to the requested palette if found, else nullptr. template vector>* PaletteList::GetPaletteListByFullPath(const string& filename) { auto palettes = s_Palettes.find(filename); if (palettes != s_Palettes.end() && !palettes->second.empty()) return &palettes->second; return nullptr; } /// /// Get the palette file with the specified full path and filename in the map. /// If that does not work, try getting it with the filename alone. /// /// The full path and filename or just the filename of the palette to retrieve /// A pointer to the requested palette if found, else nullptr. template vector>* PaletteList::GetPaletteListByFullPathOrFilename(const string& filename) { auto p = GetPaletteListByFullPath(filename); if (!p) p = GetPaletteListByFilename(filename); return p; } /// /// Get full path and filename of the pallete with the specified filename /// /// The filename only of the palette to retrieve /// A pointer to the requested palette if found, else nullptr. template string PaletteList::GetFullPathFromFilename(const string& filename) { auto filenameonly = GetFilename(filename); for (auto& palettes : s_Palettes) if (GetFilename(palettes.first) == filenameonly) return palettes.first; return ""; } /// /// 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. template bool PaletteList::GetHueAdjustedPalette(const string& filename, size_t i, T hue, Palette& palette) { if (auto unadjustedPal = GetPaletteByFullPath(filename, i)) { unadjustedPal->MakeHueAdjustedPalette(palette, hue); return true; } return false; } /// /// Clear the palette list and reset the initialization state. /// template void PaletteList::Clear() { s_Palettes.clear(); } /// /// Get the size of the palettes map. /// This will be the number of files read. /// /// The size of the palettes map template size_t PaletteList::Size() { return s_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 template size_t PaletteList::Size(size_t index) { size_t i = 0; auto p = s_Palettes.begin(); while (i < index && p != s_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 template size_t PaletteList::Size(const string& s) { return s_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 template const string& PaletteList::Name(size_t index) { size_t i = 0; auto p = s_Palettes.begin(); while (i < index && p != s_Palettes.end()) { ++i; ++p; } return p->first; } /// /// Determines whether at least one palette in the passed in palette file is modifiable, /// meaning whether the source colors have at least one element in them. /// /// The full path to the existing palette file to search for a modifiable palette in /// True if at least one palette in the file was modifiable, else false. template bool PaletteList::IsModifiable(const string& filename) { if (auto palFile = GetPaletteListByFullPathOrFilename(filename)) { for (auto& pal : *palFile) if (!pal.m_SourceColors.empty()) return true; } return false; } /// /// Get a const ref to the underlying static palette structure. /// /// s_Palettes template const map>>& PaletteList::Palettes() const { return s_Palettes; } /// /// Saves an existing file to disk. /// /// The full path to the existing palette file to save /// True if successful, else false. template bool PaletteList::Save(const string& filename) { auto fullpath = GetFullPathFromFilename(filename); try { size_t index = 0; ostringstream os; if (auto palFile = GetPaletteListByFullPathOrFilename(filename)) { ofstream f(fullpath); os << "\n"; if (f.is_open()) { for (auto& pal : *palFile) { os << "\n"; } } os << ""; string s = os.str(); f.write(s.c_str(), s.size()); return true; } } catch (const std::exception& e) { cout << "Error: Writing palette file " << fullpath << " failed: " << e.what() << "\n"; return false; } catch (...) { cout << "Error: Writing palette file " << fullpath << " failed.\n"; return false; } return false; } /// /// Parses an Xml node for all palettes present and stores them 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. /// The name of the Xml file. /// The vector to store the parsed palettes associated with this file in. template void PaletteList::ParsePalettes(xmlNode* node, const shared_ptr& filename, vector>& palettes) { char* val; xmlAttrPtr attr; int index = 0; while (node) { if (node->type == XML_ELEMENT_NODE && !Compare(node->name, "palette")) { attr = node->properties; Palette palette; while (attr) { val = reinterpret_cast(xmlGetProp(node, attr->name)); if (!Compare(attr->name, "data")) { string s1, s; size_t tmp, colorCount = 0; stringstream ss, temp(val); ss >> std::hex; s.reserve(2048); while (temp >> s1) s += s1; auto length = s.size(); for (size_t strIndex = 0; strIndex < length;) { strIndex += 2;//Skip past the 00 at the beginning of each RGB. for (glm::length_t i = 0; i < 3 && colorCount < palette.Size(); i++) { const char tmpStr[3] = { s[strIndex++], s[strIndex++], 0 };//Read out and convert the string two characters at a time. ss.clear();//Reset and fill the string stream. ss.str(tmpStr); ss >> tmp;//Do the conversion. palette.m_Entries[colorCount][i] = T(tmp) / T(255);//Hex palette is [0..255], convert to [0..1]. } colorCount++; } } else if (!Compare(attr->name, "source_colors")) { string s(val); auto vec1 = Split(s, ' '); for (auto& v : vec1) { auto vec2 = Split(v, ','); if (vec2.size() == 4) { float d1 = Clamp(std::stof(vec2[0]), 0.0f, 1.0f); palette.m_SourceColors[d1] = v4F(Clamp(std::stof(vec2[1]), 0.0f, 1.0f), Clamp(std::stof(vec2[2]), 0.0f, 1.0f), Clamp(std::stof(vec2[3]), 0.0f, 1.0f), 1.0f); } } } else if (!Compare(attr->name, "name")) { palette.m_Name = string(val); } xmlFree(val); attr = attr->next; } palette.m_Index = index++; palette.m_Filename = filename; palettes.push_back(palette); } else { ParsePalettes(node->children, filename, palettes); } node = node->next; } } /// /// Parses a gradient file for all palettes present and stores them 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. /// This format is from Ultra Fractal and Apophysis. /// /// The data to parse. /// The name of the gradient file. /// The vector to store the parsed palettes associated with this file in. /// True if at least one palette is read, else false. template bool PaletteList::ParsePalettes(const string& buf, const shared_ptr& filename, vector>& palettes) { int paletteIndex = 0; size_t index = 0; string line; string name; bool reading = false; bool found = false; istringstream iss(buf); vector splitVec; const char leftBrace = '{'; const char rightBrace = '}'; const string titleStr = "title="; const string indexStr = "index="; const string colorStr = "color="; const string titleDelStr = " =\""; const string colorDelStr = " ="; Palette palette; Color col; palettes.clear(); while (std::getline(iss, line)) { if (!reading && Contains(line, leftBrace)) { reading = true; } else if (Contains(line, rightBrace)) { if (found) palettes.push_back(palette); reading = false; found = false; } if (reading) { if (Find(line, titleStr)) { splitVec = Split(line, titleDelStr, true); if (splitVec.size() > 2) name = splitVec[1]; } else if (Find(line, indexStr) && Find(line, colorStr)) { if (!found) { index = 0; found = true; palette.Clear(); palette.m_Index = paletteIndex++; palette.m_Name = name; palette.m_Filename = filename; } splitVec = Split(line, colorDelStr, true); if (splitVec.size() > 3 && index < 256) { int val = std::stoi(splitVec[3]); col.r = (val & 0xFF) / T(255);//Hex palette is [0..255], convert to [0..1]. col.g = ((val >> 8) & 0xFF) / T(255); col.b = ((val >> 16) & 0xFF) / T(255); palette[index] = col; } index++; } } } return !palettes.empty(); } template EMBER_API class PaletteList; #ifdef DO_DOUBLE template EMBER_API class PaletteList; #endif }