Autor Beitrag
hRb
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 292
Erhaltene Danke: 12



BeitragVerfasst: So 14.09.25 21:35 
Man sollte meinen, dass nach vielen Jahren Compilerentwicklung die unterschiedlichen jpg-Formate korrekt erkannt werden. Dies scheint bei der Vielzahl der Kamerahersteller und vielfältigen Möglichkeiten wie jpg-Header aussehen können nicht der Fall.
Ich habe ein Programm mit dem Bilder angezeigt werden (Diabetrachter). In aller Regel jpg-Dateien.
Nun erhalte ich Bilder, die mit einem iPhone aufgenommen wurden und muss feststellen, dass hochkant-fotografierte Bilder nicht automatisch gedreht werden.
Mein Code:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.Grids,
  Vcl.Imaging.jpeg;


ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
procedure TForm4.FormShow(Sender: TObject);
var jpg: TJPEGImage;
    FiName: string//Pathname
    breite,hoehe: Integer;
Begin
... // Pathnamen, die in einer StringGridliste stehen, werden in FiName geladen ( mit Taste ">" vorwärts, ">" rückwärts )
... // In Form4 (Windowsstate=wsMaximized) ist ein Objekt TImage alClient plaziert. 
... // Form4 alternativ mit oder Ohne Menü-Rand 

  Form4.Image1.Picture.LoadFromFile(FiName);  // jpg-Datei
end;

procedure TForm1.GrossbildStartMitRandClick(Sender: TObject);
begin // Bildschirm Normal mit Menü 
  // if bell then DelAlarm;
  Form4.BorderStyle := bsSizeable;
  Form4.FormStyle := fsNormal;
  Form4.Windowstate := wsMaximized;
  // SetBounds(oldX, oldY, oldW, oldH);
  Form4.Show;
  Form4.SetFocus;
end;

procedure TForm1.GrossbildStartOhneRandClick(Sender: TObject);
begin // Bildschirm VollBild 
  // if bell then DelAlarm;
  Form4.BorderStyle := bsNone;
  Form4.FormStyle := fsStayOnTop;
  Form4.BoundsRect := Monitor.BoundsRect;
  Form4.Show;
  Form4.SetFocus;
end;

Leider bringt mich die Bild-Vorababfrage nach Höhe und Breite auch nicht weiter, denn selbst bei Hochkant-Foto ist Breite > Hoehe. Auf Exit-Analyse habe ich jedoch keine Lust.
Frage: wer kann mir Code liefern, der bei allen Kameras/Handys funktioniert.
PS: arbeite mit Delphi RAD 12 (vor 3 Tagen installiert)
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4799
Erhaltene Danke: 1059

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Mo 15.09.25 10:49 
user profile iconhRb hat folgendes geschrieben Zum zitierten Posting springen:
Auf Exit-Analyse habe ich jedoch keine Lust.

Du meinst sicherlich: Exif ;- )

Ohne diese wirst du aber nicht auskommen, da die Orientierung dort gespeichert ist, s. z.B. die Erklärung in JPEG Image Orientation and Exif.

PS: Dein 2. Thema habe ich gelöscht (anscheinend wolltest du editieren) und das neuere hier belassen.
hRb Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 292
Erhaltene Danke: 12



BeitragVerfasst: Mo 15.09.25 12:23 
Sorry, ja natürlich Exif!
Zitat:
Ohne diese wirst du aber nicht auskommen, da die Orientierung dort gespeichert ist

