Features:

--Add support for Exr files which use 32-bit floats for each RGBA channel. Seems to come out too washed out.
--Allow for clearing an individual color curve.
--Allow for saving multiple image types in EmberRender and EmberAnimate. All writes are threaded.
--Remove --bpc command line argument. Add format png16 as a replacement.
--Remove --enable_jpg_comments and --enable_png_comments command line arguments, and replace them with --enable_comments which applies to jpg, png and exr.
--Add menu items to variations and affine spinners which allow for easy entry of specific numeric values like pi.
--Make final render dialog be wider rather than so tall.

Bug fixes:
--Fix some OpenCL compile errors on Mac.
--Remove ability to save bitmap files on all platforms but Windows.

Code changes:
--New dependency on OpenEXR.
--Allow Curves class to interact with objects of a different template type.
--Make m_Curves member of Ember always use float as template type.
--Set the length of the curves array to always be 2^17 which should offer enough precision with new 32-bit float pixel types.
--Set pixel types to always be 32-bit float. This results in a major reduction of code in the final accumulation part of Renderer.h/cpp.
--Remove corresponding code from RendererCL and FinalAccumOpenCLKernelCreator.
--Remove Transparency, NumChannels and BytesPerPixel setters from Renderer.h/cpp.
--Add new global functions to format final image buffers and place all alpha calculation and scaling code in them.
--Blending is no longer needed in OpenGLWidget because of the new pixel type.
--Make new class, AffineDoubleSpinBox.
--Attempt to make file save dialog code work the same on all OSes.
--Remove some unused functions.
This commit is contained in:
Person
2017-07-22 13:43:35 -07:00
parent d5760e451a
commit de613404de
68 changed files with 1755 additions and 1276 deletions

View File

