Determination of the width and height of an image from file.
There are situations, where we have some JPEG, PNG or other image files, and we want to determine the effective width and height of the pictures from within a Free Pascal program. How can we do that? This tutorial shows
- how to determine the image size using FCL-Image;
- how to determine the image size using the "picslib" unit;
- for PNG files: how to extract the image size from the binary file.
Determination of the image size using FCL-Image.
FCL-Image is a powerful Lazarus/Free Pascal library, intended for image manipulation. It allows not only to read, convert, rescale, rotate, etc images, but also to create images in different file formats. The library is installed by default with Lazarus, so no need to download or install supplementary packages. FCL-Image is described, including several examples, on the Free Pascal documentation website.
The main units of FCL-Image are fpimage and fpcanvas, the latter one being used when creating an image, thus not needed here. In order to be able to read a given file format, we also have to include the corresponding file format depending "reader" unit, in particular: fpreadbmp, fpreadgif, fpreadjpeg, fpreadpng, fpreadtiff, and fpreadpcx.
To read an image file, we'll have to
- create a TFPCustomImage object: a small TFPMemoryImage object is all we need;
- create a TFPCustomImageReader object: this object has to be specific for the file format used, thus will be one of TFPReaderBMP, TFPReaderGIF, TFPReaderJPEG, TFPReaderPNG, TFPReaderTIFF, TFPReaderPCX;
- loading the file, using the TFPMemoryImage.LoadFromFile method.
The sample program "imagesize" asks the user for filenames (until an empty filename is entered) and tries to determine the width and height of the image stored in these files. The format specific reader is chosen according to the file extension. Here is the code:
program imagesize;
uses
SysUtils, fpimage, fpreadbmp, fpreadgif, fpreadjpeg, fpreadpng, fpreadtiff, fpreadpcx;
var
FileName, FileExt, Mess: string;
Img: TFPCustomImage;
Reader: TFPCustomImageReader;
begin
Writeln; Writeln('Image size determination using LCL.'); Writeln;
Img := TFPMemoryImage.Create(10, 10);
repeat
Mess := '';
Write('Filename (ENTER to terminate)? '); Readln(FileName);
if FileName <> '' then begin
if FileExists(FileName) then begin
FileExt := ExtractFileExt(FileName);
if FileExt = '.bmp' then
Reader := TFPReaderBMP.Create
else if FileExt = '.gif' then
Reader := TFPReaderGIF.Create
else if (FileExt = '.jpg') or (FileExt = '.jpeg') then
Reader := TFPReaderJPEG.Create
else if FileExt = '.png' then
Reader := TFPReaderPNG.Create
else if (FileExt = '.tif') or (FileExt = '.tiff') then
Reader := TFPReaderTIFF.Create
else if FileExt = '.pcx' then
Reader := TFPReaderPCX.Create
else
Mess := 'Unsupported file format!';
if Mess = '' then begin
Img.LoadFromFile(FileName, Reader);
Writeln('Image width = ', Img.Width);
Writeln('Image height = ', Img.Height);
end;
end
else
Mess := 'File not found!';
end;
if Mess <> '' then
Writeln('Error: ', Mess);
Writeln;
until FileName = '';
end.
The screenshot below shows the execution of the first version of my sample program. That version also included the code for portable bitmaps (.pbm) and portable pixmaps (.ppm) files. However, for both of these formats, I got a runtime error (so I removed this feature from the program). Also, I was not able to determine the image size of a big PCX file; in this case, the program hung (?).
Determination of the image size using the "picslib" unit.
The "picslib" unit is not part of Lazarus/Free Pascal (as far as I know); it's also possible that its development is abandoned for several years. Anyway, it works well for BMP, GIF, JPEG, PNG and TIFF files (PCX files are not supported) and determining the width and height of a picture from its file is nothing more than a call to the function GetImageSize(). Click the following link to download "picslib" and dependencies. Be sure to download the following files:
- the main unit picslib.pp;
- the unit StreamHelper, that you need if you want to read the files as streams;
- the image format dependent include files, i.e. all .inc files starting with "pics*".
To use the functions of the unit, place all downloaded files in your project directory (i.e. together with your main source) and add uses SysUtils, PicsLib; to your program source.
The sample program "imagesize2" asks the user for filenames (until an empty filename is entered) and tries to determine the width and height of the image stored in these files. No need, to load the picture in your program, no need to specify the image file format (that is determined by the "picslib" functions according to the file extension). Here is the code:
program imagesize2;
uses
SysUtils, PicsLib;
var
FileName, Mess: string;
Width, Height: LongWord;
begin
Writeln; Writeln('Image size determination using "picslib" unit.'); Writeln;
repeat
Mess := '';
Write('Filename (ENTER to terminate)? '); Readln(FileName);
if FileName <> '' then begin
if FileExists(FileName) then begin
if GetImageSize(FileName, Width, Height) then begin
Writeln('Image width = ', Width);
Writeln('Image height = ', Height);
end
else
Mess := 'Cannot determine image size!'
end
else
Mess := 'Cannot find file specified!';
end;
if Mess <> '' then
Writeln('Error: ', Mess);
Writeln;
until FileName = '';
end.
The screenshot shows the execution of the sample program.
Extracting the image size from a PNG file.
I have written a sample program that reads the beginning of a PNG as binary file, and from the data read, first, concludes if the file effectively is a PNG, and if so, transforms the hexadecimal width and height information into decimal numbers. Obvious advantage: There are no dependencies (no supplementary units needed).
You can find a very good description of the PNG format in the Internet article PNG structure for beginner. For what is needed here, remember the following:
- PNG files start with an 8 byte signature, that is the hexadecimal number sequence: 89504E470D0A1A0A.
- The resting file content is organized in so-called chunks.
- A chunk starts with the chunk length (4 bytes, indicating the length of the chunk data), followed by a 4-bytes chunk type identifier, then the chunk data (of variable length), and finally a 4-byte CRC.
- The width and height of the image, two 4-bytes hexadecimal numbers, are located at offsets +0 resp. +4 in the data part of a chunk with identifier "IHDR".
So, determining the size of a PNG image by reading its binary content, requires the following steps:
- Reading its 8 first bytes and checking if they correspond to the PNG signature.
- Continue reading the file, byte by byte, until we find the chunk identifier "IHDR".
- Being positioned at the beginning at the chunk data (4 bytes after the identifier), reading the 4-bytes width at offset +0 and the 4-bytes height at offset +4, and transforming the two hexadecimal values into decimal numbers.
That's what the sample program "pngsize" does. It asks the user for filenames (until an empty filename is entered) and, if the file has a PNG signature, determines the width and height of the image. Filenames may be specified with or without the .png extension (if the file as entered is not found, the program tries with the same name, with .png added); file extensions other than .png are accepted (all that counts for the program is the PNG signature). The only unit required is SysUtils; I added LazUTF8 in order to be able to access files with UTF8 filenames (this allows to read files with names containing non-ASCII characters on the recent Windows releases). Note, that filenames containing spaces are rejected. In fact, using such names seems not to be possible with the Free Pascal Open procedure (?). Here is the code:
program pngsize;
uses
SysUtils, LazUTF8;
type
TChars = array of Char;
const
// Signature at the beginning of PNG files
PNGSignature: TChars = (
#$89, #$50, #$4E, #$47, #$0D, #$0A, #$1A, #$0A
);
// PNG file IHDR chunk type indicator
// Image width and height may be found in IHDR chunk
IHDR: TChars = (
'I', 'H', 'D', 'R'
);
var
I, P: Integer;
Offset, W, H, M: Int64;
FileName, Mess: string;
OK: Boolean;
Signature, ChunkType, Width, Height: TChars;
PNGFile: file of Char;
function EqualArrays(A1, A2: TChars): Boolean;
var
I: Integer;
AreEqual: Boolean;
begin
AreEqual := True;
if Length(A1) <> Length(A2) then
AreEqual := False
else begin
for I := 0 to Length(A1) - 1 do begin
if A1[I] <> A2[I] then
AreEqual := False;
end;
end;
Result := AreEqual;
end;
begin
Writeln; Writeln('PNG size determination.'); Writeln;
repeat
Mess := '';
Write('FileName (ENTER to terminate)? '); Readln(FileName);
if FileName <> '' then begin
OK := True; P := UTF8Pos(' ', FileName);
if P <> 0 then begin
// File names including spaces are not supported
// This seems to be a limitation of FPC (?)
OK := False;
Mess := 'Invalid filename!'
end
else begin
// Check if file exists (consider file name input with or without .png extension)
OK := FileExists(FileName);
if not OK then begin
FileName += '.png';
OK := FileExists(FileName);
end;
if OK then begin
Assign(PNGFile, Filename); Reset(PNGFile);
if FileSize(PNGFile) < 25 then
Mess := 'File is not a PNG file!'
else begin
// Read the 8 first bytes of the file
SetLength(Signature, 8);
for I := 0 to 7 do
Read(PNGFile, Signature[I]);
if EqualArrays(PNGSignature, Signature) then begin
// Proceed if the 8 first bytes of the file are a PNG signature
// Find the IHDR chunk type indicator
SetLength(ChunkType, 4);
Offset := 8;
Seek(PNGFile, Offset);
for I := 0 to 3 do
Read(PNGFile, ChunkType[I]);
while (not EqualArrays(ChunkType, IHDR)) and (not EoF(PNGFile)) do begin
// As long as the 4 bytes read aren't equal to the IHDR chunk type indicator,
// read another 4 bytes, using an offset of +1 relative to the previous read operation
Inc(Offset);
Seek(PNGFile, Offset);
for I := 0 to 3 do
Read(PNGFile, ChunkType[I]);
end;
if EoF(PNGFile) then
Mess := 'IHDR chunk not found!'
else begin
// IHDR chunk found: Read the 4 bytes width and 4 bytes height
// The width is stored immediately after the chunk indicator,
// the height is stored immediately after the width
SetLength(Width, 4); SetLength(Height, 4);
Offset += 4;
Seek(PNGFile, Offset);
for I := 0 to 3 do
Read(PNGFile, Width[I]);
for I := 0 to 3 do
Read(PNGFile, Height[I]);
// Calculate the width and height from the binary values
W := 0; H := 0; M := 1;
for I := 3 downto 0 do begin
W += M * Ord(Width[I]);
H += M * Ord(Height[I]);
M *= 256;
end;
// Display the PNG image width and height
Writeln('PNG image width = ', W);
Writeln('PNG image height = ', H);
end;
end
else begin
Mess := 'File is not a PNG file!';
end;
end;
Close(PNGFile);
end
else begin
Mess := 'File not found!';
end;
end;
end;
if Mess <> '' then
Writeln('Error: ', Mess);
Writeln;
until FileName = '';
end.
Note: As you probably noticed, I decided to read the PNG as a file of characters. This is not really logical, but it simplifies the search operations for strings (like "IHDR"). For the rest, it's essentially the same as reading it as a file of bytes...
The screenshot shows the determination of the image size of two PNG files, first using the sample program "imgsize" (FCL-Image package), then "pngsize" (binary read). The values found are, of course, the same.
Click the following link to download the sources of the 3 program samples. Please, note, that the files related to the "picslib" unit are not included in the download archive.
If you find this text helpful, please, support me and this website by signing my guestbook.