Delphi-PRAXiS
Seite 2 von 2     12   

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Multimedia (https://www.delphipraxis.net/16-multimedia/)
-   -   Delphi JPG-Datei drehen und speichern -> Verlust der Exif-Daten (https://www.delphipraxis.net/52770-jpg-datei-drehen-und-speichern-verlust-der-exif-daten.html)

DataCool 6. Jul 2020 17:44

AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
 
[QUOTE=Willie1;1468978]
Zitat:

Frage: wie kann ich Zitat von xxx bekommen?
Rechts unten an jedem Post ist ein Button "Zitat"

himitsu 6. Jul 2020 17:46

AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
 
Das JPEG im delphi behaldet die Exif-Daten nicht, also wenn dort drin steht, dass das Bild gedreht angezeigt werden soll, dann wird es nicht beachtet.
Müsstest du also selber drehen.

Drehen über die Exif-Daten anstatt die Bilddaten direkt zu bearbeiten/verändern ist wirklich verlustfrei, da am Bild nichts geändert wird.

Willie1 6. Jul 2020 17:49

AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
 
Lade dir die GDI+ für Delphi herunter.
http://bilsen.com/gdiplus/index.shtml

Da sind Beispieldateien dabei, auch eine, wie du JPGs drehen kannst. Da brauchst du aber Zeit, das machst du nicht an einem Nachmittag.

Willie.

DataCool 6. Jul 2020 17:53

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

Zitat von himitsu (Beitrag 1468981)
Das JPEG im delphi behaldet die Exif-Daten nicht, also wenn dort drin steht, dass das Bild gedreht angezeigt werden soll, dann wird es nicht beachtet.
Müsstest du also selber drehen.

Drehen über die Exif-Daten anstatt die Bilddaten direkt zu bearbeiten/verändern ist wirklich verlustfrei, da am Bild nichts geändert wird.

Den ersten Teil verstehe ich, das ist ja quasi auch mein Problem.

Der zweite Teil verwirrt mich.
Ich schreibe nachher mein JPEG(Delphi) in die DB und wie Du schon sagtest gehen dabei die EXIF Infos verloren.
Deshalb möchte ich das Bild ja vor Anzeige in TImage drehen.
Ich kann die Bilddaten nicht über Manipulation der EXIF drehen.

DataCool 6. Jul 2020 17:54

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

Zitat von Willie1 (Beitrag 1468983)
Lade dir die GDI+ für Delphi herunter.
http://bilsen.com/gdiplus/index.shtml

Da sind Beispieldateien dabei, auch eine, wie du JPGs drehen kannst. Da brauchst du aber Zeit, das machst du nicht an einem Nachmittag.

Willie.

Da bin ich gerade dran.

DataCool 6. Jul 2020 17:58

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

Zitat von Willie1 (Beitrag 1468983)
Lade dir die GDI+ für Delphi herunter.
http://bilsen.com/gdiplus/index.shtml

Da sind Beispieldateien dabei, auch eine, wie du JPGs drehen kannst. Da brauchst du aber Zeit, das machst du nicht an einem Nachmittag.

Willie.

Aber ich finde keine Bsp.'s weder im Download, noch auf der Website oder bei SourceForge

Willie1 6. Jul 2020 18:13

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

JPEG-Bilder enthalten eine Anweisung, wie sie zur richtigen Anzeige gedreht werden müssen. Z.B. Top left besagt, das Bild braucht nicht gedreht zu werden. http://sylvana.net/jpegcrop/exif_orientation.html

Wenn du das Bild dauerhaft drehst, muss auch das Orientation-Tag angepasst werden. Bei GDI+ wird es angepasst.

Willie1 6. Jul 2020 18:16

AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
 
Bei mir liegt das länger zurück. Das Web ändert sich. Ich werde suchen und melde mich.

Da sind doch Demos dabei! Willie.

himitsu 6. Jul 2020 18:37

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

Zitat von DataCool (Beitrag 1468984)
Der zweite Teil verwirrt mich.

Zitat:

Zitat von Willie1 (Beitrag 1468987)
JPEG-Bilder enthalten eine Anweisung, wie sie zur richtigen Anzeige gedreht werden müssen. Z.B. Top left besagt, das Bild braucht nicht gedreht zu werden. http://sylvana.net/jpegcrop/exif_orientation.html

Jupp, z.B. der Windows-Image-Viewer stellt auch nur die Orientation in den Exif-Daten um, wenn man dort das Bild dreht.
Auch einige Kameras setzen diese Option, über einen Lagesensor.

Wird nur die Option geändert/gesetzt, dann wird erst bei Anzeige das Bild gedreht,
aber die eigentlichen Daten bleiben unverändert, also ist das somit 100% verlustfrei.

Wenn man das Bild "richtig" dreht, dann muß man ja die Pixel umherschieben, womit dann das Bild neu komprimiert/berechnet wird, was natürlich Verluste erzeugt.

Oder man versucht in den komprimierten Daten die Blöcke umzuorganiisieren und in der Codierung gleiche/ähnliche Blöcke in eine Version der gegrehten Ausrichtung zu ersetzen,
falls es da überhaupt möglich ist das 1:1 zu drehen.


Die JPEG-Implementation im Delphi kann das halt nicht.
Also müsstest du dir entweder eine andere Implementation besorgen. (vielleicht kann Windows das bereitstellen, wenn du TWICImage benutzt, oder du spielst mit GDI+ rum)
Oder du mußt selbst die Exif-Daten auswerten und dann das Bild im Canvas drehen, nach dem Laden der Datei.

Benmik 6. Jul 2020 20:44

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

Zitat von himitsu (Beitrag 1468989)
Wenn man das Bild "richtig" dreht, dann muß man ja die Pixel umherschieben, womit dann das Bild neu komprimiert/berechnet wird, was natürlich Verluste erzeugt.

Kann nicht dein Ernst sein, Himitsu. Seit Jahrzehnten ist der Algorithmus bekannt, mit dem man ein JPG verlustfrei drehen kann, sofern dies in 90°-Schritten geschieht (90°, 180° und 270°). Ich habe jetzt auf die Schnelle keine bessere Fundstelle gefunden als diese auf Seite 7. Alle anderen Grade gehen natürlich nicht verlustfrei, vermutlich meintest du das.

Warum der TE nicht eine der von mir genannten Möglichkeiten nimmt, erschließt sich mir nicht, ist aber seine Sache. Natürlich ist EXIFTool unübertroffen, aber für die allermeisten Aufgaben ein völliger Overkill, und man handelt sich ein externes Programm ein. Die einzige Aufgabe, wozu ich bis jetzt an EXIFTool gebunden bin, ist das Auslesen des Objektives. Schlichtes verlustfreies Drehen mache ich seit Jahren mit reinen Delphi-Bordmitteln - und noch einiges mehr.

jsp 7. Jul 2020 06:39

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

ich werfe hier mal https://www.imageen.com/ in die Runde.
Kostet zwar was... Ich war immer sehr zufrieden damit.

Gruss, Jörn

himitsu 7. Jul 2020 07:42

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

Zitat von Benmik (Beitrag 1468993)
Seit Jahrzehnten ist der Algorithmus bekannt,

Ich hatte mal vor 'ner Weile versucht rauszufinden wie die Blöcke codiert sind und hab dHäfte schonwieder vergessen, aber mir ist so als wenn es nicht für alles eine 1:1 Variante in gedrehter Ausrichtung gab,
aber ich darf mich da auch gern getäuscht haben.

Hab den Artikel jetzt auch nur schnell überflogen und schau später nochmal genauer rein, aber selbst im Abschnitt "Results of lossless transcoding" sieht es so aus, als wenn es einen "winzigen" Unterschied gibt, womit des demnach doch nicht ganz "verlustlos" ist. :angle:

Sinspin 7. Jul 2020 08:27

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

Zitat von himitsu (Beitrag 1469004)
Hab den Artikel jetzt auch nur schnell überflogen und schau später nochmal genauer rein, aber selbst im Abschnitt "Results of lossless transcoding" sieht es so aus, als wenn es einen "winzigen" Unterschied gibt, womit des demnach doch nicht ganz "verlustlos" ist. :angle:

Ja, es gibt einen Unterschied. Und zwar wenn die Dimensionen des Bildes nicht durch 8 teilbar sind. Dann sind am Rand Blöcke die nicht mit gedreht werden können. Die werden dann abgeschnitten bzw. weggelassen.
IrfanView weist in den Einstellungen für das Rotationstool auch drauf hin.

Willie1 7. Jul 2020 10:28

AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
 
Ich drehe JPEG's seit Jahren mit GDI+. Das geht schnell, ist verlustfrei und die Meta-Daten bleiben erhalten. Ich programmiere nur in der Windows-Welt.
W.

Benmik 7. Jul 2020 10:29

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

Zitat von Sinspin (Beitrag 1469005)
Die werden dann abgeschnitten bzw. weggelassen.

Kommt auf das Programm an. Es gibt auch welche - weiß jetzt nicht, ob IrfanView dazugehört - die bieten an, entweder den über den 8-Pixel-Block hinausgehenden Rand abzuschneiden oder ihn (und nur ihn) verlustbehaftet mitzuverarbeiten.

Zitat:

Zitat von jsp (Beitrag 1469003)
ich werfe hier mal https://www.imageen.com/ in die Runde.

ImageEn ist ein Hammer, allerdings teuer und meiner Meinung nach in manchen Routinen nicht das Schnellste. Wenn man seinem Programm Bildbearbeitung verpassen will, dann hat man mit ImageEn eine Luxuslösung. ImageEn ist übrigens ein Programm, das die oben genannte Option anbietet.

Zitat:

Zitat von himitsu (Beitrag 1469004)
doch nicht ganz "verlustlos" ist.

Bis auf die 8-Pixel-Grenze lassen sich die Scanlines (das ist jetzt leider sehr laienhaft ausgedrückt) einfach "umsortieren", das ist vollständig verlustfrei. Interessant ist, dass ein so "gedrehtes" JPG nicht mehr die exakt gleiche Größe hat wie das ungedrehte Bild. Es gibt auch irgendwo eine Erklärung, warum das so ist.

Allerdings hat die verlustfreie Drehung unter Umständen ein paar Nebenwirkungen, über die der Arzt oder Apotheker nicht informiert. Wie der TE schon bemerkt hat, geht bei manchen Programmen die EXIF-Sektion verloren. Das sind natürlich keine akzeptablen Programme. Schon etwas tiefergehender ist, dass nach der Drehung die Werte für Breite und Höhe in den EXIF-Daten vertauscht werden müssen; das machen natürlich auch nicht alle. Ganz doll ist, dass manchmal - und zwar auch bei IrfanView - eingebettete Vorschaubilder einfach gekappt werden. Die Sony-Systemkameras zum Beispiel betten in alle JPG nicht nur das "normale" kleine Vorschaubild (Thumbnail auf Deutsch), sondern noch ein weiteres Full HD-Bild von 1920x1080 Pixel ein (die JPEG-Spezifikation erlaubt ja beliebig viele davon). Einmal gedreht - weg isses! Die allermeisten Fotografen ahnen nicht mal, dass überhaupt eins da ist; sie bemerken allenfalls, dass die Datei nach dem (ersten!) Drehen plötzlich viel kleiner ist als vorher und schließen messerscharf, dass die Drehung nicht verlustfrei war. Wer das weiß und die großen Bilder sowieso nicht nutzt (es gibt praktisch kein Programm, das zusätzliche Vorschaubilder anzeigt), dann kann man seine Sony-Bilder durch zweimaliges verlustfreies Drehen hin und zurück platzsparend verkleinern.

Auch noch für Irritation bei vielen Anfänger-Drehern sorgt, dass das Orientation Tag nach jedem beliebigen Drehen immer auf 1 (Top Left) zurückgesetzt wird, auch wenn das Bild zum Beispiel auf hochkant gedreht wird. Mit etwas Nachdenken kommt man darauf, warum das so sein muss.
Für besonders viel Irritation sorgt es, wenn ein Drehprogramm das Orientation Tag auf 1 zurückgesetzt, aber die Werte für Breite und Höhe nicht angepasst hat. Dann zeigt natürlich auch der Explorer das Bild falsch an und dann ist das Rätselraten groß: "Mein Bild wird überhaupt nicht gedreht!".

Und wem man auch noch Beachtung schenken sollte, ist das Dateidatum (d.h. alle drei). Die sollten nach dem Drehen wieder zurückgesetzt werden, eleganterweise auf das EXIF-Datum.

[EDIT] Da mich das Thema - wie gesagt - laufend interessiert, habe ich mal Test mit drei Codevarianten gemacht: NativeJPG, JPEGEX und FreeImage. 10 JPG von durchschnittlich 6 MB wurden um 90° gedreht. Bei allen drei blieben die EXIF-Informationen erhalten. NativeJPG benötigte im Durchschnitt 1.100 msec, die beiden anderen um die 300 für das reine Drehen.

Nebenerkenntnis: Das "Kappen" der eingebetteten Vorschaudatei konnte ich - auch bei IrfanView - nicht (mehr) beobachten. Den Algorithmus des verlustfreien Drehens kann man übrigens in sdJpegLossless.pas studieren.

Zitat:

Zitat von Willie1 (Beitrag 1469011)
Ich drehe JPEGs seit Jahren mit GDI+. Das geht schnell, ist verlustfrei und die Meta-Daten bleiben erhalten.

Wie/womit genau machst du das? Das würde mich interessieren.

Benmik 8. Jul 2020 10:55

AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
 
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;

Willie1 9. Jul 2020 18:10

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

Zitat von Benmik (Beitrag 1468993)
Schlichtes verlustfreies Drehen mache ich seit Jahren mit reinen Delphi-Bordmitteln - und noch einiges mehr.

Hallo, dann gehen die Meta-Daten verloren.
Mir fällt auf, dass beim Drehen mit GDI+ das eingebettete Thumbnail nicht mit gedreht wird und außerdem wird das Orientation-Tag nicht angepasst. Der erste Fehler ist ein Schönheitsfehler, der zweite schon ärgerlich.
Willie.

Willie1 9. Jul 2020 19:09

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

Zitat von Benmik (Beitrag 1468993)
Schlichtes verlustfreies Drehen mache ich seit Jahren mit reinen Delphi-Bordmitteln - und noch einiges mehr.

Hallo, dann gehen die Meta-Daten verloren.
Mir fällt auf, dass beim Drehen mit GDI+ das eingebettete Thumbnail nicht mit gedreht wird und außerdem wird das Orientation-Tag nicht angepasst. Der erste Fehler ist ein Schönheitsfehler, der zweite schon ärgerlich.
Ich weise in meinem Programm darauf hin, dass die Drehung nicht verlustfrei ist, falls sich L/B nicht durch 16 teilen lassen.
Bennik, du hast dir die Antwort schon selbst gegeben, so sieht mein Quelltext in etwa aus.

Willie.

P.S. Ich musste mich neu anmelden und mein ursprünglicher Beitrag war schon gepostet!

TiGü 10. Jul 2020 08:50

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

Zitat von Benmik (Beitrag 1469074)
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).

DataCool 10. Jul 2020 17:35

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

Zitat von Benmik (Beitrag 1469074)
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
Delphi-Quellcode:
 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

TiGü 10. Jul 2020 18:00

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

Zitat von DataCool (Beitrag 1469316)
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

DataCool 10. Jul 2020 18:02

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

Zitat von TiGü (Beitrag 1469319)
Zitat:

Zitat von DataCool (Beitrag 1469316)
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 ....

Willie1 18. Jul 2020 18:01

AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
 
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.

Benmik 18. Jul 2020 22:45

AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
 
Irgendwie ist mir das Thema aus den Augen geraten.
Zitat:

Zitat von TiGü (Beitrag 1469268)
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.

Zitat:

Zitat von Willie1 (Beitrag 1469846)
...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
Delphi-Quellcode:
Orientation: Byte;
falsch und
Delphi-Quellcode:
Orientation := PByte(pPropItem.value)^;
dementsprechend auch. Der Wert muss ein UInt32 (Cardinal) sein, und das Auslesen entsprechend
Delphi-Quellcode:
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
Delphi-Quellcode:
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
Delphi-Quellcode:
.Save
anfügt. Ich habe keine Möglichkeit gefunden (was nicht heißt, dass es keine gibt), die nicht mit
Delphi-Quellcode:
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.

Zitat:

Zitat von DataCool (Beitrag 1469316)
Wie genau funktioniert
Delphi-Quellcode:
 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
Delphi-Quellcode:
Try
.)

