AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Programmierung allgemein Multimedia Delphi JPG-Datei drehen und speichern -> Verlust der Exif-Daten

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

Ein Thema von axelf98 · begonnen am 3. Sep 2005 · letzter Beitrag vom 22. Aug 2021
Antwort Antwort
Benmik

Registriert seit: 11. Apr 2009
578 Beiträge
 
Delphi 12 Athens
 
#1

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

  Alt 8. Jul 2020, 10:55
Hier noch die Verwendung von GDI+. Nur einfaches Beispiel, ohne Ressourcenschutz oder Ähnliches. Leider bietet die Save-Procedure nicht die Option des Überschreibens, daher ein bisschen Nachbau. GDI+ ist sehr schnell. Zu beachten ist, dass hier die Blockgröße 16 Pixel beträgt, Höhe/Breite also ein Vielfaches von 16 sein müssen.
Frustrierend ist, dass GDI+ als 32-Bit-DLL natürlich wieder mal nicht in 64-Bit-Anwendungen benutzt werden kann.
Delphi-Quellcode:
uses ... GDIPAPI, GDIPOBJ,GDIPUTIL;

procedure DreheMitGDIPlus1;
Var i:integer;
  DatListe:TStringDynArray;
  GPImage: TGPImage;
  EncoderCLSID: TGUID;
  Ergebnis : Status;
  TempDatname:string;
const
  Verz = 'C:\Test\';
begin
  DatListe := TDirectory.GetFiles(Verz,'*.jpg');
  GetEncoderClsid('image/jpeg', EncoderCLSID);
  For i := 0 to High(DatListe) do begin
    GPImage := TGPImage.Create(DatListe[i]);
    GPImage.RotateFlip(Rotate90FlipNone);
    TempDatname := TPath.ChangeExtension(DatListe[i],'$$$');
    Ergebnis := GPImage.Save(TempDatname,EncoderCLSID);
    GPImage.Free;
    If Ergebnis = Ok then begin
      If DeleteFile(DatListe[i])
        then RenameFile(TempDatname,DatListe[i])
        else DeleteFile(TempDatname);
    end;
  end;
end;
Die nachfolgende Version ist die "volle". An ihr ist besonders interessant, dass sie nicht nur selbständig ermittelt, ob das JPG überhaupt gedreht werden muss, sondern auch den Zugriff auf weitere GDI-Funktionen bietet. Auch kann außer dem PropertyTagOrientation noch eine Vielzahl von weiteren EXIF-Markern ausgelesen werden.
Delphi-Quellcode:
uses ... GDIPAPI, GDIPOBJ,GDIPUTIL;

procedure DreheMitGDIPlus2;
Var i:integer;
  DatListe:TStringDynArray;
  GPImage: TGPImage;
  PPropItem: PPropertyItem;
  BufferSize: Cardinal;
  Orientation: Byte;
  RotateBy: EncoderValue;
  EncoderCLSID: TGUID;
  EncoderParams: TEncoderParameters;
  EncoderTransformValue:integer;
  Ergebnis : Status;
  TempDatname:string;
const
  Verz = 'C:\Test\';
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 High(DatListe) do begin
    GPImage := TGPImage.Create(DatListe[i]);
    BufferSize := GPImage.GetPropertyItemSize(PropertyTagOrientation);
    If BufferSize > 0 then begin
      GetMem(PPropItem, BufferSize);
      Try
        GPImage.GetPropertyItem(PropertyTagOrientation, BufferSize, PPropItem);
        Orientation := PByte(PPropItem.value)^;
        case Orientation of
          3: RotateBy := EncoderValueTransformRotate180;
          6: RotateBy := EncoderValueTransformRotate90;
          8: RotateBy := EncoderValueTransformRotate270;
          else Continue;
        end;
        If (Orientation in [3,6,9]) then begin
          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;

Geändert von Benmik ( 8. Jul 2020 um 14:50 Uhr) Grund: Blockgröße 16 Pixel
  Mit Zitat antworten Zitat
