diff --git a/Codecs/H265/H265Encoder.cpp b/Codecs/H265/H265Encoder.cpp index 75e3c64..bde2ee1 100644 --- a/Codecs/H265/H265Encoder.cpp +++ b/Codecs/H265/H265Encoder.cpp @@ -151,7 +151,8 @@ void H265Encoder::Detach() packet->data = static_cast( av_memdup( packetData.data(), packetData.size() ) ); packet->size = static_cast( packetData.size() ); packet->stream_index = _params->pStream->index; - packet->pts = packet->dts = _params->pPicOut->pts; + packet->pts = _params->pPicOut->pts; + packet->dts = _params->pPicOut->dts; packet->duration = 1; AVRational encoderTimeBase{ 1, static_cast( _frameRate ) }; @@ -197,6 +198,7 @@ void H265Encoder::WriteBitmap( std::shared_ptr pBitmap ) { _width = pBitmap->GetWidth(); _height = pBitmap->GetHeight(); + _params->iFrame = 0; _params->pParam->sourceWidth = _width; _params->pParam->sourceHeight = _height; _params->pParam->fpsNum = _frameRate; @@ -315,7 +317,8 @@ void H265Encoder::WriteBitmap( std::shared_ptr pBitmap ) packet->data = static_cast( av_memdup( packetData.data(), packetData.size() ) ); packet->size = static_cast( packetData.size() ); packet->stream_index = _params->pStream->index; - packet->pts = packet->dts = _params->pPicOut->pts; + packet->pts = _params->pPicOut->pts; + packet->dts = _params->pPicOut->dts; packet->duration = 1; AVRational encoderTimeBase{ 1, static_cast( _frameRate ) }; diff --git a/GUI/CropWindow.cpp b/GUI/CropWindow.cpp index a5a85ce..d63b0d6 100644 --- a/GUI/CropWindow.cpp +++ b/GUI/CropWindow.cpp @@ -2,50 +2,74 @@ #include "MainWindow.h" #include "Serializer.h" #include "ImGuiHelpers.h" -#include "./../Transforms/CropTransform.h" + +#include "./../Transforms/ResizeTransform.h" ACMB_GUI_NAMESPACE_BEGIN CropWindow::CropWindow( const Point& gridPos ) - : PipelineElementWindow( "Crop", gridPos, PEFlags_StrictlyOneInput | PEFlags_StrictlyOneOutput ) +: PipelineElementWindow( "Crop", gridPos, PEFlags_StrictlyOneInput | PEFlags_StrictlyOneOutput ) +, SettingsInterpolationUser( this, { 0, 0, 10000, 10000 }) { } void CropWindow::DrawPipelineElementControls() { Size inputBitmapSize = { 65535, 65535 }; - if ( auto pPrimaryInput = GetPrimaryInput() ) + if ( auto pPrimaryInput = GetPrimaryInput(); pPrimaryInput && pPrimaryInput->GetPreviewedFrameNumber() >=0 ) if ( auto inputBitmapSizeExp = pPrimaryInput->GetBitmapSize() ) - inputBitmapSize = inputBitmapSizeExp.value(); + inputBitmapSize = inputBitmapSizeExp.value(); + + _dstRect.x = std::clamp(_dstRect.x, 0, inputBitmapSize.width - 1); + _dstRect.y = std::clamp( _dstRect.y, 0, inputBitmapSize.height - 1 ); + _dstRect.width = std::clamp( _dstRect.width, 1, inputBitmapSize.width - _dstRect.x ); + _dstRect.height = std::clamp( _dstRect.height, 1, inputBitmapSize.height - _dstRect.y ); - ImGui::Text( "Crop area:" ); UI::DragInt( "Left", &_dstRect.x, 1.0f, 0, inputBitmapSize.width - 1, "Left boundary of the crop area", this ); UI::DragInt( "Top", &_dstRect.y, 1.0f, 0, inputBitmapSize.height - 1,"Top boundary of the crop area", this ); UI::DragInt( "Width", &_dstRect.width, 1.0f, 1, inputBitmapSize.width - _dstRect.x, "Width of the crop area", this ); UI::DragInt( "Height", &_dstRect.height, 1.0f, 1, inputBitmapSize.height - _dstRect.y, "Height of the crop area", this ); + + DrawFrameCounter(); +} + +void CropWindow::OnPreviewedFrameNumberChanged(int val) +{ + PipelineElementWindow::OnPreviewedFrameNumberChanged(val); + _dstRect = GetInterpolatedSettings(_previewedFrameNumber); } -IBitmapPtr CropWindow::ProcessBitmapFromPrimaryInput( IBitmapPtr pSource, size_t ) +void CropWindow::OnKeyframeCommited() { - return CropTransform::Crop( pSource, _dstRect ); + AddSettings(_previewedFrameNumber, _dstRect ); +} + +IBitmapPtr CropWindow::ProcessBitmapFromPrimaryInput( IBitmapPtr pSource, size_t frameIndex ) +{ + auto interpolatedSettings = GetInterpolatedSettings( int( frameIndex ) ); + return CropTransform::Crop( pSource, interpolatedSettings); } void CropWindow::Serialize( std::ostream& out ) const { PipelineElementWindow::Serialize( out ); gui::Serialize( _dstRect, out ); + SettingsInterpolationUser::Serialize( out ); } bool CropWindow::Deserialize( std::istream& in ) { if ( !PipelineElementWindow::Deserialize( in ) ) return false; _dstRect = gui::Deserialize( in, _remainingBytes ); + SettingsInterpolationUser::Deserialize( in, _remainingBytes ); return true; } int CropWindow::GetSerializedStringSize() const { - return PipelineElementWindow::GetSerializedStringSize() + gui::GetSerializedStringSize( _dstRect ); + return PipelineElementWindow::GetSerializedStringSize() + + gui::GetSerializedStringSize( _dstRect ) + + SettingsInterpolationUser::GetSerializedStringSize(); } Expected CropWindow::GeneratePreviewBitmap() @@ -67,12 +91,16 @@ Expected CropWindow::GeneratePreviewBitmap() const Rect cropArea { - .x = std::clamp( int( _dstRect.x * xFactor ), 0, inputPreviewSize.width - 1 ), - .y = std::clamp( int( _dstRect.y * yFactor ), 0, inputPreviewSize.height - 1 ), - .width = std::clamp( int( _dstRect.width * xFactor ), 1, inputPreviewSize.width - cropArea.x ), - .height = std::clamp( int( _dstRect.height * yFactor ), 1, inputPreviewSize.height - cropArea.y ) + .x = std::clamp( int(_dstRect.x * xFactor ), 0, inputPreviewSize.width - 1 ), + .y = std::clamp( int(_dstRect.y * yFactor ), 0, inputPreviewSize.height - 1 ), + .width = std::clamp( int(_dstRect.width * xFactor ), 1, inputPreviewSize.width - cropArea.x ), + .height = std::clamp( int(_dstRect.height * yFactor ), 1, inputPreviewSize.height - cropArea.y ) }; + _pPreviewBitmap = CropTransform::Crop(pInputBitmap, cropArea ); + const Size finalSize = ResizeTransform::GetSizeWithPreservedRatio(Size{ cropArea.width, cropArea.height }, inputPreviewSize); + _pPreviewBitmap = ResizeTransform::Resize(_pPreviewBitmap, finalSize); + return {}; } diff --git a/GUI/CropWindow.h b/GUI/CropWindow.h index 7d922ef..48167c8 100644 --- a/GUI/CropWindow.h +++ b/GUI/CropWindow.h @@ -1,11 +1,15 @@ #pragma once #include "PipelineElementWindow.h" +#include "SettingsInterpolationUser.h" + +#include "./../Transforms/CropTransform.h" ACMB_GUI_NAMESPACE_BEGIN -class CropWindow : public PipelineElementWindow +class CropWindow : public PipelineElementWindow, public SettingsInterpolationUser { - Rect _dstRect = { 0, 0, 1000, 1000 }; +private: + CropTransform::Settings _dstRect = { 0, 0, 10000, 10000 }; virtual IBitmapPtr ProcessBitmapFromPrimaryInput( IBitmapPtr pSource, size_t taskNumber = 0 ) override; virtual Expected GeneratePreviewBitmap() override; @@ -18,6 +22,9 @@ class CropWindow : public PipelineElementWindow virtual bool Deserialize(std::istream& in) override; virtual int GetSerializedStringSize() const override; + virtual void OnPreviewedFrameNumberChanged(int val) override; + virtual void OnKeyframeCommited() override; + SET_MENU_PARAMS( "\xef\x84\xa5", "Crop", "Crop image to an arbitrary rectangle", 7 ); }; diff --git a/GUI/ImGuiHelpers.cpp b/GUI/ImGuiHelpers.cpp index 5b9dde3..6b9f35c 100644 --- a/GUI/ImGuiHelpers.cpp +++ b/GUI/ImGuiHelpers.cpp @@ -48,7 +48,10 @@ namespace UI static void ResetParentWindow(PipelineElementWindow* parent) { - parent->ResetPreview(); + if ( !parent ) + return; + + parent->ResetPreview(PipelineElementWindow::PropagationDir::Forward ); if ( parent->GetInOutFlags() & PEFlags::PEFlags_NoInput ) parent->ResetProgress(PipelineElementWindow::PropagationDir::Forward); @@ -143,8 +146,7 @@ namespace UI if ( ImGui::Button( name.c_str(), size) && !isInterfaceLocked ) { action(); - if ( parent ) - ResetParentWindow( parent ); + ResetParentWindow( parent ); return true; } @@ -158,8 +160,7 @@ namespace UI if ( ImGui::Button( name.c_str(), size ) ) { action(); - if ( parent ) - ResetParentWindow(parent); + ResetParentWindow(parent); return true; } @@ -174,8 +175,7 @@ namespace UI if ( pressed ) { *v = v_button; - if ( parent ) - ResetParentWindow(parent); + ResetParentWindow(parent); return true; } @@ -187,7 +187,7 @@ namespace UI bool Checkbox( const std::string& label, bool* v, const std::string& tooltip, PipelineElementWindow* parent ) { const bool isInterfaceLocked = MainWindow::GetInstance( FontRegistry::Instance() ).IsInterfaceLocked(); - if ( ImGui::Checkbox( label.c_str(), v, isInterfaceLocked ) && parent ) + if ( ImGui::Checkbox( label.c_str(), v, isInterfaceLocked ) ) { ResetParentWindow(parent); return true; @@ -200,7 +200,7 @@ namespace UI bool DragInt( const std::string& label, int* v, float v_speed, int v_min, int v_max, const std::string& tooltip, PipelineElementWindow* parent ) { const bool isInterfaceLocked = MainWindow::GetInstance( FontRegistry::Instance() ).IsInterfaceLocked(); - if ( ImGui::DragInt( label.c_str(), v, v_speed, v_min, v_max, "%d", isInterfaceLocked ? ImGuiSliderFlags_ReadOnly : ImGuiSliderFlags_AlwaysClamp ) && parent ) + if ( ImGui::DragInt( label.c_str(), v, v_speed, v_min, v_max, "%d", isInterfaceLocked ? ImGuiSliderFlags_ReadOnly : ImGuiSliderFlags_AlwaysClamp ) ) { ResetParentWindow(parent); return true; @@ -213,7 +213,7 @@ namespace UI bool DragInt2( const std::string& label, int v[2], float v_speed, int v_min, int v_max, const std::string& tooltip, acmb::gui::PipelineElementWindow* parent ) { const bool isInterfaceLocked = MainWindow::GetInstance( FontRegistry::Instance() ).IsInterfaceLocked(); - if ( ImGui::DragInt2( label.c_str(), v, v_speed, v_min, v_max, "%d", isInterfaceLocked ? ImGuiSliderFlags_ReadOnly : ImGuiSliderFlags_AlwaysClamp ) && parent ) + if ( ImGui::DragInt2( label.c_str(), v, v_speed, v_min, v_max, "%d", isInterfaceLocked ? ImGuiSliderFlags_ReadOnly : ImGuiSliderFlags_AlwaysClamp ) ) { ResetParentWindow(parent); return true; @@ -226,7 +226,7 @@ namespace UI bool DragFloat( const std::string& label, float* v, float v_speed, float v_min, float v_max, const std::string& tooltip, PipelineElementWindow* parent ) { const bool isInterfaceLocked = MainWindow::GetInstance( FontRegistry::Instance() ).IsInterfaceLocked(); - if ( ImGui::DragFloat( label.c_str(), v, v_speed, v_min, v_max, "%.4f", isInterfaceLocked ? ImGuiSliderFlags_ReadOnly : ImGuiSliderFlags_AlwaysClamp ) && parent ) + if ( ImGui::DragFloat( label.c_str(), v, v_speed, v_min, v_max, "%.4f", isInterfaceLocked ? ImGuiSliderFlags_ReadOnly : ImGuiSliderFlags_AlwaysClamp ) ) { ResetParentWindow(parent); return true; @@ -239,7 +239,7 @@ namespace UI bool InputInt( const std::string& label, int* v, int step, int step_fast, int min, int max, const std::string& tooltip, acmb::gui::PipelineElementWindow* parent ) { const bool isInterfaceLocked = MainWindow::GetInstance( FontRegistry::Instance() ).IsInterfaceLocked(); - if ( ImGui::InputInt( label.c_str(), v, step, step_fast, isInterfaceLocked ? ImGuiInputTextFlags_ReadOnly : 0 ) && parent ) + if ( ImGui::InputInt( label.c_str(), v, step, step_fast, isInterfaceLocked ? ImGuiInputTextFlags_ReadOnly : 0 ) ) { ResetParentWindow(parent); return true; diff --git a/GUI/ImageReaderWindow.cpp b/GUI/ImageReaderWindow.cpp index edc9d35..70daf2a 100644 --- a/GUI/ImageReaderWindow.cpp +++ b/GUI/ImageReaderWindow.cpp @@ -37,12 +37,11 @@ void ImageReaderWindow::DrawPipelineElementControls() { for ( int i = 0; i < int( _fileNames.size() ); ++i ) { - const bool is_selected = (_selectedItemIdx == i); + const bool is_selected = (_previewedFrameNumber == i); const std::string shortName = _fileNames[i].substr( _fileNames[i].find_last_of( "\\/" ) + 1 ); if ( ImGui::Selectable( shortName.c_str(), is_selected ) ) { - _selectedItemIdx = i; - ResetPreview(); + OnPreviewedFrameNumberChanged( i ); } // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) if ( is_selected ) @@ -51,7 +50,7 @@ void ImageReaderWindow::DrawPipelineElementControls() ImGui::EndListBox(); } - ImGui::Text( "%d frames in %d files", int( _frameCount ), int( _fileNames.size() ) ); + ImGui::Text( "%d frames in %d files", int( _taskCount ), int( _fileNames.size() ) ); auto fileDialog = FileDialog::Instance(); const auto openDialogName = "SelectImagesDialog##" + _name; @@ -72,8 +71,8 @@ void ImageReaderWindow::DrawPipelineElementControls() ImGui::CloseCurrentPopup(); _fileNames.clear(); - _frameCount = 0; - _selectedItemIdx = 0; + _taskCount = 0; + _previewedFrameNumber = 0; _taskNumberToFileIndex.clear(); ResetProgress( PropagationDir::Forward ); }, "Delete all images from the importing list", this ); @@ -95,8 +94,8 @@ void ImageReaderWindow::DrawPipelineElementControls() { auto pDecoder = ImageDecoder::Create( path ); _fileNames.push_back( path ); - _frameCount += pDecoder->GetFrameCount(); - _taskNumberToFileIndex[int( _frameCount - 1 )] = int( _fileNames.size() - 1 ); + _taskCount += pDecoder->GetFrameCount(); + _taskNumberToFileIndex[int( _taskCount - 1 )] = int( _fileNames.size() - 1 ); } catch ( std::exception& e ) { @@ -122,13 +121,13 @@ Expected ImageReaderWindow::GeneratePreviewBitmap() if ( _fileNames.empty() ) return unexpected( "No images in the list" ); - if ( _selectedItemIdx >= int( _fileNames.size() ) ) + if ( _previewedFrameNumber >= int( _fileNames.size() ) || _previewedFrameNumber < 0 ) return unexpected( "No image selected" ); - if ( _fileNames[_selectedItemIdx].empty() ) + if ( _fileNames[_previewedFrameNumber].empty() ) return unexpected( "Selected file name is empty" ); - auto pDecoder = ImageDecoder::Create( _fileNames[_selectedItemIdx] ); + auto pDecoder = ImageDecoder::Create( _fileNames[_previewedFrameNumber] ); const auto mainWindow = ImGui::FindWindowByName( "acmb" ); _pPreviewBitmap = pDecoder->ReadPreview( Size{ std::min( int( mainWindow->Size.x * 0.5f ), 1280 ), std::min( int( mainWindow->Size.y * 0.5f ), 720 ) } ); return {}; @@ -165,15 +164,15 @@ Expected ImageReaderWindow::GetBitmapSize() if ( _fileNames.empty() ) return unexpected( "No images in the list" ); - if ( _selectedItemIdx >= int( _fileNames.size() ) ) + if ( _previewedFrameNumber >= int( _fileNames.size() ) ) return unexpected( "No image selected" ); - if ( _fileNames[_selectedItemIdx].empty() ) + if ( _fileNames[_previewedFrameNumber].empty() ) return unexpected( "Selected file name is empty" ); try { - auto pDecoder = ImageDecoder::Create( _fileNames[_selectedItemIdx] ); + auto pDecoder = ImageDecoder::Create( _fileNames[_previewedFrameNumber] ); const auto res = Size{ int( pDecoder->GetWidth() ), int( pDecoder->GetHeight() ) }; pDecoder->Detach(); return res; @@ -188,8 +187,7 @@ void ImageReaderWindow::Serialize( std::ostream& out ) const { PipelineElementWindow::Serialize( out ); gui::Serialize( _workingDirectory, out ); - gui::Serialize( _fileNames, out ); - gui::Serialize( _selectedItemIdx, out ); + gui::Serialize( _fileNames, out ); gui::Serialize( _invertOrder, out ); } @@ -198,7 +196,6 @@ bool ImageReaderWindow::Deserialize( std::istream& in ) if ( !PipelineElementWindow::Deserialize( in ) ) return false; _workingDirectory = gui::Deserialize( in, _remainingBytes ); _fileNames = gui::Deserialize>( in, _remainingBytes ); - _selectedItemIdx = gui::Deserialize( in, _remainingBytes ); _invertOrder = gui::Deserialize( in, _remainingBytes ); for ( size_t i = 0; i < _fileNames.size(); ++i ) @@ -207,8 +204,8 @@ bool ImageReaderWindow::Deserialize( std::istream& in ) try { auto pDecoder = ImageDecoder::Create( fileName ); - _frameCount += pDecoder->GetFrameCount(); - _taskNumberToFileIndex[int( _frameCount - 1 )] = int( i ); + _taskCount += pDecoder->GetFrameCount(); + _taskNumberToFileIndex[int( _taskCount - 1 )] = int( i ); } catch ( std::exception& e ) { @@ -225,7 +222,6 @@ int ImageReaderWindow::GetSerializedStringSize() const return PipelineElementWindow::GetSerializedStringSize() + gui::GetSerializedStringSize( _workingDirectory ) + gui::GetSerializedStringSize( _fileNames ) - + gui::GetSerializedStringSize( _selectedItemIdx ) + gui::GetSerializedStringSize( _invertOrder ); } @@ -237,7 +233,7 @@ std::string ImageReaderWindow::GetTaskName( size_t taskNumber ) const size_t ImageReaderWindow::GetTaskCount( bool ) { - return _frameCount; + return _taskCount; } REGISTER_TOOLS_ITEM( ImageReaderWindow ) diff --git a/GUI/ImageReaderWindow.h b/GUI/ImageReaderWindow.h index a8c4774..6dd7457 100644 --- a/GUI/ImageReaderWindow.h +++ b/GUI/ImageReaderWindow.h @@ -16,9 +16,6 @@ class ImageReaderWindow : public PipelineElementWindow std::vector _fileNames; std::map _taskNumberToFileIndex; - int _selectedItemIdx = 0; - size_t _frameCount = 0; - bool _invertOrder = false; virtual Expected RunTask( size_t i ) override; virtual IBitmapPtr ProcessBitmapFromPrimaryInput( IBitmapPtr, size_t ) override { return nullptr; } diff --git a/GUI/LevelsWindow.cpp b/GUI/LevelsWindow.cpp index 9a9f96f..f4b0ae0 100644 --- a/GUI/LevelsWindow.cpp +++ b/GUI/LevelsWindow.cpp @@ -7,6 +7,7 @@ ACMB_GUI_NAMESPACE_BEGIN LevelsWindow::LevelsWindow( const Point& gridPos ) : PipelineElementWindow( "Levels", gridPos, PEFlags_StrictlyOneInput | PEFlags_StrictlyOneOutput ) +, SettingsInterpolationUser(this, LevelsTransform::Settings{}) { } @@ -125,6 +126,8 @@ void LevelsWindow::DrawPipelineElementControls() }, "Automatically adjust levels", this ); ImGui::PopStyleVar(); + + DrawFrameCounter(); } Expected LevelsWindow::AutoAdjustLevels() @@ -147,23 +150,37 @@ Expected LevelsWindow::AutoAdjustLevels() return {}; } +void LevelsWindow::OnPreviewedFrameNumberChanged(int val) +{ + PipelineElementWindow::OnPreviewedFrameNumberChanged(val); + _levelsSettings = GetInterpolatedSettings(_previewedFrameNumber); +} + +void LevelsWindow::OnKeyframeCommited() +{ + AddSettings(_previewedFrameNumber, _levelsSettings); +} + void LevelsWindow::Serialize( std::ostream& out ) const { PipelineElementWindow::Serialize( out ); gui::Serialize( _levelsSettings, out ); + SettingsInterpolationUser::Serialize(out); } bool LevelsWindow::Deserialize( std::istream& in ) { if ( !PipelineElementWindow::Deserialize( in ) ) return false; _levelsSettings = gui::Deserialize( in, _remainingBytes ); + SettingsInterpolationUser::Deserialize(in, _remainingBytes); return true; } int LevelsWindow::GetSerializedStringSize() const { return PipelineElementWindow::GetSerializedStringSize() - + gui::GetSerializedStringSize( _levelsSettings ); + + gui::GetSerializedStringSize( _levelsSettings ) + + SettingsInterpolationUser::GetSerializedStringSize(); } Expected LevelsWindow::GeneratePreviewBitmap() @@ -177,9 +194,10 @@ Expected LevelsWindow::GeneratePreviewBitmap() return {}; } -IBitmapPtr LevelsWindow::ProcessBitmapFromPrimaryInput( IBitmapPtr pSource, size_t ) +IBitmapPtr LevelsWindow::ProcessBitmapFromPrimaryInput( IBitmapPtr pSource, size_t frameIndex) { - return LevelsTransform::ApplyLevels( pSource, _levelsSettings ); + auto interpolatedSettings = GetInterpolatedSettings(int(frameIndex)); + return LevelsTransform::ApplyLevels( pSource, interpolatedSettings); } REGISTER_TOOLS_ITEM( LevelsWindow ); diff --git a/GUI/LevelsWindow.h b/GUI/LevelsWindow.h index 7566d19..3b23f2c 100644 --- a/GUI/LevelsWindow.h +++ b/GUI/LevelsWindow.h @@ -1,10 +1,13 @@ #pragma once #include "PipelineElementWindow.h" +#include "SettingsInterpolationUser.h" + #include "./../Transforms/LevelsTransform.h" + ACMB_GUI_NAMESPACE_BEGIN -class LevelsWindow : public PipelineElementWindow +class LevelsWindow : public PipelineElementWindow, public SettingsInterpolationUser { virtual IBitmapPtr ProcessBitmapFromPrimaryInput( IBitmapPtr pSource, size_t taskNumber = 0 ) override; virtual Expected GeneratePreviewBitmap() override; @@ -18,6 +21,9 @@ class LevelsWindow : public PipelineElementWindow virtual bool Deserialize( std::istream& in ) override; virtual int GetSerializedStringSize() const override; + virtual void OnPreviewedFrameNumberChanged(int number) override; + virtual void OnKeyframeCommited() override; + SET_MENU_PARAMS( "\xef\x82\x80", "Levels", "Adjust levels of the image", 9 ); private: diff --git a/GUI/PipelineElementWindow.cpp b/GUI/PipelineElementWindow.cpp index e97b2de..dc1d26e 100644 --- a/GUI/PipelineElementWindow.cpp +++ b/GUI/PipelineElementWindow.cpp @@ -273,7 +273,7 @@ size_t PipelineElementWindow::GetTaskCount(bool update) { auto pPrimaryInput = GetPrimaryInput(); if ( pPrimaryInput ) - _taskCount = pPrimaryInput->GetTaskCount(); + _taskCount = pPrimaryInput->GetTaskCount(update); } return _taskCount; @@ -286,7 +286,6 @@ size_t PipelineElementWindow::GetCompletedTaskCount() void PipelineElementWindow::ResetTasks() { - _taskCount = 0; _completedTaskCount = 0; _taskReadiness = 0; } @@ -308,7 +307,7 @@ void PipelineElementWindow::ResetProgress( PropagationDir dir ) if ( !pOutput ) pOutput = GetBottomOutput(); - if ( pOutput && pOutput->GetCompletedTaskCount() > 0 ) + if ( pOutput ) { //if ( pOutput->GetPrimaryInput().get() == this && std::dynamic_pointer_cast< StackerWindow >(pOutput) == nullptr ) //pOutput->_taskCount = _taskCount; @@ -379,14 +378,13 @@ bool PipelineElementWindow::DrawHeader() void PipelineElementWindow::DrawDialog() { + const auto taskCount = GetTaskCount(); + ImGui::ProgressBar(taskCount > 0 ? (float(_completedTaskCount) + _taskReadiness) / float(taskCount) : 0.0f, { _itemWidth, 0 }); + ImGui::PushItemWidth( 50.0f * cMenuScaling ); DrawPipelineElementControls(); ImGui::PopItemWidth(); - ImGui::SetCursorPosY( cElementHeight - ImGui::GetStyle().WindowPadding.y - ImGui::GetTextLineHeight() - 2 * ImGui::GetStyle().FramePadding.y ); - const auto taskCount = GetTaskCount(); - ImGui::ProgressBar( taskCount > 0 ? ( float( _completedTaskCount ) + _taskReadiness ) / float( taskCount ) : 0.0f, { _itemWidth, 0 } ); - auto& mainWindow = MainWindow::GetInstance(); if ( !mainWindow.IsInterfaceLocked() && ImGui::IsMouseClicked( ImGuiMouseButton_Right ) ) { @@ -429,10 +427,10 @@ void PipelineElementWindow::DrawDialog() ImGui::EndPopup(); } - if ( _showPreview && !ImGui::IsPopupOpen( cPreviewPopupName.c_str() ) ) + if ( _showPreview && !ImGui::IsPopupOpen( cPreviewPopupName.c_str() ) && _pPreviewTexture ) { ImGui::OpenPopup( cPreviewPopupName.c_str() ); - ImVec2 previewPos; + ImVec2 previewPos; if ( const auto mainWindow = ImGui::FindWindowByName( "acmb" ); ImGui::GetMousePos().x < mainWindow->Size.x / 2 ) previewPos.x = mainWindow->Size.x - _pPreviewTexture->GetWidth(); @@ -474,6 +472,7 @@ void PipelineElementWindow::Serialize( std::ostream& out ) const gui::Serialize( _name, out ); gui::Serialize( _serializedInputs, out ); gui::Serialize( _primaryInputIsOnTop, out ); + gui::Serialize(_previewedFrameNumber, out); } bool PipelineElementWindow::Deserialize( std::istream& in ) @@ -488,12 +487,16 @@ bool PipelineElementWindow::Deserialize( std::istream& in ) _name = std::move( savedName ); _serializedInputs = gui::Deserialize( in, _remainingBytes ); _primaryInputIsOnTop = gui::Deserialize( in, _remainingBytes ); + _previewedFrameNumber = gui::Deserialize( in, _remainingBytes ); return true; } int PipelineElementWindow::GetSerializedStringSize() const { - return gui::GetSerializedStringSize( _name ) + gui::GetSerializedStringSize( _serializedInputs ) + gui::GetSerializedStringSize( _primaryInputIsOnTop ); + return gui::GetSerializedStringSize( _name ) + + gui::GetSerializedStringSize( _serializedInputs ) + + gui::GetSerializedStringSize( _primaryInputIsOnTop ) + + gui::GetSerializedStringSize( _previewedFrameNumber ); } Expected PipelineElementWindow::GeneratePreviewTexture() @@ -541,16 +544,30 @@ Expected PipelineElementWindow::GeneratePreviewTexture() } } -void PipelineElementWindow::ResetPreview() +void PipelineElementWindow::ResetPreview( PropagationDir dir ) { _pPreviewBitmap.reset(); _pPreviewTexture.reset(); - auto output = GetRightOutput(); - if ( !output ) - output = GetBottomOutput(); - if ( output ) - output->ResetPreview(); + if ( int(dir) & int(PropagationDir::Forward) ) + { + auto output = GetRightOutput(); + if ( !output ) + output = GetBottomOutput(); + + if ( output ) + output->ResetPreview(PropagationDir::Forward); + } + + if ( int(dir) & int(PropagationDir::Backward) ) + { + auto input = GetLeftInput(); + if ( !input ) + input = GetTopInput(); + + if ( input ) + input->ResetPreview(PropagationDir::Backward); + } } Expected PipelineElementWindow::GetBitmapSize() @@ -562,4 +579,38 @@ Expected PipelineElementWindow::GetBitmapSize() return pPrimaryInput->GetBitmapSize(); } +void PipelineElementWindow::OnPreviewedFrameNumberChanged(int val) +{ + if ( _previewedFrameNumber == val || val < 0 || val >= _taskCount ) + return; + + _previewedFrameNumber = val; + ResetPreview(PropagationDir::None); + + auto pPrimaryInput = GetPrimaryInput(); + while ( pPrimaryInput ) + { + pPrimaryInput->OnPreviewedFrameNumberChanged(val); + pPrimaryInput->ResetPreview(PropagationDir::None); + + pPrimaryInput = pPrimaryInput->GetPrimaryInput(); + } + + auto pPrimaryOutput = GetRightOutput(); + if ( !pPrimaryOutput ) + pPrimaryOutput = GetBottomOutput(); + + while ( pPrimaryOutput ) + { + pPrimaryOutput->ResetPreview(PropagationDir::None); + pPrimaryOutput->OnPreviewedFrameNumberChanged(val); + + auto pNextPrimaryOutput = pPrimaryOutput->GetRightOutput(); + if ( !pNextPrimaryOutput ) + pNextPrimaryOutput = pPrimaryOutput->GetBottomOutput(); + + pPrimaryOutput = pNextPrimaryOutput; + } +} + ACMB_GUI_NAMESPACE_END diff --git a/GUI/PipelineElementWindow.h b/GUI/PipelineElementWindow.h index 652eaac..80d834d 100644 --- a/GUI/PipelineElementWindow.h +++ b/GUI/PipelineElementWindow.h @@ -63,6 +63,7 @@ class PipelineElementWindow : public Window enum class PropagationDir { + None = 0, Forward = 1, Backward = 2, Both = 3 @@ -92,6 +93,8 @@ class PipelineElementWindow : public Window int _remainingBytes{}; + int _previewedFrameNumber = -1; + bool _showError = false; std::string _error; @@ -176,10 +179,18 @@ class PipelineElementWindow : public Window } Expected GeneratePreviewTexture(); - void ResetPreview(); + void ResetPreview(PropagationDir dir); virtual Expected GetBitmapSize(); + virtual void OnPreviewedFrameNumberChanged( int val ); + + virtual void OnKeyframeCommited() + { + } + + int GetPreviewedFrameNumber() const { return _previewedFrameNumber; } + protected: virtual void DrawDialog() override; virtual Expected GeneratePreviewBitmap() = 0; diff --git a/GUI/SaturationWindow.cpp b/GUI/SaturationWindow.cpp index 4115680..be70a0c 100644 --- a/GUI/SaturationWindow.cpp +++ b/GUI/SaturationWindow.cpp @@ -3,18 +3,18 @@ #include "MainWindow.h" #include "ImGuiHelpers.h" -#include "./../Transforms/SaturationTransform.h" - ACMB_GUI_NAMESPACE_BEGIN SaturationWindow::SaturationWindow( const Point& gridPos ) : PipelineElementWindow( "Saturation", gridPos, PEFlags_StrictlyOneInput | PEFlags_StrictlyOneOutput ) +, SettingsInterpolationUser(this, SaturationTransform::Settings{}) { } void SaturationWindow::DrawPipelineElementControls() { - UI::DragFloat( "Saturation", &_saturation, 0.01f, 0.0f, 4.0f, "Saturation factor", this ); + UI::DragFloat( "Saturation", &_saturationSettings, 0.01f, 0.0f, 4.0f, "Saturation factor", this ); + DrawFrameCounter(); } Expected SaturationWindow::GeneratePreviewBitmap() @@ -24,32 +24,47 @@ Expected SaturationWindow::GeneratePreviewBitmap() return unexpected(pInputBitmapOrErr.error()); auto pInputBitmap = pInputBitmapOrErr.value()->Clone(); - _pPreviewBitmap = SaturationTransform::Saturate( pInputBitmap, _saturation ); + _pPreviewBitmap = SaturationTransform::Saturate( pInputBitmap, _saturationSettings ); return {}; } -IBitmapPtr SaturationWindow::ProcessBitmapFromPrimaryInput( IBitmapPtr pSource, size_t ) +IBitmapPtr SaturationWindow::ProcessBitmapFromPrimaryInput( IBitmapPtr pSource, size_t frameIndex) +{ + auto interpolatedSettings = GetInterpolatedSettings(int(frameIndex)); + return SaturationTransform::Saturate( pSource, interpolatedSettings); +} + +void SaturationWindow::OnPreviewedFrameNumberChanged(int val) +{ + PipelineElementWindow::OnPreviewedFrameNumberChanged(val); + _saturationSettings = GetInterpolatedSettings(_previewedFrameNumber); +} + +void SaturationWindow::OnKeyframeCommited() { - return SaturationTransform::Saturate( pSource, _saturation ); + AddSettings(_previewedFrameNumber, _saturationSettings); } void SaturationWindow::Serialize( std::ostream& out ) const { PipelineElementWindow::Serialize( out ); - gui::Serialize( _saturation, out ); + gui::Serialize( _saturationSettings, out ); + SettingsInterpolationUser::Serialize(out); } bool SaturationWindow::Deserialize( std::istream& in ) { if ( !PipelineElementWindow::Deserialize( in ) ) return false; - _saturation = gui::Deserialize( in, _remainingBytes ); + _saturationSettings = gui::Deserialize( in, _remainingBytes ); + SettingsInterpolationUser::Deserialize(in, _remainingBytes); return true; } int SaturationWindow::GetSerializedStringSize() const { return PipelineElementWindow::GetSerializedStringSize() - + gui::GetSerializedStringSize( _saturation ); + + gui::GetSerializedStringSize( _saturationSettings ) + + SettingsInterpolationUser::GetSerializedStringSize(); } REGISTER_TOOLS_ITEM( SaturationWindow ); diff --git a/GUI/SaturationWindow.h b/GUI/SaturationWindow.h index c2a36c5..a9bfd93 100644 --- a/GUI/SaturationWindow.h +++ b/GUI/SaturationWindow.h @@ -1,9 +1,12 @@ #pragma once #include "PipelineElementWindow.h" +#include "SettingsInterpolationUser.h" + +#include "./../Transforms/SaturationTransform.h" ACMB_GUI_NAMESPACE_BEGIN -class SaturationWindow : public PipelineElementWindow +class SaturationWindow : public PipelineElementWindow, public SettingsInterpolationUser { virtual IBitmapPtr ProcessBitmapFromPrimaryInput( IBitmapPtr pSource, size_t taskNumber = 0 ) override; virtual Expected GeneratePreviewBitmap() override; @@ -16,8 +19,11 @@ class SaturationWindow : public PipelineElementWindow SET_MENU_PARAMS( "\xef\x81\x82", "Saturation", "Adjust saturation of the image", 10 ); + virtual void OnPreviewedFrameNumberChanged(int number) override; + virtual void OnKeyframeCommited() override; + private: - float _saturation = 1.0f; + SaturationTransform::Settings _saturationSettings = 1.0f; }; ACMB_GUI_NAMESPACE_END \ No newline at end of file diff --git a/GUI/Serializer.h b/GUI/Serializer.h index 62f5da4..11ca623 100644 --- a/GUI/Serializer.h +++ b/GUI/Serializer.h @@ -1,6 +1,5 @@ #pragma once #include "./../Core/macros.h" -#include "./../Transforms/BitmapHealer.h" #include #include @@ -9,183 +8,170 @@ ACMB_GUI_NAMESPACE_BEGIN +template +struct is_vector : std::false_type +{ +}; + +template +struct is_vector> : std::true_type +{ +}; + +template +inline constexpr bool is_vector_v = is_vector::value; + + +template +struct is_map : std::false_type +{ +}; + +template +struct is_map> : std::true_type +{ +}; + +template +inline constexpr bool is_map_v = is_map::value; + template int GetSerializedStringSize( const T& val ) { - if constexpr ( std::is_same_v, std::string> ) - return int( val.size() ) + sizeof( int ); + using U = std::remove_cvref_t; - if constexpr ( std::is_same_v, std::vector> ) + if constexpr ( std::is_same_v ) + { + return sizeof(int) + int(val.size()); + } + else if constexpr ( is_vector_v ) { - int res = sizeof( int ); - for ( const auto& str : val ) - res += GetSerializedStringSize( str ); + int res = sizeof(int); + for ( const auto& e : val ) + res += GetSerializedStringSize(e); return res; } - - if constexpr ( std::is_same_v, std::vector> ) + else if constexpr ( is_map_v ) { - int res = sizeof( int ); - for ( const auto& patch : val ) - res += GetSerializedStringSize( patch ); + int res = sizeof(int); + for ( const auto& [k, v] : val ) + { + res += GetSerializedStringSize(k); + res += GetSerializedStringSize(v); + } return res; } - - if constexpr ( std::is_same_v, std::map> ) + else { - return int( sizeof( int ) * (2 * val.size() + 1) ); + return sizeof(U); } - - return sizeof( T ); } template void Serialize( T&& val, std::ostream& out ) { - if constexpr ( std::is_same_v, std::string> ) - { - Serialize( int( val.size() ), out ); - out.write( val.data(), val.size() ); - return; - } + using U = std::remove_cvref_t; - if constexpr ( std::is_same_v, std::vector> ) + if constexpr ( std::is_same_v ) { - Serialize( int( val.size() ), out ); - for ( auto& str : val ) - Serialize( std::move( str ), out ); - return; + Serialize(int(val.size()), out); + out.write(val.data(), val.size()); } - - if constexpr ( std::is_same_v, std::vector> ) + else if constexpr ( is_vector_v ) { - Serialize( int( val.size() ), out ); - for ( auto& patch : val ) - Serialize( std::move( patch ), out ); - return; + Serialize(int(val.size()), out); + for ( auto& e : val ) + Serialize(e, out); } - - if constexpr ( std::is_same_v, std::map> ) + else if constexpr ( is_map_v ) { - Serialize( int( val.size() ), out ); - for ( auto it : val ) + Serialize(int(val.size()), out); + for ( auto& [k, v] : val ) { - Serialize( it.first, out ); - Serialize( it.second, out ); + Serialize(k, out); + Serialize(v, out); } - return; } - - out.write( ( char* ) (&val), sizeof( T ) ); + else + { + out.write(reinterpret_cast(&val), sizeof(U)); + } } template T Deserialize( std::istream& in, int& remainingBytes ) { - if constexpr ( std::is_same_v, std::string> ) + using U = std::remove_cvref_t; + + auto require = [&](int bytes) { - if ( remainingBytes < int( sizeof( int ) ) ) + if ( remainingBytes < bytes ) { - in.seekg( remainingBytes, std::ios_base::cur ); + in.seekg(remainingBytes, std::ios_base::cur); remainingBytes = 0; - return {}; + return false; } + return true; + }; - int cachedRemainingBytes = remainingBytes; - int size = std::min( Deserialize( in, remainingBytes ), cachedRemainingBytes ); - if ( size <= 0 ) - remainingBytes = 0; - - if ( remainingBytes == 0 ) - return {}; - - std::string str( size, '\0' ); - in.read( &str[0], size ); - remainingBytes -= size; - return str; - } - - if constexpr ( std::is_same_v, std::vector> ) + if constexpr ( std::is_same_v ) { - if ( remainingBytes < int( sizeof( int ) ) ) - { - in.seekg( remainingBytes, std::ios_base::cur ); - remainingBytes = 0; + if ( !require(sizeof(int)) ) return {}; - } - int size = Deserialize( in, remainingBytes ); - if ( size <= 0 ) - { - remainingBytes = 0; + int size = Deserialize(in, remainingBytes); + if ( size <= 0 || !require(size) ) return {}; - } - std::vector vec( size ); - for ( int i = 0; i < size; ++i ) - vec[i] = Deserialize( in, remainingBytes ); - return vec; + std::string s(size, '\0'); + in.read(s.data(), size); + remainingBytes -= size; + return s; } - - if constexpr ( std::is_same_v, std::vector> ) + else if constexpr ( is_vector_v ) { - if ( remainingBytes < int( sizeof( int ) ) ) - { - in.seekg( remainingBytes, std::ios_base::cur ); - remainingBytes = 0; + if ( !require(sizeof(int)) ) return {}; - } - int size = Deserialize( in, remainingBytes ); + int size = Deserialize(in, remainingBytes); if ( size <= 0 ) - { - remainingBytes = 0; return {}; - } - std::vector vec( size ); + U vec; + vec.reserve(size); for ( int i = 0; i < size; ++i ) - vec[i] = Deserialize( in, remainingBytes ); + vec.push_back(Deserialize(in, remainingBytes)); return vec; } - - if constexpr ( std::is_same_v, std::map> ) + else if constexpr ( is_map_v ) { - if ( remainingBytes < int( sizeof( int ) ) ) - { - in.seekg( remainingBytes, std::ios_base::cur ); - remainingBytes = 0; + if ( !require(sizeof(int)) ) return {}; - } - int size = Deserialize( in, remainingBytes ); + int size = Deserialize(in, remainingBytes); if ( size <= 0 ) - { - remainingBytes = 0; return {}; - } - std::map map; + + U map; for ( int i = 0; i < size; ++i ) { - int key = Deserialize( in, remainingBytes ); - int value = Deserialize( in, remainingBytes ); - map.insert_or_assign( key, value ); + auto key = Deserialize(in, remainingBytes); + auto val = Deserialize(in, remainingBytes); + map.insert_or_assign(std::move(key), std::move(val)); } return map; } - - if ( remainingBytes < int( sizeof( T ) ) ) + else { - in.seekg( remainingBytes, std::ios_base::cur ); - remainingBytes = 0; - return {}; - } + if ( !require(sizeof(U)) ) + return {}; - T res; - in.read( ( char* ) (&res), sizeof( T ) ); - remainingBytes -= sizeof( T ); - return res; + U res; + in.read(reinterpret_cast(&res), sizeof(U)); + remainingBytes -= sizeof(U); + return res; + } } ACMB_GUI_NAMESPACE_END diff --git a/GUI/acmb-gui.vcxproj b/GUI/acmb-gui.vcxproj index 487fb17..c122eda 100644 --- a/GUI/acmb-gui.vcxproj +++ b/GUI/acmb-gui.vcxproj @@ -204,6 +204,7 @@ + diff --git a/GUI/acmb-gui.vcxproj.filters b/GUI/acmb-gui.vcxproj.filters index e71484e..3e0ce71 100644 --- a/GUI/acmb-gui.vcxproj.filters +++ b/GUI/acmb-gui.vcxproj.filters @@ -115,10 +115,12 @@ ToolWindows - ToolWindows + + ToolWindows + @@ -227,6 +229,9 @@ ToolWindows + + Utils + diff --git a/Transforms/AffineTransform.cpp b/Transforms/AffineTransform.cpp index 54222e2..6e41054 100644 --- a/Transforms/AffineTransform.cpp +++ b/Transforms/AffineTransform.cpp @@ -103,4 +103,44 @@ IBitmapPtr AffineTransform::ApplyTransform( IBitmapPtr pSrcBitmap, const Setting return pAffineTransform->RunAndGetBitmap(); } +AffineTransform::Settings AffineTransform::Interpolate(const Settings& a, const Settings& b, double t) +{ + auto colorA = a.pBgColor; + auto colorB = b.pBgColor; + + if ( colorA->GetPixelFormat() != colorB->GetPixelFormat() ) + throw std::invalid_argument("background colors should have the same pixel format"); + + auto pColor = IColor::Create( colorA->GetPixelFormat(), { 0, 0, 0, 0 } ); + + for ( int i = 0; i < ChannelCount(colorA->GetPixelFormat()); ++i ) + { + pColor->SetChannel( i, colorA->GetChannel( i ) + (colorB->GetChannel( i ) - colorA->GetChannel( i )) * t ); + } + + PointD translationA; + PointD translationB; + a.transform.translation( &translationA.x, &translationA.y ); + b.transform.translation( &translationB.x, &translationB.y ); + + PointD scalingA; + PointD scalingB; + a.transform.scaling( &scalingA.x, &scalingA.y ); + b.transform.scaling( &scalingB.x, &scalingB.y ); + + double rotationA = a.transform.rotation(); + double rotationB = b.transform.rotation(); + + PointD translation = translationA + (translationB - translationA) * t; + PointD scaling = scalingA + (scalingB - scalingA) * t; + + double rotation = rotationA + (rotationB - rotationA) * t; + + AffineTransform::Settings result; + result.pBgColor = pColor; + result.transform = agg::trans_affine_scaling( scaling.x, scaling.y ) * agg::trans_affine_rotation( rotation ) * agg::trans_affine_translation( translation.x, translation.y ); + + return result; +} + ACMB_NAMESPACE_END \ No newline at end of file diff --git a/Transforms/AffineTransform.h b/Transforms/AffineTransform.h index 22fb27e..3a1da7f 100644 --- a/Transforms/AffineTransform.h +++ b/Transforms/AffineTransform.h @@ -22,6 +22,8 @@ class AffineTransform : public BaseTransform static std::shared_ptr Create( IBitmapPtr pSrcBitmap, const Settings& settings ); static std::shared_ptr Create( PixelFormat, const Settings& settings ); static IBitmapPtr ApplyTransform( IBitmapPtr pSrcBitmap, const Settings& settings ); + + static Settings Interpolate( const Settings& a, const Settings& b, double t ); }; ACMB_NAMESPACE_END \ No newline at end of file diff --git a/Transforms/BitmapHealer.cpp b/Transforms/BitmapHealer.cpp index e1ec8de..3ddc0d7 100644 --- a/Transforms/BitmapHealer.cpp +++ b/Transforms/BitmapHealer.cpp @@ -124,4 +124,22 @@ IBitmapPtr BitmapHealer::ApplyTransform( IBitmapPtr pSrcBitmap, const Settings& return pBitmapHealer->RunAndGetBitmap(); } +BitmapHealer::Settings BitmapHealer::Interpolate(const BitmapHealer::Settings& a, const BitmapHealer::Settings& b, double t) +{ + if ( a.size() != b.size() ) + throw std::invalid_argument( "BitmapHealer: Settings must have the same size" ); + + BitmapHealer::Settings result( a.size() ); + + for ( size_t i = 0; i < a.size(); ++i ) + { + result[i].from = a[i].from + (b[i].from - a[i].from) * t; + result[i].to = a[i].to + (b[i].to - a[i].to) * t; + result[i].radius = a[i].radius + (b[i].radius - a[i].radius) * t; + result[i].gamma = a[i].gamma + (b[i].gamma - a[i].gamma) * t; + } + + return result; +} + ACMB_NAMESPACE_END \ No newline at end of file diff --git a/Transforms/BitmapHealer.h b/Transforms/BitmapHealer.h index 0268e79..985354d 100644 --- a/Transforms/BitmapHealer.h +++ b/Transforms/BitmapHealer.h @@ -27,6 +27,8 @@ class BitmapHealer : public BaseTransform static std::shared_ptr Create( IBitmapPtr pSrcBitmap, const Settings& settings ); static std::shared_ptr Create( PixelFormat, const Settings& settings ); static IBitmapPtr ApplyTransform( IBitmapPtr pSrcBitmap, const Settings& settings ); + + static Settings Interpolate(const Settings& a, const Settings& b, double t); }; ACMB_NAMESPACE_END diff --git a/Transforms/CropTransform.cpp b/Transforms/CropTransform.cpp index 5ded31f..f4080fe 100644 --- a/Transforms/CropTransform.cpp +++ b/Transforms/CropTransform.cpp @@ -1,4 +1,5 @@ #include "CropTransform.h" +#include "./math.h" #include #include #include @@ -150,4 +151,22 @@ void CropTransform::CalcParams( std::shared_ptr pParams ) _pixelFormat = pParams->GetPixelFormat(); } +CropTransform::Settings CropTransform::Interpolate(const CropTransform::Settings& a, const CropTransform::Settings& b, double t) +{ + PointD aCenter = a.GetCenter(); + PointD bCenter = b.GetCenter(); + + PointD resultCenter = aCenter * ( 1 - t ) + bCenter * t; + double resultWidth = a.width * ( 1 - t ) + b.width * t; + double resultHeight = a.height * ( 1 - t ) + b.height * t; + + return CropTransform::Settings + { + .x = int( resultCenter.x - resultWidth / 2 + 0.5), + .y = int( resultCenter.y - resultHeight / 2 + 0.5), + .width = int( resultWidth + 0.5), + .height = int( resultHeight + 0.5) + }; +} + ACMB_NAMESPACE_END diff --git a/Transforms/CropTransform.h b/Transforms/CropTransform.h index 9373cf1..d98a13e 100644 --- a/Transforms/CropTransform.h +++ b/Transforms/CropTransform.h @@ -27,6 +27,8 @@ class CropTransform : public BaseTransform /// returns size of destination image virtual void CalcParams( std::shared_ptr pParams ) override; + + static Settings Interpolate(const Settings& a, const Settings& b, double t); }; ACMB_NAMESPACE_END \ No newline at end of file diff --git a/Transforms/LevelsTransform.cpp b/Transforms/LevelsTransform.cpp index b988444..8e55455 100644 --- a/Transforms/LevelsTransform.cpp +++ b/Transforms/LevelsTransform.cpp @@ -179,4 +179,18 @@ LevelsTransform::Settings LevelsTransform::GetAutoSettings( IBitmapPtr pSrcBitma return result; } +LevelsTransform::Settings LevelsTransform::Interpolate(const LevelsTransform::Settings& a, const LevelsTransform::Settings& b, double t) +{ + Settings result; + for ( size_t i = 0; i < 4; ++i ) + { + result.levels[i].min = a.levels[i].min + (b.levels[i].min - a.levels[i].min) * t; + result.levels[i].max = a.levels[i].max + (b.levels[i].max - a.levels[i].max) * t; + result.levels[i].gamma = a.levels[i].gamma + (b.levels[i].gamma - a.levels[i].gamma) * t; + } + + result.adjustChannels = a.adjustChannels || b.adjustChannels; + return result; +} + ACMB_NAMESPACE_END \ No newline at end of file diff --git a/Transforms/LevelsTransform.h b/Transforms/LevelsTransform.h index 8ae2758..97d94f0 100644 --- a/Transforms/LevelsTransform.h +++ b/Transforms/LevelsTransform.h @@ -35,6 +35,8 @@ class LevelsTransform : public BaseTransform static IBitmapPtr ApplyLevels( IBitmapPtr pSrcBitmap, const Settings& levels ); static Settings GetAutoSettings( IBitmapPtr pSrcBitmap, bool adjustChannels = false ); + + static Settings Interpolate( const Settings& a, const Settings& b, double t ); }; ACMB_NAMESPACE_END diff --git a/Transforms/ResizeTransform.cpp b/Transforms/ResizeTransform.cpp index 2f363a7..d80690d 100644 --- a/Transforms/ResizeTransform.cpp +++ b/Transforms/ResizeTransform.cpp @@ -145,10 +145,21 @@ void ResizeTransform::CalcParams( std::shared_ptr pParams ) _pixelFormat = pParams->GetPixelFormat(); } -Size ResizeTransform::GetSizeWithPreservedRatio( Size srcSize, Size dstSize ) +Size ResizeTransform::GetSizeWithPreservedRatio( Size srcSize, Size maxSize ) { - const auto srcRatio = static_cast< float >(srcSize.width) / srcSize.height; - const auto dstRatio = static_cast< float >(dstSize.width) / dstSize.height; - return (srcRatio < dstRatio) ? Size{ int( dstSize.height* srcRatio + 0.5f ), dstSize.height } : Size{ dstSize.width, int( dstSize.height* srcRatio ) }; + const float widthRatio = static_cast( maxSize.width ) / static_cast( srcSize.width ); + const float heightRatio = static_cast( maxSize.height ) / static_cast( srcSize.height ); + const float ratio = std::min( widthRatio, heightRatio ); + return Size( static_cast( srcSize.width * ratio ), static_cast( srcSize.height * ratio ) ); +} + +ResizeTransform::Settings ResizeTransform::Interpolate(const Settings& a, const Settings& b, double t) +{ + ResizeTransform::Settings result; + + result.width = a.width + t * ( b.width - a.width ); + result.height = a.height + t * ( b.height - a.height ); + + return result; } ACMB_NAMESPACE_END diff --git a/Transforms/ResizeTransform.h b/Transforms/ResizeTransform.h index 171a7af..98e0248 100644 --- a/Transforms/ResizeTransform.h +++ b/Transforms/ResizeTransform.h @@ -25,6 +25,8 @@ class ResizeTransform : public BaseTransform virtual void CalcParams( std::shared_ptr pParams ) override; static Size GetSizeWithPreservedRatio( Size srcSize, Size dstSize ); + + static Settings Interpolate( const Settings& a, const Settings& b, double t ); }; ACMB_NAMESPACE_END diff --git a/Transforms/SaturationTransform.cpp b/Transforms/SaturationTransform.cpp index f0fbe45..b201f7c 100644 --- a/Transforms/SaturationTransform.cpp +++ b/Transforms/SaturationTransform.cpp @@ -94,4 +94,10 @@ IBitmapPtr SaturationTransform::Saturate( IBitmapPtr srcBitmap, float intensity return pTransform->RunAndGetBitmap(); } +SaturationTransform::Settings SaturationTransform::Interpolate(const Settings& a, const Settings& b, double t) +{ + return a + t * (b - a); +} + + ACMB_NAMESPACE_END \ No newline at end of file diff --git a/Transforms/SaturationTransform.h b/Transforms/SaturationTransform.h index b7b261e..e4399a5 100644 --- a/Transforms/SaturationTransform.h +++ b/Transforms/SaturationTransform.h @@ -16,6 +16,8 @@ class SaturationTransform : public BaseTransform static std::shared_ptr Create( IBitmapPtr pSrcBitmap, float intensity ); static std::shared_ptr Create( PixelFormat pixelFormat, float intensity ); static IBitmapPtr Saturate( IBitmapPtr pSrcBitmap, float intensity ); + + static Settings Interpolate( const Settings& a, const Settings& b, double t ); }; ACMB_NAMESPACE_END \ No newline at end of file diff --git a/Transforms/WarpTransform.cpp b/Transforms/WarpTransform.cpp index f63f8bf..090747f 100644 --- a/Transforms/WarpTransform.cpp +++ b/Transforms/WarpTransform.cpp @@ -148,4 +148,29 @@ IBitmapPtr WarpTransform::Warp( IBitmapPtr pSrcBitmap, const Settings& controls auto pWarpTransform = Create( pSrcBitmap, controls ); return pWarpTransform->RunAndGetBitmap(); } + +WarpTransform::Settings WarpTransform::Interpolate(const Settings& a, const Settings& b, double t) +{ + Settings settings; + + auto colorA = a.pBgColor; + auto colorB = b.pBgColor; + + if ( colorA->GetPixelFormat() != colorB->GetPixelFormat() ) + throw std::invalid_argument("background colors should have the same pixel format"); + + auto pColor = IColor::Create(colorA->GetPixelFormat(), { 0, 0, 0, 0 }); + + + for ( uint32_t i = 0; i < ChannelCount(colorA->GetPixelFormat()); ++i ) + { + pColor->SetChannel(i, colorA->GetChannel(i) + (colorB->GetChannel(i) - colorA->GetChannel(i)) * t); + } + + for ( int i = 0; i < 16; ++i ) + settings.controls[i] = a.controls[i] + (b.controls[i] - a.controls[i]) * t; + + settings.pBgColor = pColor; + return settings; +} ACMB_NAMESPACE_END \ No newline at end of file diff --git a/Transforms/WarpTransform.h b/Transforms/WarpTransform.h index f777be5..7a5d333 100644 --- a/Transforms/WarpTransform.h +++ b/Transforms/WarpTransform.h @@ -24,6 +24,8 @@ class WarpTransform : public BaseTransform static std::shared_ptr Create( IBitmapPtr pSrcBitmap, const Settings& controls ); static std::shared_ptr Create( PixelFormat, const Settings& controls ); static IBitmapPtr Warp( IBitmapPtr pSrcBitmap, const Settings& controls ); + + static Settings Interpolate( const Settings& a, const Settings& b, double t ); };