--User changes

-Add a palette editor.
 -Add support for reading .ugr/.gradient/.gradients palette files.
 -Allow toggling on spinners whose minimum value is not zero.
 -Allow toggling display of image, affines and grid.
 -Add new variations: cylinder2, circlesplit, tile_log, truchet_fill, waves2_radial.

--Bug fixes
 -cpow2 was wrong.
 -Palettes with rapid changes in color would produce slightly different outputs from Apo/Chaotica. This was due to a long standing bug from flam3.
 -Use exec() on Apple and show() on all other OSes for dialog boxes.
 -Trying to render a sequence with no frames would crash.
 -Selecting multiple xforms and rotating them would produce the wrong rotation.
 -Better handling when parsing flames using different encoding, such as unicode and UTF-8.
 -Switching between SP/DP didn't reselect the selected flame in the Library tab.

--Code changes
 -Make all types concerning palettes be floats, including PaletteTableWidgetItem.
 -PaletteTableWidgetItem is no longer templated because all palettes are float.
 -Include the source colors for user created gradients.
 -Change parallel_for() calls to work with very old versions of TBB which are lingering on some systems.
 -Split conditional out of accumulation loop on the CPU for better performance.
 -Vectorize summing when doing density filter for better performance.
 -Make all usage of palettes be of type float, double is pointless.
 -Allow palettes to reside in multiple folders, while ensuring only one of each name is added.
 -Refactor some palette path searching code.
 -Make ReadFile() throw and catch an exception if the file operation fails.
 -A little extra safety in foci and foci3D with a call to Zeps().
 -Cast to (real_t) in the OpenCL string for the w variation, which was having trouble compiling on Mac.
 -Fixing missing comma between paths in InitPaletteList().
 -Move Xml and PaletteList classes into cpp to shorten build times when working on them.
 -Remove default param values for IterOpenCLKernelCreator<T>::SharedDataIndexDefines in cpp file.
 -Change more NULL to nullptr.
This commit is contained in:
Person
2017-02-26 00:02:21 -08:00
parent 8a75d5d227
commit 8a4127d5d7
102 changed files with 242668 additions and 3713 deletions

View File

@ -203,6 +203,19 @@ void Affine2D<T>::Scale(T amount)
F(F() * amount);
}
/// <summary>
/// Scales all values A,B,D,E by the specified amount.
/// </summary>
/// <param name="amount">The amount to scale by</param>
template <typename T>
void Affine2D<T>::ScaleXY(T amount)
{
A(A() * amount);
B(B() * amount);
D(D() * amount);
E(E() * amount);
}
/// <summary>
/// Rotate this affine transform around its origin by the specified angle in degrees.
/// </summary>

View File

@ -76,6 +76,7 @@ public:
bool IsZero() const;
bool IsEmpty() const;
void Scale(T amount);
void ScaleXY(T amount);
Affine2D<T> ScaleCopy(T amount);
void Rotate(T rad);
void RotateTrans(T rad);

View File

@ -396,6 +396,11 @@ uint Timing::m_ProcessorCount;
EXPORTPREPOSTREGVAR(Gamma, T) \
EXPORTPREPOSTREGVAR(PRose3D, T) \
EXPORTPREPOSTREGVAR(LogDB, T) \
EXPORTPREPOSTREGVAR(CircleSplit, T) \
EXPORTPREPOSTREGVAR(Cylinder2, T) \
EXPORTPREPOSTREGVAR(TileLog, T) \
EXPORTPREPOSTREGVAR(TruchetFill, T) \
EXPORTPREPOSTREGVAR(Waves2Radial, T) \
template EMBER_API class PostSmartcropVariation<T>; /*Only implemented as post.*/ \
EXPORTPREPOSTREGVAR(DCBubble, T) \
EXPORTPREPOSTREGVAR(DCCarpet, T) \
@ -436,11 +441,10 @@ uint Timing::m_ProcessorCount;
template EMBER_API class XmlToEmber<T>; \
template EMBER_API class EmberToXml<T>;
EXPORT_SINGLE_TYPE_EMBER(float)
#define EXPORT_TWO_TYPE_EMBER(T, bucketT) \
template EMBER_API class SheepTools<T, bucketT>;
EXPORT_SINGLE_TYPE_EMBER(float)
EXPORT_TWO_TYPE_EMBER(float, float)
#ifdef DO_DOUBLE

View File

