#pragma once #include "Utils.h" #include "PaletteList.h" #include "VariationList.h" /// /// XmlToEmber and Locale classes. /// namespace EmberNs { /// /// Convenience class for setting and resetting the locale. /// It's set up in the constructor and restored in the destructor. /// This relieves the caller of having to manually do it everywhere. /// class EMBER_API Locale { public: /// /// Constructor which saves the state of the current locale and /// sets the new one based on the parameters passed in. /// /// The locale category. Default: LC_NUMERIC. /// The locale. Default: "C". Locale(int category = LC_NUMERIC, const char* loc = "C") { m_Category = category; m_NewLocale = string(loc); m_OriginalLocale = setlocale(category, nullptr);//Query. if (m_OriginalLocale.empty()) cout << "Couldn't get original locale." << endl; if (setlocale(category, loc) == nullptr)//Set. cout << "Couldn't set new locale " << category << ", " << loc << "." << endl; } /// /// Reset the locale to the value stored during construction. /// ~Locale() { if (!m_OriginalLocale.empty()) if (setlocale(m_Category, m_OriginalLocale.c_str()) == nullptr)//Restore. cout << "Couldn't restore original locale " << m_Category << ", " << m_OriginalLocale << "." << endl; } private: int m_Category; string m_NewLocale; string m_OriginalLocale; }; /// /// Class for reading Xml files into ember objects. /// This class derives from EmberReport, so the caller is able /// to retrieve a text dump of error information if any errors occur. /// Since this class contains a VariationList object, it's important to declare one /// instance and reuse it for the duration of the program instead of creating and deleting /// them as local variables. /// Template argument expected to be float or double. /// template class EMBER_API XmlToEmber : public EmberReport { public: /// /// Constructor that initializes the random context. /// XmlToEmber() { Timing t; if (!m_Init) { m_BadParamNames.reserve(100); m_BadParamNames.push_back(pair("swtin_distort", "stwin_distort"));//stwin. m_BadParamNames.push_back(pair("pow_numerator", "pow_block_numerator"));//pow_block. m_BadParamNames.push_back(pair("pow_denominator", "pow_block_denominator")); m_BadParamNames.push_back(pair("pow_root", "pow_block_root")); m_BadParamNames.push_back(pair("pow_correctn", "pow_block_correctn")); m_BadParamNames.push_back(pair("pow_correctd", "pow_block_correctd")); m_BadParamNames.push_back(pair("pow_power", "pow_block_power")); m_BadParamNames.push_back(pair("lT", "linearT_powX"));//linearT. m_BadParamNames.push_back(pair("lT", "linearT_powY")); m_BadParamNames.push_back(pair("Re_A", "Mobius_Re_A"));//Mobius. m_BadParamNames.push_back(pair("Im_A", "Mobius_Im_A")); m_BadParamNames.push_back(pair("Re_B", "Mobius_Re_B")); m_BadParamNames.push_back(pair("Im_B", "Mobius_Im_B")); m_BadParamNames.push_back(pair("Re_C", "Mobius_Re_C")); m_BadParamNames.push_back(pair("Im_C", "Mobius_Im_C")); m_BadParamNames.push_back(pair("Re_D", "Mobius_Re_D")); m_BadParamNames.push_back(pair("Im_D", "Mobius_Im_D")); m_BadParamNames.push_back(pair("rx_sin", "rotate_x_sin"));//rotate_x. m_BadParamNames.push_back(pair("rx_cos", "rotate_x_cos")); m_BadParamNames.push_back(pair("ry_sin", "rotate_y_sin"));//rotate_y. m_BadParamNames.push_back(pair("ry_cos", "rotate_y_cos")); m_BadParamNames.push_back(pair("intrfr2_a1", "interference2_a1"));//interference2. m_BadParamNames.push_back(pair("intrfr2_b1", "interference2_b1")); m_BadParamNames.push_back(pair("intrfr2_c1", "interference2_c1")); m_BadParamNames.push_back(pair("intrfr2_p1", "interference2_p1")); m_BadParamNames.push_back(pair("intrfr2_t1", "interference2_t1")); m_BadParamNames.push_back(pair("intrfr2_a2", "interference2_a2")); m_BadParamNames.push_back(pair("intrfr2_b2", "interference2_b2")); m_BadParamNames.push_back(pair("intrfr2_c2", "interference2_c2")); m_BadParamNames.push_back(pair("intrfr2_p2", "interference2_p2")); m_BadParamNames.push_back(pair("intrfr2_t2", "interference2_t2")); m_BadParamNames.push_back(pair("octa_x", "octagon_x"));//octagon. m_BadParamNames.push_back(pair("octa_y", "octagon_y")); m_BadParamNames.push_back(pair("octa_z", "octagon_z")); m_BadParamNames.push_back(pair("bubble_x", "bubble2_x"));//bubble2. m_BadParamNames.push_back(pair("bubble_y", "bubble2_y")); m_BadParamNames.push_back(pair("bubble_z", "bubble2_z")); m_BadParamNames.push_back(pair("cubic3D_xpand", "cubicLattice_3D_xpand"));//cubicLattice_3D. m_BadParamNames.push_back(pair("cubic3D_style", "cubicLattice_3D_style")); m_BadParamNames.push_back(pair("splitb_x", "SplitBrdr_x"));//SplitBrdr. m_BadParamNames.push_back(pair("splitb_y", "SplitBrdr_y")); m_BadParamNames.push_back(pair("splitb_px", "SplitBrdr_px")); m_BadParamNames.push_back(pair("splitb_py", "SplitBrdr_py")); m_BadParamNames.push_back(pair("dc_cyl_offset", "dc_cylinder_offset"));//dc_cylinder. m_BadParamNames.push_back(pair("dc_cyl_angle", "dc_cylinder_angle")); m_BadParamNames.push_back(pair("dc_cyl_scale", "dc_cylinder_scale")); m_BadParamNames.push_back(pair("cyl_x", "dc_cylinder_x")); m_BadParamNames.push_back(pair("cyl_y", "dc_cylinder_y")); m_BadParamNames.push_back(pair("cyl_blur", "dc_cylinder_blur")); m_BadParamNames.push_back(pair("mobius_radius", "mobius_strip_radius"));//mobius_strip. m_BadParamNames.push_back(pair("mobius_width", "mobius_strip_width")); m_BadParamNames.push_back(pair("mobius_rect_x", "mobius_strip_rect_x")); m_BadParamNames.push_back(pair("mobius_rect_y", "mobius_strip_rect_y")); m_BadParamNames.push_back(pair("mobius_rotate_x", "mobius_strip_rotate_x")); m_BadParamNames.push_back(pair("mobius_rotate_y", "mobius_strip_rotate_y")); m_BadParamNames.push_back(pair("bwraps2_cellsize", "bwraps_cellsize"));//bwraps2. m_BadParamNames.push_back(pair("bwraps2_space", "bwraps_space")); m_BadParamNames.push_back(pair("bwraps2_gain", "bwraps_gain")); m_BadParamNames.push_back(pair("bwraps2_inner_twist", "bwraps_inner_twist")); m_BadParamNames.push_back(pair("bwraps2_outer_twist", "bwraps_outer_twist")); m_BadParamNames.push_back(pair("bwraps7_cellsize", "bwraps_cellsize"));//bwraps7. m_BadParamNames.push_back(pair("bwraps7_space", "bwraps_space")); m_BadParamNames.push_back(pair("bwraps7_gain", "bwraps_gain")); m_BadParamNames.push_back(pair("bwraps7_inner_twist", "bwraps_inner_twist")); m_BadParamNames.push_back(pair("bwraps7_outer_twist", "bwraps_outer_twist")); m_BadParamNames.push_back(pair("pre_bwraps2_cellsize", "pre_bwraps_cellsize")); m_BadParamNames.push_back(pair("pre_bwraps2_space", "pre_bwraps_space")); m_BadParamNames.push_back(pair("pre_bwraps2_gain", "pre_bwraps_gain")); m_BadParamNames.push_back(pair("pre_bwraps2_inner_twist", "pre_bwraps_inner_twist")); m_BadParamNames.push_back(pair("pre_bwraps2_outer_twist", "pre_bwraps_outer_twist")); m_BadParamNames.push_back(pair("post_bwraps2_cellsize", "post_bwraps_cellsize")); m_BadParamNames.push_back(pair("post_bwraps2_space", "post_bwraps_space")); m_BadParamNames.push_back(pair("post_bwraps2_gain", "post_bwraps_gain")); m_BadParamNames.push_back(pair("post_bwraps2_inner_twist", "post_bwraps_inner_twist")); m_BadParamNames.push_back(pair("post_bwraps2_outer_twist", "post_bwraps_outer_twist")); m_FlattenNames.reserve(24); m_FlattenNames.push_back("linear3D"); m_FlattenNames.push_back("bubble"); m_FlattenNames.push_back("cylinder"); m_FlattenNames.push_back("zblur"); m_FlattenNames.push_back("blur3D"); m_FlattenNames.push_back("pre_ztranslate"); m_FlattenNames.push_back("pre_rotate_x"); m_FlattenNames.push_back("pre_rotate_y"); m_FlattenNames.push_back("ztranslate"); m_FlattenNames.push_back("zcone"); m_FlattenNames.push_back("post_rotate_x"); m_FlattenNames.push_back("post_rotate_y"); m_FlattenNames.push_back("julia3D"); m_FlattenNames.push_back("julia3Dz"); m_FlattenNames.push_back("curl3D_cz"); m_FlattenNames.push_back("hemisphere"); m_FlattenNames.push_back("bwraps"); m_FlattenNames.push_back("bwraps2"); m_FlattenNames.push_back("falloff2"); m_FlattenNames.push_back("crop"); m_FlattenNames.push_back("pre_falloff2"); m_FlattenNames.push_back("pre_crop"); m_FlattenNames.push_back("post_falloff2"); m_FlattenNames.push_back("post_crop"); //This is a vector of the param names as they are in the legacy, badly named flam3/Apophysis code. vector badParams; badParams.reserve(6); badParams.push_back("bwraps7_cellsize"); badParams.push_back("bwraps7_space"); badParams.push_back("bwraps7_gain"); badParams.push_back("bwraps7_inner_twist"); badParams.push_back("bwraps7_outer_twist"); m_BadVariationNames.push_back(make_pair(make_pair(string("bwraps7"), string("bwraps")), badParams));//bwraps7 is the same as bwraps. badParams.clear(); badParams.push_back("bwraps2_cellsize"); badParams.push_back("bwraps2_space"); badParams.push_back("bwraps2_gain"); badParams.push_back("bwraps2_inner_twist"); badParams.push_back("bwraps2_outer_twist"); m_BadVariationNames.push_back(make_pair(make_pair(string("bwraps2"), string("bwraps")), badParams));//bwraps2 is the same as bwraps. badParams.clear(); badParams.push_back("pre_bwraps2_cellsize"); badParams.push_back("pre_bwraps2_space"); badParams.push_back("pre_bwraps2_gain"); badParams.push_back("pre_bwraps2_inner_twist"); badParams.push_back("pre_bwraps2_outer_twist"); m_BadVariationNames.push_back(make_pair(make_pair(string("pre_bwraps2"), string("pre_bwraps")), badParams)); badParams.clear(); badParams.push_back("post_bwraps2_cellsize"); badParams.push_back("post_bwraps2_space"); badParams.push_back("post_bwraps2_gain"); badParams.push_back("post_bwraps2_inner_twist"); badParams.push_back("post_bwraps2_outer_twist"); m_BadVariationNames.push_back(make_pair(make_pair(string("post_bwraps2"), string("post_bwraps")), badParams)); badParams.clear(); badParams.push_back("mobius_radius"); badParams.push_back("mobius_width"); badParams.push_back("mobius_rect_x"); badParams.push_back("mobius_rect_y"); badParams.push_back("mobius_rotate_x"); badParams.push_back("mobius_rotate_y"); m_BadVariationNames.push_back(make_pair(make_pair(string("mobius"), string("mobius_strip")), badParams));//mobius_strip clashes with Mobius. badParams.clear(); badParams.push_back("post_dcztransl_x0"); badParams.push_back("post_dcztransl_x1"); badParams.push_back("post_dcztransl_factor"); badParams.push_back("post_dcztransl_overwrite"); badParams.push_back("post_dcztransl_clamp"); m_BadVariationNames.push_back(make_pair(make_pair(string("post_dcztransl"), string("post_dc_ztransl")), badParams)); badParams.clear(); m_BadVariationNames.push_back(make_pair(make_pair(string("pre_blur"), string("pre_gaussian_blur")), badParams)); m_BadVariationNames.push_back(make_pair(make_pair(string("pre_spin_z"), string("pre_rotate_z")), badParams)); m_BadVariationNames.push_back(make_pair(make_pair(string("post_spin_z"), string("post_rotate_z")), badParams)); m_Init = true; } } /// /// Parse the specified buffer and place the results in the vector of embers passed in. /// /// The buffer to parse /// Full path and filename, optionally empty /// The newly constructed embers based on what was parsed /// True if there were no errors, else false. bool Parse(unsigned char* buf, const char* filename, vector>& embers) { char* bn; const char* xmlPtr; const char* loc = __FUNCTION__; size_t emberSize; size_t bufSize; xmlDocPtr doc;//Parsed XML document tree. xmlNodePtr rootnode; Locale locale;//Sets and restores on exit. //Timing t; m_ErrorReport.clear(); //Parse XML string into internal document. xmlPtr = (const char*)(&buf[0]); bufSize = strlen(xmlPtr); embers.reserve(bufSize / 2500);//The Xml text for an ember is around 2500 bytes, but can be much more. Pre-allocate to aovid unnecessary resizing. doc = xmlReadMemory(xmlPtr, (int)bufSize, filename, nullptr, XML_PARSE_NONET);//Forbid network access during read. //t.Toc("xmlReadMemory"); if (doc == nullptr) { m_ErrorReport.push_back(string(loc) + " : Error parsing xml file " + string(filename)); return false; } //What is the root node of the document? rootnode = xmlDocGetRootElement(doc); //Scan for nodes, starting with this node. //t.Tic(); bn = basename((char*)filename); ScanForEmberNodes(rootnode, bn, embers); xmlFreeDoc(doc); emberSize = embers.size(); //t.Toc("ScanForEmberNodes"); //Check to see if the first control point or the second-to-last //control point has interpolation="smooth". This is invalid //and should be reset to linear (with a warning). if (emberSize > 0) { if (embers[0].m_Interp == EMBER_INTERP_SMOOTH) { cout << "Warning: smooth interpolation cannot be used for first segment.\n switching to linear.\n" << endl; embers[0].m_Interp = EMBER_INTERP_LINEAR; } if (emberSize >= 2 && embers[emberSize - 2].m_Interp == EMBER_INTERP_SMOOTH) { cout << "Warning: smooth interpolation cannot be used for last segment.\n switching to linear.\n" << endl; embers[emberSize - 2].m_Interp = EMBER_INTERP_LINEAR; } } //Finally, ensure that consecutive 'rotate' parameters never exceed //a difference of more than 180 degrees (+/-) for interpolation. //An adjustment of +/- 360 degrees is made until this is true. if (emberSize > 1) { for (unsigned int i = 1; i < emberSize; i++) { //Only do this adjustment if not in compat mode.. if (embers[i - 1].m_AffineInterp != INTERP_COMPAT && embers[i - 1].m_AffineInterp != INTERP_OLDER) { while (embers[i].m_Rotate < embers[i - 1].m_Rotate - 180) embers[i].m_Rotate += 360; while (embers[i].m_Rotate > embers[i - 1].m_Rotate + 180) embers[i].m_Rotate -= 360; } } } return true; } /// /// Parse the specified file and place the results in the vector of embers passed in. /// This will strip out ampersands because the Xml parser can't handle them. /// /// Full path and filename /// The newly constructed embers based on what was parsed /// True if there were no errors, else false. bool Parse(const char* filename, vector>& embers) { const char* loc = __FUNCTION__; string buf; //Ensure palette list is setup first. if (!m_PaletteList.Init()) { m_ErrorReport.push_back(string(loc) + " : Palette list must be initialized before parsing embers."); return false; } if (ReadFile(filename, buf)) { std::replace(buf.begin(), buf.end(), '&', '+'); return Parse((unsigned char*)buf.data(), filename, embers); } else return false; } /// /// Convert the string to a floating point value and return a bool indicating success. /// See error report for errors. /// /// The string to convert /// The converted value /// True if success, else false. bool Atof(const char* str, T& val) { bool b = true; char* endp; const char* loc = __FUNCTION__; //Reset errno. errno = 0;//Note that this is not thread-safe. //Convert the string using strtod(). val = (T)strtod(str, &endp); //Check errno & return string. if (endp != str + strlen(str)) { m_ErrorReport.push_back(string(loc) + " : Error converting " + string(str) + ", extra chars"); b = false; } if (errno) { m_ErrorReport.push_back(string(loc) + " : Error converting " + string(str)); b = false; } return b; } /// /// Thin wrapper around Atoi(). /// See error report for errors. /// /// The string to convert /// The converted unsigned integer value /// True if success, else false. bool Atoi(const char* str, unsigned int& val) { return Atoi(str, (int&)val); } /// /// Convert the string to an unsigned integer value and return a bool indicating success. /// See error report for errors. /// /// The string to convert /// The converted unsigned integer value /// True if success, else false. bool Atoi(const char* str, int& val) { bool b = true; char* endp; const char* loc = __FUNCTION__; //Reset errno. errno = 0;//Note that this is not thread-safe. //Convert the string using strtod(). val = strtol(str, &endp, 10); //Check errno & return string. if (endp != str + strlen(str)) { m_ErrorReport.push_back(string(loc) + " : Error converting " + string(str) + ", extra chars"); b = false; } if (errno) { m_ErrorReport.push_back(string(loc) + " : Error converting " + string(str)); b = false; } return b; } /// /// Convert an integer to a string. /// Just a wrapper around _itoa_s() which wraps the result in a std::string. /// /// The integer to convert /// The radix of the integer. Default: 10. /// The converted string 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); } /// /// Convert an unsigned 64-bit integer to a string. /// Just a wrapper around _ui64toa_s() which wraps the result in a std::string. /// /// The unsigned 64-bit integer to convert /// The radix of the integer. Default: 10. /// The converted string 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 m_FlattenNames; private: /// /// Scan the file for ember nodes, and parse them out into the vector of embers. /// /// The current node to parse /// The full path and filename /// The newly constructed embers based on what was parsed void ScanForEmberNodes(xmlNode* curNode, char* parentFile, vector>& embers) { bool parseEmberSuccess; xmlNodePtr thisNode = nullptr; const char* loc = __FUNCTION__; string parentFileString = string(parentFile); //Original memset to 0, but the constructors should handle that. //Loop over this level of elements. for (thisNode = curNode; thisNode; thisNode = thisNode->next) { //Check to see if this element is a element. if (thisNode->type == XML_ELEMENT_NODE && !Compare(thisNode->name, "flame")) { Ember currentEmber;//Place this inside here so its constructor is called each time. parseEmberSuccess = ParseEmberElement(thisNode, currentEmber); if (!parseEmberSuccess) { //Original leaked memory here, ours doesn't. m_ErrorReport.push_back(string(loc) + " : Error parsing ember element"); return; } if (currentEmber.PaletteIndex() != -1) { if (!m_PaletteList.GetHueAdjustedPalette(currentEmber.PaletteIndex(), currentEmber.m_Hue, currentEmber.m_Palette)) { m_ErrorReport.push_back(string(loc) + " : Error assigning palette with index " + Itos(currentEmber.PaletteIndex())); } } //if (!Interpolater::InterpMissingColors(currentEmber.m_Palette.m_Entries)) // m_ErrorReport.push_back(string(loc) + " : Error interpolating missing palette colors"); currentEmber.CacheXforms(); currentEmber.m_Index = embers.size(); currentEmber.m_ParentFilename = parentFileString; embers.push_back(currentEmber); } else { //Check all of the children of this element. ScanForEmberNodes(thisNode->children, parentFile, embers); } } } /// /// Parse an ember element. /// /// The current node to parse /// The newly constructed ember based on what was parsed /// True if there were no errors, else false. bool ParseEmberElement(xmlNode* emberNode, Ember& currentEmber) { bool ret = true; unsigned int newLinear = 0; char* attStr; const char* loc = __FUNCTION__; int soloXform = -1; unsigned int i, count, index = 0; double vals[10]; xmlAttrPtr att, curAtt; xmlNodePtr editNode, childNode, motionNode; currentEmber.m_Palette.Clear();//Wipe out the current palette. att = emberNode->properties;//The top level element is a ember element, read the attributes of it and store them. if (att == nullptr) { m_ErrorReport.push_back(string(loc) + " : element has no attributes"); return false; } for (curAtt = att; curAtt; curAtt = curAtt->next) { attStr = (char*)xmlGetProp(emberNode, curAtt->name); //First parse out simple float reads. if (ParseAndAssignFloat(curAtt->name, attStr, "time", currentEmber.m_Time, ret)) { } else if (ParseAndAssignFloat(curAtt->name, attStr, "scale", currentEmber.m_PixelsPerUnit, ret)) { currentEmber.m_OrigPixPerUnit = currentEmber.m_PixelsPerUnit; } else if (ParseAndAssignFloat(curAtt->name, attStr, "rotate", currentEmber.m_Rotate, ret)) { } else if (ParseAndAssignFloat(curAtt->name, attStr, "zoom", currentEmber.m_Zoom, ret)) { } else if (ParseAndAssignFloat(curAtt->name, attStr, "filter", currentEmber.m_SpatialFilterRadius, ret)) { } else if (ParseAndAssignFloat(curAtt->name, attStr, "temporal_filter_width", currentEmber.m_TemporalFilterWidth, ret)) { } else if (ParseAndAssignFloat(curAtt->name, attStr, "temporal_filter_exp", currentEmber.m_TemporalFilterExp, ret)) { } else if (ParseAndAssignFloat(curAtt->name, attStr, "quality", currentEmber.m_Quality, ret)) { } else if (ParseAndAssignFloat(curAtt->name, attStr, "brightness", currentEmber.m_Brightness, ret)) { } else if (ParseAndAssignFloat(curAtt->name, attStr, "gamma", currentEmber.m_Gamma, ret)) { } else if (ParseAndAssignFloat(curAtt->name, attStr, "highlight_power", currentEmber.m_HighlightPower, ret)) { } else if (ParseAndAssignFloat(curAtt->name, attStr, "vibrancy", currentEmber.m_Vibrancy, ret)) { } else if (ParseAndAssignFloat(curAtt->name, attStr, "estimator_radius", currentEmber.m_MaxRadDE, ret)) { } else if (ParseAndAssignFloat(curAtt->name, attStr, "estimator_minimum", currentEmber.m_MinRadDE, ret)) { } else if (ParseAndAssignFloat(curAtt->name, attStr, "estimator_curve", currentEmber.m_CurveDE, ret)) { } else if (ParseAndAssignFloat(curAtt->name, attStr, "gamma_threshold", currentEmber.m_GammaThresh, ret)) { } else if (ParseAndAssignFloat(curAtt->name, attStr, "cam_zpos", currentEmber.m_CamZPos, ret)) { } else if (ParseAndAssignFloat(curAtt->name, attStr, "cam_persp", currentEmber.m_CamPerspective, ret)) { } else if (ParseAndAssignFloat(curAtt->name, attStr, "cam_perspective", currentEmber.m_CamPerspective, ret)) { }//Apo bug. else if (ParseAndAssignFloat(curAtt->name, attStr, "cam_yaw", currentEmber.m_CamYaw, ret)) { } else if (ParseAndAssignFloat(curAtt->name, attStr, "cam_pitch", currentEmber.m_CamPitch, ret)) { } else if (ParseAndAssignFloat(curAtt->name, attStr, "cam_dof", currentEmber.m_CamDepthBlur, ret)) { } //Parse simple int reads. else if (ParseAndAssignInt(curAtt->name, attStr, "palette", currentEmber.m_Palette.m_Index, ret)) { } else if (ParseAndAssignInt(curAtt->name, attStr, "oversample", currentEmber.m_Supersample , ret)) { } else if (ParseAndAssignInt(curAtt->name, attStr, "supersample", currentEmber.m_Supersample , ret)) { } else if (ParseAndAssignInt(curAtt->name, attStr, "temporal_samples", currentEmber.m_TemporalSamples, ret)) { } else if (ParseAndAssignInt(curAtt->name, attStr, "soloxform", soloXform , ret)) { } else if (ParseAndAssignInt(curAtt->name, attStr, "new_linear", newLinear , ret)) { } //Parse more complicated reads that have multiple possible values. else if (!Compare(curAtt->name, "interpolation")) { if (!_stricmp("linear", attStr)) currentEmber.m_Interp = EMBER_INTERP_LINEAR; else if (!_stricmp("smooth", attStr)) currentEmber.m_Interp = EMBER_INTERP_SMOOTH; else m_ErrorReport.push_back(string(loc) + " : Unrecognized interpolation type " + string(attStr)); } else if (!Compare(curAtt->name, "palette_interpolation")) { if (!_stricmp("hsv", attStr)) currentEmber.m_PaletteInterp = INTERP_HSV; else if (!_stricmp("sweep", attStr)) currentEmber.m_PaletteInterp = INTERP_SWEEP; else m_ErrorReport.push_back(string(loc) + " : Unrecognized palette interpolation type " + string(attStr)); } else if (!Compare(curAtt->name, "interpolation_space") || !Compare(curAtt->name, "interpolation_type")) { if (!_stricmp("linear", attStr)) currentEmber.m_AffineInterp = INTERP_LINEAR; else if (!_stricmp("log", attStr)) currentEmber.m_AffineInterp = INTERP_LOG; else if (!_stricmp("old", attStr)) currentEmber.m_AffineInterp = INTERP_COMPAT; else if (!_stricmp("older", attStr)) currentEmber.m_AffineInterp = INTERP_OLDER; else m_ErrorReport.push_back(string(loc) + " : Unrecognized interpolation type " + string(attStr)); } else if (!Compare(curAtt->name, "name")) { currentEmber.m_Name = string(attStr); std::replace(currentEmber.m_Name.begin(), currentEmber.m_Name.end(), ' ', '_'); } else if (!Compare(curAtt->name, "size")) { if (sscanf_s(attStr, "%lu %lu", ¤tEmber.m_FinalRasW, ¤tEmber.m_FinalRasH) != 2) { m_ErrorReport.push_back(string(loc) + " : Invalid size attribute " + string(attStr)); xmlFree(attStr); //These return statements are bad. One because they are inconsistent with others that just assign defaults. //Two, because assigning easily guessable defaults is easy and less drastic. return false; } 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) { m_ErrorReport.push_back(string(loc) + " : Invalid center attribute " + string(attStr)); xmlFree(attStr); return false; } currentEmber.m_CenterX = T(vals[0]); currentEmber.m_CenterY = T(vals[1]); } else if (!Compare(curAtt->name, "filter_shape")) { currentEmber.m_SpatialFilterType = SpatialFilterCreator::FromString(string(attStr)); } else if (!Compare(curAtt->name, "temporal_filter_type")) { currentEmber.m_TemporalFilterType = TemporalFilterCreator::FromString(string(attStr)); } else if (!Compare(curAtt->name, "palette_mode")) { if (!_stricmp("step", attStr)) currentEmber.m_PaletteMode = PALETTE_STEP; else if (!_stricmp("linear", attStr)) currentEmber.m_PaletteMode = PALETTE_LINEAR; else { currentEmber.m_PaletteMode = PALETTE_STEP; m_ErrorReport.push_back(string(loc) + " : Unrecognized palette mode " + string(attStr) + ", using step"); } } else if (!Compare(curAtt->name, "background")) { if (sscanf_s(attStr, "%lf %lf %lf", &vals[0], &vals[1], &vals[2]) != 3) { m_ErrorReport.push_back(string(loc) + " : Invalid background attribute " + string(attStr)); xmlFree(attStr); return false; } currentEmber.m_Background[0] = T(vals[0]);//[0..1] currentEmber.m_Background[1] = T(vals[1]); currentEmber.m_Background[2] = T(vals[2]); } else if (!Compare(curAtt->name, "hue")) { Atof(attStr, currentEmber.m_Hue); currentEmber.m_Hue = fmod(currentEmber.m_Hue, T(0.5));//Orig did fmod 1, but want it in the range -0.5 - 0.5. } xmlFree(attStr); } //Finished with ember attributes. Now look at the children of the ember element. for (childNode = emberNode->children; childNode; childNode = childNode->next) { if (!Compare(childNode->name, "color")) { index = -1; double r = 0, g = 0, b = 0, a = 0; //Loop through the attributes of the color element. att = childNode->properties; if (att == nullptr) { m_ErrorReport.push_back(string(loc) + " : No attributes for color element"); continue; } for (curAtt = att; curAtt; curAtt = curAtt->next) { attStr = (char*)xmlGetProp(childNode, curAtt->name); a = 255; //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")) { Atoi(attStr, index); } else if(!Compare(curAtt->name, "rgb")) { if (sscanf_s(attStr, "%lf %lf %lf", &r, &g, &b) != 3) m_ErrorReport.push_back(string(loc) + " : Invalid rgb attribute " + string(attStr)); } else if(!Compare(curAtt->name, "rgba")) { if (sscanf_s(attStr, "%lf %lf %lf %lf", &r, &g, &b, &a) != 4) m_ErrorReport.push_back(string(loc) + " : Invalid rgba attribute " + string(attStr)); } else if(!Compare(curAtt->name, "a")) { if (sscanf_s(attStr, "%lf", &a) != 1) m_ErrorReport.push_back(string(loc) + " : Invalid a attribute " + string(attStr)); } else { m_ErrorReport.push_back(string(loc) + " : Unknown color attribute " + string((const char*)curAtt->name)); } xmlFree(attStr); } //Palette colors are [0..255], convert to [0..1]. if (index >= 0 && index <= 255) { T alphaPercent = T(a) / T(255);//Aplha percentage in the range of 0 to 1. //Premultiply the palette. currentEmber.m_Palette.m_Entries[index].r = alphaPercent * (T(r) / T(255)); currentEmber.m_Palette.m_Entries[index].g = alphaPercent * (T(g) / T(255)); currentEmber.m_Palette.m_Entries[index].b = alphaPercent * (T(b) / T(255)); currentEmber.m_Palette.m_Entries[index].a = T(a) / 255;//Will be one for RGB, and other than one if RGBA with A != 255. } else { stringstream ss; ss << "ParseEmberElement() : Color element with bad/missing index attribute " << index; m_ErrorReport.push_back(ss.str()); } } else if (!Compare(childNode->name, "colors")) { //Loop through the attributes of the color element. att = childNode->properties; if (att == nullptr) { m_ErrorReport.push_back(string(loc) + " : No attributes for colors element"); continue; } for (curAtt = att; curAtt; curAtt = curAtt->next) { attStr = (char*)xmlGetProp(childNode, curAtt->name); if (!Compare(curAtt->name, "count")) { Atoi(attStr, count); } else if (!Compare(curAtt->name, "data")) { if (!ParseHexColors(attStr, currentEmber, count, -4)) { m_ErrorReport.push_back(string(loc) + " : Error parsing hexformatted colors, some may be set to zero"); } } else { m_ErrorReport.push_back(string(loc) + " : Unknown color attribute " + string((const char*)curAtt->name)); } xmlFree(attStr); } } else if (!Compare(childNode->name, "palette")) { //This could be either the old form of palette or the new form. //Make sure BOTH are not specified, otherwise either are ok. int numColors = 0; int numBytes = 0; bool oldFormat = false; bool newFormat = false; int index0, index1; T hue0, hue1; T blend = 0.5; index0 = index1 = -1; hue0 = hue1 = 0.0; //Loop through the attributes of the palette element. att = childNode->properties; if (att == nullptr) { m_ErrorReport.push_back(string(loc) + " : No attributes for palette element"); continue; } for (curAtt = att; curAtt; curAtt = curAtt->next) { attStr = (char*)xmlGetProp(childNode, curAtt->name); if (!Compare(curAtt->name, "index0")) { oldFormat = true; Atoi(attStr, index0); } else if (!Compare(curAtt->name, "index1")) { oldFormat = true; Atoi(attStr, index1); } else if (!Compare(curAtt->name, "hue0")) { oldFormat = true; Atof(attStr, hue0); } else if (!Compare(curAtt->name, "hue1")) { oldFormat = true; Atof(attStr, hue1); } else if (!Compare(curAtt->name, "blend")) { oldFormat = true; Atof(attStr, blend); } else if (!Compare(curAtt->name, "count")) { newFormat = true; Atoi(attStr, numColors); } else if (!Compare(curAtt->name, "format")) { newFormat = true; if (!_stricmp(attStr, "RGB")) numBytes = 3; else if (!_stricmp(attStr, "RGBA")) numBytes = 4; else { m_ErrorReport.push_back(string(loc) + " : Unrecognized palette format string " + string(attStr) + ", defaulting to RGB"); numBytes = 3; } } else { m_ErrorReport.push_back(string(loc) + " : Unknown palette attribute " + string((const char*)curAtt->name)); } xmlFree(attStr); } //Old or new format? if (newFormat && oldFormat) { oldFormat = false; m_ErrorReport.push_back(string(loc) + " : Mixing of old and new palette tag syntax not allowed, defaulting to new"); } if (oldFormat) { InterpolateCmap(currentEmber.m_Palette, blend, index0, hue0, index1, hue1); } else { //Read formatted string from contents of tag. char* palStr = (char*)xmlNodeGetContent(childNode); if (!ParseHexColors(palStr, currentEmber, numColors, numBytes)) { m_ErrorReport.push_back(string(loc) + " : Problem reading hexadecimal color data in palette"); } xmlFree(palStr); } } else if (!Compare(childNode->name, "symmetry")) { int symKind = INT_MAX; //Loop through the attributes of the palette element. att = childNode->properties; if (att == nullptr) { m_ErrorReport.push_back(string(loc) + " : No attributes for palette element"); continue; } for (curAtt = att; curAtt; curAtt = curAtt->next) { attStr = (char*)xmlGetProp(childNode, curAtt->name); if (!Compare(curAtt->name, "kind")) { Atoi(attStr, symKind); } else { m_ErrorReport.push_back(string(loc) + " : Unknown symmetry attribute " + string(attStr)); continue; } xmlFree(attStr); } //if (symKind != INT_MAX)//What to do about this? Should sym not be saved? Or perhaps better intelligence when adding?//TODO//BUG. //{ // currentEmber.AddSymmetry(symKind, *(GlobalRand.get()));//Determine what to do here. //} } else if (!Compare(childNode->name, "xform") || !Compare(childNode->name, "finalxform")) { Xform* theXform = nullptr; if (!Compare(childNode->name, "finalxform")) { Xform finalXform; if (!ParseXform(childNode, finalXform, false)) { m_ErrorReport.push_back(string(loc) + " : Error parsing final xform"); } else { if (finalXform.m_Weight != 0) { finalXform.m_Weight = 0; m_ErrorReport.push_back(string(loc) + " : Final xforms should not have weight specified, setting to zero"); } currentEmber.SetFinalXform(finalXform); theXform = currentEmber.NonConstFinalXform(); } } else { Xform xform; if (!ParseXform(childNode, xform, false)) { m_ErrorReport.push_back(string(loc) + " : Error parsing xform"); } else { currentEmber.AddXform(xform); theXform = currentEmber.GetXform(currentEmber.XformCount() - 1); } } if (theXform) { //Check for non-zero motion params. if (theXform->m_MotionFreq != 0)//Original checked for motion func being non-zero, but it was set to MOTION_SIN (1) in Xform::Init(), so don't check for 0 here. { m_ErrorReport.push_back(string(loc) + " : Motion parameters should not be specified in regular, non-motion xforms"); } //Motion Language: Check the xform element for children - should be named 'motion'. for (motionNode = childNode->children; motionNode; motionNode = motionNode->next) { if (!Compare(motionNode->name, "motion")) { Xform xform; if (!ParseXform(motionNode, xform, true)) m_ErrorReport.push_back(string(loc) + " : Error parsing motion xform"); else theXform->m_Motion.push_back(xform); } } } } else if (!Compare(childNode->name, "edit")) { //Create a new XML document with this edit node as the root node. currentEmber.m_Edits = xmlNewDoc((const xmlChar*)"1.0"); editNode = xmlCopyNode(childNode, 1); xmlDocSetRootElement(currentEmber.m_Edits, editNode); } } //if (!newLinear) // currentEmber.Flatten(m_FlattenNames); for (i = 0; i < currentEmber.XformCount(); i++) if (soloXform >= 0 && i != soloXform) currentEmber.GetXform(i)->m_Opacity = 0;//Will calc the cached adjusted viz value later. return m_ErrorReport.empty(); } /// /// Parse an xform element. /// /// The current node to parse /// The newly constructed xform based on what was parsed /// True if this xform is a motion within a parent xform, else false /// True if there were no errors, else false. bool ParseXform(xmlNode* childNode, Xform& xform, bool motion) { bool success = true; char* attStr; const char* loc = __FUNCTION__; unsigned int 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; if (attPtr == nullptr) { m_ErrorReport.push_back(string(loc) + " : Error: No attributes for element"); return false; } for (curAtt = attPtr; curAtt; curAtt = curAtt->next) { attStr = (char*)xmlGetProp(childNode, curAtt->name); //First parse out simple float reads. if (ParseAndAssignFloat(curAtt->name, attStr, "weight", xform.m_Weight, success)) { } else if (ParseAndAssignFloat(curAtt->name, attStr, "color_speed", xform.m_ColorSpeed, success)) { } else if (ParseAndAssignFloat(curAtt->name, attStr, "symmetry", xform.m_ColorSpeed, success)) { xform.m_ColorSpeed = (1 - xform.m_ColorSpeed) / 2; }//Legacy support. else if (ParseAndAssignFloat(curAtt->name, attStr, "animate", xform.m_Animate, success)) { } else if (ParseAndAssignFloat(curAtt->name, attStr, "opacity", xform.m_Opacity, success)) { } else if (ParseAndAssignFloat(curAtt->name, attStr, "var_color", xform.m_DirectColor, success)) { } //Parse simple int reads. else if (ParseAndAssignInt(curAtt->name, attStr, "motion_frequency", xform.m_MotionFreq, success)) { } //Parse more complicated reads that have multiple possible values. else if (!Compare(curAtt->name, "name")) { xform.m_Name = string(attStr); std::replace(xform.m_Name.begin(), xform.m_Name.end(), ' ', '_'); } else if (!Compare(curAtt->name, "symmetry")) { //Deprecated, set both color_speed and animate to this value. //Huh? Either set it or not? Atof(attStr, temp); xform.m_ColorSpeed = (1 - temp) / 2; xform.m_Animate = T(temp > 0 ? 0 : 1); } else if (!Compare(curAtt->name, "motion_function")) { if (!_stricmp("sin", attStr)) xform.m_MotionFunc = MOTION_SIN; else if (!_stricmp("triangle", attStr)) xform.m_MotionFunc = MOTION_TRIANGLE; else if (!_stricmp("hill", attStr)) xform.m_MotionFunc = MOTION_HILL; else { xform.m_MotionFunc = MOTION_SIN; m_ErrorReport.push_back(string(loc) + " : Unknown motion function " + string(attStr) + ", using sin"); } } 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); m_ErrorReport.push_back(string(loc) + " : Malformed xform color attribute " + string(attStr) + ", using 0.5, 0.5"); } } else if (!Compare(curAtt->name, "chaos")) { stringstream ss(attStr); j = 0; while (ss >> temp) { xform.SetXaos(j, temp); j++; } } else if (!Compare(curAtt->name, "plotmode")) { if (motion == 1) { m_ErrorReport.push_back(string(loc) + " : Motion element cannot have a plotmode attribute"); } else if (!_stricmp("off", attStr)) xform.m_Opacity = 0; } 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; m_ErrorReport.push_back(string(loc) + " : Bad coeffs attribute " + string(attStr)); } xform.m_Affine.A(T(a)); xform.m_Affine.B(T(b)); xform.m_Affine.C(T(c)); xform.m_Affine.D(T(d)); xform.m_Affine.E(T(e)); xform.m_Affine.F(T(f)); } 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; m_ErrorReport.push_back(string(loc) + " : Bad post coeffs attribute " + string(attStr)); } xform.m_Post.A(T(a)); xform.m_Post.B(T(b)); xform.m_Post.C(T(c)); xform.m_Post.D(T(d)); xform.m_Post.E(T(e)); xform.m_Post.F(T(f)); } else { string s = GetCorrectedVariationName(m_BadVariationNames, curAtt); if (Variation* var = m_VariationList.GetVariation(s)) { Variation* varCopy = var->Copy(); Atof(attStr, varCopy->m_Weight); xform.AddVariation(varCopy); } //else //{ // m_ErrorReport.push_back("Unsupported variation: " + string((const char*)curAtt->name)); //} } xmlFree(attStr); } //Handle var1. for (curAtt = attPtr; curAtt; curAtt = curAtt->next) { bool var1 = false; if (!Compare(curAtt->name, "var1")) { attStr = (char*)xmlGetProp(childNode, curAtt->name); for (j = 0; j < xform.TotalVariationCount(); j++) xform.GetVariation(j)->m_Weight = 0; if (Atof(attStr, temp)) { unsigned int iTemp = (unsigned int)temp; if (iTemp < xform.TotalVariationCount()) { xform.GetVariation(iTemp)->m_Weight = 1; var1 = true; } } if (!var1) m_ErrorReport.push_back(string(loc) + " : Bad value for var1 " + string(attStr)); xmlFree(attStr); break; } } //Handle var. for (curAtt = attPtr; curAtt; curAtt = curAtt->next) { bool var = false; if (!Compare(curAtt->name, "var")) { attStr = (char*)xmlGetProp(childNode, curAtt->name); if (Atof(attStr, temp)) { for (j = 0; j < xform.TotalVariationCount(); j++) xform.GetVariation(j)->m_Weight = temp; var = true; } if (!var) m_ErrorReport.push_back(string(loc) + " : Bad value for var " + string(attStr)); xmlFree(attStr); break; } } //Now that all xforms have been parsed, go through and try to find params for the parametric variations. for (unsigned int i = 0; i < xform.TotalVariationCount(); i++) { if (ParametricVariation* parVar = dynamic_cast*>(xform.GetVariation(i))) { for (curAtt = attPtr; curAtt; curAtt = curAtt->next) { string s = GetCorrectedParamName(m_BadParamNames, (const char*)curAtt->name); const char* name = s.c_str(); if (parVar->ContainsParam(name)) { T val = 0; attStr = (char*)xmlGetProp(childNode, curAtt->name); if (Atof(attStr, val)) { parVar->SetParamVal(name, val); } else { m_ErrorReport.push_back(string(loc) + " : Failed to parse parametric variation parameter " + s + " - " + string(attStr)); } xmlFree(attStr); } } } } return true; } /// /// Some Apophysis plugins use an inconsistent naming scheme for the parametric variation variables. /// This function identifies and converts them to Ember's consistent naming convention. /// /// The vector of corrected names to search /// The current Xml node to check /// The corrected name if one was found, else the passed in name. static string GetCorrectedParamName(vector>& vec, const char* name) { for (size_t i = 0; i < vec.size(); i++) { if (!_stricmp(vec[i].first.c_str(), name)) return vec[i].second; } return name; } /// /// Some Apophysis plugins use an inconsistent naming scheme for variation names. /// This function identifies and converts them to Ember's consistent naming convention. /// It uses some additional intelligence to ensure the variation is the expected one, /// by examining the rest of the xform for the existence of parameter names. /// /// The vector of corrected names to search /// The current Xml node to check /// The corrected name if one was found, else the passed in name. static string GetCorrectedVariationName(vector, vector>>& vec, xmlAttrPtr att) { for (size_t i = 0; i < vec.size(); i++) { if (!_stricmp(vec[i].first.first.c_str(), (const char*)att->name))//Do case insensitive here. { if (!vec[i].second.empty()) { for (size_t j = 0; j < vec[i].second.size(); j++) { if (XmlContainsTag(att, vec[i].second[j].c_str())) return vec[i].first.second; } } else { return vec[i].first.second; } } } return string((const char*)att->name); } /// /// Determine if an Xml node contains a given tag. /// /// The Xml node to search /// The node name to search for /// True if name was found, else false. static bool XmlContainsTag(xmlAttrPtr att, const char* name) { xmlAttrPtr temp = att; do { if (!_stricmp(name, (const char*)temp->name)) return true; } while ((temp = temp->next)); return false; } /// /// Parse hexadecimal colors. /// This can read RGB and RGBA, however only RGB will be stored. /// /// The string of hex colors to parse /// The ember whose palette will be assigned the colors /// The number of colors present /// The number of channels in each color /// True if there were no errors, else false. bool ParseHexColors(char* colstr, Ember& ember, int numColors, int chan) { int colorIndex = 0; int colorCount = 0; unsigned int r, g, b, a; int ret; char tmps[2]; int skip = (int)abs(chan); bool ok = true; const char* loc = __FUNCTION__; //Strip whitespace prior to first color. while (isspace((int)colstr[colorIndex])) colorIndex++; do { //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)) { ok = false; r = g = b = 0; m_ErrorReport.push_back(string(loc) + " : Problem reading hexadecimal color data, assigning to 0"); break; } colorIndex += 2 * skip; while (isspace((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); 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 { m_ErrorReport.push_back(string(loc) + " : Extra data at end of hex color data " + string(&(colstr[colorIndex]))); ok = false; } return ok; } /// /// Interpolate the palette. /// Used with older formats, deprecated. /// /// The palette to interpolate /// The blend /// The first index /// The first hue /// The second index /// The second hue/param> void InterpolateCmap(Palette& palette, T blend, int index0, T hue0, int index1, T hue1) { int i, j; const char* loc = __FUNCTION__; Palette adjustedPal0, adjustedPal1; if (m_PaletteList.GetHueAdjustedPalette(index0, hue0, adjustedPal0) && m_PaletteList.GetHueAdjustedPalette(index1, hue1, adjustedPal1)) { v4T* hueAdjusted0 = adjustedPal0.m_Entries.data(); v4T* hueAdjusted1 = adjustedPal1.m_Entries.data(); for (i = 0; i < 256; i++) { T t[4], s[4]; Palette::RgbToHsv(glm::value_ptr(hueAdjusted0[i]), s); Palette::RgbToHsv(glm::value_ptr(hueAdjusted1[i]), t); s[3] = hueAdjusted0[i][3]; t[3] = hueAdjusted1[i][3]; for (j = 0; j < 4; j++) t[j] = ((1 - blend) * s[j]) + (blend * t[j]); Palette::HsvToRgb(t, glm::value_ptr(palette.m_Entries[i])); palette.m_Entries[i][3] = t[3]; } } else { m_ErrorReport.push_back(string(loc) + " : Unable to retrieve palettes"); } } /// /// Wrapper to parse a floating point Xml value and convert it to float. /// /// The xml tag to parse /// The name of the Xml attribute /// The name of the Xml tag /// The parsed value /// Bitwise ANDed with true if name matched str and the call to Atof() succeeded, else false. Used for keeping a running value between successive calls. /// True if the tag was matched, else false bool ParseAndAssignFloat(const xmlChar* name, const char* attStr, const char* str, T& val, bool& b) { bool ret = false; if (!Compare(name, str)) { b &= Atof(attStr, val); ret = true;//Means the strcmp() was right, but doesn't necessarily mean the conversion went ok. } return ret; } /// /// Wrapper to parse an int Xml string value and convert it to an int. /// /// The xml tag to parse /// The name of the Xml attribute /// The name of the Xml tag /// The parsed value /// Bitwise ANDed with true if name matched str and the call to Atoi() succeeded, else false. Used for keeping a running value between successive calls. /// True if the tag was matched, else false template bool ParseAndAssignInt(const xmlChar* name, const char* attStr, const char* str, intT& val, bool& b) { bool ret = false; T fval = 0; if (!Compare(name, str)) { b &= Atof(attStr, fval); val = (intT)fval; ret = true;//Means the strcmp() was right, but doesn't necessarily mean the conversion went ok. } return ret; } static bool m_Init; static vector> m_BadParamNames; static vector, vector>> m_BadVariationNames; VariationList m_VariationList;//The variation list used to make copies of variations to populate the embers with. PaletteList m_PaletteList; }; }