Code archives/Graphics/Retrieve image information without loading entire image

This code has been declared by its author to be Public Domain code.

Download source code

Retrieve image information without loading entire image by BlitzSupport2009
This code retrieves width, height, number of colours and some other basic (arbitrarily decided!) information directly from image files, ie. you don't have to load the full images into Blitz first.

To use:

Call GetImageInfo with your image filename, test the result in case it's Null (important!), and access the width, height, colors and info fields from the ImageInfo result, eg.

image:ImageInfo = GetImageInfo ("test.jpg")

If image <> Null
	Print image.width
	Print image.height
	Print image.colors
	Print image.info
Else
	Print "Couldn't get image information!"
EndIf


The currently supported formats are the most popular ones for Blitz usage, ie. BMP, JPEG, TGA and PNG (plus Gif). These all seem to work very well now, having been tested on around 11,500 files. In fact, the code seems to return 100% correct results as far as I can tell; if you find any results that state information couldn't be found for one of the supported images, check whether or not it really is the type of image specified by the file extension -- the chances are it isn't!

NB. You can copy/paste/call the individual formats' functions directly, if you prefer, rather than GetImageInfo (), eg. GetJPEGInfo (). Just be sure to include the ImageInfo type at the top of the code.

To do:

More consistent colour information. Currently returns mixed results between formats, eg. an indexed palette-based image might have only 256 colours, each of which can be (eg.) a 24-bit value, so I'm returning 24-bits. I think I'll return a max colours-per-pixel and number of palette entries where applicable.


If you add any other image formats (following the same conventions as the code below), please drop me an email and I'll update this code. (Please only email me if your code can be declared public domain.)
Type ImageInfo
	Field width:Int
	Field height:Int
	Field colors:Int
	Field info:String
End Type

Function GetBMPInfo:ImageInfo (f:String)

	Local image:ImageInfo = New ImageInfo

	If Lower (Left (f, 7)) = "http://"
		f = "http::" + Right (f, Len (f) - 7)
	EndIf
	
	Local bmp:TStream = LittleEndianStream (ReadFile (f))

	If bmp
		
		Try
		
			If ReadByte (bmp) = $42 And ReadByte (bmp) = $4D
	
				For Local loop:Int = 1 To 12
					ReadByte bmp
				Next
				
				Local width:Int
				Local height:Int
				
				If ReadInt (bmp) = 40
					width = ReadInt (bmp)
					height = ReadInt (bmp)
				EndIf
	
				' Not needed...
				
				ReadByte bmp
				ReadByte bmp
				
				Local depth:Int = ReadShort (bmp)
				
				Local compression:Int = ReadInt (bmp)
				
				Local version:String
				
				Select compression
					Case 0
						version = "No compression"
					Case 1
						version = "RLE-8 compression"
					Case 2
						version = "RLE-4 compression"
					Default
						version = "Unknown compression"
				End Select

				image.width	= width
				image.height	= height
				image.colors	= 2 ^ depth
				image.info	= version
			
			Else
				image = Null
			EndIf
		
			Catch ReadFail:Object
			DebugLog "Read error in " + f
			image = Null

		End Try
		
		CloseFile bmp

	Else
		image = Null
	EndIf
		
	Return image

End Function

Function GetGIFInfo:ImageInfo (f:String)

	Local image:ImageInfo = New ImageInfo
	
	If Lower (Left (f, 7)) = "http://"
		f = "http::" + Right (f, Len (f) - 7)
	EndIf
	
	' Read the file...
	
	Local gif:TStream = LittleEndianStream (ReadFile (f))

	If gif
	
		Try
		
			' First 3 bytes must be "GIF"...
			
			Local g:String
			
			Local loop:Int ' For byte-seek loops...
			
			For loop = 0 To 2
				g = g + Chr (ReadByte (gif))
			Next
	
			If g = "GIF"
	
				Print "Got GIF???"
				
				' Next 3 bytes contain version (87a or 89a)...
				
				Local version:String = "GIF version "
				
				For loop = 3 To 5
					version = version + Chr (ReadByte (gif))
				Next
		
				' Dimensions...
				
				Local width:Int = ReadShort (gif)
				Local height:Int = ReadShort (gif)
		
				' Depth is encoded in first 3 bits of this byte!
				
				Local packed:Int = ReadByte (gif)
				Local depth:Int = (packed & 1) + (packed & 1 Shl 1) + (packed & 1 Shl 2) + 1
				Local colors:Int = 2 ^ depth
		
				image.width	= width
				image.height	= height
				image.colors	= colors
				image.info	= version:String

			Else
				image = Null
			EndIf

			Catch ReadFail:Object
			DebugLog "Read error in " + f
			image = Null
			
		End Try
		
		CloseFile gif
	
	Else
		image = Null
	EndIf
	
	Return image

