![]() |
Delphi-Version: 5
GetJPGSize Funktion (wollen wir sie verbessern?)
Hallo
folgender Code gibt's im Internet. Gerne würde ich sie verbessern und genauer verstehen. So manches ist mir nicht ganz klar. Außerdem habe ich eine Prüfung mehr reingemacht, da ich ein paar JPGs habe, die auf "$C2" (siehe Code) hören. Hier mal der Code
Delphi-Quellcode:
Mein "grübeln" nun.
procedure GetJPGSize(const sFile: string; var wWidth, wHeight: word);
const ValidSig : array[0..1] of byte = ($FF, $D8); Parameterless = [$01, $D0, $D1, $D2, $D3, $D4, $D5, $D6, $D7]; var Sig: array[0..1] of byte; f: TFileStream; x: integer; Seg: byte; Dummy: array[0..15] of byte; Len: word; ReadLen: LongInt; begin FillChar(Sig, SizeOf(Sig), #0); f := TFileStream.Create(sFile, fmOpenRead); try ReadLen := f.Read(Sig[0], SizeOf(Sig)); for x := Low(Sig) to High(Sig) do if Sig[x] <> ValidSig[x] then ReadLen := 0; if ReadLen > 0 then begin ReadLen := f.Read(Seg, 1); while (Seg = $FF) and (ReadLen > 0) do begin ReadLen := f.Read(Seg, 1); if Seg <> $FF then begin if (Seg = $C0) or (Seg = $C1) or (Seg = $C2) then // $C2 von mir dazugemacht. begin ReadLen := f.Read(Dummy[0], 3); { don't need these bytes } wHeight := ReadMWord(f); wWidth := ReadMWord(f); end else begin if not (Seg in Parameterless) then begin Len := ReadMWord(f); f.Seek(Len - 2, 1); f.Read(Seg, 1); end else Seg := $FF; { Fake it to keep looping. } end; end; end; end; finally f.Free; end; end; Nach dem "Begin" wird FillChar genutzt. Wäre das hier nicht überflüssig? Nach dem "try" ist folgende Zeile angegeben:
Code:
Da ich hier die Größe nicht ändere, würde ich kein SizeOf nutzen und auch die Variable selbst ganz normal angeben. Also ohne "[0]". Also ganz normal "...Read(Sig,2);". Die Ergebnisse sind gleich. Warum macht man hier die Angabe "[0]"? Weiter im Code findet man z.B. "f.Read(Dummy[0], 3);". Das verwirrt mich jetzt ganz. Was wird denn gelesen? 3 Bytes? Warum hat hier dann "Dummy" [0..15] Of Byte" ?. Dort im Kommentar steht außerdem, dass diese Werte nicht benötigt werden. Wieso geht man dann nicht mit TFileStream.Position oder mit Seek zu der neuen stelle im Header?
ReadLen := f.Read(Sig[0], SizeOf(Sig));
Auch würde ich (da sich dies ja wohl nicht mehr ändert) die "ValidSig" und "Parameterless" Werte direkt angeben. Was würdet ihr denn so verbessern? |
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Nein, FillChar braucht man da nicht, weil Sig danach ohnehin beschrieben wird.
Mir fallen aber noch z.B. diese Punkte auf:
|
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Zitat:
|
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
@jaenicke … das mit MMF ist mir noch ganz unbekannt. Las eben nur, dass es hier Sinn macht, wenn man große Daten liest.
Ich vergas noch die ReadMWord Funktion, die dabei war.
Delphi-Quellcode:
Eine extra Funktion wollte ich nicht und habe mir was zusammengebastelt. Was nun schneller oder langsamer ist, weiß ich jetzt nicht.
function ReadMWord(f: TFileStream): Word;
type TMotorolaWord = record case Byte of 0: (Value: Word); 1: (Byte1, Byte2: Byte); end; var MW: TMotorolaWord; begin { It would probably be better to just read these two bytes in normally } { and then do a small ASM routine to swap them. But we aren't talking } { about reading entire files, so I doubt the performance gain would be } { worth the trouble. } f.read(MW.Byte2, SizeOf(Byte)); f.read(MW.Byte1, SizeOf(Byte)); Result := MW.Value; end; Mir ist die JPG Header Struktur sowieso noch nicht ganz klar. Es gibt da einige unterschiede. Meine derzeitige Prozedur (siehe unten) scheint gut zu funktionieren. Mache mir nur noch Gedanken um den "While" Block. Nicht das hier (z.B. bei defekten JPG Dateien) einfach zu viel (wäre langsam) in der Datei gesucht wird und die Bildgröße gar nicht ermittelt (wäre dann nicht so schlimm) werden kann.
Delphi-Quellcode:
Procedure GetJPGSize(sFile: String; Out wWidth, wHeight: Word);
Var FS: TFileStream; BD: Byte; WD : Word; RL: LongInt; HW : Array[0..3] Of Byte; LE : Array[0..1] Of Byte; Begin sFile := '\\?\'+SFile; wWidth := 0; wHeight := 0; FS := TFileStream.Create(sFile, fmShareDenyNone); try RL := FS.Read(WD, 2); If (Lo(WD) <> $FF) And (Hi(WD) <> $D8) Then RL := 0; If RL > 0 Then Begin RL := FS.Read(BD, 1); While (BD = $FF) and (RL > 0) Do Begin RL := FS.Read(BD, 1); If BD <> $FF Then Begin If BD In [$C0,$C1,$C2] Then Begin FS.Seek(3,1); FS.Read(HW,4); wHeight := HW[0] Shl 8 + HW[1]; wWidth := HW[2] Shl 8 + HW[3]; End Else Begin If Not (BD In [$01, $D0, $D1, $D2, $D3, $D4, $D5, $D6, $D7]) Then Begin FS.Read(Le,2); WD := Le[0] Shl 8 + Le[1]; FS.Seek(WD - 2, 1); FS.Read(BD, 1); End Else BD := $FF; End; End; End; End; Finally FS.Free; End; End; |
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Zitat:
Nur ein Delphi-Profi kann den Code lesen, versteht jedoch m.E. immer noch nicht warum da was passiert. |
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Vorallem die Magic Bytes sollte man kommentieren, damit man weiß, was da wie und warum geprüft wird.
|
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Bei dem was ich auf die schnelle recherchiert habe kommt ein fast gleiches Snippet bei raus.
Wie Luckie bereits schrieb, den Magic Bytes jede Menge beachtung schenken, weswegen dieses Snippet noch ausbaufähig ist. Geschwindigkeit scheint mir mit beiden Versuchen auf gleicher Höhe zu liegen.
Delphi-Quellcode:
Quelle:
function GetJpegSize(const FileName: string): TPoint;
var fs: TFileStream; SegmentPos: Integer; SOIcount: Integer; x, y: word; b: byte; begin fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone); try fs.Position := 0; fs.Read(x, 2); if x <> $D8FF then raise Exception.Create('Not a Jpeg file'); SOIcount := 0; fs.Position := 0; while fs.Position + 7 < fs.Size do begin fs.Read(b, 1); if b = $FF then begin fs.Read(b, 1); if b = $D8 then Inc(SOIcount); if b = $DA then Break; end; end; if b <> $DA then raise Exception.Create('Corrupt Jpeg file'); SegmentPos := -1; fs.Position := 0; while fs.Position + 7 < fs.Size do begin fs.Read(b, 1); if b = $FF then begin fs.Read(b, 1); if b in [$C0, $C1, $C2] then begin SegmentPos := fs.Position; Dec(SOIcount); if SOIcount = 0 then Break; end; end; end; if SegmentPos = -1 then raise Exception.Create('Corrupt Jpeg file'); if fs.Position + 7 > fs.Size then raise Exception.Create('Corrupt Jpeg file'); fs.Position := SegmentPos + 3; fs.Read(y, 2); fs.Read(x, 2); Result := Point(Swap(x), Swap(y)); finally fs.Free; end; end; ![]() |
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Ich habe sie mal getestet.
Genau geschaut habe ich noch nicht aber ich habe jetzt eine JPG Datei mit falschen Abmessungen (256x171 anstatt 7360x4912) und eine JPG, da stimmt die Größe zwar aber gefühlte 10 Sekunden versucht er da rumzumachen. Fehler selbst "Corrupt Jpeg file" kommt nicht. Nur mal so zur Info. Zeit habe ich noch nicht gefunden, mir ein wenig mehr von der JPG Header Struktur durchzulesen. |
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Einen TPoint als Ergebnis einer Breiten- und Höhenermittlung zurück zu geben, ist aber auch befremdlich. Ich würde in den x- und y-Koordinaten jetzt erstmal keine Dimensionen vermuten, sondern, wie der Datentyp schon impliziert, einen Punkt. Ich würde eine Funktion machen mit Höhe und Breite als var-Parameter und true oder false, im Falle eines Erfolges oder Misserfolges, zurückgeben.
|
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
![]() ![]() |
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Hallo,
mal eine ketzerische Frage, und zwar warum nicht einfach die fertigen JPEG Funktionen nehmen? Warum alles komplett neu erfinden. Bei Exif oder Icc verstehe ich das ja noch, da es in diesem Bereich nicht viel gibt, aber bei der Bildgrösse :gruebel: ?
Delphi-Quellcode:
unit Unit1;
interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls; type TForm1 = class(TForm) Button1: TButton; Memo1: TMemo; procedure Button1Click(Sender: TObject); private { Private-Deklarationen } public { Public-Deklarationen } end; var Form1: TForm1; implementation {$R *.dfm} uses Vcl.Imaging.jpeg; procedure getJpegDimensions(const FileName: String; var Width: Integer; var Height: Integer); var jpg:TJpegImage; begin jpg:=TJpegImage.Create; try jpg.loadFromFile(FileName); Width := jpg.width; Height := jpg.height; finally jpg.free; end; end; procedure TForm1.Button1Click(Sender: TObject); var Width: Integer; Height: Integer; begin getJpegDimensions('test.jpg', Width, Height); Memo1.Lines.Add('Width: '+Width.ToString); Memo1.Lines.Add('Height: '+Height.ToString); end; end. |
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Vermutlich um "schnell" und speichersparend die Größe zu bekommen?
Das Bild erst in den RAM zu laden und den komplette Inhalt zu entziffern, um an Ende 99% der geladenen Infos nicht zu verwenden... |
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Zitat:
Ich brauche das, weil ich eine Dateiliste anzeigen will, mit Infos. Mit "LoadFromFile" könnte ich wohl ein Kaffee dabei trinken, bis alles gelesen ist. |
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Nja, bei Verzeichnissen mit vielen Dateien dauert das auslesen auch ewig lang, und das nur für das Listing, (versuch mal WinSxS aufzulisten)
oder wenn der Datenträger/Netzlaufwerk hängt und auf den Timeout wartet. Man kann die Dateiliste in einem Thread erstellen, bereits schonmal in die GUI pushen, dann die weiteren Dateiinfos holen und Diese dann im Nachgang, auch Stück für Stück, in der GUI nachtragen. |
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Hmm..
Hab mir die letzte Version angeschaut.. Wieso gehst Du erst den kompletten Header durch, um dann wieder von vorne anzufangen, um nur die Pakete mit $C0 bis $C2 zu suchen. Das lesen der Größe gibt bei mir bei einigen Bildern auch eine falsche Größe wieder... Deshalb habe ich das mal aufgeräumt und überarbeitet:
Delphi-Quellcode:
type
TByteArr = array of Byte; TJFIFSegment = packed record Fix : Byte; Kind : Byte; end; TSOFData = packed record SamplePrecision : Byte; Height : WORD; // Number of lines Width : WORD; // Number of samples per line Comp : Byte; // Number of image components in frame // Data : TByteArr; end; PSOFData = ^TSOFData; // Irgendwo aus dem Netz kopiert.. function ReverseWord(w: word): word; asm {$IFDEF CPUX64} mov rax, rcx {$ENDIF} xchg al, ah end; function ReadWORD(FS : TFileStream; out AWord : WORD):boolean; begin Result := (FS.Read(AWord,SizeOf(AWord)) = SizeOf(AWord)); AWord := ReverseWord(AWord); end; function ReadSegmentHeader(FS : TFileStream; out Seg : TJFIFSegment):boolean; begin Result := (FS.Read(Seg,SizeOf(Seg)) = SizeOf(Seg)); end; function ReadData(FS : TFileStream; const ALength:WORD; var Data : TByteArr):boolean; begin SetLength(Data, ALength); Result := (FS.Read(Data[0],ALength) = ALength); end; function GetJPEGImageSize(const AFileName : string; out AHeight, AWidth : integer):boolean; var FS : TFileStream; SOI : WORD; SEG : TJFIFSegment; SegSize : WORD; C0 : PSOFData; tmpData : TByteArr; begin Result := False; FS := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyNone); try if ReadWORD(FS, SOI) and (SOI = $FFD8) then begin // Start Of Image = Magic Bytes zur Erkennung von JPG While ReadSegmentHeader(FS, SEG) and (SEG.Fix = $FF) do begin if SEG.Kind = $DA then break; // Start of Scan = End of Header, danach nur noch Imagedaten if ReadWORD(FS, SegSize) then begin SegSize := SegSize -2; // Längenangaben um die 2 Byte für die Längenangaben selber reduziert case SEG.Kind of $C0, // Baseline DCT $C1, // Extended sequential DCT, Huffman coding $C2, // Progressive DCT, Huffman coding $C3, // Lossless (sequential), Huffman coding $C9, // Extended sequential DCT, arithmetic coding $CA, // Progressive DCT, arithmetic coding $CB : // Lossless (sequential), arithmetic coding begin // SOFx, im SOF steht am Anfang die Größe des Bildes, anschließend Daten zur Dekodierung if ReadData(FS, SegSize, tmpData) then begin C0 := PSOFData(@tmpData[0]); AHeight := ReverseWord(C0.Height); AWidth := ReverseWord(C0.Width); Result := True; Break; end; end; else FS.Position := FS.Position + SegSize; // Zum nächsten Segment, die weiteren werden nicht gebraucht. end; end; end; end; finally FS.Free; end; end; Das funktioniert nun auch mit den anders kodierten JPGs ($C3,$C9..) und liefert schnell die richtige Größe zurück. (Außerdem ist es meiner Meinung nach besser lesbar ;) ) |
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Zitat:
|
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Eigentlich schon fast ideal. Allerdings hätte ich, wie schon am Anfang jemand vorschlug, statt einzelner Bytes einen Buffer gelesen und diesen durchsucht. Das bringt in der Regel einiges an Geschwindigkeit. Insbesondere kann man hier vermutlich einen Buffer wählen, der von Anfang an groß genug ist, um den kompletten Header zu lesen, so dass nur ein einziger Lesevorgang notwendig ist.
|
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Hmm..
Das Problem ist leider, das der Header zwischen nur wenigen Bytes bis hin zu KB (mit XML-EXIF Daten) aufgeblasen werden kann. Meist stehen diese dann noch am Anfang und die $Cx kommen zum Schluss des Headers.... Ich weiß auch nicht zu 100% wie der FileStream dies intern händelt.. Puffert dieser, oder liest gleich Blockweise ? |
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Zitat:
|
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Zitat:
Zitat:
Das muss ich wohl bei mir noch $C9 dazu machen. Wie findest du denn meine (bzw. eine gefundene von mir abgeänderte Version)? (hier im Thread irgendwo auch weiter oben; aber füge sie mal unten hinzu) Abgesehen vom "besser lesbarem". Hab sie halt gekürzt. Ich verstehe aber noch nicht so ganz den While Block. Also der Bereich " If Not (BD In [$01,$D0,$D1,$D2,$D3,$D4,$D5,$D6,$D7])" Bin mir da nicht so ganz klar, wie viel/lange er liest. Geht aber jedenfalls gut und schnell. Auch noch keine JPG gefunden, die hier falsche Werte (Breite/Höhe) liefert.
Delphi-Quellcode:
Procedure GetJPGSize(sFile: String; Out WW, WH: DWord);
Var FS: TFileStream; BD: Byte; WD : Word; RL: LongInt; HW : Array[0..3] Of Byte; LE : Array[0..1] Of Byte; Begin sFile := '\\?\'+SFile; WW := 0; WH := 0; FS := TFileStream.Create(sFile, fmShareDenyNone); Try RL := FS.Read(WD, 2); If (Lo(WD) <> $FF) And (Hi(WD) <> $D8) Then RL := 0; If RL > 0 Then Begin RL := FS.Read(BD, 1); While (BD = $FF) and (RL > 0) Do Begin RL := FS.Read(BD, 1); If BD <> $FF Then Begin If BD In [$C0,$C1,$C2] Then Begin FS.Seek(3,1); FS.Read(HW,4); WH := HW[0] Shl 8 + HW[1]; WW := HW[2] Shl 8 + HW[3]; End Else Begin If Not (BD In [$01,$D0,$D1,$D2,$D3,$D4,$D5,$D6,$D7]) Then Begin FS.Read(Le,2); WD := LE[0] Shl 8 + Le[1]; FS.Seek(WD - 2, 1); FS.Read(BD, 1); End Else BD := $FF; End; End; End; End; Finally FS.Free; End; End; |
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Verkürzt vielleicht, aber nicht vereinfacht. Du hast eine 5 Ebenen tiefe Verschachtelung.
|
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Zitat:
Ich habe mir eine ![]() (Andererseits: Wenn ich eine Funktion in meiner dzlib habe, die davon profitieren würde, habe ich kein Problem damit den Stream-Cache zu verwenden, schließlich ist er ja selbst Teil der Bibliothek.) Falls sich jemand den Code ansehen will: Die verlinkte Seite verweist noch auf Sourceforge, ![]() ![]() |
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Hmm..
Ist Dir eigentlich bewusst, wie sich ein Header einer JPG aufbaut? Am Anfang (2 Bytes) das SOI (FFD8) Dann gefolgt von mehreren Segmenten (beliebige Anzahl, einige Typen jedoch nur jeweils einmal). Zum Abschlus (2 Bytes) das SOS (FFDA) und somit das Ende des Headers... Danach kommen die eigentlichen Bilddaten... Jedes Segment besteht aus einem Identifer (2 Bytes), das erste Byte ist FF das 2. Byte gibt den Typ des Segmentes an. Nach dem Identifer kommt immer die Länge der Daten des Segmentes (2 Bytes). Im Anschluss die Daten des Segmentes mit der angegebenen Länge (da die 2 Bytes der Länge auch in der Länge eingerechtet sind müssen dieses abgezogen werden). Es gibt die Segmente welche die Kodierung definieren, Kommentare und weitere, wie auch das EXIF-Segment. Ohne die Längenangabe des aktuellen Segments kannst Du den Anfang des nächsten Segments nicht finden.. Und eben in den Segmenten (SOFx) für die Definition der Kodierung ($C0, $C1.. ) steht in den Daten die Größe drinnen. Somit ist dein else Zweig (If Not (BD In [$01,$D0,$D1,..) schonmal Blödsin, da es egal ist, welches andere Segment kommt, da Du nur nach dem SOFx suchst. |
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Hi Holger.
Es ist schwer alle Infos zu bekommen und natürlich bin ich nicht gleich überall gleich schlau. Hier und da dauert das bei mir :) Bei diesem Thema wird's auch noch dauern *lach Aber ich verstehe schon ein wenig mehr. Zum Beispiel das mit der Längenangabe. Aber mal ein Beispiel... da findet man ein Source-Code mit $C0 und $C1 (für's ermitteln der Bildgröße) und dann stolpert man irgendwo auf $C2. Jetzt nutzt du in deiner Funktion $C3,$C9,$CA und $CB. Inzwischen habe ich Wikipedia (in DE und EN) gelesen. DE: ![]() EN: ![]() Auf der englischen kann man in einer Tabelle lesen, dass u.a. die Bildbreite/Höhe in Verbindung mit $C0 (bzw. 0xC0) und $C2 steht. Von anderen Tags (also das 2. Byte - der Typ) ist hier nicht die Rede (in Verbindung der Bildgröße). Mein am Anfang gefundenes Beispiel nutzte nur $C0 und $C1. $C2 habe ich dann noch selbst hinzugefügt. Jetzt schreibst du:
Code:
Müsste man dann nicht nur "$C0,$C1,$C2,$C3,$C9,$CA,$CB" (wie in deinem Beispiel), sondern auch noch andere nutzen. Zumindest laut DE-Wikipedia dann auch $C5,$C6,$C7,$C9,$CD,$CE und $CF (diese mit SOFx gekennzeichnet sind).
Und eben in den Segmenten (SOFx) für die Definition der Kodierung ($C0, $C1.. ) steht in den Daten die Größe drinnen.
Und zu diesem Zweig "(If Not (BD In [$01,$D0,$D1,..)". Das habe ich aus einem Beispiel übernommen. In der DE-Wikipedia steht dazu beispielsweise "Ausnahme: Folgt dem FF einer der Restart-Marker (D0 - D7) so setzen sich die Daten direkt dahinter weiter fort:". Fragt sich natürlich noch was $01 bedeutet. Da ich aber nicht alles ganz verstehe, dachte ich natürlich... "oh... Ausnahmen; na der Beispiel-Coder hat sich wohl dabei was gedacht". Aber du sagt jetzt ja, dass im SOF Bereich (Start of Frame Marker) die Daten (Bildgröße) stehen. Jedoch suche ich vergeblich eine Info zu "End of Frame Marker" :). Ist es hier SOF15?* Gruß Mike *Eben gesehen... laut deinem Code ist $DA dann sozusagen das Ende. Also bei SOS (Start of Scan) |
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Hmm..
Lt. Wiki hast Du bei den $Cx recht, dass dort noch weitere als DCT definiert sind, somit könnten alle $Cx von 0 bis F für die Größenangaben genutzt werden, außer $C4, $C8, $CC. Jedes JPEG sollte nur einen dieser SOF enthalten. Meine Angaben hatte ich aus einer ISO Norm, wohl auch schon was älter entnommen. Hoer noch ein anderer Link: ![]() |
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Das PDF von aboutvb hatte ich auch mal kurz überflogen aber alles nicht so einfach zu verstehen um es in Delphi umzusetzen.
Also ich kannte es schon. Trotzdem Danke für den Link. Versuche mich gerade daran nochmal. Deine Funktion versuche ich erst mal zu ignorieren und will meine Funktion abändern. Einfach um alles besser zu verstehen. Inzwischen habe ich meine Funktion ein wenig geändert. Funktioniert soweit abgesehen von einer Sache (die bei dir wohl auch noch nicht funktioniert), nämlich Hoch/Querformat. Bei Hochformat werden die Angaben vertauscht (in Breite steht die Höhe, in Höhe die Breite). Hier mal mein neuer Code und schnell mal bissl Kommentare dazu:
Delphi-Quellcode:
Bei aboutvb ist nun auf Seite 764 erst mal eine Tabelle (Tabelle 27.13) mit diesen C1,C2,C3 usw. Hier wurden die 3 (C4,C8,CC) auch weggelassen... also so, wie ich es bei Wikipedia las. Soweit so gut. In Tabelle 27.14 geht es dann weiter. Dort wird aber wieder (erste Zeile in der Tabelle) von FFDxH gesprochen. Auch weiter unten im Text...
Procedure GetJPGSize(sFile: String; Out WW, WH: DWord);
Var FS: TFileStream; BD: Byte; WD : Word; RL: LongInt; HW : Array[0..3] Of Byte; LE : Array[0..1] Of Byte; Begin sFile := '\\?\'+SFile; WW := 0; WH := 0; FS := TFileStream.Create(sFile, fmShareDenyNone); Try // Erste 2 Bytes lesen RL := FS.Read(WD, 2); // Wenn FFD8 nicht vorhanden, dann RL auf 0 setzen If (Lo(WD) <> $FF) And (Hi(WD) <> $D8) Then RL := 0; If RL > 0 Then Begin // Nächstes Byte lesen. Ist (oder sollte) immer FF sein RL := FS.Read(BD, 1); While (BD = $FF) And (RL > 0) Do Begin // Nächstes Byte lesen RL := FS.Read(BD, 1); // Wenn alles außer FF, dann... If BD <> $FF Then Begin // Prüfen auf diese C0,C1 usw. If BD In [$C0,$C1,$C2] Then // <- kommt noch mehr dazu Begin // 3 Bytes weiter. Längenangabe (2 Bytes) sowie die Bit-Tiefe (1 Byte) // wird nicht benötigt FS.Seek(3,soFromCurrent); FS.Read(HW,4); // 4 Bytes (2 für Breite und 2 für Höhe) WH := HW[0] Shl 8 + HW[1]; WW := HW[2] Shl 8 + HW[3]; // ... // Hier muss noch was rein, wegen Hoch/Querformat // ... // Breite/Höhe gespeichert. Vielleicht ja noch mal prüfen? Jedenfalls hier Abbrechen Break; End Else Begin If BD<>$DA Then Begin FS.Read(Le,2); // 2 Bytes lesen (Längenangabe) WD := LE[0] Shl 8 + Le[1]; // Länge in Little Endian zurück (soll ja im JPG immer in BIG-Endian abgespeichert sein) FS.Seek(WD - 2, soFromCurrent); // Position zum nächsten Segment (-2 wegen 2 Byte-Längenangabe) FS.Read(BD, 1); // Erstes $FF lesen End Else Break; // <- DA gefunden, also fertig. End; End; End; End; Finally FS.Free; End; End; "Wegen der geforderten Baseline DCT Komprimierung sollte die Signatur FFD0H auftreten." Meinte der Autor hier nicht "FFC0H"? In meinem Code jedenfalls überspringe ich 3 Bytes mit Seek. Kommentar in meinem Code dort "3 Bytes weiter. Längenangabe (2 Bytes) sowie die Bit-Tiefe (1 Byte) wird nicht benötigt" Es funktioniert jedenfalls so. Wenn ich mir die Tabelle 27.14 (aboutvb) betrachte, scheint dies ja so richtig zu sein. Abgesehen von der Angabe dort "FFD0H". Was wohl "FFC0H" sein sollte. Oder bringe ich was durcheinander? Jedenfalls müsste ich dann wohl noch folgendes einfügen... bei meinem Kommentar (Hier muss noch was rein, wegen Hoch/Querformat) im Code: 1. 2 Byte überspringen 2. 1 Byte lesen und Werte prüfen. 3. ggf. Variablen für Breite/Höhe vertauschen Laut Tabelle: Wert 0-3 vertikal und 4-7 horizontal müsste ich ermittelt bekommen. Ich erreiche aber Werte von 17 (Querformat), mal 34 und auch mal 33 für Hochformat. Also so ganz klappt's noch nicht aber wohl fast. Am Ende müsste man sich noch fragen, ob man die richtigen Werte vom Bild bekommt. In JPG soll man ja auch kleinere Vorschaubilder speichern können. 1 Vorschaubild? Mehrere? Wohl mehrere. Ich denke damit sind diese "ID x. Komponenten" gemeint. Im PDF (aboutvb) ist ja schon von "ID 3. Komponente" die Rede. Wobei dort in der Tabelle (27.14) nicht mehr von Bildgrößen zu lesen ist, sondern nur noch (pro Komponente) von Hoch/Querformat (Abtastfaktor) und einer Nummer von einer Quantisierungstabelle. Was auch immer das nun wieder ist :) Aber wenn es kleinere Vorschaubilder geben kann, gibt es sicherlich auch für diese ganze 4 Bytes für das Abfragen der Breite/Höhe. Hoffentlich ist das 1. immer das Originalbild. Alles was danach kommt (ob nun mit Vorschaubilder oder nicht) könnte ja einem dann egal sein. Zumindest für dieses Thema (Ermitteln der JPG Bildgröße). |
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Nachtrag:
"Ich erreiche aber Werte von 17 (Querformat), mal 34 und auch mal 33 für Hochformat. Also so ganz klappt's noch nicht aber wohl fast." Ach... da steht ja BIT 0-3 und 4-7 (für das Format) :) Wird gleich funktionieren (aber muss trotzdem schauen wie ich da die erste 4 und letzten 4 Bits teile) :) |
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Ich bekomme noch die Kriese. :)
Dieser Wert "Abtastfaktor" hat gar nichts mit dem Format zu tun. Ich las "vertikal/horizontal" und dachte... Ich habe jedenfalls ein Bild im Hochformat aufgenommen, dass hier leider Breite/Höhe vertauscht. ExifTool gibt an (beim Orientation-Flag): Rotate 270 CW. Jetzt muss man wohl noch EXIF-Daten auslesen. Wobei es nicht immer EXIF-Daten gibt aber hier (ohne EXIF Daten bei Hochformat) vertauscht Windows selbst die Werte. |
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Hm..
Das ist halt die Krux, wenn Kamera Hersteller meinen es 'besser' machen zu wollen... Die Höhle und Breite werden dann getauscht, wenn mit der Kamera um 90/270 Grad gedrehtes Bild gemacht wird. Diese Drehung steht dann nur in den EXIF informationen, diese könnten jedoch mal entfernt werden, womit diese Angaben weg sind. Nur mit Programmen, welche auch die EXIF (Segment $FFE1 = APP1) auslesen und interpretieren, können das erkennen. Diese EXIF Daten können Binär oder XML sein.. Viel Spaß wenn Du diese selber auslesen willst ohne entsprechende Komponenten. Ein Thumbnail kann entweder im $FFE0 = APP0 sein, wenn dies den Identifier "JFIF" hat. Alternativ auch in den zusätzlichen JFIF extension APP0 mit einem anderen APP0 Identifier "JFXX". Oder ich glaube auch in den EXIF Daten.. Ach ja, jeweils in anderem Bild-Format.. Wenn Du all diese Information haben möchtest (Größe/Drehung/Thumb..) dann kannst Du deine Funktion vergessen und musst, wie in meinem Beispiel, Segmentbasierend arbeiten. Bei meinem Source brauche ich nur im Case die weiteren Kinds hinzufügen und dann mit Casten die Daten per vorher definierten Records sauber auslesen. Nur so behältst Du die Übersicht in deinem Source. |
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Ne, auf EXIF habe ich erst mal keine Lust
Der Fokus lag jetzt auf die Bildgröße und das verstehen des JPG-Headers. Zwar ein wenig blöd, wenn evtl. bei manchen Dateien die Breite/Höhe vertauscht ist aber das schieb ich mal nach hinten und kümmere mich um andere Codezeilen. Also mal Danke an alle. Ich habe ein wenig dazugelernt. Mike |
AW: GetJPGSize Funktion (wollen wir sie verbessern?)
Liste der Anhänge anzeigen (Anzahl: 1)
Lang hat's gedauert, da ich pers. Jpeg absolut nicht mag.
Hier ist mein Versuch was schnelles draus zu basteln, keine Benchmarks durchgeführt, selbst ist der Mann/die Frau :-) Hier meine herangehensweise, in der Hoffnung das es tatsächlich hilft: Ps: Übergeben werden muss ein gültiger kompletter Pfad. (MyGetFiles holt aus dem Verzeichniss nur die Dateinamen ab)
Delphi-Quellcode:
Ein kleines Testprogramm dem dieser Code entspringt ist angepappt.
procedure TfrmMain.ComputeData(const input: String);
type TJpgInfo = record IsJpeg: Boolean; Version : String; Dimension : String; Mode: String; end; function BytesToWord(HiByte, LoByte: Byte): Word; type TWord = record case integer of 0 : (Both : Word); 1 : (Lo, Hi : Byte); end; var Long : TWord; begin with Long do begin Hi := HiByte; Lo := LoByte; Result := Both; end; end; // BytesToWord function GetJpgInfo(const FS: TFileStream): TJpgInfo; var Buf: TBytes; i: Integer; checker: Boolean; LastPos: Integer; S: String; MaxCache: Int64; begin MaxCache := (20 * 1024); // ggf anpassen für noch dickere header... checker := True; Result.IsJpeg := False; LastPos := 0; FS.Position := LastPos; if (FS.Size >= MaxCache) then SetLength(Buf, MaxCache) else SetLength(Buf, FS.Size); FS.Read(Pointer(Buf)^, Length(Buf)); // daten puffern um es flott im RAM dynamisch auswerten zu können // dyn = wenn signaturen nicht direkt bei position 0 anfangen // Jpeg's mit exif header zBsp if checker then // Signatur Check begin checker := False; for I := Low(Buf) to High(Buf) do begin if i + 3 < High(Buf) then if ((Buf[I] = $ff) and (Buf[I+1] = $d8) and (Buf[I+2] = $ff) and (Buf[I+3] = $e0)) then begin checker := True; LastPos := i + 3; Break; end; end; end; if checker then // prüfe ob JFIF vorhanden ist, erst ab hier akzeptiere ich es als Jpeg Datei begin checker := False; for I := LastPos to High(Buf) do begin if i + 3 < High(Buf) then if ((Buf[I] = $4a) and (Buf[I+1] = $46) and (Buf[I+2] = $49) and (Buf[I+3] = $46)) then begin checker := True; Result.IsJpeg := True; // Application.MessageBox(PChar('gefunden'), PChar('gefunden'), MB_OK); LastPos := i + 3; Break; end; end; end; if Result.IsJpeg then // hole Version begin if LastPos + 3 < High(Buf) then begin if Buf[LastPos+3] < 10 then Result.Version := IntToStr(Buf[LastPos+2]) + '.' + '0' + IntToStr(Buf[LastPos+3]) else Result.Version := IntToStr(Buf[LastPos+2]) + '.' + IntToStr(Buf[LastPos+3]); LastPos := LastPos + 3; end; end; if Result.IsJpeg then // hole Dimension und Farbmodus vom letzten C0 segment was sich hoffentlich im MaxCache bereich befindet... // da diese operation den kompletten puffer betrifft // kann man hier bestimmt noch mehr speed rausholen begin checker := False; for I := LastPos to High(Buf) do begin if i + 1 < High(Buf) then if ((Buf[I] = $ff) and (Buf[I+1] = $c0)) then begin checker := True; LastPos := i; end; end; if checker then if LastPos + 10 < High(Buf) then begin Result.Dimension := IntToStr(BytesToWord(Buf[LastPos + 7], Buf[LastPos + 8])) + ' x ' + IntToStr(BytesToWord(Buf[LastPos + 5], Buf[LastPos + 6])); case Buf[LastPos + 9] of $1: Result.Mode := 'Grey'; $3: Result.Mode := 'YCbCr'; $4: Result.Mode := 'CMYK'; end; end; end; end; // GetJpgInfo var FileList: TStringDynArray; lvItem: TListItem; i: Integer; fs: TFileStream; JpgInfo: TJpgInfo; begin lvFolder.Clear; if Length(input) <= 3 then Exit; edtFolder.Text := input; FileList := MyGetFiles(input, '*.jpg;*.jpeg;*.jpe;*.jfif', False); if Length(FileList) > 0 then begin for I := Low(FileList) to High(FileList) do begin lvItem := lvFolder.Items.Add; lvItem.Caption := ExtractFileName(FileList[I]); fs := TFile.OpenRead(FileList[I]); lvItem.SubItems.Add(IntToStr(fs.Size)); JpgInfo := GetJpgInfo(fs); if JpgInfo.IsJpeg then begin lvItem.SubItems.Add(JpgInfo.Version); lvItem.SubItems.Add(JpgInfo.Dimension); lvItem.SubItems.Add(JpgInfo.Mode); end; end; fs.Free; end; end; // ComputeData Viel Spass /edit Mir ist gerade noch 'ne Speed optimierung eingefallen betreffend diesem Abschnitt:
Code:
genau andersrum machen, rückwärts abarbeiten lassen und einen break beim ersten fund...
if Result.IsJpeg then // hole Dimension und Farbmodus vom letzten C0 segment was sich hoffentlich im MaxCache bereich befindet...
// da diese operation den kompletten puffer betrifft // kann man hier bestimmt noch mehr speed rausholen begin checker := False; for I := LastPos to High(Buf) do |
Alle Zeitangaben in WEZ +1. Es ist jetzt 11:02 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