Willie1 19. Jul 2020 17:27

AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
 
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.

Benmik 19. Jul 2020 22:20

AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
 
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
Delphi-Quellcode:
GPImage.RotateFlip(RotateType)
ist also immer verlustbehaftet. Des Weiteren darf
Delphi-Quellcode:
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
Delphi-Quellcode:
IGDPImage
verwendet, so dass es kein
Delphi-Quellcode:
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;

Willie1 20. Jul 2020 10:37

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

Ich will die Bilder zunächst nur zum Ansehen drehen, das geht mit RotateFlip. Nicht zum endgültigem Drehen. Dazu ist dein Source sehr hilfreich.

Delphi-Quellcode:
Orientation := PWORD(pPropItem.value)^;
Ich habe nach gesehen, ich denke, so ist es richtig.

Willie.

Benmik 20. Jul 2020 10:55

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

Zitat von Willie1 (Beitrag 1469903)
Delphi-Quellcode:
Orientation := PWORD(pPropItem.value)^;
Ich habe nach gesehen, ich denke, so ist es richtig.

Das kann nur bei Little Endian klappen, weil es eben 4 Bytes sind, Word sind nur 2; dann würde aber sogar PByte reichen. Kann kaum glauben, dass es bei dir kein PUInt32 geben soll, aber probiers doch mal mit PCardinal, das ist doch das Gleiche. Notfalls typisier es doch einfach selbst.
Ich habe hier JPG sowohl mit Intel als auch mit Motorola; ich probiere später mal aus, wo es einen Unterschied macht und wo nicht. Mir scheint, GDI+ ist wie VBA, dass dort einiges hinter den Kulissen ausgeglichen wird; wenn du die Werte direkt ausliest (daran arbeite ich gerade), dann führt kein Weg an der Beachtung der Endianess vorbei.

