--User changes

-Allow for saving EXR as full 31-bit float per component per pixel.
 -Allow for saving the output from the final render dialog as a different image format without starting the rendering process over.

--Code changes:
 -Make StripsRender() handle memsetting the final output image so calling code no longer has to.
 -Make FinalRenderEmberController<T>::SaveCurrentRender() return the path that the image was actually saved to.
This commit is contained in:
Person 2019-11-29 23:52:17 -08:00
parent 59605f10a8
commit 35d4eb3464
12 changed files with 352 additions and 64 deletions

View File

@ -326,10 +326,12 @@ bool EmberAnimate(int argc, _TCHAR* argv[], EmberOptions& opt)
auto size = w * h; auto size = w * h;
bool doBmp = Find(opt.Format(), "bmp"); bool doBmp = Find(opt.Format(), "bmp");
bool doJpg = Find(opt.Format(), "jpg"); bool doJpg = Find(opt.Format(), "jpg");
bool doExr = Find(opt.Format(), "exr"); bool doExr16 = Find(opt.Format(), "exr");
bool doExr32 = Find(opt.Format(), "exr32");
bool doPng8 = Find(opt.Format(), "png"); bool doPng8 = Find(opt.Format(), "png");
bool doPng16 = Find(opt.Format(), "png16"); bool doPng16 = Find(opt.Format(), "png16");
bool doOnlyPng8 = doPng8 && !doPng16; bool doOnlyPng8 = doPng8 && !doPng16;
bool doOnlyExr16 = doExr16 && !doExr32;
vector<byte> rgb8Image; vector<byte> rgb8Image;
vector<std::thread> writeFileThreads; vector<std::thread> writeFileThreads;
writeFileThreads.reserve(5); writeFileThreads.reserve(5);
@ -370,7 +372,7 @@ bool EmberAnimate(int argc, _TCHAR* argv[], EmberOptions& opt)
{ {
bool doBothPng = doPng16 && (opt.Format().find("png") != opt.Format().rfind("png")); bool doBothPng = doPng16 && (opt.Format().find("png") != opt.Format().rfind("png"));
if (doBothPng || doOnlyPng8)//8-bit PNG if (doBothPng || doOnlyPng8)//8-bit PNG.
{ {
writeFileThreads.push_back(std::thread([&]() writeFileThreads.push_back(std::thread([&]()
{ {
@ -385,7 +387,7 @@ bool EmberAnimate(int argc, _TCHAR* argv[], EmberOptions& opt)
})); }));
} }
if (doPng16) if (doPng16)//16-bit PNG.
{ {
writeFileThreads.push_back(std::thread([&]() writeFileThreads.push_back(std::thread([&]()
{ {
@ -410,19 +412,56 @@ bool EmberAnimate(int argc, _TCHAR* argv[], EmberOptions& opt)
} }
} }
if (doExr) if (doExr16)
{ {
writeFileThreads.push_back(std::thread([&]() bool doBothExr = doExr32 && (opt.Format().find("exr") != opt.Format().rfind("exr"));
{
auto fn = baseFilename + ".exr";
VerbosePrint("Writing " + fn);
vector<Rgba> rgba32Image(size);
Rgba32ToRgbaExr(finalImagep, rgba32Image.data(), w, h, opt.Transparency());
auto writeSuccess = WriteExr(fn.c_str(), rgba32Image.data(), w, h, opt.EnableComments(), comments, opt.Id(), opt.Url(), opt.Nick());
if (!writeSuccess) if (doBothExr || doOnlyExr16)//16-bit EXR
cout << "Error writing " << fn << "\n"; {
})); writeFileThreads.push_back(std::thread([&]()
{
auto fn = baseFilename + ".exr";
VerbosePrint("Writing " + fn);
vector<Rgba> rgba32Image(size);
Rgba32ToRgbaExr(finalImagep, rgba32Image.data(), w, h, opt.Transparency());
auto writeSuccess = WriteExr16(fn.c_str(), rgba32Image.data(), w, h, opt.EnableComments(), comments, opt.Id(), opt.Url(), opt.Nick());
if (!writeSuccess)
cout << "Error writing " << fn << "\n";
}));
}
if (doExr32)//32-bit EXR.
{
writeFileThreads.push_back(std::thread([&]()
{
auto suffix = opt.Suffix();
auto fn = baseFilename;
if (doBothExr)//Add suffix if they specified both EXR.
{
VerbosePrint("Doing both EXR formats, so adding suffix _exr32 to avoid overwriting the same file.");
fn += "_exr32";
}
fn += ".exr";
VerbosePrint("Writing " + fn);
vector<float> r(size);
vector<float> g(size);
vector<float> b(size);
vector<float> a(size);
Rgba32ToRgba32Exr(finalImagep, r.data(), g.data(), b.data(), a.data(), w, h, opt.Transparency());
auto writeSuccess = WriteExr32(fn.c_str(),
r.data(),
g.data(),
b.data(),
a.data(),
w, h, opt.EnableComments(), comments, opt.Id(), opt.Url(), opt.Nick());
if (!writeSuccess)
cout << "Error writing " << fn << "\n";
}));
}
} }
Join(writeFileThreads); Join(writeFileThreads);

View File

@ -263,12 +263,12 @@ static void Rgba32ToRgba16(v4F* rgba, glm::uint16* rgb, size_t width, size_t hei
} }
/// <summary> /// <summary>
/// Convert an RGBA 32-bit float buffer to an EXR RGBA 32-bit float buffer. /// Convert an RGBA 32-bit float buffer to an EXR RGBA 16-bit float buffer.
/// The two buffers can point to the same memory location if needed. /// The two buffers can point to the same memory location if needed.
/// Note that this squares the values coming in, for some reason EXR expects that. /// Note that this squares the values coming in, for some reason EXR expects that.
/// </summary> /// </summary>
/// <param name="rgba">The RGBA 32-bit float buffer</param> /// <param name="rgba">The RGBA 32-bit float buffer</param>
/// <param name="rgb">The EXR RGBA 32-bit float buffer</param> /// <param name="ilmfRgba">The EXR RGBA 16-bit float buffer</param>
/// <param name="width">The width of the image in pixels</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="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> /// <param name="doAlpha">True to use alpha transparency, false to assign the max alpha value to make each pixel fully visible</param>
@ -283,6 +283,30 @@ static void Rgba32ToRgbaExr(v4F* rgba, Rgba* ilmfRgba, size_t width, size_t heig
} }
} }
/// <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.
/// Note that this squares the values coming in, for some reason EXR expects that.
/// </summary>
/// <param name="rgba">The RGBA 32-bit float buffer</param>
/// <param name="r">The EXR red 32-bit float buffer</param>
/// <param name="g">The EXR green 32-bit float buffer</param>
/// <param name="b">The EXR blue 32-bit float buffer</param>
/// <param name="a">The EXR alpha 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 Rgba32ToRgba32Exr(v4F* rgba, float* r, float* g, float* b, float* a, size_t width, size_t height, bool doAlpha)
{
for (size_t i = 0; i < (width * height); i++)
{
r[i] = Clamp<float>(Sqr(rgba[i].r), 0.0f, 1.0f);
g[i] = Clamp<float>(Sqr(rgba[i].g), 0.0f, 1.0f);
b[i] = Clamp<float>(Sqr(rgba[i].b), 0.0f, 1.0f);
a[i] = doAlpha ? Clamp<float>(rgba[i].a * 1.0f, 0.0f, 1.0f) : 1.0f;
}
}
/// <summary> /// <summary>
/// Make a filename for a single render. This is used in EmberRender. /// Make a filename for a single render. This is used in EmberRender.
/// </summary> /// </summary>
@ -599,6 +623,7 @@ static bool StripsRender(RendererBase* renderer, Ember<T>& ember, vector<v4F>& f
vector<QTIsaac<ISAAC_SIZE, ISAAC_INT>> randVec; vector<QTIsaac<ISAAC_SIZE, ISAAC_INT>> randVec;
ember.m_Quality *= strips; ember.m_Quality *= strips;
ember.m_FinalRasH = size_t(ceil(floatStripH)); ember.m_FinalRasH = size_t(ceil(floatStripH));
Memset(finalImage);
if (strips > 1) if (strips > 1)
randVec = renderer->RandVec(); randVec = renderer->RandVec();
@ -654,7 +679,6 @@ static bool StripsRender(RendererBase* renderer, Ember<T>& ember, vector<v4F>& f
if (success) if (success)
allStripsFinished(ember); allStripsFinished(ember);
Memset(finalImage);
return success; return success;
} }

View File

@ -67,11 +67,15 @@
#include <OpenEXR/ImfRgbaFile.h> #include <OpenEXR/ImfRgbaFile.h>
#include <OpenEXR/ImfStringAttribute.h> #include <OpenEXR/ImfStringAttribute.h>
#include <OpenEXR/half.h> #include <OpenEXR/half.h>
#define ENUM_DYLD_BOOL #include <OpenEXR/ImfChannelList.h>
#include <mach-o/dyld.h> #include <OpenEXR/ImfOutputFile.h>
#define ENUM_DYLD_BOOL
#include <mach-o/dyld.h>
#else #else
#include <ImfRgbaFile.h> #include <ImfRgbaFile.h>
#include <ImfStringAttribute.h> #include <ImfStringAttribute.h>
#include <ImfChannelList.h>
#include <ImfOutputFile.h>
#include <half.h> #include <half.h>
#endif #endif

View File

@ -364,9 +364,9 @@ static bool WriteBmp(const char* filename, byte* image, size_t width, size_t hei
} }
/// <summary> /// <summary>
/// Write an EXR file. /// Write an EXR file which will use the 16 bit half float format for each pixel channel.
/// This is used for extreme color precision because it uses /// This is used for high color precision because it uses
/// floats for each color channel. /// HALFS for each color channel.
/// </summary> /// </summary>
/// <param name="filename">The full path and name of the file</param> /// <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="image">Pointer to the image data to write</param>
@ -378,7 +378,7 @@ static bool WriteBmp(const char* filename, byte* image, size_t width, size_t hei
/// <param name="url">Url of the author</param> /// <param name="url">Url of the author</param>
/// <param name="nick">Nickname of the author</param> /// <param name="nick">Nickname of the author</param>
/// <returns>True if success, else false</returns> /// <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) static bool WriteExr16(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 try
{ {
@ -419,3 +419,89 @@ static bool WriteExr(const char* filename, Rgba* image, size_t width, size_t hei
return false; return false;
} }
} }
/// <summary>
/// Write an EXR file which will use the 32 bit half float format for each pixel channel.
/// 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="r">Pointer to the red image data to write</param>
/// <param name="g">Pointer to the green image data to write</param>
/// <param name="b">Pointer to the blue image data to write</param>
/// <param name="a">Pointer to the alpha 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 WriteExr32(const char* filename, float* r, float* g, float* b, float* a, 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);
std::unique_ptr<OutputFile> file;
try
{
rlg l(fileCs);
Header header(iw, ih);
header.channels().insert("R", Channel(PixelType::FLOAT));
header.channels().insert("G", Channel(PixelType::FLOAT));
header.channels().insert("B", Channel(PixelType::FLOAT));
header.channels().insert("A", Channel(PixelType::FLOAT));
file = std::make_unique<OutputFile>(filename, header);
}
catch (std::exception)
{
return false;
}
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));
}
FrameBuffer frameBuffer;
frameBuffer.insert("R",
Slice(PixelType::FLOAT,
(char*)r,
sizeof(*r) * 1,
sizeof(*r) * width));
frameBuffer.insert("G",
Slice(PixelType::FLOAT,
(char*)g,
sizeof(*g) * 1,
sizeof(*g) * width));
frameBuffer.insert("B",
Slice(PixelType::FLOAT,
(char*)b,
sizeof(*b) * 1,
sizeof(*b) * width));
frameBuffer.insert("A",
Slice(PixelType::FLOAT,
(char*)a,
sizeof(*a) * 1,
sizeof(*a) * width));
file->setFrameBuffer(frameBuffer);
file->writePixels(ih);
return true;
}
catch (std::exception e)
{
cout << e.what() << endl;
return false;
}
}