@ -1,6 +1,7 @@
#pragma once
#include "EmberCommonPch.h"
#include "EmberOptions.h"
/// <summary>
/// Global utility classes and functions that are common to all programs that use
@ -168,26 +169,133 @@ void FormatName(Ember<T>& result, ostringstream& os, streamsize padding)
}
/// <summary>
/// Convert an RGBA buffer to an RGB buffer.
/// Convert an RGBA 32-bit float buffer to an RGB 8-bit buffer.
/// The two buffers can point to the same memory location if needed.
/// </summary>
/// <param name="rgba">The RGBA buffer</param>
/// <param name="rgb">The RGB buffer</param>
/// <param name="rgba">The RGBA 32-bit float buffer</param>
/// <param name="rgb">The RGB 8-bit buffer</param>
/// <param name="width">The width of the image in pixels</param>
/// <param name="height">The height of the image in pixels</param>
static void RgbaToRgb(vector<byte>& rgba, vector<byte>& rgb, size_t width, size_t height)
static void Rgba32ToRgb8(v4F* rgba, byte* rgb, size_t width, size_t height)
{
if (rgba.data() != rgb.data())//Only resize the destination buffer if they are different.
rgb.resize(width * height * 3);
for (size_t i = 0, j = 0; i < (width * height * 4); i += 4, j += 3)
for (size_t i = 0, j = 0; i < (width * height); i++)
{
rgb[j] = rgba[i];
rgb[j + 1] = rgba[i + 1];
rgb[j + 2] = rgba[i + 2];
rgb[j++] = byte(Clamp<float>(rgba[i].r * 255.0f, 0.0f, 255.0f));
rgb[j++] = byte(Clamp<float>(rgba[i].g * 255.0f, 0.0f, 255.0f));
rgb[j++] = byte(Clamp<float>(rgba[i].b * 255.0f, 0.0f, 255.0f));
}
}
/// <summary>
/// Convert an RGBA 32-bit float buffer to an RGBA 8-bit buffer.
/// The two buffers can point to the same memory location if needed.
/// </summary>
/// <param name="rgba">The RGBA 32-bit float buffer</param>
/// <param name="rgb">The RGBA 8-bit buffer</param>
/// <param name="width">The width of the image in pixels</param>
/// <param name="height">The height of the image in pixels</param>
/// <param name="doAlpha">True to use alpha transparency, false to assign the max alpha value to make each pixel fully visible</param>
static void Rgba32ToRgba8(v4F* rgba, byte* rgb, size_t width, size_t height, bool doAlpha)
{
for (size_t i = 0, j = 0; i < (width * height); i++)
{
rgb[j++] = byte(Clamp<float>(rgba[i].r * 255.0f, 0.0f, 255.0f));
rgb[j++] = byte(Clamp<float>(rgba[i].g * 255.0f, 0.0f, 255.0f));
rgb[j++] = byte(Clamp<float>(rgba[i].b * 255.0f, 0.0f, 255.0f));
rgb[j++] = doAlpha ? byte(Clamp<float>(rgba[i].a * 255.0f, 0.0f, 255.0f)) : 255;
}
}
/// <summary>
/// Convert an RGBA 32-bit float buffer to an RGBA 16-bit buffer.
/// The two buffers can point to the same memory location if needed.
/// </summary>
/// <param name="rgba">The RGBA 32-bit float buffer</param>
/// <param name="rgb">The RGBA 16-bit buffer</param>
/// <param name="width">The width of the image in pixels</param>
/// <param name="height">The height of the image in pixels</param>
/// <param name="doAlpha">True to use alpha transparency, false to assign the max alpha value to make each pixel fully visible</param>
static void Rgba32ToRgba16(v4F* rgba, glm::uint16* rgb, size_t width, size_t height, bool doAlpha)
{
for (size_t i = 0, j = 0; i < (width * height); i++)
{
rgb[j++] = glm::uint16(Clamp<float>(rgba[i].r * 65535.0f, 0.0f, 65535.0f));
rgb[j++] = glm::uint16(Clamp<float>(rgba[i].g * 65535.0f, 0.0f, 65535.0f));
rgb[j++] = glm::uint16(Clamp<float>(rgba[i].b * 65535.0f, 0.0f, 65535.0f));
rgb[j++] = doAlpha ? glm::uint16(Clamp<float>(rgba[i].a * 65535.0f, 0.0f, 65535.0f)) : glm::uint16(65535);
}
}
/// <summary>
/// Convert an RGBA 32-bit float buffer to an EXR RGBA 32-bit float buffer.
/// The two buffers can point to the same memory location if needed.
/// </summary>
/// <param name="rgba">The RGBA 32-bit float buffer</param>
/// <param name="rgb">The EXR RGBA 32-bit float buffer</param>
/// <param name="width">The width of the image in pixels</param>
/// <param name="height">The height of the image in pixels</param>
/// <param name="doAlpha">True to use alpha transparency, false to assign the max alpha value to make each pixel fully visible</param>
static void Rgba32ToRgbaExr(v4F* rgba, Rgba* ilmfRgba, size_t width, size_t height, bool doAlpha)
{
for (size_t i = 0; i < (width * height); i++)
{
ilmfRgba[i].r = Clamp<float>(rgba[i].r, 0.0f, 1.0f);
ilmfRgba[i].g = Clamp<float>(rgba[i].g, 0.0f, 1.0f);
ilmfRgba[i].b = Clamp<float>(rgba[i].b, 0.0f, 1.0f);
ilmfRgba[i].a = doAlpha ? Clamp<float>(rgba[i].a * 1.0f, 0.0f, 1.0f) : 1.0f;
}
}
/// <summary>
/// Make a filename for a single render. This is used in EmberRender.
/// </summary>
/// <param name="path">The path portion of where to save the file</param>
/// <param name="out">The full name and path to override everything else</param>
/// <param name="finalName">The name to use when useFinalName is true</param>
/// <param name="prefix">The prefix to prepend to the filename</param>
/// <param name="suffix">True suffix to append to the filename</param>
/// <param name="format">The format extention. This must not contain a period.</param>
/// <param name="padding">The width padding to use, which will be zero filled.</param>
/// <param name="i">The numerical value to use for the filename when useFinalName is false and out is empty</param>
/// <param name="useFinalName">Whether to use the name included in the flame. The i parameter is ignored in this case.</param>
static string MakeSingleFilename(const string& path, const string& out, const string& finalName, const string& prefix, const string& suffix, const string& format, glm::uint padding, size_t i, bool useFinalName)
{
string filename;
if (!out.empty())
{
filename = out;
}
else if (useFinalName)
{
filename = path + prefix + finalName + suffix + "." + format;
}
else
{
ostringstream fnstream;
fnstream << path << prefix << setfill('0') << setprecision(0) << fixed << setw(padding) << i << suffix << "." << format;
filename = fnstream.str();
}
return filename;
}
/// <summary>
/// Make a filename for a frame of an animation render. This is used in EmberAnimate.
/// </summary>
/// <param name="path">The path portion of where to save the file</param>
/// <param name="prefix">The prefix to prepend to the filename</param>
/// <param name="suffix">True suffix to append to the filename</param>
/// <param name="format">The format extention. This must contain a period.</param>
/// <param name="padding">The width padding to use, which will be zero filled.</param>
/// <param name="ftime">The numerical value to use for the filename</param>
static string MakeAnimFilename(const string& path, const string& prefix, const string& suffix, const string& format, glm::uint padding, size_t ftime)
{
ostringstream fnstream;
fnstream << path << prefix << setfill('0') << setprecision(0) << fixed << setw(padding) << ftime << suffix << format;
return fnstream.str();
}
/// <summary>
/// Calculate the number of strips required if the needed amount of memory
/// is greater than the system memory, or greater than what the user wants to allow.
@ -439,7 +547,7 @@ static vector<unique_ptr<Renderer<T, float>>> CreateRenderers(eRendererType rend
/// <param name="allStripsFinished">Function called when all strips successfully finish rendering</param>
/// <returns>True if all rendering was successful, else false.</returns>
template <typename T>
static bool StripsRender(RendererBase* renderer, Ember<T>& ember, vector<byte>& finalImage, double time, size_t strips, bool yAxisUp,
static bool StripsRender(RendererBase* renderer, Ember<T>& ember, vector<v4F>& finalImage, double time, size_t strips, bool yAxisUp,
std::function<void(size_t strip)> perStripStart,
std::function<void(size_t strip)> perStripFinish,
std::function<void(size_t strip)> perStripError,

View File

@ -57,5 +57,17 @@
#include "SimpleGlob.h"
#include "SimpleOpt.h"
//Exr
#ifdef _WIN32
#define OPENEXR_DLL 1
#endif
#include <ImfRgbaFile.h>
#include <ImfStringAttribute.h>
#include <half.h>
using namespace Imf;
using namespace Imath;
using namespace EmberNs;
using namespace EmberCLns;

View File

@ -58,8 +58,7 @@ enum class eOptionIDs : et
OPT_INT_PALETTE,
OPT_HEX_PALETTE,
OPT_INSERT_PALETTE,
OPT_JPEG_COMMENTS,
OPT_PNG_COMMENTS,
OPT_ENABLE_COMMENTS,
OPT_WRITE_GENOME,
OPT_THREADED_WRITE,
OPT_ENCLOSED,
@ -75,7 +74,6 @@ enum class eOptionIDs : et
OPT_STRIPS,
OPT_SUPERSAMPLE,
OPT_TEMPSAMPLES,
OPT_BPC,
OPT_PRINT_EDIT_DEPTH,
OPT_JPEG,
OPT_BEGIN,
@ -363,8 +361,7 @@ public:
INITBOOLOPTION(NameEnable, Eob(eOptionUse::OPT_USE_RENDER, eOptionIDs::OPT_NAME_ENABLE, _T("--name_enable"), false, SO_NONE, " --name_enable Use the name attribute contained in the Xml as the output filename [default: false].\n"));
INITBOOLOPTION(HexPalette, Eob(eOptionUse::OPT_ANIM_GENOME, eOptionIDs::OPT_HEX_PALETTE, _T("--hex_palette"), true, SO_OPT, " --hex_palette Force palette RGB values to be hex when saving to Xml [default: true].\n"));
INITBOOLOPTION(InsertPalette, Eob(eOptionUse::OPT_USE_RENDER, eOptionIDs::OPT_INSERT_PALETTE, _T("--insert_palette"), false, SO_NONE, " --insert_palette Insert the palette into the image for debugging purposes. Disabled when running with OpenCL [default: false].\n"));
INITBOOLOPTION(JpegComments, Eob(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_JPEG_COMMENTS, _T("--enable_jpg_comments"), false, SO_NONE, " --enable_jpg_comments Enables embedding the flame parameters and user identifying information in the jpeg header [default: false].\n"));
INITBOOLOPTION(PngComments, Eob(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_PNG_COMMENTS, _T("--enable_png_comments"), false, SO_NONE, " --enable_png_comments Enables embedding the flame parameters and user identifying information in the png header [default: false].\n"));
INITBOOLOPTION(EnableComments, Eob(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_ENABLE_COMMENTS, _T("--enable_comments"), false, SO_NONE, " --enable_comments Enables embedding the flame parameters and user identifying information in the header of a jpg, png or exr [default: false].\n"));
INITBOOLOPTION(WriteGenome, Eob(eOptionUse::OPT_USE_ANIMATE, eOptionIDs::OPT_WRITE_GENOME, _T("--write_genome"), false, SO_NONE, " --write_genome Write out flame associated with center of motion blur window [default: false].\n"));
INITBOOLOPTION(ThreadedWrite, Eob(eOptionUse::OPT_USE_ANIMATE, eOptionIDs::OPT_THREADED_WRITE, _T("--threaded_write"), true, SO_OPT, " --threaded_write Use a separate thread to write images to disk. This gives better performance, but doubles the memory required for the final output buffer. [default: true].\n"));
INITBOOLOPTION(Enclosed, Eob(eOptionUse::OPT_USE_GENOME, eOptionIDs::OPT_ENCLOSED, _T("--enclosed"), true, SO_OPT, " --enclosed Use enclosing Xml tags [default: true].\n"));
@ -388,7 +385,6 @@ public:
INITUINTOPTION(Strips, Eou(eOptionUse::OPT_USE_RENDER, eOptionIDs::OPT_STRIPS, _T("--nstrips"), 1, SO_REQ_SEP, " --nstrips=<val> The number of fractions to split a single render frame into. Useful for print size renders or low memory systems [default: 1].\n"));
INITUINTOPTION(Supersample, Eou(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_SUPERSAMPLE, _T("--supersample"), 0, SO_REQ_SEP, " --supersample=<val> The supersample value used to override the one specified in the file [default: 0 (use value from file), Range: 0 - 4].\n"));
INITUINTOPTION(TemporalSamples, Eou(eOptionUse::OPT_USE_ANIMATE, eOptionIDs::OPT_TEMPSAMPLES, _T("--ts"), 0, SO_REQ_SEP, " --ts=<val> The temporal samples value used to override all of the temporal sample values specified in the file when animating [default: 0 (use value from file)].\n"));
INITUINTOPTION(BitsPerChannel, Eou(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_BPC, _T("--bpc"), 8, SO_REQ_SEP, " --bpc=<val> Bits per channel. 8 or 16 for PNG, 8 for all others, always 8 with OpenCL [default: 8].\n"));
INITUINTOPTION(PrintEditDepth, Eou(eOptionUse::OPT_USE_ALL, eOptionIDs::OPT_PRINT_EDIT_DEPTH, _T("--print_edit_depth"), 0, SO_REQ_SEP, " --print_edit_depth=<val> Depth to truncate <edit> tag structure when converting a flame to Xml. 0 prints all <edit> tags [default: 0].\n"));
INITUINTOPTION(JpegQuality, Eou(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_JPEG, _T("--jpeg"), 95, SO_REQ_SEP, " --jpeg=<val> Jpeg quality 0-100 for compression [default: 95].\n"));
INITUINTOPTION(FirstFrame, Eou(eOptionUse::OPT_USE_ANIMATE, eOptionIDs::OPT_BEGIN, _T("--begin"), UINT_MAX, SO_REQ_SEP, " --begin=<val> Time of first frame to render [default: first time specified in file].\n"));
@ -431,7 +427,11 @@ public:
INITSTRINGOPTION(Out, Eos(eOptionUse::OPT_USE_RENDER, eOptionIDs::OPT_OUT, _T("--out"), "", SO_REQ_SEP, " --out=<val> Name of a single output file. Not recommended when rendering more than one image.\n"));
INITSTRINGOPTION(Prefix, Eos(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_PREFIX, _T("--prefix"), "", SO_REQ_SEP, " --prefix=<val> Prefix to prepend to all output files.\n"));
INITSTRINGOPTION(Suffix, Eos(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_SUFFIX, _T("--suffix"), "", SO_REQ_SEP, " --suffix=<val> Suffix to append to all output files.\n"));
INITSTRINGOPTION(Format, Eos(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_FORMAT, _T("--format"), "png", SO_REQ_SEP, " --format=<val> Format of the output file. Valid values are: bmp, jpg, png [default: png].\n"));
#ifdef _WIN32
INITSTRINGOPTION(Format, Eos(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_FORMAT, _T("--format"), "png", SO_REQ_SEP, " --format=<val> Format of the output file. Valid values are: bmp, jpg, png or exr [default: png].\n"));
#else
INITSTRINGOPTION(Format, Eos(eOptionUse::OPT_RENDER_ANIM, eOptionIDs::OPT_FORMAT, _T("--format"), "png", SO_REQ_SEP, " --format=<val> Format of the output file. Valid values are: jpg, png or exr [default: png].\n"));
#endif
INITSTRINGOPTION(PalettePath, Eos(eOptionUse::OPT_USE_ALL, eOptionIDs::OPT_PALETTE_FILE, _T("--flam3_palettes"), "flam3-palettes.xml", SO_REQ_SEP, " --flam3_palettes=<val> Path and name of the palette file [default: flam3-palettes.xml].\n"));
INITSTRINGOPTION(Id, Eos(eOptionUse::OPT_USE_ALL, eOptionIDs::OPT_ID, _T("--id"), "", SO_REQ_SEP, " --id=<val> ID to use in <edit> tags / image comments.\n"));
INITSTRINGOPTION(Url, Eos(eOptionUse::OPT_USE_ALL, eOptionIDs::OPT_URL, _T("--url"), "", SO_REQ_SEP, " --url=<val> URL to use in <edit> tags / image comments.\n"));
@ -519,8 +519,7 @@ public:
PARSEBOOLOPTION(eOptionIDs::OPT_NAME_ENABLE, NameEnable);
PARSEBOOLOPTION(eOptionIDs::OPT_HEX_PALETTE, HexPalette);
PARSEBOOLOPTION(eOptionIDs::OPT_INSERT_PALETTE, InsertPalette);
PARSEBOOLOPTION(eOptionIDs::OPT_JPEG_COMMENTS, JpegComments);
PARSEBOOLOPTION(eOptionIDs::OPT_PNG_COMMENTS, PngComments);
PARSEBOOLOPTION(eOptionIDs::OPT_ENABLE_COMMENTS, EnableComments);
PARSEBOOLOPTION(eOptionIDs::OPT_WRITE_GENOME, WriteGenome);
PARSEBOOLOPTION(eOptionIDs::OPT_THREADED_WRITE, ThreadedWrite);
PARSEBOOLOPTION(eOptionIDs::OPT_ENCLOSED, Enclosed);
@ -538,7 +537,6 @@ public:
PARSEOPTION(eOptionIDs::OPT_STRIPS, Strips);
PARSEOPTION(eOptionIDs::OPT_SUPERSAMPLE, Supersample);
PARSEOPTION(eOptionIDs::OPT_TEMPSAMPLES, TemporalSamples);
PARSEOPTION(eOptionIDs::OPT_BPC, BitsPerChannel);
PARSEOPTION(eOptionIDs::OPT_PRINT_EDIT_DEPTH, PrintEditDepth);
PARSEOPTION(eOptionIDs::OPT_JPEG, JpegQuality);
PARSEOPTION(eOptionIDs::OPT_BEGIN, FirstFrame);
@ -723,18 +721,18 @@ public:
{
cout << "Usage:\n"
#ifdef _WIN32
"\tEmberRender.exe --in=test.flame [--out=outfile --format=png --verbose --progress --opencl]\n\n";
"\tEmberRender.exe --in=test.flame [--out=outfile --verbose --progress --opencl]\n\n";
#else
"\temberrender --in=test.flame [--out=outfile --format=png --verbose --progress --opencl]\n\n";
"\temberrender --in=test.flame [--out=outfile --verbose --progress --opencl]\n\n";
#endif
}
else if (optUsage == eOptionUse::OPT_USE_ANIMATE)
{
cout << "Usage:\n"
#ifdef _WIN32
"\tEmberAnimate.exe --in=sequence.flame [--format=png --verbose --progress --opencl]\n\n";
"\tEmberAnimate.exe --in=sequence.flame [--verbose --progress --opencl]\n\n";
#else
"\temberanimate --in=sequence.flame [--format=png --verbose --progress --opencl]\n\n";
"\temberanimate --in=sequence.flame [--verbose --progress --opencl]\n\n";
#endif
}
else if (optUsage == eOptionUse::OPT_USE_GENOME)
@ -808,8 +806,7 @@ public:
Eob NameEnable;
Eob HexPalette;
Eob InsertPalette;
Eob JpegComments;
Eob PngComments;
Eob EnableComments;
Eob WriteGenome;
Eob ThreadedWrite;
Eob Enclosed;
@ -828,7 +825,6 @@ public:
Eou Strips;
Eou Supersample;
Eou TemporalSamples;
Eou BitsPerChannel;
Eou Bits;
Eou PrintEditDepth;
Eou JpegQuality;

View File

@ -136,28 +136,28 @@ static bool WritePng(const char* filename, byte* image, size_t width, size_t hei
glm::uint16 testbe = 1;
vector<byte*> rows(height);
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
text[0].key = const_cast<png_charp>("flam3_version");
text[0].key = const_cast<png_charp>("ember_version");
text[0].text = const_cast<png_charp>(EmberVersion());
text[1].compression = PNG_TEXT_COMPRESSION_NONE;
text[1].key = const_cast<png_charp>("flam3_nickname");
text[1].key = const_cast<png_charp>("ember_nickname");
text[1].text = const_cast<png_charp>(nick.c_str());
text[2].compression = PNG_TEXT_COMPRESSION_NONE;
text[2].key = const_cast<png_charp>("flam3_url");
text[2].key = const_cast<png_charp>("ember_url");
text[2].text = const_cast<png_charp>(url.c_str());
text[3].compression = PNG_TEXT_COMPRESSION_NONE;
text[3].key = const_cast<png_charp>("flam3_id");
text[3].key = const_cast<png_charp>("ember_id");
text[3].text = const_cast<png_charp>(id.c_str());
text[4].compression = PNG_TEXT_COMPRESSION_NONE;
text[4].key = const_cast<png_charp>("flam3_error_rate");
text[4].key = const_cast<png_charp>("ember_error_rate");
text[4].text = const_cast<png_charp>(comments.m_Badvals.c_str());
text[5].compression = PNG_TEXT_COMPRESSION_NONE;
text[5].key = const_cast<png_charp>("flam3_samples");
text[5].key = const_cast<png_charp>("ember_samples");
text[5].text = const_cast<png_charp>(comments.m_NumIters.c_str());
text[6].compression = PNG_TEXT_COMPRESSION_NONE;
text[6].key = const_cast<png_charp>("flam3_time");
text[6].key = const_cast<png_charp>("ember_time");
text[6].text = const_cast<png_charp>(comments.m_Runtime.c_str());
text[7].compression = PNG_TEXT_COMPRESSION_zTXt;
text[7].key = const_cast<png_charp>("flam3_genome");
text[7].key = const_cast<png_charp>("ember_genome");
text[7].text = const_cast<png_charp>(comments.m_Genome.c_str());
for (i = 0; i < height; i++)
@ -326,3 +326,50 @@ static bool WriteBmp(const char* filename, byte* image, size_t width, size_t hei
b = SaveBmp(filename, bgrBuf.data(), width, height, newSize);
return b;
}
/// <summary>
/// Write an EXR file.
/// This is used for extreme color precision because it uses
/// floats for each color channel.
/// </summary>
/// <param name="filename">The full path and name of the file</param>
/// <param name="image">Pointer to the image data to write</param>
/// <param name="width">Width of the image in pixels</param>
/// <param name="height">Height of the image in pixels</param>
/// <param name="enableComments">True to embed comments, else false</param>
/// <param name="comments">The comment string to embed</param>
/// <param name="id">Id of the author</param>
/// <param name="url">Url of the author</param>
/// <param name="nick">Nickname of the author</param>
/// <returns>True if success, else false</returns>
static bool WriteExr(const char* filename, Rgba* image, size_t width, size_t height, bool enableComments, const EmberImageComments& comments, const string& id, const string& url, const string& nick)
{
try
{
int iw = int(width);
int ih = int(height);
RgbaOutputFile file(filename, iw, ih, WRITE_RGBA);
if (enableComments)
{
auto& header = const_cast<Imf::Header&>(file.header());
header.insert("ember_version", StringAttribute(EmberVersion()));
header.insert("ember_nickname", StringAttribute(nick));
header.insert("ember_url", StringAttribute(url));
header.insert("ember_id", StringAttribute(id));
header.insert("ember_error_rate", StringAttribute(comments.m_Badvals));
header.insert("ember_samples", StringAttribute(comments.m_NumIters));
header.insert("ember_time", StringAttribute(comments.m_Runtime));
header.insert("ember_genome", StringAttribute(comments.m_Genome));
}
file.setFrameBuffer(image, 1, iw);
file.writePixels(ih);
return true;
}
catch (std::exception e)
{
cout << e.what() << endl;
return false;
}
}