Recent

Author Topic: Reading Width x Height from JPEG from Header (Or without loading into Image)  (Read 1263 times)

zxandris

  • Full Member
  • ***
  • Posts: 126
I assume this is possible, to get the width and height from the JPG header, but I have to admit I have no idea how to do this.  I'm hoping someone has done this before, or has an idea how to go about it?

Any help would be appreciated,

CJ

Zvoni

  • Hero Member
  • *****
  • Posts: 2792
https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format
http://www.fastgraph.com/help/jpeg_header_format.html

In a nutshell:
The first two bytes are the SOI-Marker ("FF D8")
The next two bytes are the width in pixels
again the next two bytes are the height in pixels

EDIT: Which doesn't mean above is correct......
« Last Edit: March 07, 2024, 12:25:16 pm by Zvoni »
One System to rule them all, One Code to find them,
One IDE to bring them all, and to the Framework bind them,
in the Land of Redmond, where the Windows lie
---------------------------------------------------------------------
Code is like a joke: If you have to explain it, it's bad

Bart

  • Hero Member
  • *****
  • Posts: 5496
    • Bart en Mariska's Webstek
You can use my picslib unit for that (function GetImageSize).
(Notice that it requires several include files, which are available in the same folder.)

Bart

KodeZwerg

  • Hero Member
  • *****
  • Posts: 2269
  • Fifty shades of code.
    • Delphi & FreePascal
Complete native, limited to JPEG fileformat. Fastest method I am aware of.
Code: Pascal  [Select][+][-]
  1. type
  2.   TByteArr = array of Byte;
  3.  
  4.   TJFIFSegment = packed record
  5.     Fix : Byte;
  6.     Kind : Byte;
  7.   end;
  8.  
  9.   TSOFData = packed record
  10.     SamplePrecision : Byte;
  11.     Height : WORD; // Number of lines
  12.     Width : WORD; // Number of samples per line
  13.     Comp : Byte; // Number of image components in frame
  14. // Data : TByteArr;
  15.   end;
  16.   PSOFData = ^TSOFData;
  17.  
  18. function ReverseWord(w: word): word;
  19. begin
  20.   Result := ((w shl  8) and $FF00) or ((w shr  8) and $00FF);
  21. end;
  22.  
  23. function ReadWORD(FS : TFileStream; out AWord : WORD):boolean;
  24. begin
  25.   Result := (FS.Read(AWord,SizeOf(AWord)) = SizeOf(AWord));
  26.   AWord := ReverseWord(AWord);
  27. end;
  28.  
  29. function ReadSegmentHeader(FS : TFileStream; out Seg : TJFIFSegment):boolean;
  30. begin
  31.   Result := (FS.Read(Seg,SizeOf(Seg)) = SizeOf(Seg));
  32. end;
  33.  
  34. function ReadData(FS : TFileStream; const ALength:WORD; var Data : TByteArr):boolean;
  35. begin
  36.   SetLength(Data, ALength);
  37.   Result := (FS.Read(Data[0],ALength) = ALength);
  38. end;
  39.  
  40. function GetJPEGImageSize(const AFileName : UnicodeString; out AHeight, AWidth : dword):boolean;
  41. var
  42.   FS : TFileStream;
  43.   SOI : WORD;
  44.   SEG : TJFIFSegment;
  45.   SegSize : WORD;
  46.   C0 : PSOFData;
  47.   tmpData : TByteArr;
  48.   UTF8FileName: RawByteString;
  49. begin
  50.   Result := False;
  51.   UTF8FileName := String(AFileName);
  52.   FS := TFileStream.Create(UTF8FileName, fmOpenRead or fmShareDenyNone);
  53.   try
  54.     if ReadWORD(FS, SOI) and (SOI = $FFD8) then begin
  55.  
  56.       While ReadSegmentHeader(FS, SEG) and (SEG.Fix = $FF) do begin
  57.  
  58.         if SEG.Kind = $DA then break;
  59.  
  60.         if ReadWORD(FS, SegSize) then begin
  61.           SegSize := SegSize -2;
  62.           case SEG.Kind of
  63.             $C0, // Baseline DCT
  64.             $C1, // Extended sequential DCT, Huffman coding
  65.             $C2, // Progressive DCT, Huffman coding
  66.             $C3, // Lossless (sequential), Huffman coding
  67.             $C9, // Extended sequential DCT, arithmetic coding
  68.             $CA, // Progressive DCT, arithmetic coding
  69.             $CB : // Lossless (sequential), arithmetic coding
  70.                   begin
  71.                     if ReadData(FS, SegSize, tmpData) then begin
  72.                       C0 := PSOFData(@tmpData[0]);
  73.                       AHeight := ReverseWord(C0^.Height);
  74.                       AWidth := ReverseWord(C0^.Width);
  75.                       Result := True;
  76.                       Break;
  77.                     end;
  78.                   end;
  79.            else
  80.              FS.Position := FS.Position + SegSize;
  81.           end;
  82.         end;
  83.       end;
  84.     end;
  85.   finally
  86.     FS.Free;
  87.   end;
  88. end;
« Last Edit: Tomorrow at 31:76:97 xm by KodeZwerg »

zxandris

  • Full Member
  • ***
  • Posts: 126
You guy's are the best, thank you very much.

CJ

wp

  • Hero Member
  • *****
  • Posts: 12518
