www.xbdev.net xbdev - software development
Friday December 14, 2018
home | about | contact | Donations

     
 

JPEG File Format

Get mor data for your byte...

 

Jpeg (.jpg) File Format Explained

by bkenwright@xbdev.net

 

You can't use the internet or anything without coming across the jpg file format.  Its used everywhere, its principle is even used in video compression as well....as its that popular.  Basically, tga's and bmp's are great, but there to big!....jpg with the aid of some tricky and sneaky compression techniques can reduce your image size down to 10% of its original size, and look almost identical to the original!  Great eh!

 

But!...BUT!...its the file format is lossy...you loose information with the jpg format....as if you did a pixel by pixel...or should I say byte by byte comparison with the original, you'd find differences.  This is because the jpg format tries to throw away information that the eye can't notice...its mostly used for compression of realistic images, which contain a whole variety of colours and information...where as simple 2d simple art, can use other simple techniques, like run time compression to gain good compression...as lots of the information is the same....still jpg does good most times.

 

Its a dang hard format to get to grips with...its definetly not one of the easiest to work with...and theres lots of different flavours, versions of it...but once you've got your first simple image decompressed, its not to bad.

 

I'm going to try and take you through the process of how to write your own simple jpg decoder!...it won't be fast, but you'll be able to see how it works and use it for all your cool liittle projects :)

 

 

Starting from common ground is a good think....now rather than just use any random jpg file...I think its best to create a custom one and then you know what your looking for when you read in the data...that way if the bytes or bits arn't aligned, then you'll now about it right away.  So open up your image editor and create a simple image, 8x8 pixels wide as shown below:

 

Image: cross.jpg

 

It can't get much simpler than that...and if we dump the RGB values to a txt file, we can at least see what where looking for...ocne we can find this image, its easy to find any image...just simpler to debug this way :)

 

 

Download Source Code (5kb)
/***************************************************************************/
/*                                                                         */
/*  File: main.cpp                                                         */
/*  Autor: bkenwright@xbdev.net                                            */
/*  URL: www.xbdev.net                                                     */
/*                                                                         */
/***************************************************************************/
/*
    Jpeg File Format Explained
*/
/***************************************************************************/

#include <windows.h>

#include <stdio.h>        // sprintf(..), fopen(..)
#include <stdarg.h>     // So we can use ... (in dprintf)


/***************************************************************************/
/*                                                                         */
/* FeedBack Data                                                           */
/*                                                                         */
/***************************************************************************/

//Saving debug information to a log file
void dprintf(const char *fmt, ...) 
{
    va_list parms;
    char buf[256];

    // Try to print in the allocated space.
    va_start(parms, fmt);
    vsprintf (buf, fmt, parms);
    va_end(parms);

    // Write the information out to a txt file
    FILE *fp = fopen("output.txt", "a+");
    fprintf(fp, "%s", buf);
    fclose(fp);

}// End dprintf(..)


/***************************************************************************/
/*                                                                         */
/* jpeg functions                                                          */
/*                                                                         */
/***************************************************************************/

 
/* Okay before we start getting overwelmed by bits and bytes and
bit shifting and all sorts of special tricks.. we should first
read in the header...which is the first part of the file, and 
can tell us a lot about the jpeg file. */
 
// First lets define some things
#define             SOI                  /*Start of Image*/  0xffd8
#define             EOI                  /*End of Image  */  0xffd9
#define             APP0   /**/          0xffe0 /*to 0xffef APP15*/

