#pragma once #include "Utils.h" #include "PaletteList.h" #include "VariationList.h" #include "Ember.h" /// /// EmberToXml class. /// namespace EmberNs { /// /// Class for converting ember objects to Xml documents. /// Support for saving one or more to a single file. /// Template argument expected to be float or double. /// template class EMBER_API EmberToXml : public EmberReport { public: /// /// Empty constructor. /// EmberToXml() { } /// /// Save the ember to the specified file. /// /// Full path and filename /// The ember to save /// How deep the edit depth goes /// If true included edit tags, else don't. /// If true use integers instead of floating point numbers when embedding a non-hex formatted palette, else use floating point numbers. /// If true, embed a hexadecimal palette instead of Xml Color tags, else use Xml color tags. /// If true, append to the file if it already exists, else create a new file. /// Whether a new file is to be started /// Whether an existing file is to be ended /// True if successful, else false bool Save(string filename, Ember& ember, unsigned int printEditDepth, bool doEdits, bool intPalette, bool hexPalette, bool append = false, bool start = false, bool finish = false) { vector> vec; vec.push_back(ember); return Save(filename, vec, printEditDepth, doEdits, intPalette, hexPalette, append, start, finish); } /// /// Save a vector of embers to the specified file. /// /// Full path and filename /// The vector of embers to save /// How deep the edit depth goes /// If true included edit tags, else don't. /// If true use integers instead of floating point numbers when embedding a non-hex formatted palette, else use floating point numbers. /// If true, embed a hexadecimal palette instead of Xml Color tags, else use Xml color tags. /// If true, append to the file if it already exists, else create a new file. /// Whether a new file is to be started /// Whether an existing file is to be ended /// True if successful, else false bool Save(string filename, vector>& embers, unsigned int printEditDepth, bool doEdits, bool intPalette, bool hexPalette, bool append = false, bool start = false, bool finish = false) { bool b = false; 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()) { if ((append && start) || !append) { temp = "\n"; f.write(temp.c_str(), temp.size()); } for (size_t i = 0; i < embers.size(); i++) { string s = ToString(embers[i], "", printEditDepth, doEdits, intPalette, hexPalette); f.write(s.c_str(), s.size()); } if ((append && finish) || !append) { temp = "\n"; f.write(temp.c_str(), temp.size()); } f.close(); b = true; } else { cout << "Error: Writing flame " << filename << " failed." << endl; b = false; } } catch (...) { if (f.is_open()) f.close(); b = false; } return b; } /// /// Return the Xml string representation of an ember. /// /// The ember to create the Xml with /// If true, add extra attributes, else don't /// How deep the edit depth goes /// If true included edit tags, else don't. /// If true use integers instead of floating point numbers when embedding a non-hex formatted palette, else use floating point numbers. /// If true, embed a hexadecimal palette instead of Xml Color tags, else use Xml color tags. /// The Xml string representation of the passed in ember string ToString(Ember& ember, string extraAttributes, unsigned int printEditDepth, bool doEdits, bool intPalette, bool hexPalette = true) { unsigned int i, j; ostringstream os; vector*> variations; os << "::ToString(ember.m_SpatialFilterType)) << "\""; os << " temporal_filter_type=\"" << ToLower(TemporalFilterCreator::ToString(ember.m_TemporalFilterType)) << "\""; if (ember.m_TemporalFilterType == EXP_TEMPORAL_FILTER) os << " temporal_filter_exp=\"" << ember.m_TemporalFilterExp << "\""; os << " temporal_filter_width=\"" << ember.m_TemporalFilterWidth << "\""; os << " quality=\"" << ember.m_Quality << "\""; os << " passes=\"" << ember.m_Passes << "\""; os << " temporal_samples=\"" << ember.m_TemporalSamples << "\""; 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 << " hue=\"" << ember.m_Hue << "\"";//Oddly enough, flam3 never wrote this value out.//ORIG 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 == PALETTE_STEP) os << " palette_mode=\"step\""; else if (ember.m_PaletteMode == PALETTE_LINEAR) os << " palette_mode=\"linear\""; if (ember.m_Interp == EMBER_INTERP_SMOOTH) os << " interpolation=\"smooth\""; if (ember.m_AffineInterp == INTERP_LINEAR) os << " interpolation_type=\"linear\""; else if (ember.m_AffineInterp == INTERP_LOG) os << " interpolation_type=\"log\""; else if (ember.m_AffineInterp == INTERP_COMPAT) os << " interpolation_type=\"old\""; else if (ember.m_AffineInterp == INTERP_OLDER) os << " interpolation_type=\"older\""; if (ember.m_PaletteInterp == INTERP_SWEEP) os << " palette_interpolation=\"sweep\""; if (!extraAttributes.empty()) os << " " << extraAttributes; os << " plugins=\""; ember.GetPresentVariations(variations, false); if (!variations.empty()) ForEach(variations, [&] (Variation* var) { os << var->Name() << (var != variations.back() ? " " : "\""); }); else os << "\""; os << " new_linear=\"1\""; os << ">\n"; //This is a grey area, what to do about symmetry to avoid duplicating the symmetry xforms when reading back?//TODO//BUG. //if (ember.m_Symmetry) // os << " \n"; 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. if (hexPalette) { os << " \n"; for (i = 0; i < 32; i++) { os << " "; for (j = 0; j < 8; j++) { int idx = 8 * i + j; os << hex << setw(2) << setfill('0') << (int)Rint(ember.m_Palette[idx][0] * 255); os << hex << setw(2) << setfill('0') << (int)Rint(ember.m_Palette[idx][1] * 255); os << hex << setw(2) << setfill('0') << (int)Rint(ember.m_Palette[idx][2] * 255); } os << endl; } os << " \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)) { if (intPalette) os << ""; else os << ""; } else { if (intPalette) os << " "; else os << " "; } os << "\n"; } } if (doEdits && ember.m_Edits != NULL) os << ToString(xmlDocGetRootElement(ember.m_Edits), 1, true, printEditDepth); os << "\n"; return os.str(); } /// /// 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. /// /// The first parent, optionally NULL. /// The second parent, optionally NULL. /// The action that was taken to create the new ember /// The nickname of the author /// The Url of the author /// The id of the author /// The comment to include /// The sheep generation used if > 0. Default: 0. /// The sheep id used if > 0. Default: 0. /// xmlDocPtr CreateNewEditdoc(Ember* parent0, Ember* parent1, string action, string nick, string url, string id, string comment, int sheepGen = 0, int sheepId = 0) { char timeString[128]; char buffer[128]; char commentString[128]; tm localt; time_t myTime; xmlDocPtr commentDoc = NULL; xmlDocPtr doc = xmlNewDoc(XC "1.0"); xmlNodePtr rootNode = NULL, node = NULL, nodeCopy = NULL; xmlNodePtr rootComment = NULL; //Create the root node, called "edit". rootNode = xmlNewNode(NULL, XC "edit"); xmlDocSetRootElement(doc, rootNode); //Add the edit attributes. //Date. myTime = time(NULL); localtime_s(&localt, &myTime); strftime(timeString, 128, "%a %b %d %H:%M:%S %z %Y", &localt);//XXX use standard time format including timezone. 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, NULL, XC "sheep", NULL); //Create the sheep attributes. sprintf_s(buffer, 128, "%d", sheepGen); xmlNewProp(node, XC "generation", XC buffer); sprintf_s(buffer, 128, "%d", sheepId); xmlNewProp(node, XC "id", XC buffer); } //Check for the parents. //If parent 0 not specified, this is a randomly generated genome. if (parent0) { if (parent0->m_Edits) { //Copy the node from the parent. node = xmlDocGetRootElement(parent0->m_Edits); nodeCopy = xmlCopyNode(node, 1); AddFilenameWithoutAmpersand(nodeCopy, parent0->m_ParentFilename); sprintf_s(buffer, 128, "%d", parent0->m_Index); xmlNewProp(nodeCopy, XC "index", XC buffer); xmlAddChild(rootNode, nodeCopy); } else { //Insert a (parent has no edit) message. nodeCopy = xmlNewChild(rootNode, NULL, XC "edit", NULL); AddFilenameWithoutAmpersand(nodeCopy, parent0->m_ParentFilename); sprintf_s(buffer, 128, "%d", parent0->m_Index); xmlNewProp(nodeCopy, XC "index", XC buffer); } } if (parent1) { if (parent1->m_Edits) { //Copy the node from the parent. node = xmlDocGetRootElement(parent1->m_Edits); nodeCopy = xmlCopyNode(node, 1); AddFilenameWithoutAmpersand(nodeCopy, parent1->m_ParentFilename); sprintf_s(buffer, 128, "%d", parent1->m_Index); xmlNewProp(nodeCopy, XC "index", XC buffer); xmlAddChild(rootNode, nodeCopy); } else { //Insert a (parent has no edit) message. nodeCopy = xmlNewChild(rootNode, NULL, XC "edit",NULL); AddFilenameWithoutAmpersand(nodeCopy, parent1->m_ParentFilename); sprintf_s(buffer, 128, "%d", parent1->m_Index); xmlNewProp(nodeCopy, XC "index", XC buffer); } } //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 != "") { sprintf_s(commentString, 128, "%s", comment.c_str()); commentDoc = xmlReadMemory(commentString, (int)strlen(commentString), "comment.env", NULL, XML_PARSE_NONET); //Check for errors. if (commentDoc != NULL) { //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." << endl; } } //Return the xml doc. return doc; } private: /// /// Return the Xml string representation of an xform. /// /// The xform to create the Xml with /// The number of non-final xforms in the ember to which this xform belongs. Used for xaos. /// True if the xform is the final xform in the ember, else false. /// If true, include motion elements in the Xml string, else omit. /// The Xml string representation of the passed in xform string ToString(Xform& xform, unsigned int xformCount, bool isFinal, bool doMotion) { unsigned int i, j; ostringstream os; if (doMotion) { os << " * var = xform.GetVariation(i); ParametricVariation* parVar = dynamic_cast*>(var); if (var->m_Weight != 0) { os << var->Name() << "=\"" << var->m_Weight << "\" "; if (parVar) { ParamWithName* 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())) { 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())) { 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()) { os << " chaos=\""; for (i = 0; i < xformCount; i++) os << xform.Xaos(i) << " "; os << "\""; } if (!doMotion) 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 << " \n"; else os << " \n"; } else os << "/>\n"; return os.str(); } /// /// Return an edit node Xml string. /// /// The edit node to get the string for /// How many tabs to use /// If true, include newlines and tabs, else don't. /// How deep the edit depth goes /// The edit node Xml string string ToString(xmlNodePtr editNode, unsigned int tabs, bool formatting, unsigned int printEditDepth) { bool indentPrinted = false; char* tabString = " ", *attStr; unsigned int ti, editOrSheep = 0; xmlAttrPtr attPtr = NULL, curAtt = NULL; xmlNodePtr childPtr = NULL, curChild = NULL; 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, "edit")) { editOrSheep = 1; tabs++; } else if (!Compare(editNode->name, "sheep")) editOrSheep = 2; else editOrSheep = 0; //Print the attributes. attPtr = editNode->properties; for (curAtt = attPtr; curAtt; curAtt = curAtt->next) { attStr = (char*)xmlGetProp(editNode, curAtt->name); os << " " << curAtt->name << "=\"" << attStr << "\""; xmlFree(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, "edit") || !Compare(curChild->name, "sheep"))) { 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 << "name << ">";//Close the tag. if (formatting) os << "\n"; } } else if (editNode->type == XML_TEXT_NODE) { string s((char*)xmlNodeGetContent(editNode)); os << Trim(s); } return os.str(); } void AddFilenameWithoutAmpersand(xmlNodePtr node, string& filename) { if (filename.find_first_of('&') != std::string::npos) { string filenameWithoutAmpersands = filename; FindAndReplace(filenameWithoutAmpersands, "&", "&"); xmlNewProp(node, XC "filename", XC filenameWithoutAmpersands.c_str()); } else { xmlNewProp(node, XC "filename", XC filename.c_str()); } } }; }