End Function

Function GetJPEGInfo:ImageInfo (f:String)

	Global remote:Int = False ' Used for online images
	
	If Lower (Left (f, 7)) = "http://"
		f = "http::" + Right (f, Len (f) - 7)
		remote = True
	EndIf

	Local image:ImageInfo = New ImageInfo

	Local jpeg:TStream = BigEndianStream (ReadFile (f))

	If jpeg

		Try

			' Start of image (SOI) marker ($FFD8) -- MUST BE PRESENT!
			
			If ReadByte (jpeg) = $FF And ReadByte (jpeg) = $D8
		
				' ... followed by JFIF 'APP0' marker ($FFE0). In theory must be present, but reality says otherwise...
		
				ReadByte jpeg	' Should be $FF but not always true...
				ReadByte jpeg	' Should be $E0 but not always true...
		
				' Start of first block...
				
				Local block_length:Int = ReadShort (jpeg) - 2 ' Less these two bytes!
		
				' Check for JFIF identification string (generally treated as optional)...
		
				Local jfif:Int = 0
				
				' Have to check each byte separately as BlitzMax's 'early-out' feature may mean the
				' wrong number of bytes are read if one doesn't match, eg. If ReadByte (x) And ReadByte (y)...
				
				If ReadByte (jpeg)	= 74 Then jfif = jfif + 1	' ASCII code for "J"
				If ReadByte (jpeg)	= 70 Then jfif = jfif + 1	' ASCII code for "F"
				If ReadByte (jpeg)	= 73 Then jfif = jfif + 1	' ASCII code for "I"
				If ReadByte (jpeg)	= 70 Then jfif = jfif + 1	' ASCII code for "F"
				If ReadByte (jpeg)	= 0 Then jfif = jfif + 1		' 0
				
				If jfif = 5 Then jfif = True Else jfif = False
				
				' Read next two bytes. If the file has a JFIF marker, this is the version string. If
				' not, it's probably random bollocks...
				
				Local major:String = String (ReadByte (jpeg))			' Major revision number
				Local minor:String = RSet (String (ReadByte (jpeg)), 2)	' Minor revision (padded with leading space)
				
				Local version:String
				
				If jfif
		
					' JFIF-compliant! Yay!
					
					minor = Replace (minor, " ", "0")				' Replace space with 0!
					
					' The above changes version from (eg.) "1.2" to "1.02",
					' as in common rendering of "JFIF, version 1.02"...
					
					version = "JFIF version " + major + "." + minor
				
				Else
				
					' Missing either APP0 marker or "JFIF" string. Boo!
					
					version = "Not a 100% JFIF-compliant JPEG file"
					
				EndIf
		
				image.info = version
				
				Local loop:Int ' For byte seek loops...
					
				' Skip block length, minus the previous 7 reads since start of block...
				
				If remote
					
					' Online image, read byte-by-byte...
					
					For loop = 1 To block_length - 7
						ReadByte jpeg
					Next
				Else
					' Local image, just stream...
					SeekStream jpeg, StreamPos (jpeg) + (block_length - 7)
				EndIf
	
				Local back_byte:Int = 0 ' See below...
							
				While Not Eof (jpeg)
		
					' We should be at the start of a block; if not, bail out...
					
	'				DebugLog "---------------------------------------------------------------------------------------"
	'				DebugLog "New block at " + StreamPos (jpeg)
	'				DebugLog "---------------------------------------------------------------------------------------"
	
					Local checkff:Byte ' Byte to be tested for $FF (start of block)...
					
					' See further down -- needed as we can't seek backwards with online images...
					
					If back_byte
						' Byte from last time around...
						checkff = back_byte
					Else
						checkff = ReadByte (jpeg)
					EndIf
					
					If checkff = $FF
					
						back_byte = 0 ' Reset for next loop...
	
						' Read the byte AFTER a $FF marker...
						
						Local afterff:Byte = ReadByte (jpeg)
						
				' 		Some debug information, perhaps of interest...
				'		DebugLog "$FF" + Right (Hex (afterff), 2)
				'		$D8 = Start of Image (SOI) marker
				'		$D9 = End of Image (EOI) marker
				'		$ED = Photoshop data marker
				'		$E1 = Start of Exif data
						
						' Grab next two bytes (length of block) before proceeding...

						block_length = ReadShort (jpeg) - 2 ' The 2 subtracted bytes store the length itself...
						
						If afterff => $C0 And afterff <= $C3
				
							' Bits per pixel...
							
							Local bpp:Int = ReadByte (jpeg)
				
							' Height and width...
							
							Local height:Int = ReadShort (jpeg)' (ReadByte (jpeg) Shl 8) + ReadByte (jpeg)
							Local width:Int = ReadShort (jpeg)'(ReadByte (jpeg) Shl 8) + ReadByte (jpeg)
							
							' Components per pixel (1 for grayscale, 3 for RGB)...
							
							Local components:Int = ReadByte (jpeg)
							
							' Depth/total colours...
							
							Local depth:Int = bpp * components
							Local colors:Int = 2 ^ depth
				
							' Fill in ImageInfo data...
				
							image.width	= width
							image.height	= height
							image.colors	= colors

							' Done!
				
							Exit
	
						Else
						
							' Go to next block...
	
							If remote
							
								' Online image, read byte-by-byte...
								
								For loop = 1 To block_length
									ReadByte jpeg
								Next
								
							Else
								' Local image, just seek...
								SeekStream jpeg, StreamPos (jpeg) + block_length
							EndIf
													
							' Found huge string of zeroes after jumping block_length in PS 7 JPEG! Skip...
							
							Local next_byte:Byte = 0
							
							Repeat
								next_byte = ReadByte (jpeg)
							Until next_byte
							
							' OK, found non-zero byte, so go back one byte and return to start of loop...
							
							back_byte = next_byte ' Store last-read byte (can't seek back with online images)...
	
							' back_byte will be checked at start of While/Wend loop...
							
						EndIf
						
					Else
					
						' Not at a block marker. Oops! Bail...
						
						image = Null
						Exit
						
					EndIf
					
				Wend
		
			Else
				image = Null
			EndIf

			Catch ReadFail:Object
			DebugLog "Read error in " + f
			image = Null

		End Try
		
		CloseFile jpeg
		
	Else
		image = Null
	EndIf
	
	Return image