EDIT: Wie ich es mir schon dachte, Microsoft ändert alle Angaben VBA-mäßig zu Intel (Little Endian), egal was im EXIF steht. Das betrifft nicht nur das Orientation Tag, sondern vermutlich alle. Man kommt also im Falle des Orientation Tags mit PByte(pPropItem.value) aus.

(Ist bei euch die Formatierung von meinem Beitrag oben auch neben der Spur? Ich habe zigmal versucht, das zu ändern, ohne Erfolg.)

Willie1 20. Jul 2020 11:19

AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
 
Delphi-Quellcode:
  PropertyItem = record // NOT PACKED !!
    id      : PROPID; // ID of this property
    length  : ULONG;  // Length of the property value, in bytes
    type_    : WORD;   // Type of the value, as one of TAG_TYPE_XXX
    value   : Pointer; // property value
  end;
  TPropertyItem = PropertyItem;
  PPropertyItem = ^TPropertyItem;
Bei Orientation ist Type_ : PropertyTagTypeShort = 3 entspricht Word.

Ich bin Hobbyprogrammierer, ich kann mich irren. W.

Benmik 20. Jul 2020 14:08

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

Zitat von Willie1 (Beitrag 1469905)
Ich bin Hobbyprogrammierer, ich kann mich irren.

