Determining the size of a JPEG (JFIF) image
Asked Answered
S

5

37

I need to find the size of a JPEG (JFIF) image. The image is not saved as a stand-alone file, so I can't use GetFileSize or any other API such this one (the image is placed in a stream and no other header is present, except the usual JPEG/JFIF header(s)).

I did some research and found out that JPEG images are composed of different parts, each part starting with a frame marker (0xFF 0xXX), and the size of this frame. Using this information I was able to parse a lot of information from the file.

The problem is, I cannot find the size of the compressed data, as it seems there is no frame marker for the compressed data. Also, it seems the compressed data follows the SOS (FFDA) marker and the image ends with the End Of Image (EOI) (FFD9) marker.

A way to accomplish this would be to search for the EOI marker from byte to byte, but I think the compressed data might contain this combination of bytes, right?

Is there an easy and correct way to find the total size of the image? (I would prefer some code/idea without any external library)

Basically, I need the distance (in bytes) between the Start of Image (SOI-FFE0) and End of Image (EOI-FFD9).

Stravinsky answered 12/10, 2009 at 21:35 Comment(2)
Hmm... SOS marker in JFIF file?.. I feel like I've missed something in specs...Divisibility
Original post said "there is no file". He says that there is an SOS and an EOI. Somehow he has a JFIF stream embedded without any outer wrappers.Toxinantitoxin
J
43

The compressed data will not include SOI or EOI bytes, so you are safe there. But the comment, application data, or other headers might. Fortunately, you can identify and skip these sections as the length is given.

The JPEG specification tells you what you need:
http://www.w3.org/Graphics/JPEG/itu-t81.pdf

Look at Table B.1, on page 32. The symbols that have an * do not have a length field following it (RST, SOI, EOI, TEM). The others do.

You will need to skip over the various fields, but it is not too bad.

How to go through:

  1. Start reading SOI (FFD8). This is the start. It should be the first thing in the stream.

    • Then, progress through the file, finding more markers and skipping over the headers:

    • SOI marker (FFD8): Corrupted image. You should have found an EOI already!

    • TEM (FF01): standalone marker, keep going.

    • RST (FFD0 through FFD7): standalone marker, keep going. You could validate that the restart markers count up from FFD0 through FFD7 and repeat, but that is not necessary for measuring the length.

    • EOI marker (FFD9): You're done!

    • Any marker that is not RST, SOI, EOI, TEM (FF01 through FFFE, minus the exceptions above): After the marker, read the next 2 bytes, this is the 16-bit big-endian length of that frame header (not including the 2-byte marker, but including the length field). Skip the given amount (typically length minus 2, since you already got those bytes).

    • If you get an end-of-file before EOI, then you've got a corrupted image.

    • Once you've got an EOI, you've gotten through the JPEG and should have the length. You can start again by reading another SOI if you expect more than one JPEG in your stream.

Jezabelle answered 21/10, 2009 at 17:47 Comment(5)
This helped me a lot, but I found another reference that said when you find the SOS marker, you need to just start reading the data looking for the EOI marker and that will be the end. gvsoft.homedns.org/exif/Exif-explanation.html This matches what I'm seeing with the image I am working on ATM.Bullpup
Do you mean that all of these markers are present in JFIF? I thought they are part of EXIF spec, which in it's turn is meant to be generally incompatible with JFIF?.. Do I really miss a point somewhere?Divisibility
What's missing here is that when you find a SOS (Start of Scan) marker you have to skip not only over the marker segment itself but also over the entropy-coded segment that immediately follows it. Markers cannot occur within entropy-coded segments so just keep scanning until you read FF followed by any byte not equal to 0. (See B.1.1.5 "Entropy-coded data segments", note 2.)Commonly
it is correct, refer to this one for more info: media.mit.edu/pia/Research/deepview/exif.htmlMoss
@Commonly How is it missing? If you progress through the file, finding markers, you will skip all the data bytes after FFDA anyway.Rolo
L
3

Maybe something like this