void ReadJpgFile(char* szFileName)
{
    byte chunk[2];
    byte sizeofchunk[2];

    FILE *f;
    f = fopen(szFileName, "rb");
    // Lets read in the first 8 bytes
    fread(chunk, 1, 2, f);

    // Output what we have read in.. see what it is?
    dprintf("First 2 bytes are: 0x%x, 0x%x\n", chunk[0], chunk[1] );

    fread(chunk, 1, 2, f);
    fread(sizeofchunk, 1, 2, f);
    short unsigned int size = ((sizeofchunk[0] << 8) | sizeofchunk[1]);

    dprintf("Second 2 bytes are: 0x%x, 0x%x\n", chunk[0], chunk[1] );

    dprintf("Size of our piece of data:%u\n", size);
    // Remeber the size includes the 2 bytes for the size.



    // Now we now how big the next chunk is, we can read it in.
    // I know its an app0 chunk because the chunk was 0xffe0
    char temp[100];
    fread(&temp, size - 2, 1, f);
    temp[size - 2 + 1] = '\0'; // Null terminate the string :)

    dprintf("The APP0 value: %s\n", temp);

    // A stage further, opening up the various sections.

    //  Lets try and read in all the data...see what we get...
    //  Remeber now, its a 2 byte value which tells us what it is, 
    //  then a 2 byte value of how bit it is :)

    while(true)
    {
        fread(chunk, 1, 2, f);

        // If the chunk we read in doesn't begin with 0xff then
        // then its not a valid chunk and so exit.
        if( chunk[0] != 0xff )
        {
            dprintf("Error chunk[0] was: 0x%x, chunk[1]: 0x%x\n", chunk[0], chunk[1]);
            break;
        }


        // If we get 0xffd9 then its the EOF (End Of File)
        if( chunk[1] == 0xd9 )
        {
            dprintf("End Of File\n");
            break;
        }

        fread(sizeofchunk, 2, 1, f);
        short unsigned int size = ((sizeofchunk[0] << 8) | sizeofchunk[1]);

        if( chunk[1] == 0xda )
        {
            // Okay this means we have started to scan the encoded data.
            byte count;
            fread(&count, 1, 1, f);
            dprintf("Start of scan count: %u\n", count);

            //fseek(f, -1, SEEK_CUR);
            while(count != 0xff)
            {
                fread(&count, 1, 1, f);
                if(count == 0xff)
                {
                    fread(&count, 1, 1, f);
                    if(count != 0x00)
                        break;
                }
            }

            dprintf("\nEnd of scan value: 0xff%x\n", count);
            break;


        }

        dprintf("Chunk ID: 0x%x%x,   Size:%u\n", chunk[0], chunk[1], size);
        fseek(f, size-2, SEEK_CUR);

    }

    fclose(f);

}// End ReadJpgFile(..)


/***************************************************************************/
/*                                                                         */
/* Entry Point                                                             */
/*                                                                         */
/***************************************************************************/

int __stdcall WinMain (HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmd, int nShow)
{
  ReadJpgFile("cross.jpg");

  return 0;
}// End WinMain(..)

 

And the output if you run the above demo, you'll find below.....this program is pretty simple...its more of a binary dump I think, and just goes through each tag and dumps its id and size, ....you'll find later one that the tags follow a particular flow.. as you usually have a list of tags that describe the jpg encoding tabs and quantization information, followed by a chunk of data called the SOS (start of scan), where you decode this info using your jpg tables.

 

output.txt (debug output file)
First 2 bytes are: 0xff, 0xd8
Second 2 bytes are: 0xff, 0xe0
Size of our piece of data:16
The APP0 value: JFIF
Chunk ID: 0xffdb, Size:67
Chunk ID: 0xffdb, Size:67
Chunk ID: 0xffc0, Size:17
Chunk ID: 0xffc4, Size:31
Chunk ID: 0xffc4, Size:181
Chunk ID: 0xffc4, Size:31
Chunk ID: 0xffc4, Size:181
Start of scan count: 3

End of scan value: 0xffd9

 

 

Theres a variety of information that is stashed away in the jpg format, but you'll always notice it starting with 0xffd8...and if it contains an APP0 (0xffe0) tag then it can contain the image dimensions and a thumbnail...below shows a layout of a typical jpg header.

 

JPEG Format
Byte Order: Big-endian
Offset   Length   Contents
  0      1 byte   0xff
  1      1 byte   0xd8 (SOI)
  2      1 byte   0xff
  3      1 byte   0xe0 (APP0)
  4      2 bytes  length of APP0 block
  6      5 bytes  "JFIF\0"
 11      1 byte   [Major version]
 12      1 byte   Minor version
 13      1 byte   [Units for the X and Y densities]
                     units = 0:  no units, X and Y specify the pixel aspect ratio
                     units = 1:  X and Y are dots per inch
                     units = 2:  X and Y are dots per cm
 14      2 bytes  [Xdensity:   Horizontal pixel density]
 16      2 bytes  [Ydensity:   Vertical pixel density]
 18      1 byte   [Xthumbnail: Thumbnail horizontal pixel count]
 19      1 byte   [Ythumbnail: Thumbnail vertical pixel count]

                 ...

         1 byte  0xff
         1 byte  0xd9 (EOI) end-of-file

 

 

... ongoing...

 

 

 

 

 

 

 

 
 Visitor: 9534626  { 209.237.238.175 } Copyright (c) 2002-2017 xbdev.net - All rights reserved.
Designated tutorial and software are the property of their respective owners.