Ooops
January 15, 2021, 04:49:01 PM

Author Topic: [bmx] Retrieve image information without loading entire image by BlitzSupport [ 1+ years ago ]  (Read 532 times)

Offline BlitzBot

  • Jr. Member
  • **
  • Posts: 1
Title : Retrieve image information without loading entire image
Author : BlitzSupport
Posted : 1+ years ago

Description : 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.

Code: [Select]
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.) [/i]

Code :
Code: BlitzMax
  1. Type ImageInfo
  2.         Field width:Int
  3.         Field height:Int
  4.         Field colors:Int
  5.         Field info:String
  6. End Type
  7.  
  8. Function GetBMPInfo:ImageInfo (f:String)
  9.  
  10.         Local image:ImageInfo = New ImageInfo
  11.  
  12.         If Lower (Left (f, 7)) = "http://"
  13.                 f = "http::" + Right (f, Len (f) - 7)
  14.         EndIf
  15.        
  16.         Local bmp:TStream = LittleEndianStream (ReadFile (f))
  17.  
  18.         If bmp
  19.                
  20.                 Try
  21.                
  22.                         If ReadByte (bmp) = $42 And ReadByte (bmp) = $4D
  23.        
  24.                                 For Local loop:Int = 1 To 12
  25.                                         ReadByte bmp
  26.                                 Next
  27.                                
  28.                                 Local width:Int
  29.                                 Local height:Int
  30.                                
  31.                                 If ReadInt (bmp) = 40
  32.                                         width = ReadInt (bmp)
  33.                                         height = ReadInt (bmp)
  34.                                 EndIf
  35.        
  36.                                 ' Not needed...
  37.                                
  38.                                 ReadByte bmp
  39.                                 ReadByte bmp
  40.                                
  41.                                 Local depth:Int = ReadShort (bmp)
  42.                                
  43.                                 Local compression:Int = ReadInt (bmp)
  44.                                
  45.                                 Local version:String
  46.                                
  47.                                 Select compression
  48.                                         Case 0
  49.                                                 version = "No compression"
  50.                                         Case 1
  51.                                                 version = "RLE-8 compression"
  52.                                         Case 2
  53.                                                 version = "RLE-4 compression"
  54.                                         Default
  55.                                                 version = "Unknown compression"
  56.                                 End Select
  57.  
  58.                                 image.width     = width
  59.                                 image.height    = height
  60.                                 image.colors    = 2 ^ depth
  61.                                 image.info      = version
  62.                        
  63.                         Else
  64.                                 image = Null
  65.                         EndIf
  66.                
  67.                         Catch ReadFail:Object
  68.                         DebugLog "Read error in " + f
  69.                         image = Null
  70.  
  71.                 End Try
  72.                
  73.                 CloseFile bmp
  74.  
  75.         Else
  76.                 image = Null
  77.         EndIf
  78.                
  79.         Return image
  80.  
  81. End Function
  82.  
  83. Function GetGIFInfo:ImageInfo (f:String)
  84.  
  85.         Local image:ImageInfo = New ImageInfo
  86.        
  87.         If Lower (Left (f, 7)) = "http://"
  88.                 f = "http::" + Right (f, Len (f) - 7)
  89.         EndIf
  90.        
  91.         ' Read the file...
  92.        
  93.         Local gif:TStream = LittleEndianStream (ReadFile (f))
  94.  
  95.         If gif
  96.        
  97.                 Try
  98.                
  99.                         ' First 3 bytes must be "GIF"...
  100.                        
  101.                         Local g:String
  102.                        
  103.                         Local loop:Int ' For byte-seek loops...
  104.                        
  105.                         For loop = 0 To 2
  106.                                 g = g + Chr (ReadByte (gif))
  107.                         Next
  108.        
  109.                         If g = "GIF"
  110.        
  111.                                 Print "Got GIF???"
  112.                                
  113.                                 ' Next 3 bytes contain version (87a or 89a)...
  114.                                
  115.                                 Local version:String = "GIF version "
  116.                                
  117.                                 For loop = 3 To 5
  118.                                         version = version + Chr (ReadByte (gif))
  119.                                 Next
  120.                
  121.                                 ' Dimensions...
  122.                                
  123.                                 Local width:Int = ReadShort (gif)
  124.                                 Local height:Int = ReadShort (gif)
  125.                
  126.                                 ' Depth is encoded in first 3 bits of this byte!
  127.                                
  128.                                 Local packed:Int = ReadByte (gif)
  129.                                 Local depth:Int = (packed & 1) + (packed & 1 Shl 1) + (packed & 1 Shl 2) + 1
  130.                                 Local colors:Int = 2 ^ depth
  131.                
  132.                                 image.width     = width
  133.                                 image.height    = height
  134.                                 image.colors    = colors
  135.                                 image.info      = version:String
  136.  
  137.                         Else
  138.                                 image = Null
  139.                         EndIf
  140.  
  141.                         Catch ReadFail:Object
  142.                         DebugLog "Read error in " + f
  143.                         image = Null
  144.                        
  145.                 End Try
  146.                
  147.                 CloseFile gif
  148.        
  149.         Else
  150.                 image = Null
  151.         EndIf
  152.        
  153.         Return image
  154.  
  155. End Function
  156.  
  157. Function GetJPEGInfo:ImageInfo (f:String)
  158.  
  159.         Global remote:Int = False ' Used for online images
  160.        
  161.         If Lower (Left (f, 7)) = "http://"
  162.                 f = "http::" + Right (f, Len (f) - 7)
  163.                 remote = True
  164.         EndIf
  165.  
  166.         Local image:ImageInfo = New ImageInfo
  167.  
  168.         Local jpeg:TStream = BigEndianStream (ReadFile (f))
  169.  
  170.         If jpeg
  171.  
  172.                 Try
  173.  
  174.                         ' Start of image (SOI) marker ($FFD8) -- MUST BE PRESENT!
  175.                        
  176.                         If ReadByte (jpeg) = $FF And ReadByte (jpeg) = $D8
  177.                
  178.                                 ' ... followed by JFIF 'APP0' marker ($FFE0). In theory must be present, but reality says otherwise...
  179.                
  180.                                 ReadByte jpeg   ' Should be $FF but not always true...
  181.                                 ReadByte jpeg   ' Should be $E0 but not always true...
  182.                
  183.                                 ' Start of first block...
  184.                                
  185.                                 Local block_length:Int = ReadShort (jpeg) - 2 ' Less these two bytes!
  186.                
  187.                                 ' Check for JFIF identification string (generally treated as optional)...
  188.                
  189.                                 Local jfif:Int = 0
  190.                                
  191.                                 ' Have to check each byte separately as BlitzMax's 'early-out' feature may mean the
  192.                                 ' wrong number of bytes are read if one doesn't match, eg. If ReadByte (x) And ReadByte (y)...
  193.                                
  194.                                 If ReadByte (jpeg)      = 74 Then jfif = jfif + 1       ' ASCII code for "J"
  195.                                 If ReadByte (jpeg)      = 70 Then jfif = jfif + 1       ' ASCII code for "F"
  196.                                 If ReadByte (jpeg)      = 73 Then jfif = jfif + 1       ' ASCII code for "I"
  197.                                 If ReadByte (jpeg)      = 70 Then jfif = jfif + 1       ' ASCII code for "F"
  198.                                 If ReadByte (jpeg)      = 0 Then jfif = jfif + 1                ' 0
  199.                                
  200.                                 If jfif = 5 Then jfif = True Else jfif = False
  201.                                
  202.                                 ' Read next two bytes. If the file has a JFIF marker, this is the version string. If
  203.                                 ' not, it's probably random bollocks...
  204.                                
  205.                                 Local major:String = String (ReadByte (jpeg))                   ' Major revision number
  206.                                 Local minor:String = RSet (String (ReadByte (jpeg)), 2) ' Minor revision (padded with leading space)
  207.                                
  208.                                 Local version:String
  209.                                
  210.                                 If jfif
  211.                
  212.                                         ' JFIF-compliant! Yay!
  213.                                        
  214.                                         minor = Replace (minor, " ", "0")                               ' Replace space with 0!
  215.                                        
  216.                                         ' The above changes version from (eg.) "1.2" to "1.02",
  217.                                         ' as in common rendering of "JFIF, version 1.02"...
  218.                                        
  219.                                         version = "JFIF version " + major + "." + minor
  220.                                
  221.                                 Else
  222.                                
  223.                                         ' Missing either APP0 marker or "JFIF" string. Boo!
  224.                                        
  225.                                         version = "Not a 100% JFIF-compliant JPEG file"
  226.                                        
  227.                                 EndIf
  228.                
  229.                                 image.info = version
  230.                                
  231.                                 Local loop:Int ' For byte seek loops...
  232.                                        
  233.                                 ' Skip block length, minus the previous 7 reads since start of block...
  234.                                
  235.                                 If remote
  236.                                        
  237.                                         ' Online image, read byte-by-byte...
  238.                                        
  239.                                         For loop = 1 To block_length - 7
  240.                                                 ReadByte jpeg
  241.                                         Next
  242.                                 Else
  243.                                         ' Local image, just stream...
  244.                                         SeekStream jpeg, StreamPos (jpeg) + (block_length - 7)
  245.                                 EndIf
  246.        
  247.                                 Local back_byte:Int = 0 ' See below...
  248.                                                        
  249.                                 While Not Eof (jpeg)
  250.                
  251.                                         ' We should be at the start of a block; if not, bail out...
  252.                                        
  253.         '                               DebugLog "---------------------------------------------------------------------------------------"
  254.         '                               DebugLog "New block at " + StreamPos (jpeg)
  255.         '                               DebugLog "---------------------------------------------------------------------------------------"
  256.        
  257.                                         Local checkff:Byte ' Byte to be tested for $FF (start of block)...
  258.                                        
  259.                                         ' See further down -- needed as we can't seek backwards with online images...
  260.                                        
  261.                                         If back_byte
  262.                                                 ' Byte from last time around...
  263.                                                 checkff = back_byte
  264.                                         Else
  265.                                                 checkff = ReadByte (jpeg)
  266.                                         EndIf
  267.                                        
  268.                                         If checkff = $FF
  269.                                        
  270.                                                 back_byte = 0 ' Reset for next loop...
  271.        
  272.                                                 ' Read the byte AFTER a $FF marker...
  273.                                                
  274.                                                 Local afterff:Byte = ReadByte (jpeg)
  275.                                                
  276.                                 '               Some debug information, perhaps of interest...
  277.                                 '               DebugLog "$FF" + Right (Hex (afterff), 2)
  278.                                 '               $D8 = Start of Image (SOI) marker
  279.                                 '               $D9 = End of Image (EOI) marker
  280.                                 '               $ED = Photoshop data marker
  281.                                 '               $E1 = Start of Exif data
  282.                                                
  283.                                                 ' Grab next two bytes (length of block) before proceeding...
  284.  
  285.                                                 block_length = ReadShort (jpeg) - 2 ' The 2 subtracted bytes store the length itself...
  286.                                                
  287.                                                 If afterff => $C0 And afterff <= $C3
  288.                                
  289.                                                         ' Bits per pixel...
  290.                                                        
  291.                                                         Local bpp:Int = ReadByte (jpeg)
  292.                                
  293.                                                         ' Height and width...
  294.                                                        
  295.                                                         Local height:Int = ReadShort (jpeg)' (ReadByte (jpeg) Shl 8) + ReadByte (jpeg)
  296.                                                         Local width:Int = ReadShort (jpeg)'(ReadByte (jpeg) Shl 8) + ReadByte (jpeg)
  297.                                                        
  298.                                                         ' Components per pixel (1 for grayscale, 3 for RGB)...
  299.                                                        
  300.                                                         Local components:Int = ReadByte (jpeg)
  301.                                                        
  302.                                                         ' Depth/total colours...
  303.                                                        
  304.                                                         Local depth:Int = bpp * components
  305.                                                         Local colors:Int = 2 ^ depth
  306.                                
  307.                                                         ' Fill in ImageInfo data...
  308.                                
  309.                                                         image.width     = width
  310.                                                         image.height    = height
  311.                                                         image.colors    = colors
  312.  
  313.                                                         ' Done!
  314.                                
  315.                                                         Exit
  316.        
  317.                                                 Else
  318.                                                
  319.                                                         ' Go to next block...
  320.        
  321.                                                         If remote
  322.                                                        
  323.                                                                 ' Online image, read byte-by-byte...
  324.                                                                
  325.                                                                 For loop = 1 To block_length
  326.                                                                         ReadByte jpeg
  327.                                                                 Next
  328.                                                                
  329.                                                         Else
  330.                                                                 ' Local image, just seek...
  331.                                                                 SeekStream jpeg, StreamPos (jpeg) + block_length
  332.                                                         EndIf
  333.                                                                                                        
  334.                                                         ' Found huge string of zeroes after jumping block_length in PS 7 JPEG! Skip...
  335.                                                        
  336.                                                         Local next_byte:Byte = 0
  337.                                                        
  338.                                                         Repeat
  339.                                                                 next_byte = ReadByte (jpeg)
  340.                                                         Until next_byte
  341.                                                        
  342.                                                         ' OK, found non-zero byte, so go back one byte and return to start of loop...
  343.                                                        
  344.                                                         back_byte = next_byte ' Store last-read byte (can't seek back with online images)...
  345.        
  346.                                                         ' back_byte will be checked at start of While/Wend loop...
  347.                                                        
  348.                                                 EndIf
  349.                                                
  350.                                         Else
  351.                                        
  352.                                                 ' Not at a block marker. Oops! Bail...
  353.                                                
  354.                                                 image = Null
  355.                                                 Exit
  356.                                                
  357.                                         EndIf
  358.                                        
  359.                                 Wend
  360.                
  361.                         Else
  362.                                 image = Null
  363.                         EndIf
  364.  
  365.                         Catch ReadFail:Object
  366.                         DebugLog "Read error in " + f
  367.                         image = Null
  368.  
  369.                 End Try
  370.                
  371.                 CloseFile jpeg
  372.                
  373.         Else
  374.                 image = Null
  375.         EndIf
  376.        
  377.         Return image
  378.  
  379. End Function
  380.  
  381. Function GetPNGInfo:ImageInfo (f:String)
  382.  
  383.         If Lower (Left (f, 7)) = "http://"
  384.                 f = "http::" + Right (f, Len (f) - 7)
  385.         EndIf
  386.  
  387.         Local image:ImageInfo = New ImageInfo
  388.  
  389.         Local png:TStream = BigEndianStream (ReadFile (f))
  390.  
  391.         If png
  392.  
  393.                 Try
  394.                
  395.                         ' PNG header...
  396.                        
  397.                         If ReadByte (png) = $89 And Chr (ReadByte (png)) = "P" And Chr (ReadByte (png)) = "N" And Chr (ReadByte (png)) = "G"
  398.                        
  399.                                 ' PNG header continued...
  400.                                
  401.                                 If ReadByte (png) = 13 And ReadByte (png) = 10 And ReadByte (png) = 26 And ReadByte (png) = 10
  402.        
  403.                                         For Local loop:Int = 1 To 4
  404.                                                 ReadByte png
  405.                                         Next
  406.                                        
  407.                                         ' IHDR chunk (always first)...
  408.                                        
  409.                                         If Chr (ReadByte (png)) = "I" And Chr (ReadByte (png)) = "H" And Chr (ReadByte (png)) = "D" And Chr (ReadByte (png)) = "R"
  410.                                
  411.                                                 Local width:Int = ReadInt (png)
  412.                                                 Local height:Int        = ReadInt (png)
  413.                                                 Local depth:Int = ReadByte (png)
  414.        
  415.                                                 Local colortype:Int     = ReadByte (png)
  416.                                                
  417.                                                 Local info:String
  418.                                                
  419.                                                 Select colortype
  420.                                                
  421.                                                         Case 0
  422.                                                                 info = "Pixels represented by grayscale values"
  423.        
  424.                                                         Case 2
  425.                                                                 info = "Pixels represented by RGB values"
  426.                                                        
  427.                                                         Case 3
  428.                                                                 info = "Pixels represented by palette indices"
  429.                                                        
  430.                                                         Case 4
  431.                                                                 info = "Pixels represented by grayscale values plus alpha"
  432.                                                        
  433.                                                         Case 6
  434.                                                                 info = "Pixels represented by RGB values plus alpha"
  435.                                                        
  436.                                                         Default
  437.                                                                 info = "Unknown pixel format"
  438.                                                                
  439.                                                 End Select
  440.        
  441.                                                 image.width     = width
  442.                                                 image.height    = height
  443.                                                 image.colors    = 2 ^ depth
  444.                                                 image.info      = info
  445.                                        
  446.                                         Else
  447.                                                 image = Null
  448.                                         EndIf
  449.                                        
  450.                                 Else
  451.                                         image = Null
  452.                                 EndIf
  453.                        
  454.                         Else
  455.                                 image = Null
  456.                         EndIf
  457.                        
  458.                         Catch ReadFail:Object
  459.                         DebugLog "Read error in " + f
  460.                         image = Null
  461.                        
  462.                 End Try
  463.                
  464.                 CloseFile png
  465.                
  466.         Else
  467.                 image = Null
  468.         EndIf
  469.                
  470.         Return image
  471.  
  472. End Function
  473.  
  474. Function GetTGAInfo:ImageInfo (f$)
  475.  
  476.         If Lower (Left (f, 7)) = "http://"
  477.                 f = "http::" + Right (f, Len (f) - 7)
  478.         EndIf
  479.  
  480.         Local image:ImageInfo = New ImageInfo
  481.        
  482.         Local tga:TStream = LittleEndianStream (ReadFile (f$))
  483.  
  484.         If tga
  485.                
  486.                 Try
  487.                
  488.                         Local idlength:Byte = ReadByte (tga)
  489.        
  490.                         Local colormap:Byte = ReadByte (tga)
  491.                        
  492.                         Local imagetype:Byte = ReadByte (tga)
  493.                        
  494.                         Local info:String
  495.                        
  496.                         Select imagetype
  497.                        
  498.                                 ' First three bits:
  499.                                
  500.                                 Case 0
  501.                                         info = "No image data present"
  502.                                 Case 1
  503.                                         info = "Uncompressed color-mapped image"
  504.                                 Case 2
  505.                                         info = "Uncompressed RGB image"
  506.                                 Case 3
  507.                                         info = "Uncompressed grayscale image"
  508.        
  509.                                 ' Fourth bit:
  510.                                
  511.                                 Case 9
  512.                                         info = "RLE-compressed color-mapped image"
  513.                                 Case 10
  514.                                         info = "RLE-compressed RGB image"
  515.                                 Case 11
  516.                                         info = "RLE-compressed grayscale image"
  517.        
  518.                                 ' From http://www.gamers.org/dEngine/quake3/TGA.txt ...
  519.                                
  520.                                 Case 32
  521.                                         info = "Color-mapped image (Huffman/Delta/RLE-compressed)"
  522.        
  523.                                 Case 33
  524.                                         info = "Color-mapped image (Huffman/Delta/RLE-compressed, 4-pass quadtree)"
  525.        
  526.                                 Default
  527.                                         info = "Unknown image type"
  528.                                        
  529.                         End Select
  530.                        
  531.                         Local colormapstart:Short       = ReadShort (tga)
  532.                         Local colormaplength:Short      = ReadShort (tga)
  533.                         Local colormapbpp:Byte          = ReadByte (tga)
  534.        
  535.                         Local xorigin:Short = ReadShort (tga)
  536.                         Local yorigin:Short = ReadShort (tga)
  537.                        
  538.                         Local width:Short       = ReadShort (tga)
  539.                         Local height:Short      = ReadShort (tga)
  540.                        
  541.                         Local depth:Byte        = ReadByte (tga)
  542.        
  543.                         If colormap
  544.        
  545.                                 depth = colormapbpp
  546.        
  547.         '                               DebugLog "Color map start: " + colormapstart
  548.         '                               DebugLog "Color map length: " + colormaplength
  549.         '                               DebugLog "Color map bits per pixel: " + colormapbpp
  550.        
  551.         '                               Select colormap
  552.         '                                       Case 0
  553.         '                                               DebugLog "Image has no indexed palette"
  554.         '                                       Case 1
  555.         '                                               DebugLog "Image has indexed palette (" + colormaplength + " entries)"
  556.         '                                       Case colormap =>2 And colormap <= 127
  557.         '                                               DebugLog "Truevision-specific color map"
  558.         '                                       Case colormap => 128 And colormap <= 255
  559.         '                                               DebugLog "Third-party color map"
  560.         '                               End Select
  561.        
  562.                         EndIf
  563.                        
  564.                         Local desc:Byte = ReadByte (tga)
  565.                        
  566.                         Local pixelattr:Byte = desc & (Int (2 ^ 3) | Int (2 ^ 2) | Int (2 ^ 1) | Int (2 ^ 0))
  567.        
  568.                         Select pixelattr
  569.                        
  570.                                 Case 0
  571.        
  572.                                         info = info + ", no alpha mask"
  573.        
  574.                                 Case 1
  575.        
  576.                                         info = info + ", with background mask"
  577.        
  578.                                 Case 8
  579.        
  580.                                         info = info + ", with alpha mask"
  581.                                        
  582.                         End Select
  583.        
  584.                         ' 32-bit depth may or may not include an alpha mask, but RGB values are max 24-bit...
  585.                        
  586.                         If depth = 32 Then depth = 24
  587.                        
  588.                         Local colors:Int = Int (2 ^ depth)
  589.                        
  590.                         ' NOTE: colors value is the maximum number of colours available to each
  591.                         ' pixel. This applies even in images with a limited number of palette
  592.                         ' entries, eg. a palette of 8 indexed colours may still contain 24-bit values!
  593.                        
  594.                         image.width = width
  595.                         image.height = height
  596.                         image.colors = colors
  597.                         image.info = info
  598.                        
  599.                         Catch ReadFail:Object
  600.                         DebugLog "Read error in " + f
  601.                         image = Null
  602.  
  603.                 End Try
  604.        
  605.                 CloseFile tga
  606.        
  607.         Else
  608.                 image = Null
  609.         EndIf
  610.        
  611.         Return image
  612.  
  613. End Function
  614.  
  615. ' -----------------------------------------------------------------------------
  616. ' File format tests...
  617. ' -----------------------------------------------------------------------------
  618.  
  619. Function GotBMP:Int (f:String)
  620.  
  621.         If Lower (Left (f, 7)) = "http://"
  622.                 f = "http::" + Right (f, Len (f) - 7)
  623.         EndIf
  624.        
  625.         Local result:Int = False
  626.        
  627.         Local bmp:TStream = LittleEndianStream (ReadFile (f))
  628.  
  629.         If bmp
  630.                
  631.                 Try
  632.                
  633.                         If ReadByte (bmp) = $42 And ReadByte (bmp) = $4D
  634.        
  635.                                 For Local loop:Int = 1 To 12
  636.                                         ReadByte bmp
  637.                                 Next
  638.                                
  639.                                 If ReadInt (bmp) = 40
  640.                                         result = True
  641.                                 EndIf
  642.                
  643.                         EndIf
  644.                        
  645.                         Catch ReadFail:Object
  646.                         DebugLog "Read error in " + f
  647.                
  648.                 End Try
  649.                
  650.                 CloseFile bmp
  651.                
  652.         EndIf
  653.  
  654.         Return result
  655.        
  656. End Function
  657.  
  658. Function GotGIF:Int (f:String)
  659.  
  660.         If Lower (Left (f, 7)) = "http://"
  661.                 f = "http::" + Right (f, Len (f) - 7)
  662.         EndIf
  663.        
  664.         Local result:Int = False
  665.        
  666.         Local gif:TStream = LittleEndianStream (ReadFile (f))
  667.  
  668.         If gif
  669.        
  670.                 Try
  671.                
  672.                         ' First 3 bytes must be "GIF"...
  673.                        
  674.                         Local g:String ' /beavis: Uh... huh huh!
  675.                        
  676.                         Local loop:Int ' For byte-seek loops...
  677.                        
  678.                         For loop = 0 To 2
  679.                                 g = g + Chr (ReadByte (gif))
  680.                         Next
  681.        
  682.                         If g = "GIF"
  683.        
  684.                                 ' Next 3 bytes contain version (87a or 89a)...
  685.                                
  686.                                 Local version:String
  687.                                
  688.                                 For loop = 3 To 5
  689.                                         version = version + Chr (ReadByte (gif))
  690.                                 Next
  691.                                
  692.                                 If version = "87a" Or version$ = "89a"
  693.                                         result = True
  694.                                 EndIf
  695.  
  696.                         EndIf
  697.                        
  698.                         Catch ReadFail:Object
  699.                         DebugLog "Read error in " + f
  700.                
  701.                 End Try
  702.                
  703.                 CloseFile gif
  704.        
  705.         EndIf
  706.  
  707.         Return result
  708.                        
  709. End Function
  710.  
  711. Function GotJPEG:Int (f:String)
  712.  
  713.         If Lower (Left (f, 7)) = "http://"
  714.                 f = "http::" + Right (f, Len (f) - 7)
  715.         EndIf
  716.        
  717.         Local result:Int = False
  718.        
  719.         Local jpeg:TStream = BigEndianStream (ReadFile (f))
  720.  
  721.         If jpeg
  722.  
  723.                 Try
  724.                
  725.                         If ReadByte (jpeg) = $FF And ReadByte (jpeg) = $D8
  726.  
  727.                                 ReadByte jpeg
  728.                                 ReadByte jpeg
  729.                                
  730.                                 Local block_length:Int = ReadShort (jpeg) - 2
  731.                                
  732.                                 ReadByte jpeg
  733.                                 ReadByte jpeg
  734.                                 ReadByte jpeg
  735.                                 ReadByte jpeg
  736.                                 ReadByte jpeg
  737.  
  738.                                 ReadByte jpeg
  739.                                 ReadByte jpeg
  740.  
  741.                                 Local loop:Int
  742.                                        
  743.                                 For loop = 1 To block_length - 7
  744.                                         ReadByte jpeg
  745.                                 Next
  746.  
  747.                                 If ReadByte (jpeg) = $FF
  748.                                         result = True
  749.                                 EndIf
  750.                                
  751.                         EndIf
  752.                        
  753.                         Catch ReadFail:Object
  754.                         DebugLog "Read error in " + f
  755.                
  756.                 End Try
  757.                
  758.                 CloseFile jpeg
  759.                
  760.         EndIf
  761.  
  762.         Return result
  763.        
  764. End Function
  765.  
  766. Function GotPNG:Int (f:String)
  767.  
  768.         If Lower (Left (f, 7)) = "http://"
  769.                 f = "http::" + Right (f, Len (f) - 7)
  770.         EndIf
  771.        
  772.         Local result:Int = False
  773.        
  774.         Local png:TStream = BigEndianStream (ReadFile (f))
  775.  
  776.         If png
  777.  
  778.                 Try
  779.  
  780.                         If ReadByte (png) = $89 And Chr (ReadByte (png)) = "P" And Chr (ReadByte (png)) = "N" And Chr (ReadByte (png)) = "G"
  781.                        
  782.                                 ' PNG header continued...
  783.                                
  784.                                 If ReadByte (png) = 13 And ReadByte (png) = 10 And ReadByte (png) = 26 And ReadByte (png) = 10
  785.        
  786.                                         For Local loop:Int = 1 To 4
  787.                                                 ReadByte png
  788.                                         Next
  789.                                        
  790.                                         ' IHDR chunk (always first)...
  791.                                        
  792.                                         If Chr (ReadByte (png)) = "I" And Chr (ReadByte (png)) = "H" And Chr (ReadByte (png)) = "D" And Chr (ReadByte (png)) = "R"
  793.                                                
  794.                                                 result = True
  795.                                                
  796.                                         EndIf
  797.                                        
  798.                                 EndIf
  799.                                
  800.                         EndIf
  801.                                
  802.                         Catch ReadFail:Object
  803.                         DebugLog "Read error in " + f
  804.                
  805.                 End Try
  806.                
  807.                 CloseFile png
  808.                
  809.         EndIf
  810.  
  811.         Return result
  812.        
  813. End Function
  814.  
  815. Function GotTGA:Int (f:String, ext:Int = True)
  816.  
  817.         ' Best to take extension into account here, since there are no 100% identifying TGA markers...
  818.        
  819.         If ext
  820.                 If Lower (ExtractExt (f)) <> "tga"
  821.                         Return False
  822.                 EndIf
  823.         EndIf
  824.        
  825.         If Lower (Left (f, 7)) = "http://"
  826.                 f = "http::" + Right (f, Len (f) - 7)
  827.         EndIf
  828.        
  829.         Local result:Int = False
  830.        
  831.         Local tga:TStream = LittleEndianStream (ReadFile (f))
  832.  
  833.         If tga
  834.  
  835.                 Try
  836.  
  837.                         ReadByte tga
  838.                         ReadByte tga
  839.                         ReadByte tga
  840.  
  841.                         ReadShort tga
  842.                         ReadShort tga
  843.                         Local mapbits:Byte = ReadByte (tga)
  844.        
  845.                         ReadShort tga
  846.                         ReadShort tga
  847.                        
  848.                         ' Width and height > 0...
  849.                        
  850.                         If ReadShort (tga) > 0 And ReadShort (tga) > 0
  851.                        
  852.                                 ' Depth > 0 or bits per palette entry > 0...
  853.  
  854.                                 Local depth:Byte = ReadByte (tga)
  855.  
  856.                                 If depth
  857.  
  858.                                         result = True
  859.                                        
  860.                                         Select depth
  861.                                                 Case 8
  862.                                                 Case 16
  863.                                                 Case 24
  864.                                                 Case 32
  865.                                                 Default
  866.                                                         result = False
  867.                                         End Select
  868.                                        
  869.                                 Else
  870.                                         If mapbits
  871.        
  872.                                                 result = True
  873.                                                
  874.                                                 Select depth
  875.                                                         Case 15
  876.                                                         Case 16
  877.                                                         Case 24
  878.                                                         Case 32
  879.                                                         Default
  880.                                                                 result = False
  881.                                                 End Select
  882.                                         EndIf
  883.                                 EndIf
  884.                                
  885.                         EndIf
  886.  
  887.                         Catch ReadFail:Object
  888.                         DebugLog "Read error in " + f
  889.                
  890.                 End Try
  891.                
  892.                 CloseFile tga
  893.                
  894.         EndIf
  895.  
  896.         Return result
  897.        
  898. End Function
  899.  
  900. Function GetImageInfo:ImageInfo (f:String)
  901.  
  902.         Local i:ImageInfo = New ImageInfo
  903.        
  904.         Local ext:String = Lower (ExtractExt (f))
  905.  
  906.         Select ext:String
  907.        
  908.                 Case "jpg", "jpeg", "jpe", "jfif"
  909.                         i = GetJPEGInfo (f)
  910.                        
  911.                 Case "gif"
  912.                         i = GetGIFInfo (f)
  913.  
  914.                 Case "bmp"
  915.                         i = GetBMPInfo (f)
  916.  
  917.                 Case "png"
  918.                         i = GetPNGInfo (f)
  919.  
  920.                 Case "tga"
  921.                         i = GetTGAInfo (f)
  922.                        
  923.                 Default
  924.                         i = Null
  925.  
  926.         End Select
  927.  
  928.         If i = Null
  929.                 If GotJPEG (f) Then i = GetJPEGInfo (f); If i Then i.info = "This is really a JPEG file!"; Return i
  930.                 If GotBMP (f) Then i = GetBMPInfo (f); If i Then i.info = "This is really a BMP file!"; Return i
  931.                 If GotPNG (f) Then i = GetPNGInfo (f); If i Then i.info = "This is really a PNG file!"; Return i
  932.                 If GotGIF (f) Then i = GetGIFInfo (f); If i Then i.info = "This is really a GIF file!"; Return i
  933.                 If GotTGA (f) Then i = GetTGAInfo (f); If i Then i.info = "This is really a TGA file!"; Return i
  934.         EndIf
  935.        
  936.         Return i
  937.        
  938. End Function