View File

@ -291,10 +291,12 @@ bool EmberRender(int argc, _TCHAR* argv[], EmberOptions& opt)
auto size = finalEmber.m_FinalRasW * finalEmber.m_FinalRasH; auto size = finalEmber.m_FinalRasW * finalEmber.m_FinalRasH;
bool doBmp = Find(opt.Format(), "bmp"); bool doBmp = Find(opt.Format(), "bmp");
bool doJpg = Find(opt.Format(), "jpg"); bool doJpg = Find(opt.Format(), "jpg");
bool doExr = Find(opt.Format(), "exr"); bool doExr16 = Find(opt.Format(), "exr");
bool doExr32 = Find(opt.Format(), "exr32");
bool doPng8 = Find(opt.Format(), "png"); bool doPng8 = Find(opt.Format(), "png");
bool doPng16 = Find(opt.Format(), "png16"); bool doPng16 = Find(opt.Format(), "png16");
bool doOnlyPng8 = doPng8 && !doPng16; bool doOnlyPng8 = doPng8 && !doPng16;
bool doOnlyExr16 = doExr16 && !doExr32;
vector<byte> rgb8Image; vector<byte> rgb8Image;
vector<std::thread> writeFileThreads; vector<std::thread> writeFileThreads;
writeFileThreads.reserve(5); writeFileThreads.reserve(5);
@ -335,7 +337,7 @@ bool EmberRender(int argc, _TCHAR* argv[], EmberOptions& opt)
{ {
bool doBothPng = doPng16 && (opt.Format().find("png") != opt.Format().rfind("png")); bool doBothPng = doPng16 && (opt.Format().find("png") != opt.Format().rfind("png"));
if (doBothPng || doOnlyPng8)//8-bit PNG if (doBothPng || doOnlyPng8)//8-bit PNG.
{ {
writeFileThreads.push_back(std::thread([&]() writeFileThreads.push_back(std::thread([&]()
{ {
@ -350,7 +352,7 @@ bool EmberRender(int argc, _TCHAR* argv[], EmberOptions& opt)
})); }));
} }
if (doPng16) if (doPng16)//16-bit PNG.
{ {
writeFileThreads.push_back(std::thread([&]() writeFileThreads.push_back(std::thread([&]()
{ {
@ -374,19 +376,57 @@ bool EmberRender(int argc, _TCHAR* argv[], EmberOptions& opt)
} }
} }
if (doExr) if (doExr16)
{ {
writeFileThreads.push_back(std::thread([&]() bool doBothExr = doExr32 && (opt.Format().find("exr") != opt.Format().rfind("exr"));
{
auto filename = MakeSingleFilename(inputPath, opt.Out(), finalEmber.m_Name, opt.Prefix(), opt.Suffix(), "exr", padding, i, useName);
VerbosePrint("Writing " + filename);
vector<Rgba> rgba32Image(size);
Rgba32ToRgbaExr(finalImagep, rgba32Image.data(), finalEmber.m_FinalRasW, finalEmber.m_FinalRasH, opt.Transparency());
auto writeSuccess = WriteExr(filename.c_str(), rgba32Image.data(), finalEmber.m_FinalRasW, finalEmber.m_FinalRasH, opt.EnableComments(), comments, opt.Id(), opt.Url(), opt.Nick());
if (!writeSuccess) if (doBothExr || doOnlyExr16)//16-bit EXR.
cout << "Error writing " << filename << "\n"; {
})); writeFileThreads.push_back(std::thread([&]()
{
auto filename = MakeSingleFilename(inputPath, opt.Out(), finalEmber.m_Name, opt.Prefix(), opt.Suffix(), "exr", padding, i, useName);
VerbosePrint("Writing " + filename);
vector<Rgba> rgba32Image(size);
Rgba32ToRgbaExr(finalImagep, rgba32Image.data(), finalEmber.m_FinalRasW, finalEmber.m_FinalRasH, opt.Transparency());
auto writeSuccess = WriteExr16(filename.c_str(),
rgba32Image.data(),
finalEmber.m_FinalRasW, finalEmber.m_FinalRasH, opt.EnableComments(), comments, opt.Id(), opt.Url(), opt.Nick());
if (!writeSuccess)
cout << "Error writing " << filename << "\n";
}));
}
if (doExr32)//32-bit EXR.
{
writeFileThreads.push_back(std::thread([&]()
{
auto suffix = opt.Suffix();
if (doBothExr)//Add suffix if they specified both EXR.
{
VerbosePrint("Doing both EXR formats, so adding suffix _exr32 to avoid overwriting the same file.");
suffix += "_exr32";
}
auto filename = MakeSingleFilename(inputPath, opt.Out(), finalEmber.m_Name, opt.Prefix(), suffix, "exr", padding, i, useName);
VerbosePrint("Writing " + filename);
vector<float> r(size);
vector<float> g(size);
vector<float> b(size);
vector<float> a(size);
Rgba32ToRgba32Exr(finalImagep, r.data(), g.data(), b.data(), a.data(), finalEmber.m_FinalRasW, finalEmber.m_FinalRasH, opt.Transparency());
auto writeSuccess = WriteExr32(filename.c_str(),
r.data(),
g.data(),
b.data(),
a.data(),
finalEmber.m_FinalRasW, finalEmber.m_FinalRasH, opt.EnableComments(), comments, opt.Id(), opt.Url(), opt.Nick());
if (!writeSuccess)
cout << "Error writing " << filename << "\n";
}));
}
} }
Join(writeFileThreads); Join(writeFileThreads);

View File

@ -163,22 +163,42 @@ FractoriumFinalRenderDialog::FractoriumFinalRenderDialog(QWidget* p, Qt::WindowF
m_SupersampleSpin->setValue(m_Settings->FinalSupersample()); m_SupersampleSpin->setValue(m_Settings->FinalSupersample());
m_StripsSpin->setValue(int(m_Settings->FinalStrips())); m_StripsSpin->setValue(int(m_Settings->FinalStrips()));
Scale(eScaleType(m_Settings->FinalScale())); Scale(eScaleType(m_Settings->FinalScale()));
auto menu = new QMenu(this); auto bumpmenu = new QMenu(this);
auto add10 = new QAction("Add 10% quality", this); add10->setProperty("tag", QVariant(0.10)); auto add10 = new QAction("Add 10% quality", this); add10->setProperty("tag", QVariant(0.10));
auto add25 = new QAction("Add 25% quality", this); add25->setProperty("tag", QVariant(0.25)); auto add25 = new QAction("Add 25% quality", this); add25->setProperty("tag", QVariant(0.25));
auto add50 = new QAction("Add 50% quality", this); add50->setProperty("tag", QVariant(0.50)); auto add50 = new QAction("Add 50% quality", this); add50->setProperty("tag", QVariant(0.50));
auto add100 = new QAction("Add 100% quality", this); add100->setProperty("tag", QVariant(1.0)); auto add100 = new QAction("Add 100% quality", this); add100->setProperty("tag", QVariant(1.0));
auto add200 = new QAction("Add 200% quality", this); add200->setProperty("tag", QVariant(2.0)); auto add200 = new QAction("Add 200% quality", this); add200->setProperty("tag", QVariant(2.0));
menu->addAction(add10); bumpmenu->addAction(add10);
menu->addAction(add25); bumpmenu->addAction(add25);
menu->addAction(add50); bumpmenu->addAction(add50);
menu->addAction(add100); bumpmenu->addAction(add100);
menu->addAction(add200); bumpmenu->addAction(add200);
ui.FinalRenderBumpQualityStartButton->setMenu(menu); ui.FinalRenderBumpQualityStartButton->setMenu(bumpmenu);
ui.FinalRenderBumpQualityStartButton->setProperty("tag", add25->property("tag")); ui.FinalRenderBumpQualityStartButton->setProperty("tag", add25->property("tag"));
ui.FinalRenderBumpQualityStartButton->setText(add25->text()); ui.FinalRenderBumpQualityStartButton->setText(add25->text());
ui.FinalRenderBumpQualityStartButton->setEnabled(false); ui.FinalRenderBumpQualityStartButton->setEnabled(false);
auto saamenu = new QMenu(this);
#ifdef _WIN32
auto saabmp = new QAction("bmp", this);
saamenu->addAction(saabmp);
connect(saabmp, SIGNAL(triggered()), this, SLOT(OnSaveAgainAsClicked()));
#endif
auto saajpg = new QAction("jpg", this);
auto saapng = new QAction("png", this);
auto saaexr = new QAction("exr", this);
saamenu->addAction(saajpg);
saamenu->addAction(saapng);
saamenu->addAction(saaexr);
ui.FinalRenderSaveAgainAsButton->setMenu(saamenu);
ui.FinalRenderSaveAgainAsButton->setProperty("tag", "jpg");
ui.FinalRenderSaveAgainAsButton->setText("Save again as jpg");
ui.FinalRenderSaveAgainAsButton->setEnabled(false);
connect(ui.FinalRenderSaveAgainAsButton, SIGNAL(clicked()), this, SLOT(OnSaveAgainAsClicked()));
connect(ui.FinalRenderBumpQualityStartButton, SIGNAL(clicked()), this, SLOT(OnQualityBumpClicked())); connect(ui.FinalRenderBumpQualityStartButton, SIGNAL(clicked()), this, SLOT(OnQualityBumpClicked()));
connect(saajpg, SIGNAL(triggered()), this, SLOT(OnSaveAgainAsClicked()));
connect(saapng, SIGNAL(triggered()), this, SLOT(OnSaveAgainAsClicked()));
connect(saaexr, SIGNAL(triggered()), this, SLOT(OnSaveAgainAsClicked()));
connect(add10, SIGNAL(triggered()), this, SLOT(OnQualityBumpClicked())); connect(add10, SIGNAL(triggered()), this, SLOT(OnQualityBumpClicked()));
connect(add25, SIGNAL(triggered()), this, SLOT(OnQualityBumpClicked())); connect(add25, SIGNAL(triggered()), this, SLOT(OnQualityBumpClicked()));
connect(add50, SIGNAL(triggered()), this, SLOT(OnQualityBumpClicked())); connect(add50, SIGNAL(triggered()), this, SLOT(OnQualityBumpClicked()));
@ -246,6 +266,7 @@ FractoriumFinalRenderDialog::FractoriumFinalRenderDialog(QWidget* p, Qt::WindowF
w = SetTabOrder(this, w, m_PrefixEdit); w = SetTabOrder(this, w, m_PrefixEdit);
w = SetTabOrder(this, w, m_SuffixEdit); w = SetTabOrder(this, w, m_SuffixEdit);
w = SetTabOrder(this, w, ui.FinalRenderTextOutput); w = SetTabOrder(this, w, ui.FinalRenderTextOutput);
w = SetTabOrder(this, w, ui.FinalRenderSaveAgainAsButton);
w = SetTabOrder(this, w, ui.FinalRenderBumpQualityStartButton); w = SetTabOrder(this, w, ui.FinalRenderBumpQualityStartButton);
w = SetTabOrder(this, w, ui.FinalRenderStartButton); w = SetTabOrder(this, w, ui.FinalRenderStartButton);
w = SetTabOrder(this, w, ui.FinalRenderPauseButton); w = SetTabOrder(this, w, ui.FinalRenderPauseButton);
@ -457,6 +478,7 @@ void FractoriumFinalRenderDialog::OnDoAllCheckBoxStateChanged(int state)
ui.FinalRenderDoSequenceCheckBox->setChecked(false); ui.FinalRenderDoSequenceCheckBox->setChecked(false);
ui.FinalRenderDoSequenceCheckBox->setEnabled(ui.FinalRenderDoAllCheckBox->isChecked()); ui.FinalRenderDoSequenceCheckBox->setEnabled(ui.FinalRenderDoAllCheckBox->isChecked());
ui.FinalRenderSaveAgainAsButton->setEnabled(false);
ui.FinalRenderBumpQualityStartButton->setEnabled(false); ui.FinalRenderBumpQualityStartButton->setEnabled(false);
} }
@ -499,6 +521,7 @@ void FractoriumFinalRenderDialog::OnApplyAllCheckBoxStateChanged(int state)
if (state && m_Controller.get()) if (state && m_Controller.get())
m_Controller->SyncGuiToEmbers(); m_Controller->SyncGuiToEmbers();
ui.FinalRenderSaveAgainAsButton->setEnabled(false);
ui.FinalRenderBumpQualityStartButton->setEnabled(false); ui.FinalRenderBumpQualityStartButton->setEnabled(false);
} }
@ -776,6 +799,34 @@ void FractoriumFinalRenderDialog::OnQualityBumpClicked()
} }
} }
/// <summary>
/// Save the last rendered image output again to a different file format.
/// Note this is only when rendering a single image with no strips.
/// </summary>
void FractoriumFinalRenderDialog::OnSaveAgainAsClicked()
{
auto act = qobject_cast<QAction*>(sender());
auto tbtn = qobject_cast<QToolButton*>(sender());
if (tbtn)
{
if (m_Controller.get())
{
auto s = tbtn->property("tag").toString();
m_Tbcw->m_Combo->blockSignals(true);
m_Tbcw->m_Combo->setCurrentText(s);
m_Tbcw->m_Combo->blockSignals(false);
auto filename = m_Controller->SaveCurrentAgain();
ui.FinalRenderTextOutput->append("\nSaved again as " + filename);
}
}
else if (act)
{
ui.FinalRenderSaveAgainAsButton->setText("Save again as " + act->text());
ui.FinalRenderSaveAgainAsButton->setProperty("tag", act->text());
}
}
/// <summary> /// <summary>
/// Start the render process. /// Start the render process.
/// </summary> /// </summary>
@ -998,6 +1049,7 @@ bool FractoriumFinalRenderDialog::SetMemory()
else else
ui.FinalRenderTextOutput->clear(); ui.FinalRenderTextOutput->clear();
ui.FinalRenderSaveAgainAsButton->setEnabled(false);
ui.FinalRenderBumpQualityStartButton->setEnabled(false); ui.FinalRenderBumpQualityStartButton->setEnabled(false);
return true; return true;
} }

View File

@ -112,6 +112,7 @@ public slots:
void OnPrefixChanged(const QString& s); void OnPrefixChanged(const QString& s);
void OnSuffixChanged(const QString& s); void OnSuffixChanged(const QString& s);
void OnQualityBumpClicked(); void OnQualityBumpClicked();
void OnSaveAgainAsClicked();
void OnRenderClicked(bool checked); void OnRenderClicked(bool checked);
void OnPauseClicked(bool checked); void OnPauseClicked(bool checked);
void OnCancelRenderClicked(bool checked); void OnCancelRenderClicked(bool checked);

View File

@ -241,7 +241,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Save each RGBA component as 16-bits when saving Png files.&lt;/p&gt;&lt;p&gt;This leads to greater color precision for use in high end rendering and display on HDR monitors, however it makes the file size larger.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Save each RGBA component as 16-bits when saving Png files.&lt;/p&gt;&lt;p&gt;This leads to greater color precision for use in high end rendering and display on HDR monitors, however it makes the file size larger.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Save 16-bit Png</string> <string>Save 16-bit Png/32-bit Exr</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1192,6 +1192,19 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item>
<widget class="QToolButton" name="FinalRenderSaveAgainAsButton">
<property name="text">
<string>Save Again As</string>
</property>
<property name="popupMode">
<enum>QToolButton::MenuButtonPopup</enum>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextOnly</enum>
</property>
</widget>
</item>
<item> <item>
<widget class="QToolButton" name="FinalRenderBumpQualityStartButton"> <widget class="QToolButton" name="FinalRenderBumpQualityStartButton">
<property name="text"> <property name="text">

View File

@ -266,7 +266,6 @@ FinalRenderEmberController<T>::FinalRenderEmberController(FractoriumFinalRenderD
m_Renderer->SetEmber(it); m_Renderer->SetEmber(it);
m_Renderer->PrepFinalAccumVector(m_FinalImage);//Must manually call this first because it could be erroneously made smaller due to strips if called inside Renderer::Run(). m_Renderer->PrepFinalAccumVector(m_FinalImage);//Must manually call this first because it could be erroneously made smaller due to strips if called inside Renderer::Run().
m_Stats.Clear(); m_Stats.Clear();
Memset(m_FinalImage);
m_RenderTimer.Tic();//Toc() is called in RenderComplete(). m_RenderTimer.Tic();//Toc() is called in RenderComplete().
StripsRender<T>(m_Renderer.get(), it, m_FinalImage, 0, m_GuiState.m_Strips, m_GuiState.m_YAxisUp, StripsRender<T>(m_Renderer.get(), it, m_FinalImage, 0, m_GuiState.m_Strips, m_GuiState.m_YAxisUp,
[&](size_t strip) { currentStripForProgress = strip; },//Pre strip. [&](size_t strip) { currentStripForProgress = strip; },//Pre strip.
@ -298,7 +297,6 @@ FinalRenderEmberController<T>::FinalRenderEmberController(FractoriumFinalRenderD
m_Renderer->SetEmber(*m_Ember, isBump ? eProcessAction::KEEP_ITERATING : eProcessAction::FULL_RENDER); m_Renderer->SetEmber(*m_Ember, isBump ? eProcessAction::KEEP_ITERATING : eProcessAction::FULL_RENDER);
m_Renderer->PrepFinalAccumVector(m_FinalImage);//Must manually call this first because it could be erroneously made smaller due to strips if called inside Renderer::Run(). m_Renderer->PrepFinalAccumVector(m_FinalImage);//Must manually call this first because it could be erroneously made smaller due to strips if called inside Renderer::Run().
m_Stats.Clear(); m_Stats.Clear();
Memset(m_FinalImage);
Output(ComposePath(QString::fromStdString(m_Ember->m_Name))); Output(ComposePath(QString::fromStdString(m_Ember->m_Name)));
m_RenderTimer.Tic();//Toc() is called in RenderComplete(). m_RenderTimer.Tic();//Toc() is called in RenderComplete().
StripsRender<T>(m_Renderer.get(), *m_Ember, m_FinalImage, 0, m_GuiState.m_Strips, m_GuiState.m_YAxisUp, StripsRender<T>(m_Renderer.get(), *m_Ember, m_FinalImage, 0, m_GuiState.m_Strips, m_GuiState.m_YAxisUp,
@ -744,15 +742,29 @@ EmberNs::Renderer<T, float>* FinalRenderEmberController<T>::FirstOrDefaultRender
} }
} }
/// <summary>
/// Save the output of the last rendered image using the existing image output buffer in the renderer.
/// </summary>
/// <returns>The full path and filename the image was saved to.</returns>
template<typename T>
QString FinalRenderEmberController<T>::SaveCurrentAgain()
{
if (m_Ember)
return SaveCurrentRender(*m_Ember);
else
return "";
}
/// <summary> /// <summary>
/// Save the output of the render. /// Save the output of the render.
/// </summary> /// </summary>
/// <param name="ember">The ember whose rendered output will be saved</param> /// <param name="ember">The ember whose rendered output will be saved</param>
/// <returns>The full path and filename the image was saved to.</returns>
template<typename T> template<typename T>
void FinalRenderEmberController<T>::SaveCurrentRender(Ember<T>& ember) QString FinalRenderEmberController<T>::SaveCurrentRender(Ember<T>& ember)
{ {
auto comments = m_Renderer->ImageComments(m_Stats, 0, true); auto comments = m_Renderer->ImageComments(m_Stats, 0, true);
SaveCurrentRender(ember, comments, m_FinalImage, m_Renderer->FinalRasW(), m_Renderer->FinalRasH(), m_FinalRenderDialog->Png16Bit(), m_FinalRenderDialog->Transparency()); return SaveCurrentRender(ember, comments, m_FinalImage, m_Renderer->FinalRasW(), m_Renderer->FinalRasH(), m_FinalRenderDialog->Png16Bit(), m_FinalRenderDialog->Transparency());
} }
/// <summary> /// <summary>
@ -763,13 +775,15 @@ void FinalRenderEmberController<T>::SaveCurrentRender(Ember<T>& ember)
/// <param name="pixels">The buffer containing the pixels</param> /// <param name="pixels">The buffer containing the pixels</param>
/// <param name="width">The width in pixels of the image</param> /// <param name="width">The width in pixels of the image</param>
/// <param name="height">The height in pixels of the image</param> /// <param name="height">The height in pixels of the image</param>
/// <param name="png16Bit">Whether to use 16 bits per channel per pixel when saving as Png.</param> /// <param name="png16Bit">Whether to use 16 bits per channel per pixel when saving as Png/32-bits per channel when saving as Exr.</param>
/// <param name="transparency">Whether to use alpha when saving as Png or Exr.</param> /// <param name="transparency">Whether to use alpha when saving as Png or Exr.</param>
/// <returns>The full path and filename the image was saved to.</returns>
template<typename T> template<typename T>
void FinalRenderEmberController<T>::SaveCurrentRender(Ember<T>& ember, const EmberImageComments& comments, vector<v4F>& pixels, size_t width, size_t height, bool png16Bit, bool transparency) QString FinalRenderEmberController<T>::SaveCurrentRender(Ember<T>& ember, const EmberImageComments& comments, vector<v4F>& pixels, size_t width, size_t height, bool png16Bit, bool transparency)
{ {
QString filename = ComposePath(QString::fromStdString(ember.m_Name)); QString filename = ComposePath(QString::fromStdString(ember.m_Name));
FractoriumEmberControllerBase::SaveCurrentRender(filename, comments, pixels, width, height, png16Bit, transparency); FractoriumEmberControllerBase::SaveCurrentRender(filename, comments, pixels, width, height, png16Bit, transparency);
return filename;
} }
/// <summary> /// <summary>
@ -842,6 +856,7 @@ void FinalRenderEmberController<T>::HandleFinishedProgress()
QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderTotalProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, int((float(finishedCountCached) / float(m_ImageCount)) * 100))); QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderTotalProgress, "setValue", Qt::QueuedConnection, Q_ARG(int, int((float(finishedCountCached) / float(m_ImageCount)) * 100)));
QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderImageCountLabel, "setText", Qt::QueuedConnection, Q_ARG(const QString&, ToString<qulonglong>(finishedCountCached) + " / " + ToString<qulonglong>(m_ImageCount))); QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderImageCountLabel, "setText", Qt::QueuedConnection, Q_ARG(const QString&, ToString<qulonglong>(finishedCountCached) + " / " + ToString<qulonglong>(m_ImageCount)));
QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderSaveAgainAsButton, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, !doAll && m_Renderer.get() && m_GuiState.m_Strips == 1));
QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderBumpQualityStartButton, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, !doAll && m_Renderer.get() && m_GuiState.m_Strips == 1)); QMetaObject::invokeMethod(m_FinalRenderDialog->ui.FinalRenderBumpQualityStartButton, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, !doAll && m_Renderer.get() && m_GuiState.m_Strips == 1));
} }

View File

@ -70,6 +70,7 @@ public:
virtual double OriginalAspect() { return 1; } virtual double OriginalAspect() { return 1; }
virtual QString ComposePath(const QString& name) { return ""; } virtual QString ComposePath(const QString& name) { return ""; }
virtual bool BumpQualityRender(double d) { return false; } virtual bool BumpQualityRender(double d) { return false; }
virtual QString SaveCurrentAgain() { return ""; }
virtual void CancelRender() { } virtual void CancelRender() { }
virtual QString CheckMemory(const tuple<size_t, size_t, size_t>& p) { return ""; } virtual QString CheckMemory(const tuple<size_t, size_t, size_t>& p) { return ""; }
@ -131,6 +132,7 @@ public:
virtual double OriginalAspect() override { return double(m_Ember->m_OrigFinalRasW) / m_Ember->m_OrigFinalRasH; } virtual double OriginalAspect() override { return double(m_Ember->m_OrigFinalRasW) / m_Ember->m_OrigFinalRasH; }
virtual QString Name() const override { return QString::fromStdString(m_Ember->m_Name); } virtual QString Name() const override { return QString::fromStdString(m_Ember->m_Name); }
virtual QString ComposePath(const QString& name) override; virtual QString ComposePath(const QString& name) override;
virtual QString SaveCurrentAgain() override;
virtual void CancelRender() override; virtual void CancelRender() override;
virtual QString CheckMemory(const tuple<size_t, size_t, size_t>& p) override; virtual QString CheckMemory(const tuple<size_t, size_t, size_t>& p) override;
@ -141,8 +143,8 @@ protected:
virtual void Pause(bool pause) override; virtual void Pause(bool pause) override;
virtual bool Paused() override; virtual bool Paused() override;
void HandleFinishedProgress(); void HandleFinishedProgress();
void SaveCurrentRender(Ember<T>& ember); QString SaveCurrentRender(Ember<T>& ember);
void SaveCurrentRender(Ember<T>& ember, const EmberImageComments& comments, vector<v4F>& pixels, size_t width, size_t height, bool png16Bit, bool transparency); QString SaveCurrentRender(Ember<T>& ember, const EmberImageComments& comments, vector<v4F>& pixels, size_t width, size_t height, bool png16Bit, bool transparency);
void RenderComplete(Ember<T>& ember); void RenderComplete(Ember<T>& ember);
void RenderComplete(Ember<T>& ember, const EmberStats& stats, Timing& renderTimer); void RenderComplete(Ember<T>& ember, const EmberStats& stats, Timing& renderTimer);
void SyncGuiToEmber(Ember<T>& ember, size_t widthOverride = 0, size_t heightOverride = 0, bool dowidth = true, bool doheight = true); void SyncGuiToEmber(Ember<T>& ember, size_t widthOverride = 0, size_t heightOverride = 0, bool dowidth = true, bool doheight = true);

View File

@ -118,13 +118,13 @@ void FractoriumEmberController<T>::DeleteRenderer()
/// <param name="pixels">The buffer containing the pixels</param> /// <param name="pixels">The buffer containing the pixels</param>
/// <param name="width">The width in pixels of the image</param> /// <param name="width">The width in pixels of the image</param>
/// <param name="height">The height in pixels of the image</param> /// <param name="height">The height in pixels of the image</param>
/// <param name="png16Bit">Whether to use 16 bits per channel per pixel when saving as Png.</param> /// <param name="png16Bit">Whether to use 16 bits per channel per pixel when saving as Png/32-bits per channel when saving as Exr.</param>
/// <param name="transparency">Whether to use alpha when saving as Png or Exr.</param> /// <param name="transparency">Whether to use alpha when saving as Png or Exr.</param>
void FractoriumEmberControllerBase::SaveCurrentRender(const QString& filename, const EmberImageComments& comments, vector<v4F>& pixels, size_t width, size_t height, bool png16Bit, bool transparency) void FractoriumEmberControllerBase::SaveCurrentRender(const QString& filename, const EmberImageComments& comments, vector<v4F>& pixels, size_t width, size_t height, bool png16Bit, bool transparency)
{ {
if (filename != "") if (filename != "")
{ {
bool b = false; bool ret = false;
auto size = width * height; auto size = width * height;
auto settings = m_Fractorium->m_Settings; auto settings = m_Fractorium->m_Settings;
QFileInfo fileInfo(filename); QFileInfo fileInfo(filename);
@ -149,9 +149,9 @@ void FractoriumEmberControllerBase::SaveCurrentRender(const QString& filename, c
Rgba32ToRgb8(data, rgb8Image.data(), width, height); Rgba32ToRgb8(data, rgb8Image.data(), width, height);
if (suffix.endsWith("bmp", Qt::CaseInsensitive)) if (suffix.endsWith("bmp", Qt::CaseInsensitive))
b = WriteBmp(s.c_str(), rgb8Image.data(), width, height); ret = WriteBmp(s.c_str(), rgb8Image.data(), width, height);
else if (suffix.endsWith("jpg", Qt::CaseInsensitive)) else if (suffix.endsWith("jpg", Qt::CaseInsensitive))
b = WriteJpeg(s.c_str(), rgb8Image.data(), width, height, 100, true, comments, id, url, nick); ret = WriteJpeg(s.c_str(), rgb8Image.data(), width, height, 100, true, comments, id, url, nick);
} }
else if (suffix.endsWith("png", Qt::CaseInsensitive)) else if (suffix.endsWith("png", Qt::CaseInsensitive))
{ {
@ -159,20 +159,32 @@ void FractoriumEmberControllerBase::SaveCurrentRender(const QString& filename, c
{ {
vector<byte> rgba8Image(size * 4); vector<byte> rgba8Image(size * 4);
Rgba32ToRgba8(data, rgba8Image.data(), width, height, transparency); Rgba32ToRgba8(data, rgba8Image.data(), width, height, transparency);
b = WritePng(s.c_str(), rgba8Image.data(), width, height, 1, true, comments, id, url, nick); ret = WritePng(s.c_str(), rgba8Image.data(), width, height, 1, true, comments, id, url, nick);
} }
else else
{ {
vector<glm::uint16> rgba16Image(size * 4); vector<glm::uint16> rgba16Image(size * 4);
Rgba32ToRgba16(data, rgba16Image.data(), width, height, transparency); Rgba32ToRgba16(data, rgba16Image.data(), width, height, transparency);
b = WritePng(s.c_str(), (byte*)rgba16Image.data(), width, height, 2, true, comments, id, url, nick); ret = WritePng(s.c_str(), (byte*)rgba16Image.data(), width, height, 2, true, comments, id, url, nick);
} }
} }
else if (suffix.endsWith("exr", Qt::CaseInsensitive)) else if (suffix.endsWith("exr", Qt::CaseInsensitive))
{ {
vector<Rgba> rgba32Image(size); if (!png16Bit)//Repurpose this for EXR 32-bit.
Rgba32ToRgbaExr(data, rgba32Image.data(), width, height, transparency); {
b = WriteExr(s.c_str(), rgba32Image.data(), width, height, true, comments, id, url, nick); vector<Rgba> rgba32Image(size);
Rgba32ToRgbaExr(data, rgba32Image.data(), width, height, transparency);
ret = WriteExr16(s.c_str(), rgba32Image.data(), width, height, true, comments, id, url, nick);
}
else
{
vector<float> r(size);
vector<float> g(size);
vector<float> b(size);
vector<float> a(size);
Rgba32ToRgba32Exr(data, r.data(), g.data(), b.data(), a.data(), width, height, transparency);
ret = WriteExr32(s.c_str(), r.data(), g.data(), b.data(), a.data(), width, height, true, comments, id, url, nick);
}
} }
else else
{ {
@ -180,7 +192,7 @@ void FractoriumEmberControllerBase::SaveCurrentRender(const QString& filename, c
return; return;
} }
if (b) if (ret)
settings->SaveFolder(fileInfo.canonicalPath()); settings->SaveFolder(fileInfo.canonicalPath());
else else
m_Fractorium->ShowCritical("Save Failed", "Could not save file, try saving to a different folder.", true); m_Fractorium->ShowCritical("Save Failed", "Could not save file, try saving to a different folder.", true);

View File

@ -524,7 +524,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Save each RGBA component as 16-bits when saving Png files.&lt;/p&gt;&lt;p&gt;This leads to greater color precision for use in high end rendering and display on HDR monitors, however it makes the file size larger.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Save each RGBA component as 16-bits when saving Png files.&lt;/p&gt;&lt;p&gt;This leads to greater color precision for use in high end rendering and display on HDR monitors, however it makes the file size larger.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Save 16-bit Png</string> <string>Save 16-bit Png/32-bit Exr</string>
</property> </property>
</widget> </widget>
</item> </item>