diff --git a/Builds/MSVC/Installer/Product.wxs b/Builds/MSVC/Installer/Product.wxs index 184aa10..457244e 100644 --- a/Builds/MSVC/Installer/Product.wxs +++ b/Builds/MSVC/Installer/Product.wxs @@ -1,6 +1,6 @@ - + diff --git a/Builds/MSVC/VS2013/Ember.vcxproj b/Builds/MSVC/VS2013/Ember.vcxproj index ed07e31..4016a78 100644 --- a/Builds/MSVC/VS2013/Ember.vcxproj +++ b/Builds/MSVC/VS2013/Ember.vcxproj @@ -266,12 +266,10 @@ true - - - + diff --git a/Builds/MSVC/VS2013/Ember.vcxproj.filters b/Builds/MSVC/VS2013/Ember.vcxproj.filters index 32450ef..59e3bf5 100644 --- a/Builds/MSVC/VS2013/Ember.vcxproj.filters +++ b/Builds/MSVC/VS2013/Ember.vcxproj.filters @@ -19,9 +19,6 @@ {1ae77918-b5ee-4186-9fec-802fed55144e} - - - Header Files @@ -113,6 +110,9 @@ Header Files + + Header Files + diff --git a/Builds/MSVC/VS2013/EmberCL.vcxproj b/Builds/MSVC/VS2013/EmberCL.vcxproj index 9917da6..6787428 100644 --- a/Builds/MSVC/VS2013/EmberCL.vcxproj +++ b/Builds/MSVC/VS2013/EmberCL.vcxproj @@ -273,9 +273,6 @@ $(CUDA_PATH)lib\$(PlatformName) - - - {f62787dd-1327-448b-9818-030062bcfaa5} diff --git a/Builds/MSVC/VS2013/EmberCL.vcxproj.filters b/Builds/MSVC/VS2013/EmberCL.vcxproj.filters index bb0f844..7db175f 100644 --- a/Builds/MSVC/VS2013/EmberCL.vcxproj.filters +++ b/Builds/MSVC/VS2013/EmberCL.vcxproj.filters @@ -17,9 +17,6 @@ {d66f35ca-a4cd-470a-9c56-653b0665b598} - - - Source Files diff --git a/Builds/MSVC/VS2013/EmberGenome.vcxproj b/Builds/MSVC/VS2013/EmberGenome.vcxproj index 9f65e21..92d2585 100644 --- a/Builds/MSVC/VS2013/EmberGenome.vcxproj +++ b/Builds/MSVC/VS2013/EmberGenome.vcxproj @@ -299,7 +299,6 @@ xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" - diff --git a/Builds/MSVC/VS2013/EmberGenome.vcxproj.filters b/Builds/MSVC/VS2013/EmberGenome.vcxproj.filters index 5ae7193..267f436 100644 --- a/Builds/MSVC/VS2013/EmberGenome.vcxproj.filters +++ b/Builds/MSVC/VS2013/EmberGenome.vcxproj.filters @@ -15,7 +15,6 @@ - Resource Files diff --git a/Builds/MSVC/VS2013/EmberRender.vcxproj b/Builds/MSVC/VS2013/EmberRender.vcxproj index 30be90f..7b32cf8 100644 --- a/Builds/MSVC/VS2013/EmberRender.vcxproj +++ b/Builds/MSVC/VS2013/EmberRender.vcxproj @@ -301,7 +301,6 @@ xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" - diff --git a/Builds/MSVC/VS2013/EmberRender.vcxproj.filters b/Builds/MSVC/VS2013/EmberRender.vcxproj.filters index 38a8460..03ab99e 100644 --- a/Builds/MSVC/VS2013/EmberRender.vcxproj.filters +++ b/Builds/MSVC/VS2013/EmberRender.vcxproj.filters @@ -15,7 +15,6 @@ - Resource Files diff --git a/Builds/MSVC/VS2013/EmberTester.vcxproj b/Builds/MSVC/VS2013/EmberTester.vcxproj index 084c6f6..b51d7c5 100644 --- a/Builds/MSVC/VS2013/EmberTester.vcxproj +++ b/Builds/MSVC/VS2013/EmberTester.vcxproj @@ -297,9 +297,6 @@ xcopy /F /Y /R /D "$(SolutionDir)intel64\$(Configuration)\tbb.pdb" "$(OutDir)" xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)" - - - {1d6039f6-5078-416f-a3af-a36efc7e6a1c} diff --git a/Builds/MSVC/VS2013/EmberTester.vcxproj.filters b/Builds/MSVC/VS2013/EmberTester.vcxproj.filters index f5ad239..be5b41b 100644 --- a/Builds/MSVC/VS2013/EmberTester.vcxproj.filters +++ b/Builds/MSVC/VS2013/EmberTester.vcxproj.filters @@ -14,9 +14,6 @@ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - Header Files diff --git a/Builds/MSVC/VS2013/Fractorium.vcxproj b/Builds/MSVC/VS2013/Fractorium.vcxproj index 95d8b18..77b6dd9 100644 --- a/Builds/MSVC/VS2013/Fractorium.vcxproj +++ b/Builds/MSVC/VS2013/Fractorium.vcxproj @@ -303,6 +303,7 @@ xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)"Use Use + @@ -347,6 +348,12 @@ xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)"true true + + true + true + true + true + true true @@ -427,6 +434,12 @@ xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)"true true + + true + true + true + true + true true @@ -493,6 +506,12 @@ xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)"true true + + true + true + true + true + true true @@ -730,6 +749,32 @@ xcopy /F /Y /R /D "$(SolutionDir)..\..\..\Data\flam3-palettes.xml" "$(OutDir)""$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/TwoButtonComboWidget.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing CurvesGraphicsView.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/CurvesGraphicsView.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing CurvesGraphicsView.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/CurvesGraphicsView.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing CurvesGraphicsView.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/CurvesGraphicsView.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing CurvesGraphicsView.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/CurvesGraphicsView.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing CurvesGraphicsView.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/CurvesGraphicsView.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing CurvesGraphicsView.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fFractoriumPch.h" "-f../../../../../Source/Fractorium/CurvesGraphicsView.h" -DUNICODE -DWIN32 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_MULTIMEDIA_LIB -DQT_HELP_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_XML_LIB -D_MBCS "-I." "-I$(QTDIR)\include" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles" "-I$(ProjectDir)..\..\..\Fractorium\GeneratedFiles\ConfigurationName" "-I$(QTDIR)\..\qtmultimedia\include\QtMultimedia" "-I$(QTDIR)\..\qtmultimedia\include" "-I$(QTDIR)\..\qttools\include" "-I$(QTDIR)\..\qttools\include\QtHelp" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtXml" "-I.\GeneratedFiles" "-I$(ProjectDir)..\..\..\Source\Ember" "-I$(ProjectDir)..\..\..\Source\EmberCL" "-I$(ProjectDir)..\..\..\Source\EmberCommon" "-I$(ProjectDir)..\..\..\..\glm" "-I$(ProjectDir)..\..\..\..\tbb\include" "-I$(ProjectDir)..\..\..\..\libjpeg" "-I$(ProjectDir)..\..\..\..\libpng" "-I$(ProjectDir)..\..\..\..\libxml2\include" "-I$(ProjectDir)..\..\..\..\glew\include" "-I$(AMDAPPSDKROOT)\include" "-I$(CUDA_PATH)include" "-I.\GeneratedFiles\$(ConfigurationName)\." + diff --git a/Builds/MSVC/VS2013/Fractorium.vcxproj.filters b/Builds/MSVC/VS2013/Fractorium.vcxproj.filters index ac10fe2..c3986fe 100644 --- a/Builds/MSVC/VS2013/Fractorium.vcxproj.filters +++ b/Builds/MSVC/VS2013/Fractorium.vcxproj.filters @@ -232,6 +232,18 @@ Generated Files\ReleaseNvidia + + Widgets + + + Generated Files\Debug + + + Generated Files\Release + + + Generated Files\ReleaseNvidia + @@ -329,6 +341,9 @@ Widgets + + Widgets + diff --git a/Data/Version History.txt b/Data/Version History.txt index f0bc409..08ce724 100644 --- a/Data/Version History.txt +++ b/Data/Version History.txt @@ -1,4 +1,4 @@ -0.4.1.8 Beta +0.4.1.9 Beta --User changes Thread image writing in EmberAnimate and when doing animation sequence in final render dialog. Add total time output for verbose mode in EmberAnimate to match EmberRender. diff --git a/Source/Ember/Curves.h b/Source/Ember/Curves.h new file mode 100644 index 0000000..1838dae --- /dev/null +++ b/Source/Ember/Curves.h @@ -0,0 +1,284 @@ +#pragma once + +#include "Utils.h" +#include "Isaac.h" + +/// +/// Curves class. +/// + +namespace EmberNs +{ +/// +/// The Bezier curves used to adjust the colors during final accumulation. +/// This functionality was gotten directly from Apophysis. +/// +template +class EMBER_API Curves +{ +public: + /// + /// Constructor which sets the curve and weight values to their defaults. + /// + Curves(bool init = false) + { + if (init) + Init(); + else + Clear(); + } + + /// + /// Default copy constructor. + /// + /// The Curves object to copy + Curves(const Curves& curves) + { + Curves::operator=(curves); + } + + /// + /// Copy constructor to copy a Curves object of type U. + /// Special case that must be here in the header because it has + /// a second template parameter. + /// + /// The Curves object to copy + template + Curves(const Curves& curves) + { + Curves::operator=(curves); + } + + /// + /// Default assignment operator. + /// + /// The Curves object to copy + Curves& operator = (const Curves& curves) + { + if (this != &curves) + Curves::operator=(curves); + + return *this; + } + + /// + /// Assignment operator to assign a Curves object of type U. + /// + /// The Curves object to copy + /// Reference to updated self + template + Curves& operator = (const Curves& curves) + { + for (uint i = 0; i < 4; i++) + { + m_Points[i][0].x = T(curves.m_Points[i][0].x); m_Points[i][0].y = T(curves.m_Points[i][0].y); m_Weights[i].x = T(curves.m_Weights[i].x); + m_Points[i][1].x = T(curves.m_Points[i][1].x); m_Points[i][1].y = T(curves.m_Points[i][1].y); m_Weights[i].y = T(curves.m_Weights[i].y); + m_Points[i][2].x = T(curves.m_Points[i][2].x); m_Points[i][2].y = T(curves.m_Points[i][2].y); m_Weights[i].z = T(curves.m_Weights[i].z); + m_Points[i][3].x = T(curves.m_Points[i][3].x); m_Points[i][3].y = T(curves.m_Points[i][3].y); m_Weights[i].w = T(curves.m_Weights[i].w); + } + + return *this; + } + + /// + /// Unary addition operator to add a Curves object to this one. + /// + /// The Curves object to add + /// Reference to updated self + Curves& operator += (const Curves& curves) + { + for (uint i = 0; i < 4; i++) + { + m_Points[i][0] += curves.m_Points[i][0]; + m_Points[i][1] += curves.m_Points[i][1]; + m_Points[i][2] += curves.m_Points[i][2]; + m_Points[i][3] += curves.m_Points[i][3]; + + m_Weights[i] += curves.m_Weights[i]; + } + + return *this; + } + + /// + /// Unary multiplication operator to multiply this object by another Curves object. + /// + /// The Curves object to multiply this one by + /// Reference to updated self + Curves& operator *= (const Curves& curves) + { + for (uint i = 0; i < 4; i++) + { + m_Points[i][0] *= curves.m_Points[i][0]; + m_Points[i][1] *= curves.m_Points[i][1]; + m_Points[i][2] *= curves.m_Points[i][2]; + m_Points[i][3] *= curves.m_Points[i][3]; + + m_Weights[i] *= curves.m_Weights[i]; + } + + return *this; + } + + /// + /// Unary multiplication operator to multiply this object by a scalar of type T. + /// + /// The scalar to multiply this object by + /// Reference to updated self + Curves& operator *= (const T& t) + { + for (uint i = 0; i < 4; i++) + { + m_Points[i][0] *= t; + m_Points[i][1] *= t; + m_Points[i][2] *= t; + m_Points[i][3] *= t; + + m_Weights[i] *= t; + } + + return *this; + } + + /// + /// Set the curve and weight values to their default state. + /// + void Init() + { + for (uint i = 0; i < 4; i++) + { + m_Points[i][0] = v2T(0);//0,0 -> 0,0 -> 1,1 -> 1,1. + m_Points[i][1] = v2T(0); + m_Points[i][2] = v2T(1); + m_Points[i][3] = v2T(1); + + m_Weights[i] = v4T(1); + } + } + + /// + /// Set the curve and weight values to an empty state. + /// + void Clear() + { + memset(&m_Points, 0, sizeof(m_Points)); + memset(&m_Weights, 0, sizeof(m_Weights)); + } + + /// + /// Whether any points are not the default. + /// + /// True if any point has been set to a value other than the default, else false. + bool CurvesSet() + { + bool set = false; + + for (uint i = 0; i < 4; i++) + { + if ((m_Points[i][0] != v2T(0)) || + (m_Points[i][1] != v2T(0)) || + (m_Points[i][2] != v2T(1)) || + (m_Points[i][3] != v2T(1))) + { + set = true; + break; + } + } + + return set; + } + + /// + /// Wrapper around calling BezierSolve() on each of the 4 weight and point vectors. + /// + /// The position to apply + /// vec4 that contains the y component of the solution for each vector in each component + v4T BezierFunc(const T& t) + { + v4T result; + v2T solution(0, 0); + + BezierSolve(t, m_Points[0], &m_Weights[0], solution); result.x = solution.y; + BezierSolve(t, m_Points[1], &m_Weights[1], solution); result.y = solution.y; + BezierSolve(t, m_Points[2], &m_Weights[2], solution); result.z = solution.y; + BezierSolve(t, m_Points[3], &m_Weights[3], solution); result.w = solution.y; + + return result; + } + +private: + /// + /// Solve the given point and weight vectors for the given position and store + /// the output in the solution vec2 passed in. + /// + /// The position to apply + /// A pointer to an array of 4 vec2 + /// A pointer to an array of 4 weights + /// The vec2 to store the solution in + void BezierSolve(const T& t, v2T* src, v4T* w, v2T& solution) + { + T s, s2, s3, t2, t3, nom_x, nom_y, denom; + + s = 1 - t; + s2 = s * s; + s3 = s * s * s; + t2 = t * t; + t3 = t * t * t; + + nom_x = (w->x * s3 * src->x) + (w->y * s2 * 3 * t * src[1].x) + (w->z * s * 3 * t2 * src[2].x) + (w->w * t3 * src[3].x); + + nom_y = (w->x * s3 * src->y) + (w->y * s2 * 3 * t * src[1].y) + (w->z * s * 3 * t2 * src[2].y) + (w->w * t3 * src[3].y); + + denom = (w->x * s3) + (w->y * s2 * 3 * t) + (w->z * s * 3 * t2) + (w->w * t3); + + + if (isnan(nom_x) || isnan(nom_y) || isnan(denom) || denom == 0) + return; + + solution.x = nom_x / denom; + solution.y = nom_y / denom; + } + +public: + v2T m_Points[4][4]; + v4T m_Weights[4]; +}; + +//Must declare this outside of the class to provide for both orders of parameters. + +/// +/// Multiplication operator to multiply a Curves object by a scalar of type T. +/// +/// The curves object to multiply +/// The scalar to multiply curves by by +/// Copy of new Curves +template +Curves operator * (const Curves& curves, const T& t) +{ + Curves c(curves); + + for (uint i = 0; i < 4; i++) + { + c.m_Points[i][0] *= t; + c.m_Points[i][1] *= t; + c.m_Points[i][2] *= t; + c.m_Points[i][3] *= t; + + c.m_Weights[i] *= t; + } + + return c; +} + +/// +/// Multiplication operator for reverse order. +/// +/// The scalar to multiply curves by by +/// The curves object to multiply +/// Copy of new Curves +template +Curves operator * (const T& t, const Curves& curves) +{ + return curves * t; +} +} \ No newline at end of file diff --git a/Source/Ember/Ember.cpp b/Source/Ember/Ember.cpp index 9748997..f0aa11a 100644 --- a/Source/Ember/Ember.cpp +++ b/Source/Ember/Ember.cpp @@ -1,6 +1,7 @@ #include "EmberPch.h" #include "EmberDefines.h" #include "Isaac.h" +#include "Curves.h" #include "Ember.h" #include "Utils.h" #include "Iterator.h" @@ -390,6 +391,7 @@ template<> unique_ptr> QTIsaac; \ /*template EMBER_API class RenderCallback;*/ \ template EMBER_API class CarToRas; \ + template EMBER_API class Curves; \ template EMBER_API class XmlToEmber; \ template EMBER_API class EmberToXml; diff --git a/Source/Ember/Ember.h b/Source/Ember/Ember.h index 6de5d4f..95b211e 100644 --- a/Source/Ember/Ember.h +++ b/Source/Ember/Ember.h @@ -1,5 +1,6 @@ #pragma once +#include "Curves.h" #include "Xform.h" #include "PaletteList.h" #include "SpatialFilter.h" @@ -150,6 +151,7 @@ public: m_Index = ember.m_Index; m_ScaleType = ember.ScaleType(); m_Palette = ember.m_Palette; + m_Curves = ember.m_Curves; m_Xforms.clear(); @@ -243,6 +245,9 @@ public: m_PaletteMode = PALETTE_STEP; m_PaletteInterp = INTERP_HSV; + //Curves. + m_Curves.Init(); + m_Name = "No name"; m_ParentFilename = "No parent"; @@ -795,6 +800,7 @@ public: InterpT<&Ember::m_MinRadDE>(embers, coefs, size); InterpT<&Ember::m_CurveDE>(embers, coefs, size); InterpT<&Ember::m_SpatialFilterRadius>(embers, coefs, size); + InterpX, &Ember::m_Curves>(embers, coefs, size); //An extra step needed here due to the OOD that was not needed in the original. //A small price to pay for the conveniences it affords us elsewhere. @@ -1362,6 +1368,7 @@ public: m_Xforms.clear(); m_FinalXform.Clear(); + m_Curves.Init(); ClearEdit(); } @@ -1651,6 +1658,9 @@ public: //Xml field: "color" or "colors" or "palette" . Palette m_Palette; + //Curves used to adjust the color during final accumulation. + Curves m_Curves; + //Strings. //The name of this ember. @@ -1674,6 +1684,15 @@ private: /// eScaleType m_ScaleType; + //The vector containing all of the xforms. + //Xml field: "xform". + vector> m_Xforms; + + //Optional final xform. Default is empty. + //Discussed in section 3.2 of the paper, page 6. + //Xml field: "finalxform". + Xform m_FinalXform; + /// /// Interpolation function that takes the address of a member variable of type T as a template parameter. /// This is an alternative to using macros. @@ -1739,15 +1758,6 @@ private: for (size_t k = 0; k < size; k++) xform->*m += coefs[k] * embers[k].GetTotalXform(i)->*m; } - - //The vector containing all of the xforms. - //Xml field: "xform". - vector> m_Xforms; - - //Optional final xform. Default is empty. - //Discussed in section 3.2 of the paper, page 6. - //Xml field: "finalxform". - Xform m_FinalXform; }; /// diff --git a/Source/Ember/EmberDefines.h b/Source/Ember/EmberDefines.h index 7bb35f6..d18279e 100644 --- a/Source/Ember/EmberDefines.h +++ b/Source/Ember/EmberDefines.h @@ -42,7 +42,7 @@ namespace EmberNs { -#define EMBER_VERSION "0.4.1.8" +#define EMBER_VERSION "0.4.1.9" #define EPS6 T(1e-6) #define EPS std::numeric_limits::epsilon()//Apoplugin.h uses -20, but it's more mathematically correct to do it this way. #define ISAAC_SIZE 4 @@ -83,13 +83,23 @@ typedef std::chrono::high_resolution_clock Clock; #define DO_DOUBLE 1//Comment this out for shorter build times during development. Always uncomment for release. //#define ISAAC_FLAM3_DEBUG 1//This is almost never needed, but is very useful when troubleshooting difficult bugs. Enable it to do a side by side comparison with flam3. -#define v2T glm::detail::tvec2 -#define v3T glm::detail::tvec3 -#define v4T glm::detail::tvec4 -#define m2T glm::detail::tmat2x2 -#define m3T glm::detail::tmat3x3 -#define m4T glm::detail::tmat4x4 -#define m23T glm::detail::tmat2x3 +#if GLM_VERSION >= 96 + #define v2T glm::tvec2 + #define v3T glm::tvec3 + #define v4T glm::tvec4 + #define m2T glm::tmat2x2 + #define m3T glm::tmat3x3 + #define m4T glm::tmat4x4 + #define m23T glm::tmat2x3 +#else + #define v2T glm::detail::tvec2 + #define v3T glm::detail::tvec3 + #define v4T glm::detail::tvec4 + #define m2T glm::detail::tmat2x2 + #define m3T glm::detail::tmat3x3 + #define m4T glm::detail::tmat4x4 + #define m23T glm::detail::tmat2x3 +#endif enum eInterp : uint { EMBER_INTERP_LINEAR = 0, EMBER_INTERP_SMOOTH = 1 }; enum eAffineInterp : uint { INTERP_LINEAR = 0, INTERP_LOG = 1, INTERP_COMPAT = 2, INTERP_OLDER = 3 }; diff --git a/Source/Ember/EmberPch.h b/Source/Ember/EmberPch.h index c05af4b..3005fba 100644 --- a/Source/Ember/EmberPch.h +++ b/Source/Ember/EmberPch.h @@ -78,5 +78,7 @@ using namespace tbb; using namespace std; using namespace std::chrono; +using namespace glm; +using namespace glm::detail; using glm::uint; using glm::uint16; diff --git a/Source/Ember/EmberToXml.h b/Source/Ember/EmberToXml.h index 7e0b24a..e765e51 100644 --- a/Source/Ember/EmberToXml.h +++ b/Source/Ember/EmberToXml.h @@ -145,7 +145,7 @@ public: os << " zoom=\"" << ember.m_Zoom << "\""; os << " rotate=\"" << ember.m_Rotate << "\""; - os << " supersample=\"" << max(1, ember.m_Supersample) << "\""; + os << " supersample=\"" << std::max(1, ember.m_Supersample) << "\""; os << " filter=\"" << ember.m_SpatialFilterRadius << "\""; os << " filter_shape=\"" << ToLower(SpatialFilterCreator::ToString(ember.m_SpatialFilterType)) << "\""; @@ -207,7 +207,19 @@ public: os << "\""; os << " new_linear=\"1\""; - os << ">\n"; + os << " curves=\""; + + for (glm::length_t ci = 0; ci < 4; ci++) + { + for (glm::length_t cj = 0; cj < 4; cj++) + { + os << ember.m_Curves.m_Points[ci][cj].x << " "; + os << ember.m_Curves.m_Points[ci][cj].y << " "; + os << ember.m_Curves.m_Weights[ci][cj] << " "; + } + } + + os << "\">\n"; //This is a grey area, what to do about symmetry to avoid duplicating the symmetry xforms when reading back?//TODO//BUG. //if (ember.m_Symmetry) diff --git a/Source/Ember/Palette.h b/Source/Ember/Palette.h index 3a87213..87915f6 100644 --- a/Source/Ember/Palette.h +++ b/Source/Ember/Palette.h @@ -533,7 +533,7 @@ public: } //Identify the most saturated channel. - maxc = max(max(cBuf[0], cBuf[1]), cBuf[2]); + maxc = std::max(std::max(cBuf[0], cBuf[1]), cBuf[2]); maxa = ls * maxc; //If a channel is saturated and highlight power is non-negative diff --git a/Source/Ember/Renderer.cpp b/Source/Ember/Renderer.cpp index 0fcf7e9..f063260 100644 --- a/Source/Ember/Renderer.cpp +++ b/Source/Ember/Renderer.cpp @@ -28,41 +28,6 @@ Renderer::~Renderer() /// Non-virtual processing functions. /// -/// -/// Compute the camera. -/// This sets up the bounds of the cartesian plane that the raster bounds correspond to. -/// This must be called after ComputeBounds() which sets up the raster bounds. -/// -template -void Renderer::ComputeCamera() -{ - m_Scale = pow(T(2.0), Zoom()); - m_ScaledQuality = Quality() * m_Scale * m_Scale; - - m_PixelsPerUnitX = PixelsPerUnit() * m_Scale; - m_PixelsPerUnitY = m_PixelsPerUnitX; - m_PixelsPerUnitX /= PixelAspectRatio(); - - T shift = 0; - T t0 = T(m_GutterWidth) / (Supersample() * m_PixelsPerUnitX); - T t1 = T(m_GutterWidth) / (Supersample() * m_PixelsPerUnitY); - - //These go from ll to ur, moving from negative to positive. - m_LowerLeftX = CenterX() - FinalRasW() / m_PixelsPerUnitX / T(2.0); - m_LowerLeftY = CenterY() - FinalRasH() / m_PixelsPerUnitY / T(2.0); - m_UpperRightX = m_LowerLeftX + FinalRasW() / m_PixelsPerUnitX; - m_UpperRightY = m_LowerLeftY + FinalRasH() / m_PixelsPerUnitY; - - T carLlX = m_LowerLeftX - t0; - T carLlY = m_LowerLeftY - t1 + shift; - T carUrX = m_UpperRightX + t0; - T carUrY = m_UpperRightY + t1 + shift; - - m_RotMat.MakeID(); - m_RotMat.Rotate(-Rotate()); - m_CarToRas.Init(carLlX, carLlY, carUrX, carUrY, m_SuperRasW, m_SuperRasH, PixelAspectRatio()); -} - /// /// Add an ember to the end of the embers vector and reset the rendering process. /// Reset the rendering process. @@ -124,7 +89,7 @@ void Renderer::ComputeBounds() //If the radius of the density estimation filter is greater than the //gutter width, have to pad with more. Otherwise, use the same value. for (size_t i = 0; i < m_Embers.size(); i++) - maxDEFilterWidth = max(size_t(ceil(m_Embers[i].m_MaxRadDE) * m_Ember.m_Supersample), maxDEFilterWidth); + maxDEFilterWidth = std::max(size_t(ceil(m_Embers[i].m_MaxRadDE) * m_Ember.m_Supersample), maxDEFilterWidth); //Need an extra ss = (int)floor(m_Supersample / 2.0) of pixels so that a local iteration count for DE can be determined.//SMOULDER if (maxDEFilterWidth > 0) @@ -140,6 +105,50 @@ void Renderer::ComputeBounds() m_SuperSize = m_SuperRasW * m_SuperRasH; } +/// +/// Compute the scale based on the zoom, then the quality based on the computed scale. +/// This sets up the bounds of the cartesian plane that the raster bounds correspond to. +/// This must be called before ComputeCamera() which will use scale. +/// +template +void Renderer::ComputeQuality() +{ + m_Scale = pow(T(2.0), Zoom()); + m_ScaledQuality = Quality() * m_Scale * m_Scale; +} + +/// +/// Compute the camera. +/// This sets up the bounds of the cartesian plane that the raster bounds correspond to. +/// This must be called after ComputeBounds() which sets up the raster bounds. +/// +template +void Renderer::ComputeCamera() +{ + m_PixelsPerUnitX = PixelsPerUnit() * m_Scale; + m_PixelsPerUnitY = m_PixelsPerUnitX; + m_PixelsPerUnitX /= PixelAspectRatio(); + + T shift = 0; + T t0 = T(m_GutterWidth) / (Supersample() * m_PixelsPerUnitX); + T t1 = T(m_GutterWidth) / (Supersample() * m_PixelsPerUnitY); + + //These go from ll to ur, moving from negative to positive. + m_LowerLeftX = CenterX() - FinalRasW() / m_PixelsPerUnitX / T(2.0); + m_LowerLeftY = CenterY() - FinalRasH() / m_PixelsPerUnitY / T(2.0); + m_UpperRightX = m_LowerLeftX + FinalRasW() / m_PixelsPerUnitX; + m_UpperRightY = m_LowerLeftY + FinalRasH() / m_PixelsPerUnitY; + + T carLlX = m_LowerLeftX - t0; + T carLlY = m_LowerLeftY - t1 + shift; + T carUrX = m_UpperRightX + t0; + T carUrY = m_UpperRightY + t1 + shift; + + m_RotMat.MakeID(); + m_RotMat.Rotate(-Rotate()); + m_CarToRas.Init(carLlX, carLlY, carUrX, carUrY, m_SuperRasW, m_SuperRasH, PixelAspectRatio()); +} + /// /// Set the current ember. /// This will also populate the vector of embers with a single element copy @@ -327,7 +336,7 @@ eRenderStatus Renderer::Run(vector& finalImage, double time, s bool accumOnly = m_ProcessAction == ACCUM_ONLY; bool resume = m_ProcessState != NONE; bool newFilterAlloc; - size_t temporalSample = 0; + size_t i, temporalSample = 0; T deTime; eRenderStatus success = RENDER_OK; //double iterationTime = 0; @@ -353,6 +362,7 @@ eRenderStatus Renderer::Run(vector& finalImage, double time, s m_Gamma = 0; m_Vibrancy = 0;//Accumulate these after each temporal sample. m_VibGamCount = 0; + m_CurvesSet = false; m_Background.Clear(); } //User requested an increase in quality after finishing. @@ -365,6 +375,7 @@ eRenderStatus Renderer::Run(vector& finalImage, double time, s m_Vibrancy = 0; m_VibGamCount = 0; m_Background.Clear(); + ComputeQuality();//Must recompute quality when doing a quality increase. } //Make sure values are within valid range. @@ -427,8 +438,9 @@ eRenderStatus Renderer::Run(vector& finalImage, double time, s Interpolater::Interpolate(m_Embers, deTime, 0, m_Ember); //it.Toc("Interp 2"); - ClampGte(m_Ember.m_MinRadDE, 0); - ClampGte(m_Ember.m_MaxRadDE, 0); + ClampGteRef(m_Ember.m_MinRadDE, 0); + ClampGteRef(m_Ember.m_MaxRadDE, 0); + ClampGteRef(m_Ember.m_MaxRadDE, m_Ember.m_MinRadDE); if (!CreateDEFilter(newFilterAlloc)) { @@ -446,7 +458,7 @@ eRenderStatus Renderer::Run(vector& finalImage, double time, s //Interpolate again. //it.Tic(); - if (m_Embers.size() > 1) + if (TemporalSamples() > 1 && m_Embers.size() > 1) Interpolater::Interpolate(m_Embers, temporalTime, 0, m_Ember);//This will perform all necessary precalcs via the ember/xform/variation assignment operators. //it.Toc("Interp 3"); @@ -458,13 +470,16 @@ eRenderStatus Renderer::Run(vector& finalImage, double time, s goto Finish; } - ComputeCamera(); - - //For each temporal sample, the palette m_Dmap needs to be re-created with color scalar. 1 if no temporal samples. - MakeDmap(colorScalar); + //Don't need to do this every time through for a single image. + if (TemporalSamples() > 1 || !resume) + { + ComputeQuality(); + ComputeCamera(); + MakeDmap(colorScalar);//For each temporal sample, the palette m_Dmap needs to be re-created with color scalar. 1 if no temporal samples. + } //The actual number of times to iterate. Each thread will get (totalIters / ThreadCount) iters to do. - //This is based on zoom and scale calculated in ComputeCamera(). + //This is based on zoom and scale calculated in ComputeQuality(). //Note that the iter count is based on the final image dimensions, and not the super sampled dimensions. size_t itersPerTemporalSample = ItersPerTemporalSample();//The total number of iterations for this temporal sample without overrides. size_t sampleItersToDo;//The number of iterations to actually do in this sample, considering overrides. @@ -474,7 +489,7 @@ eRenderStatus Renderer::Run(vector& finalImage, double time, s else sampleItersToDo = itersPerTemporalSample;//Run as many iters as specified to complete this temporal sample. - sampleItersToDo = min(sampleItersToDo, itersPerTemporalSample - m_LastIter); + sampleItersToDo = std::min(sampleItersToDo, itersPerTemporalSample - m_LastIter); EmberStats stats = Iterate(sampleItersToDo, temporalSample);//The heavy work is done here. //If no iters were executed, something went catastrophically wrong. @@ -602,6 +617,13 @@ AccumOnly: //Make sure a filter has been created. CreateSpatialFilter(newFilterAlloc); + m_CurvesSet = m_Ember.m_Curves.CurvesSet(); + + //Color curves must be re-calculated as well. + if (m_CurvesSet) + for (i = 0; i < COLORMAP_LENGTH; i++) + m_Csa[i] = m_Ember.m_Curves.BezierFunc(i / T(COLORMAP_LENGTH_MINUS_1)) * T(COLORMAP_LENGTH_MINUS_1); + if (AccumulatorToFinalImage(finalImage, finalOffset) == RENDER_OK) { m_Stats.m_RenderMs = m_RenderTimer.Toc();//Record total time from the very beginning to the very end, including all intermediate calls. @@ -839,17 +861,17 @@ eRenderStatus Renderer::GaussianDensityFilter() parallel_for(size_t(0), threads, [&] (size_t threadIndex) { size_t pixelNumber = 0; - int localStartRow = int(min(startRow + (threadIndex * chunkSize), endRow - 1)); - int localEndRow = int(min(localStartRow + chunkSize, endRow)); + int localStartRow = int(std::min(startRow + (threadIndex * chunkSize), endRow - 1)); + int localEndRow = int(std::min(localStartRow + chunkSize, endRow)); size_t pixelsThisThread = size_t(localEndRow - localStartRow) * m_SuperRasW; double lastPercent = 0; - glm::detail::tvec4 logScaleBucket; + tvec4 logScaleBucket; for (intmax_t j = localStartRow; (j < localEndRow) && !m_Abort; j++) { size_t bucketRowStart = j * m_SuperRasW;//Pull out of inner loop for optimization. - const glm::detail::tvec4* bucket; - const glm::detail::tvec4* buckets = m_HistBuckets.data(); + const tvec4* bucket; + const tvec4* buckets = m_HistBuckets.data(); const T* filterCoefs = m_DensityFilter->Coefs(); const T* filterWidths = m_DensityFilter->Widths(); @@ -875,10 +897,10 @@ eRenderStatus Renderer::GaussianDensityFilter() //The original contained a glaring flaw as it would run past the boundaries of the buffers //when calculating the density for a box centered on the last row or column. //Clamp here to not run over the edge. - intmax_t densityBoxLeftX = (i - min(i, ss)); - intmax_t densityBoxRightX = (i + min(ss, intmax_t(m_SuperRasW) - i - 1)); - intmax_t densityBoxTopY = (j - min(j, ss)); - intmax_t densityBoxBottomY = (j + min(ss, intmax_t(m_SuperRasH) - j - 1)); + intmax_t densityBoxLeftX = (i - std::min(i, ss)); + intmax_t densityBoxRightX = (i + std::min(ss, intmax_t(m_SuperRasW) - i - 1)); + intmax_t densityBoxTopY = (j - std::min(j, ss)); + intmax_t densityBoxBottomY = (j + std::min(ss, intmax_t(m_SuperRasH) - j - 1)); //Count density in ssxss area. //Original went one col at a time, which is cache inefficient. Go one row at at time here for a slight speedup. @@ -1048,7 +1070,7 @@ eRenderStatus Renderer::AccumulatorToFinalImage(byte* pixels, size_t Color newBucket; size_t pixelsRowStart = (m_YAxisUp ? ((FinalRasH() - j) - 1) : j) * FinalRowSize();//Pull out of inner loop for optimization. size_t y = m_DensityFilterOffset + (j * Supersample());//Start at the beginning row of each super sample block. - uint16* p16; + glm::uint16* p16; for (size_t i = 0; i < FinalRasW(); i++, pixelsRowStart += PixelSize()) { @@ -1074,13 +1096,20 @@ eRenderStatus Renderer::AccumulatorToFinalImage(byte* pixels, size_t if (BytesPerChannel() == 2) { - p16 = reinterpret_cast(pixels + pixelsRowStart); + p16 = reinterpret_cast(pixels + pixelsRowStart); if (EarlyClip()) { - p16[0] = uint16(Clamp(newBucket.r, 0, 255) * bucketT(256)); - p16[1] = uint16(Clamp(newBucket.g, 0, 255) * bucketT(256)); - p16[2] = uint16(Clamp(newBucket.b, 0, 255) * bucketT(256)); + if (m_CurvesSet) + { + CurveAdjust(newBucket.r, 1); + CurveAdjust(newBucket.g, 2); + CurveAdjust(newBucket.b, 3); + } + + p16[0] = glm::uint16(Clamp(newBucket.r, 0, 255) * bucketT(256)); + p16[1] = glm::uint16(Clamp(newBucket.g, 0, 255) * bucketT(256)); + p16[2] = glm::uint16(Clamp(newBucket.b, 0, 255) * bucketT(256)); if (NumChannels() > 3) { @@ -1092,13 +1121,20 @@ eRenderStatus Renderer::AccumulatorToFinalImage(byte* pixels, size_t } else { - GammaCorrection(*(reinterpret_cast*>(&newBucket)), background, g, linRange, vibrancy, NumChannels() > 3, true, p16); + GammaCorrection(*(reinterpret_cast*>(&newBucket)), background, g, linRange, vibrancy, NumChannels() > 3, true, p16); } } else { if (EarlyClip()) { + if (m_CurvesSet) + { + CurveAdjust(newBucket.r, 1); + CurveAdjust(newBucket.g, 2); + CurveAdjust(newBucket.b, 3); + } + pixels[pixelsRowStart] = byte(Clamp(newBucket.r, 0, 255)); pixels[pixelsRowStart + 1] = byte(Clamp(newBucket.g, 0, 255)); pixels[pixelsRowStart + 2] = byte(Clamp(newBucket.b, 0, 255)); @@ -1113,7 +1149,7 @@ eRenderStatus Renderer::AccumulatorToFinalImage(byte* pixels, size_t } else { - GammaCorrection(*(reinterpret_cast*>(&newBucket)), background, g, linRange, vibrancy, NumChannels() > 3, true, pixels + pixelsRowStart); + GammaCorrection(*(reinterpret_cast*>(&newBucket)), background, g, linRange, vibrancy, NumChannels() > 3, true, pixels + pixelsRowStart); } } } @@ -1183,7 +1219,7 @@ EmberStats Renderer::Iterate(size_t iterCount, size_t temporalSample IterParams params; m_BadVals[threadIndex] = 0; - params.m_Count = min(totalItersPerThread, SubBatchSize()); + params.m_Count = std::min(totalItersPerThread, SubBatchSize()); params.m_Skip = FuseCount(); //params.m_OneColDiv2 = m_CarToRas.OneCol() / 2; //params.m_OneRowDiv2 = m_CarToRas.OneRow() / 2; @@ -1193,7 +1229,7 @@ EmberStats Renderer::Iterate(size_t iterCount, size_t temporalSample { //Must recalculate the number of iters to run on each sub batch because the last batch will most likely have less than SubBatchSize iters. //For example, if 51,000 are requested, and the sbs is 10,000, it should run 5 sub batches of 10,000 iters, and one final sub batch of 1,000 iters. - params.m_Count = min(params.m_Count, totalItersPerThread - m_SubBatch[threadIndex]); + params.m_Count = std::min(params.m_Count, totalItersPerThread - m_SubBatch[threadIndex]); //Use first as random point, the rest are iterated points. //Note that this gets reset with a new random point for each subBatchSize iterations. @@ -1290,16 +1326,16 @@ void Renderer::PixelAspectRatio(T pixelAspectRatio) /// Non-virtual renderer properties, getters only. /// -template T Renderer::Scale() const { return m_Scale; } -template T Renderer::PixelsPerUnitX() const { return m_PixelsPerUnitX; } -template T Renderer::PixelsPerUnitY() const { return m_PixelsPerUnitY; } -template T Renderer::K1() const { return m_K1; } -template T Renderer::K2() const { return m_K2; } -template const CarToRas* Renderer::CoordMap() const { return &m_CarToRas; } -template glm::detail::tvec4* Renderer::HistBuckets() { return m_HistBuckets.data(); } -template glm::detail::tvec4* Renderer::AccumulatorBuckets() { return m_AccumulatorBuckets.data(); } -template SpatialFilter* Renderer::GetSpatialFilter() { return m_SpatialFilter.get(); } -template TemporalFilter* Renderer::GetTemporalFilter() { return m_TemporalFilter.get(); } +template T Renderer::Scale() const { return m_Scale; } +template T Renderer::PixelsPerUnitX() const { return m_PixelsPerUnitX; } +template T Renderer::PixelsPerUnitY() const { return m_PixelsPerUnitY; } +template T Renderer::K1() const { return m_K1; } +template T Renderer::K2() const { return m_K2; } +template const CarToRas* Renderer::CoordMap() const { return &m_CarToRas; } +template tvec4* Renderer::HistBuckets() { return m_HistBuckets.data(); } +template tvec4* Renderer::AccumulatorBuckets() { return m_AccumulatorBuckets.data(); } +template SpatialFilter* Renderer::GetSpatialFilter() { return m_SpatialFilter.get(); } +template TemporalFilter* Renderer::GetTemporalFilter() { return m_TemporalFilter.get(); } /// /// Virtual renderer properties overridden from RendererBase, getters only. @@ -1405,7 +1441,7 @@ void Renderer::Accumulate(QTIsaac& rand, Poin { size_t histIndex, intColorIndex, histSize = m_HistBuckets.size(); bucketT colorIndex, colorIndexFrac; - const glm::detail::tvec4* dmap = &(palette->m_Entries[0]); + const tvec4* dmap = &(palette->m_Entries[0]); //T oneColDiv2 = m_CarToRas.OneCol() / 2; //T oneRowDiv2 = m_CarToRas.OneRow() / 2; @@ -1512,7 +1548,7 @@ void Renderer::Accumulate(QTIsaac& rand, Poin /// The row of the bucket /// The offset to add to the row template -void Renderer::AddToAccum(const glm::detail::tvec4& bucket, intmax_t i, intmax_t ii, intmax_t j, intmax_t jj) +void Renderer::AddToAccum(const tvec4& bucket, intmax_t i, intmax_t ii, intmax_t j, intmax_t jj) { if (j + jj >= 0 && j + jj < intmax_t(m_SuperRasH) && i + ii >= 0 && i + ii < intmax_t(m_SuperRasW)) m_AccumulatorBuckets[(i + ii) + ((j + jj) * m_SuperRasW)] += bucket; @@ -1536,7 +1572,7 @@ void Renderer::AddToAccum(const glm::detail::tvec4The storage space for the corrected values to be written to template template -void Renderer::GammaCorrection(glm::detail::tvec4& bucket, Color& background, T g, T linRange, T vibrancy, bool doAlpha, bool scale, accumT* correctedChannels) +void Renderer::GammaCorrection(tvec4& bucket, Color& background, T g, T linRange, T vibrancy, bool doAlpha, bool scale, accumT* correctedChannels) { T alpha, ls, a; bucketT newRgb[3];//Would normally use a Color, but don't want to call a needless constructor every time this function is called, which is once per pixel. @@ -1573,9 +1609,16 @@ void Renderer::GammaCorrection(glm::detail::tvec4(a, 0, 255));//Early clip, just assign directly. + } else + { + if (m_CurvesSet) + CurveAdjust(a, rgbi + 1); + correctedChannels[rgbi] = accumT(Clamp(a, 0, 255) * scaleVal);//Final accum, multiply by 1 for 8 bpc, or 256 for 16 bpc. + } } if (doAlpha) @@ -1589,6 +1632,15 @@ void Renderer::GammaCorrection(glm::detail::tvec4 +void Renderer::CurveAdjust(T& a, const glm::length_t& index) +{ + size_t tempIndex = size_t(Clamp(a, 0, COLORMAP_LENGTH_MINUS_1)); + size_t tempIndex2 = size_t(Clamp(m_Csa[tempIndex].x, 0, COLORMAP_LENGTH_MINUS_1)); + + a = std::round(m_Csa[tempIndex2][index]); +} + //This class had to be implemented in a cpp file because the compiler was breaking. //So the explicit instantiation must be declared here rather than in Ember.cpp where //all of the other classes are done. diff --git a/Source/Ember/Renderer.h b/Source/Ember/Renderer.h index 3756715..61c04f0 100644 --- a/Source/Ember/Renderer.h +++ b/Source/Ember/Renderer.h @@ -56,13 +56,14 @@ public: //Virtual processing functions overriden from RendererBase. virtual void ComputeBounds() override; + virtual void ComputeQuality() override; virtual void ComputeCamera() override; virtual void SetEmber(Ember& ember, eProcessAction action = FULL_RENDER) override; virtual void SetEmber(vector>& embers) override; virtual bool CreateDEFilter(bool& newAlloc) override; virtual bool CreateSpatialFilter(bool& newAlloc) override; virtual bool CreateTemporalFilter(bool& newAlloc) override; - virtual size_t HistBucketSize() const override { return sizeof(glm::detail::tvec4); } + virtual size_t HistBucketSize() const override { return sizeof(tvec4); } virtual eRenderStatus Run(vector& finalImage, double time = 0, size_t subBatchCountOverride = 0, bool forceOutput = false, size_t finalOffset = 0) override; virtual EmberImageComments ImageComments(EmberStats& stats, size_t printEditDepth = 0, bool intPalette = false, bool hexPalette = true) override; @@ -83,16 +84,16 @@ public: void PixelAspectRatio(T pixelAspectRatio); //Non-virtual renderer properties, getters only. - inline T Scale() const; - inline T PixelsPerUnitX() const; - inline T PixelsPerUnitY() const; - inline T K1() const; - inline T K2() const; - inline const CarToRas* CoordMap() const; - inline glm::detail::tvec4* HistBuckets(); - inline glm::detail::tvec4* AccumulatorBuckets(); - inline SpatialFilter* GetSpatialFilter(); - inline TemporalFilter* GetTemporalFilter(); + inline T Scale() const; + inline T PixelsPerUnitX() const; + inline T PixelsPerUnitY() const; + inline T K1() const; + inline T K2() const; + inline const CarToRas* CoordMap() const; + inline tvec4* HistBuckets(); + inline tvec4* AccumulatorBuckets(); + inline SpatialFilter* GetSpatialFilter(); + inline TemporalFilter* GetTemporalFilter(); //Virtual renderer properties overridden from RendererBase, getters only. virtual double ScaledQuality() const override; @@ -150,8 +151,9 @@ protected: private: //Miscellaneous non-virtual functions used only in this class. void Accumulate(QTIsaac& rand, Point* samples, size_t sampleCount, const Palette* palette); - /*inline*/ void AddToAccum(const glm::detail::tvec4& bucket, intmax_t i, intmax_t ii, intmax_t j, intmax_t jj); - template void GammaCorrection(glm::detail::tvec4& bucket, Color& background, T g, T linRange, T vibrancy, bool doAlpha, bool scale, accumT* correctedChannels); + /*inline*/ void AddToAccum(const tvec4& bucket, intmax_t i, intmax_t ii, intmax_t j, intmax_t jj); + template void GammaCorrection(tvec4& bucket, Color& background, T g, T linRange, T vibrancy, bool doAlpha, bool scale, accumT* correctedChannels); + void CurveAdjust(T& a, const glm::length_t& index); protected: T m_Scale; @@ -177,9 +179,9 @@ protected: Iterator* m_Iterator; unique_ptr> m_StandardIterator; unique_ptr> m_XaosIterator; - Palette m_Dmap; - vector> m_HistBuckets; - vector> m_AccumulatorBuckets; + Palette m_Dmap, m_Csa; + vector> m_HistBuckets; + vector> m_AccumulatorBuckets; unique_ptr> m_SpatialFilter; unique_ptr> m_TemporalFilter; unique_ptr> m_DensityFilter; diff --git a/Source/Ember/RendererBase.cpp b/Source/Ember/RendererBase.cpp index bd2241d..b84ab74 100644 --- a/Source/Ember/RendererBase.cpp +++ b/Source/Ember/RendererBase.cpp @@ -16,6 +16,7 @@ RendererBase::RendererBase() m_YAxisUp = false; m_InsertPalette = false; m_ReclaimOnResize = false; + m_CurvesSet = false; m_NumChannels = 3; m_BytesPerChannel = 1; m_SuperSize = 0; @@ -469,7 +470,7 @@ void RendererBase::ThreadCount(size_t threads, const char* seedString) if (seedString) { memset(seeds, 0, isaacSize * sizeof(ISAAC_INT)); - memcpy(reinterpret_cast(seeds), seedString, min(strlen(seedString), isaacSize * sizeof(ISAAC_INT))); + memcpy(reinterpret_cast(seeds), seedString, std::min(strlen(seedString), isaacSize * sizeof(ISAAC_INT))); } //This is critical for multithreading, otherwise the threads all happen diff --git a/Source/Ember/RendererBase.h b/Source/Ember/RendererBase.h index 66f06b4..b62e555 100644 --- a/Source/Ember/RendererBase.h +++ b/Source/Ember/RendererBase.h @@ -117,6 +117,7 @@ public: virtual bool CreateSpatialFilter(bool& newAlloc) = 0; virtual bool CreateTemporalFilter(bool& newAlloc) = 0; virtual void ComputeBounds() = 0; + virtual void ComputeQuality() = 0; virtual void ComputeCamera() = 0; virtual eRenderStatus Run(vector& finalImage, double time = 0, size_t subBatchCountOverride = 0, bool forceOutput = false, size_t finalOffset = 0) = 0; virtual EmberImageComments ImageComments(EmberStats& stats, size_t printEditDepth = 0, bool intPalette = false, bool hexPalette = true) = 0; @@ -201,6 +202,7 @@ protected: bool m_InFinalAccum; bool m_InsertPalette; bool m_ReclaimOnResize; + bool m_CurvesSet; volatile bool m_Abort; size_t m_SuperRasW; size_t m_SuperRasH; diff --git a/Source/Ember/SheepTools.h b/Source/Ember/SheepTools.h index 8eb4e43..a9efc9e 100644 --- a/Source/Ember/SheepTools.h +++ b/Source/Ember/SheepTools.h @@ -1288,6 +1288,7 @@ public: m_Renderer->CreateSpatialFilter(newAlloc); m_Renderer->CreateDEFilter(newAlloc); m_Renderer->ComputeBounds(); + m_Renderer->ComputeQuality(); m_Renderer->ComputeCamera(); if (ember.XaosPresent()) diff --git a/Source/Ember/Variation.h b/Source/Ember/Variation.h index ce39fc2..6bba03e 100644 --- a/Source/Ember/Variation.h +++ b/Source/Ember/Variation.h @@ -1506,7 +1506,7 @@ public: { case REAL : { - *m_Param = max(min(val, m_Max), m_Min); + *m_Param = std::max(std::min(val, m_Max), m_Min); break; } @@ -1524,7 +1524,7 @@ public: case REAL_NONZERO : { - T vd = max(min(val, m_Max), m_Min); + T vd = std::max(std::min(val, m_Max), m_Min); if (IsNearZero(vd)) *m_Param = EPS * SignNz(vd); @@ -1536,14 +1536,14 @@ public: case INTEGER : { - *m_Param = T(int(max(min(T(Floor(val + T(0.5))), m_Max), m_Min))); + *m_Param = T(int(std::max(std::min(T(Floor(val + T(0.5))), m_Max), m_Min))); break; } case INTEGER_NONZERO : default: { - int vi = int(max(min(T(Floor(val + T(0.5))), m_Max), m_Min)); + int vi = int(std::max(std::min(T(Floor(val + T(0.5))), m_Max), m_Min)); if (vi == 0) vi = int(SignNz(val)); diff --git a/Source/Ember/Variations01.h b/Source/Ember/Variations01.h index a62baf3..560ab19 100644 --- a/Source/Ember/Variations01.h +++ b/Source/Ember/Variations01.h @@ -3773,8 +3773,8 @@ public: { m_XAmpV = m_Weight * m_XAmp; m_YAmpV = m_Weight * m_YAmp; - m_XLengthV = 1 / max(SQR(m_XLength), T(1e-20)); - m_YLengthV = 1 / max(SQR(m_YLength), T(1e-20)); + m_XLengthV = 1 / std::max(SQR(m_XLength), T(1e-20)); + m_YLengthV = 1 / std::max(SQR(m_YLength), T(1e-20)); } virtual void Random(QTIsaac& rand) override diff --git a/Source/Ember/Variations02.h b/Source/Ember/Variations02.h index 784645e..dffc286 100644 --- a/Source/Ember/Variations02.h +++ b/Source/Ember/Variations02.h @@ -4610,7 +4610,7 @@ public: T y = (helper.In.y * m_S) + m_CenterY; //Calculate distance from center but constrain it to EPS. - T d = max(EPS, sqrt(SQR(x) * SQR(y))); + T d = std::max(EPS, sqrt(SQR(x) * SQR(y))); //Normalize x and y. T nx = x / d; diff --git a/Source/Ember/Variations04.h b/Source/Ember/Variations04.h index 3e1f409..acf4410 100644 --- a/Source/Ember/Variations04.h +++ b/Source/Ember/Variations04.h @@ -4530,7 +4530,7 @@ public: m_Ay = ((fabs(m_AreaY) < 0.1) ? T(0.1) : fabs(m_AreaY)) * agdoa; m_Cx = m_CenterX * agdoc; m_Cy = m_CenterY * agdoc; - m_B = m_Gamma * agdoa / (max(m_Ax, m_Ay)); + m_B = m_Gamma * agdoa / (std::max(m_Ax, m_Ay)); } protected: diff --git a/Source/Ember/Variations05.h b/Source/Ember/Variations05.h index a4b3116..663d8fc 100644 --- a/Source/Ember/Variations05.h +++ b/Source/Ember/Variations05.h @@ -2119,8 +2119,8 @@ public: const T ay = rand.Frand(T(-0.5), T(0.5)); const T az = rand.Frand(T(-0.5), T(0.5)); const T r = sqrt(Sqr(helper.In.x - m_X0) + Sqr(helper.In.y - m_Y0) + Sqr(helper.In.z - m_Z0)); - const T rc = ((m_Invert != 0 ? max(1 - r, 0) : max(r, 0)) - m_MinDist) * m_InternalScatter;//Original called a macro named min, which internally performed max. - const T rs = max(rc, 0); + const T rc = ((m_Invert != 0 ? std::max(1 - r, 0) : std::max(r, 0)) - m_MinDist) * m_InternalScatter;//Original called a macro named min, which internally performed max. + const T rs = std::max(rc, 0); T sigma, phi, rad, sigmas, sigmac, phis, phic; T scale, denom; @@ -2279,8 +2279,8 @@ public: { const v4T random(rand.Frand(T(-0.5), T(0.5)), rand.Frand(T(-0.5), T(0.5)), rand.Frand(T(-0.5), T(0.5)), rand.Frand(T(-0.5), T(0.5))); const T distA = sqrt(Sqr(helper.In.x - m_X0) + Sqr(helper.In.y - m_Y0) + Sqr(helper.In.z - m_Z0)); - const T distB = m_Invert != 0 ? max(1 - distA, 0) : max(distA, 0);//Original called a macro named min, which internally performed max. - const T dist = max((distB - m_MinDist) * m_RMax, 0); + const T distB = m_Invert != 0 ? std::max(1 - distA, 0) : std::max(distA, 0);//Original called a macro named min, which internally performed max. + const T dist = std::max((distB - m_MinDist) * m_RMax, 0); switch (int(m_Type)) { @@ -2484,11 +2484,11 @@ public: break; case 1://Square. default: - radius = max(fabs(helper.In.x - m_CenterX), max(fabs(helper.In.y - m_CenterY), (fabs(helper.In.z - m_CenterZ))));//Original called a macro named min, which internally performed max. + radius = std::max(fabs(helper.In.x - m_CenterX), std::max(fabs(helper.In.y - m_CenterY), (fabs(helper.In.z - m_CenterZ))));//Original called a macro named min, which internally performed max. break; } - const T dist = max(((m_InvertDistance != 0 ? max(1 - radius, 0) : max(radius, 0)) - m_MinDistance) * m_RMax, 0); + const T dist = std::max(((m_InvertDistance != 0 ? std::max(1 - radius, 0) : std::max(radius, 0)) - m_MinDistance) * m_RMax, 0); switch (int(m_BlurType)) { diff --git a/Source/Ember/XmlToEmber.h b/Source/Ember/XmlToEmber.h index 94b479f..88785c8 100644 --- a/Source/Ember/XmlToEmber.h +++ b/Source/Ember/XmlToEmber.h @@ -550,8 +550,8 @@ private: char* attStr; const char* loc = __FUNCTION__; int soloXform = -1; - uint i, count, index = 0; - double vals[10]; + uint i, j, count, index = 0; + double vals[16]; xmlAttrPtr att, curAtt; xmlNodePtr editNode, childNode, motionNode; @@ -704,6 +704,20 @@ private: Atof(attStr, currentEmber.m_Hue); currentEmber.m_Hue = fmod(currentEmber.m_Hue, T(0.5));//Orig did fmod 1, but want it in the range -0.5 - 0.5. } + else if (!Compare(curAtt->name, "curves")) + { + stringstream ss(attStr); + + for (i = 0; i < 4; i++) + { + for (j = 0; j < 4; j++) + { + ss >> currentEmber.m_Curves.m_Points[i][j].x; + ss >> currentEmber.m_Curves.m_Points[i][j].y; + ss >> currentEmber.m_Curves.m_Weights[i][j]; + } + } + } xmlFree(attStr); } diff --git a/Source/EmberAnimate/EmberAnimate.rc b/Source/EmberAnimate/EmberAnimate.rc index 12b7e37..4784b2e 100644 --- a/Source/EmberAnimate/EmberAnimate.rc +++ b/Source/EmberAnimate/EmberAnimate.rc @@ -49,8 +49,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,4,1,8 - PRODUCTVERSION 0,4,1,8 + FILEVERSION 0,4,1,9 + PRODUCTVERSION 0,4,1,9 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -67,12 +67,12 @@ BEGIN BEGIN VALUE "CompanyName", "Open Source" VALUE "FileDescription", "Renders fractal flames as animations with motion blur" - VALUE "FileVersion", "0.4.1.8" + VALUE "FileVersion", "0.4.1.9" VALUE "InternalName", "EmberAnimate.rc" VALUE "LegalCopyright", "Copyright (C) Matt Feemster 2013, GPL v3" VALUE "OriginalFilename", "EmberAnimate.rc" VALUE "ProductName", "Ember Animate" - VALUE "ProductVersion", "0.4.1.8" + VALUE "ProductVersion", "0.4.1.9" END END BLOCK "VarFileInfo" diff --git a/Source/EmberCL/EmberCLFunctions.h b/Source/EmberCL/EmberCLFunctions.h index 657f16f..5f0f897 100644 --- a/Source/EmberCL/EmberCLFunctions.h +++ b/Source/EmberCL/EmberCLFunctions.h @@ -140,6 +140,21 @@ static const char* CalcAlphaFunctionString = "}\n" "\n"; + +/// +/// OpenCL equivalent of Renderer::CurveAdjust(). +/// Only use float here instead of real_t because the output will be passed to write_imagef() +/// during final accumulation, which only takes floats. +/// +static const char* CurveAdjustFunctionString = +"static inline void CurveAdjust(__constant real4reals* csa, float* a, uint index)\n" +"{\n" +" uint tempIndex = (uint)Clamp(*a, 0.0, (float)COLORMAP_LENGTH_MINUS_1);\n" +" uint tempIndex2 = (uint)Clamp(csa[tempIndex].m_Real4.x, 0.0, (real_t)COLORMAP_LENGTH_MINUS_1);\n" +"\n" +" *a = (float)round(csa[tempIndex2].m_Reals[index]);\n" +"}\n"; + /// /// Use MWC 64 from David Thomas at the Imperial College of London for /// random numbers in OpenCL, instead of ISAAC which was used diff --git a/Source/EmberCL/FinalAccumOpenCLKernelCreator.cpp b/Source/EmberCL/FinalAccumOpenCLKernelCreator.cpp index a4650fe..19b383f 100644 --- a/Source/EmberCL/FinalAccumOpenCLKernelCreator.cpp +++ b/Source/EmberCL/FinalAccumOpenCLKernelCreator.cpp @@ -197,6 +197,7 @@ string FinalAccumOpenCLKernelCreator::CreateFinalAccumKernelString(bool early RgbToHsvFunctionString << HsvToRgbFunctionString << CalcAlphaFunctionString << + CurveAdjustFunctionString << SpatialFilterCLStructString; if (earlyClip) @@ -231,6 +232,8 @@ string FinalAccumOpenCLKernelCreator::CreateFinalAccumKernelString(bool early " __write_only image2d_t pixels,\n" " __constant SpatialFilterCL* spatialFilter,\n" " __constant real_t* filterCoefs,\n" + " __constant real4reals* csa,\n" + " const uint doCurves,\n" " const real_t alphaBase,\n" " const real_t alphaScale\n" "\t)\n" @@ -245,14 +248,11 @@ string FinalAccumOpenCLKernelCreator::CreateFinalAccumKernelString(bool early " finalCoord.x = GLOBAL_ID_X;\n" " finalCoord.y = (int)((spatialFilter->m_YAxisUp == 1) ? ((spatialFilter->m_FinalRasH - GLOBAL_ID_Y) - 1) : GLOBAL_ID_Y);\n" " float4floats finalColor;\n" - " real_t alpha, ls;\n" " int ii, jj;\n" " uint filterKRowIndex;\n" " const __global real4reals* accumBucket;\n" " real4reals newBucket;\n" " newBucket.m_Real4 = 0;\n" - " real4reals newRgb;\n" - " newRgb.m_Real4 = 0;\n" "\n" " for (jj = 0; jj < spatialFilter->m_FilterWidth; jj++)\n" " {\n" @@ -269,7 +269,7 @@ string FinalAccumOpenCLKernelCreator::CreateFinalAccumKernelString(bool early "\n"; //Not supporting 2 bytes per channel on the GPU. If the user wants it, run on the CPU. - if (earlyClip)//If early clip, simply assign values directly to the temp uint4 since they've been gamma corrected already, then write it straight to the output image below. + if (earlyClip)//If early clip, simply assign values directly to the temp float4 since they've been gamma corrected already, then write it straight to the output image below. { os << " finalColor.m_Float4.x = (float)newBucket.m_Real4.x;\n"//CPU side clamps, skip here because write_imagef() does the clamping for us. @@ -307,6 +307,14 @@ string FinalAccumOpenCLKernelCreator::CreateFinalAccumKernelString(bool early } os << + "\n" + " if (doCurves)\n" + " {\n" + " CurveAdjust(csa, &(finalColor.m_Floats[0]), 1);\n" + " CurveAdjust(csa, &(finalColor.m_Floats[1]), 2);\n" + " CurveAdjust(csa, &(finalColor.m_Floats[2]), 3);\n" + " }\n" + "\n" " finalColor.m_Float4 /= 255.0f;\n" " write_imagef(pixels, finalCoord, finalColor.m_Float4);\n"//Use write_imagef instead of write_imageui because only the former works when sharing with an OpenGL texture. " barrier(CLK_GLOBAL_MEM_FENCE);\n"//Required, or else page tearing will occur during interactive rendering. diff --git a/Source/EmberCL/RendererCL.cpp b/Source/EmberCL/RendererCL.cpp index 4fe07ca..5e8420a 100644 --- a/Source/EmberCL/RendererCL.cpp +++ b/Source/EmberCL/RendererCL.cpp @@ -33,6 +33,7 @@ RendererCL::RendererCL(uint platform, uint device, bool shared, GLuint output m_DEWidthsBufferName = "DEWidths"; m_DECoefIndicesBufferName = "DECoefIndices"; m_SpatialFilterCoefsBufferName = "SpatialFilterCoefs"; + m_CurvesCsaName = "CurvesCsa"; m_HistBufferName = "Hist"; m_AccumBufferName = "Accum"; m_FinalImageName = "Final"; @@ -282,6 +283,21 @@ bool RendererCL::WriteRandomPoints() template string RendererCL::IterKernel() { return m_IterKernel; } + +/// +/// Get the kernel string for the last built density filtering program. +/// +/// The string representation of the kernel for the last built density filtering program. +template +string RendererCL::DEKernel() { return m_DEOpenCLKernelCreator.GaussianDEKernel(Supersample(), m_DensityFilterCL.m_FilterWidth); } + +/// +/// Get the kernel string for the last built final accumulation program. +/// +/// The string representation of the kernel for the last built final accumulation program. +template +string RendererCL::FinalAccumKernel() { return m_FinalAccumOpenCLKernelCreator.FinalAccumKernel(EarlyClip(), Renderer::NumChannels(), Transparency()); } + /// /// Virtual functions overridden from RendererCLBase. /// @@ -551,17 +567,17 @@ bool RendererCL::Alloc() size_t accumLength = SuperSize() * sizeof(v4T); const char* loc = __FUNCTION__; - if (b && !(b = m_Wrapper.AddBuffer(m_EmberBufferName, sizeof(m_EmberCL)))) { m_ErrorReport.push_back(loc); } - if (b && !(b = m_Wrapper.AddBuffer(m_XformsBufferName, SizeOf(m_XformsCL)))) { m_ErrorReport.push_back(loc); } - if (b && !(b = m_Wrapper.AddBuffer(m_ParVarsBufferName, 128 * sizeof(T)))) { m_ErrorReport.push_back(loc); } - if (b && !(b = m_Wrapper.AddBuffer(m_DistBufferName, CHOOSE_XFORM_GRAIN))) { m_ErrorReport.push_back(loc); }//Will be resized for xaos. - if (b && !(b = m_Wrapper.AddBuffer(m_CarToRasBufferName, sizeof(m_CarToRasCL)))) { m_ErrorReport.push_back(loc); } - if (b && !(b = m_Wrapper.AddBuffer(m_DEFilterParamsBufferName, sizeof(m_DensityFilterCL)))) { m_ErrorReport.push_back(loc); } - if (b && !(b = m_Wrapper.AddBuffer(m_SpatialFilterParamsBufferName, sizeof(m_SpatialFilterCL)))) { m_ErrorReport.push_back(loc); } - - if (b && !(b = m_Wrapper.AddBuffer(m_HistBufferName, histLength))) { m_ErrorReport.push_back(loc); }//Histogram. Will memset to zero later. - if (b && !(b = m_Wrapper.AddBuffer(m_AccumBufferName, accumLength))) { m_ErrorReport.push_back(loc); }//Accum buffer. - if (b && !(b = m_Wrapper.AddBuffer(m_PointsBufferName, IterGridKernelCount() * sizeof(PointCL)))) { m_ErrorReport.push_back(loc); }//Points between iter calls. + if (b && !(b = m_Wrapper.AddBuffer(m_EmberBufferName, sizeof(m_EmberCL)))) { m_ErrorReport.push_back(loc); } + if (b && !(b = m_Wrapper.AddBuffer(m_XformsBufferName, SizeOf(m_XformsCL)))) { m_ErrorReport.push_back(loc); } + if (b && !(b = m_Wrapper.AddBuffer(m_ParVarsBufferName, 128 * sizeof(T)))) { m_ErrorReport.push_back(loc); } + if (b && !(b = m_Wrapper.AddBuffer(m_DistBufferName, CHOOSE_XFORM_GRAIN))) { m_ErrorReport.push_back(loc); }//Will be resized for xaos. + if (b && !(b = m_Wrapper.AddBuffer(m_CarToRasBufferName, sizeof(m_CarToRasCL)))) { m_ErrorReport.push_back(loc); } + if (b && !(b = m_Wrapper.AddBuffer(m_DEFilterParamsBufferName, sizeof(m_DensityFilterCL)))) { m_ErrorReport.push_back(loc); } + if (b && !(b = m_Wrapper.AddBuffer(m_SpatialFilterParamsBufferName, sizeof(m_SpatialFilterCL)))) { m_ErrorReport.push_back(loc); } + if (b && !(b = m_Wrapper.AddBuffer(m_CurvesCsaName, SizeOf(m_Csa.m_Entries)))) { m_ErrorReport.push_back(loc); } + if (b && !(b = m_Wrapper.AddBuffer(m_HistBufferName, histLength))) { m_ErrorReport.push_back(loc); }//Histogram. Will memset to zero later. + if (b && !(b = m_Wrapper.AddBuffer(m_AccumBufferName, accumLength))) { m_ErrorReport.push_back(loc); }//Accum buffer. + if (b && !(b = m_Wrapper.AddBuffer(m_PointsBufferName, IterGridKernelCount() * sizeof(PointCL)))) { m_ErrorReport.push_back(loc); }//Points between iter calls. LeaveResize(); @@ -809,8 +825,8 @@ bool RendererCL::RunIter(size_t iterCount, size_t temporalSample, size_t& ite //fuse = ((m_Calls % 4) == 0 ? 100u : 0u); #endif itersRemaining = iterCount - itersRan; - uint gridW = uint(min(ceil(double(itersRemaining) / double(iterCountPerBlock)), double(IterGridBlockWidth()))); - uint gridH = uint(min(ceil(double(itersRemaining) / double(gridW * iterCountPerBlock)), double(IterGridBlockHeight()))); + uint gridW = uint(std::min(ceil(double(itersRemaining) / double(iterCountPerBlock)), double(IterGridBlockWidth()))); + uint gridH = uint(std::min(ceil(double(itersRemaining) / double(gridW * iterCountPerBlock)), double(IterGridBlockHeight()))); uint iterCountThisLaunch = iterCountPerBlock * gridW * gridH; //Similar to what's done in the base class. @@ -1071,6 +1087,7 @@ eRenderStatus RendererCL::RunFinalAccum() uint gridH; uint blockW; uint blockH; + uint curvesSet = m_CurvesSet ? 1 : 0; const char* loc = __FUNCTION__; if (!m_Abort && accumKernelIndex != -1) @@ -1079,6 +1096,7 @@ eRenderStatus RendererCL::RunFinalAccum() m_SpatialFilterCL = ConvertSpatialFilter(); if (b && !(b = m_Wrapper.AddAndWriteBuffer(m_SpatialFilterParamsBufferName, reinterpret_cast(&m_SpatialFilterCL), sizeof(m_SpatialFilterCL)))) { m_ErrorReport.push_back(loc); } + if (b && !(b = m_Wrapper.AddAndWriteBuffer(m_CurvesCsaName, m_Csa.m_Entries.data(), SizeOf(m_Csa.m_Entries)))) { m_ErrorReport.push_back(loc); } //Since early clip requires gamma correcting the entire accumulator first, //it can't be done inside of the normal final accumulation kernel, so @@ -1119,6 +1137,9 @@ eRenderStatus RendererCL::RunFinalAccum() if (b && !(b = m_Wrapper.SetImageArg (accumKernelIndex, argIndex++, m_Wrapper.Shared(), m_FinalImageName))) { m_ErrorReport.push_back(loc); }//Final image. if (b && !(b = m_Wrapper.SetBufferArg(accumKernelIndex, argIndex++, m_SpatialFilterParamsBufferName))) { m_ErrorReport.push_back(loc); }//SpatialFilterCL. if (b && !(b = m_Wrapper.SetBufferArg(accumKernelIndex, argIndex++, m_SpatialFilterCoefsBufferName))) { m_ErrorReport.push_back(loc); }//Filter coefs. + if (b && !(b = m_Wrapper.SetBufferArg(accumKernelIndex, argIndex++, m_CurvesCsaName))) { m_ErrorReport.push_back(loc); }//Curve points. + + if (b && !(b = m_Wrapper.SetArg (accumKernelIndex, argIndex++, curvesSet))) { m_ErrorReport.push_back(loc); }//Do curves. if (b && !(b = m_Wrapper.SetArg (accumKernelIndex, argIndex++, alphaBase))) { m_ErrorReport.push_back(loc); }//Alpha base. if (b && !(b = m_Wrapper.SetArg (accumKernelIndex, argIndex++, alphaScale))) { m_ErrorReport.push_back(loc); }//Alpha scale. diff --git a/Source/EmberCL/RendererCL.h b/Source/EmberCL/RendererCL.h index 1478851..d08c1eb 100644 --- a/Source/EmberCL/RendererCL.h +++ b/Source/EmberCL/RendererCL.h @@ -121,6 +121,8 @@ public: bool WriteRandomPoints(); #endif string IterKernel(); + string DEKernel(); + string FinalAccumKernel(); //Virtual functions overridden from RendererCLBase. virtual bool ReadFinal(byte* pixels); @@ -193,6 +195,7 @@ private: string m_CarToRasBufferName; string m_DEFilterParamsBufferName; string m_SpatialFilterParamsBufferName; + string m_CurvesCsaName; string m_DECoefsBufferName; string m_DEWidthsBufferName; string m_DECoefIndicesBufferName; diff --git a/Source/EmberCommon/JpegUtils.h b/Source/EmberCommon/JpegUtils.h index 68787de..5fcc05c 100644 --- a/Source/EmberCommon/JpegUtils.h +++ b/Source/EmberCommon/JpegUtils.h @@ -146,7 +146,7 @@ static bool WritePng(const char* filename, byte* image, size_t width, size_t hei png_infop info_ptr; png_text text[PNG_COMMENT_MAX]; size_t i; - uint16 testbe = 1; + glm::uint16 testbe = 1; vector rows(height); text[0].compression = PNG_TEXT_COMPRESSION_NONE; diff --git a/Source/EmberGenome/EmberGenome.rc b/Source/EmberGenome/EmberGenome.rc index 1d67f0f..045fdab 100644 --- a/Source/EmberGenome/EmberGenome.rc +++ b/Source/EmberGenome/EmberGenome.rc @@ -49,8 +49,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,4,1,8 - PRODUCTVERSION 0,4,1,8 + FILEVERSION 0,4,1,9 + PRODUCTVERSION 0,4,1,9 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -67,12 +67,12 @@ BEGIN BEGIN VALUE "CompanyName", "Open Source" VALUE "FileDescription", "Manipulates fractal flames parameter files" - VALUE "FileVersion", "0.4.1.8" + VALUE "FileVersion", "0.4.1.9" VALUE "InternalName", "EmberGenome.rc" VALUE "LegalCopyright", "Copyright (C) Matt Feemster 2013, GPL v3" VALUE "OriginalFilename", "EmberGenome.rc" VALUE "ProductName", "Ember Genome" - VALUE "ProductVersion", "0.4.1.8" + VALUE "ProductVersion", "0.4.1.9" END END BLOCK "VarFileInfo" diff --git a/Source/EmberRender/EmberRender.cpp b/Source/EmberRender/EmberRender.cpp index a04340d..8053c26 100644 --- a/Source/EmberRender/EmberRender.cpp +++ b/Source/EmberRender/EmberRender.cpp @@ -158,7 +158,7 @@ bool EmberRender(EmberOptions& opt) { if (opt.Verbose() && embers.size() > 1) cout << "\nFlame = " << i + 1 << "/" << embers.size() << endl; - else + else if (embers.size() > 1) VerbosePrint(endl); if (opt.Supersample() > 0) diff --git a/Source/EmberRender/EmberRender.rc b/Source/EmberRender/EmberRender.rc index d9adf05..96dc832 100644 --- a/Source/EmberRender/EmberRender.rc +++ b/Source/EmberRender/EmberRender.rc @@ -49,8 +49,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,4,1,8 - PRODUCTVERSION 0,4,1,8 + FILEVERSION 0,4,1,9 + PRODUCTVERSION 0,4,1,9 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -67,12 +67,12 @@ BEGIN BEGIN VALUE "CompanyName", "Open Source" VALUE "FileDescription", "Renders fractal flames as single images" - VALUE "FileVersion", "0.4.1.8" + VALUE "FileVersion", "0.4.1.9" VALUE "InternalName", "EmberRender.rc" VALUE "LegalCopyright", "Copyright (C) Matt Feemster 2013, GPL v3" VALUE "OriginalFilename", "EmberRender.rc" VALUE "ProductName", "Ember Render" - VALUE "ProductVersion", "0.4.1.8" + VALUE "ProductVersion", "0.4.1.9" END END BLOCK "VarFileInfo" diff --git a/Source/EmberTester/EmberTester.cpp b/Source/EmberTester/EmberTester.cpp index 91c90ac..47deb50 100644 --- a/Source/EmberTester/EmberTester.cpp +++ b/Source/EmberTester/EmberTester.cpp @@ -1529,6 +1529,7 @@ void TestCpuGpuResults() renderer.CreateSpatialFilter(newAlloc); renderer.CreateDEFilter(newAlloc); renderer.ComputeBounds(); + renderer.ComputeQuality(); renderer.ComputeCamera(); renderer.AssignIterator(); @@ -1614,6 +1615,7 @@ void TestGpuVectorRead() renderer.CreateSpatialFilter(newAlloc); renderer.CreateDEFilter(newAlloc); renderer.ComputeBounds(); + renderer.ComputeQuality(); renderer.ComputeCamera(); renderer.AssignIterator(); @@ -1746,11 +1748,84 @@ double RandD(QTIsaac& rand) return ((((rand.Rand()^(rand.Rand()<<15))&0xfffffff)*3.72529e-09)-0.5); } +#define BEZ_POINT_LENGTH 4 + +void BezierSolve(double t, glm::vec2* src, double* w, glm::vec2& solution) +{ + double s, s2, s3, t2, t3, nom_x, nom_y, denom; + + s = 1 - t; + s2 = s * s; + s3 = s * s * s; + t2 = t * t; + t3 = t * t * t; + + nom_x = w[0] * s3 * src[0].x + w[1] * s2 * 3 * t * src[1].x + w[2] * s * 3 * t2 * src[2].x + w[3] * t3 * src[3].x; + + nom_y = w[0] * s3 * src[0].y + w[1] * s2 * 3 * t * src[1].y + w[2] * s * 3 * t2 * src[2].y + w[3] * t3 * src[3].y; + + denom = w[0] * s3 + w[1] * s2 * 3 * t + w[2] * s * 3 * t2 + w[3] * t3; + + + if (isnan(nom_x) || isnan(nom_y) || isnan(denom) || denom == 0) + return; + + solution.x = nom_x / denom; + solution.y = nom_y / denom; +} + +void BezierSetRect(glm::vec2* points, bool flip, glm::vec4& rect) +{ + double f; + + for (int i = 0; i < BEZ_POINT_LENGTH; i++) + { + if (flip) + f = 1 - points[i].y; + else + f = points[i].y; + + points[i].x = points[i].x * (rect.z - rect.x) + rect.x; + points[i].y = f * (rect.w - rect.y) + rect.y; + } +} + +void BezierUnsetRect(glm::vec2* points, bool flip, glm::vec4& rect) +{ + if ((rect.z - rect.x) == 0 || (rect.w - rect.y) == 0) + return; + + for (int i = 0; i < BEZ_POINT_LENGTH; i++) + { + points[i].x = (points[i].x - rect.x) / (rect.z - rect.x); + points[i].y = (points[i].y - rect.y) / (rect.w - rect.y); + + if (flip) + points[i].y = 1 - points[i].y; + } +} + +struct BezierPoints +{ + glm::vec2 points[4]; +}; + +struct BezierWeights +{ + double points[4]; +}; + int _tmain(int argc, _TCHAR* argv[]) { //int i; Timing t(4); QTIsaac rand; + glm::vec2 solution, src[4]; + double bezT = 1, w[4]; + BezierPoints curvePoints[4]; + BezierWeights curveWeights[4]; + + BezierSolve(bezT, src, w, solution); //cout << pow(-1, 5.1) << endl; @@ -1818,7 +1893,7 @@ int _tmain(int argc, _TCHAR* argv[]) //std::complex cd, cd2; //cd2 = sin(cd); - + /* t.Tic(); VariationList vlf; t.Toc("Creating VariationList"); @@ -1905,7 +1980,7 @@ int _tmain(int argc, _TCHAR* argv[]) t.Tic(); TestOperations(); t.Toc("TestOperations()"); - + */ //t.Tic(); //TestVarsSimilar(); //t.Toc("TestVarsSimilar()"); diff --git a/Source/Fractorium/AboutDialog.ui b/Source/Fractorium/AboutDialog.ui index 1850f40..4a13f20 100644 --- a/Source/Fractorium/AboutDialog.ui +++ b/Source/Fractorium/AboutDialog.ui @@ -58,7 +58,7 @@ - <html><head/><body><p align="center"><br/><span style=" font-size:12pt;">Fractorium 0.4.1.8 Beta</span></p><p align="center"><span style=" font-size:10pt;"><br/>A Qt-based fractal flame editor which uses a C++ re-write of the flam3 algorithm named Ember and a GPU capable version named EmberCL which implements a portion of the cuburn algorithm in OpenCL.</span></p><p align="center"><span style=" font-size:10pt;">Matt Feemster</span></p></body></html> + <html><head/><body><p align="center"><br/><span style=" font-size:12pt;">Fractorium 0.4.1.9 Beta</span></p><p align="center"><span style=" font-size:10pt;"><br/>A Qt-based fractal flame editor which uses a C++ re-write of the flam3 algorithm named Ember and a GPU capable version named EmberCL which implements a portion of the cuburn algorithm in OpenCL.</span></p><p align="center"><span style=" font-size:10pt;">Matt Feemster</span></p></body></html> Qt::RichText diff --git a/Source/Fractorium/CurvesGraphicsView.cpp b/Source/Fractorium/CurvesGraphicsView.cpp new file mode 100644 index 0000000..b237aaa --- /dev/null +++ b/Source/Fractorium/CurvesGraphicsView.cpp @@ -0,0 +1,213 @@ +#include "FractoriumPch.h" +#include "CurvesGraphicsView.h" + +/// +/// Construct the scene which will have a fixed rect. +/// Construct all points, pens and axes. +/// +/// Pass to the parent +CurvesGraphicsView::CurvesGraphicsView(QWidget* parent) + : QGraphicsView(parent) +{ + m_Scene.setSceneRect(0, 0, 245, 245); + + m_AllP1 = new EllipseItem(QRectF(-5, -5, 10, 10), 0, 1, this); + m_AllP1->setBrush(QBrush(Qt::GlobalColor::black)); + m_AllP2 = new EllipseItem(QRectF(-5, -5, 10, 10), 0, 2, this); + m_AllP2->setBrush(QBrush(Qt::GlobalColor::black)); + + m_RedP1 = new EllipseItem(QRectF(-5, -5, 10, 10), 1, 1, this); + m_RedP1->setBrush(QBrush(Qt::GlobalColor::red)); + m_RedP2 = new EllipseItem(QRectF(-5, -5, 10, 10), 1, 2, this); + m_RedP2->setBrush(QBrush(Qt::GlobalColor::red)); + + m_GrnP1 = new EllipseItem(QRectF(-5, -5, 10, 10), 2, 1, this); + m_GrnP1->setBrush(QBrush(Qt::GlobalColor::green)); + m_GrnP2 = new EllipseItem(QRectF(-5, -5, 10, 10), 2, 2, this); + m_GrnP2->setBrush(QBrush(Qt::GlobalColor::green)); + + m_BluP1 = new EllipseItem(QRectF(-5, -5, 10, 10), 3, 1, this); + m_BluP1->setBrush(QBrush(Qt::GlobalColor::blue)); + m_BluP2 = new EllipseItem(QRectF(-5, -5, 10, 10), 3, 2, this); + m_BluP2->setBrush(QBrush(Qt::GlobalColor::blue)); + + m_AxisPen = QPen(Qt::GlobalColor::white); + m_XLine = new QGraphicsLineItem(); + m_XLine->setPen(m_AxisPen); + m_XLine->setZValue(0); + m_YLine = new QGraphicsLineItem(); + m_YLine->setPen(m_AxisPen); + m_YLine->setZValue(0); + + m_Scene.addItem(m_AllP1); m_Points[0].first = m_AllP1; m_AllP1->setZValue(2); + m_Scene.addItem(m_AllP2); m_Points[0].second = m_AllP2; m_AllP2->setZValue(2); + m_Scene.addItem(m_RedP1); m_Points[1].first = m_RedP1; + m_Scene.addItem(m_RedP2); m_Points[1].second = m_RedP2; + m_Scene.addItem(m_GrnP1); m_Points[2].first = m_GrnP1; + m_Scene.addItem(m_GrnP2); m_Points[2].second = m_GrnP2; + m_Scene.addItem(m_BluP1); m_Points[3].first = m_BluP1; + m_Scene.addItem(m_BluP2); m_Points[3].second = m_BluP2; + m_Scene.addItem(m_XLine); + m_Scene.addItem(m_YLine); + + m_APen = QPen(Qt::GlobalColor::black); m_Pens[0] = &m_APen; + m_RPen = QPen(Qt::GlobalColor::red); m_Pens[1] = &m_RPen; + m_GPen = QPen(Qt::GlobalColor::green); m_Pens[2] = &m_GPen; + m_BPen = QPen(Qt::GlobalColor::blue); m_Pens[3] = &m_BPen; + + m_APen.setWidth(2); + m_RPen.setWidth(2); + m_GPen.setWidth(2); + m_BPen.setWidth(2); + + setScene(&m_Scene); + SetTop(CurveIndex::ALL); + //qDebug() << "Original scene rect before setting anything is: " << sceneRect(); + m_OriginalRect = sceneRect(); + show(); + //qDebug() << "Original scene rect is: " << m_OriginalRect; +} + +/// +/// Get the position of a given point within a given curve. +/// +/// The curve whose point value will be retrieved, 0-3. +/// The point within the curve value will be retrieved, 1-2. +/// The position of the point. X,Y will each be within 0-1. +void CurvesGraphicsView::PointChanged(int curveIndex, int pointIndex, const QPointF& point) +{ + double x = point.x() / width(); + double y = (height() - point.y()) / height(); + + emit PointChangedSignal(curveIndex, pointIndex, QPointF(x, y)); +} + +/// +/// Get the position of a given point within a given curve. +/// +/// The curve whose point value will be retrieved, 0-3. +/// The point within the curve whose value will be retrieved, 1-2. +/// The position of the point. X,Y will each be within 0-1. +QPointF CurvesGraphicsView::Get(int curveIndex, int pointIndex) +{ + if (curveIndex < 4) + { + EllipseItem* item = (pointIndex == 1) ? m_Points[curveIndex].first : m_Points[curveIndex].second; + + return QPointF(item->pos().x() / width(), (height() - item->pos().y()) / height()); + } + + return QPointF(); +} + +/// +/// Set the position of a given point within a given curve. +/// +/// The curve whose point will be set, 0-3. +/// The point within the curve which will be set, 1-2 +/// The position to set the point to. X,Y will each be within 0-1. +void CurvesGraphicsView::Set(int curveIndex, int pointIndex, const QPointF& point) +{ + if (curveIndex < 4) + { + if (pointIndex == 1) + m_Points[curveIndex].first->setPos(point.x() * width(), (1.0 - point.y()) * height());//Scale to scene dimensions, Y axis is flipped. + else + m_Points[curveIndex].second->setPos(point.x() * width(), (1.0 - point.y()) * height()); + } +} + +/// +/// Set the topmost curve but setting its Z value. +/// All other curves will get a value one less. +/// +/// The curve to set +void CurvesGraphicsView::SetTop(CurveIndex curveIndex) +{ + int index; + + switch (curveIndex) + { + case CurveIndex::ALL: + index = 0; + break; + case CurveIndex::RED: + index = 1; + break; + case CurveIndex::GREEN: + index = 2; + break; + case CurveIndex::BLUE: + default: + index = 3; + } + + for (int i = 0; i < 4; i++) + { + if (i == index) + { + m_Points[i].first->setZValue(2); + m_Points[i].second->setZValue(2); + } + else + { + m_Points[i].first->setZValue(1); + m_Points[i].second->setZValue(1); + } + } +} + +/// +/// Overridden paint even which draws the points, axes and curves. +/// +/// Ignored +void CurvesGraphicsView::paintEvent(QPaintEvent* e) +{ + QGraphicsView::paintEvent(e); + + int i; + QRectF rect = scene()->sceneRect(); + double w2 = width() / 2; + double h2 = height() / 2; + + //Draw axis lines. + m_XLine->setLine(QLineF(0, h2, width(), h2)); + m_YLine->setLine(QLineF(w2, 0, w2, height())); + + //This must be constructed every time and cannot be a member. + QPainter painter(viewport()); + painter.setClipRect(rect); + painter.setRenderHint(QPainter::Antialiasing); + + //Create 4 new paths. These must be constructed every time and cannot be members. + QPainterPath paths[4] = + { + QPainterPath(mapFromScene(rect.bottomLeft())), + QPainterPath(mapFromScene(rect.bottomLeft())), + QPainterPath(mapFromScene(rect.bottomLeft())), + QPainterPath(mapFromScene(rect.bottomLeft())) + }; + + //Draw paths for all but the topmost curve. + for (i = 0; i < 4; i++) + { + paths[i].cubicTo(m_Points[i].first->pos(), m_Points[i].second->pos(), mapFromScene(rect.topRight())); + + if (m_Points[i].first->zValue() == 1) + { + painter.setPen(*m_Pens[i]); + painter.drawPath(paths[i]); + } + } + + //Draw the topmost curve. + for (i = 0; i < 4; i++) + { + if (m_Points[i].first->zValue() == 2) + { + painter.setPen(*m_Pens[i]); + painter.drawPath(paths[i]); + break; + } + } +} \ No newline at end of file diff --git a/Source/Fractorium/CurvesGraphicsView.h b/Source/Fractorium/CurvesGraphicsView.h new file mode 100644 index 0000000..4c430f5 --- /dev/null +++ b/Source/Fractorium/CurvesGraphicsView.h @@ -0,0 +1,150 @@ +#pragma once +#include "FractoriumPch.h" + +/// +/// CurvesGraphicsView and EllipseItem classes. +/// + +class EllipseItem; + +/// +/// Enumeration used for setting values on a specific curve. +/// +enum CurveIndex +{ + ALL, + RED, + GREEN, + BLUE +}; + +/// +/// Derivation to display points on bezier cuves for the user to drag. +/// Selection logic and updating is handled by the base class. +/// Changes here will affect the current ember and vice versa. +/// The points, axis lines and pens are kept as members and the curves +/// themselves are drawn on the fly during each paint event. +/// Pointers to these are kept in arrays to make manipulating them in loops easier. +/// Note that this must work off a fixed rect size, hence why the control is not resizeable. +/// +class CurvesGraphicsView : public QGraphicsView +{ + Q_OBJECT + +public: + CurvesGraphicsView(QWidget* parent = 0); + + void PointChanged(int curveIndex, int pointIndex, const QPointF& point); + QPointF Get(int curveIndex, int pointIndex); + void Set(int curveIndex, int pointIndex, const QPointF& point); + void SetTop(CurveIndex curveIndex); + +Q_SIGNALS: + void PointChangedSignal(int curveIndex, int pointIndex, const QPointF& point); + +protected: + virtual void paintEvent(QPaintEvent* e) override; + + QPen m_APen; + QPen m_RPen; + QPen m_GPen; + QPen m_BPen; + QPen m_AxisPen; + EllipseItem* m_AllP1; + EllipseItem* m_AllP2; + EllipseItem* m_RedP1; + EllipseItem* m_RedP2; + EllipseItem* m_GrnP1; + EllipseItem* m_GrnP2; + EllipseItem* m_BluP1; + EllipseItem* m_BluP2; + QGraphicsLineItem* m_XLine; + QGraphicsLineItem* m_YLine; + QPen* m_Pens[4]; + QGraphicsScene m_Scene; + QRectF m_OriginalRect; + std::pair m_Points[4]; +}; + +/// +/// Derivation for draggable points needed to trigger an event whenever the item is changed. +/// Custom drawing is also done to omit drawing a selection rectangle. +/// +class EllipseItem : public QGraphicsEllipseItem +{ +public: + /// + /// Construct the point and specify the curve index it's part of, as well as the + /// point index within the curve. + /// + /// Pass to the parent + /// The curve's index this point is a part of, 0-3. + /// The point index within the curve + /// The graphics view this point is displayed on + /// The parent widget of this item + EllipseItem(const QRectF &rect, int curveIndex, int pointIndex, CurvesGraphicsView* viewParent, QGraphicsItem *parent = 0) + : QGraphicsEllipseItem(rect, parent) + { + setFlag(QGraphicsItem::ItemSendsScenePositionChanges); + setFlag(QGraphicsItem::ItemIsSelectable); + setFlag(QGraphicsItem::ItemIsMovable); + setPen(Qt::NoPen); + + m_CurveIndex = curveIndex; + m_PointIndex = pointIndex; + m_ViewParent = viewParent; + } + + /// + /// Index properties, getters only. + /// + int CurveIndex() const { return m_CurveIndex; } + int PointIndex() const { return m_PointIndex; } + +protected: + /// + /// Overridden paint event to disable the selection rectangle. + /// + /// Unused and just passed to QGraphicsEllipseItem::paint() + /// Drawing options used which will have the QStyle::State_Selected flag unset + /// Unused and just passed to QGraphicsEllipseItem::paint() + virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override + { + QStyleOptionGraphicsItem myOption(*option); + myOption.state &= ~QStyle::State_Selected; + QGraphicsEllipseItem::paint(painter, &myOption, widget); + } + + /// + /// Overridden itemChange event to notify the parent control that it has moved. + /// Movement is also restriced to the scene rect. + /// + /// Action is only taken if this value equals ItemPositionChange + /// The new position. This will be clamped to the scene rect. + /// The new position + virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value) override + { + if (change == ItemPositionChange && scene()) + { + //Value is the new position. + QPointF newPos = value.toPointF(); + QRectF rect = scene()->sceneRect(); + + if (!rect.contains(newPos)) + { + //Keep the item inside the scene rect. + newPos.setX(qMin(rect.right(), qMax(newPos.x(), rect.left()))); + newPos.setY(qMin(rect.bottom(), qMax(newPos.y(), rect.top()))); + } + + m_ViewParent->PointChanged(m_CurveIndex, m_PointIndex, newPos); + return newPos; + } + + return QGraphicsEllipseItem::itemChange(change, value); + } + + int m_CurveIndex; + int m_PointIndex; + CurvesGraphicsView* m_ViewParent; +}; diff --git a/Source/Fractorium/EmberFile.h b/Source/Fractorium/EmberFile.h index 488295c..f923996 100644 --- a/Source/Fractorium/EmberFile.h +++ b/Source/Fractorium/EmberFile.h @@ -84,6 +84,23 @@ public: return m_Embers.size(); } + /// + /// Delete the ember at the given index. + /// Will not delete anything if the size is already 1. + /// + /// The index of the ember to delete + /// True if successfully deleted, else false. + bool Delete(size_t index) + { + if (Size() > 1 && index < Size()) + { + m_Embers.erase(m_Embers.begin() + index); + return true; + } + else + return false; + } + /// /// Ensure all ember names are unique. /// @@ -116,6 +133,7 @@ public: /// /// Ensures a given input filename is unique by appending a count to the end. /// + /// The filename to ensure is unique /// The passed in name if it was unique, else a uniquely made name. static QString UniqueFilename(const QString& filename) { diff --git a/Source/Fractorium/FinalRenderDialog.cpp b/Source/Fractorium/FinalRenderDialog.cpp index bb58405..c526ed1 100644 --- a/Source/Fractorium/FinalRenderDialog.cpp +++ b/Source/Fractorium/FinalRenderDialog.cpp @@ -139,7 +139,7 @@ FractoriumFinalRenderDialog::FractoriumFinalRenderDialog(FractoriumSettings* set QSize s = size(); int desktopHeight = qApp->desktop()->availableGeometry().height(); - s.setHeight(min(s.height(), int(double(desktopHeight * 0.90)))); + s.setHeight(std::min(s.height(), int(double(desktopHeight * 0.90)))); setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, s, qApp->desktop()->availableGeometry())); QWidget* w = SetTabOrder(this, ui.FinalRenderEarlyClipCheckBox, ui.FinalRenderYAxisUpCheckBox); diff --git a/Source/Fractorium/FinalRenderEmberController.cpp b/Source/Fractorium/FinalRenderEmberController.cpp index a28847d..91bdd9f 100644 --- a/Source/Fractorium/FinalRenderEmberController.cpp +++ b/Source/Fractorium/FinalRenderEmberController.cpp @@ -115,8 +115,8 @@ FinalRenderEmberController::FinalRenderEmberController(FractoriumFinalRenderD m_PreviewEmber = *m_Ember; m_PreviewEmber.m_Quality = 100; m_PreviewEmber.m_TemporalSamples = 1; - m_PreviewEmber.m_FinalRasW = max(1, min(maxDim, size_t(scalePercentage * m_Ember->m_FinalRasW)));//Ensure neither is zero. - m_PreviewEmber.m_FinalRasH = max(1, min(maxDim, size_t(scalePercentage * m_Ember->m_FinalRasH))); + m_PreviewEmber.m_FinalRasW = std::max(1, std::min(maxDim, size_t(scalePercentage * m_Ember->m_FinalRasW)));//Ensure neither is zero. + m_PreviewEmber.m_FinalRasH = std::max(1, std::min(maxDim, size_t(scalePercentage * m_Ember->m_FinalRasH))); m_PreviewEmber.m_PixelsPerUnit = scalePercentage * m_Ember->m_PixelsPerUnit; m_FinalPreviewRenderer->EarlyClip(m_FinalRenderDialog->EarlyClip()); @@ -597,6 +597,7 @@ tuple FinalRenderEmberController::SyncAndComputeMemor m_Renderer->CreateTemporalFilter(b); m_Renderer->NumChannels(channels); m_Renderer->ComputeBounds(); + m_Renderer->ComputeQuality(); m_Renderer->ComputeCamera(); CancelPreviewRender(); m_FinalPreviewRenderFunc(); @@ -741,8 +742,8 @@ void FinalRenderEmberController::SyncGuiToEmber(Ember& ember, size_t width h = ember.m_OrigFinalRasH * hScale; } - w = max(w, 10); - h = max(h, 10); + w = std::max(w, 10); + h = std::max(h, 10); ember.SetSizeAndAdjustScale(w, h, false, m_FinalRenderDialog->Scale()); ember.m_Quality = m_FinalRenderDialog->m_QualitySpin->value(); diff --git a/Source/Fractorium/Fractorium.cpp b/Source/Fractorium/Fractorium.cpp index 7aacea1..067941a 100644 --- a/Source/Fractorium/Fractorium.cpp +++ b/Source/Fractorium/Fractorium.cpp @@ -264,8 +264,9 @@ void Fractorium::dockLocationChanged(Qt::DockWidgetArea area) /// /// -/// Event filter for taking special action on dock widget resize events, -/// which in turn trigger GLParentScrollArea events. +/// Event filter for taking special action on: +/// Dock widget resize events, which in turn trigger GLParentScrollArea events. +/// Library tree key events, specifically delete. /// /// The object /// The eevent @@ -278,6 +279,19 @@ bool Fractorium::eventFilter(QObject* o, QEvent* e) m_HeightSpin->DoubleClickNonZero(ui.GLParentScrollArea->height()); //qDebug() << "scroll area resized"; } + else if (o == ui.LibraryTree) + { + if (QKeyEvent* ke = dynamic_cast(e)) + { + if (ke->key() == Qt::Key_Delete && e->type() == QEvent::KeyRelease) + { + auto p = GetCurrentEmberIndex(); + + if (ui.LibraryTree->topLevelItem(0)->childCount() > 1 && p.second) + OnDelete(p); + } + } + } return QMainWindow::eventFilter(o, e); } diff --git a/Source/Fractorium/Fractorium.h b/Source/Fractorium/Fractorium.h index e56b557..77c38ca 100644 --- a/Source/Fractorium/Fractorium.h +++ b/Source/Fractorium/Fractorium.h @@ -10,6 +10,7 @@ #include "FinalRenderDialog.h" #include "OptionsDialog.h" #include "AboutDialog.h" +#include "CurvesGraphicsView.h" /// /// Fractorium class. @@ -140,6 +141,11 @@ public slots: void OnSaveEntireFileAsXmlButtonClicked(bool checked); void OnSaveCurrentToOpenedFileButtonClicked(bool checked); + //Library. + void OnEmberTreeItemChanged(QTreeWidgetItem* item, int col); + void OnEmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col); + void OnDelete(const pair& p); + //Params. void OnBrightnessChanged(double d);//Color. void OnGammaChanged(double d); @@ -222,7 +228,13 @@ public slots: void OnXformDirectColorChanged(double d); void OnSoloXformCheckBoxStateChanged(int state); void OnXformRefPaletteResized(int logicalIndex, int oldSize, int newSize); - + void OnResetCurvesButtonClicked(bool checked); + void OnCurvesPointChanged(int curveIndex, int pointIndex, const QPointF& point); + void OnCurvesAllRadioButtonToggled(bool checked); + void OnCurvesRedRadioButtonToggled(bool checked); + void OnCurvesGreenRadioButtonToggled(bool checked); + void OnCurvesBlueRadioButtonToggled(bool checked); + //Xforms Variations. void OnVariationSpinBoxValueChanged(double d); void OnTreeHeaderSectionClicked(int); @@ -242,10 +254,6 @@ public slots: void OnPaletteRandomSelectButtonClicked(bool checked); void OnPaletteRandomAdjustButtonClicked(bool checked); - //Library. - void OnEmberTreeItemChanged(QTreeWidgetItem* item, int col); - void OnEmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col); - //Rendering/progress. void StartRenderTimer(); void IdleTimer(); @@ -287,6 +295,9 @@ private: //Embers. bool HaveFinal(); + //Library. + pair GetCurrentEmberIndex(); + //Params. //Xforms. @@ -304,8 +315,6 @@ private: //Palette. void ResetPaletteControls(); - //Library. - //Info. void UpdateHistogramBounds(); void ErrorReportToQTextEdit(const vector& errors, QTextEdit* textEdit, bool clear = true); diff --git a/Source/Fractorium/Fractorium.rc b/Source/Fractorium/Fractorium.rc index a55d353..4b10610 100644 Binary files a/Source/Fractorium/Fractorium.rc and b/Source/Fractorium/Fractorium.rc differ diff --git a/Source/Fractorium/Fractorium.ui b/Source/Fractorium/Fractorium.ui index e2c7779..d3db396 100644 --- a/Source/Fractorium/Fractorium.ui +++ b/Source/Fractorium/Fractorium.ui @@ -353,7 +353,7 @@ QTabWidget::Triangular - 2 + 0 true @@ -420,7 +420,7 @@ - Qt::StrongFocus + Qt::WheelFocus Qt::CustomContextMenu @@ -504,8 +504,8 @@ 0 0 - 73 - 837 + 259 + 852 @@ -2285,7 +2285,7 @@ SpinBox QTabWidget::Triangular - 1 + 2 @@ -2775,6 +2775,134 @@ SpinBox + + + + + 0 + 0 + + + + + 245 + 245 + + + + + 245 + 245 + + + + true + + + false + + + QFrame::NoFrame + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + + + 219 + 219 + 219 + + + + + + 0.000000000000000 + 0.000000000000000 + 245.000000000000000 + 245.000000000000000 + + + + QGraphicsView::NoAnchor + + + QGraphicsView::FullViewportUpdate + + + + + + + Curve + + + + 6 + + + 2 + + + 6 + + + 6 + + + + + All + + + true + + + + + + + Red + + + + + + + Green + + + + + + + Blue + + + + + + + + + + + 0 + 0 + + + + Reset Curves + + + @@ -2825,8 +2953,8 @@ SpinBox 0 0 - 245 - 747 + 118 + 618 @@ -5048,8 +5176,8 @@ SpinBox 0 0 - 73 - 454 + 259 + 853 @@ -5855,6 +5983,11 @@ SpinBox
GLWidget.h
1 + + CurvesGraphicsView + QGraphicsView +
curvesgraphicsview.h
+
DockWidget diff --git a/Source/Fractorium/FractoriumEmberController.cpp b/Source/Fractorium/FractoriumEmberController.cpp index a1d13f7..ff5c89e 100644 --- a/Source/Fractorium/FractoriumEmberController.cpp +++ b/Source/Fractorium/FractoriumEmberController.cpp @@ -95,7 +95,7 @@ FractoriumEmberController::FractoriumEmberController(Fractorium* fractorium) m_PreviewRun = true; m_PreviewRunning = true; - m_PreviewRenderer->ThreadCount(max(1u, Timing::ProcessorCount() - 1));//Leave one processor free so the GUI can breathe. + m_PreviewRenderer->ThreadCount(std::max(1u, Timing::ProcessorCount() - 1));//Leave one processor free so the GUI can breathe. QTreeWidget* tree = m_Fractorium->ui.LibraryTree; if (QTreeWidgetItem* top = tree->topLevelItem(0)) @@ -208,7 +208,7 @@ void FractoriumEmberController::SetEmber(size_t index) /// Wrapper to call a function, then optionally add the requested action to the rendering queue. ///
/// The function to call -/// True to update renderer, else false. Default: false. +/// True to update renderer, else false. Default: true. /// The action to add to the rendering queue. Default: FULL_RENDER. template void FractoriumEmberController::Update(std::function func, bool updateRender, eProcessAction action) diff --git a/Source/Fractorium/FractoriumEmberController.h b/Source/Fractorium/FractoriumEmberController.h index 008f374..4969aab 100644 --- a/Source/Fractorium/FractoriumEmberController.h +++ b/Source/Fractorium/FractoriumEmberController.h @@ -55,7 +55,7 @@ public: virtual void CopyTempPalette(Palette& palette) { } #endif virtual void SetEmber(size_t index) { } - virtual void Clear() { } + //virtual void Clear() { } virtual void AddXform() { } virtual void DuplicateXform() { } virtual void ClearCurrentXform() { } @@ -98,6 +98,17 @@ public: //Toolbar. + //Library. + virtual void SyncNames() { } + virtual void SyncPointers() { } + virtual void FillLibraryTree(int selectIndex = -1) { } + virtual void UpdateLibraryTree() { } + virtual void EmberTreeItemChanged(QTreeWidgetItem* item, int col) { } + virtual void EmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col) { } + virtual void RenderPreviews(uint start = UINT_MAX, uint end = UINT_MAX) { } + virtual void StopPreviewRender() { } + virtual void Delete(const pair& p) { } + //Params. virtual void SetCenter(double x, double y) { } virtual void FillParamTablesAndPalette() { } @@ -156,6 +167,8 @@ public: virtual void XformColorSpeedChanged(double d) { } virtual void XformOpacityChanged(double d) { } virtual void XformDirectColorChanged(double d) { } + virtual void ClearColorCurves() { } + virtual void ColorCurveChanged(int curveIndex, int pointInxed, const QPointF& point) { }//need to put this in a different section because it's not xform specific.//TODO void SetPaletteRefTable(QPixmap* pixmap); //Xforms Variations. @@ -177,15 +190,6 @@ public: virtual QRgb GetQRgbFromPaletteIndex(uint i) { return QRgb(); } virtual void PaletteCellClicked(int row, int col) { } - //Library. - virtual void SyncNames() { } - virtual void FillLibraryTree(int selectIndex = -1) { } - virtual void UpdateLibraryTree() { } - virtual void EmberTreeItemChanged(QTreeWidgetItem* item, int col) { } - virtual void EmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col) { } - virtual void RenderPreviews(uint start = UINT_MAX, uint end = UINT_MAX) { } - virtual void StopPreviewRender() { } - //Info. //Rendering/progress. @@ -198,6 +202,7 @@ public: void StartRenderTimer(); void DelayedStartRenderTimer(); void StopRenderTimer(bool wait); + void ClearFinalImages(); void Shutdown(); void UpdateRender(eProcessAction action = FULL_RENDER); void DeleteRenderer(); @@ -271,7 +276,7 @@ public: virtual void CopyTempPalette(Palette& palette) override; #endif virtual void SetEmber(size_t index) override; - virtual void Clear() override { } + //virtual void Clear() override { } virtual void AddXform() override; virtual void DuplicateXform() override; virtual void ClearCurrentXform() override; @@ -317,6 +322,17 @@ public: //Toolbar. + //Library. + virtual void SyncNames() override; + virtual void SyncPointers() override; + virtual void FillLibraryTree(int selectIndex = -1) override; + virtual void UpdateLibraryTree() override; + virtual void Delete(const pair& p) override; + virtual void EmberTreeItemChanged(QTreeWidgetItem* item, int col) override; + virtual void EmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col) override; + virtual void RenderPreviews(uint start = UINT_MAX, uint end = UINT_MAX) override; + virtual void StopPreviewRender() override; + //Params. virtual void SetCenter(double x, double y) override; virtual void FillParamTablesAndPalette() override; @@ -378,6 +394,8 @@ public: virtual void XformColorSpeedChanged(double d) override; virtual void XformOpacityChanged(double d) override; virtual void XformDirectColorChanged(double d) override; + virtual void ClearColorCurves() override; + virtual void ColorCurveChanged(int curveIndex, int pointInxed, const QPointF& point) override; void FillColorWithXform(Xform* xform); //Xforms Variations. @@ -400,15 +418,6 @@ public: virtual QRgb GetQRgbFromPaletteIndex(uint i) override { return QRgb(); } virtual void PaletteCellClicked(int row, int col) override; - //Library. - virtual void SyncNames() override; - virtual void FillLibraryTree(int selectIndex = -1) override; - virtual void UpdateLibraryTree() override; - virtual void EmberTreeItemChanged(QTreeWidgetItem* item, int col) override; - virtual void EmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col) override; - virtual void RenderPreviews(uint start = UINT_MAX, uint end = UINT_MAX) override; - virtual void StopPreviewRender() override; - //Info. //Rendering/progress. @@ -433,6 +442,7 @@ private: //Xforms Color. void SetCurrentXformColorIndex(double d); + void FillCurvesControl(); //Palette. void UpdateAdjustedPaletteGUI(Palette& palette); diff --git a/Source/Fractorium/FractoriumLibrary.cpp b/Source/Fractorium/FractoriumLibrary.cpp index bb76feb..c759630 100644 --- a/Source/Fractorium/FractoriumLibrary.cpp +++ b/Source/Fractorium/FractoriumLibrary.cpp @@ -8,6 +8,35 @@ void Fractorium::InitLibraryUI() { connect(ui.LibraryTree, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(OnEmberTreeItemChanged(QTreeWidgetItem*, int)), Qt::QueuedConnection); connect(ui.LibraryTree, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(OnEmberTreeItemDoubleClicked(QTreeWidgetItem*, int)), Qt::QueuedConnection); + connect(ui.LibraryTree, SIGNAL(itemActivated(QTreeWidgetItem*, int)), this, SLOT(OnEmberTreeItemDoubleClicked(QTreeWidgetItem*, int)), Qt::QueuedConnection); + ui.LibraryTree->installEventFilter(this);//Needed for keypress events other than enter. +} + + +/// +/// Get the index of the currently selected ember in the library tree. +/// +/// A pair containing the index of the item clicked and a pointer to the item +pair Fractorium::GetCurrentEmberIndex() +{ + size_t index = 0; + QTreeWidgetItem* item = nullptr; + QTreeWidget* tree = ui.LibraryTree; + + if (QTreeWidgetItem* top = tree->topLevelItem(0)) + { + for (int i = 0; i < top->childCount(); i++)//Iterate through all of the children, which will represent the open embers. + { + item = top->child(index); + + if (item && !item->isSelected()) + index++; + else + break; + } + } + + return pair(index, item); } /// @@ -30,11 +59,10 @@ void FractoriumEmberController::SyncNames() { EmberTreeWidgetItem* item; QTreeWidget* tree = m_Fractorium->ui.LibraryTree; - QTreeWidgetItem* top = tree->topLevelItem(0); tree->blockSignals(true); - if (top) + if (QTreeWidgetItem* top = tree->topLevelItem(0)) { for (int i = 0; i < top->childCount(); i++)//Iterate through all of the children, which will represent the open embers. { @@ -46,6 +74,31 @@ void FractoriumEmberController::SyncNames() tree->blockSignals(false); } +/// +/// Set all libary tree entries to point to the underlying ember they represent. +/// +template +void FractoriumEmberController::SyncPointers() +{ + EmberTreeWidgetItem* item; + QTreeWidget* tree = m_Fractorium->ui.LibraryTree; + + tree->blockSignals(true); + + if (QTreeWidgetItem* top = tree->topLevelItem(0)) + { + size_t childCount = top->childCount(); + + for (int i = 0; i < childCount; i++)//Iterate through all of the children, which will represent the open embers. + { + if ((item = dynamic_cast*>(top->child(i))) && i < m_EmberFile.Size())//Cast the child widget to the EmberTreeWidgetItem type. + item->SetEmberPointer(&m_EmberFile.m_Embers[i]); + } + } + + tree->blockSignals(false); +} + /// /// Fill the library tree with the names of the embers in the /// currently opened file. @@ -135,12 +188,7 @@ void FractoriumEmberController::UpdateLibraryTree() //When adding elements to the vector, they may have been reshuffled which will have invalidated //the pointers contained in the EmberTreeWidgetItems. So reassign all pointers here. - for (i = 0; i < m_EmberFile.Size(); i++) - { - if (EmberTreeWidgetItem* emberItem = dynamic_cast*>(top->child(i))) - emberItem->SetEmberPointer(&m_EmberFile.m_Embers[i]); - } - + SyncPointers(); tree->blockSignals(false); RenderPreviews(childCount, m_EmberFile.Size()); } @@ -221,6 +269,47 @@ void FractoriumEmberController::EmberTreeItemDoubleClicked(QTreeWidgetItem* i void Fractorium::OnEmberTreeItemDoubleClicked(QTreeWidgetItem* item, int col) { m_Controller->EmberTreeItemDoubleClicked(item, col); } +/// +/// Delete the currently selected item in the tree. +/// Note this is not necessarilly the current ember, it's just the item +/// in the tree that is selected. +/// +/// A pair containing the index of the item clicked and a pointer to the item +template +void FractoriumEmberController::Delete(const pair& p) +{ + QTreeWidget* tree = m_Fractorium->ui.LibraryTree; + + tree->blockSignals(true); + + if (m_EmberFile.Delete(p.first)) + { + delete p.second; + SyncPointers(); + } + + tree->blockSignals(false); + + //If there is now only one item left and it wasn't selected, select it. + if (QTreeWidgetItem* top = tree->topLevelItem(0)) + { + if (top->childCount() == 1) + if (auto item = dynamic_cast*>(top->child(0))) + if (item->GetEmber()->m_Name != m_Ember.m_Name) + EmberTreeItemDoubleClicked(top->child(0), 0); + } +} + +/// +/// Called when the user presses and releases the delete key while the library tree has the focus, +/// and an item is selected. +/// +/// A pair containing the index of the item clicked and a pointer to the item +void Fractorium::OnDelete(const pair& p) +{ + m_Controller->Delete(p); +} + /// /// Stop the preview renderer if it's already running. /// Clear all of the existing preview images, then start the preview rendering thread. diff --git a/Source/Fractorium/FractoriumMenus.cpp b/Source/Fractorium/FractoriumMenus.cpp index b8dcad4..3a68655 100644 --- a/Source/Fractorium/FractoriumMenus.cpp +++ b/Source/Fractorium/FractoriumMenus.cpp @@ -252,7 +252,11 @@ void FractoriumEmberController::SaveCurrentAsXml() QString filename; FractoriumSettings* s = m_Fractorium->m_Settings; - if (QFile::exists(m_LastSaveCurrent)) + if (s->SaveAutoUnique() && m_LastSaveCurrent != "") + { + filename = EmberFile::UniqueFilename(m_LastSaveCurrent); + } + else if (QFile::exists(m_LastSaveCurrent)) { filename = m_LastSaveCurrent; } @@ -281,7 +285,9 @@ void FractoriumEmberController::SaveCurrentAsXml() if (writer.Save(filename.toStdString().c_str(), ember, 0, true, false, true)) { s->SaveFolder(fileInfo.canonicalPath()); - m_LastSaveCurrent = filename; + + if (!s->SaveAutoUnique() || m_LastSaveCurrent == "")//Only save filename on first time through when doing auto unique names. + m_LastSaveCurrent = filename; } else m_Fractorium->ShowCritical("Save Failed", "Could not save file, try saving to a different folder."); @@ -301,7 +307,9 @@ void FractoriumEmberController::SaveEntireFileAsXml() QString filename; FractoriumSettings* s = m_Fractorium->m_Settings; - if (QFile::exists(m_LastSaveAll)) + if (s->SaveAutoUnique() && m_LastSaveAll != "") + filename = EmberFile::UniqueFilename(m_LastSaveAll); + else if (QFile::exists(m_LastSaveAll)) filename = m_LastSaveAll; else filename = m_Fractorium->SetupSaveXmlDialog(m_EmberFile.m_Filename); @@ -320,7 +328,9 @@ void FractoriumEmberController::SaveEntireFileAsXml() if (writer.Save(filename.toStdString().c_str(), emberFile.m_Embers, 0, true, false, true)) { - m_LastSaveAll = filename; + if (!s->SaveAutoUnique() || m_LastSaveAll == "")//Only save filename on first time through when doing auto unique names. + m_LastSaveAll = filename; + s->SaveFolder(fileInfo.canonicalPath()); } else @@ -398,7 +408,7 @@ void FractoriumEmberController::Undo() int index = m_Ember.GetTotalXformIndex(CurrentXform()); m_LastEditWasUndoRedo = true; - m_UndoIndex = max(0u, m_UndoIndex - 1u); + m_UndoIndex = std::max(0u, m_UndoIndex - 1u); SetEmber(m_UndoList[m_UndoIndex], true); m_EditState = UNDO_REDO; @@ -423,7 +433,7 @@ void FractoriumEmberController::Redo() int index = m_Ember.GetTotalXformIndex(CurrentXform()); m_LastEditWasUndoRedo = true; - m_UndoIndex = min(m_UndoIndex + 1, m_UndoList.size() - 1); + m_UndoIndex = std::min(m_UndoIndex + 1, m_UndoList.size() - 1); SetEmber(m_UndoList[m_UndoIndex], true); m_EditState = UNDO_REDO; diff --git a/Source/Fractorium/FractoriumPch.h b/Source/Fractorium/FractoriumPch.h index 5ca78b3..bfb9f72 100644 --- a/Source/Fractorium/FractoriumPch.h +++ b/Source/Fractorium/FractoriumPch.h @@ -26,9 +26,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include diff --git a/Source/Fractorium/FractoriumRender.cpp b/Source/Fractorium/FractoriumRender.cpp index a0a8e4b..1d428e6 100644 --- a/Source/Fractorium/FractoriumRender.cpp +++ b/Source/Fractorium/FractoriumRender.cpp @@ -67,11 +67,22 @@ void FractoriumEmberControllerBase::StopRenderTimer(bool wait) void FractoriumEmberControllerBase::Shutdown() { StopRenderTimer(true); + ClearFinalImages(); while(m_Fractorium->ui.GLDisplay->Drawing()) QApplication::processEvents(); } +/// +/// Clear the output image buffers. +/// +void FractoriumEmberControllerBase::ClearFinalImages() +{ + Memset(m_FinalImage[0]); + Memset(m_FinalImage[1]); + //Unsure if we should also call RendererCL::ClearFinal() as well. At the moment it seems unnecessary. +} + /// /// Update the state of the renderer. /// Upon changing values, some intelligence is used to avoid blindly restarting the @@ -442,7 +453,11 @@ bool FractoriumEmberController::Render() //Uncomment for debugging kernel build and execution errors. //m_Fractorium->ui.InfoRenderingTextEdit->setText(QString::fromStdString(m_Fractorium->m_Wrapper.DumpInfo())); //if (rendererCL) - // m_Fractorium->ui.InfoRenderingTextEdit->setText(QString::fromStdString(rendererCL->IterKernel())); + //{ + // string s = "OpenCL Kernels: \r\n" + rendererCL->IterKernel() + "\r\n" + rendererCL->DEKernel() + "\r\n" + rendererCL->FinalAccumKernel(); + // + // QMetaObject::invokeMethod(m_Fractorium->ui.InfoRenderingTextEdit, "setText", Qt::QueuedConnection, Q_ARG(const QString&, QString::fromStdString(s))); + //} } } else//Something went very wrong, show error report. @@ -460,12 +475,16 @@ bool FractoriumEmberController::Render() m_Rendering = false; StopRenderTimer(true); m_Fractorium->m_RenderStatusLabel->setText("Rendering failed 3 or more times, stopping all rendering, see info tab. Try changing renderer types."); - Memset(m_FinalImage[m_FinalImageIndex]); - - if (rendererCL) - rendererCL->ClearFinal(); - + ClearFinalImages(); m_GLController->ClearWindow(); + + if (rendererCL) + { + //string s = "OpenCL Kernels: \r\n" + rendererCL->IterKernel() + "\r\n" + rendererCL->DEKernel() + "\r\n" + rendererCL->FinalAccumKernel(); + + rendererCL->ClearFinal(); + //QMetaObject::invokeMethod(m_Fractorium->ui.InfoRenderingTextEdit, "setText", Qt::QueuedConnection, Q_ARG(const QString&, QString::fromStdString(s))); + } } } } diff --git a/Source/Fractorium/FractoriumSettings.cpp b/Source/Fractorium/FractoriumSettings.cpp index 769124f..c752b8e 100644 --- a/Source/Fractorium/FractoriumSettings.cpp +++ b/Source/Fractorium/FractoriumSettings.cpp @@ -39,7 +39,7 @@ void FractoriumSettings::EnsureDefaults() XmlSupersample(2); if (ThreadCount() == 0 || ThreadCount() > Timing::ProcessorCount()) - ThreadCount(max(1u, Timing::ProcessorCount() - 1));//Default to one less to keep the UI responsive for first time users. + ThreadCount(std::max(1u, Timing::ProcessorCount() - 1));//Default to one less to keep the UI responsive for first time users. if (FinalThreadCount() == 0 || FinalThreadCount() > Timing::ProcessorCount()) FinalThreadCount(Timing::ProcessorCount()); @@ -172,8 +172,8 @@ void FractoriumSettings::FinalKeepAspect(bool b) { setValue(FINALKEEPASPECT, uint FractoriumSettings::FinalScale() { return value(FINALSCALE).toUInt(); } void FractoriumSettings::FinalScale(uint i) { setValue(FINALSCALE, i); } -QString FractoriumSettings::FinalExt() { return value(FINALEXT).toString(); } -void FractoriumSettings::FinalExt(const QString& s) { setValue(FINALEXT, s); } +QString FractoriumSettings::FinalExt() { return value(FINALEXT).toString(); } +void FractoriumSettings::FinalExt(const QString& s) { setValue(FINALEXT, s); } uint FractoriumSettings::FinalPlatformIndex() { return value(FINALPLATFORMINDEX).toUInt(); } void FractoriumSettings::FinalPlatformIndex(uint i) { setValue(FINALPLATFORMINDEX, i); } @@ -239,3 +239,6 @@ void FractoriumSettings::OpenImageExt(const QString& s) { setValue(OPENIMAGEE QString FractoriumSettings::SaveImageExt() { return value(SAVEIMAGEEXT).toString(); } void FractoriumSettings::SaveImageExt(const QString& s) { setValue(SAVEIMAGEEXT, s); } + +bool FractoriumSettings::SaveAutoUnique() { return value(AUTOUNIQUE).toBool(); } +void FractoriumSettings::SaveAutoUnique(bool b) { setValue(AUTOUNIQUE, b); } \ No newline at end of file diff --git a/Source/Fractorium/FractoriumSettings.h b/Source/Fractorium/FractoriumSettings.h index 4d276a8..05f57e9 100644 --- a/Source/Fractorium/FractoriumSettings.h +++ b/Source/Fractorium/FractoriumSettings.h @@ -52,6 +52,7 @@ #define SAVEXMLEXT "file/savexmlext" #define OPENIMAGEEXT "file/openimageext" #define SAVEIMAGEEXT "file/saveimageext" +#define AUTOUNIQUE "file/autounique" #define IDENTITYID "identity/id" #define IDENTITYURL "identity/url" @@ -189,6 +190,9 @@ public: QString SaveImageExt(); void SaveImageExt(const QString& s); + bool SaveAutoUnique(); + void SaveAutoUnique(bool b); + QString Id(); void Id(const QString& s); diff --git a/Source/Fractorium/FractoriumXformsColor.cpp b/Source/Fractorium/FractoriumXformsColor.cpp index fad8939..594d17d 100644 --- a/Source/Fractorium/FractoriumXformsColor.cpp +++ b/Source/Fractorium/FractoriumXformsColor.cpp @@ -25,8 +25,15 @@ void Fractorium::InitXformsColorUI() m_XformColorSpeedSpin->setDecimals(3); m_XformOpacitySpin->setDecimals(3); m_XformDirectColorSpin->setDecimals(3); - connect(ui.XformColorScroll, SIGNAL(valueChanged(int)), this, SLOT(OnXformScrollColorIndexChanged(int)), Qt::QueuedConnection); - connect(ui.SoloXformCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnSoloXformCheckBoxStateChanged(int)), Qt::QueuedConnection); + connect(ui.XformColorScroll, SIGNAL(valueChanged(int)), this, SLOT(OnXformScrollColorIndexChanged(int)), Qt::QueuedConnection); + connect(ui.SoloXformCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnSoloXformCheckBoxStateChanged(int)), Qt::QueuedConnection); + + connect(ui.ResetCurvesButton, SIGNAL(clicked(bool)), this, SLOT(OnResetCurvesButtonClicked(bool)), Qt::QueuedConnection); + connect(ui.CurvesView, SIGNAL(PointChangedSignal(int, int, const QPointF&)), this, SLOT(OnCurvesPointChanged(int, int, const QPointF&)), Qt::QueuedConnection); + connect(ui.CurvesAllRadio, SIGNAL(toggled(bool)), this, SLOT(OnCurvesAllRadioButtonToggled(bool)), Qt::QueuedConnection); + connect(ui.CurvesRedRadio, SIGNAL(toggled(bool)), this, SLOT(OnCurvesRedRadioButtonToggled(bool)), Qt::QueuedConnection); + connect(ui.CurvesGreenRadio, SIGNAL(toggled(bool)), this, SLOT(OnCurvesGreenRadioButtonToggled(bool)), Qt::QueuedConnection); + connect(ui.CurvesBlueRadio, SIGNAL(toggled(bool)), this, SLOT(OnCurvesBlueRadioButtonToggled(bool)), Qt::QueuedConnection); } /// @@ -144,6 +151,55 @@ void Fractorium::OnXformRefPaletteResized(int logicalIndex, int oldSize, int new m_Controller->SetPaletteRefTable(nullptr); } +/// +/// Reset the color curve values in the current ember to their default state and also update the curves control. +/// Called when ResetCurvesButton is clicked. +/// Resets the rendering process at either ACCUM_ONLY by default, or FILTER_AND_ACCUM when using early clip. +/// +template +void FractoriumEmberController::ClearColorCurves() +{ + Update([&] + { + m_Ember.m_Curves.Init(); + }, true, m_Renderer->EarlyClip() ? FILTER_AND_ACCUM : ACCUM_ONLY); + + FillCurvesControl(); +} + +void Fractorium::OnResetCurvesButtonClicked(bool checked) { m_Controller->ClearColorCurves(); } + +/// +/// Set the coordinate of the curve point. +/// Called when the position of any of the points in the curves editor is is changed. +/// Resets the rendering process at either ACCUM_ONLY by default, or FILTER_AND_ACCUM when using early clip. +/// +/// The curve index, 0-1/ +/// The point index within the selected curve, 1-2. +/// The new coordinate of the point in terms of the curves control rect. +template +void FractoriumEmberController::ColorCurveChanged(int curveIndex, int pointIndex, const QPointF& point) +{ + Update([&] + { + m_Ember.m_Curves.m_Points[curveIndex][pointIndex].x = point.x(); + m_Ember.m_Curves.m_Points[curveIndex][pointIndex].y = point.y(); + }, true, m_Renderer->EarlyClip() ? FILTER_AND_ACCUM : ACCUM_ONLY); +} + +void Fractorium::OnCurvesPointChanged(int curveIndex, int pointIndex, const QPointF& point) { m_Controller->ColorCurveChanged(curveIndex, pointIndex, point); } + +/// +/// Set the top most points in the curves control, which makes it easier to +/// select a point by putting it on top of all the others. +/// Called when the any of the curve color radio buttons are toggled. +/// +/// The curve index, 0-1/ +void Fractorium::OnCurvesAllRadioButtonToggled(bool checked) { if (checked) ui.CurvesView->SetTop(CurveIndex::ALL); } +void Fractorium::OnCurvesRedRadioButtonToggled(bool checked) { if (checked) ui.CurvesView->SetTop(CurveIndex::RED); } +void Fractorium::OnCurvesGreenRadioButtonToggled(bool checked) { if (checked) ui.CurvesView->SetTop(CurveIndex::GREEN); } +void Fractorium::OnCurvesBlueRadioButtonToggled(bool checked) { if (checked) ui.CurvesView->SetTop(CurveIndex::BLUE); } + /// /// Set the current xform color index spinner to the current xform's color index. /// Set the color cell in the palette ref table. @@ -168,6 +224,28 @@ void FractoriumEmberController::SetCurrentXformColorIndex(double d) }, false); } +/// +/// Set the points in the curves control to the values of the curve points in the current ember. +/// +template +void FractoriumEmberController::FillCurvesControl() +{ + m_Fractorium->ui.CurvesView->blockSignals(true); + + for (int i = 0; i < 4; i++) + { + for (int j = 1; j < 3; j++)//Only do middle points. + { + QPointF point(m_Ember.m_Curves.m_Points[i][j].x, m_Ember.m_Curves.m_Points[i][j].y); + + m_Fractorium->ui.CurvesView->Set(i, j, point); + } + } + + m_Fractorium->ui.CurvesView->blockSignals(false); + m_Fractorium->ui.CurvesView->update(); +} + /// /// Set the color index, speed and opacity spinners with the values of the current xform. /// Set the cells of the palette ref table as well. @@ -180,6 +258,7 @@ void FractoriumEmberController::FillColorWithXform(Xform* xform) m_Fractorium->m_XformColorSpeedSpin->SetValueStealth(xform->m_ColorSpeed); m_Fractorium->m_XformOpacitySpin->SetValueStealth(xform->m_Opacity); m_Fractorium->m_XformDirectColorSpin->SetValueStealth(xform->m_DirectColor); + FillCurvesControl(); m_Fractorium->OnXformColorIndexChanged(xform->m_ColorX, false);//Had to call stealth before to avoid doing an update, now manually update related controls, still without doing an update. } diff --git a/Source/Fractorium/GLEmberController.cpp b/Source/Fractorium/GLEmberController.cpp index 6b37b4b..2ddeaed 100644 --- a/Source/Fractorium/GLEmberController.cpp +++ b/Source/Fractorium/GLEmberController.cpp @@ -203,7 +203,7 @@ void GLEmberController::QueryVMP() /// by an m4. /// template <> -void GLEmberController::MultMatrix(glm::detail::tmat4x4& mat) +void GLEmberController::MultMatrix(tmat4x4& mat) { m_GL->glMultMatrixf(glm::value_ptr(mat)); } @@ -214,7 +214,7 @@ void GLEmberController::MultMatrix(glm::detail::tmat4x4. /// template <> -void GLEmberController::MultMatrix(glm::detail::tmat4x4& mat) +void GLEmberController::MultMatrix(tmat4x4& mat) { m_GL->glMultMatrixd(glm::value_ptr(mat)); } diff --git a/Source/Fractorium/GLWidget.cpp b/Source/Fractorium/GLWidget.cpp index a23f992..1e57a26 100644 --- a/Source/Fractorium/GLWidget.cpp +++ b/Source/Fractorium/GLWidget.cpp @@ -855,7 +855,7 @@ void GLWidget::DrawGrid() RendererBase* renderer = m_Fractorium->m_Controller->Renderer(); float unitX = fabs(renderer->UpperRightX(false) - renderer->LowerLeftX(false)) / 2.0f; float unitY = fabs(renderer->UpperRightY(false) - renderer->LowerLeftY(false)) / 2.0f; - float rad = max(unitX, unitY); + float rad = std::max(unitX, unitY); float xLow = floor(-unitX); float xHigh = ceil(unitX); float yLow = floor(-unitY); diff --git a/Source/Fractorium/OptionsDialog.cpp b/Source/Fractorium/OptionsDialog.cpp index 76191a6..c94164b 100644 --- a/Source/Fractorium/OptionsDialog.cpp +++ b/Source/Fractorium/OptionsDialog.cpp @@ -88,6 +88,7 @@ FractoriumOptionsDialog::FractoriumOptionsDialog(FractoriumSettings* settings, Q m_XmlTemporalSamplesSpin->setValue(m_Settings->XmlTemporalSamples()); m_XmlQualitySpin->setValue(m_Settings->XmlQuality()); m_XmlSupersampleSpin->setValue(m_Settings->XmlSupersample()); + ui.AutoUniqueCheckBox->setChecked(m_Settings->SaveAutoUnique()); OnOpenCLCheckBoxStateChanged(ui.OpenCLCheckBox->isChecked()); } @@ -102,6 +103,7 @@ bool FractoriumOptionsDialog::Transparency() { return ui.TransparencyCheckBox->i bool FractoriumOptionsDialog::OpenCL() { return ui.OpenCLCheckBox->isChecked(); } bool FractoriumOptionsDialog::Double() { return ui.DoublePrecisionCheckBox->isChecked(); } bool FractoriumOptionsDialog::ShowAllXforms() { return ui.ShowAllXformsCheckBox->isChecked(); } +bool FractoriumOptionsDialog::AutoUnique() { return ui.AutoUniqueCheckBox->isChecked(); } uint FractoriumOptionsDialog::PlatformIndex() { return ui.PlatformCombo->currentIndex(); } uint FractoriumOptionsDialog::DeviceIndex() { return ui.DeviceCombo->currentIndex(); } uint FractoriumOptionsDialog::ThreadCount() { return ui.ThreadCountSpin->value(); } @@ -162,6 +164,7 @@ void FractoriumOptionsDialog::accept() m_Settings->XmlTemporalSamples(m_XmlTemporalSamplesSpin->value()); m_Settings->XmlQuality(m_XmlQualitySpin->value()); m_Settings->XmlSupersample(m_XmlSupersampleSpin->value()); + m_Settings->SaveAutoUnique(AutoUnique()); //Identity. m_Settings->Id(m_IdEdit->text()); @@ -196,7 +199,8 @@ void FractoriumOptionsDialog::reject() m_XmlTemporalSamplesSpin->setValue(m_Settings->XmlTemporalSamples()); m_XmlQualitySpin->setValue(m_Settings->XmlQuality()); m_XmlSupersampleSpin->setValue(m_Settings->XmlSupersample()); - + ui.AutoUniqueCheckBox->setChecked(m_Settings->SaveAutoUnique()); + //Identity. m_IdEdit->setText(m_Settings->Id()); m_UrlEdit->setText(m_Settings->Url()); diff --git a/Source/Fractorium/OptionsDialog.h b/Source/Fractorium/OptionsDialog.h index 1badfe8..81d3269 100644 --- a/Source/Fractorium/OptionsDialog.h +++ b/Source/Fractorium/OptionsDialog.h @@ -40,6 +40,7 @@ private: bool OpenCL(); bool Double(); bool ShowAllXforms(); + bool AutoUnique(); uint PlatformIndex(); uint DeviceIndex(); uint ThreadCount(); diff --git a/Source/Fractorium/OptionsDialog.ui b/Source/Fractorium/OptionsDialog.ui index 3f88c84..1a8fdb3 100644 --- a/Source/Fractorium/OptionsDialog.ui +++ b/Source/Fractorium/OptionsDialog.ui @@ -486,6 +486,13 @@ in interactive mode for each mouse movement + + + + Auto Unique Filenames + + + diff --git a/Source/Fractorium/SpinBox.cpp b/Source/Fractorium/SpinBox.cpp index 1931406..923c375 100644 --- a/Source/Fractorium/SpinBox.cpp +++ b/Source/Fractorium/SpinBox.cpp @@ -77,7 +77,7 @@ void SpinBox::DoubleClickNonZero(int val) /// The small step to use for scrolling while the shift key is down void SpinBox::SmallStep(int step) { - m_SmallStep = min(1, step); + m_SmallStep = std::min(1, step); } ///