End Function

Function GetPNGInfo:ImageInfo (f:String)

	If Lower (Left (f, 7)) = "http://"
		f = "http::" + Right (f, Len (f) - 7)
	EndIf

	Local image:ImageInfo = New ImageInfo

	Local png:TStream = BigEndianStream (ReadFile (f))

	If png

		Try
		
			' PNG header...
			
			If ReadByte (png) = $89 And Chr (ReadByte (png)) = "P" And Chr (ReadByte (png)) = "N" And Chr (ReadByte (png)) = "G"
			
				' PNG header continued...
				
				If ReadByte (png) = 13 And ReadByte (png) = 10 And ReadByte (png) = 26 And ReadByte (png) = 10
	
					For Local loop:Int = 1 To 4
						ReadByte png
					Next
					
					' IHDR chunk (always first)...
					
					If Chr (ReadByte (png)) = "I" And Chr (ReadByte (png)) = "H" And Chr (ReadByte (png)) = "D" And Chr (ReadByte (png)) = "R"
				
						Local width:Int	= ReadInt (png)
						Local height:Int	= ReadInt (png)
						Local depth:Int	= ReadByte (png)
	
						Local colortype:Int	= ReadByte (png)
						
						Local info:String
						
						Select colortype
						
							Case 0
								info = "Pixels represented by grayscale values"
	
							Case 2
								info = "Pixels represented by RGB values"
							
							Case 3
								info = "Pixels represented by palette indices"
							
							Case 4
								info = "Pixels represented by grayscale values plus alpha"
							
							Case 6
								info = "Pixels represented by RGB values plus alpha"
							
							Default
								info = "Unknown pixel format"
								
						End Select
	
						image.width	= width
						image.height	= height
						image.colors	= 2 ^ depth
						image.info	= info
					
					Else
						image = Null
					EndIf
					
				Else
					image = Null
				EndIf
			
			Else
				image = Null
			EndIf
			
			Catch ReadFail:Object
			DebugLog "Read error in " + f
			image = Null
			
		End Try
		
		CloseFile png
		
	Else
		image = Null
	EndIf
		
	Return image