int GetJpgSize(unsigned char *pData, DWORD FileSizeLow, unsigned short *pWidth, unsigned short *pHeight)
{
  unsigned int i = 0;


  if ((pData[i] == 0xFF) && (pData[i + 1] == 0xD8) && (pData[i + 2] == 0xFF) && (pData[i + 3] == 0xE0)) {
    i += 4;

    // Check for valid JPEG header (null terminated JFIF)
    if ((pData[i + 2] == 'J') && (pData[i + 3] == 'F') && (pData[i + 4] == 'I') && (pData[i + 5] == 'F')
        && (pData[i + 6] == 0x00)) {

      //Retrieve the block length of the first block since the first block will not contain the size of file
      unsigned short block_length = pData[i] * 256 + pData[i + 1];

      while (i < FileSizeLow) {
        //Increase the file index to get to the next block
        i += block_length; 

        if (i >= FileSizeLow) {
          //Check to protect against segmentation faults
          return -1;
        }

        if (pData[i] != 0xFF) {
          return -2;
        } 

        if (pData[i + 1] == 0xC0) {
          //0xFFC0 is the "Start of frame" marker which contains the file size
          //The structure of the 0xFFC0 block is quite simple [0xFFC0][ushort length][uchar precision][ushort x][ushort y]
          *pHeight = pData[i + 5] * 256 + pData[i + 6];
          *pWidth = pData[i + 7] * 256 + pData[i + 8];

          return 0;
        }
        else {
          i += 2; //Skip the block marker

          //Go to the next block
          block_length = pData[i] * 256 + pData[i + 1];
        }
      }

      //If this point is reached then no size was found
      return -3;
    }
    else {
      return -4;
    } //Not a valid JFIF string
  }
  else {
    return -5;
  } //Not a valid SOI header

  return -6;
}  // GetJpgSize
Lepto answered 5/1, 2010 at 10:20 Comment(0)
B
2

Since you don't have any language posted, I'm not sure that this will work, but:

Can you Stream.Seek(0, StreamOffset.End); and then take the stream's position?

Please be specific about what framework you are using.

The real fact of the matter is, if the file header doesn't specify the expected size, you have to seek (or read) to the end of the image.

EDIT

Since you are trying to stream multiple files, you will want to use a streaming friendly container format.

OGG should be a nice fit for this.

JPEG is actually already streaming friendly, but you must guarantee that each file has a valid terminator before sending it down the stream or else you run the risk of crashing your app with unexpected input.

Bartie answered 12/10, 2009 at 21:38 Comment(6)
I could use C or perl, either one will do. However, please note that I cannot use any form of GetFileSize/GetStreamSize, as my stream could contain several pictures or any other information after the picture.Stravinsky
The issue there is if you transfer an incomplete JPEG file. You will never see the terminator. JPEG files expect to be the only thing in a stream. See the last sentence of my answer.Bartie
So, basically, your opinion is that you can't put two jpeg images into one stream without adding an extra header? In other words, you mean that the JPEG headers can't tell how big a JPEG image is?Stravinsky
Pretty much, yeah. I'm not absolutely sure that the JFIF doesn't contain that information, but if you are sending two files at a time down a stream, you absolutely must do your own framing. the Ogg Container format may be what you are looking for.Bartie
It is not my choice to change the stream format. It is how it is and I must process it the best way I can :)Stravinsky
Well, just cross you fingers and hope you recieve the ending block properly.Bartie
S
0

In python, you could just read the whole file into a string object and find the first occurrence of FF E0 and the last occurrence of FF D9. Presumably, these are the start and end that you are looking for?

f = open("filename.jpg", "r")
s = f.read()
start = s.find("\xff\xe0")
end = s.rfind("\xff\xd9")
imagesize = end - start
Salisbarry answered 12/10, 2009 at 21:58 Comment(6)
It is entirely possible for \xff\xd9 to occur in the middle of a jpeg image. The odds of any two bytes matching that pattern is 1/65536.Wrennie
Yes, that is true. However, assuming you have a valid JPEG file, find and rfind will return the first and last occurrences of the string respectively. I think it's fairly safe to assume that the first occurrence is the start and the last occurrence is the end of the image data?Salisbarry
@cgkanchi: The problem is finding the last one, when you don't know where the end of the image is. The OP appears to intend to stream multiple JPEG files through the same stream.Waltner
Also a problem is that the OP does not have a "file" but a stream that has stuff after the JFIF/JPEG image.Toxinantitoxin
@MarkRansom "It is entirely possible for \xff\xd9 to occur in the middle of a jpeg image. The odds of any two bytes matching that pattern is 1/65536" - Yes-and-no: As-per the JPEG/JFIF specification, section B.1.1.5: the spec requires any 0xFF bytes in the initial entropy-encoding output to be written to the file/stream as 0xFF 0x00, so while 0xFF 0xD9 can appear anywhere inside a JPEG file, provided it exists after the SOS marker (0xFF 0xDA) (and before any other EOI marker then) it will always indicate the EOI marker and never be actual coincidental JPEG image data.Accused
@Accused good to know, I didn't know that JPEG had rules for that. Always assumed it was indistinguishable from a random stream of bytes.Wrennie
S
0

In case of C# and .NET there is a simple solution. There is no need to parse anything manually. It reads a whole cluster anyway, but not the complete file contents:

using (var fileStream = new FileStream(imagePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
    using (var image = Image.FromStream(fileStream, false, false))
    {       
         var height = image.Height;
         var width = image.Width;
    }
}

Source: GitHub reference

Serf answered 13/8, 2023 at 9:23 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.