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