diff --git a/Builds/MSVC/VS2013/Ember.vcxproj b/Builds/MSVC/VS2013/Ember.vcxproj
index 4016a78..25d86c3 100644
--- a/Builds/MSVC/VS2013/Ember.vcxproj
+++ b/Builds/MSVC/VS2013/Ember.vcxproj
@@ -271,6 +271,7 @@
+
diff --git a/Builds/MSVC/VS2013/Ember.vcxproj.filters b/Builds/MSVC/VS2013/Ember.vcxproj.filters
index 59e3bf5..ab751a1 100644
--- a/Builds/MSVC/VS2013/Ember.vcxproj.filters
+++ b/Builds/MSVC/VS2013/Ember.vcxproj.filters
@@ -113,6 +113,9 @@
Header Files
+
+ Header Files
+
diff --git a/Builds/QtCreator/Ember/Ember.pro b/Builds/QtCreator/Ember/Ember.pro
index 58cf2c0..f4da780 100644
--- a/Builds/QtCreator/Ember/Ember.pro
+++ b/Builds/QtCreator/Ember/Ember.pro
@@ -52,5 +52,6 @@ HEADERS += \
../../../Source/Ember/Variations05.h \
../../../Source/Ember/VariationsDC.h \
../../../Source/Ember/Xform.h \
- ../../../Source/Ember/XmlToEmber.h
+ ../../../Source/Ember/XmlToEmber.h \
+ ../../../Source/Ember/EmberMotion.h
diff --git a/Source/Ember/Ember.cpp b/Source/Ember/Ember.cpp
index 9684061..89009c6 100644
--- a/Source/Ember/Ember.cpp
+++ b/Source/Ember/Ember.cpp
@@ -24,6 +24,7 @@ template<> unique_ptr> QTIsaac; \
template EMBER_API class XaosIterator; \
template EMBER_API class Xform; \
+ template EMBER_API class MotionParam; \
+ template EMBER_API class EmberMotion; \
template EMBER_API class IteratorHelper; \
template EMBER_API class Variation; \
template EMBER_API class ParamWithName; \
diff --git a/Source/Ember/Ember.h b/Source/Ember/Ember.h
index da0726e..af394ae 100644
--- a/Source/Ember/Ember.h
+++ b/Source/Ember/Ember.h
@@ -5,6 +5,7 @@
#include "PaletteList.h"
#include "SpatialFilter.h"
#include "TemporalFilter.h"
+#include "EmberMotion.h"
///
/// Ember class.
@@ -183,6 +184,8 @@ public:
if (ember.m_Edits != nullptr)
m_Edits = xmlCopyDoc(ember.m_Edits, 1);
+ CopyVec(m_EmberMotionElements, ember.m_EmberMotionElements);
+
return *this;
}
@@ -472,6 +475,8 @@ public:
{
for (size_t i = 0; i < TotalXformCount(); i++)
GetTotalXform(i)->DeleteMotionElements();
+
+ m_EmberMotionElements.clear();
}
///
@@ -1285,6 +1290,83 @@ public:
point.m_Z -= m_CamZPos;
}
+#define APP_FMP(x) x += param.second * Interpolater::MotionFuncs(motion.m_MotionFunc, motion.m_MotionFreq * (blend + motion.m_MotionOffset))
+
+ ///
+ /// Update ember parameters based on stored motion elements
+ ///
+ /// The time percentage value which dictates how much of a percentage of 360 degrees it should be rotated and the time position for the motion elements
+ void ApplyFlameMotion(T blend)
+ {
+ for (size_t i = 0; i < m_EmberMotionElements.size(); ++i)
+ {
+ auto& motion = m_EmberMotionElements[i];
+
+ for (size_t j = 0; j < motion.m_MotionParams.size(); ++j)
+ {
+ auto& param = motion.m_MotionParams[j];
+
+ switch (param.first)
+ {
+ case FLAME_MOTION_ZOOM:
+ APP_FMP(m_Zoom);
+ break;
+ case FLAME_MOTION_ZPOS:
+ APP_FMP(m_CamZPos);
+ break;
+ case FLAME_MOTION_PERSPECTIVE:
+ APP_FMP(m_CamPerspective);
+ break;
+ case FLAME_MOTION_YAW:
+ APP_FMP(m_CamYaw);
+ break;
+ case FLAME_MOTION_PITCH:
+ APP_FMP(m_CamPitch);
+ break;
+ case FLAME_MOTION_DEPTH_BLUR:
+ APP_FMP(m_CamDepthBlur);
+ break;
+ case FLAME_MOTION_CENTER_X:
+ APP_FMP(m_CenterX);
+ break;
+ case FLAME_MOTION_CENTER_Y:
+ APP_FMP(m_CenterY);
+ break;
+ case FLAME_MOTION_ROTATE:
+ APP_FMP(m_Rotate);
+ break;
+ case FLAME_MOTION_HUE:
+ APP_FMP(m_Hue);
+ break;
+ case FLAME_MOTION_BRIGHTNESS:
+ APP_FMP(m_Brightness);
+ break;
+ case FLAME_MOTION_GAMMA:
+ APP_FMP(m_Gamma);
+ break;
+ case FLAME_MOTION_GAMMA_THRESH:
+ APP_FMP(m_GammaThresh);
+ break;
+ case FLAME_MOTION_HIGHLIGHT_POWER:
+ APP_FMP(m_HighlightPower);
+ break;
+ case FLAME_MOTION_BACKGROUND_R:
+ APP_FMP(m_Background.r);
+ break;
+ case FLAME_MOTION_BACKGROUND_G:
+ APP_FMP(m_Background.g);
+ break;
+ case FLAME_MOTION_BACKGROUND_B:
+ APP_FMP(m_Background.b);
+ break;
+ case FLAME_MOTION_VIBRANCY:
+ APP_FMP(m_Vibrancy);
+ break;
+ }
+ }
+ }
+ }
+
///
/// Clear this ember and set to either reasonable or unreasonable default values.
///
@@ -1683,6 +1765,9 @@ public:
//The 0-based position of this ember in the file it was contained in.
size_t m_Index;
+ //The list of motion elements for the top-level flame params
+ vector> m_EmberMotionElements;
+
private:
///
/// The type of scaling used when resizing.
diff --git a/Source/Ember/EmberDefines.h b/Source/Ember/EmberDefines.h
index 534f3a6..43107eb 100644
--- a/Source/Ember/EmberDefines.h
+++ b/Source/Ember/EmberDefines.h
@@ -114,10 +114,32 @@ enum eInterp : uint { EMBER_INTERP_LINEAR = 0, EMBER_INTERP_SMOOTH = 1 };
enum eAffineInterp : uint { INTERP_LINEAR = 0, INTERP_LOG = 1, INTERP_COMPAT = 2, INTERP_OLDER = 3 };
enum ePaletteMode : uint { PALETTE_STEP = 0, PALETTE_LINEAR = 1 };
enum ePaletteInterp : uint { INTERP_HSV = 0, INTERP_SWEEP = 1 };
-enum eMotion : uint { MOTION_SIN = 1, MOTION_TRIANGLE = 2, MOTION_HILL = 3 };
+enum eMotion : uint { MOTION_SIN = 1, MOTION_TRIANGLE = 2, MOTION_HILL = 3, MOTION_SAW = 4 };
enum eProcessAction : uint { NOTHING = 0, ACCUM_ONLY = 1, FILTER_AND_ACCUM = 2, KEEP_ITERATING = 3, FULL_RENDER = 4 };
enum eProcessState : uint { NONE = 0, ITER_STARTED = 1, ITER_DONE = 2, FILTER_DONE = 3, ACCUM_DONE = 4 };
enum eInteractiveFilter : uint { FILTER_LOG = 0, FILTER_DE = 1 };
enum eScaleType : uint { SCALE_NONE = 0, SCALE_WIDTH = 1, SCALE_HEIGHT = 2 };
enum eRenderStatus : uint { RENDER_OK = 0, RENDER_ERROR = 1, RENDER_ABORT = 2 };
+enum eEmberMotionParam : uint
+{
+ FLAME_MOTION_NONE = 0,
+ FLAME_MOTION_ZOOM = 1,
+ FLAME_MOTION_ZPOS = 2,
+ FLAME_MOTION_PERSPECTIVE = 3,
+ FLAME_MOTION_YAW = 4,
+ FLAME_MOTION_PITCH = 5,
+ FLAME_MOTION_DEPTH_BLUR = 6,
+ FLAME_MOTION_CENTER_X = 7,
+ FLAME_MOTION_CENTER_Y = 8,
+ FLAME_MOTION_ROTATE = 9,
+ FLAME_MOTION_HUE = 10,
+ FLAME_MOTION_BRIGHTNESS = 11,
+ FLAME_MOTION_GAMMA = 12,
+ FLAME_MOTION_GAMMA_THRESH = 13,
+ FLAME_MOTION_HIGHLIGHT_POWER = 14,
+ FLAME_MOTION_BACKGROUND_R = 15,
+ FLAME_MOTION_BACKGROUND_G = 16,
+ FLAME_MOTION_BACKGROUND_B = 17,
+ FLAME_MOTION_VIBRANCY = 18
+};
}
diff --git a/Source/Ember/EmberToXml.h b/Source/Ember/EmberToXml.h
index 9d6cbf1..4080b74 100644
--- a/Source/Ember/EmberToXml.h
+++ b/Source/Ember/EmberToXml.h
@@ -221,6 +221,9 @@ public:
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 << " \n";
@@ -488,6 +491,11 @@ private:
os << "motion_function=\"triangle\" ";
else if (xform.m_MotionFunc== MOTION_HILL)
os << "motion_function=\"hill\" ";
+ else if (xform.m_MotionFunc== MOTION_SAW)
+ os << "motion_function=\"saw\" ";
+
+ if (xform.m_MotionOffset != 0)
+ os << "motion_offset=\"" << xform.m_MotionOffset << "\" ";
}
else
{
@@ -717,6 +725,114 @@ private:
return os.str();
}
+ ///
+ /// Convert a FlameMotion element to an xml string
+ ///
+ /// The FlameMotion object to convert to XML
+ string ToString(const EmberMotion& motion)
+ {
+ ostringstream os;
+ os << " 0)
+ os << "motion_offset=\"" << motion.m_MotionOffset << "\" ";
+
+ os << "motion_func=";
+
+ switch (motion.m_MotionFunc)
+ {
+ case MOTION_SIN:
+ os << "\"sin\"";
+ break;
+ case MOTION_HILL:
+ os << "\"hill\"";
+ break;
+ case MOTION_TRIANGLE:
+ os << "\"triangle\"";
+ break;
+ case MOTION_SAW:
+ os << "\"saw\"";
+ break;
+ }
+
+ T r = 0.0;
+ T g = 0.0;
+ T b = 0.0;
+ T cx = 0.0;
+ T cy = 0.0;
+
+ for (int i = 0; i < motion.m_MotionParams.size(); ++i)
+ {
+ switch(motion.m_MotionParams[i].first)
+ {
+ case FLAME_MOTION_ZOOM:
+ os << " zoom=\"" << motion.m_MotionParams[i].second << "\"";
+ break;
+ case FLAME_MOTION_ZPOS:
+ os << " cam_zpos=\"" << motion.m_MotionParams[i].second << "\"";
+ break;
+ case FLAME_MOTION_PERSPECTIVE:
+ os << " cam_persp=\"" << motion.m_MotionParams[i].second << "\"";
+ break;
+ case FLAME_MOTION_YAW:
+ os << " cam_yaw=\"" << motion.m_MotionParams[i].second << "\"";
+ break;
+ case FLAME_MOTION_PITCH:
+ os << " cam_pitch=\"" << motion.m_MotionParams[i].second << "\"";
+ break;
+ case FLAME_MOTION_DEPTH_BLUR:
+ os << " cam_dof=\"" << motion.m_MotionParams[i].second << "\"";
+ break;
+ case FLAME_MOTION_CENTER_X:
+ cx = motion.m_MotionParams[i].second;
+ break;
+ case FLAME_MOTION_CENTER_Y:
+ cy = motion.m_MotionParams[i].second;
+ break;
+ case FLAME_MOTION_ROTATE:
+ os << " rotate=\"" << motion.m_MotionParams[i].second << "\"";
+ break;
+ case FLAME_MOTION_HUE:
+ os << " hue=\"" << motion.m_MotionParams[i].second << "\"";
+ break;
+ case FLAME_MOTION_BRIGHTNESS:
+ os << " brightness=\"" << motion.m_MotionParams[i].second << "\"";
+ break;
+ case FLAME_MOTION_GAMMA:
+ os << " gamma=\"" << motion.m_MotionParams[i].second << "\"";
+ break;
+ case FLAME_MOTION_GAMMA_THRESH:
+ os << " gamma_threshold=\"" << motion.m_MotionParams[i].second << "\"";
+ break;
+ case FLAME_MOTION_HIGHLIGHT_POWER:
+ os << " highlight_power=\"" << motion.m_MotionParams[i].second << "\"";
+ break;
+ case FLAME_MOTION_BACKGROUND_R:
+ r = motion.m_MotionParams[i].second;
+ break;
+ case FLAME_MOTION_BACKGROUND_G:
+ g = motion.m_MotionParams[i].second;
+ break;
+ case FLAME_MOTION_BACKGROUND_B:
+ b = motion.m_MotionParams[i].second;
+ break;
+ case FLAME_MOTION_VIBRANCY:
+ os << " vibrancy=\"" << motion.m_MotionParams[i].second << "\"";
+ 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)
diff --git a/Source/Ember/Interpolate.h b/Source/Ember/Interpolate.h
index 23b16d2..950ca86 100644
--- a/Source/Ember/Interpolate.h
+++ b/Source/Ember/Interpolate.h
@@ -883,10 +883,14 @@ public:
return fr;
}
- else//MOTION_HILL
+ else if (funcNum == MOTION_HILL)
{
return ((1 - cos(T(2.0) * T(M_PI) * timeVal)) * T(0.5));
}
+ else //MOTION_SAW
+ {
+ return (T(2.0) * fmod(timeVal - T(0.5), T(1.0)) - T(1.0));
+ }
}
/*
diff --git a/Source/Ember/Point.h b/Source/Ember/Point.h
index b157b6d..a60cd62 100644
--- a/Source/Ember/Point.h
+++ b/Source/Ember/Point.h
@@ -196,6 +196,10 @@ public:
///
/// Member-wise constructor.
///
+ /// The red value, either 0-1 or 0-255.
+ /// The green value, either 0-1 or 0-255.
+ /// The blue value, either 0-1 or 0-255.
+ /// The alpha value, either 0-1 or 0-255.
Color(T rr, T gg, T bb, T aa)
: v4T(rr, gg, bb, aa)
{
diff --git a/Source/Ember/SheepTools.h b/Source/Ember/SheepTools.h
index d1d8c58..07c3d2f 100644
--- a/Source/Ember/SheepTools.h
+++ b/Source/Ember/SheepTools.h
@@ -1010,11 +1010,11 @@ public:
if (!xform1->m_Motion.empty())
xform2->ApplyMotion(*xform1, blend);
-
- xform2->DeleteMotionElements();
}
+ rotated.ApplyFlameMotion(blend);
rotated.RotateAffines(-blend * 360);//Rotate the affines.
+ rotated.DeleteMotionElements();//Delete all motion elements from the looped ember, at the xform level and at the parent ember level.
}
///
diff --git a/Source/Ember/Xform.h b/Source/Ember/Xform.h
index 4e94319..f36edec 100644
--- a/Source/Ember/Xform.h
+++ b/Source/Ember/Xform.h
@@ -165,6 +165,7 @@ public:
m_Wind[1] = T(xform.m_Wind[1]);
m_MotionFreq = xform.m_MotionFreq;
m_MotionFunc = xform.m_MotionFunc;
+ m_MotionOffset = xform.m_MotionOffset;
ClearAndDeleteVariations();
@@ -234,6 +235,7 @@ public:
m_Wind[0] = 0;
m_Wind[1] = 0;
m_MotionFreq = 0;
+ m_MotionOffset = 0;
}
else
{
@@ -262,6 +264,7 @@ public:
m_Wind[0] = EMPTYFIELD;
m_Wind[1] = EMPTYFIELD;
m_MotionFreq = EMPTYFIELD;
+ m_MotionOffset = EMPTYFIELD;
}
m_MotionFunc = MOTION_SIN;
@@ -716,7 +719,7 @@ public:
do \
{ \
if (currentMot.x != EMPTYFIELD) \
- x += currentMot.x * Interpolater::MotionFuncs(func, freq * blend); \
+ x += currentMot.x * Interpolater::MotionFuncs(func, freq * (blend + offset)); \
} while (0)
///
@@ -731,8 +734,9 @@ public:
{
//Original only pulls these from the first motion xform which is a bug. Want to pull it from each one.
Xform& currentMot = xform.m_Motion[i];
- intmax_t freq = currentMot.m_MotionFreq;
+ T freq = currentMot.m_MotionFreq;
eMotion func = currentMot.m_MotionFunc;
+ T offset = currentMot.m_MotionOffset;
//Clamp these to the appropriate range after all are applied.
APPMOT(m_Weight);
@@ -753,13 +757,13 @@ public:
if (!var)//It wasn't present, so add it and set the weight.
{
Variation* newVar = motVar->Copy();
- newVar->m_Weight = motVar->m_Weight * Interpolater::MotionFuncs(func, freq * blend);
+ newVar->m_Weight = motVar->m_Weight * Interpolater::MotionFuncs(func, freq * (blend + offset));
AddVariation(newVar);
var = newVar;//Use this below for params.
}
else//It was present, so apply the motion func to the weight.
{
- var->m_Weight += motVar->m_Weight * Interpolater::MotionFuncs(func, freq * blend);
+ var->m_Weight += motVar->m_Weight * Interpolater::MotionFuncs(func, freq * (blend + offset));
}
//At this point, we've added if needed, or just applied the motion func to the weight.
@@ -773,7 +777,7 @@ public:
for (size_t k = 0; k < motParVar->ParamCount(); k++)
{
if (!motParams[k].IsPrecalc())
- *(params[k].Param()) += motParams[k].ParamVal() * Interpolater::MotionFuncs(func, freq * blend);
+ *(params[k].Param()) += motParams[k].ParamVal() * Interpolater::MotionFuncs(func, freq * (blend + offset));
}
}
}
@@ -1157,6 +1161,7 @@ public:
ss << "Wind: " << m_Wind[0] << ", " << m_Wind[1] << endl;
ss << "Motion Frequency: " << m_MotionFreq << endl;
ss << "Motion Func: " << m_MotionFunc << endl;
+ ss << "Motion Offset: " << m_MotionOffset << endl;
const_cast*>(this)->AllVarsFunc([&] (vector*>& variations, bool& keepGoing)
{
@@ -1232,7 +1237,8 @@ public:
T m_Animate;//Whether or not this xform rotates during animation. 0 means stationary, > 0 means rotate. Use T instead of bool so it can be interpolated.
T m_Wind[2];
eMotion m_MotionFunc;
- intmax_t m_MotionFreq;
+ T m_MotionFreq;
+ T m_MotionOffset;
vector> m_Motion;
string m_Name;
diff --git a/Source/Ember/XmlToEmber.h b/Source/Ember/XmlToEmber.h
index 07ea62f..9612876 100644
--- a/Source/Ember/XmlToEmber.h
+++ b/Source/Ember/XmlToEmber.h
@@ -253,8 +253,9 @@ public:
/// The buffer to parse
/// Full path and filename, optionally empty
/// The newly constructed embers based on what was parsed
+ /// True to use defaults if they are not present in the file, else false to use invalid values as placeholders to indicate the values were not present. Default: true.
/// True if there were no errors, else false.
- bool Parse(byte* buf, const char* filename, vector>& embers)
+ bool Parse(byte* buf, const char* filename, vector>& embers, bool useDefaults = true)
{
char* bn;
const char* xmlPtr;
@@ -286,7 +287,7 @@ public:
//Scan for nodes, starting with this node.
//t.Tic();
bn = basename(const_cast(filename));
- ScanForEmberNodes(rootnode, bn, embers);
+ ScanForEmberNodes(rootnode, bn, embers, useDefaults);
xmlFreeDoc(doc);
emberSize = embers.size();
//t.Toc("ScanForEmberNodes");
@@ -337,8 +338,9 @@ public:
///
/// Full path and filename
/// The newly constructed embers based on what was parsed
+ /// True to use defaults if they are not present in the file, else false to use invalid values as placeholders to indicate the values were not present. Default: true.
/// True if there were no errors, else false.
- bool Parse(const char* filename, vector>& embers)
+ bool Parse(const char* filename, vector>& embers, bool useDefaults = true)
{
const char* loc = __FUNCTION__;
string buf;
@@ -353,7 +355,7 @@ public:
if (ReadFile(filename, buf))
{
std::replace(buf.begin(), buf.end(), '&', '+');
- return Parse(reinterpret_cast(const_cast(buf.data())), filename, embers);
+ return Parse(reinterpret_cast(const_cast(buf.data())), filename, embers, useDefaults);
}
else
return false;
@@ -488,7 +490,8 @@ private:
/// The current node to parse
/// The full path and filename
/// The newly constructed embers based on what was parsed
- void ScanForEmberNodes(xmlNode* curNode, char* parentFile, vector>& embers)
+ /// True to use defaults if they are not present in the file, else false to use invalid values as placeholders to indicate the values were not present.
+ void ScanForEmberNodes(xmlNode* curNode, char* parentFile, vector>& embers, bool useDefaults)
{
bool parseEmberSuccess;
xmlNodePtr thisNode = nullptr;
@@ -504,6 +507,10 @@ private:
{
Ember currentEmber;//Place this inside here so its constructor is called each time.
+ //Useful for parsing templates when not every member should be set.
+ if (!useDefaults)
+ currentEmber.Clear(false);
+
parseEmberSuccess = ParseEmberElement(thisNode, currentEmber);
if (!parseEmberSuccess)
@@ -532,7 +539,7 @@ private:
else
{
//Check all of the children of this element.
- ScanForEmberNodes(thisNode->children, parentFile, embers);
+ ScanForEmberNodes(thisNode->children, parentFile, embers, useDefaults);
}
}
}
@@ -971,7 +978,7 @@ private:
if (theXform)
{
//Check for non-zero motion params.
- if (theXform->m_MotionFreq != 0)//Original checked for motion func being non-zero, but it was set to MOTION_SIN (1) in Xform::Init(), so don't check for 0 here.
+ if (fabs(theXform->m_MotionFreq) > 0.0)//Original checked for motion func being non-zero, but it was set to MOTION_SIN (1) in Xform::Init(), so don't check for 0 here.
{
m_ErrorReport.push_back(string(loc) + " : Motion parameters should not be specified in regular, non-motion xforms");
}
@@ -998,6 +1005,117 @@ private:
editNode = xmlCopyNode(childNode, 1);
xmlDocSetRootElement(currentEmber.m_Edits, editNode);
}
+ else if (!Compare(childNode->name, "flame_motion"))
+ {
+ EmberMotion motion;
+
+ att = childNode->properties;
+
+ if (att == nullptr)
+ {
+ m_ErrorReport.push_back(string(loc) + " : element has no attributes");
+ return false;
+ }
+
+ for (curAtt = att; curAtt; curAtt = curAtt->next)
+ {
+ attStr = reinterpret_cast(xmlGetProp(childNode, curAtt->name));
+
+ if (ParseAndAssignFloat(curAtt->name, attStr, "motion_frequency", motion.m_MotionFreq, ret)) { }
+ else if (ParseAndAssignFloat(curAtt->name, attStr, "motion_offset", motion.m_MotionOffset, ret)) { }
+ else if (!Compare(curAtt->name, "motion_function"))
+ {
+ string func(attStr);
+
+ if (func == "sin")
+ motion.m_MotionFunc = MOTION_SIN;
+ else if (func == "triangle")
+ motion.m_MotionFunc = MOTION_TRIANGLE;
+ else if (func == "hill")
+ motion.m_MotionFunc = MOTION_HILL;
+ else if (func == "saw")
+ motion.m_MotionFunc = MOTION_SAW;
+ else
+ {
+ m_ErrorReport.push_back(string(loc) + " : invalid flame motion function " + func);
+ return false;
+ }
+ }
+ else if (!Compare(curAtt->name, "zoom"))
+ ret = ret && AttToEmberMotionFloat(att, attStr, FLAME_MOTION_ZOOM, motion);
+ else if (!Compare(curAtt->name, "cam_zpos"))
+ ret = ret && AttToEmberMotionFloat(att, attStr, FLAME_MOTION_ZPOS, motion);
+ else if (!Compare(curAtt->name, "cam_persp"))
+ ret = ret && AttToEmberMotionFloat(att, attStr, FLAME_MOTION_PERSPECTIVE, motion);
+ else if (!Compare(curAtt->name, "cam_yaw"))
+ ret = ret && AttToEmberMotionFloat(att, attStr, FLAME_MOTION_YAW, motion);
+ else if (!Compare(curAtt->name, "cam_pitch"))
+ ret = ret && AttToEmberMotionFloat(att, attStr, FLAME_MOTION_PITCH, motion);
+ else if (!Compare(curAtt->name, "cam_dof"))
+ ret = ret && AttToEmberMotionFloat(att, attStr, FLAME_MOTION_DEPTH_BLUR, motion);
+ else if (!Compare(curAtt->name, "rotate"))
+ ret = ret && AttToEmberMotionFloat(att, attStr, FLAME_MOTION_ROTATE, motion);
+ else if (!Compare(curAtt->name, "hue"))
+ ret = ret && AttToEmberMotionFloat(att, attStr, FLAME_MOTION_HUE, motion);
+ else if (!Compare(curAtt->name, "brightness"))
+ ret = ret && AttToEmberMotionFloat(att, attStr, FLAME_MOTION_BRIGHTNESS, motion);
+ else if (!Compare(curAtt->name, "gamma"))
+ ret = ret && AttToEmberMotionFloat(att, attStr, FLAME_MOTION_GAMMA, motion);
+ else if (!Compare(curAtt->name, "gamma_threshold"))
+ ret = ret && AttToEmberMotionFloat(att, attStr, FLAME_MOTION_GAMMA_THRESH, motion);
+ else if (!Compare(curAtt->name, "highlight_power"))
+ ret = ret && AttToEmberMotionFloat(att, attStr, FLAME_MOTION_HIGHLIGHT_POWER, motion);
+ else if (!Compare(curAtt->name, "vibrancy"))
+ ret = ret && AttToEmberMotionFloat(att, attStr, FLAME_MOTION_VIBRANCY, motion);
+ else if (!Compare(curAtt->name, "background"))
+ {
+ double r, g, b;
+
+ if (sscanf_s(attStr, "%lf %lf %lf", &r, &g, &b) != 3)
+ {
+ m_ErrorReport.push_back(string(loc) + " : Invalid flame motion background attribute " + string(attStr));
+ xmlFree(attStr);
+ return false;
+ }
+
+ if (r != 0)
+ motion.m_MotionParams.push_back(MotionParam(FLAME_MOTION_BACKGROUND_R, T(r)));
+
+ if (g != 0)
+ motion.m_MotionParams.push_back(MotionParam(FLAME_MOTION_BACKGROUND_G, T(g)));
+
+ if (b != 0)
+ motion.m_MotionParams.push_back(MotionParam(FLAME_MOTION_BACKGROUND_B, T(b)));
+ }
+ else if (!Compare(curAtt->name, "center"))
+ {
+ double cx, cy;
+
+ if (sscanf_s(attStr, "%lf %lf", &cx, &cy) != 2)
+ {
+ m_ErrorReport.push_back(string(loc) + " : Invalid flame motion center attribute " + string(attStr));
+ xmlFree(attStr);
+ return false;
+ }
+
+ if (cx != 0)
+ motion.m_MotionParams.push_back(MotionParam(FLAME_MOTION_CENTER_X, T(cx)));
+
+ if (cy != 0)
+ motion.m_MotionParams.push_back(MotionParam(FLAME_MOTION_CENTER_Y, T(cy)));
+ }
+ else
+ {
+ m_ErrorReport.push_back(string(loc) + " : Unknown flame motion attribute " + string(CCX(curAtt->name)));
+ xmlFree(attStr);
+ return false;
+ }
+
+ xmlFree(attStr);
+ }
+
+ currentEmber.m_EmberMotionElements.push_back(motion);
+ }
}
//if (!newLinear)
@@ -1010,6 +1128,33 @@ private:
return m_ErrorReport.empty();
}
+ ///
+ /// Parse a floating point value from an xml attribute and add the value to a EmberMotion object
+ ///
+ /// The current attribute
+ /// The attribute value to parse
+ /// The flame motion parameter type
+ /// The flame motion element to add the parameter to
+ /// True if there were no errors, else false.
+ bool AttToEmberMotionFloat(xmlAttrPtr att, const char* attStr, eEmberMotionParam param, EmberMotion& motion)
+ {
+ const char* loc = __FUNCTION__;
+ bool r = false;
+ T val = 0.0;
+
+ if (Atof(attStr, val))
+ {
+ motion.m_MotionParams.push_back(MotionParam(param, val));
+ r = true;
+ }
+ else
+ {
+ m_ErrorReport.push_back(string(loc) + " : Failed to parse float value for flame motion attribute \"" + string(CCX(att->name)) + "\" : " + string(attStr));
+ }
+
+ return r;
+ }
+
///
/// Parse an xform element.
///
@@ -1047,9 +1192,8 @@ private:
else if (ParseAndAssignFloat(curAtt->name, attStr, "animate", xform.m_Animate, success)) { }
else if (ParseAndAssignFloat(curAtt->name, attStr, "opacity", xform.m_Opacity, success)) { }
else if (ParseAndAssignFloat(curAtt->name, attStr, "var_color", xform.m_DirectColor, success)) { }
-
- //Parse simple int reads.
- else if (ParseAndAssignInt(curAtt->name, attStr, "motion_frequency", xform.m_MotionFreq, success)) { }
+ else if (ParseAndAssignFloat(curAtt->name, attStr, "motion_frequency", xform.m_MotionFreq, success)) { }
+ else if (ParseAndAssignFloat(curAtt->name, attStr, "motion_offset", xform.m_MotionOffset, success)) { }
//Parse more complicated reads that have multiple possible values.
else if (!Compare(curAtt->name, "name"))
@@ -1073,6 +1217,8 @@ private:
xform.m_MotionFunc = MOTION_TRIANGLE;
else if (!_stricmp("hill", attStr))
xform.m_MotionFunc = MOTION_HILL;
+ else if (!_stricmp("saw", attStr))
+ xform.m_MotionFunc = MOTION_SAW;
else
{
xform.m_MotionFunc = MOTION_SIN;
diff --git a/Source/EmberCommon/EmberCommon.h b/Source/EmberCommon/EmberCommon.h
index 69926eb..a96f12a 100644
--- a/Source/EmberCommon/EmberCommon.h
+++ b/Source/EmberCommon/EmberCommon.h
@@ -81,11 +81,12 @@ private:
/// The parser to use
/// The full path and name of the file
/// Storage for the embers read from the file
+/// True to use defaults if they are not present in the file, else false to use invalid values as placeholders to indicate the values were not present. Default: true.
/// True if success, else false.
template
-static bool ParseEmberFile(XmlToEmber& parser, string filename, vector>& embers)
+static bool ParseEmberFile(XmlToEmber& parser, string filename, vector>& embers, bool useDefaults = true)
{
- if (!parser.Parse(filename.c_str(), embers))
+ if (!parser.Parse(filename.c_str(), embers, useDefaults))
{
cout << "Error parsing flame file " << filename << ", returning without executing." << endl;
return false;
diff --git a/Source/EmberCommon/EmberOptions.h b/Source/EmberCommon/EmberOptions.h
index 047af08..87e2790 100644
--- a/Source/EmberCommon/EmberOptions.h
+++ b/Source/EmberCommon/EmberOptions.h
@@ -76,7 +76,6 @@ enum eOptionIDs
OPT_SYMMETRY,
OPT_SHEEP_GEN,
OPT_SHEEP_ID,
- OPT_LOOPS,
OPT_REPEAT,
OPT_TRIES,
OPT_MAX_XFORMS,
@@ -93,6 +92,7 @@ enum eOptionIDs
OPT_OFFSETX,
OPT_OFFSETY,
OPT_USEMEM,
+ OPT_LOOPS,
OPT_ISAAC_SEED,//String value args.
OPT_IN,
@@ -357,7 +357,6 @@ public:
INITUINTOPTION(Frame, Eou(OPT_ANIM_GENOME, OPT_FRAME, _T("--frame"), 0, SO_REQ_SEP, "\t--frame= Synonym for \"time\".\n"));
INITUINTOPTION(Dtime, Eou(OPT_USE_ANIMATE, OPT_DTIME, _T("--dtime"), 1, SO_REQ_SEP, "\t--dtime= Time between frames [default: 1].\n"));
INITUINTOPTION(Frames, Eou(OPT_USE_GENOME, OPT_NFRAMES, _T("--nframes"), 20, SO_REQ_SEP, "\t--nframes= Number of frames for each stage of the animation [default: 20].\n"));
- INITUINTOPTION(Loops, Eou(OPT_USE_GENOME, OPT_LOOPS, _T("--loops"), 1, SO_REQ_SEP, "\t--loops= Number of times to rotate each control point in sequence [default: 1].\n"));
INITUINTOPTION(Repeat, Eou(OPT_USE_GENOME, OPT_REPEAT, _T("--repeat"), 1, SO_REQ_SEP, "\t--repeat= Number of new flames to create. Ignored if sequence, inter or rotate were specified [default: 1].\n"));
INITUINTOPTION(Tries, Eou(OPT_USE_GENOME, OPT_TRIES, _T("--tries"), 10, SO_REQ_SEP, "\t--tries= Number times to try creating a flame that meets the specified constraints. Ignored if sequence, inter or rotate were specified [default: 10].\n"));
INITUINTOPTION(MaxXforms, Eou(OPT_USE_GENOME, OPT_MAX_XFORMS, _T("--maxxforms"), UINT_MAX, SO_REQ_SEP, "\t--maxxforms= The maximum number of xforms allowed in the final output.\n"));
@@ -376,6 +375,7 @@ public:
INITDOUBLEOPTION(OffsetX, Eod(OPT_USE_GENOME, OPT_OFFSETX, _T("--offsetx"), 0.0, SO_REQ_SEP, "\t--offsetx= Amount to jitter each flame horizontally when applying genome tools [default: 0].\n"));
INITDOUBLEOPTION(OffsetY, Eod(OPT_USE_GENOME, OPT_OFFSETY, _T("--offsety"), 0.0, SO_REQ_SEP, "\t--offsety= Amount to jitter each flame vertically when applying genome tools [default: 0].\n"));
INITDOUBLEOPTION(UseMem, Eod(OPT_USE_RENDER, OPT_USEMEM, _T("--use_mem"), 0.0, SO_REQ_SEP, "\t--use_mem= Number of bytes of memory to use [default: max system memory].\n"));
+ INITDOUBLEOPTION(Loops, Eod(OPT_USE_GENOME, OPT_LOOPS, _T("--loops"), 1.0, SO_REQ_SEP, "\t--loops= Number of times to rotate each control point in sequence [default: 1].\n"));
//String.
INITSTRINGOPTION(IsaacSeed, Eos(OPT_USE_ALL, OPT_ISAAC_SEED, _T("--isaac_seed"), "", SO_REQ_SEP, "\t--isaac_seed= Character-based seed for the random number generator [default: random].\n"));
@@ -487,7 +487,6 @@ public:
PARSEUINTOPTION(OPT_TIME, Time);
PARSEUINTOPTION(OPT_DTIME, Dtime);
PARSEUINTOPTION(OPT_NFRAMES, Frames);
- PARSEUINTOPTION(OPT_LOOPS, Loops);
PARSEUINTOPTION(OPT_REPEAT, Repeat);
PARSEUINTOPTION(OPT_TRIES, Tries);
PARSEUINTOPTION(OPT_MAX_XFORMS, MaxXforms);
@@ -503,6 +502,7 @@ public:
PARSEDOUBLEOPTION(OPT_OFFSETX, OffsetX);
PARSEDOUBLEOPTION(OPT_OFFSETY, OffsetY);
PARSEDOUBLEOPTION(OPT_USEMEM, UseMem);
+ PARSEDOUBLEOPTION(OPT_LOOPS, Loops);
PARSESTRINGOPTION(OPT_ISAAC_SEED, IsaacSeed);//String args.
PARSESTRINGOPTION(OPT_IN, Input);
@@ -703,7 +703,6 @@ public:
EmberOptionEntry Time;
EmberOptionEntry Dtime;
EmberOptionEntry Frames;
- EmberOptionEntry Loops;
EmberOptionEntry Repeat;
EmberOptionEntry Tries;
EmberOptionEntry MaxXforms;
@@ -719,6 +718,7 @@ public:
EmberOptionEntry OffsetX;
EmberOptionEntry OffsetY;
EmberOptionEntry UseMem;
+ EmberOptionEntry Loops;
EmberOptionEntry IsaacSeed;//Value string.
EmberOptionEntry Input;
diff --git a/Source/EmberGenome/EmberGenome.cpp b/Source/EmberGenome/EmberGenome.cpp
index 4292f82..d62bc17 100644
--- a/Source/EmberGenome/EmberGenome.cpp
+++ b/Source/EmberGenome/EmberGenome.cpp
@@ -239,7 +239,7 @@ bool EmberGenome(EmberOptions& opt)
if (opt.TemplateFile() != "")
{
- if (!ParseEmberFile(parser, opt.TemplateFile(), templateEmbers))
+ if (!ParseEmberFile(parser, opt.TemplateFile(), templateEmbers, false))//Do not use defaults here to ensure only present fields get used when applying the template.
return false;
if (templateEmbers.size() > 1)
@@ -382,9 +382,9 @@ bool EmberGenome(EmberOptions& opt)
for (i = 0; i < embers.size(); i++)
{
- if (opt.Loops())
+ if (opt.Loops() > 0)
{
- for (frame = 0; frame < opt.Frames(); frame++)
+ for (frame = 0; frame < round(T(opt.Frames()) * opt.Loops()); frame++)
{
blend = T(frame) / T(opt.Frames());
tools.Spin(embers[i], pTemplate, result, frameCount++, blend);//Result is cleared and reassigned each time inside of Spin().
@@ -394,12 +394,23 @@ bool EmberGenome(EmberOptions& opt)
if (i < embers.size() - 1)
{
+ vector> interpEmbers;
+
+ interpEmbers.push_back(embers[i]);
+ interpEmbers.push_back(embers[i + 1]);
+
+ if (opt.Loops() > 0)
+ {
+ //We might have looped a non-integral number of times, so store the last result as our flame to interpolate from.
+ interpEmbers[i] = result;
+ }
+
for (frame = 0; frame < opt.Frames(); frame++)
{
seqFlag = (frame == 0 || frame == opt.Frames() - 1);
blend = frame / T(opt.Frames());
result.Clear();
- tools.SpinInter(&embers[i], pTemplate, result, frameCount++, seqFlag, blend);
+ tools.SpinInter(&interpEmbers[i], pTemplate, result, frameCount++, seqFlag, blend);
cout << emberToXml.ToString(result, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), false, opt.HexPalette());
}
}
diff --git a/Source/Fractorium/Fractorium.cpp b/Source/Fractorium/Fractorium.cpp
index b2d3b42..d47e530 100644
--- a/Source/Fractorium/Fractorium.cpp
+++ b/Source/Fractorium/Fractorium.cpp
@@ -103,6 +103,7 @@ Fractorium::Fractorium(QWidget* p)
InitXaosUI();
InitPaletteUI();
InitLibraryUI();
+ InitInfoUI();
InitMenusUI();
//This will init the controller and fill in the variations and palette tables with template specific instances
@@ -175,6 +176,7 @@ Fractorium::Fractorium(QWidget* p)
ui.XformPaletteRefTable->setStyleSheet("QTableWidget::item { padding: 0px; border: none; margin: 0px; }");
ui.PaletteAdjustTable->setStyleSheet("QTableWidget::item { padding: 1px; }");//Need this to avoid covering the top border pixel with the spinners.
ui.statusBar->setStyleSheet("QStatusBar QLabel { padding-left: 2px; padding-right: 2px; }");
+ ui.XaosTableView->setStyleSheet("QTableView { margin: 1px}");
//setStyleSheet("QGroupBox { border: 2px solid gray; border-radius: 3px; } ");
@@ -747,7 +749,11 @@ void Fractorium::SetTabOrders()
w = SetTabOrder(this, w, ui.PaletteFilterClearButton);
w = SetTabOrder(this, w, ui.PaletteListTable);
- w = SetTabOrder(this, ui.InfoBoundsGroupBox, ui.InfoBoundsFrame);//Info.
+
+ w = SetTabOrder(this, ui.SummaryTableWidget, ui.SummaryTreeWidget);//Info summary.
+
+ w = SetTabOrder(this, ui.InfoBoundsGroupBox, ui.InfoBoundsFrame);//Info bounds.
+
w = SetTabOrder(this, w, ui.InfoBoundsTable);
w = SetTabOrder(this, w, ui.InfoFileOpeningGroupBox);
w = SetTabOrder(this, w, ui.InfoFileOpeningTextEdit);
diff --git a/Source/Fractorium/Fractorium.h b/Source/Fractorium/Fractorium.h
index ac92fe1..9f5fc9f 100644
--- a/Source/Fractorium/Fractorium.h
+++ b/Source/Fractorium/Fractorium.h
@@ -142,9 +142,6 @@ public slots:
void OnActionAbout(bool checked);//Help.
//Toolbar.
- void OnSaveCurrentAsXmlButtonClicked(bool checked);
- void OnSaveEntireFileAsXmlButtonClicked(bool checked);
- void OnSaveCurrentToOpenedFileButtonClicked(bool checked);
//Library.
void OnEmberTreeItemChanged(QTreeWidgetItem* item, int col);
@@ -275,6 +272,10 @@ public slots:
void OnPaletteFilterClearButtonClicked(bool checked);
void OnPaletteHeaderSectionClicked(int col);
+ //Info.
+ void OnSummaryTableHeaderResized(int logicalIndex, int oldSize, int newSize);
+ void OnSummaryTreeHeaderSectionClicked(int logicalIndex);
+
//Rendering/progress.
void StartRenderTimer();
void IdleTimer();
@@ -311,6 +312,7 @@ private:
void InitXaosUI();
void InitPaletteUI();
void InitLibraryUI();
+ void InitInfoUI();
void SetTabOrders();
void ToggleTableRow(QTableView* table, int logicalIndex);
@@ -342,10 +344,13 @@ private:
//Palette.
void ResetPaletteControls();
void SetPaletteFileComboIndex(const string& filename);
+ void SetPaletteTableItem(QPixmap* pixmap, QTableWidget* table, QTableWidgetItem* item, int row, int col);
//Info.
+ void FillSummary();
void UpdateHistogramBounds();
void ErrorReportToQTextEdit(const vector& errors, QTextEdit* textEdit, bool clear = true);
+ void SetTableWidgetBackgroundColor();
//Rendering/progress.
bool CreateRendererFromOptions();
@@ -441,6 +446,14 @@ private:
SpinBox* m_PaletteBlurSpin;
SpinBox* m_PaletteFrequencySpin;
+ //Info.
+ QTableWidgetItem* m_InfoNameItem;
+ QTableWidgetItem* m_InfoPaletteItem;
+ QTableWidgetItem* m_Info3dItem;
+ QTableWidgetItem* m_InfoXaosItem;
+ QTableWidgetItem* m_InfoXformCountItem;
+ QTableWidgetItem* m_InfoFinalXformItem;
+
//Files.
QFileDialog* m_FileDialog;
QFileDialog* m_FolderDialog;
diff --git a/Source/Fractorium/Fractorium.ui b/Source/Fractorium/Fractorium.ui
index caf978c..d2af808 100644
--- a/Source/Fractorium/Fractorium.ui
+++ b/Source/Fractorium/Fractorium.ui
@@ -6,7 +6,7 @@
0
0
- 1418
+ 1442
926
@@ -74,8 +74,8 @@
0
0
- 1217
- 885
+ 1175
+ 861
@@ -1551,19 +1551,31 @@
4
- 5
+ 4
- 5
+ 4
- 5
+ 4
4
-
+
+ 4
+
+
+ QLayout::SetDefaultConstraint
+
+
+ 0
+
+
+ 0
+
-
@@ -1572,6 +1584,15 @@
Clear Xaos
+
+ false
+
+
+ false
+
+
+ false
+
-
@@ -1715,7 +1736,7 @@
240
- 200
+ 498
@@ -1741,202 +1762,6 @@
4
-
-
-
-
- -
-
-
-
- 2
- 0
-
-
-
-
- 0
- 67
-
-
-
-
- 16777215
- 67
-
-
-
- Qt::NoFocus
-
-
- QFrame::Panel
-
-
- QFrame::Plain
-
-
- Qt::ScrollBarAlwaysOff
-
-
- Qt::ScrollBarAlwaysOff
-
-
- false
-
-
- QAbstractItemView::NoEditTriggers
-
-
- false
-
-
- QAbstractItemView::NoSelection
-
-
- QAbstractItemView::ScrollPerPixel
-
-
- QAbstractItemView::ScrollPerPixel
-
-
- false
-
-
- false
-
-
- 3
-
-
- 4
-
-
- false
-
-
- 62
-
-
- 62
-
-
- true
-
-
- false
-
-
- 22
-
-
- false
-
-
- 22
-
-
-
-
-
-
-
-
- -
-
- Hue
-
-
- -
-
- Contrast
-
-
- -
-
- Saturation
-
-
- -
-
- Blur
-
-
- -
-
- Brightness
-
-
- -
-
-
-
-
- -
-
- Frequency
-
-
-
-
- -
-
-
- 4
-
-
- 0
-
-
- 0
-
-
-
-
-
-
- 0
- 24
-
-
-
-
- 16777215
- 24
-
-
-
- Select a random palette from the list
-
-
- Random Palette
-
-
-
- -
-
-
-
- 0
- 24
-
-
-
-
- 16777215
- 24
-
-
-
- Apply a random adjustment to the current palette
-
-
- Random Adjustment
-
-
- false
-
-
-
-
-
-
@@ -2140,6 +1965,202 @@
+ -
+
+
+ -
+
+
+
+ 2
+ 0
+
+
+
+
+ 0
+ 67
+
+
+
+
+ 16777215
+ 67
+
+
+
+ Qt::NoFocus
+
+
+ QFrame::Panel
+
+
+ QFrame::Plain
+
+
+ Qt::ScrollBarAlwaysOff
+
+
+ Qt::ScrollBarAlwaysOff
+
+
+ false
+
+
+ QAbstractItemView::NoEditTriggers
+
+
+ false
+
+
+ QAbstractItemView::NoSelection
+
+
+ QAbstractItemView::ScrollPerPixel
+
+
+ QAbstractItemView::ScrollPerPixel
+
+
+ false
+
+
+ false
+
+
+ 3
+
+
+ 4
+
+
+ false
+
+
+ 62
+
+
+ 62
+
+
+ true
+
+
+ false
+
+
+ 22
+
+
+ false
+
+
+ 22
+
+
+
+
+
+
+
+
+ -
+
+ Hue
+
+
+ -
+
+ Contrast
+
+
+ -
+
+ Saturation
+
+
+ -
+
+ Blur
+
+
+ -
+
+ Brightness
+
+
+ -
+
+
+
+
+ -
+
+ Frequency
+
+
+
+
+ -
+
+
+ 4
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 24
+
+
+
+
+ 16777215
+ 24
+
+
+
+ Select a random palette from the list
+
+
+ Random Palette
+
+
+
+ -
+
+
+
+ 0
+ 24
+
+
+
+
+ 16777215
+ 24
+
+
+
+ Apply a random adjustment to the current palette
+
+
+ Random Adjustment
+
+
+ false
+
+
+
+
+
@@ -3284,7 +3305,7 @@ SpinBox
0
0
- 237
+ 243
745
@@ -4941,7 +4962,7 @@ SpinBox
0
0
- 237
+ 243
680
@@ -4998,15 +5019,15 @@ SpinBox
- 1030
+ 1000
0
- 200
+ 301
881
- 80
+ 137
200
@@ -5019,7 +5040,7 @@ SpinBox
Info
-
+
6
@@ -5037,7 +5058,13 @@ SpinBox
4
-
-
+
+
+
+ 0
+ 0
+
+
QFrame::NoFrame
@@ -5047,18 +5074,27 @@ SpinBox
1
+
+ Qt::ScrollBarAsNeeded
+
true
-
+
0
0
- 190
+ 291
851
+
+
+ 0
+ 0
+
+
0
@@ -5076,451 +5112,820 @@ SpinBox
0
-
-
+
-
+
0
0
-
-
- 0
- 250
-
+
+ QTabWidget::Triangular
-
-
- 16777215
- 250
-
+
+ 0
-
-
- 0
- 0
-
-
-
- Qt::StrongFocus
-
-
- false
-
-
- Histogram Bounds
-
-
-
- 4
-
-
- 6
-
-
- 4
-
-
- 6
-
-
- 6
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- 0
- 173
-
-
-
-
- 16777215
- 173
-
-
-
- false
-
-
- QFrame::Box
-
-
- QFrame::Plain
-
-
-
- 2
+
+
+ Summary
+
+
+
+ 5
+
+
+ 5
+
+
+ 4
+
+
+ 5
+
+
+ 4
+
+
-
+
+
+
+ 0
+ 0
+
-
- 2
+
+
+ 0
+ 128
+
-
- 2
+
+
+ 16777215
+ 128
+
-
- 2
+
+ Qt::NoFocus
-
- 2
+
+ QFrame::StyledPanel
+
+ QFrame::Plain
+
+
+ 1
+
+
+ Qt::ScrollBarAlwaysOff
+
+
+ Qt::ScrollBarAlwaysOff
+
+
+ QAbstractItemView::NoEditTriggers
+
+
+ QAbstractItemView::NoSelection
+
+
+ QAbstractItemView::ScrollPerItem
+
+
+ true
+
+
+ false
+
+
+ false
+
+
+ false
+
+
+ 22
+
+
+ 22
+
+
+ true
+
+
+ true
+
+
+ false
+
+
+ 21
+
+
+ 21
+
+
+ false
+
+
+
+ Name
+
+
+
+
+ Palette
+
+
+
+
+ 3D Used
+
+
+
+
+ Xaos Used
+
+
+
+
+ Xform Count
+
+
+
+
+ Final Xform
+
+
+
+
+
+
+
-
-
-
- QFrame::Box
-
-
- QFrame::Plain
-
-
- UL:
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
-
-
+
+ Test Flame
+
- -
-
-
- QFrame::Box
-
-
- LR:
-
-
- Qt::AlignBottom|Qt::AlignRight|Qt::AlignTrailing
-
-
-
- -
-
-
- QFrame::Box
-
-
- UR:
-
-
- Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing
-
-
-
- -
-
-
- QFrame::Box
-
-
- W x H:
-
-
- Qt::AlignCenter
-
-
+
-
+
+
+
-
-
-
- QFrame::Box
+
+ Yes
+
+
+ -
+
+ Yes
+
+
+ -
+
+ 5
+
+
+ -
+
+ Yes
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ QFrame::Plain
+
+
+ Qt::ScrollBarAsNeeded
+
+
+ Qt::ScrollBarAsNeeded
+
+
+ QAbstractItemView::NoEditTriggers
+
+
+ false
+
+
+ QAbstractItemView::NoSelection
+
+
+ true
+
+
+ true
+
+
+ 2
+
+
+ true
+
+
+ false
+
+
+ 100
+
+
+ 30
+
+
+ false
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+ Xform 1
+
+
+ Xform 1 Name
+
+
-
+
+ Pre Affine
- LL:
+ 1, 2, 3, 4, 5, 6
-
- Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft
+
+ -
+
+ Post Affine
-
+
+ 7, 8, 9, 10, 11, 12
+
+
+ -
+
+ Variation 1
+
+
+ 1.234
+
+
-
+
+ Var1p1
+
+
+ 2.4456
+
+
+ -
+
+ Var1p2
+
+
+ 3.56
+
+
+
+ -
+
+ Variation 2
+
+
+ 2.2
+
+
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 46
-
-
-
-
- 16777215
- 46
-
-
-
-
- true
-
-
-
- Qt::NoFocus
-
-
-
-
-
- QFrame::Panel
-
-
- QFrame::Plain
-
-
- 1
-
-
- Qt::ScrollBarAlwaysOff
-
-
- Qt::ScrollBarAlwaysOff
-
-
- false
-
-
- QAbstractItemView::NoEditTriggers
-
-
- false
-
-
- false
-
-
- false
-
-
- QAbstractItemView::NoSelection
-
-
- QAbstractItemView::ScrollPerPixel
-
-
- QAbstractItemView::ScrollPerPixel
-
-
- true
-
-
- Qt::SolidLine
-
-
- false
-
-
- false
-
-
- 2
-
-
- false
-
-
- false
-
-
- 110
-
-
- false
-
-
- 27
-
-
- true
-
-
- false
-
-
- 22
-
-
- false
-
-
- 22
-
-
- false
-
-
-
- Gutter
-
-
-
-
- DE Box Dimensions
-
-
-
-
- Field
-
-
-
-
-
-
-
- -
-
- Gutter
-
-
- -
-
- 0
-
-
- -
-
- DE Box Dimensions
-
-
- -
-
- 0
-
-
-
- true
-
-
-
- AlignLeft|AlignVCenter
-
-
-
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 0
-
-
-
- Qt::StrongFocus
-
-
- File Opening
-
-
-
- 4
+
-
+
+
+
+
+
+
+
+ -
+
+ Xform 2
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+ Final Xform
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ 0
+
-
- 6
-
-
- 4
-
-
- 6
-
-
- 6
-
- -
-
-
-
- 0
- 0
-
-
-
- Qt::StrongFocus
-
-
- true
-
-
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Qt::StrongFocus
-
-
- Rendering
-
-
-
- 4
-
-
- 6
-
-
- 4
-
-
- 6
-
-
- 6
-
-
-
-
-
-
- 0
- 0
-
-
-
- Qt::StrongFocus
-
-
- true
-
-
- true
-
-
- Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse
-
-
-
-
+
+ Bounds
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 250
+
+
+
+
+ 16777215
+ 250
+
+
+
+
+ 0
+ 0
+
+
+
+ Qt::StrongFocus
+
+
+ false
+
+
+ Histogram Bounds
+
+
+
+ 4
+
+
+ 6
+
+
+ 4
+
+
+ 6
+
+
+ 6
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 173
+
+
+
+
+ 16777215
+ 173
+
+
+
+ false
+
+
+ QFrame::Box
+
+
+ QFrame::Plain
+
+
+
+ 2
+
+
+ 2
+
+
+ 2
+
+
+ 2
+
+
+ 2
+
+
-
+
+
+ QFrame::Box
+
+
+ QFrame::Plain
+
+
+ UL:
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
+
+
+
+ -
+
+
+ QFrame::Box
+
+
+ LR:
+
+
+ Qt::AlignBottom|Qt::AlignRight|Qt::AlignTrailing
+
+
+
+ -
+
+
+ QFrame::Box
+
+
+ UR:
+
+
+ Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing
+
+
+
+ -
+
+
+ QFrame::Box
+
+
+ W x H:
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ QFrame::Box
+
+
+ LL:
+
+
+ Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 46
+
+
+
+
+ 16777215
+ 46
+
+
+
+
+ true
+
+
+
+ Qt::NoFocus
+
+
+
+
+
+ QFrame::Panel
+
+
+ QFrame::Plain
+
+
+ 1
+
+
+ Qt::ScrollBarAlwaysOff
+
+
+ Qt::ScrollBarAlwaysOff
+
+
+ false
+
+
+ QAbstractItemView::NoEditTriggers
+
+
+ false
+
+
+ false
+
+
+ false
+
+
+ QAbstractItemView::NoSelection
+
+
+ QAbstractItemView::ScrollPerPixel
+
+
+ QAbstractItemView::ScrollPerPixel
+
+
+ true
+
+
+ Qt::SolidLine
+
+
+ false
+
+
+ false
+
+
+ 2
+
+
+ false
+
+
+ false
+
+
+ 110
+
+
+ false
+
+
+ 27
+
+
+ true
+
+
+ false
+
+
+ 22
+
+
+ false
+
+
+ 22
+
+
+ false
+
+
+
+ Gutter
+
+
+
+
+ DE Box Dimensions
+
+
+
+
+ Field
+
+
+
+
+
+
+
+ -
+
+ Gutter
+
+
+ -
+
+ 0
+
+
+ -
+
+ DE Box Dimensions
+
+
+ -
+
+ 0
+
+
+
+ true
+
+
+
+ AlignLeft|AlignVCenter
+
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+ Qt::StrongFocus
+
+
+ File Opening
+
+
+
+ 4
+
+
+ 6
+
+
+ 4
+
+
+ 6
+
+
+ 6
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Qt::StrongFocus
+
+
+ true
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Qt::StrongFocus
+
+
+ Rendering
+
+
+
+ 4
+
+
+ 6
+
+
+ 4
+
+
+ 6
+
+
+ 6
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Qt::StrongFocus
+
+
+ true
+
+
+ true
+
+
+ Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse
+
+
+
+
+
+
+
+
@@ -5537,7 +5942,7 @@ SpinBox
0
0
- 1418
+ 1442
21
@@ -5671,102 +6076,17 @@ SpinBox
4
- 6
+ 5
- 3
+ 5
- 6
+ 5
- 6
+ 4
- -
-
-
- 4
-
-
-
-
-
-
- 0
- 24
-
-
-
-
- 16777215
- 24
-
-
-
- Save the current flame as an xml file
-
-
-
-
-
-
- :/Fractorium/Icons/database-medium.png:/Fractorium/Icons/database-medium.png
-
-
-
- -
-
-
-
- 0
- 24
-
-
-
-
- 16777215
- 24
-
-
-
- Save all flames as a single xml file
-
-
-
-
-
-
- :/Fractorium/Icons/databases.png:/Fractorium/Icons/databases.png
-
-
-
- -
-
-
-
- 0
- 24
-
-
-
-
- 16777215
- 24
-
-
-
- <html><head/><body><p>Save the currently displayed flame back to the opened file in memory.</p><p>This overwrites the original flame but does not store the file back to disk.</p></body></html>
-
-
-
-
-
-
- :/Fractorium/Icons/document-hf-insert.png:/Fractorium/Icons/document-hf-insert.png
-
-
-
-
-
-
@@ -5786,8 +6106,8 @@ SpinBox
0
0
- 188
- 824
+ 256
+ 830
@@ -5843,6 +6163,42 @@ SpinBox
+
+
+ toolBar
+
+
+ true
+
+
+ true
+
+
+ TopToolBarArea
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -6194,9 +6550,6 @@ SpinBox
LibraryDockWidget
- SaveCurrentAsXmlButton
- SaveEntireFileAsXmlButton
- SaveCurrentToOpenedFileButton
diff --git a/Source/Fractorium/FractoriumCommon.h b/Source/Fractorium/FractoriumCommon.h
index ff8f80d..0e318ce 100644
--- a/Source/Fractorium/FractoriumCommon.h
+++ b/Source/Fractorium/FractoriumCommon.h
@@ -111,3 +111,78 @@ static bool Exists(const QString& s)
{
return s != "" && QDir(s).exists();
}
+
+///
+/// Convert a color to one that is displayable on any background.
+///
+/// The color to convert
+/// The converted color
+static QColor VisibleColor(const QColor& color)
+{
+ int threshold = 105;
+ int delta = (color.red() * 0.299) + //Magic numbers gotten from a Stack Overflow post.
+ (color.green() * 0.587) +
+ (color.blue() * 0.114);
+
+ QColor textColor = (255 - delta < threshold) ? QColor(0, 0, 0) : QColor(255, 255, 255);
+ return textColor;
+}
+
+///
+/// Determine whether an xform in an ember is linked to any other xform
+/// in the ember.
+///
+/// The ember which contains the xform
+/// The xform to inspect
+/// The index of the xform that the xform argument is linked to, else -1
+template
+static intmax_t IsXformLinked(Ember& ember, Xform* xform)
+{
+ auto count = ember.XformCount();
+ auto index = ember.GetXformIndex(xform);
+ intmax_t linked = -1;
+ size_t toOneCount = 0;
+ size_t toZeroCount = 0;
+ size_t toOneIndex = 0;
+ size_t fromOneCount = 0;
+ size_t fromZeroCount = 0;
+ size_t fromOneIndex = 0;
+
+ if (index >= 0)
+ {
+ for (auto i = 0; i < count; i++)
+ {
+ if (xform->Xaos(i) == 0)
+ toZeroCount++;
+ else if (xform->Xaos(i) == 1)
+ {
+ toOneIndex = i;
+ toOneCount++;
+ }
+ }
+
+ if ((toZeroCount == (count - 1)) && toOneCount == 1)
+ {
+ for (auto i = 0; i < count; i++)
+ {
+ if (auto fromXform = ember.GetXform(i))
+ {
+ if (fromXform->Xaos(toOneIndex) == 0)
+ fromZeroCount++;
+ else if (fromXform->Xaos(toOneIndex) == 1)
+ {
+ fromOneIndex = i;
+ fromOneCount++;
+ }
+ }
+ }
+
+ if ((fromZeroCount == (count - 1)) && fromOneCount == 1)
+ {
+ linked = toOneIndex;
+ }
+ }
+ }
+
+ return linked;
+}
\ No newline at end of file
diff --git a/Source/Fractorium/FractoriumEmberController.cpp b/Source/Fractorium/FractoriumEmberController.cpp
index 70a4dbe..76774bb 100644
--- a/Source/Fractorium/FractoriumEmberController.cpp
+++ b/Source/Fractorium/FractoriumEmberController.cpp
@@ -217,6 +217,7 @@ template
void FractoriumEmberController::Update(std::function func, bool updateRender, eProcessAction action)
{
func();
+ FillSummary();
if (updateRender)
UpdateRender(action);
@@ -294,6 +295,8 @@ void FractoriumEmberController::UpdateXform(std::function*)> fu
break;
}
+ FillSummary();
+
if (updateRender)
UpdateRender(action);
}
@@ -305,6 +308,7 @@ void FractoriumEmberController::UpdateXform(std::function*)> fu
/// Resets the rendering process.
///
/// The ember to set as the current
+/// If true, do not overwrite temporal samples, quality or supersample value, else overwrite.
template
template
void FractoriumEmberController::SetEmberPrivate(const Ember& ember, bool verbatim)
@@ -335,6 +339,7 @@ void FractoriumEmberController::SetEmberPrivate(const Ember& ember, bool v
m_GLController->ResetMouseState();
FillXforms();//Must do this first because the palette setup in FillParamTablesAndPalette() uses the xforms combo.
FillParamTablesAndPalette();
+ FillSummary();
//If a resize happened, this won't do anything because the new size is not reflected in the scroll area yet.
//However, it will have been taken care of in SyncSizes() in that case, so it's ok.
diff --git a/Source/Fractorium/FractoriumEmberController.h b/Source/Fractorium/FractoriumEmberController.h
index 4a6b5af..07f7207 100644
--- a/Source/Fractorium/FractoriumEmberController.h
+++ b/Source/Fractorium/FractoriumEmberController.h
@@ -178,7 +178,7 @@ public:
virtual void XformColorSpeedChanged(double d) { }
virtual void XformOpacityChanged(double d) { }
virtual void XformDirectColorChanged(double d) { }
- void SetPaletteRefTable(QPixmap* pixmap);
+ virtual QColor ColorIndexToQColor(double d) { return QColor(); }
//Xforms Variations.
virtual void SetupVariationTree() { }
@@ -199,11 +199,12 @@ public:
virtual bool FillPaletteTable(const string& s) { return false; }
virtual void ApplyPaletteToEmber() { }
virtual void PaletteAdjust() { }
- virtual QRgb GetQRgbFromPaletteIndex(uint i) { return QRgb(); }
virtual void PaletteCellClicked(int row, int col) { }
+ QImage& FinalPaletteImage() { return m_FinalPaletteImage; }
//Info.
-
+ virtual void FillSummary() { }
+
//Rendering/progress.
virtual bool Render() { return false; }
virtual bool CreateRenderer(eRendererType renderType, uint platform, uint device, bool shared = true) { return false; }
@@ -413,6 +414,7 @@ public:
virtual void XformColorSpeedChanged(double d) override;
virtual void XformOpacityChanged(double d) override;
virtual void XformDirectColorChanged(double d) override;
+ virtual QColor ColorIndexToQColor(double d) override;
void FillColorWithXform(Xform* xform);
//Xforms Variations.
@@ -433,10 +435,10 @@ public:
virtual bool FillPaletteTable(const string& s) override;
virtual void ApplyPaletteToEmber() override;
virtual void PaletteAdjust() override;
- virtual QRgb GetQRgbFromPaletteIndex(uint i) override { return QRgb(); }
virtual void PaletteCellClicked(int row, int col) override;
//Info.
+ virtual void FillSummary() override;
//Rendering/progress.
virtual bool Render() override;
diff --git a/Source/Fractorium/FractoriumInfo.cpp b/Source/Fractorium/FractoriumInfo.cpp
index c0d014a..6066c02 100644
--- a/Source/Fractorium/FractoriumInfo.cpp
+++ b/Source/Fractorium/FractoriumInfo.cpp
@@ -1,6 +1,219 @@
#include "FractoriumPch.h"
#include "Fractorium.h"
+///
+/// Initialize the info UI.
+///
+void Fractorium::InitInfoUI()
+{
+ auto treeHeader = ui.SummaryTreeWidget->header();
+ auto tableHeader = ui.SummaryTableWidget->horizontalHeader();
+
+ treeHeader->setVisible(true);
+ treeHeader->setSectionsClickable(true);
+ treeHeader->setSectionResizeMode(QHeaderView::ResizeToContents);
+ connect(treeHeader, SIGNAL(sectionClicked(int)), this, SLOT(OnSummaryTreeHeaderSectionClicked(int)), Qt::QueuedConnection);
+ connect(tableHeader, SIGNAL(sectionResized(int, int, int)), this, SLOT(OnSummaryTableHeaderResized(int, int, int)), Qt::QueuedConnection);
+ SetFixedTableHeader(ui.SummaryTableWidget->verticalHeader());
+
+ ui.SummaryTableWidget->setItem(0, 0, m_InfoNameItem = new QTableWidgetItem(""));
+ ui.SummaryTableWidget->setItem(1, 0, m_InfoPaletteItem = new QTableWidgetItem(""));
+ ui.SummaryTableWidget->setItem(2, 0, m_Info3dItem = new QTableWidgetItem(""));
+ ui.SummaryTableWidget->setItem(3, 0, m_InfoXaosItem = new QTableWidgetItem(""));
+ ui.SummaryTableWidget->setItem(4, 0, m_InfoXformCountItem = new QTableWidgetItem(""));
+ ui.SummaryTableWidget->setItem(5, 0, m_InfoFinalXformItem = new QTableWidgetItem(""));
+}
+
+///
+/// Called when the palette cell of the summary table is resized in response
+/// to a resizing of the Info dock.
+///
+/// Ignored
+/// Ignored
+/// Ignored
+void Fractorium::OnSummaryTableHeaderResized(int logicalIndex, int oldSize, int newSize)
+{
+ QPixmap pixmap = QPixmap::fromImage(m_Controller->FinalPaletteImage());//Create a QPixmap out of the QImage.
+ SetPaletteTableItem(&pixmap, ui.SummaryTableWidget, m_InfoPaletteItem, 1, 0);
+}
+
+///
+/// Expand or collapse the summary tree depending on the column index clicked.
+/// 0: collapse, 1: expand.
+///
+/// The column which was clicked
+void Fractorium::OnSummaryTreeHeaderSectionClicked(int logicalIndex)
+{
+ auto tree = ui.SummaryTreeWidget;
+
+ if (logicalIndex)
+ tree->expandAll();
+ else
+ tree->collapseAll();
+}
+
+///
+/// Fill the summary tree with values from the current ember.
+/// This is meant to be a rough summary by containing only the most relevant
+/// values from the ember.
+/// It's also meant to be used in a fire-and-forget way. Once the tree is filled
+/// individual nodes are never referenced again.
+/// The entire tree is cleared and refilled for every field change.
+/// This would seem inefficient, but it appears to update with no flicker.
+/// If this ever presents a problem in the future, revisit with a more
+/// intelligent design.
+///
+template
+void FractoriumEmberController::FillSummary()
+{
+ int p = 3;
+ int vp = 4;
+ int vlen = 7;
+ char pc = 'f';
+ size_t x = 0, total = m_Ember.TotalXformCount();
+ Xform* xform = nullptr;
+ QColor color;
+ auto table = m_Fractorium->ui.SummaryTableWidget;
+ auto tree = m_Fractorium->ui.SummaryTreeWidget;
+ QVariantList states;
+ QTreeWidgetItemIterator it(tree);
+
+ while (*it)
+ {
+ if (!(*it)->parent())//Top level only.
+ states += (*it)->isExpanded();
+
+ ++it;
+ }
+
+ tree->blockSignals(true);
+ tree->clear();
+ m_Fractorium->m_InfoNameItem->setText(m_Ember.m_Name.c_str());
+ m_Fractorium->m_Info3dItem->setText(m_Ember.ProjBits() ? "Yes" : "No");
+ m_Fractorium->m_InfoXaosItem->setText(m_Ember.XaosPresent() ? "Yes" : "No");
+ m_Fractorium->m_InfoXformCountItem->setText(QString::number(m_Ember.XformCount()));
+ m_Fractorium->m_InfoFinalXformItem->setText(m_Ember.UseFinalXform() ? "Yes" : "No");
+
+ QPixmap pixmap = QPixmap::fromImage(m_FinalPaletteImage);//Create a QPixmap out of the QImage.
+ QSize size(table->columnWidth(0), table->rowHeight(1) + 1);
+ m_Fractorium->m_InfoPaletteItem->setData(Qt::DecorationRole, pixmap.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
+
+ for (x = 0; x < total && (xform = m_Ember.GetTotalXform(x)); x++)
+ {
+ size_t i = 0;
+ QString as = "Pre";
+ auto item1 = new QTreeWidgetItem(tree);
+ intmax_t linkedIndex = IsXformLinked(m_Ember, xform);
+ QString linked = (linkedIndex != -1) ? (" Linked to " + QString::number(linkedIndex + 1)) : "";
+ auto index = m_Ember.GetXformIndex(xform);
+ m_Ember.CalcNormalizedWeights(m_NormalizedWeights);
+
+ if (!m_Ember.IsFinalXform(xform) && index != -1)
+ {
+ item1->setText(0, "Xform " +
+ QString::number(x + 1) +
+ " (" + QLocale::system().toString(xform->m_Weight, pc, p) + ") (" +
+ QLocale::system().toString(double(m_NormalizedWeights[index]), pc, p) + ") " +
+ linked);
+ }
+ else
+ item1->setText(0, "Final xform");
+
+ item1->setText(1, xform->m_Name.c_str());
+
+ auto affineItem = new QTreeWidgetItem(item1);
+ affineItem->setText(0, "Affine");
+
+ if (xform->m_Affine.IsZero())
+ as += " Empty";
+ else if (xform->m_Affine.IsID())
+ as += " ID";
+
+ if (xform->HasPost())
+ {
+ as += ", Post";
+
+ if (xform->m_Post.IsZero())
+ as += " Empty";//Don't need to check further for IsID() because post is not included if it's ID.
+ }
+
+ affineItem->setText(1, as);
+
+ auto colorIndexItem = new QTreeWidgetItem(item1);
+ colorIndexItem->setText(0, "Color index");
+ colorIndexItem->setText(1, QLocale::system().toString(xform->m_ColorX, pc, p));
+ color = ColorIndexToQColor(xform->m_ColorX);
+ color.setAlphaF(xform->m_Opacity);
+ colorIndexItem->setBackgroundColor(1, color);
+ colorIndexItem->setTextColor(1, VisibleColor(color));
+
+ auto colorSpeedItem = new QTreeWidgetItem(item1);
+ colorSpeedItem->setText(0, "Color speed");
+ colorSpeedItem->setText(1, QLocale::system().toString(xform->m_ColorSpeed, pc, p));
+
+ auto opacityItem = new QTreeWidgetItem(item1);
+ opacityItem->setText(0, "Opacity");
+ opacityItem->setText(1, QLocale::system().toString(xform->m_Opacity, pc, p));
+
+ auto dcItem = new QTreeWidgetItem(item1);
+ dcItem->setText(0, "Direct color");
+ dcItem->setText(1, QLocale::system().toString(xform->m_DirectColor, pc, p));
+
+ while (auto var = xform->GetVariation(i++))
+ {
+ auto vitem = new QTreeWidgetItem(item1);
+
+ vitem->setText(0, QString::fromStdString(var->Name()));
+ vitem->setText(1, QLocale::system().toString(var->m_Weight, pc, vp).rightJustified(vlen, ' '));
+
+ if (auto parVar = dynamic_cast*>(var))
+ {
+ auto params = parVar->Params();
+
+ for (auto j = 0; j < parVar->ParamCount(); j++)
+ {
+ if (!params[j].IsPrecalc())
+ {
+ auto pitem = new QTreeWidgetItem(vitem);
+
+ pitem->setText(0, params[j].Name().c_str());
+ pitem->setText(1, QLocale::system().toString(params[j].ParamVal(), pc, vp).rightJustified(vlen, ' '));
+ }
+ }
+ }
+ }
+
+ auto item2 = new QTreeWidgetItem(tree);//Empty item in between xforms.
+ }
+
+ QTreeWidgetItemIterator it2(tree);
+
+ if (!states.isEmpty())
+ {
+ while (*it2)
+ {
+ if (!(*it2)->parent())//Top level only.
+ {
+ if (!states.isEmpty())
+ (*it2)->setExpanded(states.takeFirst().toBool());
+ else
+ (*it2)->setExpanded(true);//Expand any remainder when going from lesser to greater number of xforms.
+ }
+
+ ++it2;
+ }
+ }
+ else
+ tree->expandAll();
+
+ tree->blockSignals(false);
+}
+
+void Fractorium::FillSummary()
+{
+ m_Controller->FillSummary();
+}
+
///
/// Update the histogram bounds display labels.
/// This shows the user the actual bounds of what's
@@ -54,3 +267,9 @@ void Fractorium::ErrorReportToQTextEdit(const vector& errors, QTextEdit*
for (auto& error : errors)
QMetaObject::invokeMethod(textEdit, "append", Qt::QueuedConnection, Q_ARG(const QString&, QString::fromStdString(error) + "\n"));
}
+
+template class FractoriumEmberController;
+
+#ifdef DO_DOUBLE
+template class FractoriumEmberController;
+#endif
diff --git a/Source/Fractorium/FractoriumLibrary.cpp b/Source/Fractorium/FractoriumLibrary.cpp
index d63e40b..b780518 100644
--- a/Source/Fractorium/FractoriumLibrary.cpp
+++ b/Source/Fractorium/FractoriumLibrary.cpp
@@ -226,8 +226,9 @@ void FractoriumEmberController::EmberTreeItemChanged(QTreeWidgetItem* item, i
m_Ember.m_Name = newName;
m_LastSaveCurrent = "";//Reset will force the dialog to show on the next save current since the user probably wants a different name.
}
-
+
tree->blockSignals(false);
+ FillSummary();
}
else if (QTreeWidgetItem* parentItem = dynamic_cast(item))
{
diff --git a/Source/Fractorium/FractoriumPalette.cpp b/Source/Fractorium/FractoriumPalette.cpp
index 00502c8..7623f46 100644
--- a/Source/Fractorium/FractoriumPalette.cpp
+++ b/Source/Fractorium/FractoriumPalette.cpp
@@ -192,7 +192,7 @@ void FractoriumEmberController::UpdateAdjustedPaletteGUI(Palette& palette)
memcpy(m_FinalPaletteImage.scanLine(0), v.data(), v.size() * sizeof(v[0]));//Memcpy the data in.
QPixmap pixmap = QPixmap::fromImage(m_FinalPaletteImage);//Create a QPixmap out of the QImage.
previewPaletteItem->setData(Qt::DecorationRole, pixmap.scaled(QSize(pixmap.width(), palettePreviewTable->rowHeight(0) + 2), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));//Set the pixmap on the palette tab.
- SetPaletteRefTable(&pixmap);//Set the palette ref table on the xforms | color tab.
+ m_Fractorium->SetPaletteTableItem(&pixmap, m_Fractorium->ui.XformPaletteRefTable, m_Fractorium->m_PaletteRefItem, 0, 0);//Set the palette ref table on the xforms | color tab.
QTableWidgetItem* previewNameItem = palettePreviewTable->item(0, 0);
previewNameItem->setText(paletteName);//Finally, set the name of the palette to be both the text and the tooltip.
diff --git a/Source/Fractorium/FractoriumParams.cpp b/Source/Fractorium/FractoriumParams.cpp
index f434f14..f24c856 100644
--- a/Source/Fractorium/FractoriumParams.cpp
+++ b/Source/Fractorium/FractoriumParams.cpp
@@ -183,13 +183,7 @@ void FractoriumEmberController::BackgroundChanged(const QColor& color)
QString g = ToString(color.green());
QString b = ToString(color.blue());
- int threshold = 105;
- int delta = (color.red() * 0.299) + //Magic numbers gotten from a Stack Overflow post.
- (color.green() * 0.587) +
- (color.blue() * 0.114);
-
- QColor textColor = (255 - delta < threshold) ? QColor(0, 0, 0) : QColor(255, 255, 255);
- colorTable->item(itemRow, 1)->setTextColor(textColor);
+ colorTable->item(itemRow, 1)->setTextColor(VisibleColor(color));
colorTable->item(itemRow, 1)->setText("rgb(" + r + ", " + g + ", " + b + ")");
//Color is 0-255, normalize to 0-1.
diff --git a/Source/Fractorium/FractoriumToolbar.cpp b/Source/Fractorium/FractoriumToolbar.cpp
index ad03925..8c74396 100644
--- a/Source/Fractorium/FractoriumToolbar.cpp
+++ b/Source/Fractorium/FractoriumToolbar.cpp
@@ -6,16 +6,5 @@
///
void Fractorium::InitToolbarUI()
{
- //These aren't menus but duplicate menu functionality in a pseudo-toolbar.
- connect(ui.SaveCurrentAsXmlButton, SIGNAL(clicked(bool)), this, SLOT(OnSaveCurrentAsXmlButtonClicked(bool)), Qt::QueuedConnection);
- connect(ui.SaveEntireFileAsXmlButton, SIGNAL(clicked(bool)), this, SLOT(OnSaveEntireFileAsXmlButtonClicked(bool)), Qt::QueuedConnection);
- connect(ui.SaveCurrentToOpenedFileButton, SIGNAL(clicked(bool)), this, SLOT(OnSaveCurrentToOpenedFileButtonClicked(bool)), Qt::QueuedConnection);
+ //Empty for the moment, all functionality is handled in the designer.
}
-
-///
-/// Wrappers around calls to menu items.
-///
-
-void Fractorium::OnSaveCurrentAsXmlButtonClicked(bool checked) { OnActionSaveCurrentAsXml(checked); }
-void Fractorium::OnSaveEntireFileAsXmlButtonClicked(bool checked) { OnActionSaveEntireFileAsXml(checked); }
-void Fractorium::OnSaveCurrentToOpenedFileButtonClicked(bool checked) { OnActionSaveCurrentToOpenedFile(checked); }
diff --git a/Source/Fractorium/FractoriumXformsAffine.cpp b/Source/Fractorium/FractoriumXformsAffine.cpp
index fcb40e3..4ffe748 100644
--- a/Source/Fractorium/FractoriumXformsAffine.cpp
+++ b/Source/Fractorium/FractoriumXformsAffine.cpp
@@ -150,8 +150,8 @@ void Fractorium::InitXformsAffineUI()
//Further, the size of the dock widget won't be properly adjusted until the xforms tab is shown.
//So show it here and it will be switched back in Fractorium's constructor.
- //ui.ParamsTabWidget->setCurrentIndex(2);
- //ui.DockWidget->update();
+ //ui.ParamsTabWidget->setCurrentIndex(2);
+ //ui.DockWidget->update();
#endif
//Placing pointers to the spin boxes in arrays makes them easier to access in various places.
diff --git a/Source/Fractorium/FractoriumXformsColor.cpp b/Source/Fractorium/FractoriumXformsColor.cpp
index 6c7474a..95efeaa 100644
--- a/Source/Fractorium/FractoriumXformsColor.cpp
+++ b/Source/Fractorium/FractoriumXformsColor.cpp
@@ -142,7 +142,9 @@ void Fractorium::OnSoloXformCheckBoxStateChanged(int state)
/// Ignored
void Fractorium::OnXformRefPaletteResized(int logicalIndex, int oldSize, int newSize)
{
- m_Controller->SetPaletteRefTable(nullptr);
+ QPixmap pixmap = QPixmap::fromImage(m_Controller->FinalPaletteImage());
+
+ SetPaletteTableItem(&pixmap, ui.XformPaletteRefTable, m_PaletteRefItem, 0, 0);
}
///
@@ -194,6 +196,25 @@ void Fractorium::OnCurvesRedRadioButtonToggled(bool checked) { if (checked) ui
void Fractorium::OnCurvesGreenRadioButtonToggled(bool checked) { if (checked) ui.CurvesView->SetTop(CurveIndex::GREEN); }
void Fractorium::OnCurvesBlueRadioButtonToggled(bool checked) { if (checked) ui.CurvesView->SetTop(CurveIndex::BLUE); }
+///
+/// Look up the passed in index in the current ember's palette
+/// and return the QColor equivalent.
+///
+/// The palette index to look up, 0-1.
+/// The palette color at the given index as a QColor
+template
+QColor FractoriumEmberController::ColorIndexToQColor(double d)
+{
+ v4T entry = m_Ember.m_Palette[Clamp(d * COLORMAP_LENGTH_MINUS_1, 0, m_Ember.m_Palette.Size())];
+
+ entry.r *= 255;
+ entry.g *= 255;
+ entry.b *= 255;
+
+ QRgb rgb = uint(entry.r) << 16 | uint(entry.g) << 8 | uint(entry.b);
+ return QColor::fromRgb(rgb);
+}
+
///
/// Set the selected xforms color index to the passed in value.
/// Set the color cell in the palette ref table.
@@ -207,14 +228,7 @@ void FractoriumEmberController::SetCurrentXformColorIndex(double d, bool upda
xform->m_ColorX = Clamp(d, 0, 1);
//Grab the current color from the index and assign it to the first cell of the first table.
- v4T entry = m_Ember.m_Palette[Clamp(d * COLORMAP_LENGTH_MINUS_1, 0, m_Ember.m_Palette.Size())];
-
- entry.r *= 255;
- entry.g *= 255;
- entry.b *= 255;
-
- QRgb rgb = uint(entry.r) << 16 | uint(entry.g) << 8 | uint(entry.b);
- m_Fractorium->ui.XformColorIndexTable->item(0, 0)->setBackgroundColor(QColor::fromRgb(rgb));
+ m_Fractorium->ui.XformColorIndexTable->item(0, 0)->setBackgroundColor(ColorIndexToQColor(xform->m_ColorX)/*QColor::fromRgb(rgb)*/);
}, eXformUpdate::UPDATE_SELECTED, updateRender);
}
@@ -257,22 +271,19 @@ void FractoriumEmberController::FillColorWithXform(Xform* xform)
}
///
-/// Set the palette reference table to the passed in pixmap
+/// Set the cell at the row and column in the passed in table to the passed in pixmap.
///
-/// The pixmap
-void FractoriumEmberControllerBase::SetPaletteRefTable(QPixmap* pixmap)
+/// The pixmap to assign
+/// The table whose cell will be filled with the image
+/// The QTableWidgetItem in the cell
+/// The row of the cell
+/// The column of the cell
+void Fractorium::SetPaletteTableItem(QPixmap* pixmap, QTableWidget* table, QTableWidgetItem* item, int row, int col)
{
- QSize size(m_Fractorium->ui.XformPaletteRefTable->columnWidth(0), m_Fractorium->ui.XformPaletteRefTable->rowHeight(0) + 1);
-
if (pixmap)
{
- m_Fractorium->m_PaletteRefItem->setData(Qt::DecorationRole, pixmap->scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
- }
- else if (!m_FinalPaletteImage.isNull())
- {
- QPixmap pixTemp = QPixmap::fromImage(m_FinalPaletteImage);
-
- m_Fractorium->m_PaletteRefItem->setData(Qt::DecorationRole, pixTemp.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
+ QSize size(table->columnWidth(col), table->rowHeight(row) + 1);
+ item->setData(Qt::DecorationRole, pixmap->scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
}
}
diff --git a/Source/Fractorium/FractoriumXformsVariations.cpp b/Source/Fractorium/FractoriumXformsVariations.cpp
index 85b7b09..02843e3 100644
--- a/Source/Fractorium/FractoriumXformsVariations.cpp
+++ b/Source/Fractorium/FractoriumXformsVariations.cpp
@@ -150,6 +150,7 @@ void FractoriumEmberController::VariationSpinBoxValueChanged(double d)//Would
{
if (xformParVar->SetParamVal(sender->ParamName().c_str(), d))
{
+ FillSummary();
UpdateRender();
}
}
@@ -203,6 +204,7 @@ void FractoriumEmberController::VariationSpinBoxValueChanged(double d)//Would
}
}
+ FillSummary();
UpdateRender();
}
}
diff --git a/Source/Fractorium/OptionsDialog.ui b/Source/Fractorium/OptionsDialog.ui
index 9c1a92a..a11eae5 100644
--- a/Source/Fractorium/OptionsDialog.ui
+++ b/Source/Fractorium/OptionsDialog.ui
@@ -7,7 +7,7 @@
0
0
300
- 347
+ 368
@@ -19,13 +19,13 @@
300
- 347
+ 368
300
- 347
+ 368