End Function

Function GetTGAInfo:ImageInfo (f$)

	If Lower (Left (f, 7)) = "http://"
		f = "http::" + Right (f, Len (f) - 7)
	EndIf

	Local image:ImageInfo = New ImageInfo
	
	Local tga:TStream = LittleEndianStream (ReadFile (f$))

	If tga
		
		Try
		
			Local idlength:Byte = ReadByte (tga)
	
			Local colormap:Byte = ReadByte (tga)
			
			Local imagetype:Byte = ReadByte (tga)
			
			Local info:String
			
			Select imagetype
			
				' First three bits:
				
				Case 0
					info = "No image data present"
				Case 1
					info = "Uncompressed color-mapped image"
				Case 2
					info = "Uncompressed RGB image"
				Case 3
					info = "Uncompressed grayscale image"
	
				' Fourth bit:
				
				Case 9
					info = "RLE-compressed color-mapped image"
				Case 10
					info = "RLE-compressed RGB image"
				Case 11
					info = "RLE-compressed grayscale image"
	
				' From http://www.gamers.org/dEngine/quake3/TGA.txt ...
				
				Case 32
					info = "Color-mapped image (Huffman/Delta/RLE-compressed)"
	
				Case 33
					info = "Color-mapped image (Huffman/Delta/RLE-compressed, 4-pass quadtree)"
	
				Default
					info = "Unknown image type"
					
			End Select
			
			Local colormapstart:Short	= ReadShort (tga)
			Local colormaplength:Short	= ReadShort (tga)
			Local colormapbpp:Byte		= ReadByte (tga)
	
			Local xorigin:Short = ReadShort (tga)
			Local yorigin:Short = ReadShort (tga)
			
			Local width:Short	= ReadShort (tga)
			Local height:Short	= ReadShort (tga)
			
			Local depth:Byte	= ReadByte (tga)
	
			If colormap
	
				depth = colormapbpp
	
	'				DebugLog "Color map start: " + colormapstart
	'				DebugLog "Color map length: " + colormaplength
	'				DebugLog "Color map bits per pixel: " + colormapbpp
	
	'				Select colormap
	'					Case 0
	'						DebugLog "Image has no indexed palette"
	'					Case 1
	'						DebugLog "Image has indexed palette (" + colormaplength + " entries)"
	'					Case colormap =>2 And colormap <= 127
	'						DebugLog "Truevision-specific color map"
	'					Case colormap => 128 And colormap <= 255
	'						DebugLog "Third-party color map"
	'				End Select
	
			EndIf
			
			Local desc:Byte = ReadByte (tga)
			
			Local pixelattr:Byte = desc & (Int (2 ^ 3) | Int (2 ^ 2) | Int (2 ^ 1) | Int (2 ^ 0))
	
			Select pixelattr
			
				Case 0
	
					info = info + ", no alpha mask"
	
				Case 1
	
					info = info + ", with background mask"
	
				Case 8
	
					info = info + ", with alpha mask"
					
			End Select
	
			' 32-bit depth may or may not include an alpha mask, but RGB values are max 24-bit...
			
			If depth = 32 Then depth = 24
			
			Local colors:Int = Int (2 ^ depth)
			
			' NOTE: colors value is the maximum number of colours available to each
			' pixel. This applies even in images with a limited number of palette
			' entries, eg. a palette of 8 indexed colours may still contain 24-bit values!
			
			image.width = width
			image.height = height
			image.colors = colors
			image.info = info
			
			Catch ReadFail:Object
			DebugLog "Read error in " + f
			image = Null

		End Try
	
		CloseFile tga
	
	Else
		image = Null
	EndIf
	
	Return image

End Function

' -----------------------------------------------------------------------------
' File format tests...
' -----------------------------------------------------------------------------

Function GotBMP:Int (f:String)

	If Lower (Left (f, 7)) = "http://"
		f = "http::" + Right (f, Len (f) - 7)
	EndIf
	
	Local result:Int = False
	
	Local bmp:TStream = LittleEndianStream (ReadFile (f))

	If bmp
		
		Try
		
			If ReadByte (bmp) = $42 And ReadByte (bmp) = $4D
	
				For Local loop:Int = 1 To 12
					ReadByte bmp
				Next
				
				If ReadInt (bmp) = 40
					result = True
				EndIf
		
			EndIf
			
			Catch ReadFail:Object
			DebugLog "Read error in " + f
		
		End Try
		
		CloseFile bmp
		
	EndIf

	Return result
	