Aber muss denn jeder das Rad neu erfinden? Gibt es keine fertige Routine die besser ist als LoadfromFile?. Meine Canon-Bilder werden übrigens korrekt automatisch gedreht. Die Beschäftigung mit dem jpg-Header zeigt mir eine große Spielwiese für Hersteller. Ich will doch "nur" ein Bild anzeigen", nicht bearbeiten, nicht speichern. Das sollte doch im Standard sein.
Übrigens: es gibt kaum ein Codebeispiel das ich nicht versucht habe. Keines kann ich fehlerfrei compilieren. Entweder unbekannter Typ, keine Angabe zur uses, Lazerus-Compiler notwendig, etc...
Natürlich kenne ich Irfan und verwende ihn auch selbst. Leider wird heute oft nicht mehr fotografiert, sondern geknippst. Eine Bilderflut und selten wird gelöscht. Der Vorteil meines Bildbetrachters ist: er lädt die Bilder eines oder mehrerer Verzeichnisse in eine Stringgridliste und parallel in ein Listview. Dort kann - wenn gewünscht - sortiert werden und im Stringgrid wird markiert was später als Großbild angezeigt werden soll. Alles Bestens, selbst die iPhonebilder in der Vorschau - nur eben nicht mit Loadfromfile(nnn.jpg). Bin ich der Einzige der Bilder anzeigen will?
Gausi
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 8549
Erhaltene Danke: 478

Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
BeitragVerfasst: Di 16.09.25 12:13 
Der Punkt ist ja, dass man ein Bild im JPEG-Format auf zwei Arten speichern kann, und beides hat Vor- und Nachteile.

Einige Hersteller drehen die Bildinformationen direkt, wenn der Anwender die Kamera um 90° dreht. Andere schreiben diese Information nur in die Exif-Daten, so dass ein Bildbetrachter entscheiden kann, ob das Bild "original" oder "gedreht" angezeigt werden soll.

Die in Delphi eingebaute Funktion TGraphic.LoadfromFile (bzw. die Ableitung in TJpegImage)) scheint die Exif-Daten zu ignorieren und lädt das Bild so, wie es die Kamera abgespeichert hat. Wenn man Jpeg-spezifische Dinge unterstützen möchte, muss man das halt selber machen. Gibt es auch bestimmt schon irgendwo.

Die Exif-Daten auslesen sollte keine Zauberei sein. Aufpassen muss man ggf. wenn man das gedrehte Bild nicht nur anzeigen, sondern auch speichern möchte. Das sollte nämlich ohne den Umweg über TBitmap o.ä. gehen, sondern direkt auf den JPEG-Daten, damit das verlustfrei abläuft. Das ist wohl zumindest dann möglich, wenn die Ausmaße des Bildes ein Vielfaches von 8 oder 16 sind.

Ein weiterer Vorteil, wenn du Exif-Daten benutzen würdest: Soweit ich weiß können darin auch Thumbnails enthalten sein. Wenn man diese für die Übersichts-Liste nutzt, dürfte das die Performance deines Betrachters deutlich steigern - denn dann müssen nicht dutzende Megapixel-Bilder geladen werden, sondern nur die kleinen Vorschaubildchen. ;-)

_________________
We are, we were and will not be.

Für diesen Beitrag haben gedankt: Th69
hRb Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 292
Erhaltene Danke: 12



BeitragVerfasst: Mi 17.09.25 02:05 
Ok, bin ja schon seit einiger Zeit dran Exif-Daten zu lesen und auszuwerten.
1. Th69 hat einen Link geschickt, der zu einem Codebeispiel führt. Funktioniert aber nicht und scheint uralt zu sein. Mit BlockRead werden immer 12 Byte gelesen. Sehe ich mir jpg-Bilder im Hex-Editer an, ergibt dies überhaupt keinen Sinn. Ich hänge den Code mal unten an. Je nach Kamera sind die Blöcke unterschiedlich lang, d.h. man muss die genaue Header-Struktur kennen. Wo gibts eine übersichtliche Beschreibung?
2. Gibt es in RAD 12 keine uses z.B. Vcl.Exif um Info zu lesen?
3. Im iPhone-Bild steht an der Stelle wo üblich Exif steht JFIF. Scheint auch eine Variante der Jpeg-Norm zu sein.
Möchte ja schon die Exif-Daten auswerten, hoffte nur auf einige fertige Routinen um die Info-Daten zu lesen. (nochmals: nicht schreiben). Alles sehr komplex für mich.
Gibt es noch etwas was ich noch nicht kenne? Suche schon lange und finde nirgends brauchbaren Code.
Danke für Geduld. hRb

Exif-Unit-Code von einem unbekannten Japaner

ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
  Exif;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    OpenDialog1: TOpenDialog;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;
  Daten: Texif;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);

begin
  Daten:= TExif.Create;
  Memo1.Clear;
  opendialog1.Execute;
  Daten.ReadFromFile(opendialog1.FileName);
  Memo1.Lines.Add('fertig');
end;

end.


//========================
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
199:
200:
201:
202:
203:
204:
205:
206:
207:
208:
209:
210:
211:
212:
213:
214:
215:
216:
217:
218:
219:
220:
221:
222:
223:
224:
225:
226:
227:
228:
229:
230:
231:
232:
233:
234:
235:
236:
237:
238:
239:
240:
unit Exif;

interface

uses
  Classes, SysUtils;

type
  TExif = class(TObject)
    private
      FImageDesc          : String;     //Picture description
      FMake               : String;     //Camera manufacturer
      FModel              : String;     //Camere model
      FOrientation        : Byte;       //Image orientation - 1 normal
      FOrientationDesk    : String;     //Image orientation description
      FCopyright          : String;     //Copyright
      FValid              : Boolean;    //Has valid Exif header
      FDateTime           : String;     //Date and Time of Change
      FDateTimeOriginal   : String;     //Original Date and Time
      FDateTimeDigitized  : String;     //Camshot Date and Time
      FUserComments       : String;     //User Comments

      f                   : File;
      idfp                : Cardinal;
      function ReadValue(const Offset, Count: Cardinal): String;
      procedure Init;
    public
      constructor Create;
      procedure ReadFromFile(const FileName: AnsiString);

      property ImageDesc: String read FImageDesc;
      property Make: String read FMake;
      property Model: String read FModel;
      property Orientation: Byte read FOrientation;
      property OrientationDesk: String read FOrientationDesk;
      property Copyright: String read FCopyright;
      property Valid: Boolean read FValid;
      property DateTime: String read FDateTime;
      property DateTimeOriginal: String read FDateTimeOriginal;
      property DateTimeDigitized: String read FDateTimeDigitized;
      property UserComments: String read FUserComments;
  end;

implementation
uses unit1;

type
  TMarker = record
    Marker   : Word;      //Section marker
    Len      : Word;      //Length Section
    Indefin  : Array [0..4of Char; //Indefiner - "Exif" 00, "JFIF" 00 and ets
    Pad      : Char;      //0x00
  end;

  TTag = record
    TagID   : Word;       //Tag number
    TagType : Word;       //Type tag
    Count   : Cardinal;   //tag length
    OffSet  : Cardinal;   //Offset / Value
  end;

  TIFDHeader = record
    pad          : Byte; //00h
    ByteOrder    : Word; //II (4D4D)
    i42          : Word; //2A00
    IFD0offSet   : Cardinal; //0th offset IFD
    Interoperabil: Byte;
  end;

function TExif.ReadValue(const Offset, Count: Cardinal): String;
var fp: LongInt;
     i: Word;
begin
  SetLength(Result,Count);
  fp:=FilePos(f); //Save file offset
  Seek(f, Offset);
  try
    i:=1;
    repeat
      BlockRead(f,Result[i],1);
      inc(i);
    until (i>=Count) or (Result[i-1]=#0);
    if i<=Count then Result:=Copy(Result,1,i-1);
  except
    Result:='';
  end;
 Result:=TrimRight(Result);
 Form1.Memo1.Lines.Add(Result);
  Seek(f,fp);     //Restore file offset, Dateioffset wiederherstellen
end;

procedure TExif.Init;
begin

  idfp:=0;

  FImageDesc:='';
  FMake:='';
  FModel:='';
  FOrientation:=1;
  FOrientationDesk:='Normal';
  FDateTime:='';
  FCopyright:='';
  FValid:=False;
  FDateTimeOriginal:='';
  FDateTimeDigitized:='';
  FUserComments:='';
end;

constructor TExif.Create;
begin
  Init;
end;

procedure TExif.ReadFromFile(const FileName: AnsiString);
const ori: Array[1..8of String=
          ('Normal','Mirrored',
           'Rotated 180','Rotated 180, mirrored',
           'Rotated 90 left, mirrored',
           'Rotated 90 right',
           'Rotated 90 right, mirrored',
           'Rotated 90 left');
var j: TMarker;
  idf: TIFDHeader;
 off0: Cardinal; //Null Exif Offset
  tag: TTag;
    i: Integer;
  SOI: Word; //2 bytes SOI marker. FF D8 (Start Of Image)

begin
  if not FileExists(FileName) then exit;
  Init;

  AssignFile(f,FileName);
  reset(f,1);

  BlockRead(f,SOI,2);
  if SOI=$D8FF then
  begin //Is this Jpeg
    BlockRead(f,j,9);

    if j.Marker=$E0FF then begin //JFIF Marker Found
      Seek(f,20); //Skip JFIF Header
      BlockRead(f,j,9);
    end;

    if j.Marker=$E1FF then
    begin //If we found Exif Section. j.Indefin='Exif'.
      FValid:=True;         //Wenn wir den Exif-Abschnitt gefunden haben. j.Indefin='Exif'
      off0:=FilePos(f)+1;   //0'th offset Exif header
      BlockRead(f,idf,11);  //Read IDF Header

      i:=0;
      repeat
        inc(i);
        BlockRead(f,tag,12);
        //0E01 ImageDescription
        if tag.TagID=$010E
          then FImageDesc:=ReadValue(tag.OffSet+off0,tag.Count);
        //0F01 Make
        if tag.TagID=$010F
          then FMake:=ReadValue(tag.OffSet+off0,tag.Count);
        //1001 Model
        if tag.TagID=$0110
          then FModel:=ReadValue(tag.OffSet+off0,tag.Count);
        //6987 Exif IFD Pointer
        if tag.TagID=$8769
          then idfp:=Tag.OffSet; //Read Exif IDF offset
        //1201 Orientation
        if tag.TagID=$0112 then
        begin
           FOrientation:=tag.OffSet;
           if tag.OffSet in [1..8then
             FOrientationDesk:=ori[tag.OffSet]
             else FOrientationDesk:='Unknown';
        end;
        //3201 DateTime
        if tag.TagID=$0132
          then FDateTime:=ReadValue(tag.OffSet+off0,tag.Count);
        //9882 CopyRight
        if tag.TagID=$8298
          then FCopyright:=ReadValue(tag.OffSet+off0,tag.Count);
      until (i>11);

      if idfp>0 then begin
        Seek(f,idfp+12+2);//12 - Size header before Exif, 2 - size Exif IFD Number
        i:=0;
        repeat
          inc(i);
          BlockRead(f,tag,12);
  {
          You may simple realize read this info:
          Sie können diese Informationen ganz einfach lesen:

          tag |Name of tag

          9A82 ExposureTime
          9D82 FNumber
          0090 ExifVersion
          0390 DateTimeOriginal
          0490 DateTimeDigitized
          0191 ComponentsConfiguration
          0292 CompressedBitsPerPixel
          0192 ShutterSpeedValue
          0292 ApertureValue
          0392 BrightnessValue
          0492 ExposureBiasValue
          0592 MaxApertureRatioValue
          0692 SubjectDistance
          0792 MeteringMode
          0892 LightSource
          0992 Flash
          0A92 FocalLength
          8692 UserComments
          9092 SubSecTime
          9192 SubSecTimeOriginal
          9292 SubSecTimeDigitized
          A000 FlashPixVersion
          A001 Colorspace
          A002 Pixel X Dimension
          A003 Pixel Y Dimension
  }


          //0390 FDateTimeOriginal
          if tag.TagID=$9003
            then FDateTimeOriginal:=ReadValue(tag.OffSet+off0,tag.Count);
          //0490 DateTimeDigitized
          if tag.TagID=$9004
            then FDateTimeDigitized:=ReadValue(tag.OffSet+off0,tag.Count);
          //8692 UserComments
          if tag.TagID=$9286
            then FUserComments:=ReadValue(tag.OffSet+off0,tag.Count);
        until (i>23);
      end;
    end;
  end;
  CloseFile(f);
end;

end.
hRb Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 292
Erhaltene Danke: 12



BeitragVerfasst: Mi 17.09.25 02:10 
Zitat:
Die Exif-Daten auslesen sollte keine Zauberei sein.

Wie aus dem Text ersichtlich: Für mich schon.
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19322
Erhaltene Danke: 1749

W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Mi 17.09.25 07:00 
Wie wäre es denn mit dieser Bibliothek?
github.com/sasgis/ccr-exif