Das kenn ich!

Du hast recht, das hat der gute Boian Mitov geändert. Im Original-EXIF haben alle Einträge eine 12-Byte-Struktur, wovon die letzten 4 Bytes die Werte (oder den Offset) beinhalten. Vermutlich gibt es keine numerischen Werte, die die Grenzen eines Word überschreiten.

Eins muss man in der Unit IGDIPlus noch ändern: Ist ein Tag nicht vorhanden, dann gibt es eine Warnung. Das kann man bei 3.000 JPGs nicht wollen.

Willie1 20. Jul 2020 16:08

AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
 
Hallo Bennik,
den Umstieg auf die neue GDI+ Bibliothek kann ich mit meinen Augen nicht leisten. Die Variablennamen sind alle anders, dass schaffe ich nicht! Ich muss beim alten GDI+ bleiben.

PGPPropertyItem erkennt mein Compiler nicht, obwohl ich GDIPlus und GDPlusHeelpers eingebunden habe.

Dein Programm müsste ich wieder zurück anpassen.

Benmik 20. Jul 2020 19:59

AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
 
Das tut mir leid, dass du so Probleme mit deinen Augen hast. Du musst ja auch keineswegs umsteigen. Die GDI+-Bibliothek bleibt ja gleich, es ist nur der Delphi-Wrapper, der bei Mitov etwas moderner und bequemer ist.