Want to add my usual "advertisement" for fcl-image, the fcl graphics library which comes with FPC by default. The basic image reader type, TFPCustomImageReader, is equipped with a class function "ImageSize(ASize:TPoint)" which determines the image size in pixels without decoding the image data. In case of a jpeg image, the reader class is TFPReaderJPEG in unit fpreadjpeg; therefore, assuming that the image file is accessed via a stream, the image size can be obtained by a one-liner:
Code: Pascal  [Select][+][-]
  1. Size := TFPReaderJPEG.GetSize(stream);

If you have other image file formats you must pick the correct reader class from the fcl-image units. To help you, the basic fcl-image class, TFPCustomImage, has class methods "FindReaderFromStream(AStream)" which determines the required reader class from the file header or "FindReaderFromFileName(AFileName)" which determines the reader class from the extension of the filename.

Putting these class methods together you can determine the image size for any image format supported by fcl-image by the following short function (do not forget to list the reader units in the uses clause for all file formats requested):
Code: Pascal  [Select][+][-]
  1. uses
  2.   [...] fpImage, fpReadJpeg, fpReadBMP, fpReadPNG, fpReadGIF, fpReadPCX, fpReadTIFF, fpReadTGA;
  3.  
  4. function GetImageSize(AFileName: String; out ASize: TPoint): Boolean;
  5. var
  6.   stream: TStream;
  7.   readerClass: TFPCustomImageReaderClass;
  8. begin
  9.   Result := false;
  10.   ASize := Point(-1,-1);
  11.   stream := TFileStream.Create(AFileName, fmOpenRead);
  12.   try
  13.     readerClass := TFPCustomImage.FindReaderFromStream(stream);
  14.     if readerClass <> nil then
  15.     begin
  16.       ASize := readerClass.ImageSize(stream);
  17.       Result := true;
  18.     end;
  19.   finally
  20.     stream.Free;
  21.   end;
  22. end;

[EDIT] Just noticed that the helper function InternalSize which is called by the reader to determine the image size has not been implemented for the BMP format - I'll post a bug report (-- done: https://gitlab.com/freepascal.org/fpc/source/-/issues/40685).
« Last Edit: March 07, 2024, 05:53:10 pm by wp »

zxandris

  • Full Member
  • ***
  • Posts: 126
Want to add my usual "advertisement" for fcl-image, the fcl graphics library which comes with FPC by default. The basic image reader type, TFPCustomImageReader, is equipped with a class function "ImageSize(ASize:TPoint)" which determines the image size in pixels without decoding the image data. In case of a jpeg image, the reader class is TFPReaderJPEG in unit fpreadjpeg; therefore, assuming that the image file is accessed via a stream, the image size can be obtained by a one-liner:
Code: Pascal  [Select][+][-]
  1. Size := TFPReaderJPEG.GetSize(stream);

If you have other image file formats you must pick the correct reader class from the fcl-image units. To help you, the basic fcl-image class, TFPCustomImage, has class methods "FindReaderFromStream(AStream)" which determines the required reader class from the file header or "FindReaderFromFileName(AFileName)" which determines the reader class from the extension of the filename.

Putting these class methods together you can determine the image size for any image format supported by fcl-image by the following short function (do not forget to list the reader units in the uses clause for all file formats requested):
Code: Pascal  [Select][+][-]
  1. uses
  2.   [...] fpImage, fpReadJpeg, fpReadBMP, fpReadPNG, fpReadGIF, fpReadPCX, fpReadTIFF, fpReadTGA;
  3.  
  4. function GetImageSize(AFileName: String; out ASize: TPoint): Boolean;
  5. var
  6.   stream: TStream;
  7.   readerClass: TFPCustomImageReaderClass;
  8. begin
  9.   Result := false;
  10.   ASize := Point(-1,-1);
  11.   stream := TFileStream.Create(AFileName, fmOpenRead);
  12.   try
  13.     readerClass := TFPCustomImage.FindReaderFromStream(stream);
  14.     if readerClass <> nil then
  15.     begin
  16.       ASize := readerClass.ImageSize(stream);
  17.       Result := true;
  18.     end;
  19.   finally
  20.     stream.Free;
  21.   end;
  22. end;

[EDIT] Just noticed that the helper function InternalSize which is called by the reader to determine the image size has not been implemented for the BMP format - I'll post a bug report (-- done: https://gitlab.com/freepascal.org/fpc/source/-/issues/40685).

Just as a matter of interest and bearing in mind I'm unintiated to this library of anything much :).  Is this a really fast way of reading those sizes.  Because I have something that I need to read such properties on a fair few files at times, usually jpeg but potentially other types, and need something that willl be fast as possible.  Currently my way of doing that is really slow, as it actually has to load the image and read the width, height that way.  I'm hoping this will be much faster?

Thanks,

CJ

Bart

  • Hero Member
  • *****
  • Posts: 5496
    • Bart en Mariska's Webstek
Well, look at the code.
It reads just a few bytes from the (file)stream tot detect dimensions, then it returns.

Bart


wp

  • Hero Member
  • *****
  • Posts: 12518
Find a speed test in the attachment, you can compare all methods presented here with a dummy test (just creating the stream and reading the 1st byte) and with full reading of the image. It is hard to tell which method is the "fastest", and in fact it does not matter, because all of them are hundred or thousand times faster than full image reading. I have a folder with 760 relatively small jpeg images (1.5 Mpx). Using the "full images" method it takes 18 seconds to get the image size, while any of the methods presented here does it within a few tens of milliseconds. Another folder with 163 larger images (5 Mpx) is read in 46 s vs about 0.01 - 0.02 s (there is large scatter even in repeated measurements of the fast methods).

 

TinyPortal © 2005-2018