@ -659,23 +659,23 @@ public:
{
for (glm::length_t i = 0; i < 256; i++)
{
T t[3], s[4] = { 0, 0, 0, 0 };
float t[3], s[4] = { 0, 0, 0, 0 };
for (glm::length_t k = 0; k < size; k++)
{
Palette<T>::RgbToHsv(glm::value_ptr(embers[k].m_Palette[i]), t);
Palette<float>::RgbToHsv(glm::value_ptr(embers[k].m_Palette[i]), t);
for (size_t j = 0; j < 3; j++)
s[j] += coefs[k] * t[j];
s[j] += float(coefs[k]) * t[j];
s[3] += coefs[k] * embers[k].m_Palette[i][3];
s[3] += float(coefs[k]) * embers[k].m_Palette[i][3];
}
Palette<T>::HsvToRgb(s, glm::value_ptr(m_Palette[i]));
Palette<float>::HsvToRgb(s, glm::value_ptr(m_Palette[i]));
m_Palette[i][3] = s[3];
for (glm::length_t j = 0; j < 4; j++)
Clamp<T>(m_Palette[i][j], 0, 1);
Clamp<float>(m_Palette[i][j], 0, 1);
}
}
else if (embers[0].m_PaletteInterp == ePaletteInterp::INTERP_SWEEP)
@ -1100,8 +1100,8 @@ public:
/// <summary>
/// Placeholder to do nothing.
/// </summary>
/// <param name="point">Unused</param>
/// <param name="rand">Unused</param>
/// <param name="point">Ignored</param>
/// <param name="rand">Ignored</param>
void ProjectNone(Point<T>& point, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand)
{
}
@ -1110,7 +1110,7 @@ public:
/// Project when only z is set, and not pitch, yaw, projection or depth blur.
/// </summary>
/// <param name="point">The point to project</param>
/// <param name="rand">Unused</param>
/// <param name="rand">Ignored</param>
void ProjectZPerspective(Point<T>& point, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand)
{
T zr = Zeps(1 - m_CamPerspective * (point.m_Z - m_CamZPos));
@ -1123,7 +1123,7 @@ public:
/// Project when pitch, and optionally z and perspective are set, but not depth blur or yaw.
/// </summary>
/// <param name="point">The point to project</param>
/// <param name="rand">Unused</param>
/// <param name="rand">Ignored</param>
void ProjectPitch(Point<T>& point, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand)
{
T z = point.m_Z - m_CamZPos;
@ -1180,7 +1180,7 @@ public:
/// Project when yaw and optionally pitch, z, and perspective are set, but not depth blur.
/// </summary>
/// <param name="point">The point to project</param>
/// <param name="rand">Unused</param>
/// <param name="rand">Ignored</param>
void ProjectPitchYaw(Point<T>& point, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand)
{
T z = point.m_Z - m_CamZPos;
@ -1653,7 +1653,7 @@ public:
//The color palette to use. Can be specified inline as Xml color fields, or as a hex buffer. Can also be specified
//as an index into the palette file with an optional hue rotation applied. Inserting as a hex buffer is the preferred method.
//Xml field: "color" or "colors" or "palette" .
Palette<T> m_Palette;//Final palette that is actually used is a copy of this inside of render, which will be of type bucketT (float).
Palette<float> m_Palette;//Final palette that is actually used is a copy of this inside of render, which will be of type bucketT (float).
//Curves used to adjust the color during final accumulation.
Curves<T> m_Curves;

View File

@ -103,6 +103,8 @@ static inline size_t NowMs()
#define v2T glm::tvec2<T, glm::defaultp>
#define v3T glm::tvec3<T, glm::defaultp>
#define v4T glm::tvec4<T, glm::defaultp>
#define v4F glm::tvec4<float, glm::defaultp>
#define v4D glm::tvec4<double, glm::defaultp>
#define v4bT glm::tvec4<bucketT, glm::defaultp>
#define m2T glm::tmat2x2<T, glm::defaultp>
#define m3T glm::tmat3x3<T, glm::defaultp>
@ -112,6 +114,8 @@ static inline size_t NowMs()
#define v2T glm::detail::tvec2<T, glm::defaultp>
#define v3T glm::detail::tvec3<T, glm::defaultp>
#define v4T glm::detail::tvec4<T, glm::defaultp>
#define v4F glm::detail::tvec4<float, glm::defaultp>
#define v4D glm::detail::tvec4<double, glm::defaultp>
#define v4bT glm::detail::tvec4<bucketT, glm::defaultp>
#define m2T glm::detail::tmat2x2<T, glm::defaultp>
#define m3T glm::detail::tmat3x3<T, glm::defaultp>

880
Source/Ember/EmberToXml.cpp Normal file
View File

@ -0,0 +1,880 @@
#include "EmberPch.h"
#include "EmberToXml.h"
namespace EmberNs
{
/// <summary>
/// Save the ember to the specified file.
/// </summary>
/// <param name="filename">Full path and filename</param>
/// <param name="ember">The ember to save</param>
/// <param name="printEditDepth">How deep the edit depth goes</param>
/// <param name="doEdits">If true included edit tags, else don't.</param>
/// <param name="hexPalette">If true, embed a hexadecimal palette instead of Xml Color tags, else use Xml color tags.</param>
/// <param name="append">If true, append to the file if it already exists, else create a new file.</param>
/// <param name="start">Whether a new file is to be started</param>
/// <param name="finish">Whether an existing file is to be ended</param>
/// <returns>True if successful, else false</returns>
template <typename T>
bool EmberToXml<T>::Save(const string& filename, Ember<T>& ember, size_t printEditDepth, bool doEdits, bool hexPalette, bool append, bool start, bool finish)
{
vector<Ember<T>> vec;
vec.push_back(ember);
return Save(filename, vec, printEditDepth, doEdits, hexPalette, append, start, finish);
}
/// <summary>
/// Save a container of embers to the specified file.
/// </summary>
/// <param name="filename">Full path and filename</param>
/// <param name="embers">The container of embers to save</param>
/// <param name="printEditDepth">How deep the edit depth goes</param>
/// <param name="doEdits">If true included edit tags, else don't.</param>
/// <param name="hexPalette">If true, embed a hexadecimal palette instead of Xml Color tags, else use Xml color tags.</param>
/// <param name="append">If true, append to the file if it already exists, else create a new file.</param>
/// <param name="start">Whether a new file is to be started</param>
/// <param name="finish">Whether an existing file is to be ended</param>
/// <returns>True if successful, else false</returns>
template <typename T>
template <typename Alloc, template <typename, typename> class C>
bool EmberToXml<T>::Save(const string& filename, C<Ember<T>, Alloc>& embers, size_t printEditDepth, bool doEdits, bool hexPalette, bool append, bool start, bool finish)
{
bool b = false;
T t = 0;
string temp;
ofstream f;
try
{
if (append)
f.open(filename, std::ofstream::out | std::ofstream::app);//Appending allows us to write multiple embers to a single file.
else
f.open(filename);
if (f.is_open())
{
auto prev = embers.begin();
//Always ensure times make sense.
for (auto& ember : embers)
ember.m_Time = t++;
if ((append && start) || !append)
{
temp = "<flames>\n";
//temp = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n<flames>\n";
f.write(temp.c_str(), temp.size());
}
for (auto& ember : embers)
{
string s = ToString(ember, "", printEditDepth, doEdits, hexPalette);
f.write(s.c_str(), s.size());
}
if ((append && finish) || !append)
{
temp = "</flames>\n";
f.write(temp.c_str(), temp.size());
}
f.close();
b = true;
}
else
{
cout << "Error: Writing flame " << filename << " failed.\n";
b = false;
}
}
catch (const std::exception& e)
{
cout << "Error: Writing flame " << filename << " failed: " << e.what() << "\n";
b = false;
}
catch (...)
{
cout << "Error: Writing flame " << filename << " failed.\n";
b = false;
}
return b;
}
/// <summary>
/// Return the Xml string representation of an ember.
/// </summary>
/// <param name="ember">The ember to create the Xml with</param>
/// <param name="extraAttributes">If true, add extra attributes, else don't</param>
/// <param name="printEditDepth">How deep the edit depth goes</param>
/// <param name="doEdits">If true included edit tags, else don't.</param>
/// <param name="hexPalette">If true, embed a hexadecimal palette instead of Xml Color tags, else use Xml color tags.</param>
/// <returns>The Xml string representation of the passed in ember</returns>
template <typename T>
string EmberToXml<T>::ToString(Ember<T>& ember, const string& extraAttributes, size_t printEditDepth, bool doEdits, bool hexPalette)
{
size_t i, j;
string s;
ostringstream os;
vector<Variation<T>*> variations;
os << "<flame version=\"EMBER-" << EmberVersion() << "\" time=\"" << ember.m_Time << "\"";
if (!ember.m_Name.empty())
os << " name=\"" << ember.m_Name << "\"";
os << " size=\"" << ember.m_FinalRasW << " " << ember.m_FinalRasH << "\"";
os << " center=\"" << ember.m_CenterX << " " << ember.m_CenterY << "\"";
os << " scale=\"" << ember.m_PixelsPerUnit << "\"";
if (ember.m_Zoom != 0)
os << " zoom=\"" << ember.m_Zoom << "\"";
os << " rotate=\"" << ember.m_Rotate << "\"";
os << " supersample=\"" << std::max<size_t>(1, ember.m_Supersample) << "\"";
os << " filter=\"" << ember.m_SpatialFilterRadius << "\"";
os << " filter_shape=\"" << ToLower(SpatialFilterCreator<T>::ToString(ember.m_SpatialFilterType)) << "\"";
os << " temporal_filter_type=\"" << ToLower(TemporalFilterCreator<T>::ToString(ember.m_TemporalFilterType)) << "\"";
if (ember.m_TemporalFilterType == eTemporalFilterType::EXP_TEMPORAL_FILTER)
os << " temporal_filter_exp=\"" << ember.m_TemporalFilterExp << "\"";
os << " temporal_filter_width=\"" << ember.m_TemporalFilterWidth << "\"";
os << " quality=\"" << ember.m_Quality << "\"";
os << " temporal_samples=\"" << ember.m_TemporalSamples << "\"";
os << " sub_batch_size=\"" << ember.m_SubBatchSize << "\"";
os << " fuse=\"" << ember.m_FuseCount << "\"";
os << " background=\"" << ember.m_Background.r << " " << ember.m_Background.g << " " << ember.m_Background.b << "\"";
os << " brightness=\"" << ember.m_Brightness << "\"";
os << " gamma=\"" << ember.m_Gamma << "\"";
os << " highlight_power=\"" << ember.m_HighlightPower << "\"";
os << " vibrancy=\"" << ember.m_Vibrancy << "\"";
os << " estimator_radius=\"" << ember.m_MaxRadDE << "\"";
os << " estimator_minimum=\"" << ember.m_MinRadDE << "\"";
os << " estimator_curve=\"" << ember.m_CurveDE << "\"";
os << " gamma_threshold=\"" << ember.m_GammaThresh << "\"";
os << " cam_zpos=\"" << ember.m_CamZPos << "\"";
os << " cam_persp=\"" << ember.m_CamPerspective << "\"";
os << " cam_yaw=\"" << ember.m_CamYaw << "\"";
os << " cam_pitch=\"" << ember.m_CamPitch << "\"";
os << " cam_dof=\"" << ember.m_CamDepthBlur << "\"";
if (ember.m_PaletteMode == ePaletteMode::PALETTE_STEP)
os << " palette_mode=\"step\"";
else if (ember.m_PaletteMode == ePaletteMode::PALETTE_LINEAR)
os << " palette_mode=\"linear\"";
if (ember.m_Interp == eInterp::EMBER_INTERP_LINEAR)
os << " interpolation=\"linear\"";
else if (ember.m_Interp == eInterp::EMBER_INTERP_SMOOTH)
os << " interpolation=\"smooth\"";
if (ember.m_AffineInterp == eAffineInterp::AFFINE_INTERP_LINEAR)
os << " interpolation_type=\"linear\"";
else if (ember.m_AffineInterp == eAffineInterp::AFFINE_INTERP_LOG)
os << " interpolation_type=\"log\"";
else if (ember.m_AffineInterp == eAffineInterp::AFFINE_INTERP_COMPAT)
os << " interpolation_type=\"old\"";
else if (ember.m_AffineInterp == eAffineInterp::AFFINE_INTERP_OLDER)
os << " interpolation_type=\"older\"";
if (ember.m_PaletteInterp == ePaletteInterp::INTERP_SWEEP)
os << " palette_interpolation=\"sweep\"";
if (!extraAttributes.empty())
os << " " << extraAttributes;
os << " plugins=\"";
ember.GetPresentVariations(variations, false);
if (!variations.empty())
for (auto var : variations) os << var->Name() << (var != variations.back() ? " " : "\"");
else
os << "\"";
os << " new_linear=\"1\"";
os << " curves=\"";
for (glm::length_t ci = 0; ci < 4; ci++)
{
for (glm::length_t cj = 0; cj < 4; cj++)
{
os << ember.m_Curves.m_Points[ci][cj].x << " ";
os << ember.m_Curves.m_Points[ci][cj].y << " ";
os << ember.m_Curves.m_Weights[ci][cj] << " ";
}
}
os << "\">\n";
for (i = 0; i < ember.m_EmberMotionElements.size(); ++i)
os << " " << ToString(ember.m_EmberMotionElements[i]);
//This is a grey area, what to do about symmetry to avoid duplicating the symmetry xforms when reading back?//TODO//BUG.
//if (ember.m_Symmetry)
// os << " <symmetry kind=\"" << ember.m_Symmetry << "\"/>\n";
for (i = 0; i < ember.XformCount(); i++)
os << ToString(*ember.GetXform(i), ember.XformCount(), false, false);//Not final, don't do motion.
if (ember.UseFinalXform())
os << ToString(*ember.NonConstFinalXform(), ember.XformCount(), true, false);//Final, don't do motion.
//Note that only embedded palettes are saved. The old style of specifying a palette index to look up in a default palette file
//is no longer supported, as it makes no sense when using multiple palette files. The only way it could work is if the index was
//always meant to refer to the default file, or if the filename was embedded as well. It's easier, more straightforward and
//less error prone to just embed the palette.
if (hexPalette)
{
os << " <palette count=\"256\" format=\"RGB\"";
if (!ember.m_Palette.m_SourceColors.empty())
{
os << " source_colors=\"";
for (auto& sc : ember.m_Palette.m_SourceColors)
{
os <<
Clamp(sc.first, 0.0f, 1.0f) << "," <<
Clamp(sc.second.r, 0.0f, 1.0f) << "," <<
Clamp(sc.second.g, 0.0f, 1.0f) << "," <<
Clamp(sc.second.b, 0.0f, 1.0f) << " ";
}
os << "\"";
}
os << ">\n";
for (i = 0; i < 32; i++)
{
os << " ";
for (j = 0; j < 8; j++)
{
size_t idx = 8 * i + j;
os << hex << setw(2) << setfill('0') << int(std::rint(ember.m_Palette[idx][0] * 255));
os << hex << setw(2) << setfill('0') << int(std::rint(ember.m_Palette[idx][1] * 255));
os << hex << setw(2) << setfill('0') << int(std::rint(ember.m_Palette[idx][2] * 255));
}
os << "\n";
}
os << " </palette>\n";
}
else
{
for (i = 0; i < 256; i++)
{
double r = ember.m_Palette[i][0] * 255;
double g = ember.m_Palette[i][1] * 255;
double b = ember.m_Palette[i][2] * 255;
double a = ember.m_Palette[i][3] * 255;
os << " ";
//The original used a precision of 6 which is totally unnecessary, use 2.
if (IsClose(a, 255.0))
os << "<color index=\"" << i << "\" rgb=\"" << std::fixed << std::setprecision(2) << r << " " << g << " " << b << "\"/>";
else
os << " <color index=\"" << i << "\" rgba=\"" << std::fixed << std::setprecision(2) << r << " " << g << " " << b << " " << a << "\"/>";
os << "\n";
}
}
if (doEdits && ember.m_Edits)
os << ToString(xmlDocGetRootElement(ember.m_Edits), 1, true, printEditDepth);
os << "</flame>\n";
return os.str();
}
/// <summary>
/// Create a new editdoc optionally based on parents passed in.
/// This is used when an ember is made out of some mutation or edit from one or two existing embers and
/// the user wants to capture the genetic lineage history information in the edit doc of the new ember.
/// </summary>
/// <param name="parent0">The first parent, optionally nullptr.</param>
/// <param name="parent1">The second parent, optionally nullptr.</param>
/// <param name="action">The action that was taken to create the new ember</param>
/// <param name="nick">The nickname of the author</param>
/// <param name="url">The Url of the author</param>
/// <param name="id">The id of the author</param>
/// <param name="comment">The comment to include</param>
/// <param name="sheepGen">The sheep generation used if > 0. Default: 0.</param>
/// <param name="sheepId">The sheep id used if > 0. Default: 0.</param>
/// <returns></returns>
template <typename T>
xmlDocPtr EmberToXml<T>::CreateNewEditdoc(Ember<T>* parent0, Ember<T>* parent1, const string& action, const string& nick, const string& url, const string& id, const string& comment, intmax_t sheepGen, intmax_t sheepId)
{
char timeString[128];
time_t myTime;
string s;
xmlDocPtr commentDoc = nullptr;
xmlDocPtr doc = xmlNewDoc(XC("1.0"));
xmlNodePtr rootNode = nullptr, node = nullptr, nodeCopy = nullptr;
xmlNodePtr rootComment = nullptr;
ostringstream os;
//Create the root node, called "edit".
rootNode = xmlNewNode(nullptr, XC("edit"));
xmlDocSetRootElement(doc, rootNode);
//Add the edit attributes.
//Date.
myTime = time(nullptr);
#ifdef _WIN32
tm localt;
localtime_s(&localt, &myTime);
strftime(timeString, 128, "%a %b %d %H:%M:%S %z %Y", &localt);//XXX use standard time format including timezone.
#else
tm* localt;
localt = localtime(&myTime);
strftime(timeString, 128, "%a %b %d %H:%M:%S %z %Y", localt);//XXX use standard time format including timezone.
#endif
xmlNewProp(rootNode, XC("date"), XC(timeString));
//Nick.
if (nick != "")
xmlNewProp(rootNode, XC("nick"), XC(nick.c_str()));
//Url.
if (url != "")
xmlNewProp(rootNode, XC("url"), XC(url.c_str()));
if (id != "")
xmlNewProp(rootNode, XC("id"), XC(id.c_str()));
//Action.
xmlNewProp(rootNode, XC("action"), XC(action.c_str()));
//Sheep info.
if (sheepGen > 0 && sheepId > 0)
{
//Create a child node of the root node called sheep.
node = xmlNewChild(rootNode, nullptr, XC("sheep"), nullptr);
//Create the sheep attributes.
os << sheepGen;
s = os.str();
xmlNewProp(node, XC("generation"), XC(s.c_str()));
os.str("");
os << sheepId;
s = os.str();
xmlNewProp(node, XC("id"), XC(s.c_str()));
os.str("");
}
//Check for the parents.
//If parent 0 not specified, this is a randomly generated genome.
if (parent0)
{
os << parent0->m_Index;
s = os.str();
if (parent0->m_Edits)
{
//Copy the node from the parent.
node = xmlDocGetRootElement(parent0->m_Edits);
nodeCopy = xmlCopyNode(node, 1);
AddFilenameWithoutAmpersand(nodeCopy, parent0->m_ParentFilename);
xmlNewProp(nodeCopy, XC("index"), XC(s.c_str()));
xmlAddChild(rootNode, nodeCopy);
}
else
{
//Insert a (parent has no edit) message.
nodeCopy = xmlNewChild(rootNode, nullptr, XC("edit"), nullptr);
AddFilenameWithoutAmpersand(nodeCopy, parent0->m_ParentFilename);
xmlNewProp(nodeCopy, XC("index"), XC(s.c_str()));
}
os.str("");
}
if (parent1)
{
os << parent1->m_Index;
s = os.str();
if (parent1->m_Edits)
{
//Copy the node from the parent.
node = xmlDocGetRootElement(parent1->m_Edits);
nodeCopy = xmlCopyNode(node, 1);
AddFilenameWithoutAmpersand(nodeCopy, parent1->m_ParentFilename);
xmlNewProp(nodeCopy, XC("index"), XC(s.c_str()));
xmlAddChild(rootNode, nodeCopy);
}
else
{
//Insert a (parent has no edit) message.
nodeCopy = xmlNewChild(rootNode, nullptr, XC("edit"), nullptr);
AddFilenameWithoutAmpersand(nodeCopy, parent1->m_ParentFilename);
xmlNewProp(nodeCopy, XC("index"), XC(s.c_str()));
}
os.str("");
}
//Comment string:
//This one's hard, since the comment string must be treated as
//a valid XML document. Create a new document using the comment
//string as the in-memory document, and then copy all children of
//the root node into the edit structure
//Parsing the comment string should be done once and then copied
//for each new edit doc, but that's for later.
if (comment != "")
{
os << "<comm>" << comment << "</comm>";
s = os.str();
commentDoc = xmlReadMemory(s.c_str(), int(s.length()), "comment.env", nullptr, XML_PARSE_NONET);
os.str("");
//Check for errors.
if (commentDoc)
{
//Loop through the children of the new document and copy them into the rootNode.
rootComment = xmlDocGetRootElement(commentDoc);
for (node = rootComment->children; node; node = node->next)
{
nodeCopy = xmlCopyNode(node, 1);
xmlAddChild(rootNode, nodeCopy);
}
//Free the created document.
xmlFreeDoc(commentDoc);
}
else
{
cout << "Failed to parse comment into Xml.\n";
}
}
//Return the Xml doc.
return doc;
}
/// <summary>
/// Return the Xml string representation of an xform.
/// </summary>
/// <param name="xform">The xform to create the Xml with</param>
/// <param name="xformCount">The number of non-final xforms in the ember to which this xform belongs. Used for xaos.</param>
/// <param name="isFinal">True if the xform is the final xform in the ember, else false.</param>
/// <param name="doMotion">If true, include motion elements in the Xml string, else omit.</param>
/// <returns>The Xml string representation of the passed in xform</returns>
template <typename T>
string EmberToXml<T>::ToString(Xform<T>& xform, size_t xformCount, bool isFinal, bool doMotion)
{
size_t i, j;
ostringstream os;
if (doMotion)
{
os << " <motion motion_frequency=\"" << xform.m_MotionFreq << "\" ";
if (xform.m_MotionFunc == eMotion::MOTION_SIN)
os << "motion_function=\"sin\" ";
else if (xform.m_MotionFunc == eMotion::MOTION_TRIANGLE)
os << "motion_function=\"triangle\" ";
else if (xform.m_MotionFunc == eMotion::MOTION_HILL)
os << "motion_function=\"hill\" ";
else if (xform.m_MotionFunc == eMotion::MOTION_SAW)
os << "motion_function=\"saw\" ";
if (xform.m_MotionOffset != 0)
os << "motion_offset=\"" << xform.m_MotionOffset << "\" ";
}
else
{
if (isFinal)
os << " <finalxform ";
else
os << " <xform weight=\"" << xform.m_Weight << "\" ";
}
if (!doMotion || xform.m_ColorX != EMPTYFIELD) os << "color=\"" << xform.m_ColorX << "\" ";
//if (!doMotion || xform.m_ColorY != EMPTYFIELD) os << "color=\"" << xform.m_ColorX << " " << xform.m_ColorY << "\" ";
if (!doMotion || xform.m_DirectColor != EMPTYFIELD) os << "var_color=\"" << xform.m_DirectColor << "\" ";
if (!doMotion || xform.m_ColorSpeed != EMPTYFIELD) os << "color_speed=\"" << xform.m_ColorSpeed << "\" ";
//os << "symmetry=\"" << fabs(xform.m_ColorSpeed - 1) * 2 << "\" ";//Legacy support.
if (!doMotion)
{
string s = xform.m_Name;
std::replace(s.begin(), s.end(), ' ', '_');
os << "name=\"" << s << "\" ";//Flam3 didn't do this, but Apo does.
if (!isFinal)
os << "animate=\"" << xform.m_Animate << "\" ";
}
//Variation writing order differs slightly from the original to make it a bit more readable.
//The original wrote out all of the variation names and weights. Then wrote out the parameters for
//the parametric variations. Here, write out the params immediately after each parametric variation
//so they are more closely grouped with the variation they apply to, rather than being all grouped at the end.
for (i = 0; i < xform.TotalVariationCount(); i++)
{
Variation<T>* var = xform.GetVariation(i);
ParametricVariation<T>* parVar = dynamic_cast<ParametricVariation<T>*>(var);
if (var->m_Weight != 0)
{
os << var->Name() << "=\"" << var->m_Weight << "\" ";
if (parVar)
{
auto params = parVar->Params();
for (j = 0; j < parVar->ParamCount(); j++)
{
if ((!doMotion || (doMotion && (params[j].ParamVal() != 0))) && !params[j].IsPrecalc())
os << params[j].Name() << "=\"" << params[j].ParamVal() << "\" ";
}
}
}
}
if (!doMotion || (doMotion && !xform.m_Affine.IsZero() && !xform.m_Affine.IsEmpty()))
{
os << "coefs=\"" << xform.m_Affine.A() << " " << xform.m_Affine.D() << " " << xform.m_Affine.B() << " "
<< xform.m_Affine.E() << " " << xform.m_Affine.C() << " " << xform.m_Affine.F() << "\"";
}
if ((!doMotion && !xform.m_Post.IsID()) || (doMotion && !xform.m_Post.IsZero() && !xform.m_Post.IsEmpty()))
{
os << " post=\"" << xform.m_Post.A() << " " << xform.m_Post.D() << " " << xform.m_Post.B() << " "
<< xform.m_Post.E() << " " << xform.m_Post.C() << " " << xform.m_Post.F() << "\"";
}
//Original only printed xaos values that were not 1. Here, print them all out if any are present.
if (!isFinal && !doMotion && xform.XaosPresent())//Applying motion to xaos not supported.
{
os << " chaos=\"";
for (i = 0; i < xformCount; i++)
os << xform.Xaos(i) << " ";
os << "\"";
}
if (!doMotion || xform.m_Opacity != EMPTYFIELD) os << " opacity=\"" << xform.m_Opacity << "\"";
if (!doMotion && !xform.m_Motion.empty())
{
os << ">\n";
for (i = 0; i < xform.m_Motion.size(); i++)
os << ToString(xform.m_Motion[i], 0, false, true);
if (isFinal)//Fixed to properly close final.//SMOULDER
os << " </finalxform>\n";
else
os << " </xform>\n";
}
else
os << "/>\n";
return os.str();
}
/// <summary>
/// Return an edit node Xml string.
/// </summary>
/// <param name="editNode">The edit node to get the string for</param>
/// <param name="tabs">How many tabs to use</param>
/// <param name="formatting">If true, include newlines and tabs, else don't.</param>
/// <param name="printEditDepth">How deep the edit depth goes</param>
/// <returns>The edit node Xml string</returns>
template <typename T>
string EmberToXml<T>::ToString(xmlNodePtr editNode, size_t tabs, bool formatting, size_t printEditDepth)
{
bool indentPrinted = false;
const char* tabString = " ", *attStr;
const char* editString = "edit";
const char* sheepString = "sheep";
size_t ti;//, editOrSheep = 0;
xmlAttrPtr attPtr = nullptr, curAtt = nullptr;
xmlNodePtr childPtr = nullptr, curChild = nullptr;
ostringstream os;
if (printEditDepth > 0 && tabs > printEditDepth)
return "";
//If this node is an XML_ELEMENT_NODE, print it and its attributes.
if (editNode->type == XML_ELEMENT_NODE)
{
//Print the node at the tab specified.
if (formatting)
for (ti = 0; ti < tabs; ti++)
os << tabString;
os << "<" << editNode->name;
//This can either be an edit node or a sheep node.
//If it's an edit node, add one to the tab.
if (!Compare(editNode->name, editString))
{
//editOrSheep = 1;
tabs++;
}
else if (!Compare(editNode->name, sheepString)) {}
//editOrSheep = 2;
else {}
//editOrSheep = 0;
//Print the attributes.
attPtr = editNode->properties;
for (curAtt = attPtr; curAtt; curAtt = curAtt->next)
{
attStr = CX(xmlGetProp(editNode, curAtt->name));
os << " " << curAtt->name << "=\"" << attStr << "\"";
xmlFree(reinterpret_cast<void*>(const_cast<char*>(attStr)));
}
//Does this node have children?
if (!editNode->children || (printEditDepth > 0 && tabs > printEditDepth))
{
//Close the tag and subtract the tab.
os << "/>";
if (formatting)
os << "\n";
tabs--;
}
else
{
//Close the tag.
os << ">";
if (formatting)
os << "\n";
//Loop through the children and print them.
childPtr = editNode->children;
indentPrinted = false;
for (curChild = childPtr; curChild; curChild = curChild->next)
{
//If child is an element, indent first and then print it.
if (curChild->type == XML_ELEMENT_NODE &&
(!Compare(curChild->name, editString) || !Compare(curChild->name, sheepString)))
{
if (indentPrinted)
{
indentPrinted = false;
os << "\n";
}
os << ToString(curChild, tabs, true, printEditDepth);
}
else
{
//Child is a text node, don't want to indent more than once.
if (xmlIsBlankNode(curChild))
continue;
if (!indentPrinted && formatting)
{
for (ti = 0; ti < tabs; ti++)
os << tabString;
indentPrinted = true;
}
//Print nodes without formatting.
os << ToString(curChild, tabs, false, printEditDepth);
}
}
if (indentPrinted && formatting)
os << "\n";
tabs--;//Tab out.
if (formatting)
for (ti = 0; ti < tabs; ti++)
os << tabString;
os << "</" << editNode->name << ">";//Close the tag.
if (formatting)
os << "\n";
}
}
else if (editNode->type == XML_TEXT_NODE)
{
string s(reinterpret_cast<char*>(xmlNodeGetContent(editNode)));
os << Trim(s);
}
return os.str();
}
/// <summary>
/// Convert a EmberMotion element to an xml string
/// </summary>
/// <param name="motion">The EmberMotion object to convert to XML</param>
/// <returns>The string representation of the passed in EmberMotion object</returns>
template <typename T>
string EmberToXml<T>::ToString(const EmberMotion<T>& motion)
{
ostringstream os;
os << "<flame_motion motion_frequency=\"" << motion.m_MotionFreq << "\" ";
if (motion.m_MotionOffset > 0)
os << "motion_offset=\"" << motion.m_MotionOffset << "\" ";
os << "motion_func=";
switch (motion.m_MotionFunc)
{
case eMotion::MOTION_SIN:
os << "\"sin\"";
break;
case eMotion::MOTION_HILL:
os << "\"hill\"";
break;
case eMotion::MOTION_TRIANGLE:
os << "\"triangle\"";
break;
case eMotion::MOTION_SAW:
default:
os << "\"saw\"";
break;
}
T r = 0.0;
T g = 0.0;
T b = 0.0;
T cx = 0.0;
T cy = 0.0;
for (size_t i = 0; i < motion.m_MotionParams.size(); ++i)
{
switch (motion.m_MotionParams[i].first)
{
case eEmberMotionParam::FLAME_MOTION_ZOOM:
os << " zoom=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_ZPOS:
os << " cam_zpos=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_PERSPECTIVE:
os << " cam_persp=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_YAW:
os << " cam_yaw=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_PITCH:
os << " cam_pitch=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_DEPTH_BLUR:
os << " cam_dof=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_CENTER_X:
cx = motion.m_MotionParams[i].second;
break;
case eEmberMotionParam::FLAME_MOTION_CENTER_Y:
cy = motion.m_MotionParams[i].second;
break;
case eEmberMotionParam::FLAME_MOTION_ROTATE:
os << " rotate=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_BRIGHTNESS:
os << " brightness=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_GAMMA:
os << " gamma=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_GAMMA_THRESH:
os << " gamma_threshold=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_HIGHLIGHT_POWER:
os << " highlight_power=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_BACKGROUND_R:
r = motion.m_MotionParams[i].second;
break;
case eEmberMotionParam::FLAME_MOTION_BACKGROUND_G:
g = motion.m_MotionParams[i].second;
break;
case eEmberMotionParam::FLAME_MOTION_BACKGROUND_B:
b = motion.m_MotionParams[i].second;
break;
case eEmberMotionParam::FLAME_MOTION_VIBRANCY:
os << " vibrancy=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_NONE:
default:
break;
}
}
if (r != 0.0 || g != 0.0 || b != 0.0)
os << " background=\"" << r << " " << g << " " << b << "\"";
if (cx != 0.0 || cy != 0.0)
os << " center=\"" << cx << " " << cy << "\"";
os << "/>\n";
return os.str();
}
/// <summary>
/// Replace the character '&' in a filename with "&amp;" and set the value in
/// the passed in node to this new string.
/// If no '&' is found, the passed in string is used as-is when populating the value of the node.
/// This is done because Xml parsing can't handle ampersands.
/// </summary>
/// <param name="node">The XML node to write the new value to</param>
/// <param name="filename">The filename string to examine for ampersands.</param>
template <typename T>
void EmberToXml<T>::AddFilenameWithoutAmpersand(xmlNodePtr node, string& filename)
{
if (filename.find_first_of('&') != std::string::npos)
{
string filenameWithoutAmpersands = filename;
FindAndReplace<string>(filenameWithoutAmpersands, "&", "&amp;");
xmlNewProp(node, XC("filename"), XC(filenameWithoutAmpersands.c_str()));
}
else
{
xmlNewProp(node, XC("filename"), XC(filename.c_str()));
}
}
#define EXPORT_EMBER_TO_XML(T) \
template EMBER_API class EmberToXml<T>; \
template EMBER_API bool EmberToXml<T>::Save(const string&, vector<Ember<T>>& embers, size_t, bool, bool, bool, bool, bool); \
template EMBER_API bool EmberToXml<T>::Save(const string&, list<Ember<T>>& embers, size_t, bool, bool, bool, bool, bool);
EXPORT_EMBER_TO_XML(float)
#ifdef DO_DOUBLE
EXPORT_EMBER_TO_XML(double)
#endif
}

View File

@ -27,835 +27,16 @@ public:
~EmberToXml() = default;
EmberToXml(const EmberToXml<T>& e) = delete;
/// <summary>
/// Save the ember to the specified file.
/// </summary>
/// <param name="filename">Full path and filename</param>
/// <param name="ember">The ember to save</param>
/// <param name="printEditDepth">How deep the edit depth goes</param>
/// <param name="doEdits">If true included edit tags, else don't.</param>
/// <param name="hexPalette">If true, embed a hexadecimal palette instead of Xml Color tags, else use Xml color tags.</param>
/// <param name="append">If true, append to the file if it already exists, else create a new file.</param>
/// <param name="start">Whether a new file is to be started</param>
/// <param name="finish">Whether an existing file is to be ended</param>
/// <returns>True if successful, else false</returns>
bool Save(const string& filename, Ember<T>& ember, size_t printEditDepth, bool doEdits, bool hexPalette, bool append = false, bool start = false, bool finish = false)
{
vector<Ember<T>> vec;
vec.push_back(ember);
return Save(filename, vec, printEditDepth, doEdits, hexPalette, append, start, finish);
}
/// <summary>
/// Save a container of embers to the specified file.
/// </summary>
/// <param name="filename">Full path and filename</param>
/// <param name="embers">The container of embers to save</param>
/// <param name="printEditDepth">How deep the edit depth goes</param>
/// <param name="doEdits">If true included edit tags, else don't.</param>
/// <param name="hexPalette">If true, embed a hexadecimal palette instead of Xml Color tags, else use Xml color tags.</param>
/// <param name="append">If true, append to the file if it already exists, else create a new file.</param>
/// <param name="start">Whether a new file is to be started</param>
/// <param name="finish">Whether an existing file is to be ended</param>
/// <returns>True if successful, else false</returns>
bool Save(const string& filename, Ember<T>& ember, size_t printEditDepth, bool doEdits, bool hexPalette, bool append = false, bool start = false, bool finish = false);
template <typename Alloc, template <typename, typename> class C>
bool Save(const string& filename, C<Ember<T>, Alloc>& embers, size_t printEditDepth, bool doEdits, bool hexPalette, bool append = false, bool start = false, bool finish = false)
{
bool b = false;
bool hasTimes = false;
T t = 0;
string temp;
ofstream f;
try
{
if (append)
f.open(filename, std::ofstream::out | std::ofstream::app);//Appending allows us to write multiple embers to a single file.
else
f.open(filename);
if (f.is_open())
{
auto prev = embers.begin();
//Always ensure times make sense.
for (auto& ember : embers)
ember.m_Time = t++;
if ((append && start) || !append)
{
temp = "<flames>\n";
//temp = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n<flames>\n";
f.write(temp.c_str(), temp.size());
}
for (auto& ember : embers)
{
string s = ToString(ember, "", printEditDepth, doEdits, hexPalette);
f.write(s.c_str(), s.size());
}
if ((append && finish) || !append)
{
temp = "</flames>\n";
f.write(temp.c_str(), temp.size());
}
f.close();
b = true;
}
else
{
cout << "Error: Writing flame " << filename << " failed.\n";
b = false;
}
}
catch (const std::exception& e)
{
cout << "Error: Writing flame " << filename << " failed: " << e.what() << "\n";
b = false;
}
catch (...)
{
cout << "Error: Writing flame " << filename << " failed.\n";
b = false;
}
return b;
}
/// <summary>
/// Return the Xml string representation of an ember.
/// </summary>
/// <param name="ember">The ember to create the Xml with</param>
/// <param name="extraAttributes">If true, add extra attributes, else don't</param>
/// <param name="printEditDepth">How deep the edit depth goes</param>
/// <param name="doEdits">If true included edit tags, else don't.</param>
/// <param name="hexPalette">If true, embed a hexadecimal palette instead of Xml Color tags, else use Xml color tags.</param>
/// <returns>The Xml string representation of the passed in ember</returns>
string ToString(Ember<T>& ember, const string& extraAttributes, size_t printEditDepth, bool doEdits, bool hexPalette = true)
{
size_t i, j;
string s;
ostringstream os;
vector<Variation<T>*> variations;
os << "<flame version=\"EMBER-" << EmberVersion() << "\" time=\"" << ember.m_Time << "\"";
if (!ember.m_Name.empty())
os << " name=\"" << ember.m_Name << "\"";
os << " size=\"" << ember.m_FinalRasW << " " << ember.m_FinalRasH << "\"";
os << " center=\"" << ember.m_CenterX << " " << ember.m_CenterY << "\"";
os << " scale=\"" << ember.m_PixelsPerUnit << "\"";
if (ember.m_Zoom != 0)
os << " zoom=\"" << ember.m_Zoom << "\"";
os << " rotate=\"" << ember.m_Rotate << "\"";
os << " supersample=\"" << std::max<size_t>(1, ember.m_Supersample) << "\"";
os << " filter=\"" << ember.m_SpatialFilterRadius << "\"";
os << " filter_shape=\"" << ToLower(SpatialFilterCreator<T>::ToString(ember.m_SpatialFilterType)) << "\"";
os << " temporal_filter_type=\"" << ToLower(TemporalFilterCreator<T>::ToString(ember.m_TemporalFilterType)) << "\"";
if (ember.m_TemporalFilterType == eTemporalFilterType::EXP_TEMPORAL_FILTER)
os << " temporal_filter_exp=\"" << ember.m_TemporalFilterExp << "\"";
os << " temporal_filter_width=\"" << ember.m_TemporalFilterWidth << "\"";
os << " quality=\"" << ember.m_Quality << "\"";
os << " temporal_samples=\"" << ember.m_TemporalSamples << "\"";
os << " sub_batch_size=\"" << ember.m_SubBatchSize << "\"";
os << " fuse=\"" << ember.m_FuseCount << "\"";
os << " background=\"" << ember.m_Background.r << " " << ember.m_Background.g << " " << ember.m_Background.b << "\"";
os << " brightness=\"" << ember.m_Brightness << "\"";
os << " gamma=\"" << ember.m_Gamma << "\"";
os << " highlight_power=\"" << ember.m_HighlightPower << "\"";
os << " vibrancy=\"" << ember.m_Vibrancy << "\"";
os << " estimator_radius=\"" << ember.m_MaxRadDE << "\"";
os << " estimator_minimum=\"" << ember.m_MinRadDE << "\"";
os << " estimator_curve=\"" << ember.m_CurveDE << "\"";
os << " gamma_threshold=\"" << ember.m_GammaThresh << "\"";
os << " cam_zpos=\"" << ember.m_CamZPos << "\"";
os << " cam_persp=\"" << ember.m_CamPerspective << "\"";
os << " cam_yaw=\"" << ember.m_CamYaw << "\"";
os << " cam_pitch=\"" << ember.m_CamPitch << "\"";
os << " cam_dof=\"" << ember.m_CamDepthBlur << "\"";
if (ember.m_PaletteMode == ePaletteMode::PALETTE_STEP)
os << " palette_mode=\"step\"";
else if (ember.m_PaletteMode == ePaletteMode::PALETTE_LINEAR)
os << " palette_mode=\"linear\"";
if (ember.m_Interp == eInterp::EMBER_INTERP_LINEAR)
os << " interpolation=\"linear\"";
else if (ember.m_Interp == eInterp::EMBER_INTERP_SMOOTH)
os << " interpolation=\"smooth\"";
if (ember.m_AffineInterp == eAffineInterp::AFFINE_INTERP_LINEAR)
os << " interpolation_type=\"linear\"";
else if (ember.m_AffineInterp == eAffineInterp::AFFINE_INTERP_LOG)
os << " interpolation_type=\"log\"";
else if (ember.m_AffineInterp == eAffineInterp::AFFINE_INTERP_COMPAT)
os << " interpolation_type=\"old\"";
else if (ember.m_AffineInterp == eAffineInterp::AFFINE_INTERP_OLDER)
os << " interpolation_type=\"older\"";
if (ember.m_PaletteInterp == ePaletteInterp::INTERP_SWEEP)
os << " palette_interpolation=\"sweep\"";
if (!extraAttributes.empty())
os << " " << extraAttributes;
os << " plugins=\"";
ember.GetPresentVariations(variations, false);
if (!variations.empty())
for (auto var : variations) os << var->Name() << (var != variations.back() ? " " : "\"");
else
os << "\"";
os << " new_linear=\"1\"";
os << " curves=\"";
for (glm::length_t ci = 0; ci < 4; ci++)
{
for (glm::length_t cj = 0; cj < 4; cj++)
{
os << ember.m_Curves.m_Points[ci][cj].x << " ";
os << ember.m_Curves.m_Points[ci][cj].y << " ";
os << ember.m_Curves.m_Weights[ci][cj] << " ";
}
}
os << "\">\n";
for (i = 0; i < ember.m_EmberMotionElements.size(); ++i)
os << " " << ToString(ember.m_EmberMotionElements[i]);
//This is a grey area, what to do about symmetry to avoid duplicating the symmetry xforms when reading back?//TODO//BUG.
//if (ember.m_Symmetry)
// os << " <symmetry kind=\"" << ember.m_Symmetry << "\"/>\n";
for (i = 0; i < ember.XformCount(); i++)
os << ToString(*ember.GetXform(i), ember.XformCount(), false, false);//Not final, don't do motion.
if (ember.UseFinalXform())
os << ToString(*ember.NonConstFinalXform(), ember.XformCount(), true, false);//Final, don't do motion.
//Note that only embedded palettes are saved. The old style of specifying a palette index to look up in a default palette file
//is no longer supported, as it makes no sense when using multiple palette files. The only way it could work is if the index was
//always meant to refer to the default file, or if the filename was embedded as well. It's easier, more straightforward and
//less error prone to just embed the palette.
if (hexPalette)
{
os << " <palette count=\"256\" format=\"RGB\">\n";
for (i = 0; i < 32; i++)
{
os << " ";
for (j = 0; j < 8; j++)
{
size_t idx = 8 * i + j;
os << hex << setw(2) << setfill('0') << int(std::rint(ember.m_Palette[idx][0] * 255));
os << hex << setw(2) << setfill('0') << int(std::rint(ember.m_Palette[idx][1] * 255));
os << hex << setw(2) << setfill('0') << int(std::rint(ember.m_Palette[idx][2] * 255));
}
os << "\n";
}
os << " </palette>\n";
}
else
{
for (i = 0; i < 256; i++)
{
double r = ember.m_Palette[i][0] * 255;
double g = ember.m_Palette[i][1] * 255;
double b = ember.m_Palette[i][2] * 255;
double a = ember.m_Palette[i][3] * 255;
os << " ";
//The original used a precision of 6 which is totally unnecessary, use 2.
if (IsClose(a, 255.0))
os << "<color index=\"" << i << "\" rgb=\"" << std::fixed << std::setprecision(2) << r << " " << g << " " << b << "\"/>";
else
os << " <color index=\"" << i << "\" rgba=\"" << std::fixed << std::setprecision(2) << r << " " << g << " " << b << " " << a << "\"/>";
os << "\n";
}
}
if (doEdits && ember.m_Edits)
os << ToString(xmlDocGetRootElement(ember.m_Edits), 1, true, printEditDepth);
os << "</flame>\n";
return os.str();
}
/// <summary>
/// Create a new editdoc optionally based on parents passed in.
/// This is used when an ember is made out of some mutation or edit from one or two existing embers and
/// the user wants to capture the genetic lineage history information in the edit doc of the new ember.
/// </summary>
/// <param name="parent0">The first parent, optionally nullptr.</param>
/// <param name="parent1">The second parent, optionally nullptr.</param>
/// <param name="action">The action that was taken to create the new ember</param>
/// <param name="nick">The nickname of the author</param>
/// <param name="url">The Url of the author</param>
/// <param name="id">The id of the author</param>
/// <param name="comment">The comment to include</param>
/// <param name="sheepGen">The sheep generation used if > 0. Default: 0.</param>
/// <param name="sheepId">The sheep id used if > 0. Default: 0.</param>
/// <returns></returns>
xmlDocPtr CreateNewEditdoc(Ember<T>* parent0, Ember<T>* parent1, const string& action, const string& nick, const string& url, const string& id, const string& comment, intmax_t sheepGen = 0, intmax_t sheepId = 0)
{
char timeString[128];
time_t myTime;
string s;
xmlDocPtr commentDoc = nullptr;
xmlDocPtr doc = xmlNewDoc(XC("1.0"));
xmlNodePtr rootNode = nullptr, node = nullptr, nodeCopy = nullptr;
xmlNodePtr rootComment = nullptr;
ostringstream os;
//Create the root node, called "edit".
rootNode = xmlNewNode(nullptr, XC("edit"));
xmlDocSetRootElement(doc, rootNode);
//Add the edit attributes.
//Date.
myTime = time(nullptr);
#ifdef _WIN32
tm localt;
localtime_s(&localt, &myTime);
strftime(timeString, 128, "%a %b %d %H:%M:%S %z %Y", &localt);//XXX use standard time format including timezone.
#else
tm* localt;
localt = localtime(&myTime);
strftime(timeString, 128, "%a %b %d %H:%M:%S %z %Y", localt);//XXX use standard time format including timezone.
#endif
xmlNewProp(rootNode, XC("date"), XC(timeString));
//Nick.
if (nick != "")
xmlNewProp(rootNode, XC("nick"), XC(nick.c_str()));
//Url.
if (url != "")
xmlNewProp(rootNode, XC("url"), XC(url.c_str()));
if (id != "")
xmlNewProp(rootNode, XC("id"), XC(id.c_str()));
//Action.
xmlNewProp(rootNode, XC("action"), XC(action.c_str()));
//Sheep info.
if (sheepGen > 0 && sheepId > 0)
{
//Create a child node of the root node called sheep.
node = xmlNewChild(rootNode, nullptr, XC("sheep"), nullptr);
//Create the sheep attributes.
os << sheepGen;
s = os.str();
xmlNewProp(node, XC("generation"), XC(s.c_str()));
os.str("");
os << sheepId;
s = os.str();
xmlNewProp(node, XC("id"), XC(s.c_str()));
os.str("");
}
//Check for the parents.
//If parent 0 not specified, this is a randomly generated genome.
if (parent0)
{
os << parent0->m_Index;
s = os.str();
if (parent0->m_Edits)
{
//Copy the node from the parent.
node = xmlDocGetRootElement(parent0->m_Edits);
nodeCopy = xmlCopyNode(node, 1);
AddFilenameWithoutAmpersand(nodeCopy, parent0->m_ParentFilename);
xmlNewProp(nodeCopy, XC("index"), XC(s.c_str()));
xmlAddChild(rootNode, nodeCopy);
}
else
{
//Insert a (parent has no edit) message.
nodeCopy = xmlNewChild(rootNode, nullptr, XC("edit"), nullptr);
AddFilenameWithoutAmpersand(nodeCopy, parent0->m_ParentFilename);
xmlNewProp(nodeCopy, XC("index"), XC(s.c_str()));
}
os.str("");
}
if (parent1)
{
os << parent1->m_Index;
s = os.str();
if (parent1->m_Edits)
{
//Copy the node from the parent.
node = xmlDocGetRootElement(parent1->m_Edits);
nodeCopy = xmlCopyNode(node, 1);
AddFilenameWithoutAmpersand(nodeCopy, parent1->m_ParentFilename);
xmlNewProp(nodeCopy, XC("index"), XC(s.c_str()));
xmlAddChild(rootNode, nodeCopy);
}
else
{
//Insert a (parent has no edit) message.
nodeCopy = xmlNewChild(rootNode, nullptr, XC("edit"), nullptr);
AddFilenameWithoutAmpersand(nodeCopy, parent1->m_ParentFilename);
xmlNewProp(nodeCopy, XC("index"), XC(s.c_str()));
}
os.str("");
}
//Comment string:
//This one's hard, since the comment string must be treated as
//a valid XML document. Create a new document using the comment
//string as the in-memory document, and then copy all children of
//the root node into the edit structure
//Parsing the comment string should be done once and then copied
//for each new edit doc, but that's for later.
if (comment != "")
{
os << "<comm>" << comment << "</comm>";
s = os.str();
commentDoc = xmlReadMemory(s.c_str(), int(s.length()), "comment.env", nullptr, XML_PARSE_NONET);
os.str("");
//Check for errors.
if (commentDoc)
{
//Loop through the children of the new document and copy them into the rootNode.
rootComment = xmlDocGetRootElement(commentDoc);
for (node = rootComment->children; node; node = node->next)
{
nodeCopy = xmlCopyNode(node, 1);
xmlAddChild(rootNode, nodeCopy);
}
//Free the created document.
xmlFreeDoc(commentDoc);
}
else
{
cout << "Failed to parse comment into Xml.\n";
}
}
//Return the Xml doc.
return doc;
}
bool Save(const string& filename, C<Ember<T>, Alloc>& embers, size_t printEditDepth, bool doEdits, bool hexPalette, bool append = false, bool start = false, bool finish = false);
string ToString(Ember<T>& ember, const string& extraAttributes, size_t printEditDepth, bool doEdits, bool hexPalette = true);
xmlDocPtr CreateNewEditdoc(Ember<T>* parent0, Ember<T>* parent1, const string& action, const string& nick, const string& url, const string& id, const string& comment, intmax_t sheepGen = 0, intmax_t sheepId = 0);
private:
/// <summary>
/// Return the Xml string representation of an xform.
/// </summary>
/// <param name="xform">The xform to create the Xml with</param>
/// <param name="xformCount">The number of non-final xforms in the ember to which this xform belongs. Used for xaos.</param>
/// <param name="isFinal">True if the xform is the final xform in the ember, else false.</param>
/// <param name="doMotion">If true, include motion elements in the Xml string, else omit.</param>
/// <returns>The Xml string representation of the passed in xform</returns>
string ToString(Xform<T>& xform, size_t xformCount, bool isFinal, bool doMotion)
{
size_t i, j;
ostringstream os;
if (doMotion)
{
os << " <motion motion_frequency=\"" << xform.m_MotionFreq << "\" ";
if (xform.m_MotionFunc == eMotion::MOTION_SIN)
os << "motion_function=\"sin\" ";
else if (xform.m_MotionFunc == eMotion::MOTION_TRIANGLE)
os << "motion_function=\"triangle\" ";
else if (xform.m_MotionFunc == eMotion::MOTION_HILL)
os << "motion_function=\"hill\" ";
else if (xform.m_MotionFunc == eMotion::MOTION_SAW)
os << "motion_function=\"saw\" ";
if (xform.m_MotionOffset != 0)
os << "motion_offset=\"" << xform.m_MotionOffset << "\" ";
}
else
{
if (isFinal)
os << " <finalxform ";
else
os << " <xform weight=\"" << xform.m_Weight << "\" ";
}
if (!doMotion || xform.m_ColorX != EMPTYFIELD) os << "color=\"" << xform.m_ColorX << "\" ";
//if (!doMotion || xform.m_ColorY != EMPTYFIELD) os << "color=\"" << xform.m_ColorX << " " << xform.m_ColorY << "\" ";
if (!doMotion || xform.m_DirectColor != EMPTYFIELD) os << "var_color=\"" << xform.m_DirectColor << "\" ";
if (!doMotion || xform.m_ColorSpeed != EMPTYFIELD) os << "color_speed=\"" << xform.m_ColorSpeed << "\" ";
//os << "symmetry=\"" << fabs(xform.m_ColorSpeed - 1) * 2 << "\" ";//Legacy support.
if (!doMotion)
{
string s = xform.m_Name;
std::replace(s.begin(), s.end(), ' ', '_');
os << "name=\"" << s << "\" ";//Flam3 didn't do this, but Apo does.
if (!isFinal)
os << "animate=\"" << xform.m_Animate << "\" ";
}
//Variation writing order differs slightly from the original to make it a bit more readable.
//The original wrote out all of the variation names and weights. Then wrote out the parameters for
//the parametric variations. Here, write out the params immediately after each parametric variation
//so they are more closely grouped with the variation they apply to, rather than being all grouped at the end.
for (i = 0; i < xform.TotalVariationCount(); i++)
{
Variation<T>* var = xform.GetVariation(i);
ParametricVariation<T>* parVar = dynamic_cast<ParametricVariation<T>*>(var);
if (var->m_Weight != 0)
{
os << var->Name() << "=\"" << var->m_Weight << "\" ";
if (parVar)
{
auto params = parVar->Params();
for (j = 0; j < parVar->ParamCount(); j++)
{
if ((!doMotion || (doMotion && (params[j].ParamVal() != 0))) && !params[j].IsPrecalc())
os << params[j].Name() << "=\"" << params[j].ParamVal() << "\" ";
}
}
}
}
if (!doMotion || (doMotion && !xform.m_Affine.IsZero() && !xform.m_Affine.IsEmpty()))
{
os << "coefs=\"" << xform.m_Affine.A() << " " << xform.m_Affine.D() << " " << xform.m_Affine.B() << " "
<< xform.m_Affine.E() << " " << xform.m_Affine.C() << " " << xform.m_Affine.F() << "\"";
}
if ((!doMotion && !xform.m_Post.IsID()) || (doMotion && !xform.m_Post.IsZero() && !xform.m_Post.IsEmpty()))
{
os << " post=\"" << xform.m_Post.A() << " " << xform.m_Post.D() << " " << xform.m_Post.B() << " "
<< xform.m_Post.E() << " " << xform.m_Post.C() << " " << xform.m_Post.F() << "\"";
}
//Original only printed xaos values that were not 1. Here, print them all out if any are present.
if (!isFinal && !doMotion && xform.XaosPresent())//Applying motion to xaos not supported.
{
os << " chaos=\"";
for (i = 0; i < xformCount; i++)
os << xform.Xaos(i) << " ";
os << "\"";
}
if (!doMotion || xform.m_Opacity != EMPTYFIELD) os << " opacity=\"" << xform.m_Opacity << "\"";
if (!doMotion && !xform.m_Motion.empty())
{
os << ">\n";
for (i = 0; i < xform.m_Motion.size(); i++)
os << ToString(xform.m_Motion[i], 0, false, true);
if (isFinal)//Fixed to properly close final.//SMOULDER
os << " </finalxform>\n";
else
os << " </xform>\n";
}
else
os << "/>\n";
return os.str();
}
/// <summary>
/// Return an edit node Xml string.
/// </summary>
/// <param name="editNode">The edit node to get the string for</param>
/// <param name="tabs">How many tabs to use</param>
/// <param name="formatting">If true, include newlines and tabs, else don't.</param>
/// <param name="printEditDepth">How deep the edit depth goes</param>
/// <returns>The edit node Xml string</returns>
string ToString(xmlNodePtr editNode, size_t tabs, bool formatting, size_t printEditDepth)
{
bool indentPrinted = false;
const char* tabString = " ", *attStr;
const char* editString = "edit";
const char* sheepString = "sheep";
size_t ti;//, editOrSheep = 0;
xmlAttrPtr attPtr = nullptr, curAtt = nullptr;
xmlNodePtr childPtr = nullptr, curChild = nullptr;
ostringstream os;
if (printEditDepth > 0 && tabs > printEditDepth)
return "";
//If this node is an XML_ELEMENT_NODE, print it and its attributes.
if (editNode->type == XML_ELEMENT_NODE)
{
//Print the node at the tab specified.
if (formatting)
for (ti = 0; ti < tabs; ti++)
os << tabString;
os << "<" << editNode->name;
//This can either be an edit node or a sheep node.
//If it's an edit node, add one to the tab.
if (!Compare(editNode->name, editString))
{
//editOrSheep = 1;
tabs++;
}
else if (!Compare(editNode->name, sheepString)) { }
//editOrSheep = 2;
else { }
//editOrSheep = 0;
//Print the attributes.
attPtr = editNode->properties;
for (curAtt = attPtr; curAtt; curAtt = curAtt->next)
{
attStr = CX(xmlGetProp(editNode, curAtt->name));
os << " " << curAtt->name << "=\"" << attStr << "\"";
xmlFree(reinterpret_cast<void*>(const_cast<char*>(attStr)));
}
//Does this node have children?
if (!editNode->children || (printEditDepth > 0 && tabs > printEditDepth))
{
//Close the tag and subtract the tab.
os << "/>";
if (formatting)
os << "\n";
tabs--;
}
else
{
//Close the tag.
os << ">";
if (formatting)
os << "\n";
//Loop through the children and print them.
childPtr = editNode->children;
indentPrinted = false;
for (curChild = childPtr; curChild; curChild = curChild->next)
{
//If child is an element, indent first and then print it.
if (curChild->type == XML_ELEMENT_NODE &&
(!Compare(curChild->name, editString) || !Compare(curChild->name, sheepString)))
{
if (indentPrinted)
{
indentPrinted = false;
os << "\n";
}
os << ToString(curChild, tabs, true, printEditDepth);
}
else
{
//Child is a text node, don't want to indent more than once.
if (xmlIsBlankNode(curChild))
continue;
if (!indentPrinted && formatting)
{
for (ti = 0; ti < tabs; ti++)
os << tabString;
indentPrinted = true;
}
//Print nodes without formatting.
os << ToString(curChild, tabs, false, printEditDepth);
}
}
if (indentPrinted && formatting)
os << "\n";
tabs--;//Tab out.
if (formatting)
for (ti = 0; ti < tabs; ti++)
os << tabString;
os << "</" << editNode->name << ">";//Close the tag.
if (formatting)
os << "\n";
}
}
else if (editNode->type == XML_TEXT_NODE)
{
string s(reinterpret_cast<char*>(xmlNodeGetContent(editNode)));
os << Trim(s);
}
return os.str();
}
/// <summary>
/// Convert a FlameMotion element to an xml string
/// </summary>
/// <param name="motion">The FlameMotion object to convert to XML</param>
string ToString(const EmberMotion<T>& motion)
{
ostringstream os;
os << "<flame_motion motion_frequency=\"" << motion.m_MotionFreq << "\" ";
if (motion.m_MotionOffset > 0)
os << "motion_offset=\"" << motion.m_MotionOffset << "\" ";
os << "motion_func=";
switch (motion.m_MotionFunc)
{
case eMotion::MOTION_SIN:
os << "\"sin\"";
break;
case eMotion::MOTION_HILL:
os << "\"hill\"";
break;
case eMotion::MOTION_TRIANGLE:
os << "\"triangle\"";
break;
case eMotion::MOTION_SAW:
default:
os << "\"saw\"";
break;
}
T r = 0.0;
T g = 0.0;
T b = 0.0;
T cx = 0.0;
T cy = 0.0;
for (size_t i = 0; i < motion.m_MotionParams.size(); ++i)
{
switch (motion.m_MotionParams[i].first)
{
case eEmberMotionParam::FLAME_MOTION_ZOOM:
os << " zoom=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_ZPOS:
os << " cam_zpos=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_PERSPECTIVE:
os << " cam_persp=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_YAW:
os << " cam_yaw=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_PITCH:
os << " cam_pitch=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_DEPTH_BLUR:
os << " cam_dof=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_CENTER_X:
cx = motion.m_MotionParams[i].second;
break;
case eEmberMotionParam::FLAME_MOTION_CENTER_Y:
cy = motion.m_MotionParams[i].second;
break;
case eEmberMotionParam::FLAME_MOTION_ROTATE:
os << " rotate=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_BRIGHTNESS:
os << " brightness=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_GAMMA:
os << " gamma=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_GAMMA_THRESH:
os << " gamma_threshold=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_HIGHLIGHT_POWER:
os << " highlight_power=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_BACKGROUND_R:
r = motion.m_MotionParams[i].second;
break;
case eEmberMotionParam::FLAME_MOTION_BACKGROUND_G:
g = motion.m_MotionParams[i].second;
break;
case eEmberMotionParam::FLAME_MOTION_BACKGROUND_B:
b = motion.m_MotionParams[i].second;
break;
case eEmberMotionParam::FLAME_MOTION_VIBRANCY:
os << " vibrancy=\"" << motion.m_MotionParams[i].second << "\"";
break;
case eEmberMotionParam::FLAME_MOTION_NONE:
default:
break;
}
}
if (r != 0.0 || g != 0.0 || b != 0.0)
os << " background=\"" << r << " " << g << " " << b << "\"";
if (cx != 0.0 || cy != 0.0)
os << " center=\"" << cx << " " << cy << "\"";
os << "/>\n";
return os.str();
}
void AddFilenameWithoutAmpersand(xmlNodePtr node, string& filename)
{
if (filename.find_first_of('&') != std::string::npos)
{
string filenameWithoutAmpersands = filename;
FindAndReplace<string>(filenameWithoutAmpersands, "&", "&amp;");
xmlNewProp(node, XC("filename"), XC(filenameWithoutAmpersands.c_str()));
}
else
{
xmlNewProp(node, XC("filename"), XC(filename.c_str()));
}
}
string ToString(Xform<T>& xform, size_t xformCount, bool isFinal, bool doMotion);
string ToString(xmlNodePtr editNode, size_t tabs, bool formatting, size_t printEditDepth);
string ToString(const EmberMotion<T>& motion);
void AddFilenameWithoutAmpersand(xmlNodePtr node, string& filename);
};
}

View File

@ -13,6 +13,9 @@ namespace EmberNs
/// The palette stores a set of 256 colors which are what get accumulated to the histogram
/// for each iteration. The colors come from either the main palette Xml file or directly
/// from the ember parameter file. Either way, they come in as 0-255 and get normalized to 0-1.
/// The palette may have also come from a palette editor where the user specifies key colors, then
/// those are interpolated to make a smooth palette. In that case, the m_SourceColors map will
/// be populated.
/// In the future, 2D palette support might be added in which case this class will have to be modified.
/// Template argument expected to be float or double.
/// </summary>
@ -90,13 +93,27 @@ public:
for (size_t i = 0; i < size; i++)
{
m_Entries[i].a = T(palette15[i * 4 + 0]);
m_Entries[i].r = T(palette15[i * 4 + 1]);
m_Entries[i].g = T(palette15[i * 4 + 2]);
m_Entries[i].b = T(palette15[i * 4 + 3]);
m_Entries[i].r = T(palette15[i * 4 + 1]) / T(255);
m_Entries[i].g = T(palette15[i * 4 + 2]) / T(255);
m_Entries[i].b = T(palette15[i * 4 + 3]) / T(255);
}
}
}
/// <summary>
/// Constructor which takes the vector of colors as well as the source colors which were
/// used to create it in a palette editor. The burden is on the user to not let the two get out of sync.
/// </summary>
/// <param name="name">The name of the palette</param>
/// <param name="entries">A vector of color entries</param>
/// <param name="sourceColors">A map of colors which was used to create entries in a palette editor</param>
Palette(const string& name, vector<v4T>& entries, map<T, v4T>& sourceColors)
{
m_Name = name;
m_Entries = entries;
m_SourceColors = sourceColors;
}
/// <summary>
/// Default copy constructor.
/// </summary>
@ -145,6 +162,11 @@ public:
m_Name = palette.m_Name;
m_Filename = palette.m_Filename;
CopyCont(m_Entries, palette.m_Entries);
m_SourceColors.clear();
for (auto& kv : palette.m_SourceColors)
m_SourceColors[T(kv.first)] = v4T(kv.second);
return *this;
}
@ -183,6 +205,13 @@ public:
/// <returns>The size of the color entries vector</returns>
size_t Size() { return m_Entries.size(); }
/// <summary>
/// The size of the source color entries vector which was used to create the palette.
/// Note this will only be non-zero if this palette was created in the palette editor.
/// </summary>
/// <returns>The size of the source colors map</returns>
size_t SourceColorSize() { return m_SourceColors.size(); }
/// <summary>
/// Set all colors to either black or white, including the alpha channel.
/// </summary>
@ -585,5 +614,6 @@ public:
string m_Name = "-";//Name of this palette.
shared_ptr<string> m_Filename;//Name of the parent file this palette came from, can be empty.
vector<v4T> m_Entries;
map<T, v4T> m_SourceColors;
};
}

