PureBasic GDI+ Image Properties

 

Снова зарылся в дебри и нашел статью о свойствах изображения для GDI+. Меня особенно заинтересовала возможность добавления и чтения текстовой информации. Написал программу, которая еле-еле запустилась, помогли ребята на форуме(load_jpg.pb)

#GDIPLUS_OK = 0
Enumeration
  #RotateNoneFlipNone
  #Rotate90FlipNone
  #Rotate180FlipNone
  #Rotate270FlipNone
  #RotateNoneFlipX
  #Rotate90FlipX
  #Rotate180FlipX
  #Rotate270FlipX
  #RotateNoneFlipY   = #Rotate180FlipX
  #Rotate90FlipY     = #Rotate270FlipX
  #Rotate180FlipY    = #RotateNoneFlipX
  #Rotate270FlipY    = #Rotate90FlipX
  #RotateNoneFlipXY  = #Rotate180FlipNone
  #Rotate90FlipXY    = #Rotate270FlipNone
  #Rotate180FlipXY   = #RotateNoneFlipNone
  #Rotate270FlipXY   = #Rotate90FlipNone
EndEnumeration 
Enumeration
  #EncoderParameterValueTypeByte          = 1
  #EncoderParameterValueTypeASCII         = 2
  #EncoderParameterValueTypeShort         = 3
  #EncoderParameterValueTypeLong          = 4
  #EncoderParameterValueTypeRational      = 5
  #EncoderParameterValueTypeLongRange     = 6
  #EncoderParameterValueTypeUndefined     = 7
  #EncoderParameterValueTypeRationalRange = 8
  #EncoderParameterValueTypePointer       = 9
EndEnumeration 
;-Structures.
Structure GdiplusStartupInput
  GdiPlusVersion.l 
  *DebugEventCallback.DebugEventProc
  SuppressBackgroundThread.l 
  SuppressExternalCodecs.l 
EndStructure
;The following two structures are used for setting encoder parameters (in our case the 'quality' parameter).
Structure EncoderParameter
  guid.GUID
  NumberOfValues.l
  Type.l
  Value.l
EndStructure
Structure EncoderParameters
  Count.i
  Parameter.EncoderParameter[1]
EndStructure
Structure PropertyItem
  id.l;                 // ID of this property
  length.l;             // Length of the property value, in bytes
  type.u  ;               // Type of the value, as one of TAG_TYPE_XXX
  padding.w
  *value
EndStructure

;-Imports.
Import "gdiplus.lib"
  GdiplusStartup(token, *input.GdiplusStartupInput, output) 
  GdiplusShutdown(token)
  GdipLoadImageFromFile(*filename, *bitmap)
  GdipImageRotateFlip(image, rotateFlipType)
  GdipSaveImageToFile(image, filename.p-unicode, *clsidEncoder.CLSID, *encoderParams)
  GdipDisposeImage(image)
  GdipGetAllPropertyItems(*image, totalBufferSize.l, numProperties.l, ai);ai is *allItems.PropertyItem
  GdipGetPropertySize(*image, totalBufferSize.i, numProperties.i)
EndImport