Zitat:

Zitat von Willie1 (Beitrag 1469932)
PGPPropertyItem erkennt mein Compiler nicht...

Das liegt an der fehlenden Einbindung von IGDIPlus.pas von Mitov. Den Link hatte ich ja angegeben.

Kennst du das Tastenkürzel STRG + SHIFT + E für das Refaktorisieren von Bezeichnern aller Art? Damit erleichtert man sich die Arbeit. Aber wie gesagt, wenn du bei der "alten" Art bleibst, verlierst du nichts an Funktionalität oder Geschwindigkeit.

Willie1 21. Jul 2020 15:39

AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
 
Ich werde mich einarbeiten.
Da ich meine Bilder grundsätzlich nicht drehe und inzwischen fast alle Programme das Drehen nach dem Or.-Tag ausführen, vermisse ich das richtige Drehen nicht.
Ich werde es mit RotateFlip machen. Nur zur Anzeige reicht das.
W.

Benmik 31. Jul 2020 17:36

AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
 
Ich habe die Prozedur zum Drehen von JPG an zwei Stellen verbessert.

Zum einen ist es das Auslesen des EXIF-Datums, das jetzt ordentlich geschieht.

Zum anderen habe ich bemerkt, dass der Code bei 64-Bit-Apps nicht funktionierte. Das war mir zunächst ein Rätsel, da es keinerlei Fehlermeldung gab, die DLL ganz offensichtlich korrekt angesteuert und auch die Datei verlustfrei neu gespeichert wurde; nur gedreht wurde sie nicht! Erst als ich auf die Idee kam, bei
Delphi-Quellcode:
EncoderTransformValue
den Typ von
Delphi-Quellcode:
Integer
zu
Delphi-Quellcode:
NativeInt
zu ändern, ging es plötzlich. (Das erinnert mich an Sir Rufo: Kaum macht man's richtig, schon funktioniert's!)
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:NativeInt; // NICHT integer, sonst keine Funktion bei 64-Bit-Apps!
  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 i:Cardinal; DateOrig: array of Byte; TxtEXIFDatum:string;
  begin
    IGDPImage.GetPropertyItem(GPPropertyTagDateTime, DateBufferSize, PPropDItem);
    SetLength(DateOrig,DateBufferSize);
    Move(PPropDItem.Value,DateOrig[1],DateBufferSize);
    // DateBufferSize ist immer deutlich größer als die 19 Bytes des Datumeintrags. Die Byteanzahl vor dem Eintrag ist nicht konstant.
    i := 0;
    While i < (DateBufferSize - PPropDItem.Length) do begin
      // Format des Datumseintrags ist JJJJ:MM:TT HH:MM:SS - die 4 Doppelpunkte stehen in normiertem Abstand zueinander
      If (DateOrig[i] = $3A) and (DateOrig[i + 3] = $3A) and (DateOrig[i + 9] = $3A) and (DateOrig[i + 12] = $3A)
        then break;
      Inc(i);
    end;
    TxtEXIFDatum := Copy(TEncoding.ASCII.GetString(DateOrig),i - 3,19);
    Try
      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);
    Except
      Result := 0;
    End;
  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;

