AW: JPG-Datei drehen und speichern -> Verlust der Exif-Daten
Zitat:
https://docs.microsoft.com/en-us/win...etpropertyitem |
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 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:
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. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 23:59 Uhr. |
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