1.0.0.2 12/05/2016

--User changes
 -Add many tooltips to help clarify functionality.
 -Select multiple flames in library for del/move. Still only one allowed to be set as the current.
 -Show checkbox for current flame. Remember this is not necessarily what's selected.
 -User can now drag a square to select xforms, which keeps in sync with checkboxes.
 -Remove --nframes from command line. Replace with new params: --loopframes, --interpframes, --interploops.
 -Add two new options to EmberGenome: --cwloops --cwinterploops to specify whether rotation should go clockwise instead of the default counter clockwise.
 -Add these to Fractorium as checkboxes.
 -Apply All now also works for toggling animate flag on xforms.
 -Options dialog now allows user to set whether double click toggles spinners, or right click does.

--Bug fixes
 -Selecting final and non-final xforms, and then dragging the non-final did not drag the final with it.
 -Selecting all xforms when a final was present, then deleting crashed the program.
 -Remove support for ppm files in the command line programs, it's an outdated format.
 -Switching between SP and DP kept reapplying the palette adjustments.

--Code changes
 -Move build system to Visual Studio 2015 and Qt 5.6.
 -SSE used during addition of points to the histogram.
 -Remove last remnants of old flam3 C code and replace with C++.
 -Remove unused code involving tbb::task_group.
 -Make settings object a global shared_ptr singleton, so it doesn't have to be passed around.
This commit is contained in:
Person
2016-12-05 19:04:33 -08:00
parent 53ec438a25
commit 5cdfe0b6b9
83 changed files with 4892 additions and 1156 deletions

View File

@ -18,9 +18,6 @@
#define _stat stat
#define _fstat fstat
#define _stricmp strcmp
#define sscanf_s sscanf
#define sprintf_s snprintf
#define snprintf_s snprintf
typedef int errno_t;
#endif
@ -40,7 +37,7 @@ static void sincos(float x, float* s, float* c)
namespace EmberNs
{
#define EMBER_VERSION "1.0.0.1"
#define EMBER_VERSION "1.0.0.2"
#define EPS6 T(1e-6)
#define EPS std::numeric_limits<T>::epsilon()//Apoplugin.h uses -20, but it's more mathematically correct to do it this way.
#define ISAAC_SIZE 4

View File

@ -13,8 +13,6 @@
#ifdef _WIN32
#pragma warning(disable : 4251; disable : 4661; disable : 4100)
#define basename(x) _strdup(x)
#define snprintf _snprintf
#define snprintf_s _snprintf_s
#define WIN32_LEAN_AND_MEAN
#define EMBER_OS "WIN"
@ -69,11 +67,12 @@
#endif
//Intel's Threading Building Blocks is what's used for all threading.
#include <tbb/task_group.h>
#include <tbb/parallel_for.h>
#include <tbb/task_scheduler_init.h>
#define GLM_FORCE_RADIANS 1
#define GLM_ENABLE_EXPERIMENTAL 1
#ifndef __APPLE__
#define GLM_FORCE_INLINE 1
#endif

View File

@ -121,9 +121,6 @@ public:
b = false;
}
if (f.is_open())
f.close();
return b;
}

View File

@ -145,7 +145,7 @@ public:
throw "Out of bounds xform index in selection distribution.";
#endif
//printf("offset = %d, xform = %d, running sum = %f\n", j, i, tempDensity);
//cout << "offset = " << j << ", xform = " << i << ", running sum = " << tempDensity << "\n";
m_XformDistributions[(distrib * CHOOSE_XFORM_GRAIN) + j] = byte(i);
tempDensity += densityPerElement;
j++;

View File

@ -241,7 +241,6 @@ private:
/// <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)
{
bool hexError = false;
char* val;
const char* loc = __FUNCTION__;
xmlAttrPtr attr;
@ -259,33 +258,31 @@ private:
if (!Compare(attr->name, "data"))
{
int colorIndex = 0;
uint r, g, b;
int colorCount = 0;
hexError = false;
string s1, s;
size_t tmp, colorCount = 0, colorIndex = 0;
stringstream ss, temp(val); ss >> std::hex;
s.reserve(2048);
do
while (temp >> s1)
s += s1;
auto length = s.size();
for (size_t strIndex = 0; strIndex < length;)
{
int ret = sscanf_s(static_cast<char*>(&(val[colorIndex])), "00%2x%2x%2x", &r, &g, &b);
strIndex += 2;//Skip past the 00 at the beginning of each RGB.
if (ret != 3)
for (glm::length_t i = 0; i < 3 && colorCount < palette.Size(); i++)
{
AddToReport(string(loc) + " : Problem reading hexadecimal color data " + string(&val[colorIndex]));
hexError = true;
break;
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].
}
colorIndex += 8;
while (isspace(int(val[colorIndex])))
colorIndex++;
palette[colorCount].r = T(r) / T(255);//Store as normalized colors in the range of 0-1.
palette[colorCount].g = T(g) / T(255);
palette[colorCount].b = T(b) / T(255);
colorCount++;
}
while (colorCount < COLORMAP_LENGTH);
}
else if (!Compare(attr->name, "number"))
{
@ -300,11 +297,8 @@ private:
attr = attr->next;
}
if (!hexError)
{
palette.m_Filename = filename;
palettes.push_back(palette);
}
palette.m_Filename = filename;
palettes.push_back(palette);
}
else
{

View File

@ -354,9 +354,6 @@ eRenderStatus Renderer<T, bucketT>::Run(vector<byte>& finalImage, double time, s
size_t i, temporalSample = 0;
T deTime;
auto success = eRenderStatus::RENDER_OK;
//double iterationTime = 0;
//double accumulationTime = 0;
//Timing it;
//Reset timers and progress percent if: Beginning anew or only filtering and/or accumulating.
if (!resume || accumOnly || filterAndAccumOnly)
@ -1294,6 +1291,7 @@ EmberStats Renderer<T, bucketT>::Iterate(size_t iterCount, size_t temporalSample
size_t totalItersPerThread = size_t(ceil(double(iterCount) / double(m_ThreadsToUse)));
double percent, etaMs;
EmberStats stats;
//vector<double> accumTimes(4);
//Do this every iteration for an animation, or else do it once for a single image. CPU only.
if (!m_LastIter)
@ -1302,106 +1300,91 @@ EmberStats Renderer<T, bucketT>::Iterate(size_t iterCount, size_t temporalSample
m_ThreadEmbers.insert(m_ThreadEmbers.begin(), m_ThreadsToUse, m_Ember);
}
#ifdef TG
size_t threadIndex;
for (size_t i = 0; i < m_ThreadsToUse; i++)
{
threadIndex = i;
m_TaskGroup.run([&, threadIndex] ()
{
#else
parallel_for(size_t(0), m_ThreadsToUse, [&] (size_t threadIndex)
{
#endif
#if defined(_WIN32)
SetThreadPriority(GetCurrentThread(), int(m_Priority));
SetThreadPriority(GetCurrentThread(), int(m_Priority));
#elif defined(__APPLE__)
sched_param sp = {0};
sp.sched_priority = m_Priority;
pthread_setschedparam(pthread_self(), SCHED_RR, &sp);
sched_param sp = {0};
sp.sched_priority = m_Priority;
pthread_setschedparam(pthread_self(), SCHED_RR, &sp);
#else
pthread_setschedprio(pthread_self(), int(m_Priority));
pthread_setschedprio(pthread_self(), int(m_Priority));
#endif
//Timing t;
IterParams<T> params;
m_BadVals[threadIndex] = 0;
params.m_Count = std::min(totalItersPerThread, SubBatchSize());
params.m_Skip = FuseCount();
//params.m_OneColDiv2 = m_CarToRas.OneCol() / 2;
//params.m_OneRowDiv2 = m_CarToRas.OneRow() / 2;
//Timing t;
IterParams<T> params;
m_BadVals[threadIndex] = 0;
params.m_Count = std::min(totalItersPerThread, SubBatchSize());
params.m_Skip = FuseCount();
//params.m_OneColDiv2 = m_CarToRas.OneCol() / 2;
//params.m_OneRowDiv2 = m_CarToRas.OneRow() / 2;
//Sub batch iterations, loop 2.
for (m_SubBatch[threadIndex] = 0; (m_SubBatch[threadIndex] < totalItersPerThread) && !m_Abort; m_SubBatch[threadIndex] += params.m_Count)
//Sub batch iterations, loop 2.
for (m_SubBatch[threadIndex] = 0; (m_SubBatch[threadIndex] < totalItersPerThread) && !m_Abort; m_SubBatch[threadIndex] += params.m_Count)
{
//Must recalculate the number of iters to run on each sub batch because the last batch will most likely have less than SubBatchSize iters.
//For example, if 51,000 are requested, and the sbs is 10,000, it should run 5 sub batches of 10,000 iters, and one final sub batch of 1,000 iters.
params.m_Count = std::min(params.m_Count, totalItersPerThread - m_SubBatch[threadIndex]);
//Use first as random point, the rest are iterated points.
//Note that this gets reset with a new random point for each subBatchSize iterations.
//This helps correct if iteration happens to be on a bad trajectory.
m_Samples[threadIndex][0].m_X = m_Rand[threadIndex].template Frand11<T>();
m_Samples[threadIndex][0].m_Y = m_Rand[threadIndex].template Frand11<T>();
m_Samples[threadIndex][0].m_Z = 0;//m_Ember.m_CamZPos;//Apo set this to 0, then made the user use special variations to kick it. It seems easier to just set it to zpos.
m_Samples[threadIndex][0].m_ColorX = m_Rand[threadIndex].template Frand01<T>();
//Finally, iterate.
//t.Tic();
//Iterating, loop 3.
m_BadVals[threadIndex] += m_Iterator->Iterate(m_ThreadEmbers[threadIndex], params, m_Samples[threadIndex].data(), m_Rand[threadIndex]);
//m_BadVals[threadIndex] += m_Iterator->Iterate(m_Ember, params, m_Samples[threadIndex].data(), m_Rand[threadIndex]);
//iterationTime += t.Toc();
if (m_LockAccum)
m_AccumCs.lock();
//t.Tic();
//Map temp buffer samples into the histogram using the palette for color.
Accumulate(m_Rand[threadIndex], m_Samples[threadIndex].data(), params.m_Count, &m_Dmap);
//accumTimes[threadIndex] += t.Toc();
if (m_LockAccum)
m_AccumCs.unlock();
if (m_Callback && threadIndex == 0)
{
//Must recalculate the number of iters to run on each sub batch because the last batch will most likely have less than SubBatchSize iters.
//For example, if 51,000 are requested, and the sbs is 10,000, it should run 5 sub batches of 10,000 iters, and one final sub batch of 1,000 iters.
params.m_Count = std::min(params.m_Count, totalItersPerThread - m_SubBatch[threadIndex]);
//Use first as random point, the rest are iterated points.
//Note that this gets reset with a new random point for each subBatchSize iterations.
//This helps correct if iteration happens to be on a bad trajectory.
m_Samples[threadIndex][0].m_X = m_Rand[threadIndex].template Frand11<T>();
m_Samples[threadIndex][0].m_Y = m_Rand[threadIndex].template Frand11<T>();
m_Samples[threadIndex][0].m_Z = 0;//m_Ember.m_CamZPos;//Apo set this to 0, then made the user use special variations to kick it. It seems easier to just set it to zpos.
m_Samples[threadIndex][0].m_ColorX = m_Rand[threadIndex].template Frand01<T>();
//Finally, iterate.
//t.Tic();
//Iterating, loop 3.
m_BadVals[threadIndex] += m_Iterator->Iterate(m_ThreadEmbers[threadIndex], params, m_Samples[threadIndex].data(), m_Rand[threadIndex]);
//m_BadVals[threadIndex] += m_Iterator->Iterate(m_Ember, params, m_Samples[threadIndex].data(), m_Rand[threadIndex]);
//iterationTime += t.Toc();
if (m_LockAccum)
m_AccumCs.lock();
//t.Tic();
//Map temp buffer samples into the histogram using the palette for color.
Accumulate(m_Rand[threadIndex], m_Samples[threadIndex].data(), params.m_Count, &m_Dmap);
//accumulationTime += t.Toc();
if (m_LockAccum)
m_AccumCs.unlock();
if (m_Callback && threadIndex == 0)
{
percent = 100.0 *
percent = 100.0 *
double
(
double
(
double
(
double
(
//Takes progress of current thread and multiplies by thread count.
//This assumes the threads progress at roughly the same speed.
double(m_LastIter + (m_SubBatch[threadIndex] * m_ThreadsToUse)) / double(ItersPerTemporalSample())
) + temporalSample
) / double(TemporalSamples())
);
double percentDiff = percent - m_LastIterPercent;
double toc = m_ProgressTimer.Toc();
//Takes progress of current thread and multiplies by thread count.
//This assumes the threads progress at roughly the same speed.
double(m_LastIter + (m_SubBatch[threadIndex] * m_ThreadsToUse)) / double(ItersPerTemporalSample())
) + temporalSample
) / double(TemporalSamples())
);
double percentDiff = percent - m_LastIterPercent;
double toc = m_ProgressTimer.Toc();
if (percentDiff >= 10 || (toc > 1000 && percentDiff >= 1))//Call callback function if either 10% has passed, or one second (and 1%).
{
etaMs = ((100.0 - percent) / percent) * m_RenderTimer.Toc();
if (percentDiff >= 10 || (toc > 1000 && percentDiff >= 1))//Call callback function if either 10% has passed, or one second (and 1%).
{
etaMs = ((100.0 - percent) / percent) * m_RenderTimer.Toc();
if (!m_Callback->ProgressFunc(m_Ember, m_ProgressParameter, percent, 0, etaMs))
Abort();
if (!m_Callback->ProgressFunc(m_Ember, m_ProgressParameter, percent, 0, etaMs))
Abort();
m_LastIterPercent = percent;
m_ProgressTimer.Tic();
}
m_LastIterPercent = percent;
m_ProgressTimer.Tic();
}
}
});
#ifdef TG
}
m_TaskGroup.wait();
#endif
}
});
stats.m_Iters = std::accumulate(m_SubBatch.begin(), m_SubBatch.end(), 0ULL);//Sum of iter count of all threads.
stats.m_Badvals = std::accumulate(m_BadVals.begin(), m_BadVals.end(), 0ULL);
stats.m_IterMs = m_IterTimer.Toc();
//cout << "Accum time: " << std::accumulate(accumTimes.begin(), accumTimes.end(), 0.0) << endl;
//t2.Toc(__FUNCTION__);
return stats;
}
@ -1609,19 +1592,49 @@ void Renderer<T, bucketT>::Accumulate(QTIsaac<ISAAC_SIZE, ISAAC_INT>& rand, Poin
colorIndexFrac = colorIndex - bucketT(intColorIndex);//Interpolate between intColorIndex and intColorIndex + 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]);
const bucketT* __restrict pal2 = glm::value_ptr(palette->m_Entries[intColorIndex + 1]);
auto cifm1 = bucketT(1) - colorIndexFrac;
//Loops are unrolled to allow auto vectorization.
if (p.m_VizAdjusted == 1)
m_HistBuckets[histIndex] += ((dmap[intColorIndex] * (1 - colorIndexFrac)) + (dmap[intColorIndex + 1] * colorIndexFrac));
{
hist[0] += (pal[0] * cifm1) + (pal2[0] * colorIndexFrac);
hist[1] += (pal[1] * cifm1) + (pal2[1] * colorIndexFrac);
hist[2] += (pal[2] * cifm1) + (pal2[2] * colorIndexFrac);
hist[3] += (pal[3] * cifm1) + (pal2[3] * colorIndexFrac);
}
else
m_HistBuckets[histIndex] += (((dmap[intColorIndex] * (1 - colorIndexFrac)) + (dmap[intColorIndex + 1] * colorIndexFrac)) * bucketT(p.m_VizAdjusted));
{
auto va = bucketT(p.m_VizAdjusted);
hist[0] += ((pal[0] * cifm1) + (pal2[0] * colorIndexFrac)) * va;
hist[1] += ((pal[1] * cifm1) + (pal2[1] * colorIndexFrac)) * va;
hist[2] += ((pal[2] * cifm1) + (pal2[2] * colorIndexFrac)) * va;
hist[3] += ((pal[3] * cifm1) + (pal2[3] * colorIndexFrac)) * va;
}
}
else if (PaletteMode() == ePaletteMode::PALETTE_STEP)
{
intColorIndex = Clamp<size_t>(size_t(p.m_ColorX * COLORMAP_LENGTH), 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]);
if (p.m_VizAdjusted == 1)
m_HistBuckets[histIndex] += dmap[intColorIndex];
{
hist[0] += pal[0];
hist[1] += pal[1];
hist[2] += pal[2];
hist[3] += pal[3];
}
else
m_HistBuckets[histIndex] += (dmap[intColorIndex] * bucketT(p.m_VizAdjusted));
{
auto va = bucketT(p.m_VizAdjusted);
hist[0] += pal[0] * va;
hist[1] += pal[1] * va;
hist[2] += pal[2] * va;
hist[3] += pal[3] * va;
}
}
}
}

View File

@ -229,7 +229,6 @@ protected:
vector<size_t> m_SubBatch;
vector<size_t> m_BadVals;
vector<QTIsaac<ISAAC_SIZE, ISAAC_INT>> m_Rand;
unique_ptr<tbb::task_group> m_TaskGroup = make_unique<tbb::task_group>();
std::recursive_mutex m_RenderingCs, m_AccumCs, m_FinalAccumCs, m_ResizeCs;
Timing m_RenderTimer, m_IterTimer, m_ProgressTimer;
};

View File

@ -424,7 +424,6 @@ public:
size_t i;
T t;
ostringstream os;
char ministr[32];
if (crossMode == eCrossMode::CROSS_NOT_SPECIFIED)
{
@ -463,8 +462,7 @@ public:
for (i = 0; i < emberOut.TotalXformCount(); i++)
emberOut.GetTotalXform(i)->DeleteMotionElements();
sprintf_s(ministr, 32, "%7.5g", t);
os << "cross interpolate " << ministr;
os << "cross interpolate " << std::to_string(t);
}
else//Alternate mode.
{
@ -953,7 +951,8 @@ public:
/// <param name="ember">The ember to rotate</param>
/// <param name="rotated">The rotated xform</param>
/// <param name="blend">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</param>
void Loop(Ember<T>& ember, Ember<T>& rotated, T blend)
/// <param name="cw">True to rotate clockwise, else rotate counter clockwise. Ignored if rotations is 0.</param>
void Loop(Ember<T>& ember, Ember<T>& rotated, T blend, bool cw)
{
rotated = ember;
@ -970,7 +969,7 @@ public:
}
rotated.ApplyFlameMotion(blend);
rotated.RotateAffines(-blend * 360);//Rotate the affines.
rotated.RotateAffines((cw ? blend : -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.
}
@ -981,8 +980,10 @@ public:
/// <param name="embers">The embers to interpolate</param>
/// <param name="result">The result of the interpolation</param>
/// <param name="blend">The interpolation time</param>
/// <param name="rotations">The number of times to rotate within the interpolation</param>
/// <param name="cw">True to rotate clockwise, else rotate counter clockwise. Ignored if rotations is 0.</param>
/// <param name="seqFlag">True if embers points to the first or last ember in the entire sequence, else false.</param>
void Edge(Ember<T>* embers, Ember<T>& result, T blend, bool seqFlag)
void Edge(Ember<T>* embers, Ember<T>& result, T blend, size_t rotations, bool cw, bool seqFlag)
{
size_t i, si;
@ -1016,9 +1017,15 @@ public:
m_EdgeSpun[1].m_Time = 1;
//Call this first to establish the asymmetric reference angles.
Interpolater<T>::AsymmetricRefAngles(m_EdgeSpun, 2);
//Rotate the aligned xforms.
m_EdgeSpun[0].RotateAffines(-blend * 360);
m_EdgeSpun[1].RotateAffines(-blend * 360);
if (rotations)
{
auto cwblend = cw ? blend : -blend;
m_EdgeSpun[0].RotateAffines(cwblend * (360 * rotations));
m_EdgeSpun[1].RotateAffines(cwblend * (360 * rotations));
}
m_Interpolater.Interpolate(m_EdgeSpun, 2, m_Smooth ? Interpolater<T>::Smoother(blend) : blend, m_Stagger, result);
}
@ -1037,11 +1044,13 @@ public:
/// <param name="result">The result of the spin</param>
/// <param name="frame">The frame in the sequence to be stored in the m_Time member of result</param>
/// <param name="blend">The interpolation time</param>
void Spin(Ember<T>& parent, Ember<T>* templ, Ember<T>& result, size_t frame, T blend)
/// <param name="cw">True to rotate clockwise, else rotate counter clockwise. Ignored if rotations is 0.</param>
void Spin(Ember<T>& parent, Ember<T>* templ, Ember<T>& result, size_t frame, T blend, bool cw)
{
char temp[50];
auto cwblend = cw ? blend : -blend;
string temp = "rotate " + std::to_string(cwblend * 360.0);
//Spin the parent blend degrees.
Loop(parent, result, blend);
Loop(parent, result, blend, cw);
//Apply the template if necessary.
if (templ)
@ -1052,14 +1061,12 @@ public:
result.m_Interp = eInterp::EMBER_INTERP_LINEAR;
result.m_PaletteInterp = ePaletteInterp::INTERP_HSV;
//Create the edit doc xml.
sprintf_s(temp, 50, "rotate %g", blend * 360.0);
result.ClearEdit();
result.m_Edits = m_EmberToXml.CreateNewEditdoc(&parent, nullptr, temp, m_Nick, m_Url, m_Id, m_Comment, m_SheepGen, m_SheepId);
//Subpixel jitter.
Offset(result, m_OffsetX, m_OffsetY);
//Make the name of the flame the time.
sprintf_s(temp, 50, "%f", result.m_Time);
result.m_Name = string(temp);
result.m_Name = std::to_string(result.m_Time);
}
/// <summary>
@ -1074,11 +1081,14 @@ public:
/// <param name="frame">The frame in the sequence to be stored in the m_Time member of result</param>
/// <param name="seqFlag">True if embers points to the first or last ember in the entire sequence, else false.</param>
/// <param name="blend">The interpolation time</param>
void SpinInter(Ember<T>* parents, Ember<T>* templ, Ember<T>& result, size_t frame, bool seqFlag, T blend)
/// <param name="rotations">The number of times to rotate within the interpolation</param>
/// <param name="cw">True to rotate clockwise, else rotate counter clockwise. Ignored if rotations is 0.</param>
void SpinInter(Ember<T>* parents, Ember<T>* templ, Ember<T>& result, size_t frame, bool seqFlag, T blend, size_t rotations, bool cw)
{
char temp[50];
auto cwblend = cw ? blend : -blend;
string temp = "interpolate " + std::to_string(cwblend * 360.0);
//Interpolate between spun parents.
Edge(parents, result, blend, seqFlag);
Edge(parents, result, blend, rotations, cw, seqFlag);
//Original did an interpolated palette hack here for random palettes, but it was never used anywhere so ember omits it.//ORIG
@ -1089,14 +1099,12 @@ public:
//Set ember parameters accordingly.
result.m_Time = T(frame);
//Create the edit doc xml.
sprintf_s(temp, 50, "interpolate %g", blend * 360.0);
result.ClearEdit();
result.m_Edits = m_EmberToXml.CreateNewEditdoc(&parents[0], &parents[1], temp, m_Nick, m_Url, m_Id, m_Comment, m_SheepGen, m_SheepId);
//Subpixel jitter.
Offset(result, m_OffsetX, m_OffsetY);
//Make the name of the flame the time.
sprintf_s(temp, 50, "%f", result.m_Time);
result.m_Name = string(temp);
result.m_Name = std::to_string(result.m_Time);
}
/// <summary>

View File

@ -222,6 +222,28 @@ public:
return temp;
}
/// <summary>
/// For creating an object without passing parameters.
/// When the derived class has a default constructor, this should
/// not be called. This is only for when the derived class constructor
/// requires arguments. In that case, Instance() must first be called
/// with the proper values. Then once the singleton is constructed, this
/// can be called to just retrieve the object without having to worry about
/// parameters.
/// This is enforced by throwing if this has been called before Instance() is called.
/// </summary>
/// <returns>The constructed object</returns>
static std::shared_ptr<T> DefInstance()
{
auto& staticInstance = GetStaticInstance();
auto temp = staticInstance.lock();
if (!temp)
throw "Cannot create singleton with defaults, must first call at least once with proper arguments.";
return temp;
}
protected:
/// <summary>
/// Clever hack to get a static to behave like a member variable that can be seen between classes and functions in the hierarchy.
@ -255,64 +277,34 @@ protected:
/// Open a file in binary mode and read its entire contents into a vector of bytes. Optionally null terminate.
/// </summary>
/// <param name="filename">The full path to the file to read</param>
/// <param name="buf">The vector which will be populated with the file's contents</param>
/// <param name="buf">The string which will be populated with the file's contents</param>
/// <param name="nullTerminate">Whether to append a NULL character as the last element of the vector. Needed when reading text files. Default: true.</param>
/// <returns>True if successfully read and populated, else false</returns>
static bool ReadFile(const char* filename, string& buf, bool nullTerminate = true)
{
bool b = false;
FILE* f = nullptr;
try
{
fopen_s(&f, filename, "rb");//Open in binary mode.
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);
if (f)
{
struct _stat statBuf;
#if defined(_WIN32) || defined(__APPLE__)
int statResult = _fstat(f->_file, &statBuf);//Get data associated with file.
#else
int statResult = _fstat(f->_fileno, &statBuf);//Get data associated with file.
#endif
if (nullTerminate)//Optionally NULL terminate if they want to treat it as a string.
buf[buf.size() - 1] = 0;
if (statResult == 0)//Check if statistics are valid.
{
buf.resize(statBuf.st_size + (nullTerminate ? 1 : 0));//Allocate vector to be the size of the entire file, with an optional additional character for nullptr.
if (buf.size() == static_cast<size_t>(statBuf.st_size + 1))//Ensure allocation succeeded.
{
size_t bytesRead = fread(&buf[0], 1, statBuf.st_size, f);//Read the entire file at once.
if (bytesRead == (static_cast<size_t>(statBuf.st_size)))//Ensure the number of bytes read matched what was requested.
{
if (nullTerminate)//Optionally nullptr terminate if they want to treat it as a string.
buf[buf.size() - 1] = 0;
b = true;//Success.
}
}
}
fclose(f);
f = nullptr;
}
return true;
}
catch (const std::exception& e)
{
cout << "Error: Reading file " << filename << " failed: " << e.what() << "\n";
b = false;
}
catch (...)
{
cout << "Error: Reading file " << filename << " failed.\n";
b = false;
}
if (f)
fclose(f);
return b;
return false;
}
/// <summary>

View File

@ -442,42 +442,6 @@ public:
return b;
}
/// <summary>
/// Convert an integer to a string.
/// Just a wrapper around _itoa_s() which wraps the result in a std::string.
/// </summary>
/// <param name="i">The integer to convert</param>
/// <param name="radix">The radix of the integer. Default: 10.</param>
/// <returns>The converted string</returns>
static string Itos(int i, int radix = 10)
{
char ch[16];
#ifdef _WIN32
_itoa_s(i, ch, 16, radix);
#else
sprintf(ch, "%d", i);
#endif
return string(ch);
}
/// <summary>
/// Convert an unsigned 64-bit integer to a string.
/// Just a wrapper around _ui64toa_s() which wraps the result in a std::string.
/// </summary>
/// <param name="i">The unsigned 64-bit integer to convert</param>
/// <param name="radix">The radix of the integer. Default: 10.</param>
/// <returns>The converted string</returns>
static string Itos64(size_t i, int radix = 10)
{
char ch[64];
#ifdef _WIN32
_ui64toa_s(i, ch, 64, radix);
#else
sprintf(ch, "%lu", i);
#endif
return string(ch);
}
static vector<string> m_FlattenNames;
private:
@ -523,7 +487,7 @@ private:
if (auto pal = m_PaletteList.GetPalette(PaletteList<T>::m_DefaultFilename, currentEmber.PaletteIndex()))
currentEmber.m_Palette = *pal;
else
AddToReport(string(loc) + " : Error assigning palette with index " + Itos(currentEmber.PaletteIndex()));
AddToReport(string(loc) + " : Error assigning palette with index " + std::to_string(currentEmber.PaletteIndex()));
}
if (!currentEmber.XformCount())//Ensure there is always at least one xform or else the renderer will crash when trying to render.
@ -532,8 +496,6 @@ private:
currentEmber.AddXform(xform);
}
//if (!Interpolater<T>::InterpMissingColors(currentEmber.m_Palette.m_Entries))
// AddToReport(string(loc) + " : Error interpolating missing palette colors");
currentEmber.CacheXforms();
currentEmber.m_Index = embers.size();
currentEmber.m_ParentFilename = parentFileString;
@ -562,7 +524,6 @@ private:
const char* loc = __FUNCTION__;
int soloXform = -1;
size_t i, count = 0, index = 0;
double vals[16];
xmlAttrPtr att, curAtt;
xmlNodePtr editNode, childNode, motionNode;
currentEmber.m_Palette.Clear();//Wipe out the current palette.
@ -655,28 +616,16 @@ private:
}
else if (!Compare(curAtt->name, "size"))
{
if (sscanf_s(attStr, "%lu %lu", &currentEmber.m_FinalRasW, &currentEmber.m_FinalRasH) != 2)
{
AddToReport(string(loc) + " : Invalid size attribute " + string(attStr));
//Assign reasonable defaults.
currentEmber.m_FinalRasW = 1000;
currentEmber.m_FinalRasH = 1000;
}
istringstream is(attStr);
is >> currentEmber.m_FinalRasW >> currentEmber.m_FinalRasH;
currentEmber.m_OrigFinalRasW = currentEmber.m_FinalRasW;
currentEmber.m_OrigFinalRasH = currentEmber.m_FinalRasH;
}
else if (!Compare(curAtt->name, "center"))
{
if (sscanf_s(attStr, "%lf %lf", &vals[0], &vals[1]) != 2)
{
AddToReport(string(loc) + " : Invalid center attribute " + string(attStr));
vals[0] = 0;
vals[1] = 0;
}
currentEmber.m_CenterX = T(vals[0]);
currentEmber.m_CenterY = currentEmber.m_RotCenterY = T(vals[1]);
istringstream is(attStr);
is >> currentEmber.m_CenterX >> currentEmber.m_CenterY;
currentEmber.m_RotCenterY = currentEmber.m_CenterY;
}
else if (!Compare(curAtt->name, "filter_shape"))
{
@ -700,29 +649,22 @@ private:
}
else if (!Compare(curAtt->name, "background"))
{
if (sscanf_s(attStr, "%lf %lf %lf", &vals[0], &vals[1], &vals[2]) != 3)
{
AddToReport(string(loc) + " : Invalid background attribute " + string(attStr));
vals[0] = 0;
vals[1] = 0;
vals[2] = 0;
}
currentEmber.m_Background[0] = T(vals[0]);//[0..1]
currentEmber.m_Background[1] = T(vals[1]);
currentEmber.m_Background[2] = T(vals[2]);
istringstream is(attStr);
is >> currentEmber.m_Background[0]//[0..1]
>> currentEmber.m_Background[1]
>> currentEmber.m_Background[2];
}
else if (!Compare(curAtt->name, "curves"))
{
stringstream ss(attStr);
istringstream is(attStr);
for (i = 0; i < 4; i++)
{
for (glm::length_t j = 0; j < 4; j++)
{
ss >> currentEmber.m_Curves.m_Points[i][j].x;
ss >> currentEmber.m_Curves.m_Points[i][j].y;
ss >> currentEmber.m_Curves.m_Weights[i][j];
is >> currentEmber.m_Curves.m_Points[i][j].x
>> currentEmber.m_Curves.m_Points[i][j].y
>> currentEmber.m_Curves.m_Weights[i][j];
}
}
}
@ -748,36 +690,24 @@ private:
for (curAtt = att; curAtt; curAtt = curAtt->next)
{
attStr = reinterpret_cast<char*>(xmlGetProp(childNode, curAtt->name));
a = 255;
attStr = reinterpret_cast<char*>(xmlGetProp(childNode, curAtt->name));
istringstream is(attStr);
//This signifies that a palette is not being retrieved from the palette file, rather it's being parsed directly out of the ember xml.
//This also means the palette has already been hue adjusted and it doesn't need to be done again, which would be necessary if it were
//coming from the palette file.
currentEmber.m_Palette.m_Index = -1;
if (!Compare(curAtt->name, "index"))
{
Aton(attStr, index);
}
else if (!Compare(curAtt->name, "rgb"))
{
if (sscanf_s(attStr, "%lf %lf %lf", &r, &g, &b) != 3)
AddToReport(string(loc) + " : Invalid rgb attribute " + string(attStr));
}
is >> r >> g >> b;
else if (!Compare(curAtt->name, "rgba"))
{
if (sscanf_s(attStr, "%lf %lf %lf %lf", &r, &g, &b, &a) != 4)
AddToReport(string(loc) + " : Invalid rgba attribute " + string(attStr));
}
is >> r >> g >> b >> a;
else if (!Compare(curAtt->name, "a"))
{
if (sscanf_s(attStr, "%lf", &a) != 1)
AddToReport(string(loc) + " : Invalid a attribute " + string(attStr));
}
is >> a;
else
{
AddToReport(string(loc) + " : Unknown color attribute " + string(CCX(curAtt->name)));
}
xmlFree(attStr);
}
@ -1051,13 +981,9 @@ private:
ret = ret && AttToEmberMotionFloat(att, attStr, eEmberMotionParam::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)
{
AddToReport(string(loc) + " : Invalid flame motion background attribute " + string(attStr));
r = g = b = 0;
}
double r = 0, g = 0, b = 0;
istringstream is(attStr);
is >> r >> g >> b;
if (r != 0)
motion.m_MotionParams.push_back(MotionParam<T>(eEmberMotionParam::FLAME_MOTION_BACKGROUND_R, T(r)));
@ -1070,13 +996,9 @@ private:
}
else if (!Compare(curAtt->name, "center"))
{
double cx, cy;
if (sscanf_s(attStr, "%lf %lf", &cx, &cy) != 2)
{
AddToReport(string(loc) + " : Invalid flame motion center attribute " + string(attStr));
cx = cy = 0;
}
double cx = 0, cy = 0;
istringstream is(attStr);
is >> cx >> cy;
if (cx != 0)
motion.m_MotionParams.push_back(MotionParam<T>(eEmberMotionParam::FLAME_MOTION_CENTER_X, T(cx)));
@ -1148,7 +1070,6 @@ private:
size_t j;
T temp;
double a, b, c, d, e, f;
double vals[10];
xmlAttrPtr attPtr, curAtt;
//Loop through the attributes of the xform element.
attPtr = childNode->properties;
@ -1203,30 +1124,17 @@ private:
}
else if (!Compare(curAtt->name, "color"))
{
xform.m_ColorX = xform.m_ColorY = 0;
//Try two coords first .
if (sscanf_s(attStr, "%lf %lf", &vals[0], &vals[1]) == 2)
{
xform.m_ColorX = T(vals[0]);
xform.m_ColorY = T(vals[1]);
}
else if (sscanf_s(attStr, "%lf", &vals[0]) == 1)//Try one color.
{
xform.m_ColorX = T(vals[0]);
}
else
{
xform.m_ColorX = xform.m_ColorY = T(0.5);
AddToReport(string(loc) + " : Malformed xform color attribute " + string(attStr) + ", using 0.5, 0.5");
}
istringstream is(attStr);
xform.m_ColorX = xform.m_ColorY = T(0.5);
is >> xform.m_ColorX;
is >> xform.m_ColorY;//Very unlikely to be present, but leave for future use.
}
else if (!Compare(curAtt->name, "chaos"))
{
stringstream ss(attStr);
istringstream is(attStr);
j = 0;
while (ss >> temp)
while (is >> temp)
{
xform.SetXaos(j, temp);
j++;
@ -1243,12 +1151,9 @@ private:
}
else if (!Compare(curAtt->name, "coefs"))
{
if (sscanf_s(attStr, "%lf %lf %lf %lf %lf %lf", &a, &d, &b, &e, &c, &f) != 6)//Original did a complicated parsing scheme. This is easier.//ORIG
{
a = d = b = e = c = f = 0;
AddToReport(string(loc) + " : Bad coeffs attribute " + string(attStr));
}
istringstream is(attStr);
a = b = c = d = e = f = 0;
is >> a >> d >> b >> e >> c >> f;
xform.m_Affine.A(T(a));
xform.m_Affine.B(T(b));
xform.m_Affine.C(T(c));
@ -1258,12 +1163,9 @@ private:
}
else if (!Compare(curAtt->name, "post"))
{
if (sscanf_s(attStr, "%lf %lf %lf %lf %lf %lf", &a, &d, &b, &e, &c, &f) != 6)//Original did a complicated parsing scheme. This is easier.//ORIG
{
a = d = b = e = c = f = 0;
AddToReport(string(loc) + " : Bad post coeffs attribute " + string(attStr));
}
istringstream is(attStr);
a = b = c = d = e = f = 0;
is >> a >> d >> b >> e >> c >> f;
xform.m_Post.A(T(a));
xform.m_Post.B(T(b));
xform.m_Post.C(T(c));
@ -1469,64 +1371,33 @@ private:
/// <returns>True if there were no errors, else false.</returns>
bool ParseHexColors(char* colstr, Ember<T>& ember, size_t numColors, intmax_t chan)
{
size_t colorIndex = 0;
size_t colorCount = 0;
uint r, g, b, a;
int ret;
char tmps[2];
size_t skip = std::abs(chan);
bool ok = true;
const char* loc = __FUNCTION__;
stringstream ss, temp(colstr); ss >> std::hex;
string s1, s;
size_t tmp, colorCount = 0, colorIndex = 0;
s.reserve(1536);
//Strip whitespace prior to first color.
while (isspace(static_cast<int>(colstr[colorIndex])))
colorIndex++;
while (temp >> s1)
s += s1;
do
auto length = s.size();
for (size_t strIndex = 0; strIndex < length;)
{
//Parse an RGB triplet at a time.
if (chan == 3)
ret = sscanf_s(&(colstr[colorIndex]), "%2x%2x%2x", &r, &g, &b);
else if (chan == -4)
ret = sscanf_s(&(colstr[colorIndex]), "00%2x%2x%2x", &r, &g, &b);
else // chan==4
ret = sscanf_s(&(colstr[colorIndex]), "%2x%2x%2x%2x", &r, &g, &b, &a);
a = 1;//Original allows for alpha, even though it will most likely never happen. Ember omits support for it.
if ((chan != 4 && ret != 3) || (chan == 4 && ret != 4))
for (glm::length_t i = 0; i < 3 && colorCount < ember.m_Palette.Size(); i++)
{
ok = false;
r = g = b = 0;
AddToReport(string(loc) + " : Problem reading hexadecimal color data, assigning to 0");
break;
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.
ember.m_Palette.m_Entries[colorCount][i] = T(tmp) / T(255);//Hex palette is [0..255], convert to [0..1].
}
colorIndex += 2 * skip;
while (isspace(static_cast<int>(colstr[colorIndex])))
colorIndex++;
ember.m_Palette.m_Entries[colorCount].r = T(r) / T(255);//Hex palette is [0..255], convert to [0..1].
ember.m_Palette.m_Entries[colorCount].g = T(g) / T(255);
ember.m_Palette.m_Entries[colorCount].b = T(b) / T(255);
ember.m_Palette.m_Entries[colorCount].a = T(a);
ember.m_Palette.m_Entries[colorCount][3] = T(1);
colorCount++;
}
while (colorCount < numColors && colorCount < ember.m_Palette.m_Entries.size());
#ifdef _WIN32
if (sscanf_s(&(colstr[colorIndex]), "%1s", tmps, sizeof(tmps)) > 0) //Really need to migrate all of this parsing to C++.//TODO
#else
if (sscanf_s(&(colstr[colorIndex]), "%1s", tmps) > 0)
#endif
{
AddToReport(string(loc) + " : Extra data at end of hex color data " + string(&(colstr[colorIndex])));
ok = false;
}
return ok;
return length >= 256;
}
/// <summary>

View File

@ -155,10 +155,9 @@ bool EmberAnimate(EmberOptions& opt)
if (opt.Format() != "jpg" &&
opt.Format() != "png" &&
opt.Format() != "ppm" &&
opt.Format() != "bmp")
{
cout << "Format must be jpg, png, ppm, or bmp not " << opt.Format() << ". Setting to jpg.\n";
cout << "Format must be jpg, png, or bmp not " << opt.Format() << ". Setting to jpg.\n";
}
channels = opt.Format() == "png" ? 4 : 3;
@ -317,8 +316,6 @@ bool EmberAnimate(EmberOptions& opt)
writeSuccess = WritePng(filename.c_str(), finalImagep, w, h, opt.BitsPerChannel() / 8, opt.PngComments(), comments, opt.Id(), opt.Url(), opt.Nick());
else if (opt.Format() == "jpg")
writeSuccess = WriteJpeg(filename.c_str(), finalImagep, w, h, int(opt.JpegQuality()), opt.JpegComments(), comments, opt.Id(), opt.Url(), opt.Nick());
else if (opt.Format() == "ppm")
writeSuccess = WritePpm(filename.c_str(), finalImagep, w, h);
else if (opt.Format() == "bmp")
writeSuccess = WriteBmp(filename.c_str(), finalImagep, w, h);

View File

@ -9,6 +9,7 @@
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN//Exclude rarely-used stuff from Windows headers.
#define _USE_MATH_DEFINES
//#define CL_USE_DEPRECATED_OPENCL_1_2_APIS 1
#include "Timing.h"
#include "Renderer.h"

View File

@ -18,13 +18,11 @@
#include <BaseTsd.h>
#include <crtdbg.h>
#include <tchar.h>
#define snprintf _snprintf
#else
#include <arpa/inet.h>
#define _TCHAR char
#define _tmain main
#define _T
#define fprintf_s fprintf
#endif
#include <iostream>
@ -43,7 +41,6 @@
#define PNG_SKIP_SETJMP_CHECK 1
#include "png.h"
//#include "pnginfo.h"
//Ember.
#include "Ember.h"

View File

@ -65,6 +65,8 @@ enum class eOptionIDs : et
OPT_ENCLOSED,
OPT_NO_EDITS,
OPT_UNSMOOTH_EDGE,
OPT_CW_LOOPS,
OPT_CW_INTERP_LOOPS,
OPT_LOCK_ACCUM,
OPT_DUMP_KERNEL,
@ -80,7 +82,9 @@ enum class eOptionIDs : et
OPT_END,
OPT_FRAME,
OPT_DTIME,
OPT_NFRAMES,
OPT_LOOP_FRAMES,
OPT_INTERP_FRAMES,
OPT_INTERP_LOOPS,
OPT_SYMMETRY,
OPT_SHEEP_GEN,
OPT_SHEEP_ID,
@ -236,7 +240,7 @@ private:
{ \
if (member.m_Option.nArgType == SO_OPT) \
{ \
member(!strcmp(args.OptionArg(), "true")); \
member(!_stricmp(args.OptionArg(), "true")); \
} \
else \
{ \
@ -245,50 +249,40 @@ private:
} \
break
//Parsing is the same for all numerical option types.
#define PARSEOPTION(e, member) \
case (e): \
{ \
ss.clear(); \
ss.str(args.OptionArg()); \
ss >> member.m_Val; \
break; \
}
//Int.
#define Eoi EmberOptionEntry<intmax_t>
#define INITINTOPTION(member, option) \
member = option; \
m_IntArgs.push_back(&member)
#define PARSEINTOPTION(e, member) \
case (e): \
sscanf_s(args.OptionArg(), "%ld", &member.m_Val); \
break
//Uint.
#define Eou EmberOptionEntry<size_t>
#define INITUINTOPTION(member, option) \
member = option; \
m_UintArgs.push_back(&member)
#define PARSEUINTOPTION(e, member) \
case (e): \
sscanf_s(args.OptionArg(), "%lu", &member.m_Val); \
break
//Double.
#define Eod EmberOptionEntry<double>
#define INITDOUBLEOPTION(member, option) \
member = option; \
m_DoubleArgs.push_back(&member)
#define PARSEDOUBLEOPTION(e, member) \
case (e): \
sscanf_s(args.OptionArg(), "%lf", &member.m_Val); \
break
//String.
#define Eos EmberOptionEntry<string>
#define INITSTRINGOPTION(member, option) \
member = option; \
m_StringArgs.push_back(&member)
#define PARSESTRINGOPTION(e, member) \
case (e): \
member.m_Val = args.OptionArg(); \
break
/// <summary>
/// Class for holding all available options across all command line programs.
/// Some are used only for a single program, while others are used for more than one.
@ -345,6 +339,8 @@ public:
INITBOOLOPTION(Enclosed, Eob(eOptionUse::OPT_USE_GENOME, eOptionIDs::OPT_ENCLOSED, _T("--enclosed"), true, SO_OPT, " --enclosed Use enclosing Xml tags [default: true].\n"));
INITBOOLOPTION(NoEdits, Eob(eOptionUse::OPT_USE_GENOME, eOptionIDs::OPT_NO_EDITS, _T("--noedits"), false, SO_NONE, " --noedits Exclude edit tags when writing Xml [default: false].\n"));
INITBOOLOPTION(UnsmoothEdge, Eob(eOptionUse::OPT_USE_GENOME, eOptionIDs::OPT_UNSMOOTH_EDGE, _T("--unsmoother"), false, SO_NONE, " --unsmoother Do not use smooth blending for sheep edges [default: false].\n"));
INITBOOLOPTION(CwLoops, Eob(eOptionUse::OPT_USE_GENOME, eOptionIDs::OPT_CW_LOOPS, _T("--cwloops"), false, SO_NONE, " --cwloops Rotate loops clockwise [default: false].\n"));
INITBOOLOPTION(CwInterpLoops, Eob(eOptionUse::OPT_USE_GENOME, eOptionIDs::OPT_CW_INTERP_LOOPS, _T("--cwinterploops"), false, SO_NONE, " --cwinterploops Rotate clockwise during interpolation, ignored if --interploops is 0 [default: false].\n"));
INITBOOLOPTION(LockAccum, Eob(eOptionUse::OPT_USE_ALL, eOptionIDs::OPT_LOCK_ACCUM, _T("--lock_accum"), false, SO_NONE, " --lock_accum Lock threads when accumulating to the histogram using the CPU. This will drop performance to that of single threading [default: false].\n"));
INITBOOLOPTION(DumpKernel, Eob(eOptionUse::OPT_USE_RENDER, eOptionIDs::OPT_DUMP_KERNEL, _T("--dump_kernel"), false, SO_NONE, " --dump_kernel Print the iteration kernel string when using OpenCL (ignored for CPU) [default: false].\n"));
//Int.
@ -368,20 +364,22 @@ public:
INITUINTOPTION(LastFrame, Eou(eOptionUse::OPT_USE_ANIMATE, eOptionIDs::OPT_END, _T("--end"), UINT_MAX, SO_REQ_SEP, " --end=<val> Time of last frame to render [default: last time specified in the input file].\n"));
INITUINTOPTION(Frame, Eou(eOptionUse::OPT_ANIM_GENOME, eOptionIDs::OPT_FRAME, _T("--frame"), UINT_MAX, SO_REQ_SEP, " --frame=<val> Time of first and last frame (ie do one frame).\n"));
INITUINTOPTION(Dtime, Eou(eOptionUse::OPT_USE_ANIMATE, eOptionIDs::OPT_DTIME, _T("--dtime"), 1, SO_REQ_SEP, " --dtime=<val> Time between frames [default: 1].\n"));
INITUINTOPTION(Frames, Eou(eOptionUse::OPT_USE_GENOME, eOptionIDs::OPT_NFRAMES, _T("--nframes"), 20, SO_REQ_SEP, " --nframes=<val> Number of frames per loop and per interpolation in the animation [default: 20].\n"));
INITUINTOPTION(LoopFrames, Eou(eOptionUse::OPT_USE_GENOME, eOptionIDs::OPT_LOOP_FRAMES, _T("--loopframes"), 20, SO_REQ_SEP, " --loopframes=<val> Number of frames per loop in the animation [default: 20].\n"));
INITUINTOPTION(InterpFrames, Eou(eOptionUse::OPT_USE_GENOME, eOptionIDs::OPT_INTERP_FRAMES, _T("--interpframes"), 20, SO_REQ_SEP, " --interpframes=<val> Number of frames per interpolation in the animation [default: 20].\n"));
INITUINTOPTION(InterpLoops, Eou(eOptionUse::OPT_USE_GENOME, eOptionIDs::OPT_INTERP_LOOPS, _T("--interploops"), 1, SO_REQ_SEP, " --interploops=<val> The number of 360 degree loops to rotate when interpolating between keyframes [default: 1].\n"));
INITUINTOPTION(Repeat, Eou(eOptionUse::OPT_USE_GENOME, eOptionIDs::OPT_REPEAT, _T("--repeat"), 1, SO_REQ_SEP, " --repeat=<val> Number of new flames to create. Ignored if sequence, inter or rotate were specified [default: 1].\n"));
INITUINTOPTION(Tries, Eou(eOptionUse::OPT_USE_GENOME, eOptionIDs::OPT_TRIES, _T("--tries"), 10, SO_REQ_SEP, " --tries=<val> 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(eOptionUse::OPT_USE_GENOME, eOptionIDs::OPT_MAX_XFORMS, _T("--maxxforms"), UINT_MAX, SO_REQ_SEP, " --maxxforms=<val> The maximum number of xforms allowed in the final output.\n"));
INITUINTOPTION(StartCount, Eou(eOptionUse::OPT_USE_GENOME, eOptionIDs::OPT_START_COUNT, _T("--startcount"), 0, SO_REQ_SEP, " --startcount=<val> The number to add to each flame name when generating a sequence. Useful for programs like ffmpeg which require numerically increasing filenames [default: 0].\n"));
INITUINTOPTION(Padding, Eou(eOptionUse::OPT_USE_GENOME, eOptionIDs::OPT_PADDING, _T("--padding"), 0, SO_REQ_SEP, " --padding=<val> Override the amount of zero padding added to each flame name when generating a sequence. Useful for programs like ffmpeg which require fixed width filenames [default: 0 (auto calculate padding)].\n"));
//Double.
INITDOUBLEOPTION(SizeScale, Eod(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_SS, _T("--ss"), 1, SO_REQ_SEP, " --ss=<val> Size scale. All dimensions are scaled by this amount [default: 1.0].\n"));
INITDOUBLEOPTION(QualityScale, Eod(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_QS, _T("--qs"), 1, SO_REQ_SEP, " --qs=<val> Quality scale. All quality values are scaled by this amount [default: 1.0].\n"));
INITDOUBLEOPTION(Quality, Eod(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_QUALITY, _T("--quality"), 0, SO_REQ_SEP, " --quality=<val> Override the quality of the flame if not 0 [default: 0].\n"));
INITDOUBLEOPTION(DeMin, Eod(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_DE_MIN, _T("--demin"), -1, SO_REQ_SEP, " --demin=<val> Override the minimum size of the density estimator filter radius if not -1 [default: -1].\n"));
INITDOUBLEOPTION(DeMax, Eod(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_DE_MAX, _T("--demax"), -1, SO_REQ_SEP, " --demax=<val> Override the maximum size of the density estimator filter radius if not -1 [default: -1].\n"));
INITDOUBLEOPTION(AspectRatio, Eod(eOptionUse::OPT_USE_ALL, eOptionIDs::OPT_PIXEL_ASPECT, _T("--pixel_aspect"), 1, SO_REQ_SEP, " --pixel_aspect=<val> Aspect ratio of pixels (width over height), eg. 0.90909 for NTSC [default: 1.0].\n"));
INITDOUBLEOPTION(Stagger, Eod(eOptionUse::OPT_USE_GENOME, eOptionIDs::OPT_STAGGER, _T("--stagger"), 0, SO_REQ_SEP, " --stagger=<val> Affects simultaneity of xform interpolation during flame interpolation.\n"
INITDOUBLEOPTION(SizeScale, Eod(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_SS, _T("--ss"), 1.0, SO_REQ_SEP, " --ss=<val> Size scale. All dimensions are scaled by this amount [default: 1.0].\n"));
INITDOUBLEOPTION(QualityScale, Eod(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_QS, _T("--qs"), 1.0, SO_REQ_SEP, " --qs=<val> Quality scale. All quality values are scaled by this amount [default: 1.0].\n"));
INITDOUBLEOPTION(Quality, Eod(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_QUALITY, _T("--quality"), 0.0, SO_REQ_SEP, " --quality=<val> Override the quality of the flame if not 0 [default: 0].\n"));
INITDOUBLEOPTION(DeMin, Eod(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_DE_MIN, _T("--demin"), -1.0, SO_REQ_SEP, " --demin=<val> Override the minimum size of the density estimator filter radius if not -1 [default: -1].\n"));
INITDOUBLEOPTION(DeMax, Eod(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_DE_MAX, _T("--demax"), -1.0, SO_REQ_SEP, " --demax=<val> Override the maximum size of the density estimator filter radius if not -1 [default: -1].\n"));
INITDOUBLEOPTION(AspectRatio, Eod(eOptionUse::OPT_USE_ALL, eOptionIDs::OPT_PIXEL_ASPECT, _T("--pixel_aspect"), 1.0, SO_REQ_SEP, " --pixel_aspect=<val> Aspect ratio of pixels (width over height), eg. 0.90909 for NTSC [default: 1.0].\n"));
INITDOUBLEOPTION(Stagger, Eod(eOptionUse::OPT_USE_GENOME, eOptionIDs::OPT_STAGGER, _T("--stagger"), 0.0, SO_REQ_SEP, " --stagger=<val> Affects simultaneity of xform interpolation during flame interpolation.\n"
"\t Represents how 'separate' the xforms are interpolated. Set to 1 for each\n"
"\t xform to be interpolated individually, fractions control interpolation overlap [default: 0].\n"));
INITDOUBLEOPTION(AvgThresh, Eod(eOptionUse::OPT_USE_GENOME, eOptionIDs::OPT_AVG_THRESH, _T("--avg"), 20.0, SO_REQ_SEP, " --avg=<val> Minimum average pixel channel sum (r + g + b) threshold from 0 - 765. Ignored if sequence, inter or rotate were specified [default: 20].\n"));
@ -399,7 +397,7 @@ public:
INITSTRINGOPTION(Out, Eos(eOptionUse::OPT_USE_RENDER, eOptionIDs::OPT_OUT, _T("--out"), "", SO_REQ_SEP, " --out=<val> Name of a single output file. Not recommended when rendering more than one image.\n"));
INITSTRINGOPTION(Prefix, Eos(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_PREFIX, _T("--prefix"), "", SO_REQ_SEP, " --prefix=<val> Prefix to prepend to all output files.\n"));
INITSTRINGOPTION(Suffix, Eos(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_SUFFIX, _T("--suffix"), "", SO_REQ_SEP, " --suffix=<val> Suffix to append to all output files.\n"));
INITSTRINGOPTION(Format, Eos(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_FORMAT, _T("--format"), "png", SO_REQ_SEP, " --format=<val> Format of the output file. Valid values are: bmp, jpg, png, ppm [default: png].\n"));
INITSTRINGOPTION(Format, Eos(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_FORMAT, _T("--format"), "png", SO_REQ_SEP, " --format=<val> Format of the output file. Valid values are: bmp, jpg, png [default: png].\n"));
INITSTRINGOPTION(PalettePath, Eos(eOptionUse::OPT_USE_ALL, eOptionIDs::OPT_PALETTE_FILE, _T("--flam3_palettes"), "flam3-palettes.xml", SO_REQ_SEP, " --flam3_palettes=<val> Path and name of the palette file [default: flam3-palettes.xml].\n"));
INITSTRINGOPTION(Id, Eos(eOptionUse::OPT_USE_ALL, eOptionIDs::OPT_ID, _T("--id"), "", SO_REQ_SEP, " --id=<val> ID to use in <edit> tags / image comments.\n"));
INITSTRINGOPTION(Url, Eos(eOptionUse::OPT_USE_ALL, eOptionIDs::OPT_URL, _T("--url"), "", SO_REQ_SEP, " --url=<val> URL to use in <edit> tags / image comments.\n"));
@ -436,6 +434,7 @@ public:
EmberOptions options;
vector<CSimpleOpt::SOption> sOptions = options.GetSimpleOptions();
CSimpleOpt args(argc, argv, sOptions.data());
stringstream ss;
//Process args.
while (args.Next())
@ -492,72 +491,76 @@ public:
PARSEBOOLOPTION(eOptionIDs::OPT_ENCLOSED, Enclosed);
PARSEBOOLOPTION(eOptionIDs::OPT_NO_EDITS, NoEdits);
PARSEBOOLOPTION(eOptionIDs::OPT_UNSMOOTH_EDGE, UnsmoothEdge);
PARSEBOOLOPTION(eOptionIDs::OPT_CW_LOOPS, CwLoops);
PARSEBOOLOPTION(eOptionIDs::OPT_CW_INTERP_LOOPS, CwInterpLoops);
PARSEBOOLOPTION(eOptionIDs::OPT_LOCK_ACCUM, LockAccum);
PARSEBOOLOPTION(eOptionIDs::OPT_DUMP_KERNEL, DumpKernel);
PARSEINTOPTION(eOptionIDs::OPT_SYMMETRY, Symmetry);//Int args
PARSEINTOPTION(eOptionIDs::OPT_SHEEP_GEN, SheepGen);
PARSEINTOPTION(eOptionIDs::OPT_SHEEP_ID, SheepId);
PARSEINTOPTION(eOptionIDs::OPT_PRIORITY, Priority);
PARSEUINTOPTION(eOptionIDs::OPT_NTHREADS, ThreadCount);//uint args.
PARSEUINTOPTION(eOptionIDs::OPT_STRIPS, Strips);
PARSEUINTOPTION(eOptionIDs::OPT_SUPERSAMPLE, Supersample);
PARSEUINTOPTION(eOptionIDs::OPT_TEMPSAMPLES, TemporalSamples);
PARSEUINTOPTION(eOptionIDs::OPT_BPC, BitsPerChannel);
PARSEUINTOPTION(eOptionIDs::OPT_PRINT_EDIT_DEPTH, PrintEditDepth);
PARSEUINTOPTION(eOptionIDs::OPT_JPEG, JpegQuality);
PARSEUINTOPTION(eOptionIDs::OPT_BEGIN, FirstFrame);
PARSEUINTOPTION(eOptionIDs::OPT_END, LastFrame);
PARSEUINTOPTION(eOptionIDs::OPT_FRAME, Frame);
PARSEUINTOPTION(eOptionIDs::OPT_DTIME, Dtime);
PARSEUINTOPTION(eOptionIDs::OPT_NFRAMES, Frames);
PARSEUINTOPTION(eOptionIDs::OPT_REPEAT, Repeat);
PARSEUINTOPTION(eOptionIDs::OPT_TRIES, Tries);
PARSEUINTOPTION(eOptionIDs::OPT_MAX_XFORMS, MaxXforms);
PARSEUINTOPTION(eOptionIDs::OPT_START_COUNT, StartCount);
PARSEUINTOPTION(eOptionIDs::OPT_PADDING, Padding);
PARSEDOUBLEOPTION(eOptionIDs::OPT_SS, SizeScale);//Float args.
PARSEDOUBLEOPTION(eOptionIDs::OPT_QS, QualityScale);
PARSEDOUBLEOPTION(eOptionIDs::OPT_QUALITY, Quality);
PARSEDOUBLEOPTION(eOptionIDs::OPT_DE_MIN, DeMin);
PARSEDOUBLEOPTION(eOptionIDs::OPT_DE_MAX, DeMax);
PARSEDOUBLEOPTION(eOptionIDs::OPT_PIXEL_ASPECT, AspectRatio);
PARSEDOUBLEOPTION(eOptionIDs::OPT_STAGGER, Stagger);
PARSEDOUBLEOPTION(eOptionIDs::OPT_AVG_THRESH, AvgThresh);
PARSEDOUBLEOPTION(eOptionIDs::OPT_BLACK_THRESH, BlackThresh);
PARSEDOUBLEOPTION(eOptionIDs::OPT_WHITE_LIMIT, WhiteLimit);
PARSEDOUBLEOPTION(eOptionIDs::OPT_SPEED, Speed);
PARSEDOUBLEOPTION(eOptionIDs::OPT_OFFSETX, OffsetX);
PARSEDOUBLEOPTION(eOptionIDs::OPT_OFFSETY, OffsetY);
PARSEDOUBLEOPTION(eOptionIDs::OPT_USEMEM, UseMem);
PARSEDOUBLEOPTION(eOptionIDs::OPT_LOOPS, Loops);
PARSESTRINGOPTION(eOptionIDs::OPT_OPENCL_DEVICE, Device);//String args.
PARSESTRINGOPTION(eOptionIDs::OPT_ISAAC_SEED, IsaacSeed);
PARSESTRINGOPTION(eOptionIDs::OPT_IN, Input);
PARSESTRINGOPTION(eOptionIDs::OPT_OUT, Out);
PARSESTRINGOPTION(eOptionIDs::OPT_PREFIX, Prefix);
PARSESTRINGOPTION(eOptionIDs::OPT_SUFFIX, Suffix);
PARSESTRINGOPTION(eOptionIDs::OPT_FORMAT, Format);
PARSESTRINGOPTION(eOptionIDs::OPT_PALETTE_FILE, PalettePath);
PARSEOPTION(eOptionIDs::OPT_SYMMETRY, Symmetry);//Int args
PARSEOPTION(eOptionIDs::OPT_SHEEP_GEN, SheepGen);
PARSEOPTION(eOptionIDs::OPT_SHEEP_ID, SheepId);
PARSEOPTION(eOptionIDs::OPT_PRIORITY, Priority);
PARSEOPTION(eOptionIDs::OPT_NTHREADS, ThreadCount);//uint args.
PARSEOPTION(eOptionIDs::OPT_STRIPS, Strips);
PARSEOPTION(eOptionIDs::OPT_SUPERSAMPLE, Supersample);
PARSEOPTION(eOptionIDs::OPT_TEMPSAMPLES, TemporalSamples);
PARSEOPTION(eOptionIDs::OPT_BPC, BitsPerChannel);
PARSEOPTION(eOptionIDs::OPT_PRINT_EDIT_DEPTH, PrintEditDepth);
PARSEOPTION(eOptionIDs::OPT_JPEG, JpegQuality);
PARSEOPTION(eOptionIDs::OPT_BEGIN, FirstFrame);
PARSEOPTION(eOptionIDs::OPT_END, LastFrame);
PARSEOPTION(eOptionIDs::OPT_FRAME, Frame);
PARSEOPTION(eOptionIDs::OPT_DTIME, Dtime);
PARSEOPTION(eOptionIDs::OPT_LOOP_FRAMES, LoopFrames);
PARSEOPTION(eOptionIDs::OPT_INTERP_FRAMES, InterpFrames);
PARSEOPTION(eOptionIDs::OPT_INTERP_LOOPS, InterpLoops);
PARSEOPTION(eOptionIDs::OPT_REPEAT, Repeat);
PARSEOPTION(eOptionIDs::OPT_TRIES, Tries);
PARSEOPTION(eOptionIDs::OPT_MAX_XFORMS, MaxXforms);
PARSEOPTION(eOptionIDs::OPT_START_COUNT, StartCount);
PARSEOPTION(eOptionIDs::OPT_PADDING, Padding);
PARSEOPTION(eOptionIDs::OPT_SS, SizeScale);//Float args.
PARSEOPTION(eOptionIDs::OPT_QS, QualityScale);
PARSEOPTION(eOptionIDs::OPT_QUALITY, Quality);
PARSEOPTION(eOptionIDs::OPT_DE_MIN, DeMin);
PARSEOPTION(eOptionIDs::OPT_DE_MAX, DeMax);
PARSEOPTION(eOptionIDs::OPT_PIXEL_ASPECT, AspectRatio);
PARSEOPTION(eOptionIDs::OPT_STAGGER, Stagger);
PARSEOPTION(eOptionIDs::OPT_AVG_THRESH, AvgThresh);
PARSEOPTION(eOptionIDs::OPT_BLACK_THRESH, BlackThresh);
PARSEOPTION(eOptionIDs::OPT_WHITE_LIMIT, WhiteLimit);
PARSEOPTION(eOptionIDs::OPT_SPEED, Speed);
PARSEOPTION(eOptionIDs::OPT_OFFSETX, OffsetX);
PARSEOPTION(eOptionIDs::OPT_OFFSETY, OffsetY);
PARSEOPTION(eOptionIDs::OPT_USEMEM, UseMem);
PARSEOPTION(eOptionIDs::OPT_LOOPS, Loops);
PARSEOPTION(eOptionIDs::OPT_OPENCL_DEVICE, Device);//String args.
PARSEOPTION(eOptionIDs::OPT_ISAAC_SEED, IsaacSeed);
PARSEOPTION(eOptionIDs::OPT_IN, Input);
PARSEOPTION(eOptionIDs::OPT_OUT, Out);
PARSEOPTION(eOptionIDs::OPT_PREFIX, Prefix);
PARSEOPTION(eOptionIDs::OPT_SUFFIX, Suffix);
PARSEOPTION(eOptionIDs::OPT_FORMAT, Format);
PARSEOPTION(eOptionIDs::OPT_PALETTE_FILE, PalettePath);
//PARSESTRINGOPTION(eOptionIDs::OPT_PALETTE_IMAGE, PaletteImage);
PARSESTRINGOPTION(eOptionIDs::OPT_ID, Id);
PARSESTRINGOPTION(eOptionIDs::OPT_URL, Url);
PARSESTRINGOPTION(eOptionIDs::OPT_NICK, Nick);
PARSESTRINGOPTION(eOptionIDs::OPT_COMMENT, Comment);
PARSESTRINGOPTION(eOptionIDs::OPT_TEMPLATE, TemplateFile);
PARSESTRINGOPTION(eOptionIDs::OPT_CLONE, Clone);
PARSESTRINGOPTION(eOptionIDs::OPT_CLONE_ALL, CloneAll);
PARSESTRINGOPTION(eOptionIDs::OPT_CLONE_ACTION, CloneAction);
PARSESTRINGOPTION(eOptionIDs::OPT_ANIMATE, Animate);
PARSESTRINGOPTION(eOptionIDs::OPT_MUTATE, Mutate);
PARSESTRINGOPTION(eOptionIDs::OPT_CROSS0, Cross0);
PARSESTRINGOPTION(eOptionIDs::OPT_CROSS1, Cross1);
PARSESTRINGOPTION(eOptionIDs::OPT_METHOD, Method);
PARSESTRINGOPTION(eOptionIDs::OPT_INTER, Inter);
PARSESTRINGOPTION(eOptionIDs::OPT_ROTATE, Rotate);
PARSESTRINGOPTION(eOptionIDs::OPT_SEQUENCE, Sequence);
PARSESTRINGOPTION(eOptionIDs::OPT_USE_VARS, UseVars);
PARSESTRINGOPTION(eOptionIDs::OPT_DONT_USE_VARS, DontUseVars);
PARSESTRINGOPTION(eOptionIDs::OPT_EXTRAS, Extras);
PARSEOPTION(eOptionIDs::OPT_ID, Id);
PARSEOPTION(eOptionIDs::OPT_URL, Url);
PARSEOPTION(eOptionIDs::OPT_NICK, Nick);
PARSEOPTION(eOptionIDs::OPT_COMMENT, Comment);
PARSEOPTION(eOptionIDs::OPT_TEMPLATE, TemplateFile);
PARSEOPTION(eOptionIDs::OPT_CLONE, Clone);
PARSEOPTION(eOptionIDs::OPT_CLONE_ALL, CloneAll);
PARSEOPTION(eOptionIDs::OPT_CLONE_ACTION, CloneAction);
PARSEOPTION(eOptionIDs::OPT_ANIMATE, Animate);
PARSEOPTION(eOptionIDs::OPT_MUTATE, Mutate);
PARSEOPTION(eOptionIDs::OPT_CROSS0, Cross0);
PARSEOPTION(eOptionIDs::OPT_CROSS1, Cross1);
PARSEOPTION(eOptionIDs::OPT_METHOD, Method);
PARSEOPTION(eOptionIDs::OPT_INTER, Inter);
PARSEOPTION(eOptionIDs::OPT_ROTATE, Rotate);
PARSEOPTION(eOptionIDs::OPT_SEQUENCE, Sequence);
PARSEOPTION(eOptionIDs::OPT_USE_VARS, UseVars);
PARSEOPTION(eOptionIDs::OPT_DONT_USE_VARS, DontUseVars);
PARSEOPTION(eOptionIDs::OPT_EXTRAS, Extras);
default:
{
@ -775,6 +778,8 @@ public:
Eob Enclosed;
Eob NoEdits;
Eob UnsmoothEdge;
Eob CwLoops;
Eob CwInterpLoops;
Eob LockAccum;
Eob DumpKernel;
@ -794,7 +799,9 @@ public:
Eou LastFrame;
Eou Frame;
Eou Dtime;
Eou Frames;
Eou LoopFrames;
Eou InterpFrames;
Eou InterpLoops;
Eou Repeat;
Eou Tries;
Eou MaxXforms;

View File

@ -4,31 +4,6 @@
#define PNG_COMMENT_MAX 8
/// <summary>
/// Write a PPM file.
/// </summary>
/// <param name="filename">The full path and name of the file</param>
/// <param name="image">Pointer to the image data to write</param>
/// <param name="width">Width of the image in pixels</param>
/// <param name="height">Height of the image in pixels</param>
/// <returns>True if success, else false</returns>
static bool WritePpm(const char* filename, byte* image, size_t width, size_t height)
{
bool b = false;
size_t size = width * height * 3;
FILE* file;
if (fopen_s(&file, filename, "wb") == 0)
{
fprintf_s(file, "P6\n");
fprintf_s(file, "%lu %lu\n255\n", width, height);
b = (size == fwrite(image, 1, size, file));
fclose(file);
}
return b;
}
/// <summary>
/// Write a JPEG file.
/// </summary>
@ -53,15 +28,16 @@ static bool WriteJpeg(const char* filename, byte* image, size_t width, size_t he
size_t i;
jpeg_error_mgr jerr;
jpeg_compress_struct info;
char nickString[64], urlString[128], idString[128];
char bvString[64], niString[64], rtString[64];
char genomeString[65536], verString[64];
string nickString, urlString, idString;
string bvString, niString, rtString;
string genomeString, verString;
//Create the mandatory comment strings.
snprintf_s(genomeString, 65536, "flam3_genome: %s", comments.m_Genome.c_str());
snprintf_s(bvString, 64, "flam3_error_rate: %s", comments.m_Badvals.c_str());
snprintf_s(niString, 64, "flam3_samples: %s", comments.m_NumIters.c_str());
snprintf_s(rtString, 64, "flam3_time: %s", comments.m_Runtime.c_str());
snprintf_s(verString, 64, "flam3_version: %s", EmberVersion());
ostringstream os;
os << "genome: " << comments.m_Genome; genomeString = os.str(); os.str("");
os << "error_rate: " << comments.m_Badvals; bvString = os.str(); os.str("");
os << "samples: " << comments.m_NumIters; niString = os.str(); os.str("");
os << "time: " << comments.m_Runtime; rtString = os.str(); os.str("");
os << "version: " << EmberVersion(); verString = os.str(); os.str("");
info.err = jpeg_std_error(&jerr);
jpeg_create_compress(&info);
jpeg_stdio_dest(&info, file);
@ -83,36 +59,43 @@ static bool WriteJpeg(const char* filename, byte* image, size_t width, size_t he
//Write comments to jpeg.
if (enableComments)
{
jpeg_write_marker(&info, JPEG_COM, reinterpret_cast<byte*>(verString), uint(strlen(verString)));
string s;
jpeg_write_marker(&info, JPEG_COM, reinterpret_cast<const byte*>(verString.c_str()), uint(verString.size()));
if (nick != "")
{
snprintf_s(nickString, 64, "flam3_nickname: %s", nick.c_str());
jpeg_write_marker(&info, JPEG_COM, reinterpret_cast<byte*>(nickString), uint(strlen(nickString)));
os.str("");
os << "nickname: " << nick;
s = os.str();
jpeg_write_marker(&info, JPEG_COM, reinterpret_cast<const byte*>(s.c_str()), uint(s.size()));
}
if (url != "")
{
snprintf_s(urlString, 128, "flam3_url: %s", url.c_str());
jpeg_write_marker(&info, JPEG_COM, reinterpret_cast<byte*>(urlString), uint(strlen(urlString)));
os.str("");
os << "url: " << url;
s = os.str();
jpeg_write_marker(&info, JPEG_COM, reinterpret_cast<const byte*>(s.c_str()), uint(s.size()));
}
if (id != "")
{
snprintf_s(idString, 128, "flam3_id: %s", id.c_str());
jpeg_write_marker(&info, JPEG_COM, reinterpret_cast<byte*>(idString), uint(strlen(idString)));
os.str("");
os << "id: " << id;
s = os.str();
jpeg_write_marker(&info, JPEG_COM, reinterpret_cast<const byte*>(s.c_str()), uint(s.size()));
}
jpeg_write_marker(&info, JPEG_COM, reinterpret_cast<byte*>(bvString), uint(strlen(bvString)));
jpeg_write_marker(&info, JPEG_COM, reinterpret_cast<byte*>(niString), uint(strlen(niString)));
jpeg_write_marker(&info, JPEG_COM, reinterpret_cast<byte*>(rtString), uint(strlen(rtString)));
jpeg_write_marker(&info, JPEG_COM, reinterpret_cast<byte*>(genomeString), uint(strlen(genomeString)));
jpeg_write_marker(&info, JPEG_COM, reinterpret_cast<const byte*>(bvString.c_str()), uint(bvString.size()));
jpeg_write_marker(&info, JPEG_COM, reinterpret_cast<const byte*>(niString.c_str()), uint(niString.size()));
jpeg_write_marker(&info, JPEG_COM, reinterpret_cast<const byte*>(rtString.c_str()), uint(rtString.size()));
jpeg_write_marker(&info, JPEG_COM, reinterpret_cast<const byte*>(genomeString.c_str()), uint(genomeString.size()));
}
for (i = 0; i < height; i++)
{
JSAMPROW row_pointer[1];
row_pointer[0] = reinterpret_cast<byte*>(image) + (3 * width * i);
row_pointer[0] = image + (3 * width * i);
jpeg_write_scanlines(&info, row_pointer, 1);
}
@ -209,7 +192,7 @@ static bool WritePng(const char* filename, byte* image, size_t width, size_t hei
png_set_swap(png_ptr);
}
png_write_image(png_ptr, const_cast<png_bytepp>(rows.data()));
png_write_image(png_ptr, rows.data());
png_write_end(png_ptr, info_ptr);
png_destroy_write_struct(&png_ptr, &info_ptr);
fclose(file);
@ -227,10 +210,10 @@ static bool WritePng(const char* filename, byte* image, size_t width, size_t hei
/// <param name="height">The height.</param>
/// <param name="newSize">The size of the new buffer created</param>
/// <returns>The converted buffer if successful, else NULL.</returns>
static byte* ConvertRGBToBMPBuffer(byte* buffer, size_t width, size_t height, size_t& newSize)
static vector<byte> ConvertRGBToBMPBuffer(byte* buffer, size_t width, size_t height, size_t& newSize)
{
if (buffer == nullptr || width == 0 || height == 0)
return nullptr;
return vector<byte>();
size_t padding = 0;
size_t scanlinebytes = width * 3;
@ -240,34 +223,27 @@ static byte* ConvertRGBToBMPBuffer(byte* buffer, size_t width, size_t height, si
size_t psw = scanlinebytes + padding;
newSize = height * psw;
byte* newBuf = new byte[newSize];
vector<byte> newBuf(newSize);
size_t bufpos = 0;
size_t newpos = 0;
if (newBuf)
for (size_t y = 0; y < height; y++)
{
memset (newBuf, 0, newSize);
size_t bufpos = 0;
size_t newpos = 0;
for (size_t y = 0; y < height; y++)
for (size_t x = 0; x < 3 * width; x += 3)
{
for (size_t x = 0; x < 3 * width; x += 3)
{
bufpos = y * 3 * width + x; // position in original buffer
newpos = (height - y - 1) * psw + x; // position in padded buffer
newBuf[newpos] = buffer[bufpos + 2]; // swap r and b
newBuf[newpos + 1] = buffer[bufpos + 1]; // g stays
newBuf[newpos + 2] = buffer[bufpos]; // swap b and r
//No swap.
//newBuf[newpos] = buffer[bufpos];
//newBuf[newpos + 1] = buffer[bufpos + 1];
//newBuf[newpos + 2] = buffer[bufpos + 2];
}
bufpos = y * 3 * width + x; // position in original buffer
newpos = (height - y - 1) * psw + x; // position in padded buffer
newBuf[newpos] = buffer[bufpos + 2]; // swap r and b
newBuf[newpos + 1] = buffer[bufpos + 1]; // g stays
newBuf[newpos + 2] = buffer[bufpos]; // swap b and r
//No swap.
//newBuf[newpos] = buffer[bufpos];
//newBuf[newpos + 1] = buffer[bufpos + 1];
//newBuf[newpos + 2] = buffer[bufpos + 2];
}
return newBuf;
}
return nullptr;
return newBuf;
}
/// <summary>
@ -279,7 +255,7 @@ static byte* ConvertRGBToBMPBuffer(byte* buffer, size_t width, size_t height, si
/// <param name="height">Height of the image in pixels</param>
/// <param name="paddedSize">Padded size, greater than or equal to total image size.</param>
/// <returns>True if success, else false</returns>
static bool SaveBmp(const char* filename, byte* image, size_t width, size_t height, size_t paddedSize)
static bool SaveBmp(const char* filename, const byte* image, size_t width, size_t height, size_t paddedSize)
{
#ifdef _WIN32
BITMAPFILEHEADER bmfh;
@ -346,10 +322,7 @@ static bool WriteBmp(const char* filename, byte* image, size_t width, size_t hei
{
bool b = false;
size_t newSize;
unique_ptr<byte> bgrBuf(ConvertRGBToBMPBuffer(image, width, height, newSize));
if (bgrBuf.get())
b = SaveBmp(filename, bgrBuf.get(), width, height, newSize);
auto bgrBuf = ConvertRGBToBMPBuffer(image, width, height, newSize);
b = SaveBmp(filename, bgrBuf.data(), width, height, newSize);
return b;
}

View File

@ -453,9 +453,21 @@ bool EmberGenome(EmberOptions& opt)
{
Ember<T> result;
if (opt.Frames() == 0)
if (!opt.LoopFrames() && !opt.InterpFrames())
{
cerr << "nframes must be positive and non-zero, not " << opt.Frames() << ".\n";
cerr << "loop frames or interp frames must be positive and non-zero, not " << opt.LoopFrames() << ", " << opt.InterpFrames() << ".\n";
return false;
}
if (opt.LoopFrames() > 0 && !opt.Loops())
{
cerr << "loop frames cannot be positive while loops is zero: " << opt.LoopFrames() << ", " << opt.Loops() << ".\n";
return false;
}
if (opt.Loops() > 0 && !opt.LoopFrames())
{
cerr << "loops cannot be positive while loopframes is zero: " << opt.Loops() << ", " << opt.LoopFrames() << ".\n";
return false;
}
@ -465,19 +477,19 @@ bool EmberGenome(EmberOptions& opt)
frameCount = 0;
os.str("");
os << setfill('0') << setprecision(0) << fixed;
auto padding = opt.Padding() ? streamsize(opt.Padding()) : (streamsize(std::log10(opt.StartCount() + (((opt.Frames() * opt.Loops()) + opt.Frames()) * embers.size()))) + 1);
auto padding = opt.Padding() ? streamsize(opt.Padding()) : (streamsize(std::log10(opt.StartCount() + (((opt.LoopFrames() * opt.Loops()) + opt.InterpFrames()) * embers.size()))) + 1);
t.Tic();
for (i = 0; i < embers.size(); i++)
{
if (opt.Loops() > 0)
{
size_t roundFrames = size_t(std::round(opt.Frames() * opt.Loops()));
size_t roundFrames = size_t(std::round(opt.LoopFrames() * opt.Loops()));
for (frame = 0; frame < roundFrames; frame++)
{
blend = T(frame) / T(opt.Frames());
tools.Spin(embers[i], pTemplate, result, opt.StartCount() + frameCount++, blend);//Result is cleared and reassigned each time inside of Spin().
blend = T(frame) / T(opt.LoopFrames());
tools.Spin(embers[i], pTemplate, result, opt.StartCount() + frameCount++, blend, opt.CwLoops());//Result is cleared and reassigned each time inside of Spin().
FormatName(result, os, padding);
cout << emberToXml.ToString(result, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), opt.HexPalette());
}
@ -486,8 +498,8 @@ bool EmberGenome(EmberOptions& opt)
//Rotate the next step and save in result, but do not print.
//result will be the starting point for the interp phase below.
frame = roundFrames;
blend = T(frame) / T(opt.Frames());
tools.Spin(embers[i], pTemplate, result, opt.StartCount() + frameCount, blend);//Do not increment frameCount here.
blend = T(frame) / T(opt.LoopFrames());
tools.Spin(embers[i], pTemplate, result, opt.StartCount() + frameCount, blend, opt.CwLoops());//Do not increment frameCount here.
FormatName(result, os, padding);
}
@ -496,19 +508,19 @@ bool EmberGenome(EmberOptions& opt)
if (opt.Loops() > 0)//Store the last result as the flame to interpolate from. This applies for whole or fractional values of opt.Loops().
embers[i] = result;
for (frame = 0; frame < opt.Frames(); frame++)
for (frame = 0; frame < opt.InterpFrames(); frame++)
{
seqFlag = frame == 0 || (frame == opt.Frames() - 1);
blend = frame / T(opt.Frames());
seqFlag = frame == 0 || (frame == opt.InterpFrames() - 1);
blend = frame / T(opt.InterpFrames());
result.Clear();
tools.SpinInter(&embers[i], pTemplate, result, opt.StartCount() + frameCount++, seqFlag, blend);
tools.SpinInter(&embers[i], pTemplate, result, opt.StartCount() + frameCount++, seqFlag, blend, opt.InterpLoops(), opt.CwInterpLoops());
FormatName(result, os, padding);
cout << emberToXml.ToString(result, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), opt.HexPalette());
}
}
}
tools.Spin(embers.back(), pTemplate, result, opt.StartCount() + frameCount, 0);
tools.Spin(embers.back(), pTemplate, result, opt.StartCount() + frameCount, 0, opt.CwInterpLoops());
FormatName(result, os, padding);
cout << emberToXml.ToString(result, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), opt.HexPalette());
t.Toc("Sequencing");
@ -523,15 +535,15 @@ bool EmberGenome(EmberOptions& opt)
{
Ember<T> result, result1, result2, result3;
if (opt.Frames() == 0)
if (!opt.LoopFrames() && !opt.InterpFrames())
{
cerr << "nframes must be positive and non-zero, not " << opt.Frames() << ".\n";
cerr << "loop frames or interp frames must be positive and non-zero, not " << opt.LoopFrames() << ", " << opt.InterpFrames() << ".\n";
return false;
}
frame = opt.Frame();
blend = frame / T(opt.Frames());//Percentage between first and second flame to treat as the center flame.
spread = 1 / T(opt.Frames());//Amount to move backward and forward from the center flame.
blend = frame / T(opt.InterpFrames());//Percentage between first and second flame to treat as the center flame.
spread = 1 / T(opt.InterpFrames());//Amount to move backward and forward from the center flame.
if (opt.Enclosed())
cout << "<pick version=\"EMBER-" << EmberVersion() << "\">\n";
@ -546,12 +558,12 @@ bool EmberGenome(EmberOptions& opt)
if (frame)//Cannot spin backward below frame zero.
{
tools.Spin(embers[0], pTemplate, result1, frame - 1, blend - spread);
tools.Spin(embers[0], pTemplate, result1, frame - 1, blend - spread, opt.CwLoops());
cout << emberToXml.ToString(result1, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), opt.HexPalette());
}
tools.Spin(embers[0], pTemplate, result2, frame , blend );
tools.Spin(embers[0], pTemplate, result3, frame + 1, blend + spread);
tools.Spin(embers[0], pTemplate, result2, frame , blend , opt.CwLoops());
tools.Spin(embers[0], pTemplate, result3, frame + 1, blend + spread, opt.CwLoops());
cout << emberToXml.ToString(result2, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), opt.HexPalette());
cout << emberToXml.ToString(result3, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), opt.HexPalette());
}
@ -565,12 +577,12 @@ bool EmberGenome(EmberOptions& opt)
if (frame)//Cannot interpolate backward below frame zero.
{
tools.SpinInter(embers.data(), pTemplate, result1, frame - 1, false, blend - spread);
tools.SpinInter(embers.data(), pTemplate, result1, frame - 1, false, blend - spread, opt.InterpLoops(), opt.CwInterpLoops());
cout << emberToXml.ToString(result1, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), opt.HexPalette());
}
tools.SpinInter(embers.data(), pTemplate, result2, frame , false, blend );
tools.SpinInter(embers.data(), pTemplate, result3, frame + 1, false, blend + spread);
tools.SpinInter(embers.data(), pTemplate, result2, frame , false, blend , opt.InterpLoops(), opt.CwInterpLoops());
tools.SpinInter(embers.data(), pTemplate, result3, frame + 1, false, blend + spread, opt.InterpLoops(), opt.CwInterpLoops());
cout << emberToXml.ToString(result2, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), opt.HexPalette());
cout << emberToXml.ToString(result3, opt.Extras(), opt.PrintEditDepth(), !opt.NoEdits(), opt.HexPalette());
}

View File

@ -117,10 +117,9 @@ bool EmberRender(EmberOptions& opt)
if (opt.Format() != "jpg" &&
opt.Format() != "png" &&
opt.Format() != "ppm" &&
opt.Format() != "bmp")
{
cout << "Format must be jpg, png, ppm, or bmp not " << opt.Format() << ". Setting to jpg.\n";
cout << "Format must be jpg, png, or bmp not " << opt.Format() << ". Setting to jpg.\n";
}
channels = opt.Format() == "png" ? 4 : 3;
@ -302,8 +301,6 @@ bool EmberRender(EmberOptions& opt)
writeSuccess = WritePng(filename.c_str(), finalImagep, finalEmber.m_FinalRasW, finalEmber.m_FinalRasH, opt.BitsPerChannel() / 8, opt.PngComments(), comments, opt.Id(), opt.Url(), opt.Nick());
else if (opt.Format() == "jpg")
writeSuccess = WriteJpeg(filename.c_str(), finalImagep, finalEmber.m_FinalRasW, finalEmber.m_FinalRasH, int(opt.JpegQuality()), opt.JpegComments(), comments, opt.Id(), opt.Url(), opt.Nick());
else if (opt.Format() == "ppm")
writeSuccess = WritePpm(filename.c_str(), finalImagep, finalEmber.m_FinalRasW, finalEmber.m_FinalRasH);
else if (opt.Format() == "bmp")
writeSuccess = WriteBmp(filename.c_str(), finalImagep, finalEmber.m_FinalRasW, finalEmber.m_FinalRasH);

View File

@ -18,12 +18,9 @@ using namespace EmberCommon;
template <typename T>
void SaveFinalImage(Renderer<T, T>& renderer, vector<byte>& pixels, char* suffix)
{
Timing t;
//renderer.AccumulatorToFinalImage(pixels);
//t.Toc("AccumulatorToFinalImage()");
long newSize;
char ch[50];
sprintf_s(ch, 50, ".\\BasicFlame_%d_%s.bmp", sizeof(T), suffix);
ostringstream os;
os << ".\\BasicFlame_" << sizeof(T) << "_" << suffix ".bmp";
BYTE* bgrBuf = ConvertRGBToBMPBuffer(pixels.data(), renderer.FinalRasW(), renderer.FinalRasH(), newSize);
SaveBMP(ch, bgrBuf, renderer.FinalRasW(), renderer.FinalRasH(), newSize);
delete [] bgrBuf;
@ -1870,7 +1867,7 @@ void DistribTester()
throw "Out of bounds xform index in selection distribution.";
#endif
//printf("offset = %d, xform = %d, running sum = %f\n", j, i, tempDensity);
//cout << "offset = " << j << ", xform = " << i << ", running sum = " << tempDensity << "\n";
m_XformDistributions[(distrib * CHOOSE_XFORM_GRAIN) + j] = byte(i);
tempDensity += densityPerElement;
j++;

View File

@ -58,7 +58,7 @@
<enum>QFrame::NoFrame</enum>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;center&quot;&gt;&lt;br/&gt;Fractorium 1.0.0.1&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;A Qt-based fractal flame editor which uses a C++ re-write of the flam3 algorithm named Ember and a GPU capable version named EmberCL which implements a portion of the cuburn algorithm in OpenCL.&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;http://fractorium.com&quot;&gt;fractorium.com&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;&lt;br/&gt;Lead: Matt Feemster&lt;br/&gt;Contributors: Simon Detheridge&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;center&quot;&gt;&lt;br/&gt;Fractorium 1.0.0.2&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;A Qt-based fractal flame editor which uses a C++ re-write of the flam3 algorithm named Ember and a GPU capable version named EmberCL which implements a portion of the cuburn algorithm in OpenCL.&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;http://fractorium.com&quot;&gt;fractorium.com&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;&lt;br/&gt;Lead: Matt Feemster&lt;br/&gt;Contributors: Simon Detheridge&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>

View File

@ -1,5 +1,6 @@
#include "FractoriumPch.h"
#include "DoubleSpinBox.h"
#include "FractoriumSettings.h"
QTimer DoubleSpinBox::s_Timer;
@ -15,7 +16,6 @@ QTimer DoubleSpinBox::s_Timer;
DoubleSpinBox::DoubleSpinBox(QWidget* p, int h, double step)
: QDoubleSpinBox(p)
{
m_Select = false;
m_DoubleClick = false;
m_DoubleClickNonZero = 0;
m_DoubleClickZero = 1;
@ -159,36 +159,39 @@ void DoubleSpinBox::OnTimeout()
bool DoubleSpinBox::eventFilter(QObject* o, QEvent* e)
{
QMouseEvent* me = dynamic_cast<QMouseEvent*>(e);
auto settings = FractoriumSettings::DefInstance();
if (isEnabled() &&
me &&
me->type() == QMouseEvent::MouseButtonPress &&
me->button() == Qt::RightButton)
if (isEnabled() && me)
{
m_MouseDownPoint = m_MouseMovePoint = me->pos();
StartTimer();
}
else if (isEnabled() &&
me &&
me->type() == QMouseEvent::MouseButtonRelease &&
me->button() == Qt::RightButton)
{
StopTimer();
m_MouseDownPoint = m_MouseMovePoint = me->pos();
}
else if (isEnabled() &&
me &&
me->type() == QMouseEvent::MouseMove &&
QGuiApplication::mouseButtons() & Qt::RightButton)
{
m_MouseMovePoint = me->pos();
}
else if (m_DoubleClick && e->type() == QMouseEvent::MouseButtonDblClick && isEnabled())
{
if (IsNearZero(value()))
setValue(m_DoubleClickZero);
else
setValue(m_DoubleClickNonZero);
if (!settings->ToggleType() &&//Ensure double click toggles, not right click.
me->type() == QMouseEvent::MouseButtonPress &&
me->button() == Qt::RightButton)
{
m_MouseDownPoint = m_MouseMovePoint = me->pos();
StartTimer();
}
else if (!settings->ToggleType() &&
me->type() == QMouseEvent::MouseButtonRelease &&
me->button() == Qt::RightButton)
{
StopTimer();
m_MouseDownPoint = m_MouseMovePoint = me->pos();
}
else if (!settings->ToggleType() &&
me->type() == QMouseEvent::MouseMove &&
QGuiApplication::mouseButtons() & Qt::RightButton)
{
m_MouseMovePoint = me->pos();
}
else if (m_DoubleClick &&
((!settings->ToggleType() && e->type() == QMouseEvent::MouseButtonDblClick && me->button() == Qt::LeftButton) ||
(settings->ToggleType() && me->type() == QMouseEvent::MouseButtonRelease && me->button() == Qt::RightButton)))
{
if (IsNearZero(value()))
setValue(m_DoubleClickZero);
else
setValue(m_DoubleClickNonZero);
}
}
else
{

View File

@ -6,6 +6,8 @@
/// DoubleSpinBox and VariationTreeDoubleSpinBox classes.
/// </summary>
enum class eSpinToggle : et { NONE = 0, SPIN_DOUBLE_CLICK = 1, SPIN_RIGHT_CLICK = 2 };
/// <summary>
/// A derivation to prevent the spin box from selecting its own text
/// when editing. Also to prevent multiple spin boxes from all having
@ -43,7 +45,6 @@ private:
void StartTimer();
void StopTimer();
bool m_Select;
bool m_DoubleClick;
double m_DoubleClickNonZero;
double m_DoubleClickZero;

View File

@ -73,6 +73,7 @@ public:
m_Ember(ember)
{
setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
setCheckState(0, Qt::Unchecked);
}
/// <summary>
@ -86,6 +87,7 @@ public:
m_Ember(ember)
{
setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
setCheckState(0, Qt::Unchecked);
}
/// <summary>

View File

@ -10,7 +10,7 @@
/// <param name="settings">Pointer to the global settings object to use</param>
/// <param name="p">The parent widget</param>
/// <param name="f">The window flags. Default: 0.</param>
FractoriumFinalRenderDialog::FractoriumFinalRenderDialog(FractoriumSettings* settings, QWidget* p, Qt::WindowFlags f)
FractoriumFinalRenderDialog::FractoriumFinalRenderDialog(QWidget* p, Qt::WindowFlags f)
: QDialog(p, f)
{
ui.setupUi(this);
@ -19,7 +19,7 @@ FractoriumFinalRenderDialog::FractoriumFinalRenderDialog(FractoriumSettings* set
QTableWidget* table = ui.FinalRenderParamsTable;
m_Info = OpenCLInfo::Instance();
m_Fractorium = qobject_cast<Fractorium*>(p);
m_Settings = settings;
m_Settings = FractoriumSettings::DefInstance();
ui.FinalRenderThreadCountSpin->setRange(1, Timing::ProcessorCount());
connect(ui.FinalRenderEarlyClipCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnEarlyClipCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.FinalRenderYAxisUpCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnYAxisUpCheckBoxStateChanged(int)), Qt::QueuedConnection);

View File

@ -50,7 +50,7 @@ class FractoriumFinalRenderDialog : public QDialog
#endif
public:
FractoriumFinalRenderDialog(FractoriumSettings* settings, QWidget* p, Qt::WindowFlags f = 0);
FractoriumFinalRenderDialog(QWidget* p, Qt::WindowFlags f = 0);
void Show(bool fromSequence);
bool EarlyClip();
bool YAxisUp();
@ -132,7 +132,7 @@ private:
TwoButtonComboWidget* m_Tbcw;
QLineEdit* m_PrefixEdit;
QLineEdit* m_SuffixEdit;
FractoriumSettings* m_Settings;
shared_ptr<FractoriumSettings> m_Settings;
Fractorium* m_Fractorium;
shared_ptr<OpenCLInfo> m_Info;
vector<OpenCLWrapper> m_Wrappers;

View File

@ -14,7 +14,7 @@ FinalRenderEmberControllerBase::FinalRenderEmberControllerBase(FractoriumFinalRe
m_FinalRenderDialog(finalRenderDialog)
{
m_FinishedImageCount.store(0);
m_Settings = m_Fractorium->m_Settings;
m_Settings = FractoriumSettings::DefInstance();
}
/// <summary>
@ -28,8 +28,8 @@ void FinalRenderEmberController<T>::CancelRender()
{
if (m_Result.isRunning())
{
tbb::task_group g;
g.run([&]
//tbb::task_group g;
std::thread th([&]
{
m_Run = false;
@ -61,7 +61,7 @@ void FinalRenderEmberController<T>::CancelRender()
}
}
});
g.wait();
Join(th);
while (m_Result.isRunning())
QApplication::processEvents();

View File

@ -81,7 +81,7 @@ protected:
QFuture<void> m_Result;
std::function<void (void)> m_FinalRenderFunc;
FractoriumSettings* m_Settings;
shared_ptr<FractoriumSettings> m_Settings;
FractoriumFinalRenderDialog* m_FinalRenderDialog;
FinalRenderGuiState m_GuiState;
std::recursive_mutex m_ProgressCs;
@ -161,7 +161,7 @@ public:
using PreviewRenderer<T>::m_PreviewEmber;
using PreviewRenderer<T>::m_PreviewRenderer;
using PreviewRenderer<T>::m_PreviewFinalImage;
FinalRenderPreviewRenderer(FinalRenderEmberController<T>* controller) : m_Controller(controller)
{
}

View File

@ -45,13 +45,13 @@ Fractorium::Fractorium(QWidget* p)
m_VarSortMode = 1;//Sort by weight by default.
m_PaletteSortMode = 0;//Sort by palette ascending by default.
m_ColorDialog = new QColorDialog(this);
m_Settings = new FractoriumSettings(this);
m_Settings = FractoriumSettings::Instance();
m_QssDialog = new QssDialog(this);
m_FileDialog = nullptr;//Use lazy instantiation upon first use.
m_FolderDialog = nullptr;
m_FinalRenderDialog = new FractoriumFinalRenderDialog(m_Settings, this);
m_OptionsDialog = new FractoriumOptionsDialog(m_Settings, this);
m_VarDialog = new FractoriumVariationsDialog(m_Settings, this);
m_FinalRenderDialog = new FractoriumFinalRenderDialog(this);
m_OptionsDialog = new FractoriumOptionsDialog(this);
m_VarDialog = new FractoriumVariationsDialog(this);
m_AboutDialog = new FractoriumAboutDialog(this);
//Put the about dialog in the screen center.
const QRect screen = QApplication::desktop()->screenGeometry();
@ -157,6 +157,7 @@ Fractorium::Fractorium(QWidget* p)
if (ifs.is_open())
{
string total, qs;
total.reserve(20 * 1024);
while (std::getline(ifs, qs))
total += qs + "\n";
@ -213,9 +214,9 @@ Fractorium::~Fractorium()
/// <param name="worldY">The cartesian world y coordinate</param>
void Fractorium::SetCoordinateStatus(int rasX, int rasY, float worldX, float worldY)
{
//Use sprintf rather than allocating and concatenating 6 QStrings for efficiency since this is called on every mouse move.
sprintf_s(m_CoordinateString, sizeof(m_CoordinateString), "Window: %4d, %4d World: %2.2f, %2.2f", rasX, rasY, worldX, worldY);
m_CoordinateStatusLabel->setText(QString(m_CoordinateString));
static QString coords;
coords.sprintf("Window: %4d, %4d World: %2.2f, %2.2f", rasX, rasY, worldX, worldY);
m_CoordinateStatusLabel->setText(coords);
}
/// <summary>
@ -327,10 +328,10 @@ bool Fractorium::eventFilter(QObject* o, QEvent* e)
//Require shift for deleting to prevent it from triggering when the user enters delete in the edit box.
if (ke->key() == Qt::Key_Delete && e->type() == QEvent::KeyRelease && shift)
{
auto p = GetCurrentEmberIndex();
auto v = GetCurrentEmberIndex();
if (ui.LibraryTree->topLevelItem(0)->childCount() > 1 && p.second)
OnDelete(p);
if (ui.LibraryTree->topLevelItem(0)->childCount() > 1)
OnDelete(v);
}
}
}

View File

@ -162,7 +162,7 @@ public slots:
//Library.
void OnEmberTreeItemChanged(QTreeWidgetItem* item, int col);
void OnEmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col);
void OnDelete(const pair<size_t, QTreeWidgetItem*>& p);
void OnDelete(const vector<pair<size_t, QTreeWidgetItem*>>& v);
void OnSequenceTreeItemChanged(QTreeWidgetItem* item, int col);
void OnSequenceStartPreviewsButtonClicked(bool checked);
void OnSequenceStopPreviewsButtonClicked(bool checked);
@ -175,6 +175,7 @@ public slots:
void OnSequenceRandomizeFramesPerRotCheckBoxStateChanged(int state);
void OnSequenceRandomizeRotationsCheckBoxStateChanged(int state);
void OnSequenceRandomizeBlendFramesCheckBoxStateChanged(int state);
void OnSequenceRandomizeRotationsPerBlendCheckBoxStateChanged(int state);
void OnSequenceStaggerSpinBoxChanged(double d);
void OnSequenceRandomStaggerMaxSpinBoxChanged(double d);
void OnSequenceStartFlameSpinBoxChanged(int d);
@ -370,7 +371,7 @@ private:
void SyncOptionsToToolbar();
//Library.
pair<size_t, QTreeWidgetItem*> GetCurrentEmberIndex();
vector<pair<size_t, QTreeWidgetItem*>> GetCurrentEmberIndex();
void SyncSequenceSettings();
//Params.
@ -459,7 +460,7 @@ private:
DoubleSpinBox* m_XformWeightSpin;
SpinnerButtonWidget* m_XformWeightSpinnerButtonWidget;
QFormLayout* m_XformsSelectionLayout;
QVector<QCheckBox*> m_XformSelections;
vector<QCheckBox*> m_XformSelections;
//Xforms Color.
QTableWidgetItem* m_XformColorValueItem;
@ -522,13 +523,7 @@ private:
QProgressBar* m_ProgressBar;
QLabel* m_RenderStatusLabel;
QLabel* m_CoordinateStatusLabel;
FractoriumSettings* m_Settings;
char m_ULString[64];
char m_URString[64];
char m_LRString[64];
char m_LLString[64];
char m_WHString[64];
char m_DEString[64];
shared_ptr<FractoriumSettings> m_Settings;
char m_CoordinateString[128];
QColor m_XformComboColors[XFORM_COLOR_COUNT], m_FinalXformComboColor;
QIcon m_XformComboIcons[XFORM_COLOR_COUNT], m_FinalXformComboIcon;

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
#pragma once
#include "FractoriumPch.h"
#include "FractoriumSettings.h"
/// <summary>
/// Fractorium global utility functions.
@ -30,6 +31,7 @@
template<typename spinType, typename valType>
static void SetupSpinner(QTableWidget* table, const QObject* receiver, int& row, int col, spinType*& spinBox, int height, valType min, valType max, valType step, const char* signal, const char* slot, bool incRow = true, valType val = 0, valType doubleClickZero = -999, valType doubleClickNonZero = -999)
{
auto settings = FractoriumSettings::DefInstance();
spinBox = new spinType(table, height, step);
spinBox->setRange(min, max);
spinBox->setValue(val);
@ -450,3 +452,21 @@ bool ConstrainHigh(T* low, T* high)
return false;
}
/// <summary>
/// Move all, possibly disjointly, selected items in a range to
/// a new location and update all existing locations.
/// Atribution: Sean Parent, Going Native 2013.
/// </summary>
/// <param name="f">The location of the first selected item, or the start of the collection.</param>
/// <param name="l">The location of the last selected item, or the end of the collection.</param>
/// <returns>A pair of iterators representing the start and end locations of the list of newly moved items</returns>
template <typename I, typename S>
pair<I, I> Gather(I f, I l, I p, S s)
{
return
{
stable_partition(f, p, [&](const typename iterator_traits<I>::value_type & x) { return !s(x); }),
stable_partition(p, l, s)
};
}

View File

@ -151,7 +151,10 @@ void FractoriumEmberController<T>::SetEmber(size_t index, bool verbatim)
for (int i = 0; i < top->childCount(); i++)
{
if (auto emberItem = dynamic_cast<EmberTreeWidgetItem<T>*>(top->child(i)))
{
emberItem->setSelected(i == index);
emberItem->setCheckState(0, i == index ? Qt::Checked : Qt::Unchecked);
}
}
}
@ -228,9 +231,9 @@ void FractoriumEmberController<T>::UpdateXform(std::function<void(Xform<T>*)> fu
while (auto xform = m_Ember.GetTotalXform(i))
{
if (auto child = m_Fractorium->m_XformsSelectionLayout->itemAt(i))
if (i < m_Fractorium->m_XformSelections.size())
{
if (auto w = qobject_cast<QCheckBox*>(child->widget()))
if (auto w = m_Fractorium->m_XformSelections[i])
{
if (w->isChecked())
{
@ -257,9 +260,9 @@ void FractoriumEmberController<T>::UpdateXform(std::function<void(Xform<T>*)> fu
while (auto xform = (doFinal ? m_Ember.GetTotalXform(i) : m_Ember.GetXform(i)))
{
if (auto child = m_Fractorium->m_XformsSelectionLayout->itemAt(i))
if (i < m_Fractorium->m_XformSelections.size())
{
if (auto w = qobject_cast<QCheckBox*>(child->widget()))
if (auto w = m_Fractorium->m_XformSelections[i])
{
if (w->isChecked())
{

View File

@ -127,8 +127,8 @@ public:
virtual void StopLibraryPreviewRender() { }
virtual void StopSequencePreviewRender() { }
virtual void StopAllPreviewRenderers() { }
virtual void MoveLibraryItems(int startRow, int destRow) { }
virtual void Delete(const pair<size_t, QTreeWidgetItem*>& p) { }
virtual void MoveLibraryItems(const QModelIndexList& items, int destRow) { }
virtual void Delete(const vector<pair<size_t, QTreeWidgetItem*>>& v) { }
virtual void FillSequenceTree() { }
virtual void SequenceGenerateButtonClicked() { }
virtual void SequenceSaveButtonClicked() { }
@ -191,6 +191,8 @@ public:
virtual void RandomXformsAffine(bool pre) { }
virtual void FillBothAffines() { }
double LockedScale() { return m_LockedScale; }
double LockedX() { return m_LockedX; }
double LockedY() { return m_LockedY; }
void LockedScale(double scale) { m_LockedScale = scale; }
virtual void LockAffineScaleCheckBoxStateChanged(int state) { }
@ -265,6 +267,8 @@ protected:
uint m_FailedRenders = 0;
size_t m_UndoIndex = 0;
double m_LockedScale = 1;
double m_LockedX = 0;
double m_LockedY = 0;
eRendererType m_RenderType = eRendererType::CPU_RENDERER;
eEditUndoState m_EditState;
GLuint m_OutputTexID = 0;
@ -372,8 +376,8 @@ public:
virtual void SyncLibrary(eLibraryUpdate update) override;
virtual void FillLibraryTree(int selectIndex = -1) override;
virtual void UpdateLibraryTree() override;
virtual void MoveLibraryItems(int startRow, int destRow) override;
virtual void Delete(const pair<size_t, QTreeWidgetItem*>& p) override;
virtual void MoveLibraryItems(const QModelIndexList& items, int destRow) override;
virtual void Delete(const vector<pair<size_t, QTreeWidgetItem*>>& v) override;
virtual void EmberTreeItemChanged(QTreeWidgetItem* item, int col) override;
virtual void EmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col) override;
void RenderPreviews(QTreeWidget* tree, TreePreviewRenderer<T>* renderer, EmberFile<T>& file, uint start = UINT_MAX, uint end = UINT_MAX);
@ -477,6 +481,10 @@ public:
virtual void ClearXaos() override;
virtual void RandomXaos() override;
//Xforms Selection.
bool XformCheckboxAt(int i, std::function<void(QCheckBox*)> func);
bool XformCheckboxAt(Xform<T>* xform, std::function<void(QCheckBox*)> func);
//Palette.
virtual size_t InitPaletteList(const string& s) override;
virtual bool FillPaletteTable(const string& s) override;
@ -512,8 +520,6 @@ private:
//Xforms Selection.
QString MakeXformCaption(size_t i);
bool XformCheckboxAt(int i, std::function<void(QCheckBox*)> func);
bool XformCheckboxAt(Xform<T>* xform, std::function<void(QCheckBox*)> func);
//Palette.
void UpdateAdjustedPaletteGUI(Palette<T>& palette);
@ -624,7 +630,7 @@ public:
using PreviewRenderer<T>::m_PreviewEmber;
using PreviewRenderer<T>::m_PreviewRenderer;
using PreviewRenderer<T>::m_PreviewFinalImage;
/// <summary>
/// Initializes a new instance of the <see cref="TreePreviewRenderer{T}"/> class.
/// </summary>

View File

@ -183,25 +183,28 @@ void Fractorium::FillSummary()
/// </summary>
void Fractorium::UpdateHistogramBounds()
{
if (RendererBase* r = m_Controller->Renderer())
static QString ul, ur, lr, ll, wh, g, de;
if (auto r = m_Controller->Renderer())
{
sprintf_s(m_ULString, sizeof(m_ULString), "UL: %3.3f, %3.3f", r->LowerLeftX(), r->UpperRightY());//These bounds include gutter padding.
sprintf_s(m_URString, sizeof(m_URString), "UR: %3.3f, %3.3f", -r->LowerLeftX(), r->UpperRightY());
sprintf_s(m_LRString, sizeof(m_LRString), "LR: %3.3f, %3.3f", -r->LowerLeftX(), r->LowerLeftY());
sprintf_s(m_LLString, sizeof(m_LLString), "LL: %3.3f, %3.3f", r->LowerLeftX(), r->LowerLeftY());
sprintf_s(m_WHString, sizeof(m_WHString), "W x H: %4lu x %4lu", r->SuperRasW(), r->SuperRasH());
ui.InfoBoundsLabelUL->setText(QString(m_ULString));
ui.InfoBoundsLabelUR->setText(QString(m_URString));
ui.InfoBoundsLabelLR->setText(QString(m_LRString));
ui.InfoBoundsLabelLL->setText(QString(m_LLString));
ui.InfoBoundsLabelWH->setText(QString(m_WHString));
ui.InfoBoundsTable->item(0, 1)->setText(ToString<qulonglong>(r->GutterWidth()));
ul.sprintf("UL: %3.3f, %3.3f", r->LowerLeftX(), r->UpperRightY());//These bounds include gutter padding.
ur.sprintf("UR: %3.3f, %3.3f", -r->LowerLeftX(), r->UpperRightY());
lr.sprintf("LR: %3.3f, %3.3f", -r->LowerLeftX(), r->LowerLeftY());
ll.sprintf("LL: %3.3f, %3.3f", r->LowerLeftX(), r->LowerLeftY());
wh.sprintf("W x H: %4u x %4u", r->SuperRasW(), r->SuperRasH());
g.sprintf("%u", (uint)r->GutterWidth());
ui.InfoBoundsLabelUL->setText(ul);
ui.InfoBoundsLabelUR->setText(ur);
ui.InfoBoundsLabelLR->setText(lr);
ui.InfoBoundsLabelLL->setText(ll);
ui.InfoBoundsLabelWH->setText(wh);
ui.InfoBoundsTable->item(0, 1)->setText(g);
if (r->GetDensityFilter())
{
uint deWidth = (r->GetDensityFilter()->FilterWidth() * 2) + 1;
sprintf_s(m_DEString, sizeof(m_DEString), "%d x %d", deWidth, deWidth);
ui.InfoBoundsTable->item(1, 1)->setText(QString(m_DEString));
auto deWidth = (r->GetDensityFilter()->FilterWidth() * 2) + 1;
de.sprintf("%d x %d", deWidth, deWidth);
ui.InfoBoundsTable->item(1, 1)->setText(de);
}
else
ui.InfoBoundsTable->item(1, 1)->setText("N/A");

View File

@ -26,31 +26,33 @@ void Fractorium::InitLibraryUI()
connect(ui.SequenceRenderButton, SIGNAL(clicked(bool)), this, SLOT(OnSequenceRenderButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.SequenceSaveButton, SIGNAL(clicked(bool)), this, SLOT(OnSequenceSaveButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.SequenceOpenButton, SIGNAL(clicked(bool)), this, SLOT(OnSequenceOpenButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.SequenceRandomizeStaggerCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnSequenceRandomizeStaggerCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceRandomizeFramesPerRotCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnSequenceRandomizeFramesPerRotCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceRandomizeRotationsCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnSequenceRandomizeRotationsCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceRandomizeBlendFramesCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnSequenceRandomizeBlendFramesCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceStaggerSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnSequenceStaggerSpinBoxChanged(double)), Qt::QueuedConnection);
connect(ui.SequenceRandomStaggerMaxSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnSequenceRandomStaggerMaxSpinBoxChanged(double)), Qt::QueuedConnection);
connect(ui.SequenceStartFlameSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnSequenceStartFlameSpinBoxChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceStopFlameSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnSequenceStopFlameSpinBoxChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceFramesPerRotSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnSequenceFramesPerRotSpinBoxChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceRandomFramesPerRotMaxSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnSequenceRandomFramesPerRotMaxSpinBoxChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceRotationsSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnSequenceRotationsSpinBoxChanged(double)), Qt::QueuedConnection);
connect(ui.SequenceRandomRotationsMaxSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnSequenceRandomRotationsMaxSpinBoxChanged(double)), Qt::QueuedConnection);
connect(ui.SequenceBlendFramesSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnSequenceBlendFramesSpinBoxChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceRandomBlendMaxFramesSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnSequenceRandomBlendMaxFramesSpinBoxChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceRandomizeStaggerCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnSequenceRandomizeStaggerCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceRandomizeFramesPerRotCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnSequenceRandomizeFramesPerRotCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceRandomizeRotationsCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnSequenceRandomizeRotationsCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceRandomizeBlendFramesCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnSequenceRandomizeBlendFramesCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceRandomizeRotationsPerBlendCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnSequenceRandomizeRotationsPerBlendCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceStaggerSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnSequenceStaggerSpinBoxChanged(double)), Qt::QueuedConnection);
connect(ui.SequenceRandomStaggerMaxSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnSequenceRandomStaggerMaxSpinBoxChanged(double)), Qt::QueuedConnection);
connect(ui.SequenceStartFlameSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnSequenceStartFlameSpinBoxChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceStopFlameSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnSequenceStopFlameSpinBoxChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceFramesPerRotSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnSequenceFramesPerRotSpinBoxChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceRandomFramesPerRotMaxSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnSequenceRandomFramesPerRotMaxSpinBoxChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceRotationsSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnSequenceRotationsSpinBoxChanged(double)), Qt::QueuedConnection);
connect(ui.SequenceRandomRotationsMaxSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnSequenceRandomRotationsMaxSpinBoxChanged(double)), Qt::QueuedConnection);
connect(ui.SequenceBlendFramesSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnSequenceBlendFramesSpinBoxChanged(int)), Qt::QueuedConnection);
connect(ui.SequenceRandomBlendMaxFramesSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnSequenceRandomBlendMaxFramesSpinBoxChanged(int)), Qt::QueuedConnection);
}
/// <summary>
/// Get the index of the currently selected ember in the library tree.
/// </summary>
/// <returns>A pair containing the index of the item clicked and a pointer to the item</param>
pair<size_t, QTreeWidgetItem*> Fractorium::GetCurrentEmberIndex()
vector<pair<size_t, QTreeWidgetItem*>> Fractorium::GetCurrentEmberIndex()
{
int index = 0;
QTreeWidgetItem* item = nullptr;
auto tree = ui.LibraryTree;
vector<pair<size_t, QTreeWidgetItem*>> v;
if (auto top = tree->topLevelItem(0))
{
@ -58,14 +60,14 @@ pair<size_t, QTreeWidgetItem*> Fractorium::GetCurrentEmberIndex()
{
item = top->child(index);
if (item && !item->isSelected())
index++;
else
break;
if (item && item->isSelected())
v.push_back(make_pair(index, item));
index++;
}
}
return make_pair(index, item);
return v;
}
/// <summary>
@ -218,6 +220,9 @@ void FractoriumEmberController<T>::EmberTreeItemChanged(QTreeWidgetItem* item, i
if (auto emberItem = dynamic_cast<EmberTreeWidgetItem<T>*>(item))
{
if (!emberItem->isSelected())//Checking/unchecking other items shouldn't perform the processing below.
return;
if (emberItem->text(0).isEmpty())//Prevent empty string.
{
emberItem->UpdateEditText();
@ -225,11 +230,16 @@ void FractoriumEmberController<T>::EmberTreeItemChanged(QTreeWidgetItem* item, i
}
string oldName = emberItem->GetEmber()->m_Name;//First preserve the previous name.
string newName = emberItem->text(0).toStdString();
if (oldName == newName)//If nothing changed, nothing to do.
return;
tree->blockSignals(true);
emberItem->UpdateEmberName();//Copy edit text to the ember's name variable.
m_EmberFile.MakeNamesUnique();//Ensure all names remain unique.
SyncLibrary(eLibraryUpdate::NAME);//Copy all ember names to the tree items since some might have changed to be made unique.
string newName = emberItem->GetEmber()->m_Name;//Get the new, final, unique name.
newName = emberItem->GetEmber()->m_Name;//Get the new, final, unique name.
if (m_EmberFilePointer == emberItem->GetEmber() && oldName != newName)//If the ember edited was the current one, and the name was indeed changed, update the name of the current one.
{
@ -263,20 +273,15 @@ void Fractorium::OnEmberTreeItemChanged(QTreeWidgetItem* item, int col) { m_Cont
/// Clears the undo state.
/// Resets the rendering process.
/// Called when the user double clicks on a library tree item.
/// This will get called twice for some reason, and there's no way to prevent it.
/// Doesn't seem to cause any problems.
/// This will get called twice for some reason, so the check state is checked to prevent duplicate processing.
/// </summary>
/// <param name="item">The item double clicked on</param>
/// <param name="col">The column clicked, ignored.</param>
/// <param name="col">The column clicked</param>
template <typename T>
void FractoriumEmberController<T>::EmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col)
{
if (auto emberItem = dynamic_cast<EmberTreeWidgetItem<T>*>(item))
{
//qDebug() << "Setting current ember to: " << QString::fromStdString(emberItem->GetEmber()->m_Name);
ClearUndo();
SetEmber(*emberItem->GetEmber(), false, true);
}
if (item->checkState(col) == Qt::Unchecked)
SetEmber(m_Fractorium->ui.LibraryTree->currentIndex().row(), false);
}
void Fractorium::OnEmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col) { m_Controller->EmberTreeItemDoubleClicked(item, col); }
@ -287,32 +292,56 @@ void Fractorium::OnEmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col) {
/// <param name="startRow">The index of the source item to move</param>
/// <param name="destRow">The destination index to move the item to</param>
template <typename T>
void FractoriumEmberController<T>::MoveLibraryItems(int startRow, int destRow)
void FractoriumEmberController<T>::MoveLibraryItems(const QModelIndexList& items, int destRow)
{
int i = 0;
auto startRow = items[0].row();
auto tree = m_Fractorium->ui.LibraryTree;
auto top = tree->topLevelItem(0);
list<QString> names;
for (auto& item : items)
if (auto temp = m_EmberFile.Get(item.row()))
names.push_back(QString::fromStdString(temp->m_Name));
auto b = m_EmberFile.m_Embers.begin();
auto s = Advance(b, startRow);
auto d = Advance(b, destRow);
m_EmberFile.m_Embers.splice(d, m_EmberFile.m_Embers, s);
SyncLibrary(eLibraryUpdate::INDEX);//Only indices need syncing.
auto result = Gather(b, m_EmberFile.m_Embers.end(), Advance(b, destRow), [&](const Ember<T>& ember)
{
auto qname = QString::fromStdString(ember.m_Name);
auto position = std::find(names.begin(), names.end(), qname);
if (position != names.end())
{
names.erase(position);
return true;
}
return false;
});
SyncLibrary(eLibraryUpdate(eLibraryUpdate::INDEX | eLibraryUpdate::POINTER));
}
/// <summary>
/// Delete the currently selected item in the tree.
/// Note this is not necessarilly the current ember, it's just the item
/// in the tree that is selected.
/// Delete the currently selected items in the tree.
/// Note this is not necessarilly the current ember, it's just the items
/// in the tree that are selected.
/// </summary>
/// <param name="p">A pair containing the index of the item clicked and a pointer to the item</param>
/// <param name="v">A vector of pairs, each containing the index of the item selected and a pointer to the item</param>
template <typename T>
void FractoriumEmberController<T>::Delete(const pair<size_t, QTreeWidgetItem*>& p)
void FractoriumEmberController<T>::Delete(const vector<pair<size_t, QTreeWidgetItem*>>& v)
{
if (m_EmberFile.Delete(p.first))
size_t offset = 0;
for (auto& p : v)
{
delete p.second;
SyncLibrary(eLibraryUpdate::INDEX);
m_Fractorium->SyncFileCountToSequenceCount();
if (p.second && m_EmberFile.Delete(p.first - offset))
{
delete p.second;
SyncLibrary(eLibraryUpdate::INDEX);
m_Fractorium->SyncFileCountToSequenceCount();
}
offset++;
}
//If there is now only one item left and it wasn't selected, select it.
@ -327,10 +356,10 @@ void FractoriumEmberController<T>::Delete(const pair<size_t, QTreeWidgetItem*>&
/// Called when the user presses and releases the delete key while the library tree has the focus,
/// and an item is selected.
/// </summary>
/// <param name="p">A pair containing the index of the item clicked and a pointer to the item</param>
void Fractorium::OnDelete(const pair<size_t, QTreeWidgetItem*>& p)
/// <param name="v">A vector of pairs, each containing the index of the item selected and a pointer to the item</param>
void Fractorium::OnDelete(const vector<pair<size_t, QTreeWidgetItem*>>& v)
{
m_Controller->Delete(p);
m_Controller->Delete(v);
}
/// <summary>
@ -506,18 +535,30 @@ void FractoriumEmberController<T>::SequenceGenerateButtonClicked()
Ember<T> result;
auto& ui = m_Fractorium->ui;
auto s = m_Fractorium->m_Settings;
//Bools for determining whether to use hard coded vs. random values.
bool randStagger = ui.SequenceRandomizeStaggerCheckBox->isChecked();
bool randFramesRot = ui.SequenceRandomizeFramesPerRotCheckBox->isChecked();
bool randRot = ui.SequenceRandomizeRotationsCheckBox->isChecked();
bool randBlend = ui.SequenceRandomizeBlendFramesCheckBox->isChecked();
bool randBlendRot = ui.SequenceRandomizeRotationsPerBlendCheckBox->isChecked();
//The direction to rotate the loops.
bool loopsCw = ui.SequenceRotationsCWCheckBox->isChecked();
bool loopsBlendCw = ui.SequenceRotationsPerBlendCWCheckBox->isChecked();
//Whether to stagger, default is 1 which means no stagger.
double stagger = ui.SequenceStaggerSpinBox->value();
double staggerMax = ui.SequenceRandomStaggerMaxSpinBox->value();
//Rotations on keyframes.
double rots = ui.SequenceRotationsSpinBox->value();
double rotsMax = ui.SequenceRandomRotationsMaxSpinBox->value();
//Number of frames it takes to rotate a keyframe.
int framesPerRot = ui.SequenceFramesPerRotSpinBox->value();
int framesPerRotMax = ui.SequenceRandomFramesPerRotMaxSpinBox->value();
//Number of frames it takes to interpolate.
int framesBlend = ui.SequenceBlendFramesSpinBox->value();
int framesBlendMax = ui.SequenceRandomBlendMaxFramesSpinBox->value();
//Number of rotations performed during interpolation.
int rotsPerBlend = ui.SequenceRotationsPerBlendSpinBox->value();
int rotsPerBlendMax = ui.SequenceRotationsPerBlendMaxSpinBox->value();
size_t start = ui.SequenceStartFlameSpinBox->value();
size_t stop = ui.SequenceStopFlameSpinBox->value();
size_t startCount = ui.SequenceStartCountSpinBox->value();
@ -533,6 +574,24 @@ void FractoriumEmberController<T>::SequenceGenerateButtonClicked()
#else
"~/.config/fractorium";
#endif
if (!randRot && !randBlend)
{
if ((!rots || !framesPerRot) && !framesBlend)
{
QMessageBox::critical(m_Fractorium, "Animation sequence parameters error",
"Rotations and Frames per rot, or blend frames must be positive and non-zero");
return;
}
if (framesPerRot > 1 && !rots)//Because framesPerRot control has a min value of 1, check greater than 1. Also don't need to check the inverse like in EmberGenome.
{
QMessageBox::critical(m_Fractorium, "Animation sequence parameters error",
"Frames per rot cannot be positive while Rotations is zero");
return;
}
}
SheepTools<T, float> tools(palettePath, EmberCommon::CreateRenderer<T>(eRendererType::CPU_RENDERER, devices, false, 0, emberReport));
tools.SetSpinParams(true,
stagger,//Will be set again below if random is used.
@ -584,7 +643,7 @@ void FractoriumEmberController<T>::SequenceGenerateButtonClicked()
for (frame = 0; frame < roundFrames; frame++)
{
blend = frame / rotFrames;
tools.Spin(embers[0], nullptr, result, startCount + frameCount++, blend);//Result is cleared and reassigned each time inside of Spin().
tools.Spin(embers[0], nullptr, result, startCount + frameCount++, blend, loopsCw);//Result is cleared and reassigned each time inside of Spin().
FormatName(result, os, padding);
m_SequenceFile.m_Embers.push_back(result);
}
@ -594,7 +653,7 @@ void FractoriumEmberController<T>::SequenceGenerateButtonClicked()
//result will be the starting point for the interp phase below.
frame = roundFrames;
blend = frame / rotFrames;
tools.Spin(embers[0], nullptr, result, startCount + frameCount, blend);//Do not increment frameCount here.
tools.Spin(embers[0], nullptr, result, startCount + frameCount, blend, loopsCw);//Do not increment frameCount here.
FormatName(result, os, padding);
}
@ -606,6 +665,8 @@ void FractoriumEmberController<T>::SequenceGenerateButtonClicked()
auto it2 = it;//Need a quick temporary to avoid modifying it which is used in the loop.
embers[1] = *(++it2);//Get the next ember to be used with blending below.
size_t blendFrames = randBlend ? m_Rand.Frand<double>(framesBlend, framesBlendMax) : framesBlend;
double d = randBlendRot ? m_Rand.Frand<double>(rotsPerBlend, rotsPerBlendMax) : double(rotsPerBlend);
size_t rpb = size_t(std::round(d));
if (randStagger)
tools.Stagger(m_Rand.Frand<double>(stagger, staggerMax));
@ -615,7 +676,7 @@ void FractoriumEmberController<T>::SequenceGenerateButtonClicked()
bool seqFlag = frame == 0 || (frame == blendFrames - 1);
blend = frame / double(blendFrames);
result.Clear();
tools.SpinInter(&embers[0], nullptr, result, startCount + frameCount++, seqFlag, blend);
tools.SpinInter(&embers[0], nullptr, result, startCount + frameCount++, seqFlag, blend, rpb, loopsBlendCw);
FormatName(result, os, padding);
m_SequenceFile.m_Embers.push_back(result);
}
@ -623,7 +684,7 @@ void FractoriumEmberController<T>::SequenceGenerateButtonClicked()
}
it = Advance(m_EmberFile.m_Embers.begin(), stop);
tools.Spin(*it, nullptr, result, startCount + frameCount, 0);
tools.Spin(*it, nullptr, result, startCount + frameCount, 0, loopsBlendCw);
FormatName(result, os, padding);
m_SequenceFile.m_Embers.push_back(result);
FillSequenceTree();//The sequence has been generated, now create preview thumbnails.
@ -730,6 +791,7 @@ void Fractorium::OnSequenceRandomizeStaggerCheckBoxStateChanged(int state) { ui.
void Fractorium::OnSequenceRandomizeFramesPerRotCheckBoxStateChanged(int state) { ui.SequenceRandomFramesPerRotMaxSpinBox->setEnabled(state); }
void Fractorium::OnSequenceRandomizeRotationsCheckBoxStateChanged(int state) { ui.SequenceRandomRotationsMaxSpinBox->setEnabled(state); }
void Fractorium::OnSequenceRandomizeBlendFramesCheckBoxStateChanged(int state) { ui.SequenceRandomBlendMaxFramesSpinBox->setEnabled(state); }
void Fractorium::OnSequenceRandomizeRotationsPerBlendCheckBoxStateChanged(int state) { ui.SequenceRotationsPerBlendMaxSpinBox->setEnabled(state); }
/// <summary>
/// Constrain all min/max spinboxes.

View File

@ -642,6 +642,8 @@ bool Fractorium::CreateRendererFromOptions()
//If using OpenCL, will only get here if creating RendererCL failed, but creating a backup CPU Renderer succeeded.
ShowCritical("Renderer Creation Error", "Error creating renderer, most likely a GPU problem. Using CPU instead.");
m_Settings->OpenCL(false);
ui.ActionCpu->setChecked(true);
ui.ActionCL->setChecked(false);
m_OptionsDialog->ui.OpenCLCheckBox->setChecked(false);
m_FinalRenderDialog->ui.FinalRenderOpenCLCheckBox->setChecked(false);
ok = false;
@ -674,11 +676,9 @@ bool Fractorium::CreateControllerFromOptions()
double scale;
uint current = 0;
#ifdef DO_DOUBLE
Ember<double> ed;
EmberFile<double> efd;
Palette<double> tempPalette;
#else
Ember<float> ed;
EmberFile<float> efd;
Palette<float> tempPalette;
#endif
@ -689,16 +689,13 @@ bool Fractorium::CreateControllerFromOptions()
{
scale = m_Controller->LockedScale();
m_Controller->StopAllPreviewRenderers();//Must stop any previews first, else changing controllers will crash the program and SaveCurrentToOpenedFile() will return 0.
current = m_Controller->SaveCurrentToOpenedFile(false);
m_Controller->CopyTempPalette(tempPalette);//Convert float to double or save double verbatim;
current = m_Controller->SaveCurrentToOpenedFile(false);
//Replace below with this once LLVM fixes a crash in their compiler with default lambda parameters.//TODO
//m_Controller->CopyEmber(ed);
//m_Controller->CopyEmberFile(efd);
#ifdef DO_DOUBLE
m_Controller->CopyEmber(ed, [&](Ember<double>& ember) { });
m_Controller->CopyEmberFile(efd, false, [&](Ember<double>& ember) { });
#else
m_Controller->CopyEmber(ed, [&](Ember<float>& ember) { });
m_Controller->CopyEmberFile(efd, false, [&](Ember<float>& ember) { });
#endif
m_Controller->Shutdown();
@ -715,7 +712,9 @@ bool Fractorium::CreateControllerFromOptions()
//Restore the ember and ember file.
if (m_Controller.get())
{
ed.m_Palette = tempPalette;//Restore base temp palette. Adjustments will be then be applied and stored back in in m_Ember.m_Palette below.
if (auto prev = efd.Get(current))//Restore base temp palette. Adjustments will be then be applied and stored back in in m_Ember.m_Palette below.
prev->m_Palette = tempPalette;
m_Controller->SetEmberFile(efd, true);
m_Controller->SetEmber(current, true);
m_Controller->LockedScale(scale);

View File

@ -5,12 +5,11 @@
/// Constructor that passes the parent to the base and sets up reasonable defaults
/// if the settings file was not present or corrupted.
/// </summary>
/// <param name="p">The parent widget</param>
FractoriumSettings::FractoriumSettings(QObject* p)
FractoriumSettings::FractoriumSettings()
#ifdef _WIN32
: QSettings(QSettings::IniFormat, QSettings::UserScope, "Fractorium", "Fractorium", p)
: QSettings(QSettings::IniFormat, QSettings::UserScope, "Fractorium", "Fractorium", nullptr)
#else
: QSettings(QSettings::IniFormat, QSettings::UserScope, "fractorium", "fractorium", p)
: QSettings(QSettings::IniFormat, QSettings::UserScope, "fractorium", "fractorium", nullptr)
#endif
{
EnsureDefaults();
@ -110,6 +109,9 @@ void FractoriumSettings::Double(bool b) { setValue(DOUBLEPRECISION, b);
bool FractoriumSettings::ShowAllXforms() { return value(SHOWALLXFORMS).toBool(); }
void FractoriumSettings::ShowAllXforms(bool b) { setValue(SHOWALLXFORMS, b); }
bool FractoriumSettings::ToggleType() { return value(TOGGLETYPE).toBool(); }
void FractoriumSettings::ToggleType(bool b) { setValue(TOGGLETYPE, b); }
bool FractoriumSettings::ContinuousUpdate() { return value(CONTUPDATE).toBool(); }
void FractoriumSettings::ContinuousUpdate(bool b) { setValue(CONTUPDATE, b); }

View File

@ -13,6 +13,7 @@
#define DOUBLEPRECISION "render/dp64"
#define CONTUPDATE "render/continuousupdate"
#define SHOWALLXFORMS "render/dragshowallxforms"
#define TOGGLETYPE "render/toggletype"
#define DEVICES "render/devices"
#define THREADCOUNT "render/threadcount"
#define CPUDEFILTER "render/cpudefilter"
@ -86,11 +87,11 @@
/// runs of Fractorium. Each of these generally corresponds
/// to items in the options dialog and the final render dialog.
/// </summary>
class FractoriumSettings : public QSettings
class FractoriumSettings : public QSettings, public Singleton<FractoriumSettings>
{
Q_OBJECT
public:
FractoriumSettings(QObject* p);
SINGLETON_DERIVED_IMPL(FractoriumSettings);
void EnsureDefaults();
bool EarlyClip();
@ -111,6 +112,9 @@ public:
bool ShowAllXforms();
void ShowAllXforms(bool b);
bool ToggleType();
void ToggleType(bool b);
bool ContinuousUpdate();
void ContinuousUpdate(bool b);
@ -281,4 +285,7 @@ public:
QString Theme();
void Theme(const QString& s);
private:
FractoriumSettings();
};

View File

@ -228,7 +228,7 @@ void FractoriumEmberController<T>::DeleteXforms()
checked++;
//Do not allow deleting the only remaining non-final xform.
if (haveFinal && count <= 2 && i == 0)
if (haveFinal && count <= 2 && !(i - offset))
return;
if (!haveFinal && count == 1)
@ -354,10 +354,24 @@ void Fractorium::OnXformNameChanged(int row, int col) { m_Controller->XformNameC
template <typename T>
void FractoriumEmberController<T>::XformAnimateChanged(int state)
{
UpdateXform([&](Xform<T>* xform)
bool final = IsFinal(CurrentXform());
auto index = m_Fractorium->ui.CurrentXformCombo->currentIndex();
UpdateAll([&](Ember<T>& ember)
{
xform->m_Animate = state > 0 ? 1 : 0;
}, eXformUpdate::UPDATE_SELECTED, false);
if (final)//If the current xform was final, only apply to other embers which also have a final xform.
{
if (ember.UseFinalXform())
{
auto xform = ember.NonConstFinalXform();
xform->m_Animate = state > 0 ? 1 : 0;
}
}
else//Current was not final, so apply to other embers which have a non-final xform at this index.
{
if (auto xform = ember.GetXform(index))
xform->m_Animate = state > 0 ? 1 : 0;
}
}, false, eProcessAction::NOTHING, m_Fractorium->ApplyAll());
}
void Fractorium::OnXformAnimateCheckBoxStateChanged(int state) { m_Controller->XformAnimateChanged(state); }

View File

@ -163,6 +163,8 @@ template <typename T>
void FractoriumEmberController<T>::LockAffineScaleCheckBoxStateChanged(int state)
{
m_LockedScale = m_Ember.m_PixelsPerUnit;
m_LockedX = m_Ember.m_CenterX;
m_LockedY = m_Ember.m_CenterY;
m_Fractorium->ui.GLDisplay->update();
}

View File

@ -8,6 +8,9 @@ void Fractorium::InitXformsColorUI()
{
int spinHeight = 20, row = 0;
m_XformColorValueItem = new QTableWidgetItem();
//Can't set this in the designer, so do it here.
m_XformColorValueItem->setToolTip("The index in the palette the current xform uses.\r\n\r\n"
"This value can be changed by scrolling the mouse wheel in the box displaying the value or by dragging the scroll bar.");
ui.XformColorIndexTable->setItem(0, 0, m_XformColorValueItem);
m_PaletteRefItem = new QTableWidgetItem();
ui.XformPaletteRefTable->setItem(0, 0, m_PaletteRefItem);

View File

@ -31,8 +31,8 @@ void Fractorium::OnXformsSelectNoneButtonClicked(bool checked) { ForEachXformChe
/// <param name="checked">True if checked, else false.</param>
bool Fractorium::IsXformSelected(size_t i)
{
if (auto child = m_XformsSelectionLayout->itemAt(int(i)))
if (auto w = qobject_cast<QCheckBox*>(child->widget()))
if (i < m_XformSelections.size())
if (auto w = m_XformSelections[i])
return w->isChecked();
return false;
@ -70,7 +70,7 @@ QString FractoriumEmberController<T>::MakeXformCaption(size_t i)
bool isFinal = m_Ember.FinalXform() == m_Ember.GetTotalXform(i);
QString caption = isFinal ? "Final" : QString::number(i + 1);
if (Xform<T>* xform = m_Ember.GetTotalXform(i))
if (auto xform = m_Ember.GetTotalXform(i))
{
if (!xform->m_Name.empty())
caption += " (" + QString::fromStdString(xform->m_Name) + ")";
@ -87,13 +87,8 @@ void Fractorium::ForEachXformCheckbox(std::function<void(int, QCheckBox*)> func)
{
int i = 0;
while (QLayoutItem* child = m_XformsSelectionLayout->itemAt(i))
{
if (auto w = qobject_cast<QCheckBox*>(child->widget()))
func(i, w);
i++;
}
for (auto& cb : m_XformSelections)
func(i++, cb);
}
/// <summary>
@ -105,9 +100,9 @@ void Fractorium::ForEachXformCheckbox(std::function<void(int, QCheckBox*)> func)
template <typename T>
bool FractoriumEmberController<T>::XformCheckboxAt(int i, std::function<void(QCheckBox*)> func)
{
if (auto child = m_Fractorium->m_XformsSelectionLayout->itemAt(i))
if (i < m_Fractorium->m_XformSelections.size())
{
if (auto w = qobject_cast<QCheckBox*>(child->widget()))
if (auto w = m_Fractorium->m_XformSelections[i])
{
func(w);
return true;

View File

@ -19,7 +19,7 @@ enum class eHoverType : et { HoverNone, HoverXAxis, HoverYAxis, HoverTranslation
/// <summary>
/// Dragging an affine transform or panning, rotating or scaling the image.
/// </summary>
enum class eDragState : et { DragNone, DragPanning, DragDragging, DragRotateScale };
enum class eDragState : et { DragNone, DragSelect, DragPanning, DragDragging, DragRotateScale };
/// <summary>
/// Dragging with no keys pressed, shift, control or alt.

View File

@ -333,6 +333,22 @@ void GLEmberController<T>::DrawAffines(bool pre, bool post)
m_GL->glEnd();
m_GL->glPointSize(1.0f);//Restore point size.
}
else if (m_DragState == eDragState::DragSelect)
{
m_GL->glLineWidth(2.0f);
m_GL->glBegin(GL_LINES);
m_GL->glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
m_GL->glVertex2f(m_MouseDownWorldPos.x, m_MouseDownWorldPos.y);//UL->UR
m_GL->glVertex2f(m_MouseWorldPos.x, m_MouseDownWorldPos.y);
m_GL->glVertex2f(m_MouseDownWorldPos.x, m_MouseWorldPos.y);//LL->LR
m_GL->glVertex2f(m_MouseWorldPos.x, m_MouseWorldPos.y);
m_GL->glVertex2f(m_MouseDownWorldPos.x, m_MouseDownWorldPos.y);//UL->LL
m_GL->glVertex2f(m_MouseDownWorldPos.x, m_MouseWorldPos.y);
m_GL->glVertex2f(m_MouseWorldPos.x, m_MouseDownWorldPos.y);//UR->LR
m_GL->glVertex2f(m_MouseWorldPos.x, m_MouseWorldPos.y);
m_GL->glEnd();
m_GL->glLineWidth(1.0f);
}
else if (m_HoverType != eHoverType::HoverNone && m_HoverXform == m_SelectedXform)//Draw large turquoise dot on hover if they are hovering over the selected xform.
{
m_GL->glPointSize(6.0f);
@ -514,6 +530,9 @@ void GLEmberController<T>::MouseRelease(QMouseEvent* e)
if (m_DragState == eDragState::DragDragging && (e->button() & Qt::LeftButton))
UpdateHover(mouseFlipped);
if (m_DragState == eDragState::DragNone)
m_Fractorium->OnXformsSelectNoneButtonClicked(false);
m_DragState = eDragState::DragNone;
m_DragModifier = 0;
m_GL->repaint();//Force immediate redraw.
@ -571,6 +590,26 @@ void GLEmberController<T>::MouseMove(QMouseEvent* e)
m_FractoriumEmberController->FillAffineWithXform(m_SelectedXform, pre);//Update the spinners in the affine tab of the main window.
m_FractoriumEmberController->UpdateRender();//Restart the rendering process.
}
else if ((m_DragState == eDragState::DragNone || m_DragState == eDragState::DragSelect) && (e->buttons() & Qt::LeftButton))
{
m_DragState = eDragState::DragSelect;//Only set drag state once the user starts moving the mouse with the left button down.
//Iterate over each xform, seeing if it's in the bounding box.
QPointF tl(m_MouseDownWorldPos.x, m_MouseDownWorldPos.y);
QPointF br(m_MouseWorldPos.x, m_MouseWorldPos.y);
QRectF qrf(tl, br);
T scale = m_FractoriumEmberController->AffineScaleCurrentToLocked();
int i = 0;
m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform)
{
QPointF cd(xform->m_Affine.C() * scale, xform->m_Affine.F() * scale);
bool b = qrf.contains(cd);
m_FractoriumEmberController->XformCheckboxAt(i, [&](QCheckBox * cb)
{
cb->setChecked(b);
});
i++;
}, eXformUpdate::UPDATE_ALL, false);
}
else if (m_DragState == eDragState::DragPanning)//Translating the whole image.
{
T x = -(m_MouseWorldPos.x - m_MouseDownWorldPos.x);

View File

@ -18,7 +18,7 @@ void LibraryTreeWidget::SetMainWindow(Fractorium* f)
void LibraryTreeWidget::dropEvent(QDropEvent* de)
{
QModelIndex droppedIndex = indexAt(de->pos());
auto items = selectionModel()->selectedIndexes();
auto items = selectionModel()->selectedRows();
if (!droppedIndex.isValid())//Don't process drop because it's outside of the droppable area.
{
@ -33,7 +33,7 @@ void LibraryTreeWidget::dropEvent(QDropEvent* de)
if (dp == QAbstractItemView::BelowItem)
row++;
m_Fractorium->m_Controller->MoveLibraryItems(items[0].row(), row);
QTimer::singleShot(500, [ = ]() { m_Fractorium->m_Controller->MoveLibraryItems(items, row); });//Need to fire this after this event has internally reshuffled the items.
}
QTreeWidget::dropEvent(de);

View File

@ -8,12 +8,12 @@
/// <param name="settings">A pointer to the settings object to use</param>
/// <param name="p">The parent widget. Default: nullptr.</param>
/// <param name="f">The window flags. Default: 0.</param>
FractoriumOptionsDialog::FractoriumOptionsDialog(FractoriumSettings* settings, QWidget* p, Qt::WindowFlags f)
FractoriumOptionsDialog::FractoriumOptionsDialog(QWidget* p, Qt::WindowFlags f)
: QDialog(p, f)
{
int i, row = 0, spinHeight = 20;
ui.setupUi(this);
m_Settings = settings;
m_Settings = FractoriumSettings::DefInstance();
m_Info = OpenCLInfo::Instance();
QTableWidget* table = ui.OptionsXmlSavingTable;
ui.ThreadCountSpin->setRange(1, Timing::ProcessorCount());
@ -66,6 +66,7 @@ bool FractoriumOptionsDialog::ContinuousUpdate() { return ui.ContinuousUpdateChe
bool FractoriumOptionsDialog::OpenCL() { return ui.OpenCLCheckBox->isChecked(); }
bool FractoriumOptionsDialog::Double() { return ui.DoublePrecisionCheckBox->isChecked(); }
bool FractoriumOptionsDialog::ShowAllXforms() { return ui.ShowAllXformsCheckBox->isChecked(); }
bool FractoriumOptionsDialog::ToggleType() { return ui.ToggleTypeCheckBox->isChecked(); }
bool FractoriumOptionsDialog::AutoUnique() { return ui.AutoUniqueCheckBox->isChecked(); }
uint FractoriumOptionsDialog::ThreadCount() { return ui.ThreadCountSpin->value(); }
uint FractoriumOptionsDialog::RandomCount() { return ui.RandomCountSpin->value(); }
@ -172,6 +173,7 @@ void FractoriumOptionsDialog::GuiToData()
m_Settings->OpenCL(OpenCL());
m_Settings->Double(Double());
m_Settings->ShowAllXforms(ShowAllXforms());
m_Settings->ToggleType(ToggleType());
m_Settings->ThreadCount(ThreadCount());
m_Settings->RandomCount(RandomCount());
m_Settings->CpuSubBatch(ui.CpuSubBatchSpin->value());
@ -204,6 +206,7 @@ void FractoriumOptionsDialog::DataToGui()
ui.OpenCLCheckBox->setChecked(m_Settings->OpenCL());
ui.DoublePrecisionCheckBox->setChecked(m_Settings->Double());
ui.ShowAllXformsCheckBox->setChecked(m_Settings->ShowAllXforms());
ui.ToggleTypeCheckBox->setChecked(m_Settings->ToggleType());
ui.ThreadCountSpin->setValue(m_Settings->ThreadCount());
ui.RandomCountSpin->setValue(m_Settings->RandomCount());
ui.CpuSubBatchSpin->setValue(m_Settings->CpuSubBatch());

View File

@ -24,7 +24,7 @@ class FractoriumOptionsDialog : public QDialog
friend Fractorium;
public:
FractoriumOptionsDialog(FractoriumSettings* settings, QWidget* p = nullptr, Qt::WindowFlags f = 0);
FractoriumOptionsDialog(QWidget* p = nullptr, Qt::WindowFlags f = 0);
public slots:
void OnOpenCLCheckBoxStateChanged(int state);
@ -45,6 +45,7 @@ private:
bool OpenCL();
bool Double();
bool ShowAllXforms();
bool ToggleType();
bool AutoUnique();
uint ThreadCount();
uint RandomCount();
@ -59,5 +60,5 @@ private:
QLineEdit* m_IdEdit;
QLineEdit* m_UrlEdit;
QLineEdit* m_NickEdit;
FractoriumSettings* m_Settings;
shared_ptr<FractoriumSettings> m_Settings;
};

View File

@ -414,6 +414,16 @@ in interactive mode for each mouse movement</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="ToggleTypeCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Checked: right clicking toggles spin boxes, right button dragging disabled.&lt;/p&gt;&lt;p&gt;Unchecked: double clicking toggles spin boxes, right button dragging enabled.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Right Click Toggles Spinboxes</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="OptionsXmlSavingTab">

View File

@ -57,27 +57,22 @@ QssDialog::QssDialog(Fractorium* parent) :
m_LastStyle = m_Parent->styleSheet();
setWindowTitle("QSS Editor - default.qss");
connect(ui->QssEdit, SIGNAL(textChanged()), this, SLOT(SlotTextChanged()));
QToolBar* toolBar = new QToolBar(this);
QMenu* colorActionMenu = new QMenu(this);
QMenu* geomActionMenu = new QMenu(this);
QMenu* borderActionMenu = new QMenu(this);
QMenu* styleActionMenu = new QMenu(this);
(m_ColorActionMapper = new QSignalMapper(this))->setMapping(m_AddColorAction, QString());
(m_GeomActionMapper = new QSignalMapper(this))->setMapping(m_AddGeomAction, QString());
(m_BorderActionMapper = new QSignalMapper(this))->setMapping(m_AddBorderAction, QString());
(m_StyleActionMapper = new QSignalMapper(this))->setMapping(m_AddStyleAction, QString());
connect(ui->QssLoadButton, SIGNAL(clicked()), this, SLOT(LoadButton_clicked()), Qt::QueuedConnection);
connect(ui->QssSaveButton, SIGNAL(clicked()), this, SLOT(SaveButton_clicked()), Qt::QueuedConnection);
connect(ui->QssBasicButton, SIGNAL(clicked()), this, SLOT(BasicButton_clicked()), Qt::QueuedConnection);
connect(ui->QssMediumButton, SIGNAL(clicked()), this, SLOT(MediumButton_clicked()), Qt::QueuedConnection);
connect(ui->QssAdvancedButton, SIGNAL(clicked()), this, SLOT(AdvancedButton_clicked()), Qt::QueuedConnection);
connect(m_AddFontAction, SIGNAL(triggered()), this, SLOT(SlotAddFont()));
QVector<QPair<QString, QString>> colorVec;
colorVec.reserve(12);
colorVec.push_back(QPair<QString, QString>("color", ""));
colorVec.push_back(QPair<QString, QString>("background-color", ""));
@ -94,14 +89,12 @@ QssDialog::QssDialog(Fractorium* parent) :
for (auto& c : colorVec)
{
auto colorAction = colorActionMenu->addAction(c.first);
m_ColorMap[c.first] = c.second;
connect(colorAction, SIGNAL(triggered()), m_ColorActionMapper, SLOT(map()));
m_ColorActionMapper->setMapping(colorAction, c.first);
}
QVector<QPair<QString, QString>> geomVec;
geomVec.reserve(12);
geomVec.push_back(QPair<QString, QString>("width", "100px"));
geomVec.push_back(QPair<QString, QString>("height", "50px"));
@ -120,14 +113,12 @@ QssDialog::QssDialog(Fractorium* parent) :
for (auto& g : geomVec)
{
auto geomAction = geomActionMenu->addAction(g.first);
m_GeomMap[g.first] = g.second;
connect(geomAction, SIGNAL(triggered()), m_GeomActionMapper, SLOT(map()));
m_GeomActionMapper->setMapping(geomAction, g.first);
}
QVector<QPair<QString, QString>> borderVec;
borderVec.reserve(8);
borderVec.push_back(QPair<QString, QString>("border", "1px solid black"));
borderVec.push_back(QPair<QString, QString>("border-top", "1px inset black"));
@ -141,18 +132,16 @@ QssDialog::QssDialog(Fractorium* parent) :
for (auto& b : borderVec)
{
auto borderAction = borderActionMenu->addAction(b.first);
m_BorderMap[b.first] = b.second;
connect(borderAction, SIGNAL(triggered()), m_BorderActionMapper, SLOT(map()));
m_BorderActionMapper->setMapping(borderAction, b.first);
}
auto styles = QStyleFactory::keys();
for (auto& s : styles)
{
auto styleAction = styleActionMenu->addAction(s);
m_StyleMap[s] = s;
connect(styleAction, SIGNAL(triggered()), m_StyleActionMapper, SLOT(map()));
m_StyleActionMapper->setMapping(styleAction, s);
@ -162,12 +151,10 @@ QssDialog::QssDialog(Fractorium* parent) :
connect(m_GeomActionMapper, SIGNAL(mapped(QString)), this, SLOT(SlotAddGeom(QString)));
connect(m_BorderActionMapper, SIGNAL(mapped(QString)), this, SLOT(SlotAddBorder(QString)));
connect(m_StyleActionMapper, SIGNAL(mapped(QString)), this, SLOT(SlotSetTheme(QString)));
m_AddColorAction->setMenu(colorActionMenu);
m_AddGeomAction->setMenu(geomActionMenu);
m_AddBorderAction->setMenu(borderActionMenu);
m_AddStyleAction->setMenu(styleActionMenu);
toolBar->addAction(m_AddColorAction);
toolBar->addAction(m_AddGeomAction);
toolBar->addAction(m_AddBorderAction);
@ -237,7 +224,6 @@ QList<QString> QssDialog::GetClassNames(bool includeObjectNames)
{
QSet<QString> dlgSet;
auto dlgWidgetList = dlg->findChildren<QWidget*>();//Find all children of the dialog.
dlgSet.insert(classAndName);//Add the basic dialog class name, opening curly brace will be added later.
classAndName += " ";
@ -290,8 +276,10 @@ bool QssDialog::IsStyleSheetValid(const QString& styleSheet)
{
QCss::Parser parser(styleSheet);
QCss::StyleSheet sheet;
if (parser.parse(&sheet))
return true;
QString fullSheet = QStringLiteral("* { ");
fullSheet += styleSheet;
fullSheet += QLatin1Char('}');
@ -344,7 +332,7 @@ void QssDialog::showEvent(QShowEvent* e)
m_LastTheme = m_Parent->m_Theme;//The style() member cannot be relied upon, it is *not* the same object passed to setStyle();
SetText(m_LastStyle);
}
QDialog::showEvent(e);
}
@ -378,12 +366,12 @@ void QssDialog::SlotAddColor(const QString& s)
if (color.alpha() == 255)
{
colorStr = QString(QStringLiteral("rgb(%1, %2, %3)")).arg(
color.red()).arg(color.green()).arg(color.blue());
color.red()).arg(color.green()).arg(color.blue());
}
else
{
colorStr = QString(QStringLiteral("rgba(%1, %2, %3, %4)")).arg(
color.red()).arg(color.green()).arg(color.blue()).arg(color.alpha());
color.red()).arg(color.green()).arg(color.blue()).arg(color.alpha());
}
InsertCssProperty(s, colorStr);
@ -396,7 +384,6 @@ void QssDialog::SlotAddColor(const QString& s)
void QssDialog::SlotAddGeom(const QString& s)
{
auto val = m_GeomMap[s];
InsertCssProperty(s, val);
}
@ -407,7 +394,6 @@ void QssDialog::SlotAddGeom(const QString& s)
void QssDialog::SlotAddBorder(const QString& s)
{
auto val = m_BorderMap[s];
InsertCssProperty(s, val);
}
@ -446,14 +432,16 @@ void QssDialog::SlotAddFont()
switch (font.style())
{
case QFont::StyleItalic:
fontStr += QStringLiteral("italic ");
break;
case QFont::StyleOblique:
fontStr += QStringLiteral("oblique ");
break;
default:
break;
case QFont::StyleItalic:
fontStr += QStringLiteral("italic ");
break;
case QFont::StyleOblique:
fontStr += QStringLiteral("oblique ");
break;
default:
break;
}
fontStr += QString::number(font.pointSize());
@ -526,10 +514,7 @@ void QssDialog::SaveButton_clicked()
string s = Text().toStdString();
if (of.is_open())
{
of << s;
of.close();
}
else
QMessageBox::critical(this, "File save error", "Failed to save " + path + ", style will not be set as default");
}
@ -547,10 +532,7 @@ void QssDialog::SaveAsDefault()
auto s = Text().toStdString();
if (of.is_open())
{
of << s;
of.close();
}
else
QMessageBox::critical(this, "File save error", "Failed to save " + path + ", style will not be set as default");
}
@ -615,13 +597,12 @@ void QssDialog::InsertCssProperty(const QString& name, const QString& value)
cursor.beginEditBlock();
cursor.removeSelectedText();
cursor.movePosition(QTextCursor::EndOfLine);
//Simple check to see if we're in a selector scope.
const QTextDocument* doc = editor->document();
const QTextCursor closing = doc->find(QStringLiteral("}"), cursor, QTextDocument::FindBackward);
const QTextCursor opening = doc->find(QStringLiteral("{"), cursor, QTextDocument::FindBackward);
const bool inSelector = !opening.isNull() && (closing.isNull() ||
closing.position() < opening.position());
closing.position() < opening.position());
QString insertion;
//Reasonable attempt at positioning things correctly. This can and often is wrong, but is sufficient for our purposes.
@ -666,9 +647,7 @@ void QssDialog::SetupFileDialog()
QString QssDialog::OpenFile()
{
QStringList filenames;
SetupFileDialog();
m_FileDialog->setFileMode(QFileDialog::ExistingFile);
m_FileDialog->setAcceptMode(QFileDialog::AcceptOpen);
m_FileDialog->setNameFilter("Qss (*.qss)");
@ -688,7 +667,6 @@ QString QssDialog::OpenFile()
QString QssDialog::SaveFile()
{
QStringList filenames;
SetupFileDialog();
m_FileDialog->setFileMode(QFileDialog::AnyFile);
m_FileDialog->setAcceptMode(QFileDialog::AcceptSave);

View File

@ -1,5 +1,6 @@
#include "FractoriumPch.h"
#include "SpinBox.h"
#include "FractoriumSettings.h"
QTimer SpinBox::s_Timer;
@ -15,7 +16,6 @@ QTimer SpinBox::s_Timer;
SpinBox::SpinBox(QWidget* p, int h, int step)
: QSpinBox(p)
{
m_Select = false;
m_DoubleClick = false;
m_DoubleClickNonZero = 0;
m_DoubleClickZero = 1;
@ -135,36 +135,39 @@ void SpinBox::OnTimeout()
bool SpinBox::eventFilter(QObject* o, QEvent* e)
{
QMouseEvent* me = dynamic_cast<QMouseEvent*>(e);
auto settings = FractoriumSettings::DefInstance();
if (isEnabled() &&
me &&
me->type() == QMouseEvent::MouseButtonPress &&
me->button() == Qt::RightButton)
if (isEnabled() && me)
{
m_MouseDownPoint = m_MouseMovePoint = me->pos();
StartTimer();
}
else if (isEnabled() &&
me &&
me->type() == QMouseEvent::MouseButtonRelease &&
me->button() == Qt::RightButton)
{
StopTimer();
m_MouseDownPoint = m_MouseMovePoint = me->pos();
}
else if (isEnabled() &&
me &&
me->type() == QMouseEvent::MouseMove &&
QGuiApplication::mouseButtons() & Qt::RightButton)
{
m_MouseMovePoint = me->pos();
}
else if (m_DoubleClick && e->type() == QMouseEvent::MouseButtonDblClick && isEnabled())
{
if (value() == 0)
setValue(m_DoubleClickZero);
else
setValue(m_DoubleClickNonZero);
if (!settings->ToggleType() &&//Ensure double click toggles, not right click.
me->type() == QMouseEvent::MouseButtonPress &&
me->button() == Qt::RightButton)
{
m_MouseDownPoint = m_MouseMovePoint = me->pos();
StartTimer();
}
else if (!settings->ToggleType() &&
me->type() == QMouseEvent::MouseButtonRelease &&
me->button() == Qt::RightButton)
{
StopTimer();
m_MouseDownPoint = m_MouseMovePoint = me->pos();
}
else if (!settings->ToggleType() &&
me->type() == QMouseEvent::MouseMove &&
QGuiApplication::mouseButtons() & Qt::RightButton)
{
m_MouseMovePoint = me->pos();
}
else if (m_DoubleClick &&
((!settings->ToggleType() && e->type() == QMouseEvent::MouseButtonDblClick && me->button() == Qt::LeftButton) ||
(settings->ToggleType() && me->type() == QMouseEvent::MouseButtonRelease && me->button() == Qt::RightButton)))
{
if (IsNearZero(value()))
setValue(m_DoubleClickZero);
else
setValue(m_DoubleClickNonZero);
}
}
else
{
@ -172,9 +175,9 @@ bool SpinBox::eventFilter(QObject* o, QEvent* e)
{
//Take special action for shift to reduce the scroll amount. Control already
//increases it automatically.
if (QWheelEvent* wev = dynamic_cast<QWheelEvent*>(e))
if (QWheelEvent* we = dynamic_cast<QWheelEvent*>(e))
{
Qt::KeyboardModifiers mod = wev->modifiers();
Qt::KeyboardModifiers mod = we->modifiers();
if (mod.testFlag(Qt::ShiftModifier))
setSingleStep(m_SmallStep);

View File

@ -1,6 +1,7 @@
#pragma once
#include "FractoriumPch.h"
#include "DoubleSpinBox.h"
/// <summary>
/// SpinBox class.
@ -41,7 +42,6 @@ private:
void StartTimer();
void StopTimer();
bool m_Select;
bool m_DoubleClick;
int m_DoubleClickNonZero;
int m_DoubleClickZero;

View File

@ -8,13 +8,13 @@
/// <param name="settings">Pointer to the global settings object to use</param>
/// <param name="p">The parent widget. Default: nullptr.</param>
/// <param name="f">The window flags. Default: 0.</param>
FractoriumVariationsDialog::FractoriumVariationsDialog(FractoriumSettings* settings, QWidget* p, Qt::WindowFlags f)
FractoriumVariationsDialog::FractoriumVariationsDialog(QWidget* p, Qt::WindowFlags f)
: QDialog(p, f),
m_Settings(settings),
m_VariationList(VariationList<float>::Instance())
{
ui.setupUi(this);
auto table = ui.VariationsTable;
m_Settings = FractoriumSettings::DefInstance();
m_Vars = m_Settings->Variations();
Populate();
OnSelectAllButtonClicked(true);

View File

@ -19,7 +19,7 @@ class FractoriumVariationsDialog : public QDialog
{
Q_OBJECT
public:
FractoriumVariationsDialog(FractoriumSettings* settings, QWidget* p = nullptr, Qt::WindowFlags f = nullptr);
FractoriumVariationsDialog(QWidget* p = nullptr, Qt::WindowFlags f = nullptr);
void ForEachCell(std::function<void(QTableWidgetItem* cb)> func);
void ForEachSelectedCell(std::function<void(QTableWidgetItem* cb)> func);
void SyncSettings();
@ -46,6 +46,6 @@ private:
shared_ptr<VariationList<float>> m_VariationList;
vector<QCheckBox*> m_CheckBoxes;
QMap<QString, QVariant> m_Vars;
FractoriumSettings* m_Settings;
shared_ptr<FractoriumSettings> m_Settings;
Ui::VariationsDialog ui;
};