mirror of
https://bitbucket.org/mfeemster/fractorium.git
synced 2025-01-21 21:20:07 -05:00
--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:
parent
59605f10a8
commit
35d4eb3464
@ -326,10 +326,12 @@ bool EmberAnimate(int argc, _TCHAR* argv[], EmberOptions& opt)
|
||||
auto size = w * h;
|
||||
bool doBmp = Find(opt.Format(), "bmp");
|
||||
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 doPng16 = Find(opt.Format(), "png16");
|
||||
bool doOnlyPng8 = doPng8 && !doPng16;
|
||||
bool doOnlyExr16 = doExr16 && !doExr32;
|
||||
vector<byte> rgb8Image;
|
||||
vector<std::thread> writeFileThreads;
|
||||
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"));
|
||||
|
||||
if (doBothPng || doOnlyPng8)//8-bit PNG
|
||||
if (doBothPng || doOnlyPng8)//8-bit PNG.
|
||||
{
|
||||
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([&]()
|
||||
{
|
||||
@ -410,19 +412,56 @@ bool EmberAnimate(int argc, _TCHAR* argv[], EmberOptions& opt)
|
||||
}
|
||||
}
|
||||
|
||||
if (doExr)
|
||||
if (doExr16)
|
||||
{
|
||||
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 = WriteExr(fn.c_str(), rgba32Image.data(), w, h, opt.EnableComments(), comments, opt.Id(), opt.Url(), opt.Nick());
|
||||
bool doBothExr = doExr32 && (opt.Format().find("exr") != opt.Format().rfind("exr"));
|
||||
|
||||
if (!writeSuccess)
|
||||
cout << "Error writing " << fn << "\n";
|
||||
}));
|
||||
if (doBothExr || doOnlyExr16)//16-bit EXR
|
||||
{
|
||||
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);
|
||||
|
@ -263,12 +263,12 @@ static void Rgba32ToRgba16(v4F* rgba, glm::uint16* rgb, size_t width, size_t hei
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// 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="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="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>
|
||||
@ -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>
|
||||
/// Make a filename for a single render. This is used in EmberRender.
|
||||
/// </summary>
|
||||
@ -599,6 +623,7 @@ static bool StripsRender(RendererBase* renderer, Ember<T>& ember, vector<v4F>& f
|
||||
vector<QTIsaac<ISAAC_SIZE, ISAAC_INT>> randVec;
|
||||
ember.m_Quality *= strips;
|
||||
ember.m_FinalRasH = size_t(ceil(floatStripH));
|
||||
Memset(finalImage);
|
||||
|
||||
if (strips > 1)
|
||||
randVec = renderer->RandVec();
|
||||
@ -654,7 +679,6 @@ static bool StripsRender(RendererBase* renderer, Ember<T>& ember, vector<v4F>& f
|
||||
if (success)
|
||||
allStripsFinished(ember);
|
||||
|
||||
Memset(finalImage);
|
||||
return success;
|
||||
}
|
||||
|
||||
|
@ -67,11 +67,15 @@
|
||||
#include <OpenEXR/ImfRgbaFile.h>
|
||||
#include <OpenEXR/ImfStringAttribute.h>
|
||||
#include <OpenEXR/half.h>
|
||||
#define ENUM_DYLD_BOOL
|
||||
#include <mach-o/dyld.h>
|
||||
#include <OpenEXR/ImfChannelList.h>
|
||||
#include <OpenEXR/ImfOutputFile.h>
|
||||
#define ENUM_DYLD_BOOL
|
||||
#include <mach-o/dyld.h>
|
||||
#else
|
||||
#include <ImfRgbaFile.h>
|
||||
#include <ImfStringAttribute.h>
|
||||
#include <ImfChannelList.h>
|
||||
#include <ImfOutputFile.h>
|
||||
#include <half.h>
|
||||
#endif
|
||||
|
||||
|
@ -364,9 +364,9 @@ static bool WriteBmp(const char* filename, byte* image, size_t width, size_t hei
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write an EXR file.
|
||||
/// This is used for extreme color precision because it uses
|
||||
/// floats for each color channel.
|
||||
/// Write an EXR file which will use the 16 bit half float format for each pixel channel.
|
||||
/// This is used for high color precision because it uses
|
||||
/// HALFS 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>
|
||||
@ -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="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)
|
||||
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
|
||||
{
|
||||
@ -419,3 +419,89 @@ static bool WriteExr(const char* filename, Rgba* image, size_t width, size_t hei
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -291,10 +291,12 @@ bool EmberRender(int argc, _TCHAR* argv[], EmberOptions& opt)
|
||||
auto size = finalEmber.m_FinalRasW * finalEmber.m_FinalRasH;
|
||||
bool doBmp = Find(opt.Format(), "bmp");
|
||||
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 doPng16 = Find(opt.Format(), "png16");
|
||||
bool doOnlyPng8 = doPng8 && !doPng16;
|
||||
bool doOnlyExr16 = doExr16 && !doExr32;
|
||||
vector<byte> rgb8Image;
|
||||
vector<std::thread> writeFileThreads;
|
||||
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"));
|
||||
|
||||
if (doBothPng || doOnlyPng8)//8-bit PNG
|
||||
if (doBothPng || doOnlyPng8)//8-bit PNG.
|
||||
{
|
||||
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([&]()
|
||||
{
|
||||
@ -374,19 +376,57 @@ bool EmberRender(int argc, _TCHAR* argv[], EmberOptions& opt)
|
||||
}
|
||||
}
|
||||
|
||||
if (doExr)
|
||||
if (doExr16)
|
||||
{
|
||||
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 = WriteExr(filename.c_str(), rgba32Image.data(), finalEmber.m_FinalRasW, finalEmber.m_FinalRasH, opt.EnableComments(), comments, opt.Id(), opt.Url(), opt.Nick());
|
||||
bool doBothExr = doExr32 && (opt.Format().find("exr") != opt.Format().rfind("exr"));
|
||||
|
||||
if (!writeSuccess)
|
||||
cout << "Error writing " << filename << "\n";
|
||||
}));
|
||||
if (doBothExr || doOnlyExr16)//16-bit EXR.
|
||||
{
|
||||
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);
|
||||
|
@ -163,22 +163,42 @@ FractoriumFinalRenderDialog::FractoriumFinalRenderDialog(QWidget* p, Qt::WindowF
|
||||
m_SupersampleSpin->setValue(m_Settings->FinalSupersample());
|
||||
m_StripsSpin->setValue(int(m_Settings->FinalStrips()));
|
||||
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 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 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));
|
||||
menu->addAction(add10);
|
||||
menu->addAction(add25);
|
||||
menu->addAction(add50);
|
||||
menu->addAction(add100);
|
||||
menu->addAction(add200);
|
||||
ui.FinalRenderBumpQualityStartButton->setMenu(menu);
|
||||
bumpmenu->addAction(add10);
|
||||
bumpmenu->addAction(add25);
|
||||
bumpmenu->addAction(add50);
|
||||
bumpmenu->addAction(add100);
|
||||
bumpmenu->addAction(add200);
|
||||
ui.FinalRenderBumpQualityStartButton->setMenu(bumpmenu);
|
||||
ui.FinalRenderBumpQualityStartButton->setProperty("tag", add25->property("tag"));
|
||||
ui.FinalRenderBumpQualityStartButton->setText(add25->text());
|
||||
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(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(add25, 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_SuffixEdit);
|
||||
w = SetTabOrder(this, w, ui.FinalRenderTextOutput);
|
||||
w = SetTabOrder(this, w, ui.FinalRenderSaveAgainAsButton);
|
||||
w = SetTabOrder(this, w, ui.FinalRenderBumpQualityStartButton);
|
||||
w = SetTabOrder(this, w, ui.FinalRenderStartButton);
|
||||
w = SetTabOrder(this, w, ui.FinalRenderPauseButton);
|
||||
@ -457,6 +478,7 @@ void FractoriumFinalRenderDialog::OnDoAllCheckBoxStateChanged(int state)
|
||||
ui.FinalRenderDoSequenceCheckBox->setChecked(false);
|
||||
|
||||
ui.FinalRenderDoSequenceCheckBox->setEnabled(ui.FinalRenderDoAllCheckBox->isChecked());
|
||||
ui.FinalRenderSaveAgainAsButton->setEnabled(false);
|
||||
ui.FinalRenderBumpQualityStartButton->setEnabled(false);
|
||||
}
|
||||
|
||||
@ -499,6 +521,7 @@ void FractoriumFinalRenderDialog::OnApplyAllCheckBoxStateChanged(int state)
|
||||
if (state && m_Controller.get())
|
||||
m_Controller->SyncGuiToEmbers();
|
||||
|
||||
ui.FinalRenderSaveAgainAsButton->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>
|
||||
/// Start the render process.
|
||||
/// </summary>
|
||||
@ -998,6 +1049,7 @@ bool FractoriumFinalRenderDialog::SetMemory()
|
||||
else
|
||||
ui.FinalRenderTextOutput->clear();
|
||||
|
||||
ui.FinalRenderSaveAgainAsButton->setEnabled(false);
|
||||
ui.FinalRenderBumpQualityStartButton->setEnabled(false);
|
||||
return true;
|
||||
}
|
||||
|
@ -112,6 +112,7 @@ public slots:
|
||||
void OnPrefixChanged(const QString& s);
|
||||
void OnSuffixChanged(const QString& s);
|
||||
void OnQualityBumpClicked();
|
||||
void OnSaveAgainAsClicked();
|
||||
void OnRenderClicked(bool checked);
|
||||
void OnPauseClicked(bool checked);
|
||||
void OnCancelRenderClicked(bool checked);
|
||||
|
@ -241,7 +241,7 @@
|
||||
<string><html><head/><body><p>Save each RGBA component as 16-bits when saving Png files.</p><p>This leads to greater color precision for use in high end rendering and display on HDR monitors, however it makes the file size larger.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save 16-bit Png</string>
|
||||
<string>Save 16-bit Png/32-bit Exr</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -1192,6 +1192,19 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</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>
|
||||
<widget class="QToolButton" name="FinalRenderBumpQualityStartButton">
|
||||
<property name="text">
|
||||
|
@ -266,7 +266,6 @@ FinalRenderEmberController<T>::FinalRenderEmberController(FractoriumFinalRenderD
|
||||
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_Stats.Clear();
|
||||
Memset(m_FinalImage);
|
||||
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,
|
||||
[&](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->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();
|
||||
Memset(m_FinalImage);
|
||||
Output(ComposePath(QString::fromStdString(m_Ember->m_Name)));
|
||||
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,
|
||||
@ -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>
|
||||
/// Save the output of the render.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
void FinalRenderEmberController<T>::SaveCurrentRender(Ember<T>& ember)
|
||||
QString FinalRenderEmberController<T>::SaveCurrentRender(Ember<T>& ember)
|
||||
{
|
||||
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>
|
||||
@ -763,13 +775,15 @@ void FinalRenderEmberController<T>::SaveCurrentRender(Ember<T>& ember)
|
||||
/// <param name="pixels">The buffer containing the pixels</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="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>
|
||||
/// <returns>The full path and filename the image was saved to.</returns>
|
||||
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));
|
||||
FractoriumEmberControllerBase::SaveCurrentRender(filename, comments, pixels, width, height, png16Bit, transparency);
|
||||
return filename;
|
||||
}
|
||||
|
||||
/// <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.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));
|
||||
}
|
||||
|
||||
|
@ -70,6 +70,7 @@ public:
|
||||
virtual double OriginalAspect() { return 1; }
|
||||
virtual QString ComposePath(const QString& name) { return ""; }
|
||||
virtual bool BumpQualityRender(double d) { return false; }
|
||||
virtual QString SaveCurrentAgain() { return ""; }
|
||||
virtual void CancelRender() { }
|
||||
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 QString Name() const override { return QString::fromStdString(m_Ember->m_Name); }
|
||||
virtual QString ComposePath(const QString& name) override;
|
||||
virtual QString SaveCurrentAgain() override;
|
||||
virtual void CancelRender() 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 bool Paused() override;
|
||||
void HandleFinishedProgress();
|
||||
void 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);
|
||||
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, const EmberStats& stats, Timing& renderTimer);
|
||||
void SyncGuiToEmber(Ember<T>& ember, size_t widthOverride = 0, size_t heightOverride = 0, bool dowidth = true, bool doheight = true);
|
||||
|
@ -118,13 +118,13 @@ void FractoriumEmberController<T>::DeleteRenderer()
|
||||
/// <param name="pixels">The buffer containing the pixels</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="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>
|
||||
void FractoriumEmberControllerBase::SaveCurrentRender(const QString& filename, const EmberImageComments& comments, vector<v4F>& pixels, size_t width, size_t height, bool png16Bit, bool transparency)
|
||||
{
|
||||
if (filename != "")
|
||||
{
|
||||
bool b = false;
|
||||
bool ret = false;
|
||||
auto size = width * height;
|
||||
auto settings = m_Fractorium->m_Settings;
|
||||
QFileInfo fileInfo(filename);
|
||||
@ -149,9 +149,9 @@ void FractoriumEmberControllerBase::SaveCurrentRender(const QString& filename, c
|
||||
Rgba32ToRgb8(data, rgb8Image.data(), width, height);
|
||||
|
||||
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))
|
||||
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))
|
||||
{
|
||||
@ -159,20 +159,32 @@ void FractoriumEmberControllerBase::SaveCurrentRender(const QString& filename, c
|
||||
{
|
||||
vector<byte> rgba8Image(size * 4);
|
||||
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
|
||||
{
|
||||
vector<glm::uint16> rgba16Image(size * 4);
|
||||
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))
|
||||
{
|
||||
vector<Rgba> rgba32Image(size);
|
||||
Rgba32ToRgbaExr(data, rgba32Image.data(), width, height, transparency);
|
||||
b = WriteExr(s.c_str(), rgba32Image.data(), width, height, true, comments, id, url, nick);
|
||||
if (!png16Bit)//Repurpose this for EXR 32-bit.
|
||||
{
|
||||
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
|
||||
{
|
||||
@ -180,7 +192,7 @@ void FractoriumEmberControllerBase::SaveCurrentRender(const QString& filename, c
|
||||
return;
|
||||
}
|
||||
|
||||
if (b)
|
||||
if (ret)
|
||||
settings->SaveFolder(fileInfo.canonicalPath());
|
||||
else
|
||||
m_Fractorium->ShowCritical("Save Failed", "Could not save file, try saving to a different folder.", true);
|
||||
|
@ -524,7 +524,7 @@
|
||||
<string><html><head/><body><p>Save each RGBA component as 16-bits when saving Png files.</p><p>This leads to greater color precision for use in high end rendering and display on HDR monitors, however it makes the file size larger.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save 16-bit Png</string>
|
||||
<string>Save 16-bit Png/32-bit Exr</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
Loading…
Reference in New Issue
Block a user