Comments :


BlitzSupport(Posted 1+ years ago)

 Quick test -- add some image filenames:
Code: [Select]

' ************ Copy main code archive entry here! ************

' -----------------------------------------------------------------------------
' D E M O . . .
' -----------------------------------------------------------------------------

' Provide some files of jpg, gif, bmp, tga and png types...

Local f1:String = "test.jpg"
Local f2:String = "test.gif"
Local f3:String = "test.bmp"
Local f4:String = "test.png"
Local f5:String = "test.tga"

' IMPORTANT! ALWAYS check return value from GetImageInfo is non-Null before
' attempting to access information...

' File names defined at top of code!

Print ""

Local info:ImageInfo

' -----------------------------------------------------------------------------
' JPEG test...
' -----------------------------------------------------------------------------

info = GetImageInfo (f1$)

If info
Print "Info for ~q" + f1$ + "~q:"
Print ""
Print "~t" + info.width + " x " + info.height + ", " + info.colors + " colours (" + info.info + ")"
Print ""
Else
Print "No information returned for ~q" + f1$ + "~q!"
EndIf

' -----------------------------------------------------------------------------
' GIF test...
' -----------------------------------------------------------------------------

info = GetImageInfo (f2$)

If info
Print "Info for ~q" + f2$ + "~q:"
Print ""
Print "~t" + info.width + " x " + info.height + ", " + info.colors + " colours (" + info.info + ")"
Print ""
Else
Print "No information returned for ~q" + f2$ + "~q!"
EndIf

