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

Code: Select all

Case $E0FFD8FF;            JPEG

with

Code: Select all

Case -$1F002701;           JPEG
(-$1F002701 = $E0FFD8FF - $100000000).

Then it works with both the 32 bit and the 64 bit PB compiler.


//edit:
Another bug: :mrgreen:

Code: Select all

Case '8FIG';                  GIF
works only in ASCII mode, but not in Unicode mode (32 bit or 64 bit doesn't matter here).

Replace that with

Code: Select all

Case $38464947;               GIF
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 :wink: 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 :wink:) - 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 :wink:

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