Jump to content

How to Copy MetaData (Exif And more) from a Photo (Jpeg) to Another Photo (Jpeg) ?


Go to solution Solved by KaFu,

Recommended Posts

#include <GDIPlus.au3>
#include <WinAPIHObj.au3>



Local $sfile = FileOpenDialog("Please select file", "", "JPEG files (*.jpg;*.jpeg)");


_Clone_MetaData($sfile)

Func _Clone_MetaData($sfile, $sMasterfile = @ScriptDir & "\IMG_Base.JPG")
    Local $hImage1, $hImage2, $hGraphic

    If Not FileExists($sfile) Then Return 0
    If Not FileExists($sMasterfile) Then Return 0

    ; Initialize GDI+ library
    _GDIPlus_Startup()

    $hImage2 = _GDIPlus_ImageLoadFromFile($sfile)
    $hImage1 = _GDIPlus_ImageLoadFromFile($sMasterfile)

    Local $aDim = _GDIPlus_ImageGetDimension ( $hImage1 )
    Local $iXUR = $aDim[0]
    Local $iYDR = $aDim[1]

    ; Draw one image in another
    $hGraphic = _GDIPlus_ImageGetGraphicsContext($hImage1)


    _GDIPlus_GraphicsSetInterpolationMode ( $hGraphic, 7 )
    _GDIPlus_DrawImagePoints($hGraphic, $hImage2, 0, 0, $iXUR, 0, 0, $iYDR)


    ; Save resultant image
    Local $sNewFile = FileSaveDialog("Choose a filename.", @ScriptDir, "JPEG files (*.jpg;*.jpeg)", 2, "IMG_Cloned.JPG"
    _GDIPlus_ImageSaveToFile($hImage1, $sNewFile)

    ; Clean up resources
    _GDIPlus_ImageDispose($hImage1)
    _GDIPlus_ImageDispose($hImage2)


    ; Shut down GDI+ library
    _GDIPlus_Shutdown()

    If FileExists($sNewFile) Then ShellExecute($sNewFile)
EndFunc   ;==>_Clone_MetaData

i use this sample for clone metadata, i need a better solution 

Link to comment
Share on other sites

2 hours ago, Parsix said:

i use this sample for clone metadata, i need a better solution

Possibly, the use of the well-known command line tool  exiftool  (by Phil Harvey) would be a suitable solution.

exiftool -TagsFromFile fromImage.jpg toImage.jpg

Additional info on -TagsFromFile, see for example : https://exiftool.org/forum/index.php?topic=10189.0

 

Musashi-C64.png

"In the beginning the Universe was created. This has made a lot of people very angry and been widely regarded as a bad move."

Link to comment
Share on other sites

1 minute ago, Musashi said:

Possibly, the use of the well-known command line tool  exiftool  (by Phil Harvey) would be a suitable solution.

exiftool -TagsFromFile fromImage.jpg toImage.jpg

Additional info on -TagsFromFile, see for example : https://exiftool.org/forum/index.php?topic=10189.0

 

thanks, do you have a solution without exiftool (any external app or dll) ?

Link to comment
Share on other sites

35 minutes ago, Parsix said:

thanks, do you have a solution without exiftool (any external app or dll) ?

Unfortunately, no external app or dll I have worked with so far. I use exiftool only for removing metadata from images, but I know, that the tool has many more powerful features. The command line variant does not even need to be installed.

By the way : what difficulties do you expect regarding the use of exiftool ?

Musashi-C64.png

"In the beginning the Universe was created. This has made a lot of people very angry and been widely regarded as a bad move."

Link to comment
Share on other sites

  • Solution

I use something like this in my program BIC (as a fallback for the prerfered ExifTool method).

Although I too prefer ExifTool over this, as GDI JPG processing is not lossless.

I process the files, save them as PNG, use an external tool called jpge.exe to encode a jpg from the png and then set the Exif infos with ExifTool.

; jpg with GDI always bad
; https://www.autoitscript.com/forum/topic/187573-perplexed-by-jpg-conversion-result/
; "The problem (after reading your edits) is probably due to the fact that GDI+ uses 4:1:1 subsampling for JPG files in it's default Encoder."
;  The issue is that GDI+ enables chroma subsampling which modifies the color values accordingly.

#include <GDIPlus.au3>
#include <WinAPIHObj.au3>

; Initialize GDI+ library
_GDIPlus_Startup()

Local $sfile_input = FileOpenDialog("Please select file", "", "JPEG files (*.jpg;*.jpeg)") ;
If @error Then Exit

Local $sfile_output = StringReplace($sfile_input, ".", "_Out.")

_Clone_MetaData($sfile_input, $sfile_output)

If FileExists($sfile_output) Then ShellExecute($sfile_output)

; Shut down GDI+ library
_GDIPlus_Shutdown()


Func _Clone_MetaData($sfile_input, $sfile_output)

    If Not FileExists($sfile_input) Then Return 0

    Local $a_DateTimeOriginal = FileGetTime($sfile_input)
    Local $s_DateTimeOriginal = $a_DateTimeOriginal[0] & ":" & $a_DateTimeOriginal[1] & ":" & $a_DateTimeOriginal[2] & " " & $a_DateTimeOriginal[3] & ":" & $a_DateTimeOriginal[4] & ":" & $a_DateTimeOriginal[5]

    Local $hImage = _GDIPlus_ImageLoadFromFile($sfile_input)

    Local $aDim = _GDIPlus_ImageGetDimension($hImage)
    Local $a_Local_Input_EXIF_Dimensions[2]
    $a_Local_Input_EXIF_Dimensions[0] = $aDim[0]
    $a_Local_Input_EXIF_Dimensions[1] = $aDim[1]

    Local $a_Local_Input_EXIF_Properties = cGDIPlus_ImageGetAllPropertyItems($hImage)

    Local $hClone = _GDIPlus_BitmapCloneArea($hImage, 0, 0, $aDim[0], $aDim[1], $GDIP_PXF32ARGB)

    If IsArray($a_Local_Input_EXIF_Properties) Then _GDIPlus_ImageSetPropertyItem_Array($hClone, $a_Local_Input_EXIF_Properties)

    Local $sString = "Code ripped from BIC, https://funk.eu"
    Local $iRes = _GDIPlus_ImageSetPropertyItem_Ex($hClone, 0x0131, $sString, 2, StringLen($sString) + 1)         ; Software > 2=ASCII

    If IsArray($a_Local_Input_EXIF_Dimensions) Then
        $iRes = _GDIPlus_ImageSetPropertyItem_Ex($hClone, 0x0100, $a_Local_Input_EXIF_Dimensions[0], 4, -1)       ; ImageWidth > 4=UINT
    EndIf

    If IsArray($a_Local_Input_EXIF_Dimensions) Then
        $iRes = _GDIPlus_ImageSetPropertyItem_Ex($hClone, 0x0101, $a_Local_Input_EXIF_Dimensions[1], 4, -1)       ; ImageHeight > 4=UINT
    EndIf

    $sString = @YEAR & ":" & @MON & ":" & @MDAY & " " & @HOUR & ":" & @MIN & ":" & @SEC

    $iRes = _GDIPlus_ImageSetPropertyItem_Ex($hClone, 0x0132, $sString, 2, StringLen($sString) + 1)         ; 0x0132 ModifyDate > 2=ASCII

    $sString = "Custom User Comment"
    $iRes = _GDIPlus_ImageSetPropertyItem_Ex($hClone, 0x9286, $sString, 2, StringLen($sString) + 1)         ; 0x9286 UserComment > 2=ASCII

    If $s_DateTimeOriginal Then $iRes = _GDIPlus_ImageSetPropertyItem_Ex($hClone, 0x9003, $s_DateTimeOriginal, 2, StringLen($s_DateTimeOriginal) + 1)         ; 0x9003 DateTimeOriginal > 2=ASCII

    _GDIPlus_ImageSaveToFile($hClone, $sfile_output)

    ; Clean up resources
    _GDIPlus_ImageDispose($hImage)
    _GDIPlus_BitmapDispose($hClone)

EndFunc   ;==>_Clone_MetaData





Func _GDIPlus_ImageSetPropertyItem_Array($hClone, $a_Local_Input_EXIF_Properties)
    For $i = 0 To UBound($a_Local_Input_EXIF_Properties) - 1
        Local $aInput = [$a_Local_Input_EXIF_Properties[$i][0], $a_Local_Input_EXIF_Properties[$i][1], $a_Local_Input_EXIF_Properties[$i][2], $a_Local_Input_EXIF_Properties[$i][3]]
        cGDIPlus_ImageSetPropertyItem($hClone, $aInput)
    Next
EndFunc   ;==>_GDIPlus_ImageSetPropertyItem_Array

Func _GDIPlus_ImageSetPropertyItem_Ex($hImage, $i_PropertyItem_Number, $s_PropertyItem_Value, $iType, $iLength)
    ; https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html
    #cs
        0x0131 > Software
        0x010e > ImageDescription
        0x9286 > UserComment
        0x0132 > ModifyDate
        0x9003 > DateTimeOriginal   string  ExifIFD     (date/time when original image was taken)
    #ce
    Local $a_PropertyValue = [$s_PropertyItem_Value]
    Local $a_PropertyItem = [$i_PropertyItem_Number, $iLength, $iType, $a_PropertyValue]
    Local $iRes = cGDIPlus_ImageSetPropertyItem($hImage, $a_PropertyItem)
    Return SetError(@error, @extended, $iRes)
EndFunc   ;==>_GDIPlus_ImageSetPropertyItem_Ex

; #FUNCTION# ====================================================================================================================
; Name ..........: cGDIPlus_ImageSetPropertyItem
; Description ...: Sets a specified property item (piece of meta data) for an Image object
; Syntax ........: cGDIPlus_ImageSetPropertyItem($hImage, $a1PropertyItem)
; Parameters ....: $hImage              - Pointer to an image object
;                  $a1PropertyItem      - Array containing the values of the property item
;                      [0] - identifier
;                      [1] - size of the value array
;                               positive: in bytes
;                               negative: for numeric types: in number of values
;                      [2] - type of value(s) in the value array
;                      [3] - value array
; Return values .: Success: True and @error = 0
;                  Failure: sets the @error flag to:
;                      1:  DllCall failed.  Common cause: _GIDPlus_Startup() has not been called
;                      2:  $a1PropertyItem is not a 4-element 1-d array
;                     10:  @extended may contain GPSTATUS error code ($GPID_ERR* see GDIPlusConstants.au3).
;                    101:  identifier is not an integer
;                    201:  size is not an integer
;                    202:  size cannot be number of values for ASCII string and undefined types
;                    202:  size is zero
;                    301:  type is illegal
;                    401:  value array is either not a 1-d array or has no elements
;                    402:  size is incompatible with type
;                    403:  size cannot be negative for ASCII string and undefined types
;                    404:  Numerator and denominator are required for rational types
;                    405:  size and number of elements in value array differ
; Author ........: Eukalyptus
; Modified ......: c.haslam, UEZ
; Remarks .......: If size parameter is negative, calculates size in bytes from type.
;                  Convenient when setting a few property items
;+
;                  types: unsigned byte = 1, ASCII string = 2, unsigned short = 3, unsigned long = 4,
;                         unsinged rational = 5, undefined = 7, signed long = 9, signed rational = 10
;+
;                   For list of EXIF property items, see https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html
;                  + and https://www.media.mit.edu/pia/Research/deepview/exif.html
; Related .......: _GDIPlus_ImageGetPropertyIdList, cGDIPlus_ImageGetPropertyItem
; Link ..........: https://msdn.microsoft.com/en-us/library/windows/desktop/ms535390(v=vs.85).aspx,
;                  https://msdn.microsoft.com/en-us/library/windows/desktop/ms534493(v=vs.85).aspx,
;                  https://msdn.microsoft.com/en-us/library/windows/desktop/ms534414(v=vs.85).aspx
; Example .......: Yes
; ===============================================================================================================================
Func cGDIPlus_ImageSetPropertyItem($hImage, $a1PropertyItem)

    ;--------- should be moved to GDIPlusConstants.au3
    Local Const $GDIP_PROPERTYTAGTYPEBYTE = 1, $GDIP_PROPERTYTAGTYPEASCII = 2, $GDIP_PROPERTYTAGTYPESHORT = 3, $GDIP_PROPERTYTAGTYPELONG = 4, _
            $GDIP_PROPERTYTAGTYPERATIONAL = 5, $GDIP_PROPERTYTAGTYPEUNDEFINED = 7, $GDIP_PROPERTYTAGTYPESLONG = 9, _
            $GDIP_PROPERTYTAGTYPESRATIONAL = 10
    ;-------------------------------------------------

    If (Not IsArray($a1PropertyItem)) Or UBound($a1PropertyItem, 0) <> 1 Or UBound($a1PropertyItem) <> 4 Then
        Return SetError(2, 0, False)
    EndIf
    Local $iId = $a1PropertyItem[0]
    Local $iLength = $a1PropertyItem[1]
    Local $iType = $a1PropertyItem[2]
    Local $a1values = $a1PropertyItem[3]

    If Not IsInt($iId) Then
        Return SetError(101, 0, False)
    EndIf
    If Not IsInt($iLength) Then
        Return SetError(201, 0, False)
    EndIf
    If $iLength = 0 Then
        Return SetError(202, 0, False)
    EndIf
    Switch $iType
        Case $GDIP_PROPERTYTAGTYPEBYTE, $GDIP_PROPERTYTAGTYPEASCII, $GDIP_PROPERTYTAGTYPESHORT, _
                $GDIP_PROPERTYTAGTYPELONG, $GDIP_PROPERTYTAGTYPERATIONAL, $GDIP_PROPERTYTAGTYPEUNDEFINED, _
                $GDIP_PROPERTYTAGTYPESLONG, $GDIP_PROPERTYTAGTYPESRATIONAL
            ; do nothing
        Case Else
            Return SetError(301, 0, False)
    EndSwitch
    If (Not IsArray($a1values)) Or UBound($a1values, 0) <> 1 Or UBound($a1values) = 0 Then
        Return SetError(401, 0, False)
    EndIf

    Local $iBytes, $iqValues
    If $iLength > 0 Then ; useful when copying all property items
        $iBytes = $iLength
        Switch $iType
            Case $GDIP_PROPERTYTAGTYPEASCII ;ASCII String
                $iqValues = 1
            Case $GDIP_PROPERTYTAGTYPESHORT ;Array of UShort
                If BitAND($iLength, 1) <> 0 Then
                    Return SetError(402, 0, False)
                EndIf
                $iqValues = Int($iLength / 2)
            Case $GDIP_PROPERTYTAGTYPELONG, $GDIP_PROPERTYTAGTYPERATIONAL, $GDIP_PROPERTYTAGTYPESLONG, _
                    $GDIP_PROPERTYTAGTYPESRATIONAL ;Array of UInt / Fraction
                If BitAND($iLength, 3) <> 0 Then
                    Return SetError(402, 0, False)
                EndIf
                $iqValues = Int($iLength / 4)
            Case Else ; Array of Bytes, undefined
                $iqValues = 1
        EndSwitch
    ElseIf $iLength < 0 Then ; convenient when setting a few property items
        Switch $iType
            Case $GDIP_PROPERTYTAGTYPEASCII, $GDIP_PROPERTYTAGTYPEUNDEFINED ;ASCII String, undefined
                Return SetError(403, 0, False)
            Case $GDIP_PROPERTYTAGTYPESHORT ;Array of UShort
                $iBytes = -$iLength * 2
            Case $GDIP_PROPERTYTAGTYPELONG, $GDIP_PROPERTYTAGTYPERATIONAL, $GDIP_PROPERTYTAGTYPESLONG, _
                    $GDIP_PROPERTYTAGTYPESRATIONAL ;Array of UInt / Fraction
                $iBytes = -$iLength * 4
            Case Else ;Array of Bytes
                $iBytes = -$iLength
        EndSwitch
        $iqValues = -$iLength
    EndIf

    If $iType = $GDIP_PROPERTYTAGTYPERATIONAL Or $iType = $GDIP_PROPERTYTAGTYPERATIONAL Then
        If BitAND($iqValues, 1) <> 0 Then
            Return SetError(404, 0, False)
        EndIf
    EndIf

    Switch $iType
        Case $GDIP_PROPERTYTAGTYPESHORT, $GDIP_PROPERTYTAGTYPELONG, $GDIP_PROPERTYTAGTYPERATIONAL, _
                $GDIP_PROPERTYTAGTYPESLONG, $GDIP_PROPERTYTAGTYPESRATIONAL
            If UBound($a1values) <> $iqValues Then
                Return SetError(405, 0, False)
            EndIf
    EndSwitch

    Local $tPropItem = DllStructCreate("int id; int length; short type; ptr pValue;")
    DllStructSetData($tPropItem, 'id', $iId)
    DllStructSetData($tPropItem, 'length', $iBytes)
    DllStructSetData($tPropItem, 'type', $iType)

    ; _ConsoleWrite($iId & @TAB & $iType & @CRLF)

    Local $tValues

    Switch $iType
        Case $GDIP_PROPERTYTAGTYPEASCII ;ASCII String
            $tValues = DllStructCreate("char[" & $iBytes & "];")
        Case $GDIP_PROPERTYTAGTYPESHORT ;Array of UShort
            $tValues = DllStructCreate("ushort[" & $iqValues & "];")
        Case $GDIP_PROPERTYTAGTYPELONG, $GDIP_PROPERTYTAGTYPERATIONAL ;Array of UInt / Fraction
            $tValues = DllStructCreate("uint[" & $iqValues & "];")
        Case $GDIP_PROPERTYTAGTYPESLONG, $GDIP_PROPERTYTAGTYPESRATIONAL ;Array of Int / Fraction
            $tValues = DllStructCreate("int[" & $iqValues & "];")
        Case Else ;Array of Bytes
            $tValues = DllStructCreate("byte[" & $iBytes & "];")
    EndSwitch
    If $iType = $GDIP_PROPERTYTAGTYPEASCII Or $iType = $GDIP_PROPERTYTAGTYPEUNDEFINED Then ; ASCII string or undefined
        DllStructSetData($tValues, 1, $a1values[0])
    Else
        For $i = 0 To $iqValues - 1
            DllStructSetData($tValues, 1, $a1values[$i], $i + 1)
        Next
    EndIf
    DllStructSetData($tPropItem, 'pValue', DllStructGetPtr($tValues))

    Local $aResult = DllCall($__g_hGDIPDll, "uint", "GdipSetPropertyItem", "handle", $hImage, "struct*", $tPropItem)
    If @error Then Return SetError(@error, @extended, -1)
    If $aResult[0] Then Return SetError(10, $aResult[0], -1)
    Return True
EndFunc   ;==>cGDIPlus_ImageSetPropertyItem

; #FUNCTION# ====================================================================================================================
; Name ..........: cGDIPlus_ImageGetAllPropertyItems
; Description ...: Gets all property item (piece of meta data) from an Image object
; Syntax ........: cGDIPlus_ImageGetAllPropertyItems($hImage)
; Parameters ....: $hImage              - Pointer to an image object
; Return values .: Success: Array in which each row contains these columns:
;                      [0] - identifier
;                      [1] - size, in bytes, of the value array
;                      [2] - type of value(s) in the value array
;                      [3] - value array
;                  Failure: sets the @error flag to:
;                      1:  DllCall failed.  Common cause: _GIDPlus_Startup() has not been called
;                     10:  @extended may contain GPSTATUS error code ($GPID_ERR* see GDIPlusConstants.au3).
;                          @extended=2; no property items found
; Author ........: c.haslam
; Modified ......:
; Remarks .......: types: unsigned byte = 1, ASCII string = 2, unsigned short = 3, unsigned long = 4,
;                         unsinged rational = 5, signed byte = 6, undefined = 7, signed long = 9,
;                         signed rational = 10
;+
;                   For list of EXIF property items, see https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html
;                  + and https://www.media.mit.edu/pia/Research/deepview/exif.html
; Related .......: cGDIPlus_ImageGetPropertyItem, _GDIPlus_ImageGetPropertyIdList, cGDIPlus_ImageSetPropertyItem
; Link ..........: https://msdn.microsoft.com/en-us/library/windows/desktop/ms535390(v=vs.85).aspx,
;                  https://msdn.microsoft.com/en-us/library/windows/desktop/ms534493(v=vs.85).aspx,
;                  https://msdn.microsoft.com/en-us/library/windows/desktop/ms534414(v=vs.85).aspx,
;                  https://msdn.microsoft.com/en-us/library/ms534416.aspx#_gdiplus_constant_propertytaggpsver
; Example .......: Yes
; ===============================================================================================================================

Func cGDIPlus_ImageGetAllPropertyItems($hImage)
    Local $aResult = DllCall($__g_hGDIPDll, "uint", "GdipGetPropertySize", "handle", $hImage, "uint*", 0, "uint*", 0)
    If @error Then Return SetError(@error, @extended, -1)
    If $aResult[0] Then Return SetError(10, $aResult[0], -1)
    Local $iTotalBufferSize = $aResult[2]
    Local $iNumProperties = $aResult[3]

    Local $tBuffer = DllStructCreate("byte[" & $iTotalBufferSize & "]")
    Local $pBuffer = DllStructGetPtr($tBuffer)

    $aResult = DllCall($__g_hGDIPDll, "uint", "GdipGetAllPropertyItems", "handle", $hImage, _
            "uint", $iTotalBufferSize, "uint", $iNumProperties, "ptr", $pBuffer)
    If @error Then Return SetError(@error, @extended, -1)
    If $aResult[0] Then Return SetError(10, $aResult[0], -1)

    Local $aPropertyItems[$iNumProperties][4]
    Local $a1
    Local $tPropertyItem = DllStructCreate("int id; int length; short type; ptr value;")

    For $iI = 0 To $iNumProperties - 1
        $a1 = __GDIPlus_ImageGetPropertyItemValues($pBuffer)
        For $j = 0 To 3
            $aPropertyItems[$iI][$j] = $a1[$j]
        Next
        $pBuffer += DllStructGetSize($tPropertyItem)
    Next

    Return $aPropertyItems
EndFunc   ;==>cGDIPlus_ImageGetAllPropertyItems

; #INTERNAL_USE_ONLY# ===========================================================================================================
; Name ..........: __GDIPlus_ImageGetPropertyItemValues($pBuffer)
; Description ...: Converts a project item from structured to equivalent nested arrays
; Syntax ........: __GDIPlus_ImageGetPropertyItemValues($pBuffer)
; Parameters ....: $bBuffer             - Pointer to start of Property Item sructure
; Return values .:     [0] - identifier
;                      [1] - size, in bytes, of the value array
;                      [2] - type of value(s) in the value array
;                      [3] - value array
; Author ........: c.haslam
; Modified ......: UEZ
; Remarks .......: types: unsigned byte = 1, ASCII string = 2, unsigned short = 3, unsigned long = 4,
;                         unsigned rational = 5, signed byte = 6, undefined = 7, signed long = 9,
;                         signed rational = 10
; Related .......:
; Link ..........:
; Example .......: No
; ===============================================================================================================================

Func __GDIPlus_ImageGetPropertyItemValues($pBuffer)
    Local $tPropertyItem = DllStructCreate("int id; int length; short type; ptr value;", $pBuffer)
    Local $iBytes = DllStructGetData($tPropertyItem, "length")
    Local $pValue = DllStructGetData($tPropertyItem, "value")

    ;--------- should be moved to GDIPlusConstants.au3
    Local Const $GDIP_PROPERTYTAGTYPEBYTE = 1, $GDIP_PROPERTYTAGTYPEASCII = 2, $GDIP_PROPERTYTAGTYPESHORT = 3, $GDIP_PROPERTYTAGTYPELONG = 4, _
            $GDIP_PROPERTYTAGTYPERATIONAL = 5, $GDIP_PROPERTYTAGTYPEUNDEFINED = 7, $GDIP_PROPERTYTAGTYPESLONG = 9, _
            $GDIP_PROPERTYTAGTYPESRATIONAL = 10
    ;-------------------------------------------------

    Local $aRet[4]
    Local $type = DllStructGetData($tPropertyItem, 'type')
    Local $tValues, $iValues

    Switch $type
        Case $GDIP_PROPERTYTAGTYPEASCII ;ASCII String
            $iValues = 1
            $tValues = DllStructCreate("char[" & $iBytes & "];", $pValue)
        Case $GDIP_PROPERTYTAGTYPESHORT ;Array of UShort
            $iValues = Int($iBytes / 2)
            $tValues = DllStructCreate("ushort[" & $iValues & "];", $pValue)
        Case $GDIP_PROPERTYTAGTYPELONG, $GDIP_PROPERTYTAGTYPERATIONAL ;Array of UInt / Fraction
            $iValues = Int($iBytes / 4)
            $tValues = DllStructCreate("uint[" & $iValues & "];", $pValue)
        Case $GDIP_PROPERTYTAGTYPESLONG, $GDIP_PROPERTYTAGTYPESRATIONAL ;Array of Int / Fraction
            $iValues = Int($iBytes / 4)
            $tValues = DllStructCreate("int[" & $iValues & "];", $pValue)
        Case Else ;Array of Bytes
            $iValues = 1
            $tValues = DllStructCreate("byte[" & $iBytes & "];", $pValue)
    EndSwitch
    $aRet[0] = DllStructGetData($tPropertyItem, 'id')
    $aRet[1] = $iBytes
    $aRet[2] = $type
    Local $a1values[$iValues]
    If $type = $GDIP_PROPERTYTAGTYPEASCII Or $type = $GDIP_PROPERTYTAGTYPEUNDEFINED Then ; ASCII string or undefined
        $a1values[0] = DllStructGetData($tValues, 1)
    Else
        For $i = 0 To $iValues - 1
            $a1values[$i] = DllStructGetData($tValues, 1, $i + 1)
        Next
    EndIf
    $aRet[3] = $a1values
    Return $aRet
EndFunc   ;==>__GDIPlus_ImageGetPropertyItemValues

 

Edited by KaFu
Link to comment
Share on other sites

7 hours ago, KaFu said:

I use something like this in my program BIC (as a fallback for the prerfered ExifTool method).

Although I too prefer ExifTool over this, as GDI JPG processing is not lossless.

I process the files, save them as PNG, use an external tool called jpge.exe to encode a jpg from the png and then set the Exif infos with ExifTool.

; jpg with GDI always bad
; https://www.autoitscript.com/forum/topic/187573-perplexed-by-jpg-conversion-result/
; "The problem (after reading your edits) is probably due to the fact that GDI+ uses 4:1:1 subsampling for JPG files in it's default Encoder."
;  The issue is that GDI+ enables chroma subsampling which modifies the color values accordingly.

#include <GDIPlus.au3>
#include <WinAPIHObj.au3>

; Initialize GDI+ library
_GDIPlus_Startup()

Local $sfile_input = FileOpenDialog("Please select file", "", "JPEG files (*.jpg;*.jpeg)") ;
If @error Then Exit

Local $sfile_output = StringReplace($sfile_input, ".", "_Out.")

_Clone_MetaData($sfile_input, $sfile_output)

If FileExists($sfile_output) Then ShellExecute($sfile_output)

; Shut down GDI+ library
_GDIPlus_Shutdown()


Func _Clone_MetaData($sfile_input, $sfile_output)

    If Not FileExists($sfile_input) Then Return 0

    Local $a_DateTimeOriginal = FileGetTime($sfile_input)
    Local $s_DateTimeOriginal = $a_DateTimeOriginal[0] & ":" & $a_DateTimeOriginal[1] & ":" & $a_DateTimeOriginal[2] & " " & $a_DateTimeOriginal[3] & ":" & $a_DateTimeOriginal[4] & ":" & $a_DateTimeOriginal[5]

    Local $hImage = _GDIPlus_ImageLoadFromFile($sfile_input)

    Local $aDim = _GDIPlus_ImageGetDimension($hImage)
    Local $a_Local_Input_EXIF_Dimensions[2]
    $a_Local_Input_EXIF_Dimensions[0] = $aDim[0]
    $a_Local_Input_EXIF_Dimensions[1] = $aDim[1]

    Local $a_Local_Input_EXIF_Properties = cGDIPlus_ImageGetAllPropertyItems($hImage)

    Local $hClone = _GDIPlus_BitmapCloneArea($hImage, 0, 0, $aDim[0], $aDim[1], $GDIP_PXF32ARGB)

    If IsArray($a_Local_Input_EXIF_Properties) Then _GDIPlus_ImageSetPropertyItem_Array($hClone, $a_Local_Input_EXIF_Properties)

    Local $sString = "Code ripped from BIC, https://funk.eu"
    Local $iRes = _GDIPlus_ImageSetPropertyItem_Ex($hClone, 0x0131, $sString, 2, StringLen($sString) + 1)         ; Software > 2=ASCII

    If IsArray($a_Local_Input_EXIF_Dimensions) Then
        $iRes = _GDIPlus_ImageSetPropertyItem_Ex($hClone, 0x0100, $a_Local_Input_EXIF_Dimensions[0], 4, -1)       ; ImageWidth > 4=UINT
    EndIf

    If IsArray($a_Local_Input_EXIF_Dimensions) Then
        $iRes = _GDIPlus_ImageSetPropertyItem_Ex($hClone, 0x0101, $a_Local_Input_EXIF_Dimensions[1], 4, -1)       ; ImageHeight > 4=UINT
    EndIf

    $sString = @YEAR & ":" & @MON & ":" & @MDAY & " " & @HOUR & ":" & @MIN & ":" & @SEC

    $iRes = _GDIPlus_ImageSetPropertyItem_Ex($hClone, 0x0132, $sString, 2, StringLen($sString) + 1)         ; 0x0132 ModifyDate > 2=ASCII

    $sString = "Custom User Comment"
    $iRes = _GDIPlus_ImageSetPropertyItem_Ex($hClone, 0x9286, $sString, 2, StringLen($sString) + 1)         ; 0x9286 UserComment > 2=ASCII

    If $s_DateTimeOriginal Then $iRes = _GDIPlus_ImageSetPropertyItem_Ex($hClone, 0x9003, $s_DateTimeOriginal, 2, StringLen($s_DateTimeOriginal) + 1)         ; 0x9003 DateTimeOriginal > 2=ASCII

    _GDIPlus_ImageSaveToFile($hClone, $sfile_output)

    ; Clean up resources
    _GDIPlus_ImageDispose($hImage)
    _GDIPlus_BitmapDispose($hClone)

EndFunc   ;==>_Clone_MetaData





Func _GDIPlus_ImageSetPropertyItem_Array($hClone, $a_Local_Input_EXIF_Properties)
    For $i = 0 To UBound($a_Local_Input_EXIF_Properties) - 1
        Local $aInput = [$a_Local_Input_EXIF_Properties[$i][0], $a_Local_Input_EXIF_Properties[$i][1], $a_Local_Input_EXIF_Properties[$i][2], $a_Local_Input_EXIF_Properties[$i][3]]
        cGDIPlus_ImageSetPropertyItem($hClone, $aInput)
    Next
EndFunc   ;==>_GDIPlus_ImageSetPropertyItem_Array

Func _GDIPlus_ImageSetPropertyItem_Ex($hImage, $i_PropertyItem_Number, $s_PropertyItem_Value, $iType, $iLength)
    ; https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html
    #cs
        0x0131 > Software
        0x010e > ImageDescription
        0x9286 > UserComment
        0x0132 > ModifyDate
        0x9003 > DateTimeOriginal   string  ExifIFD     (date/time when original image was taken)
    #ce
    Local $a_PropertyValue = [$s_PropertyItem_Value]
    Local $a_PropertyItem = [$i_PropertyItem_Number, $iLength, $iType, $a_PropertyValue]
    Local $iRes = cGDIPlus_ImageSetPropertyItem($hImage, $a_PropertyItem)
    Return SetError(@error, @extended, $iRes)
EndFunc   ;==>_GDIPlus_ImageSetPropertyItem_Ex

; #FUNCTION# ====================================================================================================================
; Name ..........: cGDIPlus_ImageSetPropertyItem
; Description ...: Sets a specified property item (piece of meta data) for an Image object
; Syntax ........: cGDIPlus_ImageSetPropertyItem($hImage, $a1PropertyItem)
; Parameters ....: $hImage              - Pointer to an image object
;                  $a1PropertyItem      - Array containing the values of the property item
;                      [0] - identifier
;                      [1] - size of the value array
;                               positive: in bytes
;                               negative: for numeric types: in number of values
;                      [2] - type of value(s) in the value array
;                      [3] - value array
; Return values .: Success: True and @error = 0
;                  Failure: sets the @error flag to:
;                      1:  DllCall failed.  Common cause: _GIDPlus_Startup() has not been called
;                      2:  $a1PropertyItem is not a 4-element 1-d array
;                     10:  @extended may contain GPSTATUS error code ($GPID_ERR* see GDIPlusConstants.au3).
;                    101:  identifier is not an integer
;                    201:  size is not an integer
;                    202:  size cannot be number of values for ASCII string and undefined types
;                    202:  size is zero
;                    301:  type is illegal
;                    401:  value array is either not a 1-d array or has no elements
;                    402:  size is incompatible with type
;                    403:  size cannot be negative for ASCII string and undefined types
;                    404:  Numerator and denominator are required for rational types
;                    405:  size and number of elements in value array differ
; Author ........: Eukalyptus
; Modified ......: c.haslam, UEZ
; Remarks .......: If size parameter is negative, calculates size in bytes from type.
;                  Convenient when setting a few property items
;+
;                  types: unsigned byte = 1, ASCII string = 2, unsigned short = 3, unsigned long = 4,
;                         unsinged rational = 5, undefined = 7, signed long = 9, signed rational = 10
;+
;                   For list of EXIF property items, see https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html
;                  + and https://www.media.mit.edu/pia/Research/deepview/exif.html
; Related .......: _GDIPlus_ImageGetPropertyIdList, cGDIPlus_ImageGetPropertyItem
; Link ..........: https://msdn.microsoft.com/en-us/library/windows/desktop/ms535390(v=vs.85).aspx,
;                  https://msdn.microsoft.com/en-us/library/windows/desktop/ms534493(v=vs.85).aspx,
;                  https://msdn.microsoft.com/en-us/library/windows/desktop/ms534414(v=vs.85).aspx
; Example .......: Yes
; ===============================================================================================================================
Func cGDIPlus_ImageSetPropertyItem($hImage, $a1PropertyItem)

    ;--------- should be moved to GDIPlusConstants.au3
    Local Const $GDIP_PROPERTYTAGTYPEBYTE = 1, $GDIP_PROPERTYTAGTYPEASCII = 2, $GDIP_PROPERTYTAGTYPESHORT = 3, $GDIP_PROPERTYTAGTYPELONG = 4, _
            $GDIP_PROPERTYTAGTYPERATIONAL = 5, $GDIP_PROPERTYTAGTYPEUNDEFINED = 7, $GDIP_PROPERTYTAGTYPESLONG = 9, _
            $GDIP_PROPERTYTAGTYPESRATIONAL = 10
    ;-------------------------------------------------

    If (Not IsArray($a1PropertyItem)) Or UBound($a1PropertyItem, 0) <> 1 Or UBound($a1PropertyItem) <> 4 Then
        Return SetError(2, 0, False)
    EndIf
    Local $iId = $a1PropertyItem[0]
    Local $iLength = $a1PropertyItem[1]
    Local $iType = $a1PropertyItem[2]
    Local $a1values = $a1PropertyItem[3]

    If Not IsInt($iId) Then
        Return SetError(101, 0, False)
    EndIf
    If Not IsInt($iLength) Then
        Return SetError(201, 0, False)
    EndIf
    If $iLength = 0 Then
        Return SetError(202, 0, False)
    EndIf
    Switch $iType
        Case $GDIP_PROPERTYTAGTYPEBYTE, $GDIP_PROPERTYTAGTYPEASCII, $GDIP_PROPERTYTAGTYPESHORT, _
                $GDIP_PROPERTYTAGTYPELONG, $GDIP_PROPERTYTAGTYPERATIONAL, $GDIP_PROPERTYTAGTYPEUNDEFINED, _
                $GDIP_PROPERTYTAGTYPESLONG, $GDIP_PROPERTYTAGTYPESRATIONAL
            ; do nothing
        Case Else
            Return SetError(301, 0, False)
    EndSwitch
    If (Not IsArray($a1values)) Or UBound($a1values, 0) <> 1 Or UBound($a1values) = 0 Then
        Return SetError(401, 0, False)
    EndIf

    Local $iBytes, $iqValues
    If $iLength > 0 Then ; useful when copying all property items
        $iBytes = $iLength
        Switch $iType
            Case $GDIP_PROPERTYTAGTYPEASCII ;ASCII String
                $iqValues = 1
            Case $GDIP_PROPERTYTAGTYPESHORT ;Array of UShort
                If BitAND($iLength, 1) <> 0 Then
                    Return SetError(402, 0, False)
                EndIf
                $iqValues = Int($iLength / 2)
            Case $GDIP_PROPERTYTAGTYPELONG, $GDIP_PROPERTYTAGTYPERATIONAL, $GDIP_PROPERTYTAGTYPESLONG, _
                    $GDIP_PROPERTYTAGTYPESRATIONAL ;Array of UInt / Fraction
                If BitAND($iLength, 3) <> 0 Then
                    Return SetError(402, 0, False)
                EndIf
                $iqValues = Int($iLength / 4)
            Case Else ; Array of Bytes, undefined
                $iqValues = 1
        EndSwitch
    ElseIf $iLength < 0 Then ; convenient when setting a few property items
        Switch $iType
            Case $GDIP_PROPERTYTAGTYPEASCII, $GDIP_PROPERTYTAGTYPEUNDEFINED ;ASCII String, undefined
                Return SetError(403, 0, False)
            Case $GDIP_PROPERTYTAGTYPESHORT ;Array of UShort
                $iBytes = -$iLength * 2
            Case $GDIP_PROPERTYTAGTYPELONG, $GDIP_PROPERTYTAGTYPERATIONAL, $GDIP_PROPERTYTAGTYPESLONG, _
                    $GDIP_PROPERTYTAGTYPESRATIONAL ;Array of UInt / Fraction
                $iBytes = -$iLength * 4
            Case Else ;Array of Bytes
                $iBytes = -$iLength
        EndSwitch
        $iqValues = -$iLength
    EndIf

    If $iType = $GDIP_PROPERTYTAGTYPERATIONAL Or $iType = $GDIP_PROPERTYTAGTYPERATIONAL Then
        If BitAND($iqValues, 1) <> 0 Then
            Return SetError(404, 0, False)
        EndIf
    EndIf

    Switch $iType
        Case $GDIP_PROPERTYTAGTYPESHORT, $GDIP_PROPERTYTAGTYPELONG, $GDIP_PROPERTYTAGTYPERATIONAL, _
                $GDIP_PROPERTYTAGTYPESLONG, $GDIP_PROPERTYTAGTYPESRATIONAL
            If UBound($a1values) <> $iqValues Then
                Return SetError(405, 0, False)
            EndIf
    EndSwitch

    Local $tPropItem = DllStructCreate("int id; int length; short type; ptr pValue;")
    DllStructSetData($tPropItem, 'id', $iId)
    DllStructSetData($tPropItem, 'length', $iBytes)
    DllStructSetData($tPropItem, 'type', $iType)

    ; _ConsoleWrite($iId & @TAB & $iType & @CRLF)

    Local $tValues

    Switch $iType
        Case $GDIP_PROPERTYTAGTYPEASCII ;ASCII String
            $tValues = DllStructCreate("char[" & $iBytes & "];")
        Case $GDIP_PROPERTYTAGTYPESHORT ;Array of UShort
            $tValues = DllStructCreate("ushort[" & $iqValues & "];")
        Case $GDIP_PROPERTYTAGTYPELONG, $GDIP_PROPERTYTAGTYPERATIONAL ;Array of UInt / Fraction
            $tValues = DllStructCreate("uint[" & $iqValues & "];")
        Case $GDIP_PROPERTYTAGTYPESLONG, $GDIP_PROPERTYTAGTYPESRATIONAL ;Array of Int / Fraction
            $tValues = DllStructCreate("int[" & $iqValues & "];")
        Case Else ;Array of Bytes
            $tValues = DllStructCreate("byte[" & $iBytes & "];")
    EndSwitch
    If $iType = $GDIP_PROPERTYTAGTYPEASCII Or $iType = $GDIP_PROPERTYTAGTYPEUNDEFINED Then ; ASCII string or undefined
        DllStructSetData($tValues, 1, $a1values[0])
    Else
        For $i = 0 To $iqValues - 1
            DllStructSetData($tValues, 1, $a1values[$i], $i + 1)
        Next
    EndIf
    DllStructSetData($tPropItem, 'pValue', DllStructGetPtr($tValues))

    Local $aResult = DllCall($__g_hGDIPDll, "uint", "GdipSetPropertyItem", "handle", $hImage, "struct*", $tPropItem)
    If @error Then Return SetError(@error, @extended, -1)
    If $aResult[0] Then Return SetError(10, $aResult[0], -1)
    Return True
EndFunc   ;==>cGDIPlus_ImageSetPropertyItem

; #FUNCTION# ====================================================================================================================
; Name ..........: cGDIPlus_ImageGetAllPropertyItems
; Description ...: Gets all property item (piece of meta data) from an Image object
; Syntax ........: cGDIPlus_ImageGetAllPropertyItems($hImage)
; Parameters ....: $hImage              - Pointer to an image object
; Return values .: Success: Array in which each row contains these columns:
;                      [0] - identifier
;                      [1] - size, in bytes, of the value array
;                      [2] - type of value(s) in the value array
;                      [3] - value array
;                  Failure: sets the @error flag to:
;                      1:  DllCall failed.  Common cause: _GIDPlus_Startup() has not been called
;                     10:  @extended may contain GPSTATUS error code ($GPID_ERR* see GDIPlusConstants.au3).
;                          @extended=2; no property items found
; Author ........: c.haslam
; Modified ......:
; Remarks .......: types: unsigned byte = 1, ASCII string = 2, unsigned short = 3, unsigned long = 4,
;                         unsinged rational = 5, signed byte = 6, undefined = 7, signed long = 9,
;                         signed rational = 10
;+
;                   For list of EXIF property items, see https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html
;                  + and https://www.media.mit.edu/pia/Research/deepview/exif.html
; Related .......: cGDIPlus_ImageGetPropertyItem, _GDIPlus_ImageGetPropertyIdList, cGDIPlus_ImageSetPropertyItem
; Link ..........: https://msdn.microsoft.com/en-us/library/windows/desktop/ms535390(v=vs.85).aspx,
;                  https://msdn.microsoft.com/en-us/library/windows/desktop/ms534493(v=vs.85).aspx,
;                  https://msdn.microsoft.com/en-us/library/windows/desktop/ms534414(v=vs.85).aspx,
;                  https://msdn.microsoft.com/en-us/library/ms534416.aspx#_gdiplus_constant_propertytaggpsver
; Example .......: Yes
; ===============================================================================================================================

Func cGDIPlus_ImageGetAllPropertyItems($hImage)
    Local $aResult = DllCall($__g_hGDIPDll, "uint", "GdipGetPropertySize", "handle", $hImage, "uint*", 0, "uint*", 0)
    If @error Then Return SetError(@error, @extended, -1)
    If $aResult[0] Then Return SetError(10, $aResult[0], -1)
    Local $iTotalBufferSize = $aResult[2]
    Local $iNumProperties = $aResult[3]

    Local $tBuffer = DllStructCreate("byte[" & $iTotalBufferSize & "]")
    Local $pBuffer = DllStructGetPtr($tBuffer)

    $aResult = DllCall($__g_hGDIPDll, "uint", "GdipGetAllPropertyItems", "handle", $hImage, _
            "uint", $iTotalBufferSize, "uint", $iNumProperties, "ptr", $pBuffer)
    If @error Then Return SetError(@error, @extended, -1)
    If $aResult[0] Then Return SetError(10, $aResult[0], -1)

    Local $aPropertyItems[$iNumProperties][4]
    Local $a1
    Local $tPropertyItem = DllStructCreate("int id; int length; short type; ptr value;")

    For $iI = 0 To $iNumProperties - 1
        $a1 = __GDIPlus_ImageGetPropertyItemValues($pBuffer)
        For $j = 0 To 3
            $aPropertyItems[$iI][$j] = $a1[$j]
        Next
        $pBuffer += DllStructGetSize($tPropertyItem)
    Next

    Return $aPropertyItems
EndFunc   ;==>cGDIPlus_ImageGetAllPropertyItems

; #INTERNAL_USE_ONLY# ===========================================================================================================
; Name ..........: __GDIPlus_ImageGetPropertyItemValues($pBuffer)
; Description ...: Converts a project item from structured to equivalent nested arrays
; Syntax ........: __GDIPlus_ImageGetPropertyItemValues($pBuffer)
; Parameters ....: $bBuffer             - Pointer to start of Property Item sructure
; Return values .:     [0] - identifier
;                      [1] - size, in bytes, of the value array
;                      [2] - type of value(s) in the value array
;                      [3] - value array
; Author ........: c.haslam
; Modified ......: UEZ
; Remarks .......: types: unsigned byte = 1, ASCII string = 2, unsigned short = 3, unsigned long = 4,
;                         unsigned rational = 5, signed byte = 6, undefined = 7, signed long = 9,
;                         signed rational = 10
; Related .......:
; Link ..........:
; Example .......: No
; ===============================================================================================================================

Func __GDIPlus_ImageGetPropertyItemValues($pBuffer)
    Local $tPropertyItem = DllStructCreate("int id; int length; short type; ptr value;", $pBuffer)
    Local $iBytes = DllStructGetData($tPropertyItem, "length")
    Local $pValue = DllStructGetData($tPropertyItem, "value")

    ;--------- should be moved to GDIPlusConstants.au3
    Local Const $GDIP_PROPERTYTAGTYPEBYTE = 1, $GDIP_PROPERTYTAGTYPEASCII = 2, $GDIP_PROPERTYTAGTYPESHORT = 3, $GDIP_PROPERTYTAGTYPELONG = 4, _
            $GDIP_PROPERTYTAGTYPERATIONAL = 5, $GDIP_PROPERTYTAGTYPEUNDEFINED = 7, $GDIP_PROPERTYTAGTYPESLONG = 9, _
            $GDIP_PROPERTYTAGTYPESRATIONAL = 10
    ;-------------------------------------------------

    Local $aRet[4]
    Local $type = DllStructGetData($tPropertyItem, 'type')
    Local $tValues, $iValues

    Switch $type
        Case $GDIP_PROPERTYTAGTYPEASCII ;ASCII String
            $iValues = 1
            $tValues = DllStructCreate("char[" & $iBytes & "];", $pValue)
        Case $GDIP_PROPERTYTAGTYPESHORT ;Array of UShort
            $iValues = Int($iBytes / 2)
            $tValues = DllStructCreate("ushort[" & $iValues & "];", $pValue)
        Case $GDIP_PROPERTYTAGTYPELONG, $GDIP_PROPERTYTAGTYPERATIONAL ;Array of UInt / Fraction
            $iValues = Int($iBytes / 4)
            $tValues = DllStructCreate("uint[" & $iValues & "];", $pValue)
        Case $GDIP_PROPERTYTAGTYPESLONG, $GDIP_PROPERTYTAGTYPESRATIONAL ;Array of Int / Fraction
            $iValues = Int($iBytes / 4)
            $tValues = DllStructCreate("int[" & $iValues & "];", $pValue)
        Case Else ;Array of Bytes
            $iValues = 1
            $tValues = DllStructCreate("byte[" & $iBytes & "];", $pValue)
    EndSwitch
    $aRet[0] = DllStructGetData($tPropertyItem, 'id')
    $aRet[1] = $iBytes
    $aRet[2] = $type
    Local $a1values[$iValues]
    If $type = $GDIP_PROPERTYTAGTYPEASCII Or $type = $GDIP_PROPERTYTAGTYPEUNDEFINED Then ; ASCII string or undefined
        $a1values[0] = DllStructGetData($tValues, 1)
    Else
        For $i = 0 To $iValues - 1
            $a1values[$i] = DllStructGetData($tValues, 1, $i + 1)
        Next
    EndIf
    $aRet[3] = $a1values
    Return $aRet
EndFunc   ;==>__GDIPlus_ImageGetPropertyItemValues

 

Very nice

can be resized with keep quality and metadat?

Edited by Parsix
Link to comment
Share on other sites

As the picture is re-encoded, JPG is not lossless and you can not determine the initial quality setting of a JPG, the quality will always be reduced.

#include <GDIPlus.au3>
#include <WinAPIHObj.au3>
#include <Array.au3>

; Initialize GDI+ library
_GDIPlus_Startup()

Local $sfile_input = FileOpenDialog("Please select file", "", "JPEG files (*.jpg;*.jpeg)")
If @error Then Exit

Local $sfile_output = StringReplace($sfile_input, ".", "_Out.")

_Clone_MetaData($sfile_input, $sfile_output, Default, 500)

If FileExists($sfile_output) Then ShellExecute($sfile_output)

; Shut down GDI+ library
_GDIPlus_Shutdown()


Func _Clone_MetaData($sfile_input, $sfile_output, $iWidth = Default, $iHeight = Default, $i_JPG_Quality = 95)

    If Not FileExists($sfile_input) Then Return 0

    Local $a_DateTimeOriginal = FileGetTime($sfile_input)
    Local $s_DateTimeOriginal = $a_DateTimeOriginal[0] & ":" & $a_DateTimeOriginal[1] & ":" & $a_DateTimeOriginal[2] & " " & $a_DateTimeOriginal[3] & ":" & $a_DateTimeOriginal[4] & ":" & $a_DateTimeOriginal[5]

    Local $hImage = _GDIPlus_ImageLoadFromFile($sfile_input)

    Local $aDim = _GDIPlus_ImageGetDimension($hImage)

    Local $a_Local_Input_EXIF_Properties = cGDIPlus_ImageGetAllPropertyItems($hImage)
    Local $i_Local_Input_EXIF_Properties_Orientation = 1
    For $i = 0 To UBound($a_Local_Input_EXIF_Properties) - 1
        If $a_Local_Input_EXIF_Properties[$i][0] = 0x0112 Then ; Orientation
            $i_Local_Input_EXIF_Properties_Orientation = $a_Local_Input_EXIF_Properties[$i][3]
            $i_Local_Input_EXIF_Properties_Orientation = $i_Local_Input_EXIF_Properties_Orientation[0]
            ExitLoop
        EndIf
    Next
    Switch $i_Local_Input_EXIF_Properties_Orientation
        Case 5 To 9
            ConsoleWrite("! Exif orientation applied" & @CRLF)
            Local $iBuffer = $aDim[0]
            $aDim[0] = $aDim[1]
            $aDim[1] = $iBuffer
    EndSwitch

    Local $iRatio = $aDim[0] / $aDim[1]

    If $iWidth <> Default Or $iHeight <> Default Then
        If $iWidth = Default Then
            $iWidth = $iHeight / $iRatio
        EndIf

        If $iHeight = Default Then
            $iHeight = $iWidth * $iRatio
        EndIf

        Local $hBitmap_Scaled = _GDIPlus_ImageResize($hImage, $iWidth, $iHeight)

        ConsoleWrite($aDim[0] & @TAB & $aDim[1] & @TAB & $aDim[0] / $aDim[1] & @TAB & $iWidth & @TAB & $iHeight & @TAB & $hBitmap_Scaled & @CRLF)

        _GDIPlus_ImageDispose($hImage)
        $hImage = $hBitmap_Scaled

    EndIf

    If IsArray($a_Local_Input_EXIF_Properties) Then _GDIPlus_ImageSetPropertyItem_Array($hImage, $a_Local_Input_EXIF_Properties)

    Local $sString = "Code ripped from BIC, https://funk.eu"
    Local $iRes = _GDIPlus_ImageSetPropertyItem_Ex($hImage, 0x0131, $sString, 2, StringLen($sString) + 1)         ; Software > 2=ASCII

    $iRes = _GDIPlus_ImageSetPropertyItem_Ex($hImage, 0x0100, $iWidth, 4, -1)       ; ImageWidth > 4=UINT

    $iRes = _GDIPlus_ImageSetPropertyItem_Ex($hImage, 0x0101, $iHeight, 4, -1)       ; ImageHeight > 4=UINT

    $sString = @YEAR & ":" & @MON & ":" & @MDAY & " " & @HOUR & ":" & @MIN & ":" & @SEC

    $iRes = _GDIPlus_ImageSetPropertyItem_Ex($hImage, 0x0132, $sString, 2, StringLen($sString) + 1)         ; 0x0132 ModifyDate > 2=ASCII

    $sString = "Original Image Dimensions = " & $aDim[0] & "x" & $aDim[1]
    $iRes = _GDIPlus_ImageSetPropertyItem_Ex($hImage, 0x9286, $sString, 2, StringLen($sString) + 1)         ; 0x9286 UserComment > 2=ASCII

    If $s_DateTimeOriginal Then $iRes = _GDIPlus_ImageSetPropertyItem_Ex($hImage, 0x9003, $s_DateTimeOriginal, 2, StringLen($s_DateTimeOriginal) + 1)         ; 0x9003 DateTimeOriginal > 2=ASCII

    Local $sCLSID = _GDIPlus_EncodersGetCLSID("JPG")
    Local $tParams = _GDIPlus_ParamInit(1)
    Local $tData = DllStructCreate("int Quality")
    DllStructSetData($tData, "Quality", $i_JPG_Quality)
    Local $pData = DllStructGetPtr($tData)
    _GDIPlus_ParamAdd($tParams, $GDIP_EPGQUALITY, 1, $GDIP_EPTLONG, $pData)
    Local $pParams = DllStructGetPtr($tParams)
    _GDIPlus_ImageSaveToFileEx($hImage, $sfile_output, $sCLSID, $pParams)

    ; Clean up resources
    _GDIPlus_ImageDispose($hImage)

EndFunc   ;==>_Clone_MetaData





Func _GDIPlus_ImageSetPropertyItem_Array($hClone, $a_Local_Input_EXIF_Properties)
    For $i = 0 To UBound($a_Local_Input_EXIF_Properties) - 1
        Local $aInput = [$a_Local_Input_EXIF_Properties[$i][0], $a_Local_Input_EXIF_Properties[$i][1], $a_Local_Input_EXIF_Properties[$i][2], $a_Local_Input_EXIF_Properties[$i][3]]
        cGDIPlus_ImageSetPropertyItem($hClone, $aInput)
    Next
EndFunc   ;==>_GDIPlus_ImageSetPropertyItem_Array

Func _GDIPlus_ImageSetPropertyItem_Ex($hImage, $i_PropertyItem_Number, $s_PropertyItem_Value, $iType, $iLength)
    ; https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html
    #cs
        0x0131 > Software
        0x010e > ImageDescription
        0x9286 > UserComment
        0x0132 > ModifyDate
        0x9003 > DateTimeOriginal   string  ExifIFD     (date/time when original image was taken)
    #ce
    Local $a_PropertyValue = [$s_PropertyItem_Value]
    Local $a_PropertyItem = [$i_PropertyItem_Number, $iLength, $iType, $a_PropertyValue]
    Local $iRes = cGDIPlus_ImageSetPropertyItem($hImage, $a_PropertyItem)
    Return SetError(@error, @extended, $iRes)
EndFunc   ;==>_GDIPlus_ImageSetPropertyItem_Ex

; #FUNCTION# ====================================================================================================================
; Name ..........: cGDIPlus_ImageSetPropertyItem
; Description ...: Sets a specified property item (piece of meta data) for an Image object
; Syntax ........: cGDIPlus_ImageSetPropertyItem($hImage, $a1PropertyItem)
; Parameters ....: $hImage              - Pointer to an image object
;                  $a1PropertyItem      - Array containing the values of the property item
;                      [0] - identifier
;                      [1] - size of the value array
;                               positive: in bytes
;                               negative: for numeric types: in number of values
;                      [2] - type of value(s) in the value array
;                      [3] - value array
; Return values .: Success: True and @error = 0
;                  Failure: sets the @error flag to:
;                      1:  DllCall failed.  Common cause: _GIDPlus_Startup() has not been called
;                      2:  $a1PropertyItem is not a 4-element 1-d array
;                     10:  @extended may contain GPSTATUS error code ($GPID_ERR* see GDIPlusConstants.au3).
;                    101:  identifier is not an integer
;                    201:  size is not an integer
;                    202:  size cannot be number of values for ASCII string and undefined types
;                    202:  size is zero
;                    301:  type is illegal
;                    401:  value array is either not a 1-d array or has no elements
;                    402:  size is incompatible with type
;                    403:  size cannot be negative for ASCII string and undefined types
;                    404:  Numerator and denominator are required for rational types
;                    405:  size and number of elements in value array differ
; Author ........: Eukalyptus
; Modified ......: c.haslam, UEZ
; Remarks .......: If size parameter is negative, calculates size in bytes from type.
;                  Convenient when setting a few property items
;+
;                  types: unsigned byte = 1, ASCII string = 2, unsigned short = 3, unsigned long = 4,
;                         unsinged rational = 5, undefined = 7, signed long = 9, signed rational = 10
;+
;                   For list of EXIF property items, see https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html
;                  + and https://www.media.mit.edu/pia/Research/deepview/exif.html
; Related .......: _GDIPlus_ImageGetPropertyIdList, cGDIPlus_ImageGetPropertyItem
; Link ..........: https://msdn.microsoft.com/en-us/library/windows/desktop/ms535390(v=vs.85).aspx,
;                  https://msdn.microsoft.com/en-us/library/windows/desktop/ms534493(v=vs.85).aspx,
;                  https://msdn.microsoft.com/en-us/library/windows/desktop/ms534414(v=vs.85).aspx
; Example .......: Yes
; ===============================================================================================================================
Func cGDIPlus_ImageSetPropertyItem($hImage, $a1PropertyItem)

    ;--------- should be moved to GDIPlusConstants.au3
    Local Const $GDIP_PROPERTYTAGTYPEBYTE = 1, $GDIP_PROPERTYTAGTYPEASCII = 2, $GDIP_PROPERTYTAGTYPESHORT = 3, $GDIP_PROPERTYTAGTYPELONG = 4, _
            $GDIP_PROPERTYTAGTYPERATIONAL = 5, $GDIP_PROPERTYTAGTYPEUNDEFINED = 7, $GDIP_PROPERTYTAGTYPESLONG = 9, _
            $GDIP_PROPERTYTAGTYPESRATIONAL = 10
    ;-------------------------------------------------

    If (Not IsArray($a1PropertyItem)) Or UBound($a1PropertyItem, 0) <> 1 Or UBound($a1PropertyItem) <> 4 Then
        Return SetError(2, 0, False)
    EndIf
    Local $iId = $a1PropertyItem[0]
    Local $iLength = $a1PropertyItem[1]
    Local $iType = $a1PropertyItem[2]
    Local $a1values = $a1PropertyItem[3]

    If Not IsInt($iId) Then
        Return SetError(101, 0, False)
    EndIf
    If Not IsInt($iLength) Then
        Return SetError(201, 0, False)
    EndIf
    If $iLength = 0 Then
        Return SetError(202, 0, False)
    EndIf
    Switch $iType
        Case $GDIP_PROPERTYTAGTYPEBYTE, $GDIP_PROPERTYTAGTYPEASCII, $GDIP_PROPERTYTAGTYPESHORT, _
                $GDIP_PROPERTYTAGTYPELONG, $GDIP_PROPERTYTAGTYPERATIONAL, $GDIP_PROPERTYTAGTYPEUNDEFINED, _
                $GDIP_PROPERTYTAGTYPESLONG, $GDIP_PROPERTYTAGTYPESRATIONAL
            ; do nothing
        Case Else
            Return SetError(301, 0, False)
    EndSwitch
    If (Not IsArray($a1values)) Or UBound($a1values, 0) <> 1 Or UBound($a1values) = 0 Then
        Return SetError(401, 0, False)
    EndIf

    Local $iBytes, $iqValues
    If $iLength > 0 Then ; useful when copying all property items
        $iBytes = $iLength
        Switch $iType
            Case $GDIP_PROPERTYTAGTYPEASCII ;ASCII String
                $iqValues = 1
            Case $GDIP_PROPERTYTAGTYPESHORT ;Array of UShort
                If BitAND($iLength, 1) <> 0 Then
                    Return SetError(402, 0, False)
                EndIf
                $iqValues = Int($iLength / 2)
            Case $GDIP_PROPERTYTAGTYPELONG, $GDIP_PROPERTYTAGTYPERATIONAL, $GDIP_PROPERTYTAGTYPESLONG, _
                    $GDIP_PROPERTYTAGTYPESRATIONAL ;Array of UInt / Fraction
                If BitAND($iLength, 3) <> 0 Then
                    Return SetError(402, 0, False)
                EndIf
                $iqValues = Int($iLength / 4)
            Case Else ; Array of Bytes, undefined
                $iqValues = 1
        EndSwitch
    ElseIf $iLength < 0 Then ; convenient when setting a few property items
        Switch $iType
            Case $GDIP_PROPERTYTAGTYPEASCII, $GDIP_PROPERTYTAGTYPEUNDEFINED ;ASCII String, undefined
                Return SetError(403, 0, False)
            Case $GDIP_PROPERTYTAGTYPESHORT ;Array of UShort
                $iBytes = -$iLength * 2
            Case $GDIP_PROPERTYTAGTYPELONG, $GDIP_PROPERTYTAGTYPERATIONAL, $GDIP_PROPERTYTAGTYPESLONG, _
                    $GDIP_PROPERTYTAGTYPESRATIONAL ;Array of UInt / Fraction
                $iBytes = -$iLength * 4
            Case Else ;Array of Bytes
                $iBytes = -$iLength
        EndSwitch
        $iqValues = -$iLength
    EndIf

    If $iType = $GDIP_PROPERTYTAGTYPERATIONAL Or $iType = $GDIP_PROPERTYTAGTYPERATIONAL Then
        If BitAND($iqValues, 1) <> 0 Then
            Return SetError(404, 0, False)
        EndIf
    EndIf

    Switch $iType
        Case $GDIP_PROPERTYTAGTYPESHORT, $GDIP_PROPERTYTAGTYPELONG, $GDIP_PROPERTYTAGTYPERATIONAL, _
                $GDIP_PROPERTYTAGTYPESLONG, $GDIP_PROPERTYTAGTYPESRATIONAL
            If UBound($a1values) <> $iqValues Then
                Return SetError(405, 0, False)
            EndIf
    EndSwitch

    Local $tPropItem = DllStructCreate("int id; int length; short type; ptr pValue;")
    DllStructSetData($tPropItem, 'id', $iId)
    DllStructSetData($tPropItem, 'length', $iBytes)
    DllStructSetData($tPropItem, 'type', $iType)

    ; _ConsoleWrite($iId & @TAB & $iType & @CRLF)

    Local $tValues

    Switch $iType
        Case $GDIP_PROPERTYTAGTYPEASCII ;ASCII String
            $tValues = DllStructCreate("char[" & $iBytes & "];")
        Case $GDIP_PROPERTYTAGTYPESHORT ;Array of UShort
            $tValues = DllStructCreate("ushort[" & $iqValues & "];")
        Case $GDIP_PROPERTYTAGTYPELONG, $GDIP_PROPERTYTAGTYPERATIONAL ;Array of UInt / Fraction
            $tValues = DllStructCreate("uint[" & $iqValues & "];")
        Case $GDIP_PROPERTYTAGTYPESLONG, $GDIP_PROPERTYTAGTYPESRATIONAL ;Array of Int / Fraction
            $tValues = DllStructCreate("int[" & $iqValues & "];")
        Case Else ;Array of Bytes
            $tValues = DllStructCreate("byte[" & $iBytes & "];")
    EndSwitch
    If $iType = $GDIP_PROPERTYTAGTYPEASCII Or $iType = $GDIP_PROPERTYTAGTYPEUNDEFINED Then ; ASCII string or undefined
        DllStructSetData($tValues, 1, $a1values[0])
    Else
        For $i = 0 To $iqValues - 1
            DllStructSetData($tValues, 1, $a1values[$i], $i + 1)
        Next
    EndIf
    DllStructSetData($tPropItem, 'pValue', DllStructGetPtr($tValues))

    Local $aResult = DllCall($__g_hGDIPDll, "uint", "GdipSetPropertyItem", "handle", $hImage, "struct*", $tPropItem)
    If @error Then Return SetError(@error, @extended, -1)
    If $aResult[0] Then Return SetError(10, $aResult[0], -1)
    Return True
EndFunc   ;==>cGDIPlus_ImageSetPropertyItem

; #FUNCTION# ====================================================================================================================
; Name ..........: cGDIPlus_ImageGetAllPropertyItems
; Description ...: Gets all property item (piece of meta data) from an Image object
; Syntax ........: cGDIPlus_ImageGetAllPropertyItems($hImage)
; Parameters ....: $hImage              - Pointer to an image object
; Return values .: Success: Array in which each row contains these columns:
;                      [0] - identifier
;                      [1] - size, in bytes, of the value array
;                      [2] - type of value(s) in the value array
;                      [3] - value array
;                  Failure: sets the @error flag to:
;                      1:  DllCall failed.  Common cause: _GIDPlus_Startup() has not been called
;                     10:  @extended may contain GPSTATUS error code ($GPID_ERR* see GDIPlusConstants.au3).
;                          @extended=2; no property items found
; Author ........: c.haslam
; Modified ......:
; Remarks .......: types: unsigned byte = 1, ASCII string = 2, unsigned short = 3, unsigned long = 4,
;                         unsinged rational = 5, signed byte = 6, undefined = 7, signed long = 9,
;                         signed rational = 10
;+
;                   For list of EXIF property items, see https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html
;                  + and https://www.media.mit.edu/pia/Research/deepview/exif.html
; Related .......: cGDIPlus_ImageGetPropertyItem, _GDIPlus_ImageGetPropertyIdList, cGDIPlus_ImageSetPropertyItem
; Link ..........: https://msdn.microsoft.com/en-us/library/windows/desktop/ms535390(v=vs.85).aspx,
;                  https://msdn.microsoft.com/en-us/library/windows/desktop/ms534493(v=vs.85).aspx,
;                  https://msdn.microsoft.com/en-us/library/windows/desktop/ms534414(v=vs.85).aspx,
;                  https://msdn.microsoft.com/en-us/library/ms534416.aspx#_gdiplus_constant_propertytaggpsver
; Example .......: Yes
; ===============================================================================================================================

Func cGDIPlus_ImageGetAllPropertyItems($hImage)
    Local $aResult = DllCall($__g_hGDIPDll, "uint", "GdipGetPropertySize", "handle", $hImage, "uint*", 0, "uint*", 0)
    If @error Then Return SetError(@error, @extended, -1)
    If $aResult[0] Then Return SetError(10, $aResult[0], -1)
    Local $iTotalBufferSize = $aResult[2]
    Local $iNumProperties = $aResult[3]

    Local $tBuffer = DllStructCreate("byte[" & $iTotalBufferSize & "]")
    Local $pBuffer = DllStructGetPtr($tBuffer)

    $aResult = DllCall($__g_hGDIPDll, "uint", "GdipGetAllPropertyItems", "handle", $hImage, _
            "uint", $iTotalBufferSize, "uint", $iNumProperties, "ptr", $pBuffer)
    If @error Then Return SetError(@error, @extended, -1)
    If $aResult[0] Then Return SetError(10, $aResult[0], -1)

    Local $aPropertyItems[$iNumProperties][4]
    Local $a1
    Local $tPropertyItem = DllStructCreate("int id; int length; short type; ptr value;")

    For $iI = 0 To $iNumProperties - 1
        $a1 = __GDIPlus_ImageGetPropertyItemValues($pBuffer)
        For $j = 0 To 3
            $aPropertyItems[$iI][$j] = $a1[$j]
        Next
        $pBuffer += DllStructGetSize($tPropertyItem)
    Next

    Return $aPropertyItems
EndFunc   ;==>cGDIPlus_ImageGetAllPropertyItems

; #INTERNAL_USE_ONLY# ===========================================================================================================
; Name ..........: __GDIPlus_ImageGetPropertyItemValues($pBuffer)
; Description ...: Converts a project item from structured to equivalent nested arrays
; Syntax ........: __GDIPlus_ImageGetPropertyItemValues($pBuffer)
; Parameters ....: $bBuffer             - Pointer to start of Property Item sructure
; Return values .:     [0] - identifier
;                      [1] - size, in bytes, of the value array
;                      [2] - type of value(s) in the value array
;                      [3] - value array
; Author ........: c.haslam
; Modified ......: UEZ
; Remarks .......: types: unsigned byte = 1, ASCII string = 2, unsigned short = 3, unsigned long = 4,
;                         unsigned rational = 5, signed byte = 6, undefined = 7, signed long = 9,
;                         signed rational = 10
; Related .......:
; Link ..........:
; Example .......: No
; ===============================================================================================================================

Func __GDIPlus_ImageGetPropertyItemValues($pBuffer)
    Local $tPropertyItem = DllStructCreate("int id; int length; short type; ptr value;", $pBuffer)
    Local $iBytes = DllStructGetData($tPropertyItem, "length")
    Local $pValue = DllStructGetData($tPropertyItem, "value")

    ;--------- should be moved to GDIPlusConstants.au3
    Local Const $GDIP_PROPERTYTAGTYPEBYTE = 1, $GDIP_PROPERTYTAGTYPEASCII = 2, $GDIP_PROPERTYTAGTYPESHORT = 3, $GDIP_PROPERTYTAGTYPELONG = 4, _
            $GDIP_PROPERTYTAGTYPERATIONAL = 5, $GDIP_PROPERTYTAGTYPEUNDEFINED = 7, $GDIP_PROPERTYTAGTYPESLONG = 9, _
            $GDIP_PROPERTYTAGTYPESRATIONAL = 10
    ;-------------------------------------------------

    Local $aRet[4]
    Local $type = DllStructGetData($tPropertyItem, 'type')
    Local $tValues, $iValues

    Switch $type
        Case $GDIP_PROPERTYTAGTYPEASCII ;ASCII String
            $iValues = 1
            $tValues = DllStructCreate("char[" & $iBytes & "];", $pValue)
        Case $GDIP_PROPERTYTAGTYPESHORT ;Array of UShort
            $iValues = Int($iBytes / 2)
            $tValues = DllStructCreate("ushort[" & $iValues & "];", $pValue)
        Case $GDIP_PROPERTYTAGTYPELONG, $GDIP_PROPERTYTAGTYPERATIONAL ;Array of UInt / Fraction
            $iValues = Int($iBytes / 4)
            $tValues = DllStructCreate("uint[" & $iValues & "];", $pValue)
        Case $GDIP_PROPERTYTAGTYPESLONG, $GDIP_PROPERTYTAGTYPESRATIONAL ;Array of Int / Fraction
            $iValues = Int($iBytes / 4)
            $tValues = DllStructCreate("int[" & $iValues & "];", $pValue)
        Case Else ;Array of Bytes
            $iValues = 1
            $tValues = DllStructCreate("byte[" & $iBytes & "];", $pValue)
    EndSwitch
    $aRet[0] = DllStructGetData($tPropertyItem, 'id')
    $aRet[1] = $iBytes
    $aRet[2] = $type
    Local $a1values[$iValues]
    If $type = $GDIP_PROPERTYTAGTYPEASCII Or $type = $GDIP_PROPERTYTAGTYPEUNDEFINED Then ; ASCII string or undefined
        $a1values[0] = DllStructGetData($tValues, 1)
    Else
        For $i = 0 To $iValues - 1
            $a1values[$i] = DllStructGetData($tValues, 1, $i + 1)
        Next
    EndIf
    $aRet[3] = $a1values
    Return $aRet
EndFunc   ;==>__GDIPlus_ImageGetPropertyItemValues

 

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...