mirror of
https://bitbucket.org/mfeemster/fractorium.git
synced 2025-01-21 21:20:07 -05:00
90ec5b8246
-Show common folder locations such as documents, downloads, pictures in the sidebar in all file dialogs. -Warning message about exceeding memory in final render dialog now suggests strips as the solution to the problem. -Strips now has a tooltip explaining what it does. -Allow more digits in the spinners on the color section the flame tab. -Add manually adjustable size spinners in the final render dialog. Percentage scale and absolute size are fully synced. -Default prefix in final render is now the filename when doing animations (coming from sequence section of the library tab). -Changed the elliptic variation back to using a less precise version for float, and a more precise version for double. The last release had it always using double. -New applied xaos table that shows a read-only view of actual weights by taking the base xform weights and multiplying them by the xaos values. -New table in the xaos tab that gives a graphical representation of the probability that each xform is chosen, with and without xaos. -Add button to transpose the xaos rows and columns. -Add support for importing .chaos files from Chaotica. --Pasting back to Chaotica will work for most, but not all, variations due to incompatible parameter names in some. -Curves are now splines instead of Bezier. This adds compatibility with Chaotica, but breaks it for Apophysis. Xmls are still pastable, but the color curves will look different. --The curve editor on the palette tab can now add points by clicking on the lines and remove points by clicking on the points themselves, just like Chaotica. --Splines are saved in four new xml fields: overall_curve, red_curve, green_curve and blue_curve. -Allow for specifying the percentage of a sub batch each thread should iterate through per kernel call when running with OpenCL. This gives a roughly 1% performance increase due to having to make less kernel calls while iterating. --This field is present for interactive editing (where it's not very useful) and in the final render dialog. --On the command line, this is specified as --sbpctth for EmberRender and EmberAnimate. -Allow double clicking to toggle the supersample field in the flame tab between 1 and 2 for easily checking the effect of the field. -When showing affine values as polar coordinates, show angles normalized to 360 to match Chaotica. -Fuse Count spinner now toggles between 15 and 100 when double clicking for easily checking the effect of the field. -Added field for limiting the range in the x and y direction that the initial points are chosen from. -Added a field called K2 which is an alternative way to set brightness, ignored when zero. --This has no effect for many variations, but hs a noticeable effect for some. -Added new variations: arcsech arcsech2 arcsinh arctanh asteria block bwraps_rand circlecrop2 coth_spiral crackle2 depth_blur depth_blur2 depth_gaussian depth_gaussian2 depth_ngon depth_ngon2 depth_sine depth_sine2 dragonfire dspherical dust excinis exp2 flipx flowerdb foci_p gaussian glynnia2 glynnsim4 glynnsim5 henon henon hex_rand hex_truchet hypershift lazyjess lens lozi lozi modulusx modulusy oscilloscope2 point_symmetry pointsymmetry projective pulse rotate scry2 shift smartshape spher squares starblur2 swirl3 swirl3r tanh_spiral target0 target2 tile_hlp truchet_glyph truchet_inv truchet_knot unicorngaloshen vibration vibration2 --hex_truchet, hex_rand should always use double. They are extremely sensitive. --Bug fixes: -Bounds sign was flipped for x coordinate of world space when center was not zero. -Right clicking and dragging spinner showed menu on mouse up, even if it was very far away. -Text boxes for size in final render dialog were hard to type in. Same bug as xform weight used to be so fix the same way. -Fix spelling to be plural in toggle color speed box. -Stop using the blank user palette to generate flames. Either put colored palettes in it, or exclude it from randoms. -Clicking the random palette button for a palette file with only one palette in it would freeze the program. -Clicking none scale in final render did not re-render the preview. -Use less precision on random xaos. No need for 12 decimal places. -The term sub batch is overloaded in the options dialog. Change the naming and tooltip of those settings for cpu and opencl. --Also made clear in the tooltip for the default opencl quality setting that the value is per device. -The arrows spinner in palette editor appears like a read-only label. Made it look like a spinner. -Fix border colors for various spin boxes and table headers in the style sheet. Requires reload. -Fix a bug in the bwraps variation which would produce different results than Chaotica and Apophysis. -Synth was allowed to be selected for random flame generation when using an Nvidia card but it shouldn't have been because Nvidia has a hard time compiling synth. -A casting bug in the OpenCL kernels for log scaling and density filtering was preventing successful compilations on Intel iGPUs. Fixed even though we don't support anything other than AMD and Nvidia. -Palette rotation (click and drag) position was not being reset when loading a new flame. -When the xform circles were hidden, opening and closing the options dialog would improperly reshow them. -Double click toggle was broken on integer spin boxes. -Fixed tab order of some controls. -Creating a palette from a jpg in the palette editor only produced a single color. --Needed to package imageformats/qjpeg.dll with the Windows installer. -The basic memory benchmark test flame was not really testing memory. Make it more spread out. -Remove the temporal samples field from the flame tab, it was never used because it's only an animation parameter which is specified in the final render dialog or on the command line with EmberAnimate. --Code changes: -Add IsEmpty() to Palette to determine if a palette is all black. -Attempt to avoid selecting a blank palette in PaletteList::GetRandomPalette(). -Add function ScanForChaosNodes() and some associated helper functions in XmlToEmber. -Make variation param name correction be case insensitive in XmlToEmber. -Report error when assigning a variation param value in XmlToEmber. -Add SubBatchPercentPerThread() method to RendererCL. -Override enterEvent() and leaveEvent() in DoubleSpinBox and SpinBox to prevent the context menu from showing up on right mouse up after already leaving the spinner. -Filtering the mouse wheel event in TableWidget no longer appears to be needed. It was probably an old Qt bug that has been fixed. -Gui/ember syncing code in the final render dialog needed to be reworked to accommodate absolute sizes.
791 lines
23 KiB
C++
791 lines
23 KiB
C++
#include "EmberPch.h"
|
|
#include "PaletteList.h"
|
|
|
|
namespace EmberNs
|
|
{
|
|
/// <summary>
|
|
/// Empty constructor which initializes the palette map with the default palette file.
|
|
/// </summary>
|
|
template <typename T>
|
|
PaletteList<T>::PaletteList()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Destructor which saves any modifiable palettes to file, just in case they were modified.
|
|
/// </summary>
|
|
template <typename T>
|
|
PaletteList<T>::~PaletteList()
|
|
{
|
|
for (auto& palFile : s_Palettes)
|
|
{
|
|
if (IsModifiable(palFile.first))
|
|
Save(palFile.first);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a new palette file with the given name and vector of palettes, and save it.
|
|
/// </summary>
|
|
/// <param name="filename">The full path to the file to add</param>
|
|
/// <param name="palettes">The list of palettes which comprise the file</param>
|
|
/// <returns>True if the file did not exist, was successfully added and saved, else false.</returns>
|
|
template <typename T>
|
|
bool PaletteList<T>::AddPaletteFile(const string& filename, const vector<Palette<T>>& palettes)
|
|
{
|
|
if (!GetPaletteListByFullPath(filename))
|
|
{
|
|
auto item = s_Palettes.insert(make_pair(filename, palettes));
|
|
Save(filename);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create an new empty palette file with the given name with a single modifiable palette in it.
|
|
/// </summary>
|
|
/// <param name="filename">The full path to the file to add</param>
|
|
/// <param name="palettes">The list of palettes which comprise the file</param>
|
|
/// <returns>True if the file did not exist, was successfully added and saved, else false.</returns>
|
|
template <typename T>
|
|
bool PaletteList<T>::AddEmptyPaletteFile(const string& filename)
|
|
{
|
|
if (!GetPaletteListByFullPath(filename))
|
|
{
|
|
auto item = s_Palettes.insert(make_pair(filename, vector<Palette<T>>()));
|
|
Palette<T> p;
|
|
p.m_Index = 0;
|
|
p.m_Name = "empty-default";
|
|
p.m_Filename = make_shared<string>(filename);
|
|
p.m_SourceColors = map<T, v4T>
|
|
{
|
|
{ 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a new palette to an existing palette file and save the file.
|
|
/// </summary>
|
|
/// <param name="filename">The full path to the existing palette file to add</param>
|
|
/// <param name="palette">The new palette to add to the file</param>
|
|
/// <returns>True if the palette file existed, the palette was added, and the file was successfully saved, else false.</returns>
|
|
template <typename T>
|
|
bool PaletteList<T>::AddPaletteToFile(const string& filename, const Palette<T>& palette)
|
|
{
|
|
if (auto p = GetPaletteListByFullPathOrFilename(filename))
|
|
{
|
|
p->push_back(palette);
|
|
p->back().m_Filename = make_shared<string>(filename);//Ensure the filename matches because this could have been duplicated from another palette file.
|
|
p->back().m_Index = int(p->size()) - 1;
|
|
Save(filename);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="filename">The full path to the existing palette file to replace a palette in</param>
|
|
/// <param name="palette">The new palette to use to replace an existing one in the file</param>
|
|
/// <returns>True if the palette file existed, the palette was replaced, and the file was successfully saved, else false.</returns>
|
|
template <typename T>
|
|
bool PaletteList<T>::Replace(const string& filename, const Palette<T>& 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="filename">The full path to the existing palette file to replace a palette in</param>
|
|
/// <param name="palette">The new palette to use to replace an existing one in the file</param>
|
|
/// <param name="index">The 0-based index of the palette to replace</param>
|
|
/// <returns>True if the palette file existed, the palette was replaced, and the file was successfully saved, else false.</returns>
|
|
template <typename T>
|
|
bool PaletteList<T>::Replace(const string& filename, const Palette<T>& 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Delete an existing palette from a palette file.
|
|
/// The match is done based on the passed in index.
|
|
/// </summary>
|
|
/// <param name="filename">The full path to the existing palette file to delete a palette from</param>
|
|
/// <param name="index">The 0-based index of the palette to delete</param>
|
|
/// <returns>True if the palette file existed, the palette was deleted, and the file was successfully saved, else false.</returns>
|
|
template <typename T>
|
|
bool PaletteList<T>::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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read an Xml palette file into memory.
|
|
/// This must be called before any palette file usage.
|
|
/// </summary>
|
|
/// <param name="filename">The full path to the file to read</param>
|
|
/// <param name="force">If true, override the initialization state and force a read, else observe the initialization state.</param>
|
|
/// <returns>Whether anything was read</returns>
|
|
template <typename T>
|
|
bool PaletteList<T>::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<Palette<T>>()));
|
|
|
|
if (force || palettes.second)
|
|
{
|
|
string buf;
|
|
const char* loc = __FUNCTION__;
|
|
|
|
if (ReadFile(filename.c_str(), buf))
|
|
{
|
|
auto lower = ToLower(filename);
|
|
auto pfilename = shared_ptr<string>(new string(filename));
|
|
|
|
if (EndsWith(lower, ".xml"))
|
|
{
|
|
xmlDocPtr doc = xmlReadMemory(static_cast<const char*>(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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the palette at a random index in a random file in the map.
|
|
/// Attempt to avoid selecting a palette which is all black.
|
|
/// </summary>
|
|
/// <returns>A pointer to a random palette in a random file if successful, else nullptr.</returns>
|
|
template <typename T>
|
|
Palette<T>* PaletteList<T>::GetRandomPalette()
|
|
{
|
|
size_t attempts = 0;
|
|
|
|
while (attempts < Size() * 10)
|
|
{
|
|
auto p = s_Palettes.begin();
|
|
auto paletteFileIndex = QTIsaac<ISAAC_SIZE, ISAAC_INT>::LockedRand() % Size();
|
|
size_t i = 0;
|
|
|
|
//Move p forward i elements.
|
|
while (i < paletteFileIndex && p != s_Palettes.end())
|
|
{
|
|
++i;
|
|
++p;
|
|
}
|
|
|
|
if (i < Size())
|
|
{
|
|
size_t paletteIndex = QTIsaac<ISAAC_SIZE, ISAAC_INT>::LockedRand() % p->second.size();
|
|
|
|
if (paletteIndex < p->second.size() && !p->second[paletteIndex].IsEmpty())
|
|
return &p->second[paletteIndex];
|
|
}
|
|
|
|
attempts++;
|
|
}
|
|
|
|
return Size() ? &s_Palettes[0][0] : nullptr;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the palette at a specified index in the specified file in the map.
|
|
/// </summary>
|
|
/// <param name="filename">The filename of the palette to retrieve</param>
|
|
/// <param name="i">The index of the palette to read. A value of -1 indicates a random palette.</param>
|
|
/// <returns>A pointer to the requested palette if the index was in range, else nullptr.</returns>
|
|
template <typename T>
|
|
Palette<T>* PaletteList<T>::GetPaletteByFilename(const string& filename, size_t i)
|
|
{
|
|
if (auto palettes = GetPaletteListByFilename(filename))
|
|
if (i < palettes->size())
|
|
return &(*palettes)[i];
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the palette at a specified index in the specified file in the map.
|
|
/// </summary>
|
|
/// <param name="filename">The full path and filename of the palette to retrieve</param>
|
|
/// <param name="i">The index of the palette to read. A value of -1 indicates a random palette.</param>
|
|
/// <returns>A pointer to the requested palette if the index was in range, else nullptr.</returns>
|
|
template <typename T>
|
|
Palette<T>* PaletteList<T>::GetPaletteByFullPath(const string& filename, size_t i)
|
|
{
|
|
if (auto palettes = GetPaletteListByFullPath(filename))
|
|
if (i < palettes->size())
|
|
return &(*palettes)[i];
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a pointer to a palette with a specified name in the specified full path and filename in the map.
|
|
/// </summary>
|
|
/// <param name="filename">The filename of the palette to retrieve</param>
|
|
/// <param name="name">The name of the palette to retrieve</param>
|
|
/// <returns>A pointer to the requested palette if found, else nullptr.</returns>
|
|
template <typename T>
|
|
Palette<T>* PaletteList<T>::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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the palette file with the specified filename in the map.
|
|
/// </summary>
|
|
/// <param name="filename">The filename of the palette to retrieve</param>
|
|
/// <returns>A pointer to the requested palette if found, else nullptr.</returns>
|
|
template <typename T>
|
|
vector<Palette<T>>* PaletteList<T>::GetPaletteListByFilename(const string& filename)
|
|
{
|
|
auto filenameonly = GetFilename(filename);
|
|
|
|
for (auto& palettes : s_Palettes)
|
|
if (GetFilename(palettes.first) == filenameonly)
|
|
return &palettes.second;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the palette file with the specified full path and filename in the map.
|
|
/// </summary>
|
|
/// <param name="filename">The full path and filename of the palette to retrieve</param>
|
|
/// <returns>A pointer to the requested palette if found, else nullptr.</returns>
|
|
template <typename T>
|
|
vector<Palette<T>>* PaletteList<T>::GetPaletteListByFullPath(const string& filename)
|
|
{
|
|
auto palettes = s_Palettes.find(filename);
|
|
|
|
if (palettes != s_Palettes.end() && !palettes->second.empty())
|
|
return &palettes->second;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="filename">The full path and filename or just the filename of the palette to retrieve</param>
|
|
/// <returns>A pointer to the requested palette if found, else nullptr.</returns>
|
|
template <typename T>
|
|
vector<Palette<T>>* PaletteList<T>::GetPaletteListByFullPathOrFilename(const string& filename)
|
|
{
|
|
auto p = GetPaletteListByFullPath(filename);
|
|
|
|
if (!p)
|
|
p = GetPaletteListByFilename(filename);
|
|
|
|
return p;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get full path and filename of the pallete with the specified filename
|
|
/// </summary>
|
|
/// <param name="filename">The filename only of the palette to retrieve</param>
|
|
/// <returns>A pointer to the requested palette if found, else nullptr.</returns>
|
|
template <typename T>
|
|
string PaletteList<T>::GetFullPathFromFilename(const string& filename)
|
|
{
|
|
auto filenameonly = GetFilename(filename);
|
|
|
|
for (auto& palettes : s_Palettes)
|
|
if (GetFilename(palettes.first) == filenameonly)
|
|
return palettes.first;
|
|
|
|
return "";
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="filename">The filename of the palette to retrieve</param>
|
|
/// <param name="i">The index of the palette to read.</param>
|
|
/// <param name="hue">The hue adjustment to apply</param>
|
|
/// <param name="palette">The palette to store the output</param>
|
|
/// <returns>True if successful, else false.</returns>
|
|
template <typename T>
|
|
bool PaletteList<T>::GetHueAdjustedPalette(const string& filename, size_t i, T hue, Palette<T>& palette)
|
|
{
|
|
if (auto unadjustedPal = GetPaletteByFullPath(filename, i))
|
|
{
|
|
unadjustedPal->MakeHueAdjustedPalette(palette, hue);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear the palette list and reset the initialization state.
|
|
/// </summary>
|
|
template <typename T>
|
|
void PaletteList<T>::Clear()
|
|
{
|
|
s_Palettes.clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the size of the palettes map.
|
|
/// This will be the number of files read.
|
|
/// </summary>
|
|
/// <returns>The size of the palettes map</returns>
|
|
template <typename T>
|
|
size_t PaletteList<T>::Size() { return s_Palettes.size(); }
|
|
|
|
/// <summary>
|
|
/// Get the size of specified palette vector in the palettes map.
|
|
/// </summary>
|
|
/// <param name="index">The index of the palette in the map to retrieve</param>
|
|
/// <returns>The size of the palette vector at the specified index in the palettes map</returns>
|
|
template <typename T>
|
|
size_t PaletteList<T>::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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the size of specified palette vector in the palettes map.
|
|
/// </summary>
|
|
/// <param name="s">The filename of the palette in the map to retrieve</param>
|
|
/// <returns>The size of the palette vector at the specified index in the palettes map</returns>
|
|
template <typename T>
|
|
size_t PaletteList<T>::Size(const string& s)
|
|
{
|
|
return s_Palettes[s].size();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the name of specified palette in the palettes map.
|
|
/// </summary>
|
|
/// <param name="index">The index of the palette in the map to retrieve</param>
|
|
/// <returns>The name of the palette vector at the specified index in the palettes map</returns>
|
|
template <typename T>
|
|
const string& PaletteList<T>::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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether all palettes in the passed in palette file are modifiable,
|
|
/// meaning whether the source colors have at least one element in them.
|
|
/// </summary>
|
|
/// <param name="filename">The full path to the existing palette file to search for a modifiable palette in</param>
|
|
/// <returns>True if at all palettes in the file were modifiable, else false.</returns>
|
|
template <typename T>
|
|
bool PaletteList<T>::IsModifiable(const string& filename)
|
|
{
|
|
if (auto palFile = GetPaletteListByFullPathOrFilename(filename))
|
|
for (auto& pal : *palFile)
|
|
if (pal.m_SourceColors.empty())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a const ref to the underlying static palette structure.
|
|
/// </summary>
|
|
/// <returns>s_Palettes</returns>
|
|
template <typename T>
|
|
const map<string, vector<Palette<T>>>& PaletteList<T>::Palettes() const
|
|
{
|
|
return s_Palettes;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saves an existing file to disk.
|
|
/// </summary>
|
|
/// <param name="filename">The full path to the existing palette file to save</param>
|
|
/// <returns>True if successful, else false.</returns>
|
|
template <typename T>
|
|
bool PaletteList<T>::Save(const string& filename)
|
|
{
|
|
auto fullpath = GetFullPathFromFilename(filename);
|
|
|
|
try
|
|
{
|
|
size_t index = 0;
|
|
ostringstream os;
|
|
|
|
if (auto palFile = GetPaletteListByFullPathOrFilename(filename))
|
|
{
|
|
ofstream f(fullpath);
|
|
os << "<palettes>\n";
|
|
|
|
if (f.is_open())
|
|
{
|
|
for (auto& pal : *palFile)
|
|
{
|
|
os << "<palette number=\"" << index++ << "\" name=\"" << pal.m_Name << "\"";
|
|
|
|
if (!pal.m_SourceColors.empty())
|
|
{
|
|
os << " source_colors=\"";
|
|
|
|
for (auto& sc : pal.m_SourceColors)//Need to clamp these each from 0 to 1. Use our custom clamp funcs.//TODO
|
|
os << Clamp<T>(sc.first, 0, 1) << "," << Clamp<T>(sc.second.r, 0, 1) << "," << Clamp<T>(sc.second.g, 0, 1) << "," << Clamp<T>(sc.second.b, 0, 1) << " ";
|
|
|
|
os << "\"";
|
|
}
|
|
|
|
os << " data=\"";
|
|
|
|
for (int i = 0; i < 32; i++)
|
|
{
|
|
for (int j = 0; j < 8; j++)
|
|
{
|
|
size_t idx = 8 * i + j;
|
|
os << "00";
|
|
os << hex << setw(2) << setfill('0') << int(std::rint(pal[idx][0] * 255));
|
|
os << hex << setw(2) << setfill('0') << int(std::rint(pal[idx][1] * 255));
|
|
os << hex << setw(2) << setfill('0') << int(std::rint(pal[idx][2] * 255));
|
|
}
|
|
|
|
os << "\n";
|
|
}
|
|
|
|
os << "\"/>\n";
|
|
}
|
|
}
|
|
|
|
os << "</palettes>";
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="node">The parent note of all palettes in the Xml file.</param>
|
|
/// <param name="filename">The name of the Xml file.</param>
|
|
/// <param name="palettes">The vector to store the parsed palettes associated with this file in.</param>
|
|
template <typename T>
|
|
void PaletteList<T>::ParsePalettes(xmlNode* node, const shared_ptr<string>& filename, vector<Palette<T>>& palettes)
|
|
{
|
|
char* val;
|
|
xmlAttrPtr attr;
|
|
int index = 0;
|
|
|
|
while (node)
|
|
{
|
|
if (node->type == XML_ELEMENT_NODE && !Compare(node->name, "palette"))
|
|
{
|
|
attr = node->properties;
|
|
Palette<T> palette;
|
|
|
|
while (attr)
|
|
{
|
|
val = reinterpret_cast<char*>(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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="buf">The data to parse.</param>
|
|
/// <param name="filename">The name of the gradient file.</param>
|
|
/// <param name="palettes">The vector to store the parsed palettes associated with this file in.</param>
|
|
/// <returns>True if at least one palette is read, else false.</returns>
|
|
template <typename T>
|
|
bool PaletteList<T>::ParsePalettes(const string& buf, const shared_ptr<string>& filename, vector<Palette<T>>& palettes)
|
|
{
|
|
int paletteIndex = 0;
|
|
size_t index = 0;
|
|
string line;
|
|
string name;
|
|
bool reading = false;
|
|
bool found = false;
|
|
istringstream iss(buf);
|
|
vector<string> 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<T> palette;
|
|
Color<T> 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<float>;
|
|
|
|
#ifdef DO_DOUBLE
|
|
template EMBER_API class PaletteList<double>;
|
|
#endif
|
|
}
|