Page 1 of 4
Get image width and height without loading image
Posted: Fri Mar 13, 2015 4:22 pm
by Michael Vogel
I'd like to check some image information for thousands of files, so I don't want to use loadimage...
I started wih the following, but would like to add more file formats (and make it bulletproof), so help is welcome...
Code: Select all
Procedure.i ImageFileDimension(filename.s,*size.point)
#ImageMinimumSize=16
#ImageHeaderSize=512
Protected Buffer.s
Protected n,m
Protected Ok
If FindString(".png.bmp.jpg.jpeg.gif.","."+LCase(GetExtensionPart(filename))+".")
If FileSize(filename)>#ImageMinimumSize
If ReadFile(0,filename)
Buffer=Space(#ImageHeaderSize)
If ReadData(0,@Buffer,#ImageHeaderSize)
Select PeekL(@Buffer)
Case '8FIG'; GIF
*size\x=PeekW(@Buffer+6)
*size\y=PeekW(@Buffer+8)
Ok=1
Case $E0FFD8FF; JPEG
n=4
Repeat
n+PeekA(@Buffer+n)<<8+PeekA(@Buffer+n+1)
m=PeekW(@Buffer+n)&$FFFF
n+2
If m=$C0FF
Ok=n+3
n=#ImageHeaderSize
ElseIf (m&$FF)<>$FF
n=#ImageHeaderSize
EndIf
Until n>=#ImageHeaderSize
If Ok
*size\x=PeekA(@Buffer+Ok+2)<<8+PeekA(@Buffer+Ok+3)
*size\y=PeekA(@Buffer+Ok+0)<<8+PeekA(@Buffer+Ok+1)
EndIf
Case 'GNP‰'; PNG
*size\x=PeekA(@Buffer+22)<<8+PeekA(@Buffer+23)
*size\y=PeekA(@Buffer+18)<<8+PeekA(@Buffer+19)
Ok=1
Default
If PeekW(@Buffer)='MB'; BMP
*size\x=PeekA(@Buffer+21)<<8+PeekA(@Buffer+22)
*size\y=PeekA(@Buffer+17)<<8+PeekA(@Buffer+18)
Ok=1
EndIf
EndSelect
EndIf
CloseFile(0)
EndIf
EndIf
EndIf
If Ok
ProcedureReturn *size\x<<16+*size\y
Else
ProcedureReturn #Null
EndIf
EndProcedure
x.point
Debug "> "+Hex(ImageFileDimension("0.gif",x))
Debug x\x
Debug x\y
Re: Get image width and height without loading image
Posted: Fri Mar 13, 2015 8:16 pm
by Little John
Hi Michael,
I like this idea!
Your code already covers important file formats, unfortunately I don't have detailed information about additional formats.
However, I want to help with testing, and I found a bug:
The code does recognize JPEG files when compiled with PB 32 bit, but
not with PB 64 bit (tested with PB 5.31 on Windows).
The reason is, that PeekL() returns a
signed long, i.e. a value between -2147483648 and +2147483647. In contrast, $E0FFD8FF represents an
unsigned long (+3774863615), which is out of the range of signed long.
To correct the bug, you could use an
unsigned Peek long function instead of PeekL().
Or much simpler, just replace
with
(-$1F002701 = $E0FFD8FF - $100000000).
Then it works with both the 32 bit and the 64 bit PB compiler.
//edit:
Another bug:
works only in ASCII mode, but
not in Unicode mode (32 bit or 64 bit doesn't matter here).
Replace that with
The same kind of problem is there with detecting PNG or BMP files.
Re: Get image width and height without loading image
Posted: Sat Mar 14, 2015 12:57 am
by Hi-Toro
I did something similar here:
http://www.blitzbasic.com/codearcs/code ... ?code=2477
I don't know how accurate you've found your own code to be, but this supports BMP, JPEG, TGA and PNG and Gif and I successfully tested it on about 15,000 images -- it might be of some use anyway.
Re: Get image width and height without loading image
Posted: Sat Mar 14, 2015 9:24 am
by Michael Vogel
Thanks for all your tips, I've seen the unicode issue and did a quick and dirty fix by using the byte values already (I don't like that it is needed to do logical ands for each peek to get correct results, like PeekW(...)&$FFFF !).
I definitely did not see the 64 bit problem and from now on I will use the magic number $100000000 in my source codes

Hopefully it works on most of the commonly used image files, maybe somone is willing to do some testing. As I know fo know, it fails for (the old and rare) RGB-coded bitmap files in OS/2 format.
Code: Select all
Procedure.i ImageFileDimension(filename.s,*size.point)
#ImageMinimumSize=16
#ImageHeaderSize=512
#LittleBit=1<<32
#ImageHeaderBMP=$00004D42; 'BM'
#ImageHeaderPCX=$0000000A; -
#ImageHeaderGIF=$38464947-#LittleBit; 'GIF8'
#ImageHeaderJPG=$E0FFD8FF-#LittleBit; -
#ImageHeaderJP2=$0C000000-#LittleBit; -
#ImageHeaderPNG=$474E5089-#LittleBit; '‰PNG'
Protected Buffer.s
Protected n,m
Protected Ok
If FindString(".png.jpg.bmp.jpeg.jp2.gif.pcx.","."+LCase(GetExtensionPart(filename))+".")
Debug "----------"+filename+"--------------"
If FileSize(filename)>#ImageMinimumSize
If ReadFile(0,filename)
Buffer=Space(#ImageHeaderSize)
If ReadData(0,@Buffer,#ImageHeaderSize)
n=PeekL(@Buffer)
;Debug Hex(PeekL(@Buffer))
Select n
Case #ImageHeaderGIF
*size\x=PeekW(@Buffer+6)
*size\y=PeekW(@Buffer+8)
Ok=1
Case #ImageHeaderJPG
n=4
Repeat
n+PeekA(@Buffer+n)<<8+PeekA(@Buffer+n+1)
m=PeekW(@Buffer+n)&$FFFF
n+2
If m=$C0FF
*size\x=PeekA(@Buffer+n+5)<<8+PeekA(@Buffer+n+6)
*size\y=PeekA(@Buffer+n+3)<<8+PeekA(@Buffer+n+4)
Ok=1
n=#ImageHeaderSize
ElseIf (m&$FF)<>$FF
n=#ImageHeaderSize
EndIf
Until n>=#ImageHeaderSize
Case #ImageHeaderJP2
n=PeekB(@Buffer+14)<<8+PeekB(@Buffer+15)+35
If n<#ImageHeaderSize
*size\x=PeekA(@Buffer+n-1)<<8+PeekA(@Buffer+n)
*size\y=PeekA(@Buffer+n-5)<<8+PeekA(@Buffer+n-4)
Ok=1
EndIf
Case #ImageHeaderPNG
*size\x=PeekA(@Buffer+18)<<8+PeekA(@Buffer+19)
*size\y=PeekA(@Buffer+22)<<8+PeekA(@Buffer+23)
Ok=1
Default
n&$FFFF
If n=#ImageHeaderBMP
*size\x=PeekW(@Buffer+18)
*size\y=PeekW(@Buffer+22)&$FFFF
If *size\y<0
*size\y=-*size\y
EndIf
Ok=1
ElseIf n>>8<6 And n&$FF=#ImageHeaderPCX
*size\x=PeekW(@Buffer+8)+1
*size\y=PeekW(@Buffer+10)+1
Ok=1
EndIf
EndSelect
EndIf
CloseFile(0)
EndIf
EndIf
EndIf
If Ok
Debug Str(*size\x)+" x "+Str(*size\y)
ProcedureReturn *size\x<<16+*size\y
Else
*size\x=-1
*size\y=-1
ProcedureReturn #Null
EndIf
EndProcedure
x.point
ImageFileDimension("0.gif",x)
ImageFileDimension("0.jpg",x)
ImageFileDimension("0.jp2",x)
ImageFileDimension("0.png",x)
ImageFileDimension("0.bmp",x)
ImageFileDimension("o.bmp",x)
ImageFileDimension("0.pcx",x)
Supported file formats: BMP, GIF, JPG and Jpeg2000, PCX, PNG.
Re: Get image width and height without loading image
Posted: Sat Mar 14, 2015 9:37 pm
by Little John
Michael Vogel wrote:Procedure.i ImageFileDimension(filename.s,*size.point)
#ImageMinimumSize=16
#ImageHeaderSize=512
#LittleBit=1<<32
#ImageHeaderBMP=$00004D42; 'BM'
#ImageHeaderPCX=$0000000A; -
#ImageHeaderGIF=$38464947-#LittleBit; 'GIF8'
#ImageHeaderJPG=$E0FFD8FF-#LittleBit; -
#ImageHeaderJP2=$0C000000-#LittleBit; -
#ImageHeaderPNG=$474E5089-#LittleBit; '‰PNG'
Maybe I didn't express myself clear enough previously.
Converting an unsigned long to a signed long (by subtracting $100000000) does
only make sense,
if the unsigned long is too big, i.e. if it is not in the range of signed long. In other words, if it is
> 2147483647 (> $7FFFFFFF).
So this subtraction must
not be done with the above constants for GIF, JP2, and PNG. The parts which I have marked red must be removed. Actually, I have tested the code with PB 5.31 64 bit for GIF and PNG files, and it only works correctly after removing the red parts from your above code.
(I don't have any JPEG2000 files.)
Re: Get image width and height without loading image
Posted: Sun Mar 15, 2015 2:23 pm
by Michael Vogel
Could you please tell me what will be the output of the following lines, when executed by using the 64 bit version of purebasic?
Code: Select all
Buffer.s=Space(2001)
#LittleBit=1<<32
#ImageHeaderGIF=$38464947-#LittleBit; 'GIF8'
;Pokey=#ImageHeaderGIF; [1]
;PokeL(@Buffer,Pokey)
PokeL(@Buffer,#ImageHeaderGIF); [2]
For i=0 To 3
Debug Hex(PeekA(@Buffer+i))
Next i
Debug Hex(PeekI(@Buffer))
Debug Hex(PeekL(@Buffer))
Debug Hex(PeekI(@Buffer)&$FFFFFFFF)
Debug Hex(PeekL(@Buffer)&$FFFFFFFF)
Debug Hex(PeekQ(@Buffer)&$FFFFFFFF)
I am wondering, why line [1] results in an overflow, but not line [2] (Purebasic 5.31 32 bit)
Re: Get image width and height without loading image
Posted: Mon Mar 16, 2015 6:14 am
by Little John
Michael Vogel wrote:Could you please tell me what will be the output of the following lines, when executed by using the 64 bit version of purebasic?
Code: Select all
Buffer.s=Space(2001)
#LittleBit=1<<32
#ImageHeaderGIF=$38464947-#LittleBit; 'GIF8'
;Pokey=#ImageHeaderGIF; [1]
;PokeL(@Buffer,Pokey)
PokeL(@Buffer,#ImageHeaderGIF); [2]
For i=0 To 3
Debug Hex(PeekA(@Buffer+i))
Next i
Debug Hex(PeekI(@Buffer))
Debug Hex(PeekL(@Buffer))
Debug Hex(PeekI(@Buffer)&$FFFFFFFF)
Debug Hex(PeekL(@Buffer)&$FFFFFFFF)
Debug Hex(PeekQ(@Buffer)&$FFFFFFFF)
Using PureBasic 5.31 x64 (on Windows), the above code yields the following output:
Code: Select all
47
49
46
38
20002038464947
38464947
38464947
38464947
38464947
Re: Get image width and height without loading image
Posted: Mon Mar 16, 2015 9:38 am
by Michael Vogel
Thank you Little John,
now I understand what happens - even it is still unclear for me, why purebasic has different definitions for "long" (variable.l and PeekL is not compatible).
Code: Select all
Buffer.s=Space(2001)
#LittleBit=1<<32
#ImageHeaderGIF=$F846F947
Pokey=#ImageHeaderGIF
;PokeL(@Buffer,Pokey)
PokeL(@Buffer,#ImageHeaderGIF)
l.l=PeekL(@Buffer)
If l=#ImageHeaderGIF
Debug "Ok Var (only 32 bit)"
EndIf
If PeekL(@Buffer)=#ImageHeaderGIF
Debug "Ok Peek (only 32 bit)"
EndIf
If PeekL(@Buffer)=#ImageHeaderGIF-#LittleBit
Debug "Ok Little Trick"
EndIf
If PeekL(@Buffer)&$FFFFFFFF=#ImageHeaderGIF
Debug "Ok Extra Casting"
EndIf
Re: Get image width and height without loading image
Posted: Mon Mar 16, 2015 3:19 pm
by dige
I would like to use the freeimage.dll for that.
->
http://freeimage.sourceforge.net/
-> supports 35 image formats and RAW images
-> FreeImage_Load( ..., #FIF_LOAD_NOPIXELS) load only header data and possibly metadata
Greetz, dige
Re: Get image width and height without loading image
Posted: Mon Mar 16, 2015 3:58 pm
by Fred
You could use structures instead of peeking at "buffer+x". Each format usually have a standard header which can be mapped to a structure.
Re: Get image width and height without loading image
Posted: Mon Mar 16, 2015 4:16 pm
by Michael Vogel
Fred wrote:You could use structures instead of peeking at "buffer+x". Each format usually have a standard header which can be mapped to a structure.
This would be easy for headers with a fixed size, but there are only few image formats which have implemented a single form of header. Even "simple" BMP may store the image width and height as word or long values, other header types (e.g. JPEG, TIF, Exif) are built by using multiple containers.
In other words, you're right, it would be a perfect approach to do so (so who want to do the first step

) - on the other hand it is a lot of work to get all needed structures coded (I had to read the Exif-Header of photos quite a while ago and it took me weeks to add "all" exceptions for Canon, Nikon, Olympus, Panasonic, Sony etc.)
Re: Get image width and height without loading image
Posted: Mon Mar 16, 2015 4:37 pm
by wilbert
You can use a memory array instead of using Peek.
The Point structure doesn't exist on OS X so I didn't use it.
Also wrote a faster way to do the first check based on filename.
Code: Select all
Structure ImageSize
Width.l
Height.l
EndStructure
Structure MemArray
StructureUnion
a.a[0]
w.w[0]
l.l[0]
EndStructureUnion
EndStructure
Procedure.l CheckImageName(*Name)
!xor eax, eax
!xor ecx, ecx
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!mov rdx, [p.p_Name]
!iiname_loop:
!shrd eax, ecx, 8
CompilerIf #PB_Compiler_Unicode
!mov cx, [rdx]
!add rdx, 2
CompilerElse
!movzx cx, byte [rdx]
!inc rdx
CompilerEndIf
CompilerElse
!mov edx, [p.p_Name]
!iiname_loop:
!shrd eax, ecx, 8
CompilerIf #PB_Compiler_Unicode
!mov cx, [edx]
!add edx, 2
CompilerElse
!movzx cx, byte [edx]
!inc edx
CompilerEndIf
CompilerEndIf
!test cx, cx
!jnz iiname_loop
!or eax, 0x20202020
!cmp eax, '.bmp'
!je iiname_exit
!cmp eax, '.gif'
!je iiname_exit
!cmp eax, '.jp2'
!je iiname_exit
!cmp eax, '.jpg'
!je iiname_exit
!cmp eax, '.pcx'
!je iiname_exit
!cmp eax, '.png'
!je iiname_exit
!cmp eax, 'jpeg'
!je iiname_exit
!xor eax, eax
!iiname_exit:
ProcedureReturn
EndProcedure
Procedure.i ImageFileDimension(FileName.s, *Size.ImageSize)
#ImageMinimumSize = 24
#ImageHeaderSize = 2048
Protected.MemArray *Buffer, *MemArray
Protected.i BytesRead
If CheckImageName(@FileName) And ReadFile(0, FileName, #PB_File_NoBuffering)
*Buffer = AllocateMemory(#ImageHeaderSize, #PB_Memory_NoClear)
BytesRead = ReadData(0, *Buffer, #ImageHeaderSize)
If BytesRead >= #ImageMinimumSize
Select *Buffer\l[0] & $FFFFFFFF
Case $E0FFD8FF; jpeg
*MemArray = *Buffer + *Buffer\a[4] << 8 | *Buffer\a[5] + 4
While *MemArray - *Buffer + 6 < BytesRead
If *MemArray\a[0] = $FF
If *MemArray\a[1] = $C0
*Size\Width = *MemArray\a[5] << 8 | *MemArray\a[6]
*Size\Height = *MemArray\a[3] << 8 | *MemArray\a[4]
Break
EndIf
Else
Break
EndIf
*MemArray + *MemArray\a[2] << 8 | *MemArray\a[3] + 2
Wend
Case $474E5089; png
*Size\Width = *Buffer\a[18] << 8 | *Buffer\a[19]
*Size\Height = *Buffer\a[22] << 8 | *Buffer\a[23]
Case $38464947; gif
*Size\Width = *Buffer\w[3]
*Size\Height = *Buffer\w[4]
EndSelect
EndIf
FreeMemory(*Buffer)
CloseFile(0)
EndIf
EndProcedure
ImageFileDimension("myFile.jpg", @Size.ImageSize)
Debug Size\Width
Debug Size\Height
Re: Get image width and height without loading image
Posted: Mon Mar 16, 2015 5:11 pm
by Michael Vogel
Thank you Wilbert,
it will take some time for me to see every detail, but it seems to be a great step forward - cool

Re: Get image width and height without loading image
Posted: Mon Mar 16, 2015 6:05 pm
by Vera
wilbert wrote:The Point structure doesn't exist on OS X so I didn't use it.
Same on Linux and I quite often stumble across this hinderance
(similar with the color-constants), but in most cases it can be overcome by adding s.th. like this to the code:
Code: Select all
Structure point
x.i
y.i
EndStructure
Define x.point
With this Michael's code runs fine for me, except it doesn't work for some of my jpgs, but I wouldn't know how make out the differences between those files to allow a decent reply. ... So I'm just following along quietly
greets ~ Vera
Re: Get image width and height without loading image
Posted: Mon Mar 16, 2015 7:00 pm
by wilbert
Vera wrote:With this Michael's code runs fine for me, except it doesn't work for some of my jpgs, but I wouldn't know how make out the differences between those files to allow a decent reply. ... So I'm just following along quietly

There's two possible causes I can think of.
1. (least likely), the jpg contains an embedded preview so there's not enough data read from the file to find the image dimensions.
2. the jpg is a progressive jpg file.
I updated my code I posted above so it hopefully works.
To update the code from Michael, don't test only for FFC0 but also for FFC1, FFC2, FFC3, FFC5, FFC6, FFC7, FFC8, FFCA, FFCB, FFCC, FFCD, FFCE, FFCF.
Code: Select all
Structure ImageSize
Width.l
Height.l
EndStructure
Structure MemArray
StructureUnion
a.a[0]
w.w[0]
l.l[0]
EndStructureUnion
EndStructure
Procedure.l CheckImageName(*Name)
!xor eax, eax
!xor ecx, ecx
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!mov rdx, [p.p_Name]
!iiname_loop:
!shrd eax, ecx, 8
CompilerIf #PB_Compiler_Unicode
!mov cx, [rdx]
!add rdx, 2
CompilerElse
!movzx cx, byte [rdx]
!inc rdx
CompilerEndIf
CompilerElse
!mov edx, [p.p_Name]
!iiname_loop:
!shrd eax, ecx, 8
CompilerIf #PB_Compiler_Unicode
!mov cx, [edx]
!add edx, 2
CompilerElse
!movzx cx, byte [edx]
!inc edx
CompilerEndIf
CompilerEndIf
!test cx, cx
!jnz iiname_loop
!or eax, 0x20202020
!cmp eax, '.bmp'
!je iiname_exit
!cmp eax, '.gif'
!je iiname_exit
!cmp eax, '.jp2'
!je iiname_exit
!cmp eax, '.jpg'
!je iiname_exit
!cmp eax, '.pcx'
!je iiname_exit
!cmp eax, '.png'
!je iiname_exit
!cmp eax, 'jpeg'
!je iiname_exit
!xor eax, eax
!iiname_exit:
ProcedureReturn
EndProcedure
Procedure.i ImageFileDimension(FileName.s, *Size.ImageSize)
#ImageMinimumSize = 24
#ImageHeaderSize = 2048
Protected.MemArray *Buffer, *MemArray
Protected BytesRead.i, a.a, n.l
If CheckImageName(@FileName) And ReadFile(0, FileName, #PB_File_NoBuffering)
*Buffer = AllocateMemory(#ImageHeaderSize, #PB_Memory_NoClear)
BytesRead = ReadData(0, *Buffer, #ImageHeaderSize)
If BytesRead >= #ImageMinimumSize
Select *Buffer\l[0] & $FFFFFFFF
Case $E0FFD8FF; jpeg
n = *Buffer\a[4] << 8 | *Buffer\a[5] + 4
If n > 20
FileSeek(0, n)
BytesRead = ReadData(0, *Buffer, #ImageHeaderSize)
n = 0
EndIf
*MemArray = *Buffer + n
While *MemArray - *Buffer + 6 < BytesRead
If *MemArray\a[0] = $FF
a = *MemArray\a[1] - $C0
If a < 16 And a <> 4 And a <> 8
*Size\Width = *MemArray\a[5] << 8 | *MemArray\a[6]
*Size\Height = *MemArray\a[3] << 8 | *MemArray\a[4]
Break
EndIf
Else
Break
EndIf
*MemArray + *MemArray\a[2] << 8 | *MemArray\a[3] + 2
Wend
Case $474E5089; png
*Size\Width = *Buffer\a[18] << 8 | *Buffer\a[19]
*Size\Height = *Buffer\a[22] << 8 | *Buffer\a[23]
Case $38464947; gif
*Size\Width = *Buffer\w[3]
*Size\Height = *Buffer\w[4]
EndSelect
EndIf
FreeMemory(*Buffer)
CloseFile(0)
EndIf
EndProcedure