TiGü

Registriert seit: 6. Apr 2011
Ort: Berlin
3.079 Beiträge
 
Delphi 10.4 Sydney
 
#2

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

  Alt 10. Jul 2020, 08:50
Frustrierend ist, dass GDI+ als 32-Bit-DLL natürlich wieder mal nicht in 64-Bit-Anwendungen benutzt werden kann.
Wie meinst du das?

Die Datei GdiPlus.dll kommt bei Windows 7, 8, 10 (64-Bit) sowohl in 32-Bit als auch in 64-Bit mit.
Man sollte nicht den "Fehler" machen, eine eigene Version der DLL mitzudeployen.
Siehe:
C:\Windows\System32\GdiPlus.dll (64-Bit)
C:\Windows\SysWOW64\GdiPlus.dll (32-Bit)

(Ja, sind teilweise auch nur symbolische Links auf andere Orte, aber im 64-Bit Windows kommt die DLL in beiden Bitness-Varianten vor).
  Mit Zitat antworten Zitat
Benutzerbild von DataCool
DataCool

Registriert seit: 10. Feb 2003
Ort: Lingen
909 Beiträge
 
Delphi 10.3 Rio
 
#3

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

  Alt 10. Jul 2020, 17:35
Hier noch die Verwendung von GDI+. Nur einfaches Beispiel, ohne Ressourcenschutz oder Ähnliches. Leider bietet die Save-Procedure nicht die Option des Überschreibens, daher ein bisschen Nachbau. GDI+ ist sehr schnell. Zu beachten ist, dass hier die Blockgröße 16 Pixel beträgt, Höhe/Breite also ein Vielfaches von 16 sein müssen.
Frustrierend ist, dass GDI+ als 32-Bit-DLL natürlich wieder mal nicht in 64-Bit-Anwendungen benutzt werden kann.
Delphi-Quellcode:
uses ... GDIPAPI, GDIPOBJ,GDIPUTIL;

procedure DreheMitGDIPlus1;
Var i:integer;
  DatListe:TStringDynArray;
  GPImage: TGPImage;
  EncoderCLSID: TGUID;
  Ergebnis : Status;
  TempDatname:string;
const
  Verz = 'C:\Test\';
begin
  DatListe := TDirectory.GetFiles(Verz,'*.jpg');
  GetEncoderClsid('image/jpeg', EncoderCLSID);
  For i := 0 to High(DatListe) do begin
    GPImage := TGPImage.Create(DatListe[i]);
    GPImage.RotateFlip(Rotate90FlipNone);
    TempDatname := TPath.ChangeExtension(DatListe[i],'$$$');
    Ergebnis := GPImage.Save(TempDatname,EncoderCLSID);
    GPImage.Free;
    If Ergebnis = Ok then begin
      If DeleteFile(DatListe[i])
        then RenameFile(TempDatname,DatListe[i])
        else DeleteFile(TempDatname);
    end;
  end;
end;
Die nachfolgende Version ist die "volle". An ihr ist besonders interessant, dass sie nicht nur selbständig ermittelt, ob das JPG überhaupt gedreht werden muss, sondern auch den Zugriff auf weitere GDI-Funktionen bietet. Auch kann außer dem PropertyTagOrientation noch eine Vielzahl von weiteren EXIF-Markern ausgelesen werden.
Delphi-Quellcode:
uses ... GDIPAPI, GDIPOBJ,GDIPUTIL;

procedure DreheMitGDIPlus2;
Var i:integer;
  DatListe:TStringDynArray;
  GPImage: TGPImage;
  PPropItem: PPropertyItem;
  BufferSize: Cardinal;
  Orientation: Byte;
  RotateBy: EncoderValue;
  EncoderCLSID: TGUID;
  EncoderParams: TEncoderParameters;
  EncoderTransformValue:integer;
  Ergebnis : Status;
  TempDatname:string;
