Ich wollte diese GDIPlus-Sache abschließen und habe mich nochmal reingekniet. Dabei ist mir auch wieder eingefallen, dass es ja mit
IGDIPlus von Boian Mitov eine neuere und bessere Portierung gibt. Die letzte Änderung ist zwar von 2016, aber das ist wesentlich neuer als Prodigy von 2002.
Dann habe ich nochmal
"Lossless transform of a JPEG image" von Microsoft gründlicher gelesen. Dort steht klipp und klar, dass es verlustfrei nur geht, wenn man mit EncoderTransformation speichert. Die Lösung mit
GPImage.RotateFlip(RotateType)
ist also immer verlustbehaftet. Des Weiteren darf
EncoderParameters
nur ein einziges Objekt in seinem Array haben. Das war aber schon im bisherigen Code richtig drin.
Ausgehend davon habe ich eine Routine erstellt, die alle JPG-Dateien im Verzeichnis anhand des Orientation Tags verlustfrei dreht. Zudem setzt sie das Orientation Tag auf "1" zurück und - als Schmankerl - setzt sie das Dateidatum auf das EXIF-Datum zurück. Leider ist die Umwandlung des PPropDItem.Value zu einem TDateTime noch etwas rustikal (aber sie funktioniert). Vermutlich gibt es an einigen Stellen noch etwas zu meckern, aber funktionieren tut das Ding. Speicherlecks gibt es laut FastMM5 auch nicht.
Auch habe ich ein bisschen optimiert; manche Werte wie die Buffergrößen etc. müssen nur einmal ermittelt werden und nicht bei jedem Datei-Durchlauf. Zudem habe ich die Interface-Variante von
IGDPImage
verwendet, so dass es kein
Free
gibt.
Da die Prozedur dadurch sehr lang wurde und ich ja sowieso ein großer Fan von Unterprozeduren bin, habe ich den Spaghetticode in handgerechte Abschnitte unterteilt.
Auf meinem gegenwärtigen System (Ryzen 5 3400G) braucht die Prozedur für 40 Dateien mit 245 MB etwa 30 Sekunden, also etwa 750 msec für eine. Das ist jetzt nicht so toll, aber man dreht ja auch nicht so oft.
Im Explorer werden die Vorschaubilder immer richtig angezeigt. Ober dieses
GDI+ jetzt tatsächlich auch die Vorschaubilder mitdreht oder der Explorer sie selber erstellt, weiß ich nicht, aber es scheint in der Praxis egal zu sein.
Delphi-Quellcode:
uses ... IGDIPlus;
procedure DreheMitIGDIPlus;
Var i:integer;
Orientation: UInt32;
EXIFDatum:TDateTime;
DatListe:TStringDynArray;
IGDPImage: IGPImage;
PPropOItem,PPropDItem: PGPPropertyItem;
PropBufferSize,DateBufferSize: Cardinal;
DreheXGrad: TIGPEncoderValue;
EncoderParams: TIGPEncoderParameters;
EncoderTransformValue:integer;
TempDatname:string;
EncoderCLSID: TGUID;
//--------------------------------------------------------------------------------------------------------------------------------------------------------------
procedure Initialisiere;
begin
PropBufferSize := 0;
PPropOItem := nil;
EncoderParams.Count := 1;
EncoderParams.Parameter[0].Guid := EncoderTransformation;
EncoderParams.Parameter[0].DataType := EncoderParameterValueTypeLong;
EncoderParams.Parameter[0].NumberOfValues := 1;
GetEncoderClsid('image/jpeg', EncoderCLSID);
end;
//--------------------------------------------------------------------------------------------------------------------------------------------------------------
function BufferGesetzt:Boolean;
begin
If (PropBufferSize > 0) and (DateBufferSize > 0)
then exit(True);
PropBufferSize := IGDPImage.GetPropertyItemSize(GPPropertyTagOrientation);
If PropBufferSize > 0
then GetMem(PPropOItem, PropBufferSize);
DateBufferSize := IGDPImage.GetPropertyItemSize(GPPropertyTagDateTime);
If DateBufferSize > 0
then GetMem(PPropDItem, DateBufferSize);
Result := (PropBufferSize > 0) and (DateBufferSize > 0);
end;
//--------------------------------------------------------------------------------------------------------------------------------------------------------------
function BestimmeRotation(var DreheXGrad:TIGPEncoderValue):Boolean;
var Orientation: UInt32;
begin
IGDPImage.GetPropertyItem(GPPropertyTagOrientation, PropBufferSize, PPropOItem);
Orientation := PUInt32(PPropOItem.value)^;
Case Orientation of
3: DreheXGrad := EncoderValueTransformRotate180;
6: DreheXGrad := EncoderValueTransformRotate90;
8: DreheXGrad := EncoderValueTransformRotate270;
else DreheXGrad := TIGPEncoderValue(100); // zum Testen "else DreheXGrad := EncoderValueTransformRotate90;" , wenn man keine Hochkant-Bilder hat
end;
Result := (DreheXGrad in [EncoderValueTransformRotate90..EncoderValueTransformRotate270]);
end;
//--------------------------------------------------------------------------------------------------------------------------------------------------------------
function BestimmeEXIFDatum:TDateTime;
var DateOrig: array of Byte; TxtEXIFDatum:string;
begin
IGDPImage.GetPropertyItem(GPPropertyTagDateTime, DateBufferSize, PPropDItem);
SetLength(DateOrig,DateBufferSize);
Move(PPropDItem.Value,DateOrig[1],DateBufferSize);
TxtEXIFDatum := Copy(TEncoding.ASCII.GetString(DateOrig),6,19); // Das ist nicht der Weisheit letzter Schluss
Result :=
EncodeDate(StrToInt(Copy(TxtEXIFDatum, 1, 4)),StrToInt(Copy(TxtEXIFDatum, 6, 2)),StrToInt(Copy(TxtEXIFDatum, 9, 2))) +
EncodeTime(StrToInt(Copy(TxtEXIFDatum, 12, 2)),StrToInt(Copy(TxtEXIFDatum, 15, 2)),StrToInt(Copy(TxtEXIFDatum, 18, 2)), 0);
end;
//--------------------------------------------------------------------------------------------------------------------------------------------------------------
function SpeichereJPG:Boolean;
begin
Result := False;
TempDatname := TPath.ChangeExtension(DatListe[i],'$$$');
If TFile.Exists(TempDatname)
then exit;
IGDPImage.Save(WideString(TempDatname),EncoderCLSID,@EncoderParams);
IGDPImage := nil;
If TFile.Exists(TempDatname) then begin
Result := DeleteFile(DatListe[i]);
If Result
then Result := RenameFile(TempDatname,DatListe[i])
else DeleteFile(TempDatname);
end;
end;
//--------------------------------------------------------------------------------------------------------------------------------------------------------------
function SetzeDatumAufEXIF(Datname:string;EXIFDatum:TDateTime):Boolean;
begin
Result := True;
Try
TFile.SetCreationTime(Datname,EXIFDatum);
TFile.SetLastWriteTime(Datname,EXIFDatum);
Except
Result := False;
End;
end;
//--------------------------------------------------------------------------------------------------------------------------------------------------------------
procedure RäumeAuf;
begin
FreeMem(PPropOItem);
FreeMem(PPropDItem);
IGDPImage := nil;
end;
//--------------------------------------------------------------------------------------------------------------------------------------------------------------
begin
Initialisiere;
Try
DatListe := TDirectory.GetFiles(Verz,'*.jpg');
For i := 0 to High(Datliste) do begin // zum Testen auf "0 to 0" setzen
IGDPImage := TIGPImage.Create(DatListe[i]);
If not BufferGesetzt
then exit;
EXIFDatum := BestimmeEXIFDatum;
If BestimmeRotation(DreheXGrad) then begin
EncoderTransformValue := Ord(DreheXGrad);
EncoderParams.Parameter[0].Value := @EncoderTransformValue;
Orientation := 1; // zum Testen z.B. auf 3 setzen, wenn man keine Hochkant-Bilder hat
PPropOItem.Value := @Orientation;
IGDPImage.SetPropertyItem(PPropOItem^);
If not SpeichereJPG
then exit;
SetzeDatumAufEXIF(DatListe[i],EXIFDatum);
end;
IGDPImage := nil;
end;
Finally
RäumeAuf;
end;
end;