Skip to content

Commit

Permalink
Batch process: add support for animated images
Browse files Browse the repository at this point in the history
This has been on my to-do list ever since I tackled animation back in v8.0, but finally it's ready!  You can now use PD's batch converter to process animated images, including tasks like mass-converting animated GIFs to APNG or animated WebP instead.

Most of this is handled silently by PD.  It automatically detects files with animation data and switches to animation import/export engines instead of static ones.  The new major new UI element is that when you're exporting to a fixed destination format (for e.g. mass-converting from one file format to another), there is now a checkbox for auto-detecting animation files, and an associated button for setting fixed animation export parameters.  This how you set things like WebP animation export quality for destination files.

I also fixed a bunch of minor UI bugs and quirks in the batch process wizard while here.  (The batch processor tends to get ignored during dev cycles, then mass tested-and-fixed just before release... so I'm actually ahead of the game this time? lol)
  • Loading branch information
tannerhelland committed Oct 28, 2021
1 parent 1de515b commit 647927e
Show file tree
Hide file tree
Showing 11 changed files with 352 additions and 225 deletions.
8 changes: 7 additions & 1 deletion Classes/cFileDialogVista.cls
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,10 @@ End Function
Public Function DialogShow(ByVal ownerHwnd As Long, _
Optional ByVal Mode As FileDialogModeEnum = FDLG_FILEOPEN, _
Optional dialogTitle As String) As Long


'Added by Tanner: notify the UI subsystem that an OS-owned dialog is active
Interface.NotifySystemDialogState True

' The user-selected item(s) are passed via the Result property of this class
' Results are in the form of an IShellItem object passed to you
' There are a few IShell_[xxx] methods provided so that you can get
Expand Down Expand Up @@ -537,6 +540,9 @@ Public Function DialogShow(ByVal ownerHwnd As Long, _
Call Me.Clear
If pReturn Then Set m_Result = pvPointerToIUnknown(pReturn, True)

'Added by Tanner: notify the UI subsystem that an OS-owned dialog is inactive
Interface.NotifySystemDialogState False

End Function
'--------------------------------------------------------------------------------------------------

Expand Down
6 changes: 6 additions & 0 deletions Classes/cUnicodeBrowseFolders.cls
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ Private m_InitPath As String ' startup path for the dialog, either string or

Public Function ShowBrowseForFolder(ByVal ownerHwnd As Long, Optional releasePIDL As Boolean = True) As Boolean

'Added by Tanner: notify the UI subsystem that an OS-owned dialog is active
Interface.NotifySystemDialogState True

' Depending on what flags you set, you may get a returned path and/or file name or nothing.
' If the user makes a selection to a virtual path/object, you will not get a path returned.

Expand Down Expand Up @@ -174,6 +177,9 @@ Public Function ShowBrowseForFolder(ByVal ownerHwnd As Long, Optional releasePID
obif.lpfnCallback = 0&
End If

'Added by Tanner: notify the UI subsystem that an OS-owned dialog is inactive
Interface.NotifySystemDialogState False

End Function

' forces an error to occur if user cancels/closes dialog without selecting anything
Expand Down
2 changes: 1 addition & 1 deletion Classes/pdTimerAnimation.cls
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ End Function
Friend Sub NotifyFrameCount(ByVal newFrameCount As Long)

m_FrameCount = newFrameCount
ReDim m_FrameTimesMS(0 To m_FrameCount - 1) As Long
If (m_FrameCount > 0) Then ReDim m_FrameTimesMS(0 To m_FrameCount - 1) As Long

'Reset frame time trackers (debug only)
m_FramesDisplayed = 0
Expand Down
5 changes: 3 additions & 2 deletions Classes/pdWebP.cls
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,7 @@ End Function
'Save a pdImage (with animation data) to an animated WebP file
Friend Function SaveAnimatedWebP_ToFile(ByRef srcImage As pdImage, ByRef srcOptions As String, ByRef dstFile As String) As Boolean

Const FUNC_NAME As String = "SaveWebP_ToFile"
Const FUNC_NAME As String = "SaveAnimatedWebP_ToFile"
SaveAnimatedWebP_ToFile = False

'Basic input validation
Expand All @@ -630,6 +630,7 @@ Friend Function SaveAnimatedWebP_ToFile(ByRef srcImage As pdImage, ByRef srcOpti
'Before doing anything else, make sure the source image has multiple layers. (If it doesn't,
' we'll silently forward it to the single-frame saver.)
If (srcImage.GetNumOfLayers < 2) Then
PDDebug.LogAction "single frame file found - rerouting to static exporter"
SaveAnimatedWebP_ToFile = Me.SaveWebP_ToFile(srcImage, srcOptions, dstFile)
Exit Function
End If
Expand Down Expand Up @@ -1471,7 +1472,7 @@ Private Sub InternalError(ByVal funcName As String, Optional ByRef errString As
If (libReturn <> VP8_STATUS_OK) Then
PDDebug.LogAction funcName & "returned error #" & libReturn & "(" & errString & ")", PDM_External_Lib
Else
PDDebug.LogAction funcName & " error:" & errString, PDM_External_Lib
PDDebug.LogAction funcName & "error: " & errString, PDM_External_Lib
End If
End Sub

Expand Down
458 changes: 281 additions & 177 deletions Forms/File_BatchWizard.frm

Large diffs are not rendered by default.

24 changes: 15 additions & 9 deletions Forms/File_Export_AnimatedGIF.frm
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ Attribute VB_Exposed = False
'Animated GIF export dialog
'Copyright 2012-2021 by Tanner Helland
'Created: 26/August/19
'Last updated: 06/September/19
'Last update: separate animated GIF and PNG export dialogs
'Last updated: 28/October/21
'Last update: prep dialog for compatibility with batch processor
'
'In v8.0, PhotoDemon gained the ability to export animated GIF files.
'
Expand Down Expand Up @@ -282,7 +282,7 @@ Public Sub ShowDialog(Optional ByRef srcImage As pdImage = Nothing)

End If

'Next, prepare various controls on the metadata panel
'Next, prepare various controls on the metadata panel (TODO)
'mtdManager.SetParentImage m_SrcImage, PDIF_GIF

'Apply translations and visual themes
Expand All @@ -301,7 +301,10 @@ Public Sub ShowDialog(Optional ByRef srcImage As pdImage = Nothing)
End Sub

Private Sub btnPlay_Click(Index As Integer, ByVal Shift As ShiftConstants)


'Failsafe check for batch process mode (which won't supply a source image)
If (m_FrameCount <= 0) Or (m_SrcImage Is Nothing) Then Exit Sub

Select Case Index

'Play/pause
Expand Down Expand Up @@ -348,7 +351,7 @@ End Sub
Private Sub cmdBar_OKClick()
m_Timer.StopTimer
m_FormatParamString = GetExportParamString
'm_MetadataParamString = mtdManager.GetMetadataSettings
'm_MetadataParamString = mtdManager.GetMetadataSettings (TODO)
m_UserDialogAnswer = vbOK
Me.Visible = False
End Sub
Expand All @@ -362,7 +365,7 @@ Private Sub cmdBar_ReadCustomPresetData()

'If all frames have undefined frame times (e.g. none embedded a frame time in the layer name),
' default to a "fixed" frame time suggestion
If m_FrameTimesUndefined Then btsFrameTimes.ListIndex = 0 Else btsFrameTimes.ListIndex = 1
If m_FrameTimesUndefined And (Not m_SrcImage Is Nothing) Then btsFrameTimes.ListIndex = 0 Else btsFrameTimes.ListIndex = 1

End Sub

Expand All @@ -385,7 +388,7 @@ Private Sub cmdBar_ResetClick()

'If all frames have undefined frame times (e.g. none embedded a frame time in the layer name),
' default to a "fixed" frame time suggestion
If m_FrameTimesUndefined Then btsFrameTimes.ListIndex = 0 Else btsFrameTimes.ListIndex = 1
If m_FrameTimesUndefined And (Not m_SrcImage Is Nothing) Then btsFrameTimes.ListIndex = 0 Else btsFrameTimes.ListIndex = 1

End Sub

Expand Down Expand Up @@ -479,7 +482,9 @@ Private Sub UpdateAgainstCurrentTheme()
End Sub

Private Sub m_Timer_DrawFrame(ByVal idxFrame As Long)


If (m_FrameCount <= 0) Or (m_SrcImage Is Nothing) Then Exit Sub

'Render the current frame
RenderAnimationFrame

Expand All @@ -494,7 +499,7 @@ End Sub
' like frame delay times)
Private Sub UpdateAnimationSettings()

If (m_SrcImage Is Nothing) Then Exit Sub
If (m_FrameCount <= 0) Or (m_SrcImage Is Nothing) Then Exit Sub

'Suspend automatic control-based updates while we get everything synchronized
m_DoNotUpdate = True
Expand Down Expand Up @@ -588,6 +593,7 @@ Private Sub RenderAnimationFrame()

If m_DoNotUpdate Then Exit Sub
If (m_AniFrame Is Nothing) Then Exit Sub
If (m_FrameCount <= 0) Or (m_SrcImage Is Nothing) Then Exit Sub

Dim idxFrame As Long
idxFrame = m_Timer.GetCurrentFrame()
Expand Down
18 changes: 12 additions & 6 deletions Forms/File_Export_AnimatedPNG.frm
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,8 @@ Attribute VB_Exposed = False
'Animated PNG export dialog
'Copyright 2012-2021 by Tanner Helland
'Created: 26/August/19
'Last updated: 06/September/19
'Last update: separate from original animated GIF export dialog, as the two exporters have different needs
'Last updated: 28/October/21
'Last update: prep dialog for compatibility with batch processor
'
'In v8.0, PhotoDemon gained the ability to export animated PNG files. This dialog exposes relevant
' export parameters to the user.
Expand Down Expand Up @@ -272,6 +272,9 @@ End Sub

Private Sub btnPlay_Click(Index As Integer, ByVal Shift As ShiftConstants)

'Failsafe check for batch process mode (which won't supply a source image)
If (m_FrameCount <= 0) Or (m_SrcImage Is Nothing) Then Exit Sub

Select Case Index

'Play/pause
Expand Down Expand Up @@ -332,7 +335,7 @@ Private Sub cmdBar_ReadCustomPresetData()

'If all frames have undefined frame times (e.g. none embedded a frame time in the layer name),
' default to a "fixed" frame time suggestion
If m_FrameTimesUndefined Then btsFrameTimes.ListIndex = 0 Else btsFrameTimes.ListIndex = 1
If m_FrameTimesUndefined And (Not m_SrcImage Is Nothing) Then btsFrameTimes.ListIndex = 0 Else btsFrameTimes.ListIndex = 1

End Sub

Expand All @@ -355,7 +358,7 @@ Private Sub cmdBar_ResetClick()

'If all frames have undefined frame times (e.g. none embedded a frame time in the layer name),
' default to a "fixed" frame time suggestion
If m_FrameTimesUndefined Then btsFrameTimes.ListIndex = 0 Else btsFrameTimes.ListIndex = 1
If m_FrameTimesUndefined And (Not m_SrcImage Is Nothing) Then btsFrameTimes.ListIndex = 0 Else btsFrameTimes.ListIndex = 1

End Sub

Expand Down Expand Up @@ -440,7 +443,9 @@ Private Sub UpdateAgainstCurrentTheme()
End Sub

Private Sub m_Timer_DrawFrame(ByVal idxFrame As Long)


If (m_FrameCount <= 0) Or (m_SrcImage Is Nothing) Then Exit Sub

'Render the current frame
RenderAnimationFrame

Expand All @@ -455,7 +460,7 @@ End Sub
' like frame delay times)
Private Sub UpdateAnimationSettings()

If (m_SrcImage Is Nothing) Then Exit Sub
If (m_FrameCount <= 0) Or (m_SrcImage Is Nothing) Then Exit Sub

'Suspend automatic control-based updates while we get everything synchronized
m_DoNotUpdate = True
Expand Down Expand Up @@ -549,6 +554,7 @@ Private Sub RenderAnimationFrame()

If m_DoNotUpdate Then Exit Sub
If (m_AniFrame Is Nothing) Then Exit Sub
If (m_FrameCount <= 0) Or (m_SrcImage Is Nothing) Then Exit Sub

Dim idxFrame As Long
idxFrame = m_Timer.GetCurrentFrame()
Expand Down
18 changes: 12 additions & 6 deletions Forms/File_Export_AnimatedWebP.frm
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ Attribute VB_Exposed = False
'Animated WebP export dialog
'Copyright 2019-2021 by Tanner Helland
'Created: 26/August/19
'Last updated: 30/September/21
'Last update: wrap up feature support unique to WebP
'Last updated: 28/October/21
'Last update: prep dialog for compatibility with batch processor
'
'In v9.0, PhotoDemon gained the ability to export animated WebP files. This dialog exposes relevant
' export parameters to the user.
Expand Down Expand Up @@ -293,6 +293,9 @@ End Sub

Private Sub btnPlay_Click(Index As Integer, ByVal Shift As ShiftConstants)

'Failsafe check for batch process mode (which won't supply a source image)
If (m_FrameCount <= 0) Or (m_SrcImage Is Nothing) Then Exit Sub

Select Case Index

'Play/pause
Expand Down Expand Up @@ -353,7 +356,7 @@ Private Sub cmdBar_ReadCustomPresetData()

'If all frames have undefined frame times (e.g. none embedded a frame time in the layer name),
' default to a "fixed" frame time suggestion
If m_FrameTimesUndefined Then btsFrameTimes.ListIndex = 0 Else btsFrameTimes.ListIndex = 1
If m_FrameTimesUndefined And (Not m_SrcImage Is Nothing) Then btsFrameTimes.ListIndex = 0 Else btsFrameTimes.ListIndex = 1

End Sub

Expand All @@ -376,7 +379,7 @@ Private Sub cmdBar_ResetClick()

'If all frames have undefined frame times (e.g. none embedded a frame time in the layer name),
' default to a "fixed" frame time suggestion
If m_FrameTimesUndefined Then btsFrameTimes.ListIndex = 0 Else btsFrameTimes.ListIndex = 1
If m_FrameTimesUndefined And (Not m_SrcImage Is Nothing) Then btsFrameTimes.ListIndex = 0 Else btsFrameTimes.ListIndex = 1

'WebP-specific values follow
sldQuality.Value = 75
Expand Down Expand Up @@ -475,7 +478,9 @@ Private Sub UpdateAgainstCurrentTheme()
End Sub

Private Sub m_Timer_DrawFrame(ByVal idxFrame As Long)


If (m_FrameCount <= 0) Or (m_SrcImage Is Nothing) Then Exit Sub

'Render the current frame
RenderAnimationFrame

Expand All @@ -490,7 +495,7 @@ End Sub
' like frame delay times)
Private Sub UpdateAnimationSettings()

If (m_SrcImage Is Nothing) Then Exit Sub
If (m_FrameCount <= 0) Or (m_SrcImage Is Nothing) Then Exit Sub

'Suspend automatic control-based updates while we get everything synchronized
m_DoNotUpdate = True
Expand Down Expand Up @@ -590,6 +595,7 @@ Private Sub RenderAnimationFrame()
If m_DoNotUpdate Then Exit Sub
If (m_AniFrame Is Nothing) Then Exit Sub
If (m_tmpAnimationFrame Is Nothing) Then Exit Sub
If (m_FrameCount <= 0) Or (m_SrcImage Is Nothing) Then Exit Sub

Dim idxFrame As Long
idxFrame = m_Timer.GetCurrentFrame()
Expand Down
6 changes: 4 additions & 2 deletions Modules/Loading.bas
Original file line number Diff line number Diff line change
Expand Up @@ -307,10 +307,12 @@ Public Function LoadFileAsNewImage(ByRef srcFile As String, Optional ByVal sugge

PDDebug.LogAction "Finalizing image details..."

'The finalized pdImage object is finally worthy of being added to the master PD collection. Note that this function will
' automatically update PDImages.GetActiveImageID() to point to the new image.
'The finalized pdImage object is finally worthy of being added to the master PD collection.
' (Note that this function will automatically update PDImages.GetActiveImageID() to point
' at the new image.)
PDImages.AddImageToMasterCollection targetImage

'The UI needs a *lot* of changes to reflect the state of the newly loaded image
ImageImporter.ApplyPostLoadUIChanges srcFile, targetImage, addToRecentFiles

'Because ExifTool is sending us data in the background, we periodically yield for metadata piping.
Expand Down
30 changes: 10 additions & 20 deletions Modules/Saving.bas
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@ Public Function PhotoDemon_SaveImage(ByRef srcImage As pdImage, ByVal dstPath As

'After a successful dialog invocation, immediately save the metadata parameters to the parent pdImage object.
' ExifTool will handle those settings separately, independent of the format-specific export engine.
If Saving.GetExportParamsFromDialog(srcImage, saveFormat, saveParameters, metadataParameters) Then
Dim useAnimationDialog As Boolean
If (Not srcImage Is Nothing) Then useAnimationDialog = srcImage.IsAnimated Else useAnimationDialog = False
If Saving.GetExportParamsFromDialog(srcImage, saveFormat, saveParameters, metadataParameters, useAnimationDialog) Then
srcImage.ImgStorage.AddEntry "MetadataSettings", metadataParameters

'If the user cancels the dialog, exit immediately
Expand Down Expand Up @@ -325,7 +327,7 @@ End Sub
' returned from the associated format-specific dialog.
'
'Returns: TRUE if dialog was closed via OK button; FALSE otherwise.
Public Function GetExportParamsFromDialog(ByRef srcImage As pdImage, ByVal outputPDIF As PD_IMAGE_FORMAT, ByRef dstParamString As String, ByRef dstMetadataString As String) As Boolean
Public Function GetExportParamsFromDialog(ByRef srcImage As pdImage, ByVal outputPDIF As PD_IMAGE_FORMAT, ByRef dstParamString As String, ByRef dstMetadataString As String, Optional ByVal displayAnimationVersion As Boolean = False) As Boolean

'As a failsafe, make sure the requested format even *has* an export dialog!
If ImageFormats.IsExportDialogSupported(outputPDIF) Then
Expand All @@ -339,12 +341,8 @@ Public Function GetExportParamsFromDialog(ByRef srcImage As pdImage, ByVal outpu
GetExportParamsFromDialog = (Dialogs.PromptBMPSettings(srcImage, dstParamString, dstMetadataString) = vbOK)

Case PDIF_GIF
If (Not srcImage Is Nothing) Then
If srcImage.IsAnimated Then
GetExportParamsFromDialog = (Dialogs.PromptExportAnimatedGIF(srcImage, dstParamString, dstMetadataString) = vbOK)
Else
GetExportParamsFromDialog = (Dialogs.PromptGIFSettings(srcImage, dstParamString, dstMetadataString) = vbOK)
End If
If displayAnimationVersion Then
GetExportParamsFromDialog = (Dialogs.PromptExportAnimatedGIF(srcImage, dstParamString, dstMetadataString) = vbOK)
Else
GetExportParamsFromDialog = (Dialogs.PromptGIFSettings(srcImage, dstParamString, dstMetadataString) = vbOK)
End If
Expand All @@ -362,12 +360,8 @@ Public Function GetExportParamsFromDialog(ByRef srcImage As pdImage, ByVal outpu
GetExportParamsFromDialog = (Dialogs.PromptJXRSettings(srcImage, dstParamString, dstMetadataString) = vbOK)

Case PDIF_PNG
If (Not srcImage Is Nothing) Then
If srcImage.IsAnimated Then
GetExportParamsFromDialog = (Dialogs.PromptExportAnimatedPNG(srcImage, dstParamString, dstMetadataString) = vbOK)
Else
GetExportParamsFromDialog = (Dialogs.PromptPNGSettings(srcImage, dstParamString, dstMetadataString) = vbOK)
End If
If displayAnimationVersion Then
GetExportParamsFromDialog = (Dialogs.PromptExportAnimatedPNG(srcImage, dstParamString, dstMetadataString) = vbOK)
Else
GetExportParamsFromDialog = (Dialogs.PromptPNGSettings(srcImage, dstParamString, dstMetadataString) = vbOK)
End If
Expand All @@ -385,12 +379,8 @@ Public Function GetExportParamsFromDialog(ByRef srcImage As pdImage, ByVal outpu
GetExportParamsFromDialog = (Dialogs.PromptTIFFSettings(srcImage, dstParamString, dstMetadataString) = vbOK)

Case PDIF_WEBP
If (Not srcImage Is Nothing) Then
If srcImage.IsAnimated Then
GetExportParamsFromDialog = (Dialogs.PromptExportAnimatedWebP(srcImage, dstParamString, dstMetadataString) = vbOK)
Else
GetExportParamsFromDialog = (Dialogs.PromptWebPSettings(srcImage, dstParamString, dstMetadataString) = vbOK)
End If
If displayAnimationVersion Then
GetExportParamsFromDialog = (Dialogs.PromptExportAnimatedWebP(srcImage, dstParamString, dstMetadataString) = vbOK)
Else
GetExportParamsFromDialog = (Dialogs.PromptWebPSettings(srcImage, dstParamString, dstMetadataString) = vbOK)
End If
Expand Down
2 changes: 1 addition & 1 deletion PhotoDemon.vbp
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ Description="PhotoDemon Photo Editor"
CompatibleMode="0"
MajorVer=8
MinorVer=9
RevisionVer=994
RevisionVer=1004
AutoIncrementVer=1
ServerSupportFiles=0
VersionComments="Copyright 2000-2021 Tanner Helland - photodemon.org"
Expand Down

0 comments on commit 647927e

Please sign in to comment.