Einzelnen Beitrag anzeigen

Benmik

Registriert seit: 11. Apr 2009
544 Beiträge
 
Delphi 11 Alexandria
 
#64

AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten

  Alt 18. Jul 2020, 22:45
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 .)
  Mit Zitat antworten Zitat