MartinK 20. Aug 2021 16:29

AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
 
Da sind einige sehr wertvolle Infos für mich in diesem Thread.

Ich bräuchte noch mindestens ein weiteres Feature und tue mich leider mit dem interpretieren der IGDIPlus.pas leider schwer.

Ich möchte vor dem speichern mit "IGDPImage.Save" gerne noch die Kompressionsrate/Qualität angeben.
Hat hierzu jemand weiter Infos oder kann mir einen Tipp geben?

Benmik 22. Aug 2021 00:01

AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
 
Hilft dir das hier weiter?

MartinK 22. Aug 2021 09:47

AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
 
Herzlichen Dank Benmik. Das hilft mir schon um einiges weiter.
Ein Problem gelöst .... nächstes aufgetaucht ;)

Die Encoder Parameter sind nur ein Array von 0..0

TIGPEncoderParameters = packed record
Count : UINT; // Number of parameters in this structure
Parameter : array[0..0] of TIGPEncoderParameter; // Parameter values
end;

Wenn ich jetzt mehrere Parameter in einem Rutsch definieren will, also zB "Orientierung" und "SaveQuality" geht das entsprechend nicht.
Oder irre ich mich da?

Finde es auch seltsam das es extra so etwas gibt wie ...
EncoderParameters.Count := 1;

