Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   GetJPGSize Funktion (wollen wir sie verbessern?) (https://www.delphipraxis.net/203339-getjpgsize-funktion-wollen-wir-sie-verbessern.html)

MicMic 7. Feb 2020 22:57

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:
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;
Mein "grübeln" nun.
Nach dem "Begin" wird FillChar genutzt. Wäre das hier nicht überflüssig?

Nach dem "try" ist folgende Zeile angegeben:
Code:
ReadLen := f.Read(Sig[0], SizeOf(Sig));
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?
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?

jaenicke 8. Feb 2020 08:26

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:
  • Ein normaler TFileStream ist nicht optimal, besser wäre eine Pufferung z.B. durch eine MMF
  • Sig als Array mit zwei Byte-Einträgen ist nicht gerade schnell. Besser wäre ein einfacher Word-Wert, der beide Bytes umfasst.
  • Es sollten out- statt var-Parameter sein
  • Wenn keine Größe gefunden wird, werden wWidth und wHeight nicht beschrieben, es gibt aber auch keine Fehlerbehandlung wie einen Rückgabewert als Boolean.

Luckie 8. Feb 2020 10:03

AW: GetJPGSize Funktion (wollen wir sie verbessern?)
 
Zitat:

Wenn keine Größe gefunden wird, werden wWidth und wHeight nicht beschrieben, es gibt aber auch keine Fehlerbehandlung wie einen Rückgabewert als Boolean.
Man könnte in diesem Fall jeweils 0 oder -1 eintragen, dann weiß man, dass keine Größe ermittelt werden konnte, da 0 oder -1 wohl unsinnige Werte sind.

MicMic 8. Feb 2020 14:37

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:
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;
Eine extra Funktion wollte ich nicht und habe mir was zusammengebastelt. Was nun schneller oder langsamer ist, weiß ich jetzt nicht.
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;

freimatz 10. Feb 2020 07:47

AW: GetJPGSize Funktion (wollen wir sie verbessern?)
 
Zitat:

Zitat von MicMic (Beitrag 1456968)
Was würdet ihr denn so verbessern?

Den Code lesbarer machen :)
Nur ein Delphi-Profi kann den Code lesen, versteht jedoch m.E. immer noch nicht warum da was passiert.

Luckie 10. Feb 2020 08:52

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.

KodeZwerg 10. Feb 2020 11:23

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:
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;
Quelle: get-width-and-height-of-jpg-image.html

MicMic 12. Feb 2020 00:45

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.

Luckie 12. Feb 2020 06:19

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.

himitsu 12. Feb 2020 10:03

AW: GetJPGSize Funktion (wollen wir sie verbessern?)
 
Delphi-Referenz durchsuchenTSize

Delphi-Referenz durchsuchenTSmallSize passt von den Bits her, ist aber mit Vorzeichen und Unsigned gibt es hier nicht vordefiniert.

jus 12. Feb 2020 16:34

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.

himitsu 12. Feb 2020 17:19

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...

MicMic 12. Feb 2020 20:57

AW: GetJPGSize Funktion (wollen wir sie verbessern?)
 
Zitat:

Zitat von himitsu (Beitrag 1457320)
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...

So ist es :)
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.

himitsu 13. Feb 2020 09:53

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.

HolgerX 14. Feb 2020 07:06

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 ;) )

freimatz 14. Feb 2020 07:28

AW: GetJPGSize Funktion (wollen wir sie verbessern?)
 
Zitat:

Zitat von HolgerX (Beitrag 1457452)
(Außerdem ist es meiner Meinung nach besser lesbar ;) )

Definitiv :thumb:

dummzeuch 14. Feb 2020 08:59

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.

HolgerX 14. Feb 2020 10:01

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 ?

MicMic 14. Feb 2020 12:12

AW: GetJPGSize Funktion (wollen wir sie verbessern?)
 
Zitat:

Zitat von himitsu (Beitrag 1457369)
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.

Wenn man eine Dateiliste schon sortiert (Name, Datum, Größe etc.) einlesen könnte, wäre das klasse. Da dies jedoch nicht geht, sieht's optisch ein wenig doof aus (auch wenn es schnell geht), wenn man schon angezeigte Dateinamen am Bildschirm hat, diese dann plötzlich verschwinden und durch andere ausgetauscht werden.

MicMic 14. Feb 2020 12:27

AW: GetJPGSize Funktion (wollen wir sie verbessern?)
 
Zitat:

Zitat von HolgerX (Beitrag 1457452)
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;
… gekürzt... (weiter oben im Thread komplett)

Zitat:

Zitat von HolgerX (Beitrag 1457452)
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 ;) )


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;

Luckie 14. Feb 2020 13:03

AW: GetJPGSize Funktion (wollen wir sie verbessern?)
 
Verkürzt vielleicht, aber nicht vereinfacht. Du hast eine 5 Ebenen tiefe Verschachtelung.

dummzeuch 14. Feb 2020 13:03

AW: GetJPGSize Funktion (wollen wir sie verbessern?)
 
Zitat:

Zitat von HolgerX (Beitrag 1457476)
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 ?

Nein, er puffert überhaupt nicht, er liest die Blöcke so, wie die Read-Aufrufe es vorgeben. Nur der Windows-Cache sitzt dann noch dazwischen, und der bringt beim ersten Lesen nicht viel.

Ich habe mir eine Stream-Cache Klasse geschrieben, die das für mich übernimmt. Allerdings würde ich die in solch einer Funktion nicht verwenden, denn das würde wieder eine Abhängigkeit schaffen, die man da nicht unbedingt haben will.

(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, dzlib ist aber inzwischen auf OSDN.

HolgerX 14. Feb 2020 13:09

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.

MicMic 15. Feb 2020 00:59

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: https://de.wikipedia.org/wiki/JPEG_F...rchange_Format
EN: https://en.wikipedia.org/wiki/JPEG

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:
Und eben in den Segmenten (SOFx) für die Definition der Kodierung ($C0, $C1.. ) steht in den Daten die Größe drinnen.
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 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)

HolgerX 15. Feb 2020 06:52

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:
http://www.aboutvb.de/bas/formate/pdf/jpg.pdf

MicMic 15. Feb 2020 21:20

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:
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;
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...

"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).

MicMic 15. Feb 2020 21:32

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) :)

MicMic 15. Feb 2020 23:00

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.

HolgerX 16. Feb 2020 05:36

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.

MicMic 19. Feb 2020 17:57

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

KodeZwerg 10. Mär 2020 06:51

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:
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
Ein kleines Testprogramm dem dieser Code entspringt ist angepappt.
Viel Spass


/edit
Mir ist gerade noch 'ne Speed optimierung eingefallen betreffend diesem Abschnitt:
Code:
    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
genau andersrum machen, rückwärts abarbeiten lassen und einen break beim ersten fund...


Alle Zeitangaben in WEZ +1. Es ist jetzt 02:04 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