![]() |
AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
[QUOTE=Willie1;1468978]
Zitat:
|
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. |
AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
Lade dir die GDI+ für Delphi herunter.
![]() Da sind Beispieldateien dabei, auch eine, wie du JPGs drehen kannst. Da brauchst du aber Zeit, das machst du nicht an einem Nachmittag. Willie. |
AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
Zitat:
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. |
AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
Zitat:
|
AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
Zitat:
|
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. ![]() Wenn du das Bild dauerhaft drehst, muss auch das Orientation-Tag angepasst werden. Bei GDI+ wird es angepasst. |
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. |
AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
Zitat:
Zitat:
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. |
AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
Zitat:
![]() 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. |
AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
Hallo zusammen
ich werfe hier mal ![]() Kostet zwar was... Ich war immer sehr zufrieden damit. Gruss, Jörn |
AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
Zitat:
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: |
AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
Zitat:
IrfanView weist in den Einstellungen für das Rotationstool auch drauf hin. |
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. |
AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
Zitat:
Zitat:
Zitat:
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 ![]() 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 ![]() Zitat:
|
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:
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.
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;
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; |
AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
Zitat:
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. |
AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
Zitat:
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! |
AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
Zitat:
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). |
AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
Zitat:
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:
Speichert die Aufruf schon die entsprechende Information in die Datei ? Oder muss man explizit .Save aufrufen ?
GPImage.SetPropertyItem(PPropItem^);
Sorry, für die blöden Fragen, finde leider keine gescheite Doku zu GDI+. Gretes Data |
AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
Zitat:
![]() |
AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
Zitat:
|
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:
Dabei war mir ein Beitrag in StackOverflow sehr hilfreich. Ich denke, dass ist eine gute Lösung. Was meint ihr?
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; Man kann das Bild natürlich auch speichern, dann müsst ihr aber noch das Or.-Tag anpassen und das Thumbnail drehen. Willie. |
AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
Irgendwie ist mir das Thema aus den Augen geraten.
Zitat:
Zitat:
Delphi-Quellcode:
falsch und
Orientation: Byte;
Delphi-Quellcode:
dementsprechend auch. Der Wert muss ein UInt32 (Cardinal) sein, und das Auslesen entsprechend
Orientation := PByte(pPropItem.value)^;
Delphi-Quellcode:
. 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
Orientation := PUInt32(pPropItem.value)^;
Delphi-Quellcode:
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.
Swap(PUInt32(pPropItem.value)^)
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:
anfügt. Ich habe keine Möglichkeit gefunden (was nicht heißt, dass es keine gibt), die nicht mit
.Save
Delphi-Quellcode:
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.
EncoderTransformation
Zitat:
![]()
Delphi-Quellcode:
(Nicht vergessen, für's GPImage braucht es auch noch ein
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;
Delphi-Quellcode:
.)
Try
|
AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
Zitat:
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. |
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
![]() Dann habe ich nochmal ![]()
Delphi-Quellcode:
ist also immer verlustbehaftet. Des Weiteren darf
GPImage.RotateFlip(RotateType)
Delphi-Quellcode:
nur ein einziges Objekt in seinem Array haben. Das war aber schon im bisherigen Code richtig drin.
EncoderParameters
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:
verwendet, so dass es kein
IGDPImage
Delphi-Quellcode:
gibt.
Free
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; |
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:
Ich habe nach gesehen, ich denke, so ist es richtig.
Orientation := PWORD(pPropItem.value)^;
Willie. |
AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
Zitat:
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.) |
AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
Delphi-Quellcode:
Bei Orientation ist Type_ : PropertyTagTypeShort = 3 entspricht Word.
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; Ich bin Hobbyprogrammierer, ich kann mich irren. W. |
AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
Zitat:
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. |
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. |
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:
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. |
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. |
AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
Ich habe die
![]() 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:
den Typ von
EncoderTransformValue
Delphi-Quellcode:
zu
Integer
Delphi-Quellcode:
zu ändern, ging es plötzlich. (Das erinnert mich an Sir Rufo: Kaum macht man's richtig, schon funktioniert's!)
NativeInt
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; |
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? |
AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
Hilft dir
![]() |
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; |
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
![]() |
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 00:15 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