diff --git a/Source/EmberAnimate/EmberAnimate.cpp b/Source/EmberAnimate/EmberAnimate.cpp index 95acfd9..999d15b 100644 --- a/Source/EmberAnimate/EmberAnimate.cpp +++ b/Source/EmberAnimate/EmberAnimate.cpp @@ -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 rgb8Image; vector 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 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 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 r(size); + vector g(size); + vector b(size); + vector 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); diff --git a/Source/EmberCommon/EmberCommon.h b/Source/EmberCommon/EmberCommon.h index d986099..6cd7e9e 100644 --- a/Source/EmberCommon/EmberCommon.h +++ b/Source/EmberCommon/EmberCommon.h @@ -263,12 +263,12 @@ static void Rgba32ToRgba16(v4F* rgba, glm::uint16* rgb, size_t width, size_t hei } /// -/// 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. /// /// The RGBA 32-bit float buffer -/// The EXR RGBA 32-bit float buffer +/// The EXR RGBA 16-bit float buffer /// The width of the image in pixels /// The height of the image in pixels /// True to use alpha transparency, false to assign the max alpha value to make each pixel fully visible @@ -283,6 +283,30 @@ static void Rgba32ToRgbaExr(v4F* rgba, Rgba* ilmfRgba, size_t width, size_t heig } } +/// +/// 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. +/// +/// The RGBA 32-bit float buffer +/// The EXR red 32-bit float buffer +/// The EXR green 32-bit float buffer +/// The EXR blue 32-bit float buffer +/// The EXR alpha 32-bit float buffer +/// The width of the image in pixels +/// The height of the image in pixels +/// True to use alpha transparency, false to assign the max alpha value to make each pixel fully visible +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(Sqr(rgba[i].r), 0.0f, 1.0f); + g[i] = Clamp(Sqr(rgba[i].g), 0.0f, 1.0f); + b[i] = Clamp(Sqr(rgba[i].b), 0.0f, 1.0f); + a[i] = doAlpha ? Clamp(rgba[i].a * 1.0f, 0.0f, 1.0f) : 1.0f; + } +} + /// /// Make a filename for a single render. This is used in EmberRender. /// @@ -599,6 +623,7 @@ static bool StripsRender(RendererBase* renderer, Ember& ember, vector& f vector> 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& ember, vector& f if (success) allStripsFinished(ember); - Memset(finalImage); return success; } diff --git a/Source/EmberCommon/EmberCommonPch.h b/Source/EmberCommon/EmberCommonPch.h index bf24e70..0517231 100644 --- a/Source/EmberCommon/EmberCommonPch.h +++ b/Source/EmberCommon/EmberCommonPch.h @@ -67,11 +67,15 @@ #include #include #include - #define ENUM_DYLD_BOOL - #include + #include + #include + #define ENUM_DYLD_BOOL + #include #else #include #include + #include + #include #include #endif diff --git a/Source/EmberCommon/JpegUtils.h b/Source/EmberCommon/JpegUtils.h index 78f6d84..b69ff9f 100644 --- a/Source/EmberCommon/JpegUtils.h +++ b/Source/EmberCommon/JpegUtils.h @@ -364,9 +364,9 @@ static bool WriteBmp(const char* filename, byte* image, size_t width, size_t hei } /// -/// 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. /// /// The full path and name of the file /// Pointer to the image data to write @@ -378,7 +378,7 @@ static bool WriteBmp(const char* filename, byte* image, size_t width, size_t hei /// Url of the author /// Nickname of the author /// True if success, else false -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; } } + +/// +/// 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. +/// +/// The full path and name of the file +/// Pointer to the red image data to write +/// Pointer to the green image data to write +/// Pointer to the blue image data to write +/// Pointer to the alpha image data to write +/// Width of the image in pixels +/// Height of the image in pixels +/// True to embed comments, else false +/// The comment string to embed +/// Id of the author +/// Url of the author +/// Nickname of the author +/// True if success, else false +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 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(filename, header); + } + catch (std::exception) + { + return false; + } + + if (enableComments) + { + auto& header = const_cast(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; + } +} diff --git a/Source/EmberRender/EmberRender.cpp b/Source/EmberRender/EmberRender.cpp index 5233de7..882f51d 100644 --- a/Source/EmberRender/EmberRender.cpp +++ b/Source/EmberRender/EmberRender.cpp @@ -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 rgb8Image; vector 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 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 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 r(size); + vector g(size); + vector b(size); + vector 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); diff --git a/Source/Fractorium/FinalRenderDialog.cpp b/Source/Fractorium/FinalRenderDialog.cpp index 145e074..2c86626 100644 --- a/Source/Fractorium/FinalRenderDialog.cpp +++ b/Source/Fractorium/FinalRenderDialog.cpp @@ -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() } } +/// +/// Save the last rendered image output again to a different file format. +/// Note this is only when rendering a single image with no strips. +/// +void FractoriumFinalRenderDialog::OnSaveAgainAsClicked() +{ + auto act = qobject_cast(sender()); + auto tbtn = qobject_cast(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()); + } +} + /// /// Start the render process. /// @@ -998,6 +1049,7 @@ bool FractoriumFinalRenderDialog::SetMemory() else ui.FinalRenderTextOutput->clear(); + ui.FinalRenderSaveAgainAsButton->setEnabled(false); ui.FinalRenderBumpQualityStartButton->setEnabled(false); return true; } diff --git a/Source/Fractorium/FinalRenderDialog.h b/Source/Fractorium/FinalRenderDialog.h index 79a73b4..9aac02e 100644 --- a/Source/Fractorium/FinalRenderDialog.h +++ b/Source/Fractorium/FinalRenderDialog.h @@ -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); diff --git a/Source/Fractorium/FinalRenderDialog.ui b/Source/Fractorium/FinalRenderDialog.ui index 727e4dd..135920e 100644 --- a/Source/Fractorium/FinalRenderDialog.ui +++ b/Source/Fractorium/FinalRenderDialog.ui @@ -241,7 +241,7 @@ <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> - Save 16-bit Png + Save 16-bit Png/32-bit Exr @@ -1192,6 +1192,19 @@ + + + + Save Again As + + + QToolButton::MenuButtonPopup + + + Qt::ToolButtonTextOnly + + + diff --git a/Source/Fractorium/FinalRenderEmberController.cpp b/Source/Fractorium/FinalRenderEmberController.cpp index f617221..202a731 100644 --- a/Source/Fractorium/FinalRenderEmberController.cpp +++ b/Source/Fractorium/FinalRenderEmberController.cpp @@ -266,7 +266,6 @@ FinalRenderEmberController::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(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::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(m_Renderer.get(), *m_Ember, m_FinalImage, 0, m_GuiState.m_Strips, m_GuiState.m_YAxisUp, @@ -744,15 +742,29 @@ EmberNs::Renderer* FinalRenderEmberController::FirstOrDefaultRender } } +/// +/// Save the output of the last rendered image using the existing image output buffer in the renderer. +/// +/// The full path and filename the image was saved to. +template +QString FinalRenderEmberController::SaveCurrentAgain() +{ + if (m_Ember) + return SaveCurrentRender(*m_Ember); + else + return ""; +} + /// /// Save the output of the render. /// /// The ember whose rendered output will be saved +/// The full path and filename the image was saved to. template -void FinalRenderEmberController::SaveCurrentRender(Ember& ember) +QString FinalRenderEmberController::SaveCurrentRender(Ember& 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()); } /// @@ -763,13 +775,15 @@ void FinalRenderEmberController::SaveCurrentRender(Ember& ember) /// The buffer containing the pixels /// The width in pixels of the image /// The height in pixels of the image -/// Whether to use 16 bits per channel per pixel when saving as Png. +/// Whether to use 16 bits per channel per pixel when saving as Png/32-bits per channel when saving as Exr. /// Whether to use alpha when saving as Png or Exr. +/// The full path and filename the image was saved to. template -void FinalRenderEmberController::SaveCurrentRender(Ember& ember, const EmberImageComments& comments, vector& pixels, size_t width, size_t height, bool png16Bit, bool transparency) +QString FinalRenderEmberController::SaveCurrentRender(Ember& ember, const EmberImageComments& comments, vector& 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; } /// @@ -842,6 +856,7 @@ void FinalRenderEmberController::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(finishedCountCached) + " / " + ToString(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)); } diff --git a/Source/Fractorium/FinalRenderEmberController.h b/Source/Fractorium/FinalRenderEmberController.h index 7fde3ae..f17b3dd 100644 --- a/Source/Fractorium/FinalRenderEmberController.h +++ b/Source/Fractorium/FinalRenderEmberController.h @@ -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& 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& p) override; @@ -141,8 +143,8 @@ protected: virtual void Pause(bool pause) override; virtual bool Paused() override; void HandleFinishedProgress(); - void SaveCurrentRender(Ember& ember); - void SaveCurrentRender(Ember& ember, const EmberImageComments& comments, vector& pixels, size_t width, size_t height, bool png16Bit, bool transparency); + QString SaveCurrentRender(Ember& ember); + QString SaveCurrentRender(Ember& ember, const EmberImageComments& comments, vector& pixels, size_t width, size_t height, bool png16Bit, bool transparency); void RenderComplete(Ember& ember); void RenderComplete(Ember& ember, const EmberStats& stats, Timing& renderTimer); void SyncGuiToEmber(Ember& ember, size_t widthOverride = 0, size_t heightOverride = 0, bool dowidth = true, bool doheight = true); diff --git a/Source/Fractorium/FractoriumRender.cpp b/Source/Fractorium/FractoriumRender.cpp index eb01881..c090e89 100644 --- a/Source/Fractorium/FractoriumRender.cpp +++ b/Source/Fractorium/FractoriumRender.cpp @@ -118,13 +118,13 @@ void FractoriumEmberController::DeleteRenderer() /// The buffer containing the pixels /// The width in pixels of the image /// The height in pixels of the image -/// Whether to use 16 bits per channel per pixel when saving as Png. +/// Whether to use 16 bits per channel per pixel when saving as Png/32-bits per channel when saving as Exr. /// Whether to use alpha when saving as Png or Exr. void FractoriumEmberControllerBase::SaveCurrentRender(const QString& filename, const EmberImageComments& comments, vector& 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 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 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 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 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 r(size); + vector g(size); + vector b(size); + vector 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); diff --git a/Source/Fractorium/OptionsDialog.ui b/Source/Fractorium/OptionsDialog.ui index 216c071..d69a2c3 100644 --- a/Source/Fractorium/OptionsDialog.ui +++ b/Source/Fractorium/OptionsDialog.ui @@ -524,7 +524,7 @@ <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> - Save 16-bit Png + Save 16-bit Png/32-bit Exr