' -----------------------------------------------------------------------------
' BMP test...
' -----------------------------------------------------------------------------

info = GetImageInfo (f3$)

If info
Print "Info for ~q" + f3$ + "~q:"
Print ""
Print "~t" + info.width + " x " + info.height + ", " + info.colors + " colours (" + info.info + ")"
Print ""
Else
Print "No information returned for ~q" + f3$ + "~q!"
EndIf

' -----------------------------------------------------------------------------
' PNG test...
' -----------------------------------------------------------------------------

info = GetImageInfo (f4$)

If info
Print "Info for ~q" + f4$ + "~q:"
Print ""
Print "~t" + info.width + " x " + info.height + ", " + info.colors + " colours (" + info.info + ")"
Print ""
Else
Print "No information returned for ~q" + f4$ + "~q!"
EndIf

' -----------------------------------------------------------------------------
' TGA test...
' -----------------------------------------------------------------------------

info = GetImageInfo (f5$)

If info
Print "Info for ~q" + f5$ + "~q:"
Print ""
Print "~t" + info.width + " x " + info.height + ", " + info.colors + " colours (" + info.info + ")"
Print ""
Else
Print "No information returned for ~q" + f5$ + "~q!"
EndIf
If you have a folder full of images (including any sub-folders), this will go through all of them and print the details.
Code: [Select]

' ************ Copy main code archive entry here! ************

Local f:String = "G:DocsMy Pictures" ' Your image folder!

' -----------------------------------------------------------------------------
' D E M O . . .
' -----------------------------------------------------------------------------

Print ""
ParseFolder f ' Defined above!
End

Function PrintImageInfo (f:String)

Local info:ImageInfo = GetImageInfo (f:String)

If info
Print "Info for ~q" + f:String + "~q:"
Print ""
Print "~t" + info.width + " x " + info.height + ", " + info.colors + " colours (" + info.info + ")"
Print ""
Else
Print "No information returned for ~q" + f:String + "~q!"
EndIf

Print ""

End Function

Function ParseFolder (dir:String)

If Right (dir:String, 1) <> "" And Right (dir:String, 1) <> "/"
dir:String = dir:String + "/"
EndIf

Local folder:Int = ReadDir (dir:String)

If folder

Repeat

Local entry:String = NextFile (folder)

If entry = "" Then Exit

If entry <> "." And entry <> ".."

Local file:String
Local full:String

If FileType (dir + entry) = FILETYPE_FILE

file = entry

full = dir

If Right (full, 1) <> "" And Right (full, 1) <> "/"
full = full + ""
EndIf

full = full + file

PrintImageInfo (full)

Else