View File

@ -0,0 +1,779 @@
#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()
{
//Add(string(m_DefaultFilename));
}
/// <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_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);
palettes.first->second.clear();
palettes.first->second.reserve(buf.size() / 2048);//Roughly what it takes per 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.
/// </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()
{
auto p = s_Palettes.begin();
size_t i = 0, paletteFileIndex = QTIsaac<ISAAC_SIZE, ISAAC_INT>::LockedRand() % Size();
//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())
return &p->second[paletteIndex];
}
return 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 at least one palette in the passed in palette file is 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 least one palette in the file was 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 true;
}
return false;
}
/// <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 << sc.first << "," << sc.second.r << "," << sc.second.g << "," << sc.second.b << " ";
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
}

View File

@ -1,6 +1,7 @@
#pragma once
#include "Palette.h"
#include "Point.h"
/// <summary>
/// PaletteList class.
@ -12,7 +13,11 @@ namespace EmberNs
/// Holds a list of palettes read from an Xml file. Since the default list from flam3-palettes.xml is fairly large at 700 palettes,
/// the list member is kept as a static. This class derives from EmberReport in order to report any errors that occurred while reading the Xml.
/// 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.
/// Template argument expected to be float or double.
/// This can hold read only palettes, as well as user created and modifiable ones.
/// The key in the map is the fully qualified path and filename to each palette file.
/// Despite the keys being full paths, the same filename cannot be inserted twice, even if they reside
/// in different folders. Functions are provided to retrieve palette files via filename only, or full path.
/// Template argument should always be float (which makes the templating of this class pointless).
/// </summary>
template <typename T>
class EMBER_API PaletteList : public EmberReport
@ -20,294 +25,37 @@ class EMBER_API PaletteList : public EmberReport
public:
static const char* m_DefaultFilename;
/// <summary>
/// Empty constructor which initializes the palette map with the default palette file.
/// </summary>
PaletteList()
{
Add(string(m_DefaultFilename));
}
~PaletteList() = default;
PaletteList();
PaletteList(const PaletteList<T>& paletteList) = delete;
/// <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>
bool Add(const string& filename, bool force = false)
{
bool added = true;
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))
{
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);
auto pfilename = shared_ptr<string>(new string(filename));
palettes.first->second.clear();
palettes.first->second.reserve(buf.size() / 2048);//Roughly what it takes per 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
{
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.
/// </summary>
Palette<T>* GetRandomPalette()
{
auto p = s_Palettes.begin();
size_t i = 0, paletteFileIndex = QTIsaac<ISAAC_SIZE, ISAAC_INT>::LockedRand() % Size();
//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())
return &p->second[paletteIndex];
}
return 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>
Palette<T>* GetPalette(const string& filename, size_t i)
{
auto& palettes = s_Palettes[filename];
if (!palettes.empty() && i < palettes.size())
return &palettes[i];
return nullptr;
}
/// <summary>
/// Get a pointer to a palette with a specified name in the specified file 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 palette if found, else nullptr</returns>
Palette<T>* GetPaletteByName(const string& filename, const string& name)
{
for (auto& palettes : s_Palettes)
if (palettes.first == filename)
for (auto& palette : palettes.second)
if (palette.m_Name == name)
return &palette;
return nullptr;
}
/// <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>
bool GetHueAdjustedPalette(const string& filename, size_t i, T hue, Palette<T>& palette)
{
bool b = false;
if (Palette<T>* unadjustedPal = GetPalette(filename, i))
{
unadjustedPal->MakeHueAdjustedPalette(palette, hue);
b = true;
}
return b;
}
/// <summary>
/// Clear the palette list and reset the initialization state.
/// </summary>
void 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>
size_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>
size_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>
size_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>
const string& 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;
}
~PaletteList();
bool AddPaletteFile(const string& filename, const vector<Palette<T>>& palettes);
bool AddEmptyPaletteFile(const string& filename);
bool AddPaletteToFile(const string& filename, const Palette<T>& palette);
bool Replace(const string& filename, const Palette<T>& palette);
bool Replace(const string& filename, const Palette<T>& palette, int index);
bool Delete(const string& filename, int index);
bool Add(const string& filename, bool force = false);
Palette<T>* GetRandomPalette();
Palette<T>* GetPaletteByFilename(const string& filename, size_t i);
Palette<T>* GetPaletteByFullPath(const string& filename, size_t i);
Palette<T>* GetPaletteByName(const string& filename, const string& name);
vector<Palette<T>>* GetPaletteListByFilename(const string& filename);
vector<Palette<T>>* GetPaletteListByFullPath(const string& filename);
vector<Palette<T>>* GetPaletteListByFullPathOrFilename(const string& filename);
string GetFullPathFromFilename(const string& filename);
bool GetHueAdjustedPalette(const string& filename, size_t i, T hue, Palette<T>& palette);
void Clear();
size_t Size();
size_t Size(size_t index);
size_t Size(const string& s);
const string& Name(size_t index);
bool IsModifiable(const string& filename);
const map<string, vector<Palette<T>>>& Palettes() const;
private:
/// <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 paresed palettes associated with this file in.</param>
void ParsePalettes(xmlNode* node, const shared_ptr<string>& filename, vector<Palette<T>>& palettes)
{
char* val;
const char* loc = __FUNCTION__;
xmlAttrPtr attr;
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, colorIndex = 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, "number"))
{
palette.m_Index = atoi(val);
}
else if (!Compare(attr->name, "name"))
{
palette.m_Name = string(val);
}
xmlFree(val);
attr = attr->next;
}
palette.m_Filename = filename;
palettes.push_back(palette);
}
else
{
ParsePalettes(node->children, filename, palettes);
}
node = node->next;
}
}
bool Save(const string& filename);
void ParsePalettes(xmlNode* node, const shared_ptr<string>& filename, vector<Palette<T>>& palettes);
bool ParsePalettes(const string& buf, const shared_ptr<string>& filename, vector<Palette<T>>& palettes);
static map<string, vector<Palette<T>>> s_Palettes;//The map of filenames to vectors that store the palettes.
};