sourceFile$="~photo_2025-02-18_14-38-49.jpg"
;sourceFile$="1584812707-a92e3f716beaa04c7000e8afc3b87b59.png"
input.GdiplusStartupInput\GdiPlusVersion = 1
GdiplusStartup(@token, @input, #Null)
;Was the initialisation successful?
If token
  If GdipLoadImageFromFile(@sourceFile$, @image) = #GDIPLUS_OK
    totalBufferSize=0
    numProperties=0
    If GdipGetPropertySize(image, @totalBufferSize, @numProperties) = #GDIPLUS_OK
      If totalBufferSize And numProperties
        *m=AllocateMemory(totalBufferSize)
        rr=gdipGetAllPropertyItems(image,totalBufferSize, numProperties,*m)
        ;     CreateFile(0,"prop.bin")
        ;     WriteData(0,*m,totalBufferSize)
        ;     CloseFile(0)
        *pp.PropertyItem=AllocateMemory(SizeOf(PropertyItem))
        For i=0 To numProperties-1
          CopyMemory(*m+i*SizeOf(PropertyItem),*pp,SizeOf(PropertyItem))
          Debug "id="+Hex(*pp\id)+",len="+Str(*pp\length)+",type="+Str(*pp\type);;+",value="+Str(pp\*value)
           If *pp\type=1 And *pp\id=$9c9c
             ; ShowMemoryViewer(*pp\value, *pp\length)
             Debug PeekS(*pp\value,*pp\length,#PB_Unicode)
           EndIf
          ;type=1, id=9c9c EXIF comment
          If *pp\type=2
            Debug PeekS(*pp\value,*pp\length,#PB_Ascii);ShowMemoryViewer(*pp\value, 1024)
          EndIf
        Next i
        FreeMemory(*pp)
        FreeMemory(*m)
      EndIf
    EndIf;If GdipGetPropertySize
    
    GdipDisposeImage(image)
  EndIf
  ;         ;Tidy up.
  GdiplusShutdown(token)
EndIf

DataSection
  ;CLSID for the gdi+ jpeg encoder.
  clsid_jpeg:
  Data.l $557CF401
  Data.w $1A04, $11D3
  Data.b $9A, $73, $00, $00, $F8, $1E, $F3, $2E
  ;CLSID for the relevant gdi+ encoder parameter.
  clsid_EncoderQuality:
  Data.l $1D5BE4B5
  Data.w $FA4A, $452D
  Data.b $9C, $DD, $5D, $B3, $51, $05, $E7, $EB
EndDataSection


Скормил программе чистую картинку без EXIF и с текстовым маркером. Нифига не получил, так как структура Image Properties описана через задницу:

Structure PropertyItem
  id.l;                 // ID of this property
  length.l;             // Length of the property value, in bytes
  type.u  ;               // Type of the value, as one of TAG_TYPE_XXX
  padding.w ; это поле отсутствовало
  *value
EndStructure

Разобрался с выводом данных и получил, что тестовый маркер комментария помечен как PropertyTagExifUserComment $9286. Но мне показалось, что мало, я добавил к файлу ~photo_2025-02-18_14-38-49.jpg свой комментарий, поле помечено как $9C9C.- по спецификации  это Exif.Image.XPComment. Подобные исследования может продолжить каждый,
использовав этот код. Но мне показалось мало, и я обратился к формату PNG, скормил файл с порцией iTXt, программа ничего не вывела.

Я стал разбираться с записью комментария к файлу JPG(save_draw_jpg.pb)

;https://www.purebasic.fr/english/viewtopic.php?t=49026

Structure GdiplusStartupInput
  GdiPlusVersion.l 
  *DebugEventCallback.DebugEventProc
  SuppressBackgroundThread.l 
  SuppressExternalCodecs.l 
EndStructure

Structure PropertyItem
  id.l;                 // ID of this property
  length.l;             // Length of the property value, in bytes
  type.u  ;               // Type of the value, as one of TAG_TYPE_XXX
  padding.w
  *value
EndStructure

;The following two structures are used for setting encoder parameters (in our case the 'quality' parameter).
Structure EncoderParameter
  guid.GUID
  NumberOfValues.l
  Type.l
  Value.l
EndStructure

Structure EncoderParameters
  Count.i
  Parameter.EncoderParameter[1]
EndStructure

Import "gdiplus.lib"
  GdiplusStartup(token, *input.GdiplusStartupInput, output) 
  GdiplusShutdown(token)
  GdipLoadImageFromFile(*filename, *bitmap)
  GdipImageRotateFlip(image, rotateFlipType)
  GdipSaveImageToFile(image, filename.p-unicode, *clsidEncoder.CLSID, *encoderParams)
  GdipDisposeImage(image)
  GdipGetAllPropertyItems(*image, totalBufferSize.l, numProperties.l, ai);ai is *allItems.PropertyItem
  GdipGetPropertySize(*image, totalBufferSize.i, numProperties.i)
  GdipCreateBitmapFromHBITMAP(hbm.i, hpal.i, bitmap)
  GdipSetPropertyItem(*image, *pi)
EndImport

path$ = GetCurrentDirectory()+"ttest.jpg"

; Make an image to test with
CreateImage(0, 640,640)
StartDrawing(ImageOutput(0))
  Box(0,0,640,640,#White)
  Circle(319,319,319,#Blue)
  Circle(319,319,219,#Green)
  Circle(319,319,119,#Yellow)
StopDrawing()


#EncoderParameterValueTypeLong = 4

;Define Unicode$=Space(Len(path$)*2+2), path_as_bstr, lib
;Define bmp.BITMAP,input.GdiplusStartupInput, *token, *image
;PokeS(@Unicode$, path$, -1, #PB_Unicode) 
;path_as_bstr = SysAllocString_(@Unicode$) 

input.GdiplusStartupInput\GdiPlusVersion=1 
GdiplusStartup(@*token, @input, #Null)

; Here is where we have a gdiplus image object that we want to save as jpeg with selectable quality
GdipCreateBitmapFromHBITMAP(ImageID(0), 0, @*image)

quality.l = 100 ; qualities can range from 0 - 100, with 100 being best
With encoderparams.EncoderParameters
  \Count = 1
  \Parameter[0]\Type = #EncoderParameterValueTypeLong
  \Parameter[0]\NumberOfValues = 1
  \parameter[0]\Value = @quality
EndWith
CopyMemory(?clsid_EncoderQuality, encoderparams\parameter[0]\Guid, SizeOf(GUID))

c$="simple text"
#PropertyTagImageTitle = $0320
#PropertyTagExifUserComment=$9286
#PropertyTagTypeASCII = 2
*NewTitle.PropertyItem=AllocateMemory(SizeOf(PropertyItem))
NewTitle.s = Space(MemoryStringLength(@c$, #PB_Ascii))
PokeS(@NewTitle, c$, -1, #PB_Ascii)

*NewTitle\id = #PropertyTagExifUserComment;#PropertyTagImageTitle
*NewTitle\length=Len(c$)+1
*NewTitle\type = #PropertyTagTypeASCII
*NewTitle\value = @NewTitle
rr=GdipSetPropertyItem(*image, *NewTitle)
Debug rr
FreeMemory(*NewTitle)
result = GdipSaveImageToFile(*image, path$, ?clsid_jpeg, encoderparams)

GdipDisposeImage(image)
GdiplusShutdown(token)

DataSection
  clsid_jpeg: ; clsid for jpeg image format
  Data.l $557CF401
  Data.w $1A04
  Data.w $11D3
  Data.b $9A,$73,$00,$00,$F8,$1E,$F3,$2E
  
  clsid_EncoderQuality:
  Data.l $1D5BE4B5
  Data.w $FA4A
  Data.w $452D
  Data.b $9C,$DD,$5D,$B3,$51,$05,$E7,$EB
EndDataSection

GDI+ добавляет разные поля в зависимости от 
*NewTitle\id = #PropertyTagExifUserComment;#PropertyTagImageTitle

Если использовать  #PropertyTagImageTitle, то некоторые программы не понимают этот тег.Или я не осилил описание? Короче, в оригинальном исходном тексте добавляется пользовательский комментарий. Выходит, что текстовый маркер для JPG не пишется. С PNG не стал разбираться, меня утомили эти поиски информации и ковыряние с убогим API.
Исходники с картинками здесь.


Комментарии