If FileType (dir + entry) = FILETYPE_DIR

file = entry

If file <> "." And file <> ".."

Local ffolder:String = dir

If Right (ffolder, 1) <> "" And Right (ffolder, 1) <> "/"
ffolder = ffolder + ""
EndIf

ffolder = ffolder + file

ParseFolder (ffolder)

EndIf

EndIf

EndIf

EndIf

Forever

EndIf

End Function



GfK(Posted 1+ years ago)

 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?


BlitzSupport(Posted 1+ years ago)

 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 <a href="http://www.takenet.or.jp/~ryuuji/minisoft/exifread/english/download.html" target="_blank">this program[/url] seems to prove EXIF isn't the problem.


Nate the Great(Posted 1+ years ago)

 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?


BlitzSupport(Posted 1+ years ago)

 <div class="quote"> part of the file is run as an exe </div>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...


BlitzSupport(Posted 1+ years ago)

 [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:<a href="http://photo.net/bboard/q-and-a-fetch-msg?msg_id=003j8d" target="_blank">http://photo.net/bboard/q-and-a-fetch-msg?msg_id=003j8d[/url]<a href="http://www.codeproject.com/KB/graphics/iptc.aspx?fid=2301&df=90&mpp=25&noise=3&sort=Position&view=Quick&fr=26&select=716178" target="_blank">http://www.codeproject.com/KB/graphics/iptc.aspx?fid=2301&df=90&mpp=25&noise=3&sort=Position&view=Quick&fr=26&select=716178[/url]<a href="http://www.tow.com/photo/articles/1d_jpeg_iptc/" target="_blank">http://www.tow.com/photo/articles/1d_jpeg_iptc/[/url]http://209.85.229.132/search?q=cache:Lf0CVkgJLMwJ:www.adobeforums.com/webx%3F14@@...


BlitzSupport(Posted 1+ years ago)

 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).