View File

@ -60,25 +60,6 @@ bool Renderer<T, bucketT>::AssignIterator()
template <typename T, typename bucketT>
void Renderer<T, bucketT>::ComputeBounds()
{
//size_t maxDEFilterWidth = 0;
//Use type T to account for negative numbers which will occur with a larger supersample and smaller filter width.
//The final value will be of type size_t.
//m_GutterWidth = size_t(ClampGte<T>((T(m_SpatialFilter->FinalFilterWidth()) - T(Supersample())) / 2, 0));
//
////Check the size of the density estimation filter.
////If the radius of the density estimation filter is greater than the
////gutter width, have to pad with more. Otherwise, use the same value.
//for (auto& ember : *m_EmbersP)
// maxDEFilterWidth = std::max<size_t>(size_t(ceil(ember.m_MaxRadDE) * m_Ember.m_Supersample), maxDEFilterWidth);
//
////Need an extra ss = (int)floor(m_Supersample / 2.0) of pixels so that a local iteration count for DE can be determined.//SMOULDER
//if (maxDEFilterWidth > 0)
// maxDEFilterWidth += size_t(Floor<T>(m_Ember.m_Supersample / T(2)));
//To have a fully present set of pixels for the spatial filter, must
//add the DE filter width to the spatial filter width.//SMOULDER
//m_DensityFilterOffset = maxDEFilterWidth;
//m_GutterWidth += m_DensityFilterOffset;
//
//Original did a lot of work to compute a gutter that changes size based on various parameters, which seems to be of no benefit.
//It also prevents the renderer from only performing filtering or final accum based on a filter parameter change, since that
//change may have changed the gutter.
@ -720,7 +701,7 @@ EmberImageComments Renderer<T, bucketT>::ImageComments(const EmberStats& stats,
template <typename T, typename bucketT>
void Renderer<T, bucketT>::MakeDmap(T colorScalar)
{
m_Ember.m_Palette.template MakeDmap<bucketT>(m_Dmap, colorScalar);
m_Ember.m_Palette.template MakeDmap<bucketT>(m_Dmap, bucketT(colorScalar));
}
/// <summary>
@ -863,48 +844,33 @@ eRenderStatus Renderer<T, bucketT>::LogScaleDensityFilter(bool forceOutput)
size_t endRow = m_SuperRasH;
size_t endCol = m_SuperRasW;
//Timing t(4);
//if (forceOutput)//Assume interactive render, so speed up at the expense of slight quality.
//{
// parallel_for(startRow, endRow, [&](size_t j)
// {
// if (!m_Abort)
// {
// size_t row = j * m_SuperRasW;
// size_t rowEnd = row + endCol;
// VectorizedLogScale(row, rowEnd);
// }
// });
//}
//else
//Original didn't parallelize this, doing so gives a 50-75% speedup.
//The value can be directly assigned, which is quicker than summing.
parallel_for(startRow, endRow, size_t(1), [&](size_t j)
{
//Original didn't parallelize this, doing so gives a 50-75% speedup.
//The value can be directly assigned, which is quicker than summing.
parallel_for(startRow, endRow, [&](size_t j)
size_t row = j * m_SuperRasW;
size_t rowEnd = row + endCol;
if (!m_Abort)
{
size_t row = j * m_SuperRasW;
size_t rowEnd = row + endCol;
if (!m_Abort)
for (size_t i = row; i < rowEnd; i++)
{
for (size_t i = row; i < rowEnd; i++)
//Check for visibility first before doing anything else to avoid all possible unnecessary calculations.
if (m_HistBuckets[i].a != 0)
{
//Check for visibility first before doing anything else to avoid all possible unnecessary calculations.
if (m_HistBuckets[i].a != 0)
{
bucketT logScale = (m_K1 * std::log(1 + m_HistBuckets[i].a * m_K2)) / m_HistBuckets[i].a;
//Original did a temporary assignment, then *= logScale, then passed the result to bump_no_overflow().
//Combine here into one operation for a slight speedup.
//Vectorized version:
bucketT* __restrict hist = glm::value_ptr(m_HistBuckets[i]);//Vectorizer can't tell these point to different locations.
bucketT* __restrict acc = glm::value_ptr(m_AccumulatorBuckets[i]);
bucketT logScale = (m_K1 * std::log(1 + m_HistBuckets[i].a * m_K2)) / m_HistBuckets[i].a;
//Original did a temporary assignment, then *= logScale, then passed the result to bump_no_overflow().
//Combine here into one operation for a slight speedup.
//Vectorized version:
bucketT* __restrict hist = glm::value_ptr(m_HistBuckets[i]);//Vectorizer can't tell these point to different locations.
bucketT* __restrict acc = glm::value_ptr(m_AccumulatorBuckets[i]);
for (size_t v = 0; v < 4; v++)
acc[v] = hist[v] * logScale;
}
for (size_t v = 0; v < 4; v++)
acc[v] = hist[v] * logScale;
}
}
});
}
}
});
//t.Toc(__FUNCTION__);
return m_Abort ? eRenderStatus::RENDER_ABORT : eRenderStatus::RENDER_OK;
}
@ -930,7 +896,7 @@ eRenderStatus Renderer<T, bucketT>::GaussianDensityFilter()
intmax_t endCol = m_SuperRasW - (Supersample() - 1);
size_t chunkSize = size_t(ceil(double(endRow - startRow) / double(threads)));
//parallel_for scales very well, dividing the work almost perfectly among all processors.
parallel_for(size_t(0), threads, [&] (size_t threadIndex)
parallel_for(size_t(0), threads, size_t(1), [&] (size_t threadIndex)
{
size_t pixelNumber = 0;
int localStartRow = int(std::min<size_t>(startRow + (threadIndex * chunkSize), endRow - 1));
@ -1115,7 +1081,7 @@ eRenderStatus Renderer<T, bucketT>::AccumulatorToFinalImage(byte* pixels, size_t
//The original does it this way as well and it's roughly 11 times faster to do it this way than inline below with each pixel.
if (EarlyClip())
{
parallel_for(size_t(0), m_SuperRasH, [&] (size_t j)
parallel_for(size_t(0), m_SuperRasH, size_t(1), [&] (size_t j)
{
auto rowStart = m_AccumulatorBuckets.data() + (j * m_SuperRasW);//Pull out of inner loop for optimization.
auto rowEnd = rowStart + m_SuperRasW;
@ -1138,7 +1104,7 @@ eRenderStatus Renderer<T, bucketT>::AccumulatorToFinalImage(byte* pixels, size_t
//otherwise artifacts that resemble page tearing will occur in an interactive run. It's
//critical to never exit this loop prematurely.
//for (size_t j = 0; j < FinalRasH(); j++)//Keep around for debugging.
parallel_for(size_t(0), FinalRasH(), [&](size_t j)
parallel_for(size_t(0), FinalRasH(), size_t(1), [&](size_t j)
{
Color<bucketT> newBucket;
size_t pixelsRowStart = (m_YAxisUp ? ((FinalRasH() - j) - 1) : j) * FinalRowSize();//Pull out of inner loop for optimization.
@ -1300,13 +1266,13 @@ EmberStats Renderer<T, bucketT>::Iterate(size_t iterCount, size_t temporalSample
m_ThreadEmbers.insert(m_ThreadEmbers.begin(), m_ThreadsToUse, m_Ember);
}
parallel_for(size_t(0), m_ThreadsToUse, [&] (size_t threadIndex)
parallel_for(size_t(0), m_ThreadsToUse, size_t(1), [&] (size_t threadIndex)
{
#if defined(_WIN32)
SetThreadPriority(GetCurrentThread(), int(m_Priority));
#elif defined(__APPLE__)
sched_param sp = {0};
sp.sched_priority = m_Priority;
sp.sched_priority = int(m_Priority);
pthread_setschedparam(pthread_self(), SCHED_RR, &sp);
#else
pthread_setschedprio(pthread_self(), int(m_Priority));
@ -1441,31 +1407,31 @@ template <typename T, typename bucketT> DensityFilterBase* Renderer<T, bucketT>:
/// Non-virtual ember wrappers, getters only.
/// </summary>
template <typename T, typename bucketT> bool Renderer<T, bucketT>::XaosPresent() const { return m_Ember.XaosPresent(); }
template <typename T, typename bucketT> size_t Renderer<T, bucketT>::Supersample() const { return m_Ember.m_Supersample; }
template <typename T, typename bucketT> size_t Renderer<T, bucketT>::PaletteIndex() const { return m_Ember.PaletteIndex(); }
template <typename T, typename bucketT> T Renderer<T, bucketT>::Time() const { return m_Ember.m_Time; }
template <typename T, typename bucketT> T Renderer<T, bucketT>::Quality() const { return m_Ember.m_Quality; }
template <typename T, typename bucketT> T Renderer<T, bucketT>::SpatialFilterRadius() const { return m_Ember.m_SpatialFilterRadius; }
template <typename T, typename bucketT> T Renderer<T, bucketT>::PixelsPerUnit() const { return m_Ember.m_PixelsPerUnit; }
template <typename T, typename bucketT> T Renderer<T, bucketT>::Zoom() const { return m_Ember.m_Zoom; }
template <typename T, typename bucketT> T Renderer<T, bucketT>::CenterX() const { return m_Ember.m_CenterX; }
template <typename T, typename bucketT> T Renderer<T, bucketT>::CenterY() const { return m_Ember.m_CenterY; }
template <typename T, typename bucketT> T Renderer<T, bucketT>::Rotate() const { return m_Ember.m_Rotate; }
template <typename T, typename bucketT> bucketT Renderer<T, bucketT>::Brightness() const { return bucketT(m_Ember.m_Brightness); }
template <typename T, typename bucketT> bucketT Renderer<T, bucketT>::Gamma() const { return bucketT(m_Ember.m_Gamma); }
template <typename T, typename bucketT> bucketT Renderer<T, bucketT>::Vibrancy() const { return bucketT(m_Ember.m_Vibrancy); }
template <typename T, typename bucketT> bucketT Renderer<T, bucketT>::GammaThresh() const { return bucketT(m_Ember.m_GammaThresh); }
template <typename T, typename bucketT> bucketT Renderer<T, bucketT>::HighlightPower() const { return bucketT(m_Ember.m_HighlightPower); }
template <typename T, typename bucketT> Color<T> Renderer<T, bucketT>::Background() const { return m_Ember.m_Background; }
template <typename T, typename bucketT> const Xform<T>* Renderer<T, bucketT>::Xforms() const { return m_Ember.Xforms(); }
template <typename T, typename bucketT> Xform<T>* Renderer<T, bucketT>::NonConstXforms() { return m_Ember.NonConstXforms(); }
template <typename T, typename bucketT> size_t Renderer<T, bucketT>::XformCount() const { return m_Ember.XformCount(); }
template <typename T, typename bucketT> const Xform<T>* Renderer<T, bucketT>::FinalXform() const { return m_Ember.FinalXform(); }
template <typename T, typename bucketT> Xform<T>* Renderer<T, bucketT>::NonConstFinalXform() { return m_Ember.NonConstFinalXform(); }
template <typename T, typename bucketT> bool Renderer<T, bucketT>::UseFinalXform() const { return m_Ember.UseFinalXform(); }
template <typename T, typename bucketT> const Palette<T>* Renderer<T, bucketT>::GetPalette() const { return &m_Ember.m_Palette; }
template <typename T, typename bucketT> ePaletteMode Renderer<T, bucketT>::PaletteMode() const { return m_Ember.m_PaletteMode; }
template <typename T, typename bucketT> bool Renderer<T, bucketT>::XaosPresent() const { return m_Ember.XaosPresent(); }
template <typename T, typename bucketT> size_t Renderer<T, bucketT>::Supersample() const { return m_Ember.m_Supersample; }
template <typename T, typename bucketT> size_t Renderer<T, bucketT>::PaletteIndex() const { return m_Ember.PaletteIndex(); }
template <typename T, typename bucketT> T Renderer<T, bucketT>::Time() const { return m_Ember.m_Time; }
template <typename T, typename bucketT> T Renderer<T, bucketT>::Quality() const { return m_Ember.m_Quality; }
template <typename T, typename bucketT> T Renderer<T, bucketT>::SpatialFilterRadius() const { return m_Ember.m_SpatialFilterRadius; }
template <typename T, typename bucketT> T Renderer<T, bucketT>::PixelsPerUnit() const { return m_Ember.m_PixelsPerUnit; }
template <typename T, typename bucketT> T Renderer<T, bucketT>::Zoom() const { return m_Ember.m_Zoom; }
template <typename T, typename bucketT> T Renderer<T, bucketT>::CenterX() const { return m_Ember.m_CenterX; }
template <typename T, typename bucketT> T Renderer<T, bucketT>::CenterY() const { return m_Ember.m_CenterY; }
template <typename T, typename bucketT> T Renderer<T, bucketT>::Rotate() const { return m_Ember.m_Rotate; }
template <typename T, typename bucketT> bucketT Renderer<T, bucketT>::Brightness() const { return bucketT(m_Ember.m_Brightness); }
template <typename T, typename bucketT> bucketT Renderer<T, bucketT>::Gamma() const { return bucketT(m_Ember.m_Gamma); }
template <typename T, typename bucketT> bucketT Renderer<T, bucketT>::Vibrancy() const { return bucketT(m_Ember.m_Vibrancy); }
template <typename T, typename bucketT> bucketT Renderer<T, bucketT>::GammaThresh() const { return bucketT(m_Ember.m_GammaThresh); }
template <typename T, typename bucketT> bucketT Renderer<T, bucketT>::HighlightPower() const { return bucketT(m_Ember.m_HighlightPower); }
template <typename T, typename bucketT> Color<T> Renderer<T, bucketT>::Background() const { return m_Ember.m_Background; }
template <typename T, typename bucketT> const Xform<T>* Renderer<T, bucketT>::Xforms() const { return m_Ember.Xforms(); }
template <typename T, typename bucketT> Xform<T>* Renderer<T, bucketT>::NonConstXforms() { return m_Ember.NonConstXforms(); }
template <typename T, typename bucketT> size_t Renderer<T, bucketT>::XformCount() const { return m_Ember.XformCount(); }
template <typename T, typename bucketT> const Xform<T>* Renderer<T, bucketT>::FinalXform() const { return m_Ember.FinalXform(); }
template <typename T, typename bucketT> Xform<T>* Renderer<T, bucketT>::NonConstFinalXform() { return m_Ember.NonConstFinalXform(); }
template <typename T, typename bucketT> bool Renderer<T, bucketT>::UseFinalXform() const { return m_Ember.UseFinalXform(); }
template <typename T, typename bucketT> const Palette<float>* Renderer<T, bucketT>::GetPalette() const { return &m_Ember.m_Palette; }
template <typename T, typename bucketT> ePaletteMode Renderer<T, bucketT>::PaletteMode() const { return m_Ember.m_PaletteMode; }
/// <summary>
/// Virtual ember wrappers overridden from RendererBase, getters only.
@ -1528,53 +1494,52 @@ void Renderer<T, bucketT>::Accumulate(QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand, Poin
{
size_t histIndex, intColorIndex, histSize = m_HistBuckets.size();
bucketT colorIndex, colorIndexFrac;
auto dmap = palette->m_Entries.data();
//It's critical to understand what's going on here as it's one of the most important parts of the algorithm.
//A color value gets retrieved from the palette and
//its RGB values are added to the existing RGB values in the histogram bucket.
//Alpha is always 1 in the palettes, so that serves as the hit count.
//This differs from the original since redundantly adding both an alpha component and a hit count is omitted.
//This will eventually leave us with large values for pixels with many hits, which will be log scaled down later.
//Original used a function called bump_no_overflow(). Just do a straight add because the type will always be float or double.
//Doing so gives a 25% speed increase.
//Splitting these conditionals into separate loops makes no speed difference.
for (size_t i = 0; i < sampleCount && !m_Abort; i++)
//Linear is a linear scale for when the color index is not a whole number, which is most of the time.
//It uses a portion of the value of the index, and the remainder of the next index.
//Example: index = 25.7
//Fraction = 0.7
//Color = (dmap[25] * 0.3) + (dmap[26] * 0.7)
//Use overloaded addition and multiplication operators in vec4 to perform the accumulation.
if (PaletteMode() == ePaletteMode::PALETTE_LINEAR)
{
Point<T> p(samples[i]);//Slightly faster to cache this.
if (Rotate() != 0)
//It's critical to understand what's going on here as it's one of the most important parts of the algorithm.
//A color value gets retrieved from the palette and
//its RGB values are added to the existing RGB values in the histogram bucket.
//Alpha is always 1 in the palettes, so that serves as the hit count.
//This differs from the original since redundantly adding both an alpha component and a hit count is omitted.
//This will eventually leave us with large values for pixels with many hits, which will be log scaled down later.
//Original used a function called bump_no_overflow(). Just do a straight add because the type will always be float or double.
//Doing so gives a 25% speed increase.
//Splitting these conditionals into separate loops makes no speed difference.
for (size_t i = 0; i < sampleCount && !m_Abort; i++)
{
T p00 = p.m_X - CenterX();
T p11 = p.m_Y - m_Ember.m_RotCenterY;
p.m_X = (p00 * m_RotMat.A()) + (p11 * m_RotMat.B()) + CenterX();
p.m_Y = (p00 * m_RotMat.D()) + (p11 * m_RotMat.E()) + m_Ember.m_RotCenterY;
}
Point<T> p(samples[i]);//Slightly faster to cache this.
//Checking this first before converting gives better performance than converting and checking a single value, which the original did.
//Second, an interesting optimization observation is that when keeping the bounds vars within m_CarToRas and calling its InBounds() member function,
//rather than here as members, about a 7% speedup is achieved. This is possibly due to the fact that data from m_CarToRas is accessed
//right after the call to Convert(), so some caching efficiencies get realized.
if (m_CarToRas.InBounds(p))
{
if (p.m_VizAdjusted != 0)
if (Rotate() != 0)
{
m_CarToRas.Convert(p, histIndex);
T p00 = p.m_X - CenterX();
T p11 = p.m_Y - m_Ember.m_RotCenterY;
p.m_X = (p00 * m_RotMat.A()) + (p11 * m_RotMat.B()) + CenterX();
p.m_Y = (p00 * m_RotMat.D()) + (p11 * m_RotMat.E()) + m_Ember.m_RotCenterY;
}
//There is a very slim chance that a point will be right on the border and will technically be in bounds, passing the InBounds() test,
//but ends up being mapped to a histogram bucket that is out of bounds due to roundoff error. Perform one final check before proceeding.
//This will result in a few points at the very edges getting discarded, but prevents a crash and doesn't seem to make a speed difference.
if (histIndex < histSize)
//Checking this first before converting gives better performance than converting and checking a single value, which the original did.
//Second, an interesting optimization observation is that when keeping the bounds vars within m_CarToRas and calling its InBounds() member function,
//rather than here as members, about a 7% speedup is achieved. This is possibly due to the fact that data from m_CarToRas is accessed
//right after the call to Convert(), so some caching efficiencies get realized.
if (m_CarToRas.InBounds(p))
{
if (p.m_VizAdjusted != 0)
{
//Linear is a linear scale for when the color index is not a whole number, which is most of the time.
//It uses a portion of the value of the index, and the remainder of the next index.
//Example: index = 25.7
//Fraction = 0.7
//Color = (dmap[25] * 0.3) + (dmap[26] * 0.7)
//Use overloaded addition and multiplication operators in vec4 to perform the accumulation.
if (PaletteMode() == ePaletteMode::PALETTE_LINEAR)
m_CarToRas.Convert(p, histIndex);
//There is a very slim chance that a point will be right on the border and will technically be in bounds, passing the InBounds() test,
//but ends up being mapped to a histogram bucket that is out of bounds due to roundoff error. Perform one final check before proceeding.
//This will result in a few points at the very edges getting discarded, but prevents a crash and doesn't seem to make a speed difference.
if (histIndex < histSize)
{
colorIndex = bucketT(p.m_ColorX) * COLORMAP_LENGTH;
colorIndex = bucketT(p.m_ColorX) * COLORMAP_LENGTH_MINUS_1;
intColorIndex = size_t(colorIndex);
if (intColorIndex < 0)
@ -1614,9 +1579,33 @@ void Renderer<T, bucketT>::Accumulate(QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand, Poin
hist[3] += ((pal[3] * cifm1) + (pal2[3] * colorIndexFrac)) * va;
}
}
else if (PaletteMode() == ePaletteMode::PALETTE_STEP)
}
}
}
}
else if (PaletteMode() == ePaletteMode::PALETTE_STEP)//Duplicate of above, but for step mode.
{
for (size_t i = 0; i < sampleCount && !m_Abort; i++)
{
Point<T> p(samples[i]);//Slightly faster to cache this.
if (Rotate() != 0)
{
T p00 = p.m_X - CenterX();
T p11 = p.m_Y - m_Ember.m_RotCenterY;
p.m_X = (p00 * m_RotMat.A()) + (p11 * m_RotMat.B()) + CenterX();
p.m_Y = (p00 * m_RotMat.D()) + (p11 * m_RotMat.E()) + m_Ember.m_RotCenterY;
}
if (m_CarToRas.InBounds(p))
{
if (p.m_VizAdjusted != 0)
{
m_CarToRas.Convert(p, histIndex);
if (histIndex < histSize)
{
intColorIndex = Clamp<size_t>(size_t(p.m_ColorX * COLORMAP_LENGTH), 0, COLORMAP_LENGTH_MINUS_1);
intColorIndex = Clamp<size_t>(size_t(p.m_ColorX * COLORMAP_LENGTH_MINUS_1), 0, COLORMAP_LENGTH_MINUS_1);
bucketT* __restrict hist = glm::value_ptr(m_HistBuckets[histIndex]);//Vectorizer can't tell these point to different locations.
const bucketT* __restrict pal = glm::value_ptr(palette->m_Entries[intColorIndex]);
@ -1654,7 +1643,13 @@ template <typename T, typename bucketT>
void Renderer<T, bucketT>::AddToAccum(const tvec4<bucketT, glm::defaultp>& bucket, intmax_t i, intmax_t ii, intmax_t j, intmax_t jj)
{
if (j + jj >= 0 && j + jj < intmax_t(m_SuperRasH) && i + ii >= 0 && i + ii < intmax_t(m_SuperRasW))
m_AccumulatorBuckets[(i + ii) + ((j + jj) * m_SuperRasW)] += bucket;
{
auto* __restrict accum = m_AccumulatorBuckets.data() + ((i + ii) + ((j + jj) * m_SuperRasW));//For vectorizer, results in a 33% speedup.
accum->r += bucket.r;
accum->g += bucket.g;
accum->b += bucket.b;
accum->a += bucket.a;
}
}
/// <summary>

View File

@ -107,33 +107,33 @@ public:
virtual DensityFilterBase* GetDensityFilter() override;
//Non-virtual ember wrappers, getters only.
inline bool XaosPresent() const;
inline size_t Supersample() const;
inline size_t PaletteIndex() const;
inline T Time() const;
inline T Quality() const;
inline T SpatialFilterRadius() const;
inline T PixelsPerUnit() const;
inline T Zoom() const;
inline T CenterX() const;
inline T CenterY() const;
inline T Rotate() const;
inline T Hue() const;
inline bucketT Brightness() const;
inline bucketT Contrast() const;
inline bucketT Gamma() const;
inline bucketT Vibrancy() const;
inline bucketT GammaThresh() const;
inline bucketT HighlightPower() const;
inline Color<T> Background() const;
inline const Xform<T>* Xforms() const;
inline Xform<T>* NonConstXforms();
inline size_t XformCount() const;
inline const Xform<T>* FinalXform() const;
inline Xform<T>* NonConstFinalXform();
inline bool UseFinalXform() const;
inline const Palette<T>* GetPalette() const;
inline ePaletteMode PaletteMode() const;
inline bool XaosPresent() const;
inline size_t Supersample() const;
inline size_t PaletteIndex() const;
inline T Time() const;
inline T Quality() const;
inline T SpatialFilterRadius() const;
inline T PixelsPerUnit() const;
inline T Zoom() const;
inline T CenterX() const;
inline T CenterY() const;
inline T Rotate() const;
inline T Hue() const;
inline bucketT Brightness() const;
inline bucketT Contrast() const;
inline bucketT Gamma() const;
inline bucketT Vibrancy() const;
inline bucketT GammaThresh() const;
inline bucketT HighlightPower() const;
inline Color<T> Background() const;
inline const Xform<T>* Xforms() const;
inline Xform<T>* NonConstXforms();
inline size_t XformCount() const;
inline const Xform<T>* FinalXform() const;
inline Xform<T>* NonConstFinalXform();
inline bool UseFinalXform() const;
inline const Palette<float>* GetPalette() const;
inline ePaletteMode PaletteMode() const;
//Virtual ember wrappers overridden from RendererBase, getters only.
virtual size_t TemporalSamples() const override;

View File

@ -597,7 +597,6 @@ public:
int var, samed, multid, samepost;
glm::length_t i, j, k, n;
size_t varCount = m_VariationList->Size();
Palette<T> palette;
static size_t xformDistrib[] =
{
2, 2, 2, 2,
@ -863,8 +862,8 @@ public:
for (i = 0; i < m_Renderer->FinalDimensions(); i++)
{
m_Hist[(p[0] * res / 256) +
(p[1] * res / 256) * res +
(p[2] * res / 256) * res * res]++;//A specific histogram index representing the sum of R,G,B values.
(p[1] * res / 256) * res +
(p[2] * res / 256) * res * res]++;//A specific histogram index representing the sum of R,G,B values.
p += m_Renderer->PixelSize();//Advance the pointer by 1 pixel.
}
@ -1366,7 +1365,7 @@ private:
unique_ptr<XaosIterator<T>> m_XaosIterator = make_unique<XaosIterator<T>>();
unique_ptr<Renderer<T, bucketT>> m_Renderer;
QTIsaac<ISAAC_SIZE, ISAAC_INT> m_Rand;
PaletteList<T> m_PaletteList;
PaletteList<float> m_PaletteList;
shared_ptr<VariationList<T>> m_VariationList;
};
}

View File

@ -53,6 +53,18 @@ static inline bool Contains(c& container, const T& val)
return std::find_if(container.begin(), container.end(), [&](const T & t) -> bool { return t == val; }) != container.end();
}
/// <summary>
/// Thin specialization for string because the compiler can't figure out the container template type
/// when passing string to the function above.
/// </summary>
/// <param name="str1">The string to call find() on</param>
/// <param name="str2">The string to search for</param>
/// <returns>True if str2 was present at least once, else false.</returns>
static bool Find(const string& str1, const string& str2)
{
return str1.find(str2) != string::npos;
}
/// <summary>
/// Thin wrapper around computing the total size of a vector.
/// </summary>
@ -284,16 +296,21 @@ static bool ReadFile(const char* filename, string& buf, bool nullTerminate = tru
{
try
{
ifstream ifs(filename, ios::binary | ios::ate);
auto pos = ifs.tellg();
buf.resize(pos + streampos(nullTerminate ? 1 : 0));
ifs.seekg(0, ios::beg);
ifs.read(&buf[0], pos);
ifstream ifs;
ifs.exceptions(ifstream::failbit);
ifs.open(filename, ios::binary | ios::ate);
if (nullTerminate)//Optionally NULL terminate if they want to treat it as a string.
buf[buf.size() - 1] = 0;
if (auto pos = ifs.tellg())//Ensure it exists and wasn't empty.
{
buf.resize(pos + streampos(nullTerminate ? 1 : 0));
ifs.seekg(0, ios::beg);
ifs.read(&buf[0], pos);
return true;
if (nullTerminate)//Optionally NULL terminate if they want to treat it as a string.
buf[buf.size() - 1] = 0;
return true;
}
}
catch (const std::exception& e)
{
@ -812,6 +829,18 @@ static inline T NormalizeDeg180(T angle)
return a;
}
/// <summary>
/// Determine whether the passed in string ends with the passed in suffix, case sensitive.
/// </summary>
/// <param name="str">The string to test</param>
/// <param name="suffix">The string to test for</param>
/// <returns>True if str ends with suffix, else false.</returns>
static bool EndsWith(const std::string& str, const std::string& suffix)
{
return str.size() >= suffix.size() &&
str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
}
/// <summary>
/// Return a lower case copy of a string.
/// </summary>
@ -867,21 +896,61 @@ static string Trim(const string& str, char ch = ' ')
}
/// <summary>
/// Return a copy of a file path string with the path portion removed.
/// Split a string into tokens and place them in a vector.
/// </summary>
/// <param name="str">The string to split</param>
/// <param name="del">The delimiter to split the string on. Note that only one character has to match, so this is a good way to use multiple delimiters</param>
/// <param name="removeEmpty">True to omit empty strings, else false to keep them.</param>
/// <returns>The vector containing the split tokens</returns>
static vector<std::string> Split(const string& str, const string& del, bool removeEmpty = false)
{
int current = 0;
int next = -1;
vector<string> vec;
do
{
current = next + 1;
next = int(str.find_first_of(del, current));
string ent(Trim(str.substr(current, next - current)));
if (!removeEmpty || ent.length() > 0)
vec.push_back(ent);
}
while (next != int(string::npos));
return vec;
}
/// <summary>
/// Return a copy of a file path string with the file portion removed.
/// </summary>
/// <param name="filename">The string to retrieve the path from</param>
/// <returns>The path portion of the string</returns>
static string GetPath(const string& filename)
{
const size_t lastSlash = filename.find_last_of("\\/");
if (std::string::npos != lastSlash)
return filename.substr(0, lastSlash + 1);
else
return filename;
}
/// <summary>
/// Return a copy of a file path string with the path portion removed.
/// </summary>
/// <param name="filename">The string to retrieve the filename from</param>
/// <returns>The filename portion of the string</returns>
static string GetFilename(const string& filename)
{
string s;
const size_t lastSlash = filename.find_last_of("\\/");
if (std::string::npos != lastSlash)
s = filename.substr(0, lastSlash + 1);
return filename.substr(lastSlash + 1, filename.size() - lastSlash);
else
s = "";
return s;
return filename;
}
/// <summary>

View File

@ -44,329 +44,334 @@ enum class eVariationAssignType : et
/// </summary>
enum class eVariationId : et
{
VAR_ARCH ,
VAR_AUGER ,
VAR_BARYCENTROID ,
VAR_BCIRCLE ,
VAR_BCOLLIDE ,
VAR_BENT ,
VAR_BENT2 ,
VAR_BIPOLAR ,
VAR_BISPLIT ,
VAR_BLADE ,
VAR_BLADE3D ,
VAR_BLOB ,
VAR_BLOB2 ,
VAR_BLOB3D ,
VAR_BLOCKY ,
VAR_BLUR ,
VAR_BLUR_CIRCLE ,
VAR_BLUR_HEART ,
VAR_BLUR_LINEAR ,
VAR_BLUR_PIXELIZE ,
VAR_BLUR_SQUARE ,
VAR_BLUR_ZOOM ,
VAR_BLUR3D ,
VAR_BMOD ,
VAR_BOARDERS ,
VAR_BOARDERS2 ,
VAR_BSWIRL ,
VAR_BTRANSFORM ,
VAR_BUBBLE ,
VAR_BUBBLE2 ,
VAR_BUBBLET3D ,
VAR_BUTTERFLY ,
VAR_BWRAPS ,
VAR_CARDIOID ,
VAR_CELL ,
VAR_CHECKS ,
VAR_CIRCLEBLUR ,
VAR_CIRCLECROP ,
VAR_CIRCLELINEAR ,
VAR_CIRCLERAND ,
VAR_CIRCLETRANS1 ,
VAR_CIRCLIZE ,
VAR_CIRCLIZE2 ,
VAR_CIRCUS ,
VAR_COLLIDEOSCOPE ,
VAR_CONIC ,
VAR_COS ,
VAR_COS_WRAP ,
VAR_COSH ,
VAR_COSHQ ,
VAR_COSINE ,
VAR_COSQ ,
VAR_COT ,
VAR_COTH ,
VAR_COTHQ ,
VAR_COTQ ,
VAR_CPOW ,
VAR_CPOW2 ,
VAR_CRACKLE ,
VAR_CRESCENTS ,
VAR_CROB ,
VAR_CROP ,
VAR_CROPN ,
VAR_CROSS ,
VAR_CSC ,
VAR_CSCH ,
VAR_CSCHQ ,
VAR_CSCQ ,
VAR_CUBIC3D ,
VAR_ARCH,
VAR_AUGER ,
VAR_BARYCENTROID,
VAR_BCIRCLE ,
VAR_BCOLLIDE ,
VAR_BENT ,
VAR_BENT2 ,
VAR_BIPOLAR ,
VAR_BISPLIT ,
VAR_BLADE ,
VAR_BLADE3D ,
VAR_BLOB ,
VAR_BLOB2 ,
VAR_BLOB3D ,
VAR_BLOCKY ,
VAR_BLUR ,
VAR_BLUR_CIRCLE ,
VAR_BLUR_HEART,
VAR_BLUR_LINEAR ,
VAR_BLUR_PIXELIZE,
VAR_BLUR_SQUARE ,
VAR_BLUR_ZOOM ,
VAR_BLUR3D ,
VAR_BMOD ,
VAR_BOARDERS ,
VAR_BOARDERS2 ,
VAR_BSWIRL ,
VAR_BTRANSFORM ,
VAR_BUBBLE ,
VAR_BUBBLE2 ,
VAR_BUBBLET3D ,
VAR_BUTTERFLY ,
VAR_BWRAPS ,
VAR_CARDIOID ,
VAR_CELL ,
VAR_CHECKS ,
VAR_CIRCLEBLUR ,
VAR_CIRCLECROP,
VAR_CIRCLELINEAR,
VAR_CIRCLERAND,
VAR_CIRCLESPLIT,
VAR_CIRCLETRANS1,
VAR_CIRCLIZE ,
VAR_CIRCLIZE2 ,
VAR_CIRCUS,
VAR_COLLIDEOSCOPE,
VAR_CONIC ,
VAR_COS ,
VAR_COS_WRAP ,
VAR_COSH ,
VAR_COSHQ,
VAR_COSINE ,
VAR_COSQ,
VAR_COT ,
VAR_COTH ,
VAR_COTHQ ,
VAR_COTQ ,
VAR_CPOW ,
VAR_CPOW2 ,
VAR_CRACKLE ,
VAR_CRESCENTS ,
VAR_CROB ,
VAR_CROP ,
VAR_CROPN ,
VAR_CROSS ,
VAR_CSC ,
VAR_CSCH ,
VAR_CSCHQ ,
VAR_CSCQ ,
VAR_CUBIC3D ,
VAR_CUBIC_LATTICE3D,
VAR_CURL ,
VAR_CURL3D ,
VAR_CURL_SP ,
VAR_CURVATURE ,
VAR_CURVE ,
VAR_CYLINDER ,
VAR_DELTA_A ,
VAR_DEPTH ,
VAR_DIAMOND ,
VAR_DISC ,
VAR_DISC2 ,
VAR_DISC3D ,
VAR_ECLIPSE ,
VAR_ECOLLIDE ,
VAR_EDISC ,
VAR_EJULIA ,
VAR_ELLIPTIC ,
VAR_EMOD ,
VAR_EMOTION ,
VAR_ENNEPERS ,
VAR_EPISPIRAL ,
VAR_EPUSH ,
VAR_ERF ,
VAR_EROTATE ,
VAR_ESCALE ,
VAR_ESCHER ,
VAR_ESTIQ ,
VAR_ESWIRL ,
VAR_EX ,
VAR_EXP ,
VAR_EXPO ,
VAR_EXPONENTIAL ,
VAR_EXTRUDE ,
VAR_EYEFISH ,
VAR_FALLOFF ,
VAR_FALLOFF2 ,
VAR_FALLOFF3 ,
VAR_FAN ,
VAR_FAN2 ,
VAR_FARBLUR ,
VAR_FDISC ,
VAR_FIBONACCI ,
VAR_FIBONACCI2 ,
VAR_FISHEYE ,
VAR_FLATTEN ,
VAR_FLIP_CIRCLE ,
VAR_FLIP_Y ,
VAR_FLOWER ,
VAR_FLUX ,
VAR_FOCI ,
VAR_FOCI3D ,
VAR_FOURTH ,
VAR_FUNNEL ,
VAR_GAMMA ,
VAR_GAUSSIAN_BLUR ,
VAR_GDOFFS ,
VAR_GLYNNIA ,
VAR_GLYNNSIM1 ,
VAR_GLYNNSIM2 ,
VAR_GLYNNSIM3 ,
VAR_GRIDOUT ,
VAR_HANDKERCHIEF ,
VAR_HEART ,
VAR_HEAT ,
VAR_HEMISPHERE ,
VAR_HEXAPLAY3D ,
VAR_HEXCROP ,
VAR_HEXES ,
VAR_HEXNIX3D ,
VAR_HO ,
VAR_HOLE ,
VAR_HORSESHOE ,
VAR_HYPERBOLIC ,
VAR_HYPERTILE ,
VAR_HYPERTILE1 ,
VAR_HYPERTILE2 ,
VAR_HYPERTILE3D ,
VAR_HYPERTILE3D1 ,
VAR_HYPERTILE3D2 ,
VAR_IDISC ,
VAR_INTERFERENCE2 ,
VAR_JAC_CN ,
VAR_JAC_DN ,
VAR_JAC_SN ,
VAR_JULIA ,
VAR_JULIA3D ,
VAR_JULIA3DQ ,
VAR_JULIA3DZ ,
VAR_JULIAC ,
VAR_JULIAN ,
VAR_JULIAN2 ,
VAR_JULIAN3DX ,
VAR_JULIANAB ,
VAR_JULIAQ ,
VAR_JULIASCOPE ,
VAR_KALEIDOSCOPE ,
VAR_LAZY_TRAVIS ,
VAR_LAZYSUSAN ,
VAR_LINE ,
VAR_LINEAR ,
VAR_LINEAR_T ,
VAR_LINEAR_T3D ,
VAR_CURL ,
VAR_CURL3D ,
VAR_CURL_SP,
VAR_CURVATURE,
VAR_CURVE ,
VAR_CYLINDER ,
VAR_CYLINDER2,
VAR_DELTA_A ,
VAR_DEPTH,
VAR_DIAMOND ,
VAR_DISC ,
VAR_DISC2 ,
VAR_DISC3D ,
VAR_ECLIPSE ,
VAR_ECOLLIDE ,
VAR_EDISC ,
VAR_EJULIA ,
VAR_ELLIPTIC ,
VAR_EMOD ,
VAR_EMOTION ,
VAR_ENNEPERS ,
VAR_EPISPIRAL ,
VAR_EPUSH ,
VAR_ERF ,
VAR_EROTATE ,
VAR_ESCALE ,
VAR_ESCHER ,
VAR_ESTIQ,
VAR_ESWIRL ,
VAR_EX ,
VAR_EXP ,
VAR_EXPO ,
VAR_EXPONENTIAL ,
VAR_EXTRUDE ,
VAR_EYEFISH ,
VAR_FALLOFF ,
VAR_FALLOFF2 ,
VAR_FALLOFF3 ,
VAR_FAN ,
VAR_FAN2 ,
VAR_FARBLUR,
VAR_FDISC ,
VAR_FIBONACCI ,
VAR_FIBONACCI2 ,
VAR_FISHEYE ,
VAR_FLATTEN ,
VAR_FLIP_CIRCLE ,
VAR_FLIP_Y ,
VAR_FLOWER ,
VAR_FLUX ,
VAR_FOCI ,
VAR_FOCI3D ,
VAR_FOURTH,
VAR_FUNNEL ,
VAR_GAMMA ,
VAR_GAUSSIAN_BLUR,
VAR_GDOFFS,
VAR_GLYNNIA ,
VAR_GLYNNSIM1 ,
VAR_GLYNNSIM2 ,
VAR_GLYNNSIM3 ,
VAR_GRIDOUT ,
VAR_HANDKERCHIEF,
VAR_HEART ,
VAR_HEAT,
VAR_HEMISPHERE ,
VAR_HEXAPLAY3D ,
VAR_HEXCROP ,
VAR_HEXES ,
VAR_HEXNIX3D ,
VAR_HO ,
VAR_HOLE ,
VAR_HORSESHOE ,
VAR_HYPERBOLIC ,
VAR_HYPERTILE ,
VAR_HYPERTILE1 ,
VAR_HYPERTILE2 ,
VAR_HYPERTILE3D ,
VAR_HYPERTILE3D1,
VAR_HYPERTILE3D2,
VAR_IDISC ,
VAR_INTERFERENCE2,
VAR_JAC_CN ,
VAR_JAC_DN ,
VAR_JAC_SN ,
VAR_JULIA ,
VAR_JULIA3D ,
VAR_JULIA3DQ ,
VAR_JULIA3DZ ,
VAR_JULIAC,
VAR_JULIAN ,
VAR_JULIAN2 ,
VAR_JULIAN3DX,
VAR_JULIANAB,
VAR_JULIAQ ,
VAR_JULIASCOPE ,
VAR_KALEIDOSCOPE,
VAR_LAZY_TRAVIS ,
VAR_LAZYSUSAN ,
VAR_LINE ,
VAR_LINEAR ,
VAR_LINEAR_T ,
VAR_LINEAR_T3D ,
//VAR_LINEAR_XZ ,
//VAR_LINEAR_YZ ,
VAR_LINEAR3D ,
VAR_LISSAJOUS ,
VAR_LOG ,
VAR_LOG_DB ,
VAR_LOQ ,
VAR_LOONIE ,
VAR_LOONIE2 ,
VAR_LOONIE3 ,
VAR_LOONIE3D ,
VAR_MASK ,
VAR_MCARPET ,
VAR_MIRROR_X ,
VAR_MIRROR_Y ,
VAR_MIRROR_Z ,
VAR_MOBIQ ,
VAR_MOBIUS ,
VAR_MOBIUS_STRIP ,
VAR_MOBIUSN ,
VAR_MODULUS ,
VAR_MURL ,
VAR_MURL2 ,
VAR_NBLUR ,
VAR_NGON ,
VAR_NOISE ,
VAR_NPOLAR ,
VAR_OCTAGON ,
VAR_OCTAPOL ,
VAR_ORTHO ,
VAR_OSCILLOSCOPE ,
VAR_OVOID ,
VAR_OVOID3D ,
VAR_PARABOLA ,
VAR_PDJ ,
VAR_PERSPECTIVE ,
VAR_PETAL ,
VAR_PHOENIX_JULIA ,
VAR_PIE ,
VAR_PIE3D ,
VAR_POINCARE ,
VAR_POINCARE3D ,
VAR_POLAR ,
VAR_POLAR2 ,
VAR_POLYNOMIAL ,
VAR_POPCORN ,
VAR_POPCORN2 ,
VAR_POPCORN23D ,
VAR_POW_BLOCK ,
VAR_POWER ,
VAR_PRESSURE_WAVE ,
VAR_PROSE3D ,
VAR_PSPHERE ,
VAR_Q_ODE ,
VAR_RADIAL_BLUR ,
VAR_RATIONAL3 ,
VAR_RAYS ,
VAR_RBLUR ,
VAR_RECTANGLES ,
VAR_RINGS ,
VAR_RINGS2 ,
VAR_RIPPLE ,
VAR_RIPPLED ,
VAR_ROTATE_X ,
VAR_ROTATE_Y ,
VAR_ROTATE_Z ,
VAR_ROUNDSPHER ,
VAR_ROUNDSPHER3D ,
VAR_SCRY ,
VAR_SCRY3D ,
VAR_SEC ,
VAR_SECANT2 ,
VAR_SECH ,
VAR_SECHQ ,
VAR_SECQ ,
VAR_SEPARATION ,
VAR_SHRED_RAD ,
VAR_SHRED_LIN ,
VAR_SIGMOID ,
VAR_SIN ,
VAR_SINEBLUR ,
VAR_SINH ,
VAR_SINHQ ,
VAR_SINQ ,
VAR_SINTRANGE ,
VAR_SINUS_GRID ,
VAR_SINUSOIDAL ,
VAR_SINUSOIDAL3D ,
VAR_LINEAR3D ,
VAR_LISSAJOUS ,
VAR_LOG ,
VAR_LOG_DB ,
VAR_LOQ ,
VAR_LOONIE ,
VAR_LOONIE2 ,
VAR_LOONIE3 ,
VAR_LOONIE3D ,
VAR_MASK ,
VAR_MCARPET ,
VAR_MIRROR_X,
VAR_MIRROR_Y,
VAR_MIRROR_Z,
VAR_MOBIQ,
VAR_MOBIUS ,
VAR_MOBIUS_STRIP,
VAR_MOBIUSN ,
VAR_MODULUS ,
VAR_MURL ,
VAR_MURL2 ,
VAR_NBLUR ,
VAR_NGON ,
VAR_NOISE ,
VAR_NPOLAR ,
VAR_OCTAGON ,
VAR_OCTAPOL ,
VAR_ORTHO ,
VAR_OSCILLOSCOPE,
VAR_OVOID ,
VAR_OVOID3D ,
VAR_PARABOLA ,
VAR_PDJ ,
VAR_PERSPECTIVE ,
VAR_PETAL ,
VAR_PHOENIX_JULIA,
VAR_PIE ,
VAR_PIE3D ,
VAR_POINCARE ,
VAR_POINCARE3D ,
VAR_POLAR ,
VAR_POLAR2 ,
VAR_POLYNOMIAL ,
VAR_POPCORN ,
VAR_POPCORN2 ,
VAR_POPCORN23D ,
VAR_POW_BLOCK ,
VAR_POWER ,
VAR_PRESSURE_WAVE,
VAR_PROSE3D ,
VAR_PSPHERE ,
VAR_Q_ODE,
VAR_RADIAL_BLUR ,
VAR_RATIONAL3 ,
VAR_RAYS ,
VAR_RBLUR,
VAR_RECTANGLES ,
VAR_RINGS ,
VAR_RINGS2 ,
VAR_RIPPLE ,
VAR_RIPPLED ,
VAR_ROTATE_X,
VAR_ROTATE_Y,
VAR_ROTATE_Z,
VAR_ROUNDSPHER ,
VAR_ROUNDSPHER3D,
VAR_SCRY ,
VAR_SCRY3D ,
VAR_SEC ,
VAR_SECANT2 ,
VAR_SECH ,
VAR_SECHQ,
VAR_SECQ,
VAR_SEPARATION ,
VAR_SHRED_RAD ,
VAR_SHRED_LIN ,
VAR_SIGMOID ,
VAR_SIN ,
VAR_SINEBLUR ,
VAR_SINH ,
VAR_SINHQ ,
VAR_SINQ ,
VAR_SINTRANGE,
VAR_SINUS_GRID ,
VAR_SINUSOIDAL ,
VAR_SINUSOIDAL3D,
//VAR_SMARTCROP ,
VAR_SPHERICAL ,
VAR_SPHERICAL3D ,
VAR_SPHERICALN ,
VAR_SPHERIVOID ,
VAR_SPHYP3D ,
VAR_SPIRAL ,
VAR_SPIRAL_WING ,
VAR_SPIROGRAPH ,
VAR_SPLIT ,
VAR_SPLIT_BRDR ,
VAR_SPLITS ,
VAR_SPLITS3D ,
VAR_SQUARE ,
VAR_SQUARE3D ,
VAR_SQUARIZE ,
VAR_SQUIRREL ,
VAR_SQUISH ,
VAR_SSCHECKS ,
VAR_STARBLUR ,
VAR_STRIPES ,
VAR_STWIN ,
VAR_SUPER_SHAPE ,
VAR_SUPER_SHAPE3D ,
VAR_SVF ,
VAR_SWIRL ,
VAR_SYNTH ,
VAR_TAN ,
VAR_TANCOS ,
VAR_TANGENT ,
VAR_TANH ,
VAR_TANHQ ,
VAR_TANQ ,
VAR_TARGET ,
VAR_TAURUS ,
VAR_TRADE ,
VAR_TRUCHET ,
VAR_TWINTRIAN ,
VAR_TWO_FACE ,
VAR_UNPOLAR ,
VAR_VORON ,
VAR_W ,
VAR_WAFFLE ,
VAR_WAVES ,
VAR_WAVES2 ,
VAR_WAVES23D ,
VAR_WAVES2B ,
VAR_WAVESN ,
VAR_WDISC ,
VAR_WEDGE ,
VAR_WEDGE_JULIA ,
VAR_WEDGE_SPH ,
VAR_WHORL ,
VAR_X ,
VAR_XERF ,
VAR_XHEART ,
VAR_XTRB ,
VAR_Y ,
VAR_Z ,
VAR_ZBLUR ,
VAR_ZCONE ,
VAR_ZSCALE ,
VAR_ZTRANSLATE ,
VAR_SPHERICAL ,
VAR_SPHERICAL3D ,
VAR_SPHERICALN ,
VAR_SPHERIVOID,
VAR_SPHYP3D ,
VAR_SPIRAL ,
VAR_SPIRAL_WING ,
VAR_SPIROGRAPH ,
VAR_SPLIT ,
VAR_SPLIT_BRDR,
VAR_SPLITS ,
VAR_SPLITS3D ,
VAR_SQUARE ,
VAR_SQUARE3D ,
VAR_SQUARIZE ,
VAR_SQUIRREL ,
VAR_SQUISH,
VAR_SSCHECKS ,
VAR_STARBLUR ,
VAR_STRIPES ,
VAR_STWIN ,
VAR_SUPER_SHAPE ,
VAR_SUPER_SHAPE3D,
VAR_SVF ,
VAR_SWIRL ,
VAR_SYNTH ,
VAR_TAN ,
VAR_TANCOS,
VAR_TANGENT ,
VAR_TANH ,
VAR_TANHQ ,
VAR_TANQ ,
VAR_TARGET ,
VAR_TAURUS ,
VAR_TILE_LOG,
VAR_TRADE ,
VAR_TRUCHET,
VAR_TRUCHET_FILL,
VAR_TWINTRIAN ,
VAR_TWO_FACE ,
VAR_UNPOLAR ,
VAR_VORON,
VAR_W ,
VAR_WAFFLE,
VAR_WAVES ,
VAR_WAVES2 ,
VAR_WAVES2_RADIAL,
VAR_WAVES23D ,
VAR_WAVES2B ,
VAR_WAVESN ,
VAR_WDISC ,
VAR_WEDGE ,
VAR_WEDGE_JULIA ,
VAR_WEDGE_SPH ,
VAR_WHORL ,
VAR_X ,
VAR_XERF ,
VAR_XHEART ,
VAR_XTRB ,
VAR_Y ,
VAR_Z ,
VAR_ZBLUR ,
VAR_ZCONE ,
VAR_ZSCALE ,
VAR_ZTRANSLATE,
VAR_PRE_ARCH,
VAR_PRE_AUGER,
@ -408,6 +413,7 @@ enum class eVariationId : et
VAR_PRE_CIRCLECROP,
VAR_PRE_CIRCLELINEAR,
VAR_PRE_CIRCLERAND,
VAR_PRE_CIRCLESPLIT,
VAR_PRE_CIRCLETRANS1,
VAR_PRE_CIRCLIZE,
VAR_PRE_CIRCLIZE2,
@ -444,6 +450,7 @@ enum class eVariationId : et
VAR_PRE_CURVATURE,
VAR_PRE_CURVE,
VAR_PRE_CYLINDER,
VAR_PRE_CYLINDER2,
VAR_PRE_DELTA_A,
VAR_PRE_DEPTH,
VAR_PRE_DIAMOND,
@ -663,8 +670,10 @@ enum class eVariationId : et
VAR_PRE_TANQ,
VAR_PRE_TARGET,
VAR_PRE_TAURUS,
VAR_PRE_TILE_LOG,
VAR_PRE_TRADE,
VAR_PRE_TRUCHET,
VAR_PRE_TRUCHET_FILL,
VAR_PRE_TWINTRIAN,
VAR_PRE_TWO_FACE,
VAR_PRE_UNPOLAR,
@ -673,6 +682,7 @@ enum class eVariationId : et
VAR_PRE_WAFFLE,
VAR_PRE_WAVES,
VAR_PRE_WAVES2,
VAR_PRE_WAVES2_RADIAL,
VAR_PRE_WAVES23D,
VAR_PRE_WAVES2B,
VAR_PRE_WAVESN,
@ -732,6 +742,7 @@ enum class eVariationId : et
VAR_POST_CIRCLECROP,
VAR_POST_CIRCLELINEAR,
VAR_POST_CIRCLERAND,
VAR_POST_CIRCLESPLIT,
VAR_POST_CIRCLETRANS1,
VAR_POST_CIRCLIZE,
VAR_POST_CIRCLIZE2,
@ -768,6 +779,7 @@ enum class eVariationId : et
VAR_POST_CURVATURE,
VAR_POST_CURVE,
VAR_POST_CYLINDER,
VAR_POST_CYLINDER2,
VAR_POST_DELTA_A,
VAR_POST_DEPTH,
VAR_POST_DIAMOND,
@ -987,8 +999,10 @@ enum class eVariationId : et
VAR_POST_TANQ,
VAR_POST_TARGET,
VAR_POST_TAURUS,
VAR_POST_TILE_LOG,
VAR_POST_TRADE,
VAR_POST_TRUCHET,
VAR_POST_TRUCHET_FILL,
VAR_POST_TWINTRIAN,
VAR_POST_TWO_FACE,
VAR_POST_UNPOLAR,
@ -997,6 +1011,7 @@ enum class eVariationId : et
VAR_POST_WAFFLE,
VAR_POST_WAVES,
VAR_POST_WAVES2,
VAR_POST_WAVES2_RADIAL,
VAR_POST_WAVES23D,
VAR_POST_WAVES2B,
VAR_POST_WAVESN,

View File

@ -344,6 +344,11 @@ VariationList<T>::VariationList()
ADDPREPOSTREGVAR(Gamma)
ADDPREPOSTREGVAR(PRose3D)
ADDPREPOSTREGVAR(LogDB)
ADDPREPOSTREGVAR(CircleSplit)
ADDPREPOSTREGVAR(Cylinder2)
ADDPREPOSTREGVAR(TileLog)
ADDPREPOSTREGVAR(TruchetFill)
ADDPREPOSTREGVAR(Waves2Radial)
//ADDPREPOSTREGVAR(LinearXZ)
//ADDPREPOSTREGVAR(LinearYZ)
//DC are special.

View File

@ -3940,7 +3940,7 @@ public:
virtual void Func(IteratorHelper<T>& helper, Point<T>& outPoint, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand) override
{
T expx = std::exp(helper.In.x) * T(0.5);
T expnx = T(0.25) / expx;
T expnx = T(0.25) / Zeps(expx);
T sn, cn, tmp;
sincos(helper.In.y, &sn, &cn);
tmp = m_Weight / Zeps(expx + expnx - cn);
@ -3955,7 +3955,7 @@ public:
intmax_t varIndex = IndexInXform();
ss << "\t{\n"
<< "\t\treal_t expx = exp(vIn.x) * (real_t)(0.5);\n"
<< "\t\treal_t expnx = (real_t)(0.25) / expx;\n"
<< "\t\treal_t expnx = (real_t)(0.25) / Zeps(expx);\n"
<< "\t\treal_t sn = sin(vIn.y);\n"
<< "\t\treal_t cn = cos(vIn.y);\n"
<< "\t\treal_t tmp = Zeps(expx + expnx - cn);\n"

View File

@ -1951,7 +1951,6 @@ class DeltaAVariation : public Variation<T>
{
public:
DeltaAVariation(T weight = 1.0) : Variation<T>("deltaa", eVariationId::VAR_DELTA_A, weight) { }
VARCOPY(DeltaAVariation)
virtual void Func(IteratorHelper<T>& helper, Point<T>& outPoint, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand) override
@ -4551,7 +4550,7 @@ protected:
m_Params.push_back(ParamWithName<T>(true, &m_S, prefix + "ripple_s"));
m_Params.push_back(ParamWithName<T>(true, &m_Is, prefix + "ripple_is"));
m_Params.push_back(ParamWithName<T>(true, &m_Vxp, prefix + "ripple_vxp"));
m_Params.push_back(ParamWithName<T>(true, &m_Pxa , prefix + "ripple_pxa"));
m_Params.push_back(ParamWithName<T>(true, &m_Pxa, prefix + "ripple_pxa"));
m_Params.push_back(ParamWithName<T>(true, &m_Pixa, prefix + "ripple_pixa"));
}
@ -5420,14 +5419,14 @@ public:
virtual void Func(IteratorHelper<T>& helper, Point<T>& outPoint, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand) override
{
T a = helper.m_PrecalcAtanyx;
int n = rand.Rand(uint(m_Spread));
int n = rand.Rand(m_SpreadUint);
if (a < 0)
n++;
a += M_2PI * n;
if (std::cos(a * m_InvSpread) < rand.Rand() * 2 / 0xFFFFFFFF - 1)//Rand max.
if (std::cos(a * m_InvSpread) < rand.Rand() * T(2) / 0xFFFFFFFF - T(1))//Rand max.
a -= m_FullSpread;
T lnr2 = std::log(helper.m_PrecalcSumSquares);
@ -5464,7 +5463,7 @@ public:
<< "\n"
<< "\t\ta += M_2PI * n;\n"
<< "\n"
<< "\t\tif (cos(a * " << invSpread << ") < MwcNext(mwc) * 2 / 0xFFFFFFFF - 1)\n"
<< "\t\tif (cos(a * " << invSpread << ") < MwcNext(mwc) * (real_t)2.0 / 0xFFFFFFFF - (real_t)1.0)\n"
<< "\t\t a -= " << fullSpread << ";\n"
<< "\n"
<< "\t\treal_t lnr2 = log(precalcSumSquares);\n"
@ -5487,6 +5486,7 @@ public:
m_HalfD = m_D / 2;
m_InvSpread = T(0.5) / m_Spread;
m_FullSpread = M_2PI * m_Spread;
m_SpreadUint = uint(m_Spread);
}
protected:
@ -5512,7 +5512,8 @@ private:
T m_A;
T m_Divisor;
T m_Spread;
T m_C;//Precalc.
uint m_SpreadUint;//Precalc.
T m_C;
T m_HalfC;
T m_D;
T m_HalfD;

View File

@ -1038,7 +1038,7 @@ public:
T expx = std::exp(helper.In.x) * T(0.5);
T expnx = T(0.25) / expx;
T boot = helper.In.z == 0 ? helper.m_PrecalcAtanyx : helper.In.z;
T tmp = m_Weight / (expx + expnx - (std::cos(helper.In.y) * std::cos(boot)));
T tmp = m_Weight / Zeps(expx + expnx - (std::cos(helper.In.y) * std::cos(boot)));
helper.Out.x = (expx - expnx) * tmp;
helper.Out.y = std::sin(helper.In.y) * tmp;
helper.Out.z = std::sin(boot) * tmp;
@ -1052,7 +1052,7 @@ public:
<< "\t\treal_t expx = exp(vIn.x) * (real_t)(0.5);\n"
<< "\t\treal_t expnx = (real_t)(0.25) / expx;\n"
<< "\t\treal_t boot = vIn.z == 0 ? precalcAtanyx : vIn.z;\n"
<< "\t\treal_t tmp = xform->m_VariationWeights[" << varIndex << "] / (expx + expnx - (cos(vIn.y) * cos(boot)));\n"
<< "\t\treal_t tmp = xform->m_VariationWeights[" << varIndex << "] / Zeps(expx + expnx - (cos(vIn.y) * cos(boot)));\n"
<< "\n"
<< "\t\tvOut.x = (expx - expnx) * tmp;\n"
<< "\t\tvOut.y = sin(vIn.y) * tmp;\n"
@ -1060,6 +1060,11 @@ public:
<< "\t}\n";
return ss.str();
}
virtual vector<string> OpenCLGlobalFuncNames() const override
{
return vector<string> { "Zeps" };
}
};
/// <summary>

View File

@ -4379,17 +4379,17 @@ protected:
{
string prefix = Prefix();
m_Params.clear();
m_Params.push_back(ParamWithName<T>(&m_Power , prefix + "smartcrop_power", 4)); //Original used a prefix of scrop_, which is incompatible with Ember's design.
m_Params.push_back(ParamWithName<T>(&m_Radius , prefix + "smartcrop_radius", 1));
m_Params.push_back(ParamWithName<T>(&m_Roundstr , prefix + "smartcrop_roundstr"));
m_Params.push_back(ParamWithName<T>(&m_Power, prefix + "smartcrop_power", 4)); //Original used a prefix of scrop_, which is incompatible with Ember's design.
m_Params.push_back(ParamWithName<T>(&m_Radius, prefix + "smartcrop_radius", 1));
m_Params.push_back(ParamWithName<T>(&m_Roundstr, prefix + "smartcrop_roundstr"));
m_Params.push_back(ParamWithName<T>(&m_Roundwidth, prefix + "smartcrop_roundwidth", 1));
m_Params.push_back(ParamWithName<T>(&m_Distortion, prefix + "smartcrop_distortion", 1));
m_Params.push_back(ParamWithName<T>(&m_Edge , prefix + "smartcrop_edge"));
m_Params.push_back(ParamWithName<T>(&m_Scatter , prefix + "smartcrop_scatter"));
m_Params.push_back(ParamWithName<T>(&m_Offset , prefix + "smartcrop_offset"));
m_Params.push_back(ParamWithName<T>(&m_Rotation , prefix + "smartcrop_rotation"));
m_Params.push_back(ParamWithName<T>(&m_Cropmode , prefix + "smartcrop_cropmode", 1, eParamType::INTEGER, -1, 2));
m_Params.push_back(ParamWithName<T>(&m_Static , prefix + "smartcrop_static", 1, eParamType::INTEGER, -1, 3));
m_Params.push_back(ParamWithName<T>(&m_Edge, prefix + "smartcrop_edge"));
m_Params.push_back(ParamWithName<T>(&m_Scatter, prefix + "smartcrop_scatter"));
m_Params.push_back(ParamWithName<T>(&m_Offset, prefix + "smartcrop_offset"));
m_Params.push_back(ParamWithName<T>(&m_Rotation, prefix + "smartcrop_rotation"));
m_Params.push_back(ParamWithName<T>(&m_Cropmode, prefix + "smartcrop_cropmode", 1, eParamType::INTEGER, -1, 2));
m_Params.push_back(ParamWithName<T>(&m_Static , prefix + "smartcrop_static", 1, eParamType::INTEGER, -1, 3));
m_Params.push_back(ParamWithName<T>(true, &m_Mode, prefix + "smartcrop_mode"));//Precalc.
m_Params.push_back(ParamWithName<T>(true, &m_Radial, prefix + "smartcrop_radial"));
m_Params.push_back(ParamWithName<T>(true, &m_WorkRadius, prefix + "smartcrop_work_radius"));
@ -4649,7 +4649,7 @@ public:
<< "\n"
<< "\t\tif (" << hypergon << " != 0)\n"
<< "\t\t{\n"
<< "\t\t temp1 = fmod(fabs(a), M_2PI / " << hypergonN << ") - M_PI / " << hypergonN << ";\n"
<< "\t\t temp1 = fmod(fabs(a), (real_t)M_2PI / " << hypergonN << ") - M_PI / " << hypergonN << ";\n"
<< "\t\t temp2 = Sqr(tan(temp1)) + 1;\n"
<< "\n"
<< "\t\t if (temp2 >= Sqr(" << hypergonD << "))\n"
@ -4664,7 +4664,7 @@ public:
<< "\n"
<< "\t\tif (" << star << "!= 0)\n"
<< "\t\t{\n"
<< "\t\t temp1 = tan(fabs(fmod(fabs(a), M_2PI / " << starN << ") - M_PI / " << starN << "));\n"
<< "\t\t temp1 = tan(fabs(fmod(fabs(a), (real_t)M_2PI / " << starN << ") - M_PI / " << starN << "));\n"
<< "\t\t total += " << star << " * sqrt(Sqr(" << tanStarSlope << ") * (1 + Sqr(temp1)) / Sqr(temp1 + " << tanStarSlope << "));\n"
<< "\t\t}\n"
<< "\n"
@ -4684,7 +4684,7 @@ public:
<< "\t\t{\n"
<< "\t\t if (" << hypergon << " != 0.0)\n"
<< "\t\t {\n"
<< "\t\t temp1 = fmod(fabs(a2), M_2PI / " << hypergonN << ") - M_PI / " << hypergonN << ";\n"
<< "\t\t temp1 = fmod(fabs(a2), (real_t)M_2PI / " << hypergonN << ") - M_PI / " << hypergonN << ";\n"
<< "\t\t temp2 = Sqr(tan(temp1)) + 1;\n"
<< "\n"
<< "\t\t if (temp2 >= Sqr(" << hypergonD << "))\n"
@ -4699,7 +4699,7 @@ public:
<< "\n"
<< "\t\t if (" << star << " != 0)\n"
<< "\t\t {\n"
<< "\t\t temp1 = tan(fabs(fmod(fabs(a2), M_2PI / " << starN << ") - M_PI / " << starN << "));\n"
<< "\t\t temp1 = tan(fabs(fmod(fabs(a2), (real_t)M_2PI / " << starN << ") - M_PI / " << starN << "));\n"
<< "\t\t total2 += " << star << " * sqrt(Sqr(" << tanStarSlope << ") * (1 + Sqr(temp1)) / Sqr(temp1 + " << tanStarSlope << "));\n"
<< "\t\t }\n"
<< "\n"

View File

@ -948,6 +948,513 @@ private:
T m_FixPe;
};
/// <summary>
/// circlesplit.
/// By tatasz.
/// http://fav.me/dapecsh
/// </summary>
template <typename T>
class CircleSplitVariation : public ParametricVariation<T>
{
public:
CircleSplitVariation(T weight = 1.0) : ParametricVariation<T>("circlesplit", eVariationId::VAR_CIRCLESPLIT, weight, true, true)
{
Init();
}
PARVARCOPY(CircleSplitVariation)
virtual void Func(IteratorHelper<T>& helper, Point<T>& outPoint, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand) override
{
T x1, y1;
if (helper.m_PrecalcSqrtSumSquares < (m_Radius - m_Split))
{
x1 = helper.In.x;
y1 = helper.In.y;
}
else
{
T a = std::atan2(helper.In.y, helper.In.x);
T len = helper.m_PrecalcSqrtSumSquares + m_Split;
x1 = std::cos(a) * len;
y1 = std::sin(a) * len;
}
helper.Out.x = m_Weight * x1;
helper.Out.y = m_Weight * y1;
}
virtual string OpenCLString() const override
{
ostringstream ss, ss2;
intmax_t i = 0, varIndex = IndexInXform();
ss2 << "_" << XformIndexInEmber() << "]";
string index = ss2.str();
string cs_radius = "parVars[" + ToUpper(m_Params[i++].Name()) + index;
string cs_split = "parVars[" + ToUpper(m_Params[i++].Name()) + index;
ss << "\t{\n"
<< "\t\treal_t x1, y1;\n"
<< "\n"
<< "\t\tif (precalcSqrtSumSquares < (" << cs_radius << " - " << cs_split << "))\n"
<< "\t\t{\n"
<< "\t\t\tx1 = vIn.x;\n"
<< "\t\t\ty1 = vIn.y;\n"
<< "\t\t}\n"
<< "\t\telse\n"
<< "\t\t{\n"
<< "\t\t\treal_t a = (real_t)atan2(vIn.y, vIn.x);\n"
<< "\t\t\treal_t len = precalcSqrtSumSquares + " << cs_split << ";\n"
<< "\t\t\tx1 = cos(a) * len;\n"
<< "\t\t\ty1 = sin(a) * len;\n"
<< "\t\t}"
<< "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * x1;\n"
<< "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * y1;\n"
<< "\t}\n";
return ss.str();
}
protected:
void Init()
{
string prefix = Prefix();
m_Params.clear();
m_Params.push_back(ParamWithName<T>(&m_Radius, prefix + "circlesplit_radius", 1));
m_Params.push_back(ParamWithName<T>(&m_Split, prefix + "circlesplit_split", T(0.5)));
}
private:
T m_Radius;
T m_Split;
};
/// <summary>
/// cylinder2.
/// By tatasz.
/// http://fav.me/dapecsh
/// </summary>
template <typename T>
class Cylinder2Variation : public Variation<T>
{
public:
Cylinder2Variation(T weight = 1.0) : Variation<T>("cylinder2", eVariationId::VAR_CYLINDER2, weight) { }
VARCOPY(Cylinder2Variation)
virtual void Func(IteratorHelper<T>& helper, Point<T>& outPoint, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand) override
{
helper.Out.x = m_Weight * (helper.In.x / Zeps(std::sqrt(SQR(helper.In.x) + 1)));
helper.Out.y = m_Weight * helper.In.y;
}
virtual string OpenCLString() const override
{
ostringstream ss;
intmax_t varIndex = IndexInXform();
ss << "\t{\n"
<< "\n"
<< "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (vIn.x / Zeps(sqrt(SQR(vIn.x) + (real_t)1.0)));\n"
<< "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n"
<< "\t}\n";
return ss.str();
}
virtual vector<string> OpenCLGlobalFuncNames() const override
{
return vector<string> { "Zeps" };
}
};
/// <summary>
/// tile_log.
/// By zy0rg.
/// http://zy0rg.deviantart.com
/// </summary>
template <typename T>
class TileLogVariation : public ParametricVariation<T>
{
public:
TileLogVariation(T weight = 1.0) : ParametricVariation<T>("tile_log", eVariationId::VAR_TILE_LOG, weight)
{
Init();
}
PARVARCOPY(TileLogVariation)
virtual void Func(IteratorHelper<T>& helper, Point<T>& outPoint, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand) override
{
T temp = Round(std::log(rand.Frand01<T>()) * (rand.Rand() & 1 ? m_Spread : -m_Spread));
helper.Out.x = m_Weight * (helper.In.x + temp);
helper.Out.y = m_Weight * helper.In.y;
}
virtual string OpenCLString() const override
{
ostringstream ss, ss2;
intmax_t i = 0, varIndex = IndexInXform();
ss2 << "_" << XformIndexInEmber() << "]";
string index = ss2.str();
string spread = "parVars[" + ToUpper(m_Params[i++].Name()) + index;
ss << "\t{\n"
<< "\t\treal_t temp = (real_t) (Round(log(MwcNext01(mwc)) * (MwcNext(mwc) & 1 ? " << spread << " : -" << spread << ")));\n"
<< "\n"
<< "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (vIn.x + temp);\n"
<< "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * vIn.y;\n"
<< "\t}\n";
return ss.str();
}
virtual vector<string> OpenCLGlobalFuncNames() const override
{
return vector<string> { "Round" };
}
protected:
void Init()
{
string prefix = Prefix();
m_Params.clear();
m_Params.push_back(ParamWithName<T>(&m_Spread, prefix + "tile_log_spread", 1));
}
private:
T m_Spread;
};
/// <summary>
/// Truchet_fill.
/// By tatasz.
/// http://fav.me/dapecsh
/// </summary>
template <typename T>
class TruchetFillVariation : public ParametricVariation<T>
{
public:
TruchetFillVariation(T weight = 1.0) : ParametricVariation<T>("Truchet_fill", eVariationId::VAR_TRUCHET_FILL, weight)
{
Init();
}
PARVARCOPY(TruchetFillVariation)
virtual void Func(IteratorHelper<T>& helper, Point<T>& outPoint, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand) override
{
T modbase = T(65535);
T multiplier = T(32747); //1103515245;
T offset = T(12345);
T x = helper.In.x * m_Scale;
T y = helper.In.y * m_Scale;
T intx = Round(x);
T inty = Round(y);
T r = x - intx;
if (r < 0)
x = 1 + r;
else
x = r;
r = y - inty;
if (r < 0)
y = 1 + r;
else
y = r;
T tiletype = 0;
if (m_Seed != 0)
{
if (m_Seed == 1)
{
tiletype = m_Seed;
}
else
{
T xrand = helper.In.x;
T yrand = helper.In.y;
xrand = Round(std::abs(xrand)) * m_Seed2;
yrand = Round(std::abs(yrand)) * m_Seed2;
T niter = xrand + yrand + (xrand * yrand);
T randint = (m_Seed + niter) * m_Seed2 * T(0.5);
randint = std::fmod((randint * multiplier + offset), modbase);
tiletype = std::fmod(randint, T(2.0));
}
}
T r0, r1;
if (tiletype < T(1))
{
//Slow drawmode
r0 = std::pow((std::pow(std::fabs(x), m_FinalExponent) + std::pow(std::fabs(y), m_FinalExponent)), m_OneOverEx);
r1 = std::pow((std::pow(std::fabs(x - T(1.0)), m_FinalExponent) + std::pow(std::fabs(y - 1), m_FinalExponent)), m_OneOverEx);
}
else
{
r0 = std::pow((std::pow(std::fabs(x - T(1.0)), m_FinalExponent) + std::pow(std::fabs(y), m_FinalExponent)), m_OneOverEx);
r1 = std::pow((std::pow(std::fabs(x), m_FinalExponent) + std::pow(std::fabs(y - T(1.0)), m_FinalExponent)), m_OneOverEx);
}
T x1, y1;
T r00 = fabs(r0 - T(0.5)) / m_Rmax;
if (r00 < 1)
{
x1 = 2 * (x + std::floor(helper.In.x));
y1 = 2 * (y + std::floor(helper.In.y));
}
else
{
x1 = 0;
y1 = 0;
}
T r11 = std::fabs(r1 - T(0.5)) / m_Rmax;
if (r11 < 1)
{
helper.Out.x = x1 + (2 * (x + std::floor(helper.In.x))) - helper.In.x;
helper.Out.y = y1 + (2 * (y + std::floor(helper.In.y))) - helper.In.y;
}
else
{
helper.Out.x = x1 - helper.In.x;
helper.Out.y = y1 - helper.In.y;
}
}
virtual string OpenCLString() const override
{
ostringstream ss, ss2;
intmax_t i = 0;
ss2 << "_" << XformIndexInEmber() << "]";
string index = ss2.str();
string exponent = "parVars[" + ToUpper(m_Params[i++].Name()) + index;
string arcWidth = "parVars[" + ToUpper(m_Params[i++].Name()) + index;
string seed = "parVars[" + ToUpper(m_Params[i++].Name()) + index;
string finalexponent = "parVars[" + ToUpper(m_Params[i++].Name()) + index;
string oneOverEx = "parVars[" + ToUpper(m_Params[i++].Name()) + index;
string width = "parVars[" + ToUpper(m_Params[i++].Name()) + index;
string seed2 = "parVars[" + ToUpper(m_Params[i++].Name()) + index;
string rmax = "parVars[" + ToUpper(m_Params[i++].Name()) + index;
string scale = "parVars[" + ToUpper(m_Params[i++].Name()) + index;
ss << "\t{\n"
<< "\t\treal_t modbase = 65535;\n"
<< "\t\treal_t multiplier = 32747;\n"
<< "\t\treal_t offset = 12345;\n"
<< "\n"
<< "\t\treal_t x = vIn.x * " << scale << ";\n"
<< "\t\treal_t y = vIn.y * " << scale << ";\n"
<< "\t\treal_t intx = Round(x);\n"
<< "\t\treal_t inty = Round(y);\n"
<< "\n"
<< "\t\treal_t r = x - intx;\n"
<< "\n"
<< "\t\tif (r < 0)\n"
<< "\t\t\tx = r + 1;\n"
<< "\t\telse\n"
<< "\t\t\tx = r;\n"
<< "\n"
<< "\t\tr = y - inty;\n"
<< "\n"
<< "\t\tif (r < 0)\n"
<< "\t\t\ty = r + 1;\n"
<< "\t\telse\n"
<< "\t\t\ty = r;\n"
<< "\n"
<< "\t\treal_t tiletype = 0;\n"
<< "\n"
<< "\t\tif (" << seed << " != 0)\n"
<< "\t\t{\n"
<< "\t\t\tif (" << seed << " == 1)\n"
<< "\t\t\t{\n"
<< "\t\t\t\ttiletype = " << seed << ";\n"
<< "\t\t\t}\n"
<< "\t\t\telse\n"
<< "\t\t\t{\n"
<< "\t\t\t\treal_t xrand = vIn.x;\n"
<< "\t\t\t\treal_t yrand = vIn.y;\n"
<< "\n"
<< "\t\t\t\txrand = Round(fabs(xrand)) * " << seed2 << ";\n"
<< "\t\t\t\tyrand = Round(fabs(yrand)) * " << seed2 << ";\n"
<< "\n"
<< "\t\t\t\treal_t niter = xrand + yrand + (xrand * yrand);\n"
<< "\t\t\t\treal_t randint = (" << seed << " + niter) * " << seed2 << " * ((real_t) 0.5);\n"
<< "\n"
<< "\t\t\t\trandint = fmod((randint * multiplier + offset), modbase);\n"
<< "\t\t\t\ttiletype = fmod(randint, 2);\n"
<< "\t\t\t}\n"
<< "\t\t}\n"
<< "\n"
<< "\t\treal_t r0, r1;\n"
<< "\n"
<< "\t\tif (tiletype < 1)\n"
<< "\t\t{\n"
<< "\t\t\tr0 = pow((pow(fabs(x), " << finalexponent << ") + pow(fabs(y), " << finalexponent << ")), " << oneOverEx << ");\n"
<< "\t\t\tr1 = pow((pow(fabs(x-1), " << finalexponent << ") + pow(fabs(y-1), " << finalexponent << ")), " << oneOverEx << ");\n"
<< "\t\t}\n"
<< "\t\telse\n"
<< "\t\t{\n"
<< "\t\t\tr0 = pow((pow(fabs(x-1), " << finalexponent << ") + pow(fabs(y), " << finalexponent << ")), " << oneOverEx << ");\n"
<< "\t\t\tr1 = pow((pow(fabs(x), " << finalexponent << ") + pow(fabs(y-1), " << finalexponent << ")), " << oneOverEx << ");\n"
<< "\t\t}\n"
<< "\n"
<< "\t\treal_t x1, y1;\n"
<< "\t\treal_t r00 = fabs(r0 - (real_t) 0.5) / " << rmax << ";\n"
<< "\n"
<< "\t\tif (r00 < 1.0)\n"
<< "\t\t{\n"
<< "\t\t\tx1 = 2 * (x + floor(vIn.x));\n"
<< "\t\t\ty1 = 2 * (y + floor(vIn.y));\n"
<< "\t\t}\n"
<< "\t\telse\n"
<< "\t\t{\n"
<< "\t\t\tx1 = 0;\n"
<< "\t\t\ty1 = 0;\n"
<< "\t\t}\n"
<< "\n"
<< "\t\treal_t r11 = fabs(r1 - (real_t) 0.5) / " << rmax << ";\n"
<< "\n"
<< "\t\tif (r11 < 1)\n"
<< "\t\t{\n"
<< "\t\t\tvOut.x = x1 + (2 * (x + floor(vIn.x))) - vIn.x;\n"
<< "\t\t\tvOut.y = y1 + (2 * (y + floor(vIn.y))) - vIn.y;\n"
<< "\t\t}\n"
<< "\t\telse\n"
<< "\t\t{\n"
<< "\t\t\tvOut.x = x1 - vIn.x;\n"
<< "\t\t\tvOut.y = y1 - vIn.y;\n"
<< "\t\t}\n"
<< "\n"
<< "\t\tvOut.z = " << DefaultZCl()
<< "\t}\n";
return ss.str();
}
virtual vector<string> OpenCLGlobalFuncNames() const override
{
return vector<string> { "Round" };
}
virtual void Precalc() override
{
m_FinalExponent = m_Exponent > T(2) ? T(2) : (m_Exponent < T(0.001) ? T(0.001) : m_Exponent);
m_OneOverEx = T(1) / m_FinalExponent;
m_Width = m_ArcWidth > T(1) ? T(1) : (m_ArcWidth < T(0.001) ? T(0.001) : m_ArcWidth);
m_Seed2 = std::sqrt(m_Seed * T(1.5)) / (m_Seed * T(0.5)) * T(0.25);
m_Rmax = T(0.5) * (std::pow(T(2), m_OneOverEx) - T(1)) * m_Width;
m_Scale = T(1) / m_Weight;
}
protected:
void Init()
{
string prefix = Prefix();
m_Params.clear();
m_Params.push_back(ParamWithName<T>(&m_Exponent, prefix + "Truchet_fill_exponent", 2, eParamType::REAL_CYCLIC, T(0.001), 2));
m_Params.push_back(ParamWithName<T>(&m_ArcWidth, prefix + "Truchet_fill_arc_width", T(0.5), eParamType::REAL_CYCLIC, T(0.001), 1));
m_Params.push_back(ParamWithName<T>(&m_Seed, prefix + "Truchet_fill_seed"));
m_Params.push_back(ParamWithName<T>(true, &m_FinalExponent, prefix + "Truchet_fill_final_exponent"));//Precalc
m_Params.push_back(ParamWithName<T>(true, &m_OneOverEx, prefix + "Truchet_fill_oneoverex"));
m_Params.push_back(ParamWithName<T>(true, &m_Width, prefix + "Truchet_fill_width"));
m_Params.push_back(ParamWithName<T>(true, &m_Seed2, prefix + "Truchet_fill_seed2"));
m_Params.push_back(ParamWithName<T>(true, &m_Rmax, prefix + "Truchet_fill_rmax"));
m_Params.push_back(ParamWithName<T>(true, &m_Scale, prefix + "Truchet_fill_scale"));
}
private:
T m_Exponent;
T m_ArcWidth;
T m_Seed;
T m_FinalExponent;//Precalc.
T m_OneOverEx;
T m_Width;
T m_Seed2;
T m_Rmax;
T m_Scale;
};
/// <summary>
/// waves2_radial.
/// By tatasz.
/// http://fav.me/dapecsh
/// </summary>
template <typename T>
class Waves2RadialVariation : public ParametricVariation<T>
{
public:
Waves2RadialVariation(T weight = 1.0) : ParametricVariation<T>("waves2_radial", eVariationId::VAR_WAVES2_RADIAL, weight, true, true)
{
Init();
}
PARVARCOPY(Waves2RadialVariation)
virtual void Func(IteratorHelper<T>& helper, Point<T>& outPoint, QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand) override
{
T x0 = helper.In.x;
T y0 = helper.In.y;
T dist = helper.m_PrecalcSqrtSumSquares;
T factor = (dist < m_Distance) ? (dist - m_Null) / Zeps(m_Distance - m_Null) : T(1);
factor = (dist < m_Null) ? T(0) : factor;
helper.Out.x = m_Weight * (x0 + factor * std::sin(y0 * m_Freqx) * m_Scalex);
helper.Out.y = m_Weight * (y0 + factor * std::sin(x0 * m_Freqy) * m_Scaley);
}
virtual string OpenCLString() const override
{
ostringstream ss, ss2;
intmax_t i = 0, varIndex = IndexInXform();
ss2 << "_" << XformIndexInEmber() << "]";
string index = ss2.str();
string freqX = "parVars[" + ToUpper(m_Params[i++].Name()) + index;
string scaleX = "parVars[" + ToUpper(m_Params[i++].Name()) + index;
string freqY = "parVars[" + ToUpper(m_Params[i++].Name()) + index;
string scaleY = "parVars[" + ToUpper(m_Params[i++].Name()) + index;
string nullVar = "parVars[" + ToUpper(m_Params[i++].Name()) + index;
string distance = "parVars[" + ToUpper(m_Params[i++].Name()) + index;
ss << "\t{\n"
<< "\t\treal_t x0 = vIn.x;\n"
<< "\t\treal_t y0 = vIn.y;\n"
<< "\n"
<< "\t\treal_t dist = precalcSqrtSumSquares;\n"
<< "\t\treal_t factor = (dist < " << distance << ") ? (dist - " << nullVar << ") / Zeps(" << distance << "-" << nullVar << ") : (real_t)(1.0);\n"
<< "\t\tfactor = (dist < " << nullVar << ") ? (real_t) 0.0 : factor;\n"
<< "\n"
<< "\t\tvOut.x = xform->m_VariationWeights[" << varIndex << "] * (x0 + factor * sin(y0 * " << freqX << ") * " << scaleX << ");\n"
<< "\t\tvOut.y = xform->m_VariationWeights[" << varIndex << "] * (y0 + factor * sin(x0 * " << freqY << ") * " << scaleY << ");\n"
<< "\t\tvOut.z = " << DefaultZCl()
<< "\t}\n";
return ss.str();
}
virtual vector<string> OpenCLGlobalFuncNames() const override
{
return vector<string> { "Zeps" };
}
protected:
void Init()
{
string prefix = Prefix();
m_Params.clear();
m_Params.push_back(ParamWithName<T>(&m_Freqx, prefix + "waves2_radial_freqx", 7));
m_Params.push_back(ParamWithName<T>(&m_Scalex, prefix + "waves2_radial_scalex", T(0.1)));
m_Params.push_back(ParamWithName<T>(&m_Freqy, prefix + "waves2_radial_freqy", 13));
m_Params.push_back(ParamWithName<T>(&m_Scaley, prefix + "waves2_radial_scaley", T(0.1)));
m_Params.push_back(ParamWithName<T>(&m_Null, prefix + "waves2_radial_null", 2));
m_Params.push_back(ParamWithName<T>(&m_Distance, prefix + "waves2_radial_distance", 10));
}
private:
T m_Freqx;
T m_Scalex;
T m_Freqy;
T m_Scaley;
T m_Null;
T m_Distance;
};
MAKEPREPOSTPARVAR(Splits3D, splits3D, SPLITS3D)
MAKEPREPOSTPARVAR(Waves2B, waves2b, WAVES2B)
MAKEPREPOSTPARVAR(JacCn, jac_cn, JAC_CN)
@ -957,4 +1464,9 @@ MAKEPREPOSTPARVAR(PressureWave, pressure_wave, PRESSURE_WAVE)
MAKEPREPOSTVAR(Gamma, gamma, GAMMA)
MAKEPREPOSTPARVAR(PRose3D, pRose3D, PROSE3D)
MAKEPREPOSTPARVAR(LogDB, log_db, LOG_DB)
MAKEPREPOSTPARVAR(CircleSplit, circlesplit, CIRCLESPLIT)
MAKEPREPOSTVAR(Cylinder2, cylinder2, CYLINDER2)
MAKEPREPOSTPARVAR(TileLog, tile_log, TILE_LOG)
MAKEPREPOSTPARVAR(TruchetFill, Truchet_fill, TRUCHET_FILL)
MAKEPREPOSTPARVAR(Waves2Radial, waves2_radial, WAVES2_RADIAL)
}

1456
Source/Ember/XmlToEmber.cpp Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff