05/31/2017

--User changes
 -Add support for adjusting xform color indices in the palette editor. Fixed palettes can now be displayed there, but they will have no color arrows as they are not editable.
 -Add support for independent dimension scaling in the EmberRender and EmberAnimate programs to bring them in line with the final render dialog Fractorium.

--Bug fixes
 -File paths with a space in them did not work in the command line programs.
 -Any Xml file in the search paths would erroneously be treated as a palette file.

--Code changes
 -Change some for loops to while loops when iterating through xforms.
 -Allow FractoriumEmberController<T>::UpdateXform() to be able to apply the action to an xform at a specific index.
 -Remove old code blocks build files that were never used.
 -Make GetPath() return empty string if no path is present in the passed in file path.
 -GetTotalXform() was always counting the final xform, even if it was unused.
This commit is contained in:
Person
2017-05-31 19:50:05 -07:00
parent f4bdc1c50a
commit 5a8b4b1148
49 changed files with 743 additions and 4031 deletions

View File

@ -316,17 +316,17 @@ public:
{
if (i < XformCount())
return const_cast<Xform<T>*>(&m_Xforms[i]);
else if (i == XformCount() || forceFinal)
else if (forceFinal || (i == XformCount() && UseFinalXform()))
return const_cast<Xform<T>*>(&m_FinalXform);
else
return nullptr;
}
/// <summary>
/// Search the xforms, excluding final, to find which one's address matches the address of the specified xform.
/// </summary>
/// <param name="xform">A pointer to the xform to find</param>
/// <returns>The index of the matched xform if found, else -1.</returns>
/// <summary>
/// Search the xforms, excluding final, to find which one's address matches the address of the specified xform.
/// </summary>
/// <param name="xform">A pointer to the xform to find</param>
/// <returns>The index of the matched xform if found, else -1.</returns>
intmax_t GetXformIndex(Xform<T>* xform) const
{
intmax_t index = -1;
@ -388,8 +388,10 @@ public:
/// </summary>
void DeleteMotionElements()
{
for (size_t i = 0; i < TotalXformCount(); i++)
GetTotalXform(i)->DeleteMotionElements();
size_t i = 0;
while (auto xform = GetTotalXform(i++))
xform->DeleteMotionElements();
m_EmberMotionElements.clear();
}
@ -399,9 +401,10 @@ public:
/// </summary>
void CacheXforms()
{
for (size_t i = 0; i < TotalXformCount(); i++)
size_t i = 0;
while (auto xform = GetTotalXform(i++))
{
auto xform = GetTotalXform(i);
xform->CacheColorVals();
xform->SetPrecalcFlags();
}
@ -594,12 +597,10 @@ public:
bool Flatten(vector<string>& names)
{
bool flattened = false;
size_t i = 0;
for (auto& xform : m_Xforms)
flattened |= xform.Flatten(names);
if (UseFinalXform())
flattened |= m_FinalXform.Flatten(names);
while (auto xform = GetTotalXform(i++))
flattened |= xform->Flatten(names);
return flattened;
}
@ -743,12 +744,8 @@ public:
for (size_t i = 0; i < totalXformCount; i++)//For each xform to populate.
{
for (size_t j = 0; j < size; j++)//For each ember in the list.
{
if (i < embers[j].TotalXformCount())//Xform in this position in this ember.
{
xformVec.push_back(embers[j].GetTotalXform(i));//Temporary list to pass to MergeXforms().
}
}
if (i < maxXformCount)//Working with standard xforms?
AddXform(Interpolater<T>::MergeXforms(xformVec, true));//Merge, set weights to zero, and add to the xform list.

View File

@ -211,10 +211,14 @@ bool PaletteList<T>::Add(const string& filename, bool force)
if (doc)
{
auto rootNode = xmlDocGetRootElement(doc);
palettes.first->second.clear();
palettes.first->second.reserve(buf.size() / 2048);//Roughly what it takes per palette.
ParsePalettes(rootNode, pfilename, palettes.first->second);
xmlFreeDoc(doc);
if (!Compare(rootNode->name, "palettes"))
{
palettes.first->second.clear();
palettes.first->second.reserve(buf.size() / 2048);//Roughly the size in bytes it takes to store the xml text of palette.
ParsePalettes(rootNode, pfilename, palettes.first->second);
xmlFreeDoc(doc);
}
if (palettes.first->second.empty())
{

View File

@ -108,28 +108,30 @@ public:
string TruncateVariations(Ember<T>& ember, size_t maxVars)
{
intmax_t smallest;
size_t i, j, numVars;
size_t i = 0, j, numVars;
T sv = 0;
ostringstream os;
//First clear out any xforms that are not the final, and have a density of less than 0.001.
for (i = 0; i < ember.XformCount(); i++)
{
auto xform = ember.GetXform(i);
while (auto xform = ember.GetXform(i))
{
if (xform->m_Weight < T(0.001))
{
os << "trunc_density " << i;
ember.DeleteXform(i);
i = 0;//Size will have changed, so start over.
continue;
}
i++;
}
//Now consider all xforms, including final.
for (i = 0; i < ember.TotalXformCount(); i++)
{
auto xform = ember.GetTotalXform(i);
i = 0;
while (auto xform = ember.GetTotalXform(i))
{
do
{
Variation<T>* var = nullptr;
@ -164,6 +166,8 @@ public:
}
}
while (numVars > maxVars);
i++;
}
return os.str();

View File

@ -86,7 +86,7 @@ public:
/// Days, hours and minutes are only included if 1 or more of them has elapsed. Seconds are always
/// included as a decimal value with the precision the user specified in the constructor.
/// </summary>
/// <param name="ms">The ms</param>
/// <param name="ms">The time in milliseconds to format</param>
/// <returns>The formatted string</returns>
string Format(double ms) const
{

View File

@ -924,6 +924,8 @@ static vector<std::string> Split(const string& str, const string& del, bool remo
/// <summary>
/// Return a copy of a file path string with the file portion removed.
/// If no path is present, such as having only a filename present, then
/// empty string is returned.
/// </summary>
/// <param name="filename">The string to retrieve the path from</param>
/// <returns>The path portion of the string</returns>
@ -931,10 +933,10 @@ static string GetPath(const string& filename)
{
const size_t lastSlash = filename.find_last_of("\\/");
if (std::string::npos != lastSlash)
if (lastSlash != std::string::npos)
return filename.substr(0, lastSlash + 1);
else
return filename;
return "";
}
/// <summary>

View File

@ -205,7 +205,10 @@ bool EmberAnimate(EmberOptions& opt)
//-Have equal dimensions.
for (i = 0; i < embers.size(); i++)
{
if (i > 0 && embers[i].m_Time <= embers[i - 1].m_Time)
auto& ember = embers[i];
auto& emberm1 = embers[i - 1];
if (i > 0 && ember.m_Time <= emberm1.m_Time)
{
if (!unsorted)
firstUnsortedIndex = i;
@ -213,57 +216,77 @@ bool EmberAnimate(EmberOptions& opt)
unsorted = true;
}
if (i > 0 && embers[i].m_Time == embers[i - 1].m_Time)
if (i > 0 && ember.m_Time == emberm1.m_Time)
{
cout << "Image " << i << " time of " << embers[i].m_Time << " equaled previous image time of " << embers[i - 1].m_Time << ". Adjusting up by 1.\n";
embers[i].m_Time++;
cout << "Image " << i << " time of " << ember.m_Time << " equaled previous image time of " << emberm1.m_Time << ". Adjusting up by 1.\n";
ember.m_Time++;
}
if (opt.Supersample() > 0)
embers[i].m_Supersample = opt.Supersample();
ember.m_Supersample = opt.Supersample();
if (opt.TemporalSamples() > 0)
embers[i].m_TemporalSamples = opt.TemporalSamples();
ember.m_TemporalSamples = opt.TemporalSamples();
if (opt.Quality() > 0)
embers[i].m_Quality = T(opt.Quality());
ember.m_Quality = T(opt.Quality());
if (opt.DeMin() > -1)
embers[i].m_MinRadDE = T(opt.DeMin());
ember.m_MinRadDE = T(opt.DeMin());
if (opt.DeMax() > -1)
embers[i].m_MaxRadDE = T(opt.DeMax());
ember.m_MaxRadDE = T(opt.DeMax());
ember.m_Quality *= T(opt.QualityScale());
if (opt.SizeScale() != 1.0)
{
ember.m_FinalRasW = size_t(T(ember.m_FinalRasW) * opt.SizeScale());
ember.m_FinalRasH = size_t(T(ember.m_FinalRasH) * opt.SizeScale());
ember.m_PixelsPerUnit *= T(opt.SizeScale());
}
else if (opt.WidthScale() != 1.0 || opt.HeightScale() != 1.0)
{
auto scaleType = eScaleType::SCALE_NONE;
if (ToLower(opt.ScaleType()) == "width")
scaleType = eScaleType::SCALE_WIDTH;
else if (ToLower(opt.ScaleType()) == "height")
scaleType = eScaleType::SCALE_HEIGHT;
else if (ToLower(opt.ScaleType()) != "none")
cout << "Scale type must be width height or none. Setting to none.\n";
auto w = std::max<size_t>(size_t(ember.m_OrigFinalRasW * opt.WidthScale()), 10);
auto h = std::max<size_t>(size_t(ember.m_OrigFinalRasH * opt.HeightScale()), 10);
ember.SetSizeAndAdjustScale(w, h, false, scaleType);
}
embers[i].m_Quality *= T(opt.QualityScale());
embers[i].m_FinalRasW = size_t(T(embers[i].m_FinalRasW) * opt.SizeScale());
embers[i].m_FinalRasH = size_t(T(embers[i].m_FinalRasH) * opt.SizeScale());
embers[i].m_PixelsPerUnit *= T(opt.SizeScale());
//Cast to double in case the value exceeds 2^32.
double imageMem = double(channels) * double(embers[i].m_FinalRasW)
* double(embers[i].m_FinalRasH) * double(renderers[0]->BytesPerChannel());
double imageMem = double(channels) * double(ember.m_FinalRasW)
* double(ember.m_FinalRasH) * double(renderers[0]->BytesPerChannel());
double maxMem = pow(2.0, double((sizeof(void*) * 8) - 1));
if (imageMem > maxMem)//Ensure the max amount of memory for a process isn't exceeded.
{
cout << "Image " << i << " size > " << maxMem << ". Setting to 1920 x 1080.\n";
embers[i].m_FinalRasW = 1920;
embers[i].m_FinalRasH = 1080;
ember.m_FinalRasW = 1920;
ember.m_FinalRasH = 1080;
}
if (embers[i].m_FinalRasW == 0 || embers[i].m_FinalRasH == 0)
if (ember.m_FinalRasW == 0 || ember.m_FinalRasH == 0)
{
cout << "Warning: Output image " << i << " has dimension 0: " << embers[i].m_FinalRasW << ", " << embers[i].m_FinalRasH << ". Setting to 1920 x 1080.\n";
embers[i].m_FinalRasW = 1920;
embers[i].m_FinalRasH = 1080;
cout << "Warning: Output image " << i << " has dimension 0: " << ember.m_FinalRasW << ", " << ember.m_FinalRasH << ". Setting to 1920 x 1080.\n";
ember.m_FinalRasW = 1920;
ember.m_FinalRasH = 1080;
}
if ((embers[i].m_FinalRasW != embers[0].m_FinalRasW) ||
(embers[i].m_FinalRasH != embers[0].m_FinalRasH))
if ((ember.m_FinalRasW != embers[0].m_FinalRasW) ||
(ember.m_FinalRasH != embers[0].m_FinalRasH))
{
cout << "Warning: flame " << i << " at time " << embers[i].m_Time << " size mismatch. (" << embers[i].m_FinalRasW << ", " << embers[i].m_FinalRasH <<
cout << "Warning: flame " << i << " at time " << ember.m_Time << " size mismatch. (" << ember.m_FinalRasW << ", " << ember.m_FinalRasH <<
") should be (" << embers[0].m_FinalRasW << ", " << embers[0].m_FinalRasH << "). Setting to " << embers[0].m_FinalRasW << ", " << embers[0].m_FinalRasH << ".\n";
embers[i].m_FinalRasW = embers[0].m_FinalRasW;
embers[i].m_FinalRasH = embers[0].m_FinalRasH;
ember.m_FinalRasW = embers[0].m_FinalRasW;
ember.m_FinalRasH = embers[0].m_FinalRasH;
}
}
@ -373,7 +396,7 @@ bool EmberAnimate(EmberOptions& opt)
if (opt.WriteGenome())
{
flameName = filename.substr(0, filename.find_last_of('.')) + ".flam3";
flameName = filename.substr(0, filename.find_last_of('.')) + ".flame";
if (opt.Verbose())
{

View File

@ -38,7 +38,7 @@ template <typename T>
string IterOpenCLKernelCreator<T>::CreateIterKernelString(const Ember<T>& ember, const string& parVarDefines, const string& globalSharedDefines, bool lockAccum, bool doAccum)
{
bool doublePrecision = typeid(T) == typeid(double);
size_t i, v, varIndex, varCount, totalXformCount = ember.TotalXformCount();
size_t i = 0, v, varIndex, varCount;
ostringstream kernelIterBody, xformFuncs, os;
vector<Variation<T>*> variations;
xformFuncs << VariationStateString(ember);
@ -49,9 +49,8 @@ string IterOpenCLKernelCreator<T>::CreateIterKernelString(const Ember<T>& ember,
if (var)
xformFuncs << var->OpenCLFuncsString();
for (i = 0; i < totalXformCount; i++)
while (auto xform = ember.GetTotalXform(i))
{
auto xform = ember.GetTotalXform(i);
bool needPrecalcSumSquares = false;
bool needPrecalcSqrtSumSquares = false;
bool needPrecalcAngles = false;
@ -206,6 +205,7 @@ string IterOpenCLKernelCreator<T>::CreateIterKernelString(const Ember<T>& ember,
xformFuncs << "\toutPoint->m_ColorX = tempColor + xform->m_DirectColor * (outPoint->m_ColorX - tempColor);\n";
xformFuncs << "}\n"
<< "\n";
i++;
}
os <<
@ -536,31 +536,28 @@ string IterOpenCLKernelCreator<T>::CreateIterKernelString(const Ember<T>& ember,
template <typename T>
string IterOpenCLKernelCreator<T>::GlobalFunctionsString(const Ember<T>& ember)
{
size_t i, j, xformCount = ember.TotalXformCount();
size_t i = 0, j;
vector<string> funcNames;//Can't use a set here because they sort and we must preserve the insertion order due to nested function calls.
ostringstream os;
static string zeps = "Zeps";
for (i = 0; i < xformCount; i++)
while (auto xform = ember.GetTotalXform(i++))
{
if (auto xform = ember.GetTotalXform(i))
size_t varCount = xform->TotalVariationCount();
if (xform->NeedPrecalcAngles())
if (!Contains(funcNames, zeps))
funcNames.push_back(zeps);
for (j = 0; j < varCount; j++)
{
size_t varCount = xform->TotalVariationCount();
if (xform->NeedPrecalcAngles())
if (!Contains(funcNames, zeps))
funcNames.push_back(zeps);
for (j = 0; j < varCount; j++)
if (auto var = xform->GetVariation(j))
{
if (auto var = xform->GetVariation(j))
{
auto names = var->OpenCLGlobalFuncNames();
auto names = var->OpenCLGlobalFuncNames();
for (auto& name : names)
if (!Contains(funcNames, name))
funcNames.push_back(name);
}
for (auto& name : names)
if (!Contains(funcNames, name))
funcNames.push_back(name);
}
}
}
@ -618,43 +615,42 @@ string IterOpenCLKernelCreator<T>::GlobalFunctionsString(const Ember<T>& ember)
template <typename T>
void IterOpenCLKernelCreator<T>::ParVarIndexDefines(const Ember<T>& ember, pair<string, vector<T>>& params, bool doVals, bool doString)
{
size_t i, j, k, size = 0, xformCount = ember.TotalXformCount();
size_t i = 0, j, k, size = 0;
ostringstream os;
if (doVals)
params.second.clear();
for (i = 0; i < xformCount; i++)
while (auto xform = ember.GetTotalXform(i))
{
if (auto xform = ember.GetTotalXform(i))
size_t varCount = xform->TotalVariationCount();
for (j = 0; j < varCount; j++)
{
size_t varCount = xform->TotalVariationCount();
for (j = 0; j < varCount; j++)
if (auto parVar = dynamic_cast<ParametricVariation<T>*>(xform->GetVariation(j)))
{
if (auto parVar = dynamic_cast<ParametricVariation<T>*>(xform->GetVariation(j)))
for (k = 0; k < parVar->ParamCount(); k++)
{
for (k = 0; k < parVar->ParamCount(); k++)
if (!parVar->Params()[k].IsState())
{
if (!parVar->Params()[k].IsState())
if (doString)
os << "#define " << ToUpper(parVar->Params()[k].Name()) << "_" << i << " " << size << "\n";//Uniquely identify this param in this variation in this xform.
auto elements = parVar->Params()[k].Size() / sizeof(T);
if (doVals)
{
if (doString)
os << "#define " << ToUpper(parVar->Params()[k].Name()) << "_" << i << " " << size << "\n";//Uniquely identify this param in this variation in this xform.
auto elements = parVar->Params()[k].Size() / sizeof(T);
if (doVals)
{
for (auto l = 0; l < elements; l++)
params.second.push_back(*(parVar->Params()[k].Param() + l));
}
size += elements;
for (auto l = 0; l < elements; l++)
params.second.push_back(*(parVar->Params()[k].Param() + l));
}
size += elements;
}
}
}
}
i++;
}
if (doString)
@ -676,7 +672,7 @@ void IterOpenCLKernelCreator<T>::ParVarIndexDefines(const Ember<T>& ember, pair<
template <typename T>
void IterOpenCLKernelCreator<T>::SharedDataIndexDefines(const Ember<T>& ember, pair<string, vector<T>>& params, bool doVals, bool doString)
{
size_t i, j, offset = 0, xformCount = ember.TotalXformCount();
size_t i = 0, j, offset = 0;
string s;
vector<string> dataNames;//Can't use a set here because they sort and we must preserve the insertion order due to nested function calls.
ostringstream os;
@ -685,35 +681,32 @@ void IterOpenCLKernelCreator<T>::SharedDataIndexDefines(const Ember<T>& ember, p
if (doVals)
params.second.clear();
for (i = 0; i < xformCount; i++)
while (auto xform = ember.GetTotalXform(i++))
{
if (auto xform = ember.GetTotalXform(i))
size_t varCount = xform->TotalVariationCount();
for (j = 0; j < varCount; j++)
{
size_t varCount = xform->TotalVariationCount();
for (j = 0; j < varCount; j++)
if (auto var = xform->GetVariation(j))
{
if (auto var = xform->GetVariation(j))
auto names = var->OpenCLGlobalDataNames();
for (auto& name : names)
{
auto names = var->OpenCLGlobalDataNames();
for (auto& name : names)
if (!Contains(dataNames, name))
{
if (!Contains(dataNames, name))
s = ToUpper(name);
if (auto dataInfo = varFuncs->GetSharedData(s))///Will contain a name, pointer to data, and size of the data in units of sizeof(T).
{
s = ToUpper(name);
if (doString)
os << "#define " << ToUpper(name) << " " << offset << "\n";
if (auto dataInfo = varFuncs->GetSharedData(s))///Will contain a name, pointer to data, and size of the data in units of sizeof(T).
{
if (doString)
os << "#define " << ToUpper(name) << " " << offset << "\n";
if (doVals)
params.second.insert(params.second.end(), dataInfo->first, dataInfo->first + dataInfo->second);
if (doVals)
params.second.insert(params.second.end(), dataInfo->first, dataInfo->first + dataInfo->second);
dataNames.push_back(name);
offset += dataInfo->second;
}
dataNames.push_back(name);
offset += dataInfo->second;
}
}
}
@ -738,22 +731,14 @@ void IterOpenCLKernelCreator<T>::SharedDataIndexDefines(const Ember<T>& ember, p
template <typename T>
string IterOpenCLKernelCreator<T>::VariationStateString(const Ember<T>& ember)
{
size_t i = 0;
ostringstream os;
os << "typedef struct __attribute__ " ALIGN_CL " _VariationState\n{";
for (size_t i = 0; i < ember.TotalXformCount(); i++)
{
if (auto xform = ember.GetTotalXform(i))
{
for (size_t j = 0; j < xform->TotalVariationCount(); j++)
{
if (auto var = xform->GetVariation(j))
{
os << var->StateOpenCLString();
}
}
}
}
while (auto xform = ember.GetTotalXform(i++))
for (size_t j = 0; j < xform->TotalVariationCount(); j++)
if (auto var = xform->GetVariation(j))
os << var->StateOpenCLString();
os << "\n} VariationState;\n\n";
return os.str();
@ -770,21 +755,13 @@ string IterOpenCLKernelCreator<T>::VariationStateString(const Ember<T>& ember)
template <typename T>
string IterOpenCLKernelCreator<T>::VariationStateInitString(const Ember<T>& ember)
{
size_t i = 0;
ostringstream os;
for (size_t i = 0; i < ember.TotalXformCount(); i++)
{
if (auto xform = ember.GetTotalXform(i))
{
for (size_t j = 0; j < xform->TotalVariationCount(); j++)
{
if (auto var = xform->GetVariation(j))
{
os << var->StateInitOpenCLString();
}
}
}
}
while (auto xform = ember.GetTotalXform(i++))
for (size_t j = 0; j < xform->TotalVariationCount(); j++)
if (auto var = xform->GetVariation(j))
os << var->StateInitOpenCLString();
return os.str();
}

View File

@ -96,6 +96,8 @@ enum class eOptionIDs : et
OPT_PRIORITY,
OPT_SS,//Float value args.
OPT_WS,
OPT_HS,
OPT_QS,
OPT_QUALITY,
OPT_DE_MIN,
@ -111,7 +113,8 @@ enum class eOptionIDs : et
OPT_USEMEM,
OPT_LOOPS,
OPT_OPENCL_DEVICE,//String value args.
OPT_SCALE_TYPE,//String value args.
OPT_OPENCL_DEVICE,
OPT_ISAAC_SEED,
OPT_IN,
OPT_OUT,
@ -119,7 +122,6 @@ enum class eOptionIDs : et
OPT_SUFFIX,
OPT_FORMAT,
OPT_PALETTE_FILE,
//OPT_PALETTE_IMAGE,
OPT_ID,
OPT_URL,
OPT_NICK,
@ -225,6 +227,35 @@ private:
T m_Val;
};
/// <summary>
/// Class to force a stringstream to not split on space and
/// read to the end of the string.
/// </summary>
struct NoDelimiters : std::ctype<char>
{
/// <summary>
/// Constructor that passes the table created in GetTable() to the base.
/// </summary>
NoDelimiters()
: std::ctype<char>(GetTable())
{
}
/// <summary>
/// Create and return a pointer to an empty table with no delimiters.
/// </summary>
/// <returns>A pointer to the empty delimiter table</returns>
static std::ctype_base::mask const* GetTable()
{
typedef std::ctype<char> cctype;
static const cctype::mask* const_rc = cctype::classic_table();
static cctype::mask rc[cctype::table_size];
std::memset(rc, 0, cctype::table_size * sizeof(cctype::mask));
return &rc[0];
}
};
/// <summary>
/// Macros for setting up and parsing various option types.
/// </summary>
@ -296,7 +327,7 @@ public:
/// </summary>
EmberOptions()
{
const size_t size = 40;
const size_t size = (size_t)eOptionIDs::OPT_EXTRAS;
m_BoolArgs.reserve(size);
m_IntArgs.reserve(size);
m_UintArgs.reserve(size);
@ -374,6 +405,8 @@ public:
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.0, SO_REQ_SEP, " --ss=<val> Size scale. All dimensions are scaled by this amount [default: 1.0].\n"));
INITDOUBLEOPTION(WidthScale, Eod(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_WS, _T("--ws"), 1.0, SO_REQ_SEP, " --ws=<val> Width scale. The width is scaled by this amount [default: 1.0].\n"));
INITDOUBLEOPTION(HeightScale, Eod(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_HS, _T("--hs"), 1.0, SO_REQ_SEP, " --hs=<val> Height scale. The height is 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"));
@ -391,6 +424,7 @@ public:
INITDOUBLEOPTION(UseMem, Eod(eOptionUse::OPT_USE_RENDER, eOptionIDs::OPT_USEMEM, _T("--use_mem"), 0.0, SO_REQ_SEP, " --use_mem=<val> Number of bytes of memory to use [default: max system memory].\n"));
INITDOUBLEOPTION(Loops, Eod(eOptionUse::OPT_USE_GENOME, eOptionIDs::OPT_LOOPS, _T("--loops"), 1.0, SO_REQ_SEP, " --loops=<val> Number of times to rotate each control point in sequence [default: 1].\n"));
//String.
INITSTRINGOPTION(ScaleType, Eos(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_SCALE_TYPE, _T("--scaletype"), "none", SO_REQ_SEP, " --scaletype The type of scaling to use with the --ws or --hs options. Valid values are --width --height [default: width].\n"));
INITSTRINGOPTION(Device, Eos(eOptionUse::OPT_USE_ALL, eOptionIDs::OPT_OPENCL_DEVICE, _T("--device"), "0", SO_REQ_SEP, " --device The comma-separated OpenCL device indices to use. Single device: 0 Multi device: 0,1,3,4 [default: 0].\n"));
INITSTRINGOPTION(IsaacSeed, Eos(eOptionUse::OPT_USE_ALL, eOptionIDs::OPT_ISAAC_SEED, _T("--isaac_seed"), "", SO_REQ_SEP, " --isaac_seed=<val> Character-based seed for the random number generator [default: random].\n"));
INITSTRINGOPTION(Input, Eos(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_IN, _T("--in"), "", SO_REQ_SEP, " --in=<val> Name of the input file.\n"));
@ -435,6 +469,7 @@ public:
vector<CSimpleOpt::SOption> sOptions = options.GetSimpleOptions();
CSimpleOpt args(argc, argv, sOptions.data());
stringstream ss;
ss.imbue(std::locale(std::locale(), new NoDelimiters()));
//Process args.
while (args.Next())
@ -518,7 +553,9 @@ public:
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_SS, SizeScale);//Double args.
PARSEOPTION(eOptionIDs::OPT_WS, WidthScale);
PARSEOPTION(eOptionIDs::OPT_HS, HeightScale);
PARSEOPTION(eOptionIDs::OPT_QS, QualityScale);
PARSEOPTION(eOptionIDs::OPT_QUALITY, Quality);
PARSEOPTION(eOptionIDs::OPT_DE_MIN, DeMin);
@ -533,7 +570,8 @@ public:
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_SCALE_TYPE, ScaleType);//String args.
PARSEOPTION(eOptionIDs::OPT_OPENCL_DEVICE, Device);
PARSEOPTION(eOptionIDs::OPT_ISAAC_SEED, IsaacSeed);
PARSEOPTION(eOptionIDs::OPT_IN, Input);
PARSEOPTION(eOptionIDs::OPT_OUT, Out);
@ -541,7 +579,6 @@ public:
PARSEOPTION(eOptionIDs::OPT_SUFFIX, Suffix);
PARSEOPTION(eOptionIDs::OPT_FORMAT, Format);
PARSEOPTION(eOptionIDs::OPT_PALETTE_FILE, PalettePath);
//PARSESTRINGOPTION(eOptionIDs::OPT_PALETTE_IMAGE, PaletteImage);
PARSEOPTION(eOptionIDs::OPT_ID, Id);
PARSEOPTION(eOptionIDs::OPT_URL, Url);
PARSEOPTION(eOptionIDs::OPT_NICK, Nick);
@ -809,6 +846,8 @@ public:
Eou Padding;
Eod SizeScale;//Value double.
Eod WidthScale;
Eod HeightScale;
Eod QualityScale;
Eod Quality;
Eod DeMin;
@ -824,7 +863,8 @@ public:
Eod UseMem;
Eod Loops;
Eos Device;//Value string.
Eos ScaleType;//Value string.
Eos Device;
Eos IsaacSeed;
Eos Input;
Eos Out;
@ -832,7 +872,6 @@ public:
Eos Suffix;
Eos Format;
Eos PalettePath;
//Eos PaletteImage;
Eos Id;
Eos Url;
Eos Nick;

View File

@ -164,50 +164,71 @@ bool EmberRender(EmberOptions& opt)
for (i = 0; i < embers.size(); i++)
{
auto& ember = embers[i];
if (opt.Verbose() && embers.size() > 1)
cout << "\nFlame = " << i + 1 << "/" << embers.size() << "\n";
else if (embers.size() > 1)
VerbosePrint("\n");
if (opt.Supersample() > 0)
embers[i].m_Supersample = opt.Supersample();
ember.m_Supersample = opt.Supersample();
if (opt.Quality() > 0)
embers[i].m_Quality = T(opt.Quality());
ember.m_Quality = T(opt.Quality());
if (opt.DeMin() > -1)
embers[i].m_MinRadDE = T(opt.DeMin());
ember.m_MinRadDE = T(opt.DeMin());
if (opt.DeMax() > -1)
embers[i].m_MaxRadDE = T(opt.DeMax());
ember.m_MaxRadDE = T(opt.DeMax());
embers[i].m_TemporalSamples = 1;//Force temporal samples to 1 for render.
embers[i].m_Quality *= T(opt.QualityScale());
embers[i].m_FinalRasW = size_t(T(embers[i].m_FinalRasW) * opt.SizeScale());
embers[i].m_FinalRasH = size_t(T(embers[i].m_FinalRasH) * opt.SizeScale());
embers[i].m_PixelsPerUnit *= T(opt.SizeScale());
ember.m_TemporalSamples = 1;//Force temporal samples to 1 for render.
ember.m_Quality *= T(opt.QualityScale());
if (embers[i].m_FinalRasW == 0 || embers[i].m_FinalRasH == 0)
if (opt.SizeScale() != 1.0)
{
cout << "Output image " << i << " has dimension 0: " << embers[i].m_FinalRasW << ", " << embers[i].m_FinalRasH << ". Setting to 1920 x 1080.\n";
embers[i].m_FinalRasW = 1920;
embers[i].m_FinalRasH = 1080;
ember.m_FinalRasW = size_t(T(ember.m_FinalRasW) * opt.SizeScale());
ember.m_FinalRasH = size_t(T(ember.m_FinalRasH) * opt.SizeScale());
ember.m_PixelsPerUnit *= T(opt.SizeScale());
}
else if (opt.WidthScale() != 1.0 || opt.HeightScale() != 1.0)
{
auto scaleType = eScaleType::SCALE_NONE;
if (ToLower(opt.ScaleType()) == "width")
scaleType = eScaleType::SCALE_WIDTH;
else if (ToLower(opt.ScaleType()) == "height")
scaleType = eScaleType::SCALE_HEIGHT;
else if (ToLower(opt.ScaleType()) != "none")
cout << "Scale type must be width height or none. Setting to none.\n";
auto w = std::max<size_t>(size_t(ember.m_OrigFinalRasW * opt.WidthScale()), 10);
auto h = std::max<size_t>(size_t(ember.m_OrigFinalRasH * opt.HeightScale()), 10);
ember.SetSizeAndAdjustScale(w, h, false, scaleType);
}
if (ember.m_FinalRasW == 0 || ember.m_FinalRasH == 0)
{
cout << "Output image " << i << " has dimension 0: " << ember.m_FinalRasW << ", " << ember.m_FinalRasH << ". Setting to 1920 x 1080.\n";
ember.m_FinalRasW = 1920;
ember.m_FinalRasH = 1080;
}
//Cast to double in case the value exceeds 2^32.
double imageMem = double(renderer->NumChannels()) * double(embers[i].m_FinalRasW)
* double(embers[i].m_FinalRasH) * double(renderer->BytesPerChannel());
double imageMem = double(renderer->NumChannels()) * double(ember.m_FinalRasW)
* double(ember.m_FinalRasH) * double(renderer->BytesPerChannel());
double maxMem = pow(2.0, double((sizeof(void*) * 8) - 1));
if (imageMem > maxMem)//Ensure the max amount of memory for a process is not exceeded.
{
cout << "Image " << i << " size > " << maxMem << ". Setting to 1920 x 1080.\n";
embers[i].m_FinalRasW = 1920;
embers[i].m_FinalRasH = 1080;
ember.m_FinalRasW = 1920;
ember.m_FinalRasH = 1080;
}
stats.Clear();
renderer->SetEmber(embers[i]);
renderer->SetEmber(ember);
renderer->PrepFinalAccumVector(finalImage);//Must manually call this first because it could be erroneously made smaller due to strips if called inside Renderer::Run().
if (opt.Strips() > 1)
@ -223,7 +244,7 @@ bool EmberRender(EmberOptions& opt)
VerbosePrint("Setting strips to " << strips << " with specified memory usage of " << opt.UseMem());
}
strips = VerifyStrips(embers[i].m_FinalRasH, strips,
strips = VerifyStrips(ember.m_FinalRasH, strips,
[&](const string & s) { cout << s << "\n"; }, //Greater than height.
[&](const string & s) { cout << s << "\n"; }, //Mod height != 0.
[&](const string & s) { cout << s << "\n"; }); //Final strips value to be set.
@ -237,7 +258,7 @@ bool EmberRender(EmberOptions& opt)
// resume = true;
//}
//while (success && renderer->ProcessState() != ACCUM_DONE);
StripsRender<T>(renderer.get(), embers[i], finalImage, 0, strips, opt.YAxisUp(),
StripsRender<T>(renderer.get(), ember, finalImage, 0, strips, opt.YAxisUp(),
[&](size_t strip)//Pre strip.
{
if (opt.Verbose() && (strips > 1) && strip > 0)

View File

@ -772,7 +772,7 @@ template<typename T>
void FinalRenderEmberController<T>::RenderComplete(Ember<T>& ember, const EmberStats& stats, Timing& renderTimer)
{
rlg l(m_ProgressCs);
string renderTimeString = renderTimer.Format(renderTimer.Toc()), totalTimeString;
string renderTimeString = renderTimer.Format(renderTimer.Toc());
QString status, filename = ComposePath(QString::fromStdString(ember.m_Name));
QString itersString = ToString<qulonglong>(stats.m_Iters);
QString itersPerSecString = ToString<qulonglong>(size_t(stats.m_Iters / (stats.m_IterMs / 1000.0)));
@ -789,10 +789,9 @@ void FinalRenderEmberController<T>::RenderComplete(Ember<T>& ember, const EmberS
xmlFreeDoc(tempEdit);
}
status = "Pure render time: " + QString::fromStdString(renderTimeString);
status = "Render time: " + QString::fromStdString(renderTimeString);
Output(status);
totalTimeString = renderTimer.Format(renderTimer.Toc());
status = "Total time: " + QString::fromStdString(totalTimeString) + "\nTotal iters: " + itersString + "\nIters/second: " + itersPerSecString + "\n";
status = "Total iters: " + itersString + "\nIters/second: " + itersPerSecString + "\n";
Output(status);
QMetaObject::invokeMethod(m_FinalRenderDialog, "MoveCursorToEnd", Qt::QueuedConnection);

View File

@ -23,6 +23,7 @@ Fractorium::Fractorium(QWidget* p)
Timing t;
ui.setupUi(this);
m_Info = OpenCLInfo::Instance();
qRegisterMetaType<size_t>("size_t");
qRegisterMetaType<QVector<int>>("QVector<int>");//For previews.
qRegisterMetaType<vector<byte>>("vector<byte>");
qRegisterMetaType<EmberTreeWidgetItemBase*>("EmberTreeWidgetItemBase*");

View File

@ -281,7 +281,7 @@ public slots:
//Xforms Color.
void OnXformColorIndexChanged(double d);
void OnXformColorIndexChanged(double d, bool updateRender);
void OnXformColorIndexChanged(double d, bool updateRender, eXformUpdate update = eXformUpdate::UPDATE_SELECTED, size_t index = 0);
void OnXformScrollColorIndexChanged(int d);
void OnRandomColorIndicesButtonClicked(bool b);
void OnToggleColorIndicesButtonClicked(bool b);
@ -332,6 +332,7 @@ public slots:
void OnPaletteHeaderSectionClicked(int col);
void OnPaletteEditorColorChanged();
void OnPaletteEditorFileChanged();
void OnPaletteEditorColorIndexChanged(size_t index, float value);
//Info.
void OnSummaryTableHeaderResized(int logicalIndex, int oldSize, int newSize);
@ -419,7 +420,6 @@ private:
void FillSummary();
void UpdateHistogramBounds();
void ErrorReportToQTextEdit(const vector<string>& errors, QTextEdit* textEdit, bool clear = true);
void SetTableWidgetBackgroundColor();
//Rendering/progress.
void ShutdownAndRecreateFromOptions();

View File

@ -383,6 +383,8 @@ static void AddPaletteToTable(QTableWidget* paletteTable, Palette<float>* palett
auto v = palette->MakeRgbPaletteBlock(PALETTE_CELL_HEIGHT);
auto nameCol = new QTableWidgetItem(palette->m_Name.c_str());
nameCol->setToolTip(palette->m_Name.c_str());
nameCol->setFlags(palette->m_SourceColors.empty() ? (Qt::ItemIsEnabled | Qt::ItemIsSelectable)
: (Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable));
paletteTable->setItem(row, 0, nameCol);
QImage image(v.data(), int(palette->Size()), PALETTE_CELL_HEIGHT, QImage::Format_RGB888);
auto paletteItem = new PaletteTableWidgetItem(palette);

View File

@ -203,8 +203,9 @@ void FractoriumEmberController<T>::UpdateAll(std::function<void(Ember<T>& ember)
/// <param name="updateType">Whether to apply this update operation on the current, all or selected xforms. Default: eXformUpdate::UPDATE_CURRENT.</param>
/// <param name="updateRender">True to update renderer, else false. Default: true.</param>
/// <param name="action">The action to add to the rendering queue. Default: eProcessAction::FULL_RENDER.</param>
/// <param name="index">The xform index to use when action is eXformUpdate::UPDATE_SPECIFIC. Default: 0.</param>
template <typename T>
void FractoriumEmberController<T>::UpdateXform(std::function<void(Xform<T>*)> func, eXformUpdate updateType, bool updateRender, eProcessAction action)
void FractoriumEmberController<T>::UpdateXform(std::function<void(Xform<T>*)> func, eXformUpdate updateType, bool updateRender, eProcessAction action, size_t index)
{
int i = 0;
bool isCurrentFinal = m_Ember.IsFinalXform(CurrentXform());
@ -212,6 +213,13 @@ void FractoriumEmberController<T>::UpdateXform(std::function<void(Xform<T>*)> fu
switch (updateType)
{
case eXformUpdate::UPDATE_SPECIFIC:
{
if (auto xform = m_Ember.GetTotalXform(index))
func(xform);
}
break;
case eXformUpdate::UPDATE_CURRENT:
{
if (auto xform = CurrentXform())

View File

@ -16,7 +16,7 @@ enum class eEditUndoState : et { REGULAR_EDIT, UNDO_REDO, EDIT_UNDO };
/// <summary>
/// An enum representing which xforms an update should be applied to.
/// </summary>
enum class eXformUpdate : et { UPDATE_CURRENT, UPDATE_SELECTED, UPDATE_CURRENT_AND_SELECTED, UPDATE_SELECTED_EXCEPT_FINAL, UPDATE_ALL, UPDATE_ALL_EXCEPT_FINAL };
enum class eXformUpdate : et { UPDATE_SPECIFIC, UPDATE_CURRENT, UPDATE_SELECTED, UPDATE_CURRENT_AND_SELECTED, UPDATE_SELECTED_EXCEPT_FINAL, UPDATE_ALL, UPDATE_ALL_EXCEPT_FINAL };
/// <summary>
/// An enum representing the type of synchronizing to do between the list of Embers kept in memory
@ -199,7 +199,7 @@ public:
virtual void InitLockedScale() { }
//Xforms Color.
virtual void XformColorIndexChanged(double d, bool updateRender) { }
virtual void XformColorIndexChanged(double d, bool updateRender, eXformUpdate update = eXformUpdate::UPDATE_SELECTED, size_t index = 0) { }
virtual void XformScrollColorIndexChanged(int d) { }
virtual void RandomColorIndicesButtonClicked() { }
virtual void ToggleColorIndicesButtonClicked() { }
@ -448,7 +448,7 @@ public:
virtual void FillXforms(int index = 0) override;
void FillWithXform(Xform<T>* xform);
Xform<T>* CurrentXform();
void UpdateXform(std::function<void(Xform<T>*)> func, eXformUpdate updateType = eXformUpdate::UPDATE_CURRENT, bool updateRender = true, eProcessAction action = eProcessAction::FULL_RENDER);
void UpdateXform(std::function<void(Xform<T>*)> func, eXformUpdate updateType = eXformUpdate::UPDATE_CURRENT, bool updateRender = true, eProcessAction action = eProcessAction::FULL_RENDER, size_t index = 0);
//Xforms Affine.
virtual void AffineSetHelper(double d, int index, bool pre) override;
@ -466,7 +466,7 @@ public:
T AffineScaleLockedToCurrent();
//Xforms Color.
virtual void XformColorIndexChanged(double d, bool updateRender) override;
virtual void XformColorIndexChanged(double d, bool updateRender, eXformUpdate update = eXformUpdate::UPDATE_SELECTED, size_t index = 0) override;
virtual void XformScrollColorIndexChanged(int d) override;
virtual void RandomColorIndicesButtonClicked() override;
virtual void ToggleColorIndicesButtonClicked() override;

View File

@ -317,9 +317,16 @@ void Fractorium::OnPaletteRandomAdjustButtonClicked(bool checked)
template <typename T>
void FractoriumEmberController<T>::PaletteEditorButtonClicked()
{
size_t i = 0;
auto ed = m_Fractorium->m_PaletteEditor;
Palette<float> prevPal = m_TempPalette;
ed->SetPalette(m_TempPalette);
map<size_t, float> colorIndices;
while (auto xform = m_Ember.GetTotalXform(i))
colorIndices[i++] = xform->m_ColorX;
ed->SetColorIndices(colorIndices);
if (ed->exec() == QDialog::Accepted)
{
@ -363,8 +370,9 @@ void Fractorium::OnPaletteEditorButtonClicked(bool checked)
if (!m_PaletteEditor)
{
m_PaletteEditor = new PaletteEditor(this);
connect(m_PaletteEditor, SIGNAL(PaletteChanged()), this, SLOT(OnPaletteEditorColorChanged()), Qt::QueuedConnection);
connect(m_PaletteEditor, SIGNAL(PaletteFileChanged()), this, SLOT(OnPaletteEditorFileChanged()), Qt::QueuedConnection);
connect(m_PaletteEditor, SIGNAL(PaletteChanged()), this, SLOT(OnPaletteEditorColorChanged()), Qt::QueuedConnection);
connect(m_PaletteEditor, SIGNAL(PaletteFileChanged()), this, SLOT(OnPaletteEditorFileChanged()), Qt::QueuedConnection);
connect(m_PaletteEditor, SIGNAL(ColorIndexChanged(size_t, float)), this, SLOT(OnPaletteEditorColorIndexChanged(size_t, float)), Qt::QueuedConnection);
}
m_PaletteChanged = false;
@ -389,6 +397,25 @@ void Fractorium::OnPaletteEditorFileChanged()
m_PaletteFileChanged = true;
}
/// <summary>
/// Slot called every time an xform color index is changed in the palette editor.
/// If a special value of size_t max is passed for index, it means update all color indices.
/// </summary>
/// <param name="index">The index of the xform whose color index has been changed. Special value of size_t max to update all</param>
/// <param name="value">The value of the color index</param>
void Fractorium::OnPaletteEditorColorIndexChanged(size_t index, float value)
{
if (index == std::numeric_limits<size_t>::max())
{
auto indices = m_PaletteEditor->GetColorIndices();
for (auto& it : indices)
OnXformColorIndexChanged(it.second, true, eXformUpdate::UPDATE_SPECIFIC, it.first);
}
else
OnXformColorIndexChanged(value, true, eXformUpdate::UPDATE_SPECIFIC, index);
}
/// <summary>
/// Apply the text in the palette filter text box to only show palettes whose names
/// contain the substring.

View File

@ -322,28 +322,26 @@ bool FractoriumEmberController<T>::Render()
//Take care of solo xforms and set the current ember and action.
if (action != eProcessAction::NOTHING)
{
int i, solo = m_Fractorium->ui.CurrentXformCombo->property("soloxform").toInt();
size_t i = 0;
int solo = m_Fractorium->ui.CurrentXformCombo->property("soloxform").toInt();
if (solo != -1)
{
m_TempOpacities.resize(m_Ember.TotalXformCount());
for (i = 0; i < m_Ember.TotalXformCount(); i++)
while (auto xform = m_Ember.GetTotalXform(i))
{
m_TempOpacities[i] = m_Ember.GetTotalXform(i)->m_Opacity;
m_Ember.GetTotalXform(i)->m_Opacity = i == solo ? 1 : 0;
m_TempOpacities[i] = xform->m_Opacity;
xform->m_Opacity = i++ == solo ? 1 : 0;
}
}
i = 0;
m_Renderer->SetEmber(m_Ember, action);
if (solo != -1)
{
for (i = 0; i < m_Ember.TotalXformCount(); i++)
{
m_Ember.GetTotalXform(i)->m_Opacity = m_TempOpacities[i];
}
}
while (auto xform = m_Ember.GetTotalXform(i))
xform->m_Opacity = m_TempOpacities[i++];
}
//Ensure sizes are equal and if not, update dimensions.

View File

@ -40,27 +40,35 @@ void Fractorium::InitXformsColorUI()
/// </summary>
/// <param name="d">The color index, 0-1/</param>
/// <param name="updateRender">True to reset the rendering process, else don't.</param>
/// <param name="update">The type of updating to do, default: eXformUpdate::UPDATE_SELECTED.</param>
/// <param name="index">The index of the xform to update. Ignored unless update is eXformUpdate::UPDATE_SPECIFIC. Default: 0.</param>
template <typename T>
void FractoriumEmberController<T>::XformColorIndexChanged(double d, bool updateRender)
void FractoriumEmberController<T>::XformColorIndexChanged(double d, bool updateRender, eXformUpdate update, size_t index)
{
auto scroll = m_Fractorium->ui.XformColorScroll;
int scrollVal = d * scroll->maximum();
scroll->blockSignals(true);
scroll->setValue(scrollVal);
scroll->blockSignals(false);
m_Fractorium->ui.XformColorIndexTable->item(0, 0)->setBackgroundColor(ColorIndexToQColor(d));//Grab the current color from the index and assign it to the first cell of the first table.
bool updateGUI = update != eXformUpdate::UPDATE_SPECIFIC || index == m_Fractorium->ui.CurrentXformCombo->currentIndex();
if (updateGUI)
{
auto scroll = m_Fractorium->ui.XformColorScroll;
int scrollVal = d * scroll->maximum();
scroll->blockSignals(true);
scroll->setValue(scrollVal);
scroll->blockSignals(false);
m_Fractorium->m_XformColorIndexSpin->SetValueStealth(CurrentXform()->m_ColorX);
m_Fractorium->ui.XformColorIndexTable->item(0, 0)->setBackgroundColor(ColorIndexToQColor(d));//Grab the current color from the index and assign it to the first cell of the first table.
}
if (updateRender)//False when just updating GUI, true when in response to a GUI change so update values and reset renderer.
{
UpdateXform([&](Xform<T>* xform)
{
xform->m_ColorX = Clamp<T>(d, 0, 1);
}, eXformUpdate::UPDATE_SELECTED, updateRender);
}, update, updateRender, eProcessAction::FULL_RENDER, index);
}
}
void Fractorium::OnXformColorIndexChanged(double d) { OnXformColorIndexChanged(d, true); }
void Fractorium::OnXformColorIndexChanged(double d, bool updateRender) { m_Controller->XformColorIndexChanged(d, updateRender); }
void Fractorium::OnXformColorIndexChanged(double d) { OnXformColorIndexChanged(d, true, eXformUpdate::UPDATE_SELECTED, 0); }
void Fractorium::OnXformColorIndexChanged(double d, bool updateRender, eXformUpdate update, size_t index) { m_Controller->XformColorIndexChanged(d, updateRender, update, index); }
/// <summary>
/// Set the color index of the current xform.
@ -86,7 +94,6 @@ template <typename T>
void FractoriumEmberController<T>::RandomColorIndicesButtonClicked()
{
UpdateXform([&](Xform<T>* xform) { xform->m_ColorX = m_Rand.Frand01<T>(); }, eXformUpdate::UPDATE_ALL);
m_Fractorium->m_XformColorIndexSpin->SetValueStealth(CurrentXform()->m_ColorX);
m_Fractorium->OnXformColorIndexChanged(CurrentXform()->m_ColorX, false);//Update GUI, no need to update renderer because UpdateXform() did it.
}
void Fractorium::OnRandomColorIndicesButtonClicked(bool b) { m_Controller->RandomColorIndicesButtonClicked(); }
@ -101,7 +108,6 @@ void FractoriumEmberController<T>::ToggleColorIndicesButtonClicked()
{
char ch = 1;
UpdateXform([&](Xform<T>* xform) { xform->m_ColorX = T(ch ^= 1); }, eXformUpdate::UPDATE_ALL);
m_Fractorium->m_XformColorIndexSpin->SetValueStealth(CurrentXform()->m_ColorX);
m_Fractorium->OnXformColorIndexChanged(CurrentXform()->m_ColorX, false);//Update GUI, no need to update renderer because UpdateXform() did it.
}
void Fractorium::OnToggleColorIndicesButtonClicked(bool b) { m_Controller->ToggleColorIndicesButtonClicked(); }

View File

@ -71,10 +71,8 @@ QString FractoriumEmberController<T>::MakeXformCaption(size_t i)
QString caption = isFinal ? "Final" : QString::number(i + 1);
if (auto xform = m_Ember.GetTotalXform(i))
{
if (!xform->m_Name.empty())
caption += " (" + QString::fromStdString(xform->m_Name) + ")";
}
return caption;
}
@ -128,5 +126,5 @@ bool FractoriumEmberController<T>::XformCheckboxAt(Xform<T>* xform, std::functio
template class FractoriumEmberController<float>;
#ifdef DO_DOUBLE
template class FractoriumEmberController<double>;
template class FractoriumEmberController<double>;
#endif

View File

@ -308,10 +308,11 @@ void GLEmberController<T>::DrawAffines(bool pre, bool post)
{
if (pre && m_Fractorium->DrawAllPre())//Draw all pre affine if specified.
{
for (size_t i = 0; i < ember->TotalXformCount(); i++)
size_t i = 0;
while (auto xform = ember->GetTotalXform(i))
{
auto xform = ember->GetTotalXform(i);
bool selected = m_Fractorium->IsXformSelected(i) || (dragging ? (m_SelectedXform == xform) : (m_HoverXform == xform));
bool selected = m_Fractorium->IsXformSelected(i++) || (dragging ? (m_SelectedXform == xform) : (m_HoverXform == xform));
DrawAffine(xform, true, selected);
}
}
@ -322,10 +323,11 @@ void GLEmberController<T>::DrawAffines(bool pre, bool post)
if (post && m_Fractorium->DrawAllPost())//Draw all post affine if specified.
{
for (size_t i = 0; i < ember->TotalXformCount(); i++)
size_t i = 0;
while (auto xform = ember->GetTotalXform(i))
{
auto xform = ember->GetTotalXform(i);
bool selected = m_Fractorium->IsXformSelected(i) || (dragging ? (m_SelectedXform == xform) : (m_HoverXform == xform));
bool selected = m_Fractorium->IsXformSelected(i++) || (dragging ? (m_SelectedXform == xform) : (m_HoverXform == xform));
DrawAffine(xform, false, selected);
}
}
@ -1029,7 +1031,7 @@ int GLEmberController<T>::UpdateHover(v3T& glCoords)
bool post = m_Fractorium->ui.PostAffineGroupBox->isChecked();
bool preAll = pre && m_Fractorium->DrawAllPre();
bool postAll = post && m_Fractorium->DrawAllPost();
int bestIndex = -1;
int i = 0, bestIndex = -1;
T bestDist = 10;
auto ember = m_FractoriumEmberController->CurrentEmber();
m_HoverType = eHoverType::HoverNone;
@ -1053,10 +1055,8 @@ int GLEmberController<T>::UpdateHover(v3T& glCoords)
}
//Check all xforms.
for (int i = 0; i < int(ember->TotalXformCount()); i++)
while (auto xform = ember->GetTotalXform(i))
{
auto xform = ember->GetTotalXform(i);
if (preAll || (pre && m_HoverXform == xform))//Only check pre affine if they are shown.
{
if (CheckXformHover(xform, glCoords, bestDist, true, false))
@ -1074,6 +1074,8 @@ int GLEmberController<T>::UpdateHover(v3T& glCoords)
bestIndex = i;
}
}
i++;
}
}

View File

@ -43,7 +43,7 @@ Q_SIGNALS:
protected:
void resizeEvent(QResizeEvent* event) override;
private slots:
private Q_SLOTS:
void OnColorViewerClicked();
void OnTriangleColorChanged(const QColor& col);

View File

@ -97,7 +97,7 @@ public:
virtual int heightForWidth(int w) const override;
virtual QSize sizeHint() const override;
signals:
Q_SIGNALS:
void ColorChanged(const QColor& col);
protected:

View File

@ -76,3 +76,52 @@ private:
QPolygon m_Area;
QColor m_Color;
};
/// <summary>
/// Thin derivation to handle drawing arrows at the top of the gradient area to
/// represent the color indices of each xform.
/// </summary>
class TopArrow : public GradientArrow
{
public:
/// <summary>
/// Default constructor which is only present so this class can be used with containers.
/// This should never be used by a caller.
/// </summary>
TopArrow()
: TopArrow(10, 0)
{
}
/// <summary>
/// Constructor which takes the width used to draw the arrow and the xform index
/// this arrow represents.
/// </summary>
/// <param name="width">The width used to draw the arrow</param>
/// <param name="index">The xform index this arrow represents</param>
TopArrow(int width, size_t index)
{
QPolygon area;
int center = 10;
int mid = width / 2;
int left = center - mid;
int right = center + mid;
area << QPoint(left, 0) << QPoint(right, 0) << QPoint(right, 10) << QPoint(center, 15) << QPoint(left, 10) << QPoint(left, 0);
Area(area);
m_Index = index;
m_Width = width;
m_Text = QString::number(index + 1);
}
/// <summary>
/// Getters.
/// </summary>
int Width() { return m_Width; }
size_t Index() { return m_Index; }
QString Text() { return m_Text; }
private:
int m_Width;
size_t m_Index;
QString m_Text;
};

View File

@ -73,7 +73,7 @@ void GradientColorsView::SetFocus(size_t position)
it.second.Focus(b);
}
if (!focused)
if (!focused && !m_Arrows.empty())
m_Arrows.begin()->second.Focus(true);
update();
@ -100,6 +100,7 @@ void GradientColorsView::SetFocusColor(const QColor& color)
/// <summary>
/// Add an arrow whose color will be assigned the passed in color.
/// This should never be called on a fixed palette.
/// </summary>
/// <param name="color">The color to assign to the new arrow</param>
void GradientColorsView::AddArrow(const QColor& color)
@ -202,14 +203,28 @@ void GradientColorsView::DeleteFocusedArrow()
/// </summary>
void GradientColorsView::InvertColors()
{
for (auto& it : m_Arrows)
if (!m_Arrows.empty())
{
auto& arrow = it.second;
auto col = arrow.Color();
arrow.Color(QColor(255 - col.red(), 255 - col.green(), 255 - col.blue()));
for (auto& it : m_Arrows)
{
auto& arrow = it.second;
auto col = arrow.Color();
arrow.Color(QColor(255 - col.red(), 255 - col.green(), 255 - col.blue()));
if (arrow.Focus())
emit ArrowDoubleClicked(arrow);
if (arrow.Focus())
emit ArrowDoubleClicked(arrow);
}
}
else
{
for (auto& col : m_Palette.m_Entries)
{
col.r = 1 - col.r;
col.g = 1 - col.g;
col.b = 1 - col.b;
}
SetPalette(m_Palette);//Reset associated pixmap etc...
}
update();
@ -235,16 +250,20 @@ void GradientColorsView::RandomColors()
/// </summary>
void GradientColorsView::DistributeColors()
{
map<float, GradientArrow> arrows;
float index = 0, inc = 1.0f / std::max<size_t>(size_t(1), m_Arrows.size() - 1);
for (auto it : m_Arrows)
if (!m_Arrows.empty())
{
arrows[index] = it.second;
index = std::min(1.0f, index + inc);
map<float, GradientArrow> arrows;
float index = 0, inc = 1.0f / std::max<size_t>(size_t(1), m_Arrows.size() - 1);
for (auto it : m_Arrows)
{
arrows[index] = it.second;
index = std::min(1.0f, index + inc);
}
m_Arrows = std::move(arrows);
}
m_Arrows = std::move(arrows);
update();
}
@ -265,6 +284,7 @@ void GradientColorsView::ResetToDefault()
void GradientColorsView::ClearArrows()
{
m_Arrows.clear();
update();
}
/// <summary>
@ -334,20 +354,6 @@ int GradientColorsView::GetFocusedIndex()
return index;
}
/// <summary>
/// Return a pixmap to be used to draw the palette.
/// The pixmap is lazily instantiated on the first call, and all subsequent
/// calls return a pointer to the same pixmap.
/// </summary>
/// <returns>The pixmap</returns>
QPixmap* GradientColorsView::GetBackGround()
{
if (!m_Background.get())
CreateBackground(m_BackgroundVerSpace, m_BackgroundHorSpace);
return m_Background.get();
}
/// <summary>
/// Return a reference to the arrows map.
/// Be very careful what you do with this.
@ -366,34 +372,37 @@ map<float, GradientArrow>& GradientColorsView::GetArrows()
/// <returns>A reference to the internal map containing the arrows</returns>
Palette<float>& GradientColorsView::GetPalette(int size)
{
QSize imageSize(size, 1);
QImage image(imageSize, QImage::Format_ARGB32_Premultiplied);
QPainter p;
QLinearGradient grad(QPoint(0, 0), QPoint(imageSize.width(), imageSize.height()));
m_Palette.m_SourceColors.clear();
for (auto& it : m_Arrows)
if (!m_Arrows.empty())
{
auto pos = it.first;
auto col = it.second.Color();
m_Palette.m_SourceColors[pos] = v4F(col.red() / 255.0f, col.green() / 255.0f, col.blue() / 255.0f, 1.0f);
grad.setColorAt(pos, col);
QPainter p;
QSize imageSize(size, 1);
QImage image(imageSize, QImage::Format_ARGB32_Premultiplied);
QLinearGradient grad(QPoint(0, 0), QPoint(imageSize.width(), imageSize.height()));
m_Palette.m_SourceColors.clear();
for (auto& it : m_Arrows)
{
auto pos = it.first;
auto col = it.second.Color();
m_Palette.m_SourceColors[pos] = v4F(col.red() / 255.0f, col.green() / 255.0f, col.blue() / 255.0f, 1.0f);
grad.setColorAt(pos, col);
}
p.begin(&image);
p.fillRect(image.rect(), grad);
p.end();
m_Palette.m_Entries.reserve(image.width());
for (int i = 0; i < image.width(); i++)
{
QColor col(image.pixel(i, 0));
m_Palette[i].r = col.red() / 255.0f;
m_Palette[i].g = col.green() / 255.0f;
m_Palette[i].b = col.blue() / 255.0f;
}
}
p.begin(&image);
p.fillRect(image.rect(), grad);
p.end();
m_Palette.m_Entries.reserve(image.width());
for (int i = 0; i < image.width(); i++)
{
QColor col(image.pixel(i, 0));
m_Palette[i].r = col.red() / 255.0f;
m_Palette[i].g = col.green() / 255.0f;
m_Palette[i].b = col.blue() / 255.0f;
}
return m_Palette;
return m_Palette;//If fixed palette, just return verbatim.
}
/// <summary>
@ -406,11 +415,11 @@ Palette<float>& GradientColorsView::GetPalette(int size)
/// <param name="palette">The palette whose source colors will be assigned to the arrow map</param>
void GradientColorsView::SetPalette(const Palette<float>& palette)
{
m_Arrows.clear();
m_Palette = palette;
if (palette.m_SourceColors.size() > 1)
{
m_Palette = palette;
m_Arrows.clear();
for (auto& col : m_Palette.m_SourceColors)
{
auto& rgb = col.second;
@ -418,7 +427,48 @@ void GradientColorsView::SetPalette(const Palette<float>& palette)
}
SetFocus(size_t(0));
update();
}
else
{
auto v = m_Palette.MakeRgbPaletteBlock(m_ViewRect.height());//Make the palette repeat for PALETTE_CELL_HEIGHT rows.
auto image = QImage(int(m_Palette.Size()), m_ViewRect.height(), QImage::Format_RGB888);//Create a QImage out of it.
memcpy(image.scanLine(0), v.data(), v.size() * sizeof(v[0]));//Memcpy the data in.
m_FinalFixedPixmap = QPixmap(QPixmap::fromImage(image));//Create a QPixmap out of the QImage.
}
update();
}
/// <summary>
/// Return a temporary copy of the xform color indices as a map.
/// The keys are the xform indices, and the values are the color indices.
/// </summary>
/// <param name="palette">The color indices</param>
map<size_t, float> GradientColorsView::GetColorIndices() const
{
map<size_t, float> indices;
for (auto& it : m_ColorIndicesArrows)
indices[it.first] = it.second.first;
return indices;
}
/// <summary>
/// Assign the values of the xform color indices to the arrows.
/// This will clear out any existing values first.
/// </summary>
/// <param name="palette">The color indices to assign</param>
void GradientColorsView::SetColorIndices(const map<size_t, float>& indices)
{
QPainter painter(this);
m_ColorIndicesArrows.clear();
for (auto& it : indices)
{
auto text = QString::number(it.first + 1);//Get text width, which is used to adjust the size of the arrow.
QFontMetrics fm = painter.fontMetrics();
m_ColorIndicesArrows[it.first] = make_pair(it.second, TopArrow(fm.width(text) + 5, it.first));
}
}
@ -431,44 +481,60 @@ void GradientColorsView::paintEvent(QPaintEvent*)
m_ViewRect.size().isEmpty() ||
m_ViewRect.topLeft() == m_ViewRect.bottomRight())
{
m_ViewRect = QRect(QPoint(5, 0), QPoint(width() - 15, height() / 3 * 2 - 10));
m_ViewRect.translate(5, 5);
CreateBackground();
resizeEvent(nullptr);//Setup rects.
}
QPainter painter(this);
if (m_Background.get())
painter.drawPixmap(m_ViewRect, *m_Background.get(), m_ViewRect);
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
painter.setRenderHint(QPainter::Antialiasing);
QPoint gradStart = QPoint(m_ViewRect.topLeft().x(), m_ViewRect.bottomLeft().y() / 2);
QPoint gradStop = QPoint(m_ViewRect.topRight().x(), m_ViewRect.bottomRight().y() / 2);
QLinearGradient grad(gradStart, gradStop);
for (auto& it : m_Arrows)
if (!m_Arrows.empty())
{
GradientArrow& arrow = it.second;
grad.setColorAt(it.first, arrow.Color());
QPolygon arrowPolygon = arrow.Area();
int iPosX = it.first * (width() - 20),
iPosY = height() / 3 * 2;
arrowPolygon.translate(iPosX, iPosY);
QPainterPath paintPath;
paintPath.addPolygon(arrowPolygon);
painter.setBrush(QBrush(arrow.Color()));
QPoint gradStart = QPoint(m_ViewRect.topLeft().x(), m_ViewRect.bottomLeft().y() / 2);
QPoint gradStop = QPoint(m_ViewRect.topRight().x(), m_ViewRect.bottomRight().y() / 2);
QLinearGradient grad(gradStart, gradStop);
if (arrow.Focus())
paintPath.addRect(iPosX + 5, iPosY + 20, 10, 5);
for (auto& it : m_Arrows)
{
GradientArrow& arrow = it.second;
grad.setColorAt(it.first, arrow.Color());
QPolygon arrowPolygon = arrow.Area();
int iPosX = it.first * RectWidth(),
iPosY = m_ViewRect.height() + m_ViewRect.top() + 3;
arrowPolygon.translate(iPosX, iPosY);
QPainterPath paintPath;
paintPath.addPolygon(arrowPolygon);
painter.setBrush(QBrush(arrow.Color()));
painter.drawPath(paintPath);
painter.setBrush(QBrush(Qt::NoBrush));
if (arrow.Focus())
paintPath.addRect(iPosX + 5, iPosY + 20, 10, 5);
painter.drawPath(paintPath);
painter.setBrush(QBrush(Qt::NoBrush));
}
QBrush brush(grad);
painter.fillRect(m_ViewRect, brush);
painter.drawRect(m_ViewRect);
}
else
{
painter.drawPixmap(m_ViewRect, m_FinalFixedPixmap);
}
for (auto& it : m_ColorIndicesArrows)
{
QPainterPath topArrowPaintPath;
auto& topArrow = it.second.second;
auto topArrowPolygon = topArrow.Area();
topArrowPolygon.translate(it.second.first * RectWidth(), 0);
auto topArrowRect = topArrowPolygon.boundingRect();
topArrowPaintPath.addPolygon(topArrowPolygon);
painter.drawPath(topArrowPaintPath);//When using a separate painter, the sides aren't as thick.
//Draw text inside of the arrow.
painter.drawText(topArrowRect.x() + (topArrowRect.width() - (topArrow.Width() - 5)) / 2.0, topArrowRect.y() + (topArrowRect.height() - 5), topArrow.Text());
}
QBrush brush(grad);
painter.fillRect(m_ViewRect, brush);
painter.drawRect(m_ViewRect);
painter.end();
}
@ -484,7 +550,7 @@ void GradientColorsView::mousePressEvent(QMouseEvent* e)
{
auto& arrow = it.second;
QPolygon poly = arrow.Area();
poly.translate(it.first * (width() - 20), height() / 3 * 2);
poly.translate(it.first * m_ViewRectSize.x(), m_ViewRectSize.y());
if (poly.containsPoint(m_DragStart, Qt::OddEvenFill))
{
@ -495,6 +561,21 @@ void GradientColorsView::mousePressEvent(QMouseEvent* e)
arrow.Focus(false);
}
for (auto& it : m_ColorIndicesArrows)
{
auto& arrow = it.second.second;
QPolygon poly = arrow.Area();
poly.translate(it.second.first * m_ViewRectSize.x(), 0);
if (poly.containsPoint(m_DragStart, Qt::OddEvenFill))
{
m_ColorIndexArrowMoving = true;
arrow.Focus(true);
}
else
arrow.Focus(false);
}
update();
}
@ -508,7 +589,7 @@ void GradientColorsView::mouseDoubleClickEvent(QMouseEvent* e)
{
auto& arrow = it.second;
QPolygon poly = arrow.Area();
poly.translate(it.first * (width() - 20), height() / 3 * 2);
poly.translate(it.first * m_ViewRectSize.x(), m_ViewRectSize.y());
if (poly.containsPoint(e->pos(), Qt::OddEvenFill))
{
@ -526,50 +607,76 @@ void GradientColorsView::mouseDoubleClickEvent(QMouseEvent* e)
/// <param name="event">The mouse event</param>
void GradientColorsView::mouseMoveEvent(QMouseEvent* e)
{
if (!m_ArrowMoving) return;
if (!m_ArrowMoving && !m_ColorIndexArrowMoving) return;
size_t index = 0;
qreal maxMove = 11.5 / (width() - 20);
qreal maxMove = 11.5 / RectWidth();
for (auto it = m_Arrows.begin(); it != m_Arrows.end(); ++it)
if (m_ArrowMoving)
{
auto& arrow = it->second;
if (arrow.Focus())
for (auto it = m_Arrows.begin(); it != m_Arrows.end(); ++it)
{
qreal lastPos = it->first;
qreal start = m_DragStart.x();
qreal end = width() - 20;
qreal dPos = ((qreal) e->pos().x() - start) / end;
qreal newPos = lastPos + dPos;
auto& arrow = it->second;
if ( (it->first + dPos > 1) || (it->first + dPos < 0) )
return;
if (dPos < 0 && index > 0)
if (arrow.Focus())
{
qreal posBefore = std::prev(it)->first;
qreal lastPos = it->first;
qreal start = m_DragStart.x();
qreal end = RectWidth();
qreal dPos = ((qreal)e->pos().x() - start) / end;
qreal newPos = lastPos + dPos;
if ( (lastPos - maxMove + dPos) <= posBefore )
if ((lastPos + dPos > 1) || (lastPos + dPos < 0))
return;
if (dPos < 0 && index > 0)
{
qreal posBefore = std::prev(it)->first;
if ((lastPos - maxMove + dPos) <= posBefore)
return;
}
if ((dPos > 0) && (index < (m_Arrows.size() - 1)))
{
qreal posAfter = std::next(it)->first;
if ((lastPos + maxMove + dPos) >= posAfter)
return;
}
GradientArrow arrowCopy(it->second);
m_Arrows.erase(lastPos);
m_Arrows[newPos] = arrowCopy;
emit ArrowMove(lastPos, arrow);
break;
}
if ((dPos > 0) && (index < (m_Arrows.size() - 1)))
{
qreal posAfter = std::next(it)->first;
if ((lastPos + maxMove + dPos) >= posAfter)
return;
}
GradientArrow arrowCopy(it->second);
m_Arrows.erase(lastPos);
m_Arrows[newPos] = arrowCopy;
emit ArrowMove(lastPos, arrow);
break;
index++;
}
}
else if (m_ColorIndexArrowMoving)
{
for (auto& it : m_ColorIndicesArrows)
{
auto& arrow = it.second.second;
index++;
if (arrow.Focus())
{
qreal lastPos = it.second.first;
qreal start = m_DragStart.x();
qreal end = RectWidth();
qreal dPos = ((qreal)e->pos().x() - start) / end;
qreal newPos = lastPos + dPos;
if ((lastPos + dPos > 1) || (lastPos + dPos < 0))
return;
it.second.first = newPos;
emit ColorIndexMove(it.first, it.second.first);
break;
}
}
}
m_DragStart = e->pos();
@ -582,6 +689,7 @@ void GradientColorsView::mouseMoveEvent(QMouseEvent* e)
void GradientColorsView::mouseReleaseEvent(QMouseEvent*)
{
m_ArrowMoving = false;
m_ColorIndexArrowMoving = false;
}
/// <summary>
@ -589,42 +697,25 @@ void GradientColorsView::mouseReleaseEvent(QMouseEvent*)
/// </summary>
void GradientColorsView::resizeEvent(QResizeEvent*)
{
m_ViewRect = QRect(QPoint(5, 0), QPoint(width() - 15, height() / 3 * 2 - 10));
m_ViewRect.translate(5, 5);
m_ViewRectSize = QPoint(RectWidth(), RectHeight());
m_ViewRect = QRect(m_ViewRectOffset, QPoint(m_ViewRectSize.x() + 5, m_ViewRectSize.y() - 10));
m_ViewRect.translate(m_ViewRectTranslate);
}
/// <summary>
/// Create the background to represent the palette.
/// Return the width used to draw the gradient area.
/// </summary>
/// <param name="vertLineSpace">The space between vertical lines to use</param>
/// <param name="horLineSpace">The space between horizontal lines to use</param>
void GradientColorsView::CreateBackground(int vertLineSpace, int horLineSpace)
/// <returns>The width</returns>
int GradientColorsView::RectWidth()
{
m_BackgroundVerSpace = vertLineSpace;
m_BackgroundHorSpace = horLineSpace;
m_Background = make_unique<QPixmap>(QSize(800, 800));
m_Background->fill(Qt::white);
QPainter painter(m_Background.get());
int x = 0;
while (x < m_Background->width())//Veritcal lines.
{
const QPoint lineStart(x, 0);
const QPoint lineStop(x, m_Background->height());
painter.drawLine(lineStart, lineStop);
x += vertLineSpace;
}
int y = 0;
while (y < m_Background->height())//Horizontal lines.
{
const QPoint lineStart(0, y);
const QPoint lineStop(m_Background->width(), y);
painter.drawLine(lineStart, lineStop);
y += horLineSpace;
}
painter.end();
update();
return width() - 20;
}
/// <summary>
/// Return the height used to draw the gradient area.
/// </summary>
/// <returns>The height</returns>
int GradientColorsView::RectHeight()
{
return height() / 3 * 2;
}

View File

@ -50,14 +50,16 @@ public:
void SetArrows(map<float, GradientArrow>& newArrows);
int ArrowCount();
int GetFocusedIndex();
QPixmap* GetBackGround();
map<float, GradientArrow>& GetArrows();
Palette<float>& GetPalette(int size);
void SetPalette(const Palette<float>& palette);
map<size_t, float> GetColorIndices() const;
void SetColorIndices(const map<size_t, float>& indices);
signals:
Q_SIGNALS:
void ArrowMove(qreal lastPos, const GradientArrow& arrow);
void ArrowDoubleClicked(const GradientArrow& arrow);
void ColorIndexMove(size_t index, float value);
protected:
virtual void paintEvent(QPaintEvent* e) override;
@ -68,13 +70,17 @@ protected:
virtual void resizeEvent(QResizeEvent*) override;
private:
void CreateBackground(int vertLineSpace = 5, int horLineSpace = 5);
int RectWidth();
int RectHeight();
bool m_ArrowMoving = false;
int m_BackgroundVerSpace = 5;
int m_BackgroundHorSpace = 5;
bool m_ColorIndexArrowMoving = false;
QPoint m_ViewRectSize;
QPoint m_ViewRectOffset = QPoint(5, 15);
QPoint m_ViewRectTranslate = QPoint(5, 5);
QRect m_ViewRect;
QPoint m_DragStart;
unique_ptr<QPixmap> m_Background;
map<float, GradientArrow> m_Arrows;
map<size_t, pair<float, TopArrow>> m_ColorIndicesArrows;
Palette<float> m_Palette;
QPixmap m_FinalFixedPixmap;
};

View File

@ -27,6 +27,7 @@ PaletteEditor::PaletteEditor(QWidget* p) :
connect(m_ColorPicker, SIGNAL(ColorChanged(const QColor&)), this, SLOT(OnColorPickerColorChanged(const QColor&)));
connect(m_GradientColorView, SIGNAL(ArrowMove(qreal, const GradientArrow&)), this, SLOT(OnArrowMoved(qreal, const GradientArrow&)));
connect(m_GradientColorView, SIGNAL(ArrowDoubleClicked(const GradientArrow&)), this, SLOT(OnArrowDoubleClicked(const GradientArrow&)));
connect(m_GradientColorView, SIGNAL(ColorIndexMove(size_t, float)), this, SLOT(OnColorIndexMove(size_t, float)));
connect(ui->CreatePaletteFromImageButton, SIGNAL(clicked()), this, SLOT(OnCreatePaletteFromImageButtonClicked()));
connect(ui->CreatePaletteAgainFromImageButton, SIGNAL(clicked()), this, SLOT(OnCreatePaletteAgainFromImageButton()));
connect(ui->AddColorButton, SIGNAL(clicked()), this, SLOT(OnAddColorButtonClicked()));
@ -54,11 +55,8 @@ PaletteEditor::PaletteEditor(QWidget* p) :
for (auto& pal : pals)
{
if (m_PaletteList->IsModifiable(pal.first))//Only add user created palettes.
{
QFileInfo info(QString::fromStdString(pal.first));
ui->PaletteFilenameCombo->addItem(info.fileName());
}
QFileInfo info(QString::fromStdString(pal.first));
ui->PaletteFilenameCombo->addItem(info.fileName());
}
if (ui->PaletteFilenameCombo->count() > 0)
@ -74,15 +72,6 @@ bool PaletteEditor::Sync()
return ui->SyncCheckBox->isChecked();
}
/// <summary>
/// Get a pointer to the palette pixmap from the underlying gradient color view.
/// </summary>
/// <returns>QPixmap*</returns>
QPixmap* PaletteEditor::GetBackGround()
{
return m_GradientColorView->GetBackGround();
}
/// <summary>
/// Populate and retrieve a reference to the palette from the underlying gradient color view
/// using the specified number of elements.
@ -96,23 +85,43 @@ Palette<float>& PaletteEditor::GetPalette(int size)
/// <summary>
/// Set the palette of the underlying gradient color view.
/// Note this assignment will only take place if
/// the number of source colors is 2 or more.
/// This will only be the case if it was a user created palette made here.
/// All palettes gotten from elsewhere are not assignable.
/// This can be a modifiable palette or a fixed one.
/// </summary>
/// <param name="palette">The palette to assign</param>
void PaletteEditor::SetPalette(Palette<float>& palette)
void PaletteEditor::SetPalette(const Palette<float>& palette)
{
if (palette.m_SourceColors.size() > 1)
{
m_PaletteIndex = std::numeric_limits<int>::max();
m_GradientColorView->SetPalette(palette);
auto& arrows = m_GradientColorView->GetArrows();
auto combo = ui->PaletteFilenameCombo;
m_PaletteIndex = std::numeric_limits<int>::max();
m_GradientColorView->SetPalette(palette);
auto& arrows = m_GradientColorView->GetArrows();
if (!arrows.empty())
m_ColorPicker->SetColorPanelColor(arrows.begin()->second.Color());
}
if (!arrows.empty())
m_ColorPicker->SetColorPanelColor(arrows.begin()->second.Color());//Will emit PaletteChanged() if color changed...
QFileInfo info(QString::fromStdString(*palette.m_Filename.get()));
combo->setCurrentIndex(combo->findData(info.fileName(), Qt::DisplayRole));
EnablePaletteControls();
EmitPaletteChanged();//...So emit here just to be safe.
}
/// <summary>
/// Return a temporary copy of the xform color indices as a map.
/// The keys are the xform indices, and the values are the color indices.
/// </summary>
/// <param name="palette">The color indices</param>
map<size_t, float> PaletteEditor::GetColorIndices() const
{
return m_GradientColorView->GetColorIndices();
}
/// <summary>
/// Assign the values of the xform color indices to the arrows.
/// This will clear out any existing values first.
/// </summary>
/// <param name="palette">The color indices to assign</param>
void PaletteEditor::SetColorIndices(const map<size_t, float>& indices)
{
m_GradientColorView->SetColorIndices(indices);
}
/// <summary>
@ -229,7 +238,9 @@ void PaletteEditor::OnCreatePaletteAgainFromImageButton()
void PaletteEditor::OnColorPickerColorChanged(const QColor& col)
{
m_GradientColorView->SetFocusColor(col);
EmitPaletteChanged();
if (m_GradientColorView->ArrowCount())
EmitPaletteChanged();
}
/// <summary>
@ -252,6 +263,7 @@ void PaletteEditor::OnArrowDoubleClicked(const GradientArrow& arrow)
void PaletteEditor::OnSyncCheckBoxStateChanged(int state)
{
EmitPaletteChanged();
EmitColorIndexChanged(std::numeric_limits<size_t>::max(), 0);//Pass special value to update all.
}
/// <summary>
@ -268,7 +280,7 @@ void PaletteEditor::OnPaletteFilenameComboChanged(const QString& text)
::FillPaletteTable(text.toStdString(), paletteTable, m_PaletteList);
auto fullname = m_PaletteList->GetFullPathFromFilename(m_CurrentPaletteFilePath);
ui->PaletteFilenameCombo->setToolTip(QString::fromStdString(fullname));
OnPaletteCellClicked(0, 1);
EnablePaletteFileControls();
}
}
@ -302,8 +314,11 @@ void PaletteEditor::OnPaletteCellChanged(int row, int col)
{
if (auto palette = m_PaletteList->GetPaletteByFilename(m_CurrentPaletteFilePath, row))
{
palette->m_Name = ui->PaletteListTable->item(row, col)->text().toStdString();
emit PaletteFileChanged();
if (!palette->m_SourceColors.empty())
{
palette->m_Name = ui->PaletteListTable->item(row, col)->text().toStdString();
emit PaletteFileChanged();
}
}
}
}
@ -410,6 +425,17 @@ void PaletteEditor::OnArrowMoved(qreal, const GradientArrow&)
EmitPaletteChanged();
}
/// <summary>
/// Emit an xform color index changed event.
/// Called when one of the top arrows are moved.
/// </summary>
/// <param name="index">The index of the xform whose color index has been changed. Special value of size_t max to update all</param>
/// <param name="value">The value of the color index</param>
void PaletteEditor::OnColorIndexMove(size_t index, float value)
{
EmitColorIndexChanged(index, value);
}
/// <summary>
/// Emit a palette changed event if the sync checkbox is checked.
/// </summary>
@ -419,6 +445,17 @@ void PaletteEditor::EmitPaletteChanged()
emit PaletteChanged();
}
/// <summary>
/// Emit an xform color index changed event if the sync checkbox is checked.
/// </summary>
/// <param name="index">The index of the xform whose color index has been changed. Special value of size_t max to update all</param>
/// <param name="value">The value of the color index</param>
void PaletteEditor::EmitColorIndexChanged(size_t index, float value)
{
if (ui->SyncCheckBox->isChecked())
emit ColorIndexChanged(index, value);
}
/// <summary>
/// Helper to lazily instantiate an open file dialog.
/// Once created, it will remain alive for the duration of the program run.
@ -514,3 +551,31 @@ map<float, GradientArrow> PaletteEditor::GetRandomColorsFromImage(QString filena
return colors;
}
/// <summary>
/// Enable/disable controls related to switching between modifiable and fixed palette files.
/// </summary>
void PaletteEditor::EnablePaletteFileControls()
{
bool b = m_PaletteList->IsModifiable(m_CurrentPaletteFilePath);//At least one in the file is not fixed.
ui->DeletePaletteButton->setEnabled(b);
ui->CopyPaletteFileButton->setEnabled(b);
ui->AppendPaletteButton->setEnabled(b);
}
/// <summary>
/// Enable/disable controls related to switching between modifiable and fixed palettes.
/// </summary>
void PaletteEditor::EnablePaletteControls()
{
auto& palette = m_GradientColorView->GetPalette(256);
bool b = !palette.m_SourceColors.empty();
ui->OverwritePaletteButton->setEnabled(b);
ui->AddColorButton->setEnabled(b);
ui->DistributeColorsButton->setEnabled(b);
ui->AutoDistributeCheckBox->setEnabled(b);
ui->RandomColorsButton->setEnabled(b);
ui->RemoveColorButton->setEnabled(b);
ui->ResetColorsButton->setEnabled(b);
ui->ArrowsSpinBox->setEnabled(b);
}

View File

@ -30,15 +30,17 @@ public:
public:
bool Sync();
QPixmap* GetBackGround();
Palette<float>& GetPalette(int size);
void SetPalette(Palette<float>& palette);
void SetPalette(const Palette<float>& palette);
map<size_t, float> GetColorIndices() const;
void SetColorIndices(const map<size_t, float>& indices);
signals:
Q_SIGNALS:
void PaletteChanged();
void PaletteFileChanged();
void ColorIndexChanged(size_t index, float value);
private slots:
private Q_SLOTS:
void OnAddColorButtonClicked();
void OnRemoveColorButtonClicked();
void OnInvertColorsButtonClicked();
@ -51,6 +53,7 @@ private slots:
void OnArrowDoubleClicked(const GradientArrow& arrow);
void OnSyncCheckBoxStateChanged(int state);
void OnArrowMoved(qreal lastPos, const GradientArrow& arrow);
void OnColorIndexMove(size_t index, float value);
void OnPaletteFilenameComboChanged(const QString& text);
void OnPaletteCellClicked(int row, int col);
void OnPaletteCellChanged(int row, int col);
@ -62,10 +65,12 @@ private slots:
private:
void EmitPaletteChanged();
void EmitColorIndexChanged(size_t index, float value);
QStringList SetupOpenImagesDialog();
void AddArrow(const QColor& color);
map<float, GradientArrow> GetRandomColorsFromImage(QString filename, int numPoints);
void EnablePaletteFileControls();
void EnablePaletteControls();
bool m_PaletteFileChanged = false;
int m_PaletteIndex = 0;
QString m_Filename;