End Function

Function GotGIF:Int (f:String)

	If Lower (Left (f, 7)) = "http://"
		f = "http::" + Right (f, Len (f) - 7)
	EndIf
	
	Local result:Int = False
	
	Local gif:TStream = LittleEndianStream (ReadFile (f))

	If gif
	
		Try
		
			' First 3 bytes must be "GIF"...
			
			Local g:String ' /beavis: Uh... huh huh!
			
			Local loop:Int ' For byte-seek loops...
			
			For loop = 0 To 2
				g = g + Chr (ReadByte (gif))
			Next
	
			If g = "GIF"
	
				' Next 3 bytes contain version (87a or 89a)...
				
				Local version:String
				
				For loop = 3 To 5
					version = version + Chr (ReadByte (gif))
				Next
				
				If version = "87a" Or version$ = "89a"
					result = True
				EndIf

			EndIf
			
			Catch ReadFail:Object
			DebugLog "Read error in " + f
		
		End Try
		
		CloseFile gif
	
	EndIf

	Return result
			
End Function

Function GotJPEG:Int (f:String)

	If Lower (Left (f, 7)) = "http://"
		f = "http::" + Right (f, Len (f) - 7)
	EndIf
	
	Local result:Int = False
	
	Local jpeg:TStream = BigEndianStream (ReadFile (f))

	If jpeg

		Try
		
			If ReadByte (jpeg) = $FF And ReadByte (jpeg) = $D8

				ReadByte jpeg
				ReadByte jpeg
				
				Local block_length:Int = ReadShort (jpeg) - 2
				
				ReadByte jpeg
				ReadByte jpeg
				ReadByte jpeg
				ReadByte jpeg
				ReadByte jpeg

				ReadByte jpeg
				ReadByte jpeg

				Local loop:Int
					
				For loop = 1 To block_length - 7
					ReadByte jpeg
				Next

				If ReadByte (jpeg) = $FF
					result = True
				EndIf
				
			EndIf
			
			Catch ReadFail:Object
			DebugLog "Read error in " + f
		
		End Try
		
		CloseFile jpeg
		
	EndIf

	Return result
	
End Function

Function GotPNG:Int (f:String)

	If Lower (Left (f, 7)) = "http://"
		f = "http::" + Right (f, Len (f) - 7)
	EndIf
	
	Local result:Int = False
	
	Local png:TStream = BigEndianStream (ReadFile (f))

	If png

		Try

			If ReadByte (png) = $89 And Chr (ReadByte (png)) = "P" And Chr (ReadByte (png)) = "N" And Chr (ReadByte (png)) = "G"
			
				' PNG header continued...
				
				If ReadByte (png) = 13 And ReadByte (png) = 10 And ReadByte (png) = 26 And ReadByte (png) = 10
	
					For Local loop:Int = 1 To 4
						ReadByte png
					Next
					
					' IHDR chunk (always first)...
					
					If Chr (ReadByte (png)) = "I" And Chr (ReadByte (png)) = "H" And Chr (ReadByte (png)) = "D" And Chr (ReadByte (png)) = "R"
						
						result = True
						
					EndIf
					
				EndIf
				
			EndIf
				
			Catch ReadFail:Object
			DebugLog "Read error in " + f
		
		End Try
		
		CloseFile png
		
	EndIf

	Return result
	
End Function

Function GotTGA:Int (f:String, ext:Int = True)

	' Best to take extension into account here, since there are no 100% identifying TGA markers...
	
	If ext
		If Lower (ExtractExt (f)) <> "tga"
			Return False
		EndIf
	EndIf
	
	If Lower (Left (f, 7)) = "http://"
		f = "http::" + Right (f, Len (f) - 7)
	EndIf
	
	Local result:Int = False
	
	Local tga:TStream = LittleEndianStream (ReadFile (f))

	If tga

		Try

			ReadByte tga
			ReadByte tga
			ReadByte tga

			ReadShort tga
			ReadShort tga
			Local mapbits:Byte = ReadByte (tga)
	
			ReadShort tga
			ReadShort tga
			
			' Width and height > 0...
			
			If ReadShort (tga) > 0 And ReadShort (tga) > 0
			
				' Depth > 0 or bits per palette entry > 0...

				Local depth:Byte = ReadByte (tga)

				If depth

					result = True
					
					Select depth
						Case 8
						Case 16
						Case 24
						Case 32
						Default
							result = False
					End Select
					
				Else
					If mapbits
	
						result = True
						
						Select depth
							Case 15
							Case 16
							Case 24
							Case 32
							Default
								result = False
						End Select
					EndIf
				EndIf
				
			EndIf

			Catch ReadFail:Object
			DebugLog "Read error in " + f
		
		End Try
		
		CloseFile tga
		
	EndIf

	Return result
	
End Function

Function GetImageInfo:ImageInfo (f:String)

	Local i:ImageInfo = New ImageInfo
	
	Local ext:String = Lower (ExtractExt (f))

	Select ext:String
	
		Case "jpg", "jpeg", "jpe", "jfif"
			i = GetJPEGInfo (f)
			
		Case "gif"
			i = GetGIFInfo (f)

		Case "bmp"
			i = GetBMPInfo (f)

		Case "png"
			i = GetPNGInfo (f)

		Case "tga"
			i = GetTGAInfo (f)
			
		Default
			i = Null

	End Select

	If i = Null
		If GotJPEG (f) Then i = GetJPEGInfo (f); If i Then i.info = "This is really a JPEG file!"; Return i
		If GotBMP (f) Then i = GetBMPInfo (f); If i Then i.info = "This is really a BMP file!"; Return i
		If GotPNG (f) Then i = GetPNGInfo (f); If i Then i.info = "This is really a PNG file!"; Return i
		If GotGIF (f) Then i = GetGIFInfo (f); If i Then i.info = "This is really a GIF file!"; Return i
		If GotTGA (f) Then i = GetTGAInfo (f); If i Then i.info = "This is really a TGA file!"; Return i
	EndIf
	
	Return i
	
End Function

Comments

BlitzSupport2009
Quick test -- add some image filenames:



If you have a folder full of images (including any sub-folders), this will go through all of them and print the details.




GfK2009
Some JPEG files contain EXIF data - data which shows information from the digital camera/phone the picture was taken on; such as date/time taken, shutter speed, ISO, aperture, white balance and so on.

You checked for any correlation between that and the jpeg images that don't work right?


BlitzSupport2009
No, it doesn't appear to be related to EXIF information. I believe some files don't contain the expected $FF, or possibly $C0, values, though I need to look into it further...