const
  Verz = 'C:\Test\';
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 High(DatListe) do begin
    GPImage := TGPImage.Create(DatListe[i]);
    BufferSize := GPImage.GetPropertyItemSize(PropertyTagOrientation);
    If BufferSize > 0 then begin
      GetMem(PPropItem, BufferSize);
      Try
        GPImage.GetPropertyItem(PropertyTagOrientation, BufferSize, PPropItem);
        Orientation := PByte(PPropItem.value)^;
        case Orientation of
          3: RotateBy := EncoderValueTransformRotate180;
          6: RotateBy := EncoderValueTransformRotate90;
          8: RotateBy := EncoderValueTransformRotate270;
          else Continue;
        end;
        If (Orientation in [3,6,9]) then begin
          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;
Danke @Benmik

Das kommt dem sehr nahe was ich gesucht habe.

Jetzt werden die Bilder in Delphi "korrekt" angezeigt nur im Windows-Explorer Vorschau noch falsch ... Weil der Orientation Tag nach der Drehung noch auf dem "alten" Wert steht. Eigentlich müßte er auf "1" zurück gesetzt werden.
Was mich zu meiner nächsten Frage führt :

Wie genau funktioniert
 GPImage.SetPropertyItem(PPropItem^); Speichert die Aufruf schon die entsprechende Information in die Datei ? Oder muss man explizit .Save aufrufen ?
Sorry, für die blöden Fragen, finde leider keine gescheite Doku zu GDI+.

Gretes Data
Der Horizont vieler Menschen ist ein Kreis mit Radius Null, und das nennen sie ihren Standpunkt.
  Mit Zitat antworten Zitat
TiGü

Registriert seit: 6. Apr 2011
Ort: Berlin
3.079 Beiträge
 
Delphi 10.4 Sydney
 
#4

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

  Alt 10. Jul 2020, 18:00
Speichert die Aufruf schon die entsprechende Information in die Datei ? Oder muss man explizit .Save aufrufen ?
Sorry, für die blöden Fragen, finde leider keine gescheite Doku zu GDI+.

Gretes Data
Öh...MSDN?
https://docs.microsoft.com/en-us/win...etpropertyitem
  Mit Zitat antworten Zitat
Benutzerbild von DataCool
DataCool

Registriert seit: 10. Feb 2003
Ort: Lingen
909 Beiträge
 
Delphi 10.3 Rio
 
#5

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

  Alt 10. Jul 2020, 18:02
Speichert die Aufruf schon die entsprechende Information in die Datei ? Oder muss man explizit .Save aufrufen ?
Sorry, für die blöden Fragen, finde leider keine gescheite Doku zu GDI+.

Gretes Data
Öh...MSDN?
https://docs.microsoft.com/en-us/win...etpropertyitem
Jup, manchmal sieht man den Wald vor lauter Bäumen nicht ....
Der Horizont vieler Menschen ist ein Kreis mit Radius Null, und das nennen sie ihren Standpunkt.
  Mit Zitat antworten Zitat
Willie1

Registriert seit: 28. Mai 2008
750 Beiträge
 
Delphi 10.1 Berlin Starter
 
#6

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

  Alt 18. Jul 2020, 18:01
Hallo Leute,
ich habe mir zum Drehen (im Speicher) von Bildern nach dem Orientation-Tag das Folgende ausgedacht.
Delphi-Quellcode:
uses GDIPAPI, GDIPOBJ;
procedure TForm1.Button1Click(Sender: TObject);
var
  GPImage: TGPImage;
  GPGraphics: TGPGraphics;
  pPropItem: PPropertyItem;
  BufferSize: Cardinal;
  Orientation: Byte;
  RotateType: TRotateFlipType;
  W,H: Integer;
  Ratio: Double;
  Exif: Boolean;

begin
  if opd.Execute then begin
    GPImage := TGPImage.Create(opd.FileName);
    try
      Exif:=false;
      BufferSize := GPImage.GetPropertyItemSize(PropertyTagOrientation);
      try
        if BufferSize > 0 then // 0 = kein/e Metadaten oder Orientation-Tag
        begin
          Exif:=true;
          GetMem(pPropItem, BufferSize);
          GPImage.GetPropertyItem(PropertyTagOrientation, BufferSize, pPropItem);
          Orientation := PByte(pPropItem.value)^;
          case Orientation of
            1: RotateType := RotateNoneFlipNone; // Horizontal - No rotation required
            2: RotateType := RotateNoneFlipX;
            3: RotateType := Rotate180FlipNone;
            4: RotateType := Rotate180FlipX;
            5: RotateType := Rotate90FlipX;
            6: RotateType := Rotate90FlipNone;
            7: RotateType := Rotate270FlipX;
            8: RotateType := Rotate270FlipNone;
          else
            RotateType := RotateNoneFlipNone; // Unknown rotation?
          end;
          if RotateType <> RotateNoneFlipNone then
            GPImage.RotateFlip(RotateType);
        end;
        Ratio:=GPImage.GetWidth / img.Width;
        if Ratio < GPImage.GetHeight / img.Height then Ratio:=GPImage.GetHeight / img.Height;
        W:=Round(GPImage.GetWidth / Ratio);
        H:=Round(GPImage.GetHeight / Ratio);
        ClearImage(img);
        try
          GPGraphics:=TGPGraphics.Create(img.Canvas.Handle);
          GPGraphics.DrawImage(GPImage, (img.Width - W) shr 1, (img.Height - H) shr 1, W, H)
        finally
          GPGraphics.Free;
        end;
      finally
        if Exif then FreeMem(pPropItem);
      end;
    finally
      GPImage.Free
    end;
  end;
end;
Dabei war mir ein Beitrag in StackOverflow sehr hilfreich. Ich denke, dass ist eine gute Lösung. Was meint ihr?
Man kann das Bild natürlich auch speichern, dann müsst ihr aber noch das Or.-Tag anpassen und das Thumbnail drehen.
Willie.
Gut hören kann ich schlecht, schlecht sehen kann ich gut - Ersteres stimmt nicht, das zweite schon.

Geändert von Willie1 (18. Jul 2020 um 18:22 Uhr) Grund: Rechtschreibung
  Mit Zitat antworten Zitat
Benmik

Registriert seit: 11. Apr 2009
578 Beiträge
 
Delphi 12 Athens
 
#7

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
Willie1

Registriert seit: 28. Mai 2008
750 Beiträge
 
Delphi 10.1 Berlin Starter
 
#8

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

  Alt 19. Jul 2020, 17:27
Zitat:
. Daher ist Orientation: Byte; falsch
Das ist ein wichtiger Hinweis für mich. Es hätte mir auffallen können, denn BufferSize ist 18 Byte groß. PUInt32 kennt mein Compiler (10.1) (noch) nicht.

Mein Konstrukt soll nur zum automatischen Drehen, entsprechend dem Or.-Tag, dienen. Selbst Windows dreht im Explorer bei rechts/links Drehen nicht das Bild, sondern ändert nur das Or.-Tag.

Für das endgültige Drehen ist dein Entwurf gut, wie ich Or.-Tag anpasse, zeigst du ja. Ich muss nur noch das Thumbnail drehen. In meinem Programm benutze ich in der Vorschau die Thumbnails, wenn vorhanden. Das geht schneller.

Willie.
Gut hören kann ich schlecht, schlecht sehen kann ich gut - Ersteres stimmt nicht, das zweite schon.

Geändert von Willie1 (19. Jul 2020 um 17:42 Uhr)
  Mit Zitat antworten Zitat
Antwort Antwort

Themen-Optionen Thema durchsuchen
Thema durchsuchen:

Erweiterte Suche
Ansicht

Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 13:08 Uhr.
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz