Irgendwie ist mir das Thema aus den Augen geraten.
Die Datei GdiPlus.dll kommt bei Windows 7, 8, 10 (64-Bit) sowohl in 32-Bit als auch in 64-Bit mit.
Du hast natürlich Recht! Ich kam darauf, weil ich Mühe hatte, die richtige gdiplus.dll zu erwischen (warum gibt es nicht einfach eine gdiplus64.dll?). Auf meinem System gibt es nicht weniger als 31 Exemplare davon. Jetzt habe ich sie händisch an einen sicheren Ort kopiert und bin ganz happy.
...ich habe mir zum Drehen (im Speicher) von Bildern nach dem Orientation-Tag das Folgende ausgedacht.
In meinem Code steckt ein Fehler (ich hatte da kopiert, ohne nochmal nachzuprüfen), der entsprechend auch in deinem auftaucht: Der Wert eines EXIFTags besteht immer aus 4 Byte. Daher ist
Orientation: Byte;
falsch und
Orientation := PByte(pPropItem.value)^;
dementsprechend auch. Der Wert muss ein UInt32 (Cardinal) sein, und das Auslesen entsprechend
Orientation := PUInt32(pPropItem.value)^;
. Ganz tückisch ist, dass der Wert bei Little Endian auch als Byte richtig gelesen wird. Das bedeutet auch, dass bei BigEndian der Wert korrekt mit
Swap(PUInt32(pPropItem.value)^)
ausgelesen werden müsste. Müsste. Denn ein Versuch mit einem JPG mit BigEndian hat ergeben, dass das Orientation Tag trotzdem richtig ausgelesen wurde. Und beim Setzen (siehe unten) auch im richtigen Format gesetzt wurde. Bequem, ist mir aber suspekt.
Ein großes Problem finde ich, das JPG mit den richtigen
GDI+-Parametern so zu speichern, dass es nicht noch einmal kodiert wird. Ich beobachte zum Beispiel, dass sich die Größe glatt halbieren kann; zum Beispiel, wenn man nach deinem Code ein
.Save
anfügt. Ich habe keine Möglichkeit gefunden (was nicht heißt, dass es keine gibt), die nicht mit
EncoderTransformation
arbeiten würde. Bei meiner Volllösung (siehe unten) wird meine Datei sogar geringfügig größer, um dann bei allen Drehungen aber gleich zu bleiben. Hm. Da habe ich das Optimum wohl noch nicht gefunden. Vorläufig verwende ich
GDI+ nicht, wenn ich danach speichern muss.
Wie genau funktioniert
GPImage.SetPropertyItem(PPropItem^);
Die Antwort steht neben der bereits zitierten Stelle auch
hier. Der Link bezieht sich zwar auf Dotnet; aber dort werden die Schwierigkeiten mit SetPropertyItem erläutert. Am besten ist offenbar, man ruft das Orientation Tag mit GetPropertyItem ab, füllt das PropertyItem neu (bzw. nur Value) und setzt es mit SetPropertyItem. Also so:
Delphi-Quellcode:
uses ... GDIPAPI, GDIPOBJ,GDIPUTIL;
procedure DreheMitGDIPlus2;
var i:integer;
DatListe:TStringDynArray;
GPImage: TGPImage;
PPropItem: PPropertyItem;
BufferSize: Cardinal;
Orientation: UInt32;
RotateBy: EncoderValue;
EncoderCLSID: TGUID;
EncoderParams: TEncoderParameters;
EncoderTransformValue:integer;
Ergebnis : Status;
TempDatname:string;
const: Verz = 'C:\Temp\';
begin
DatListe := TDirectory.GetFiles(Verz,'*.jpg');
GetEncoderClsid('image/jpeg', EncoderCLSID);
FillChar(EncoderParams, SizeOf(EncoderParams), 0);
EncoderParams.Count := 1;
EncoderParams.Parameter[0].Guid := EncoderTransformation;
EncoderParams.Parameter[0].Type_ := EncoderParameterValueTypeLong;
EncoderParams.Parameter[0].NumberOfValues := 1;
For i := 0 to 0 do begin // 0 to High(Datliste)
GPImage := TGPImage.Create(DatListe[i]);
BufferSize := GPImage.GetPropertyItemSize(PropertyTagOrientation);
If BufferSize > 0 then begin
GetMem(PPropItem, BufferSize);
Try
GPImage.GetPropertyItem(PropertyTagOrientation, BufferSize, PPropItem);
Orientation := PUInt32(pPropItem.value)^;
case Orientation of
3: RotateBy := EncoderValueTransformRotate180;
6: RotateBy := EncoderValueTransformRotate90;
8: RotateBy := EncoderValueTransformRotate270;
else continue; // RotateBy := EncoderValueTransformRotate90; zum Testen
end;
If (Orientation in [3,6,9]) then begin // zum Testen [1,3,6,9]
Orientation := 1; // oder 3, zum Testen
pPropItem.value := @Orientation;
GPImage.SetPropertyItem(pPropItem^);
EncoderTransformValue := Ord(RotateBy);
EncoderParams.Parameter[0].Value := @EncoderTransformValue;
TempDatname := TPath.ChangeExtension(DatListe[i],'$$$');
Ergebnis := GPImage.Save(WideString(TempDatname),EncoderCLSID,@EncoderParams);
GPImage.Free;
If Ergebnis = Ok then begin
If DeleteFile(DatListe[i])
then RenameFile(TempDatname,DatListe[i])
else DeleteFile(TempDatname);
end;
end;
Finally
FreeMem(PPropItem);
end;
end;
end;
end;
(Nicht vergessen, für's GPImage braucht es auch noch ein
Try
.)