($FF marks the start of a block of information, and a following $C0 marks the block containing width/height information... at a very quick glance, it seems either that $C0 isn't found after a $FF in the files that fail, or the expected $FF block marker isn't found where it's expected at some point.)

I believe EXIF would just be another $FF block (or several), but this program seems to prove EXIF isn't the problem.


Nate the Great2009
I read somewhere that when you open a jpeg, part of the file is run as an exe so perhaps they built some kind of protection into it... maybe check the disc space it takes up, then ctrl-prtSc it and save as the same resolution jpeg. and see if it still doesnt return info?


BlitzSupport2009

part of the file is run as an exe


I don't think that's true!

I've almost got it sussed, though -- only 8 of my 10,000+ JPEGs are failing now (as opposed to around 1,400 yesterday!). I just had to check for some more $FFCx markers and relax the requirement for certain 'correct' details which are often skipped in reality. The only other 'fails' were images with incorrect extensions (eg. a BMP file named as "blah.jpg"), so that's pretty good.

I have an idea what might be wrong with the remaining 8 files, but will be updating this code soon anyway...


BlitzSupport2009
[EDIT: Sorted!]

Gah! Photoshop 7 JPEGs are the only ones failing, and it seems that they're widely incompatible unless saved using the 'Save for Web' function, since Adobe wisely decided to do their own thing that nobody else does. I might have a go at working around this, but I don't think it's that important -- see links below if interested:

http://photo.net/bboard/q-and-a-fetch-msg?msg_id=003j8d

http://www.codeproject.com/KB/graphics/iptc.aspx?fid=2301&df=90&mpp=25&noise=3&sort=Position&view=Quick&fr=26&select=716178

http://www.tow.com/photo/articles/1d_jpeg_iptc/

http://209.85.229.132/search?q=cache:Lf0CVkgJLMwJ:www.adobeforums.com/webx%3F14@@...


BlitzSupport2009
I've updated the code (and my first post) with the fixed JPEG reader. It seems to correctly retrieve the details of any supported image file I throw at it now! The only exceptions have been images named with the wrong extension, just because the test code passes images to each decoder based on extension (so it actually flags mis-named files!).

Just got to test on PPC Mac, attempt to work around stupid PS 7 JPEGs and add some checking for corrupt files (so I don't call ReadByte past the end of broken files, not that this has come up so far).


xlsior2009
I read somewhere that when you open a jpeg, part of the file is run as an exe so perhaps they built some kind of protection into it.


That's not true, although there have been some viruses that did spread through plain images: This was due to an archaic windows 3.x printer image format that contained some macro system which was found to be vulnerable to buffer overflows. This was exploited by renaming the bad files in question to .JPG, which IE would then blindly hand over to the Windows rendering system to display... Except it wasn't really a JPEG but a different file format containing the virus code which then got executed.

The problem was 'fixed' by Microsoft by removing support for the other image format altogether.

Normal JPEGs have no executable code or 'smarts' inside of them, it's just a common lossy compression algorithm.


BlitzSupport2009
For anyone that may be interested, this now appears to provide 100% correct results for all supported image formats -- BMP, PNG, JPEG and Gif so far. Please see updated code (including example in first post), and my updated comments before the code.


TaskMaster2009
I haven't looked at your code, but does your code also determine the pic type by looking at the info, or does it use the file extension?

Can you just feed it a file (such as picture.dat) and it tell you whether it is jpg, gif, bmp, or png as well as give you the info?


Difference2009
This is great!

Any thought of extending it to support exif reading?

(Not the only place I put this request):

/Community/posts.php?topic=84682

http://code.google.com/p/maxmods/issues/detail?id=9


Beaker2009
Thanks James. I had my own hacked jpg size reader before. I just adapted Marks jpg loader but hacked it so it only read the first few bytes - seemed to work ok, but this is much better.


Space_guy2009
Sounds good but dont forget the tga format :)


BlitzSupport2009
I've added TGA support, and (@ TaskMaster) I do plan to add proper image type checking rather than just relying on extension.

Dunno about Exif... I got really bogged down by that in the JPEG code, but might have another go!

(I've put the demos into the first post so the main code can just be copied and pasted into your own projects. You'll have to paste it into the demos to try them!)


BlitzSupport2009
Wa-hey... coming in the next day or so:

Info for "http://www.hi-toro.com/images/test.tga":

	512 x 384, 16777216 colours (Uncompressed color-mapped image, no alpha mask)


Info for "http://www.hi-toro.com/images/test.png":

	300 x 300, 256 colours (Pixels represented by RGB values)


URLs (http:// only) can now be passed as easily as filenames. Online images do (eg. JPEG) block jumps byte-by-byte, while local images can just use SeekStream to keep the same speed.

I need to do some more error-checking (particularly for EOF) before adding any more features (eg. found a couple of instances of not returning Null on failure to retrieve information).


BlitzSupport2009
I've updated this entry with online support (just pass a normal http:// based URL), and I've added Try/Catch around byte read sections, so that broken files are aborted safely. (Tested on some deliberately truncated files, online and locally.)

image:ImageInfo = GetImageInfo ("http://www.hi-toro.com/images/test.png")

If image <> Null
	Print "Width: " + image.width
	Print "Height: " + image.height
	Print "Colours: " + image.colors
	Print "Info: " + image.info
Else
	Print "Couldn't get image information!"
EndIf


Next will be proper file-type checking, rather than choosing which function to call based on the file extension. Also need to add consistency between formats' colour reporting, which is a bit mixed at present.

EDIT: Fixed variable name discrepancy when running without SuperStrict.


BlitzSupport2009
Fixed endianness so it now works correctly on PPC Macs -- it was failing on some file types.

There is a minor problem still on the PPC Mac, but I think it's a Blitz bug/discrepancy. It doesn't crash, though -- it just returns false information for images that are broken/truncated. For normal files it's fine.


BlitzSupport2009
Proper file-type checking now added...

It now checks using the file extension first, as before (since most files are named correctly), but if the result of GetImageInfo () is Null (the expected result where a file's extension is incorrect), it byte-checks the actual file type. (It puts a warning with the correct file type into the .info field, but the width/height/etc results should be correct regardless, assuming you have a valid, non-Null ImageInfo result.)


BlitzSupport2009
The "minor problem" on PPC Mac is non-existent if your BlitzMax installation is up to date -- version 1.33 upwards will be fine. Corrupt files are correctly caught and skipped.


Code Archives Forum