xlsior(Posted 1+ years ago)

 <div class="quote"> 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. </div>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.


BlitzSupport(Posted 1+ years ago)

 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.


TaskMaster(Posted 1+ years ago)

 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?


Difference(Posted 1+ years ago)

 This is great!Any thought of extending it to support exif reading?(Not the only place I put this request):<a href="../Community/postsb3b2.html?topic=84682" target="_blank">/Community/posts.php?topic=84682[/url]<a href="http://code.google.com/p/maxmods/issues/detail?id=9" target="_blank">http://code.google.com/p/maxmods/issues/detail?id=9[/url]


Beaker(Posted 1+ years ago)

 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_guy(Posted 1+ years ago)

 Sounds good but dont forget the tga format :)


BlitzSupport(Posted 1+ years ago)

 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!)


BlitzSupport(Posted 1+ years ago)

 Wa-hey... coming in the next day or so:
Code: [Select]
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).


BlitzSupport(Posted 1+ years ago)

 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.)
Code: [Select]
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.


BlitzSupport(Posted 1+ years ago)

 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 <a href="../Community/postsec9a-4.html?topic=84743" target="_blank">Blitz bug/discrepancy[/url]. It doesn't crash, though -- it just returns false information for images that are broken/truncated. For normal files it's fine.


BlitzSupport(Posted 1+ years ago)

 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.)


BlitzSupport(Posted 1+ years ago)

 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. [/i]

 

SimplePortal 2.3.6 © 2008-2014, SimplePortal