MartinK 22. Aug 2021 10:08

AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
 
und falls sich noch jemand anders ausser mir ebenfalls schwertut die selbstverfasste Wiki des Autors in den Weiten des WWW zu finden

http://www.mitov.com/wiki/index.php?...IPlus.IGPImage

Benmik 22. Aug 2021 20:18

AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
 
Leider bin ich zurzeit nur beschränkt aktionsfähig; Internet z.B. nur über Handy, daher kann ich nur eingeschränkt Auskunft geben.

Denke daran, dass Mitov und Konsorten nur Wrapper für GDIPlus sind; bei Fragen kannst du also auch bei MSN nachschauen. Ich hatte damals dermaßen viele Wege ausprobiert, dass ich selbst erst nachschauen musste, für welche Variante ich mich eigentlich entschieden hatte. Wie ich jetzt sehe, ist es GdiPlus von Erik van Bilsen geworden (2009), allerdings modifiziert; ich habe dieses furchtbare GdiCheck, das bei der geringsten Kleinigkeit eine Exception ausgelöst hat, durch die Rückgabe des Status ersetzt (wie im Original von Microsoft). Warum Eric und nicht mehr Mitov, weiß ich nicht mehr.

Bei van Bilsen sehe ich, dass Add eine Menge von Überladungen hat; bei einer kann als Parameter auch ein Array von Parametern übergeben werden; das Argument ist dann ein Pointer auf das erste Arrayitem. Das müsste bei Mitov genauso sein.


Alle Zeitangaben in WEZ +1. Es ist jetzt 11:03 Uhr.
Seite 2 von 2     12   

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz