Click here to Skip to main content
15,868,141 members
Articles / Programming Languages / VC++

PNG Image Steganography with libpng

Rate me:
Please Sign up or sign in to vote.
4.96/5 (16 votes)
22 Apr 2013GPL317 min read 61.3K   4.8K   33   8
Performing steganography on PNG images

Introduction 

Digital steganography is defined as hiding messages within digital media. Here we'll take a look at hiding information in images. In this article I introduce the basic concepts of digital steganography and a proof of concept using PNG images. If you're reading this, I assume a strong knowledge of C++ and a good grasp on binary arithmetic.

Fun Fact: This is a common technique used by clandestine organizations and terrorist groups such as Al Qaeda alike to covertly share information. Two parties agree on an image and a location beforehand. The party that wants to communicate information encodes hidden data into the image, uploads it, and the second party downloads it. To any onlookers, the image is completely normal, but underneath the hood the image contains any arbitrary hidden data. An example from Al Qaeda is here

Background  

In the example I will show you, we will hide data in the least significant bits of a PNG image. There are many ways to hide data  in an image and this is just one technique of the many.  Here I will discuss at a rudimentary level some of the details of the PNG file specification to give the necessary background to understand the code.

There are three varieties of PNG image; we will examine truecolor images. The other two are greyscale and palette. We will not be using an alpha channel. For the curious, the alpha channel provides color transparency information.  

PNG images are comprised of chunks.  There are many different types of chunks each serving various roles. The three chunk types we will concern ourselves with are the IHDR, IDAT, and IEND chunks.

The full specification is here. Below is a synopsis of the relevant parts for this article. 

Chunk layout 

Each chunk consists of three or four fields. 

Length - A four-byte unsigned integer giving the number of bytes in the chunk's data field. The length counts only the data field, not itself, the chunk type, or the CRC. Zero is a valid length. Although encoders and decoders should treat the length as unsigned, its value shall not exceed 231-1 bytes.

Chunk Type - A sequence of four bytes defining the chunk type. Each byte of a chunk type is restricted to the decimal values 65 to 90 and 97 to 122. These correspond to the uppercase and lowercase ISO 646 letters (A-Z and a-z) respectively for convenience in description and examination of PNG datastreams. Encoders and decoders shall treat the chunk types as fixed binary values, not character strings. For example, it would not be correct to represent the chunk type IDAT by the equivalents of those letters in the UCS 2 character set.

Chunk Data - The data bytes appropriate to the chunk type, if any. This field can be of zero length.

CRC - A four-byte CRC (Cyclic Redundancy Code) calculated on the preceding bytes in the chunk, including the chunk type field and chunk data fields, but not including the length field. The CRC can be used to check for corruption of the data. The CRC is always present, even for chunks containing no data. 

The IHDR Chunk

The four-byte chunk type field contains the decimal values 73 72 68 82.  

The IHDR chunk shall be the first chunk in the PNG datastream. It contains:

Width4 bytes
Height4 bytes
Bit depth1 byte
Colour type1 byte
Compression method1 byte
Filter method1 byte
Interlace method1 byte 

Width and height give the image dimensions in pixels. They are PNG four-byte unsigned integers. Zero is an invalid value. Bit depth is a single-byte integer giving the number of bits per sample. Sample here means one color. Valid values are 1, 2, 4, 8, and 16, although not all values are allowed for all color types. Each pixel is made up of three bytes. Each of the three bytes represents a different color, in this case red, green, blue. These colors combined make up the one pixel you actually see. 

The IDAT Chunk 

The four-byte chunk type field contains the decimal values 73 68 65 84. The IDAT chunk contains the actual image data which is the output stream of the compression algorithm. If you're curious about the filtering and compression on PNG images check out  Filtering and Compression. There may be multiple IDAT chunks; if so, they shall appear consecutively with no other intervening chunks. The compressed datastream is then the concatenation of the contents of the data fields of all the IDAT chunks. 

The IEND Chunk 

The four-byte chunk type field contains the decimal values 73 69 78 68. The IEND chunk marks the end of the PNG datastream. The chunk's data field is empty.

Steganography on PNG 

As mentioned earlier each pixel is arranged in 3 bytes the first red, the second green, and the third blue. Various studies have yielded different results, but the all-reliable source wikipedia says that the human eye can distinguish approximately 10 million different colors. With three bytes total we can represent 2^(8+3) or 16,777,216 different colors. That means that there approximately 6,777,216 colors that we can represent, but the human eye won't notice the change as compared to one of the other 10 million colors. 

Said another way:

Given the following truecolor pixel (this data would be located in an IDAT chunk) 

Red Part -> 10100100 Green Part -> 11101100 Blue Part ->  1010100 

The human eye would not be able to distinguish the difference if we changed the three parts to

Red Part -> 10100101 Green Part -> 11101101 Blue Part ->  1010101

You can probably see where this is going. This means that I can change the least significant bit of each byte and no one will be able to visually tell the difference between the original pixel and the changed pixel unless of course they have super human highdef eyes (which to my extensive medical knowledge don't exist Poke tongue | <img src=).

What we're going to do is leverage this to hide messages in the least significant bits in the following manner:

One byte of a message to hide: 10101010 

8 bytes of image data (that's two pixels and the red and green bytes of a third pixel) 

11110000, 10101010, 11001100, 11100011, 11111111,  00000000, 00001111, 10011011 

Encode our hidden message from least significant bit (LSB) to most significant bit (MSB) into the least significant bits of the image data. The image data then becomes: 

11110000, 10101011, 11001100, 11100011, 11111110,  00000001, 00001110, 10011011

Now we've got one byte of message data hidden in our image. Do it several thousand more times and we can hide quite a lot of data. Clearly this isn't the most efficient scheme since the PNG image we use we'll need to be 8 times the size of the hidden data. We're going for simple here rather than super slick. We could get a lot more efficiency by leveraging other hiding places, compression techniques, using more bits that wouldn't be detectable, alpha channels, text channels, and a million other things, but here we'll stick to the least significant bit Big Grin | <img src=

Using the code

Programmer Note: Everything below was done in Visual Studio 2012 

To work with the PNG images I made a PNG image class to implement the steganography portion and used the libpng library and the zlib library to actually do all the PNG manipulation and such. It's worth mentioning again that PNG image files are filtered and then compressed so that they take up less space. For this reason we can't edit the IDAT chunks of a raw PNG or you'll get some really funky results (I tried just for giggles and it's more than a little noticeable when you try to encode something). That being said, we need libpng to decompress and then unfilter the image for us. The compression algorithm used by libpng is deflate, which is implemented by zlib in case you were wondering why we need zlib.

You can view the libpng documentation here

The PNG_file class definition is as follows:

C++
#include <png.h>

/* Class PNG_file
 * Contains the data for a PNG file object
 */
class PNG_file {

public:
    
    //Constructor
    PNG_file(const char *inputFileName);

    //Function for encoding data into the PNG from a file
    void encode(const char *fileToEncodeName);

    //Function for outputing the newly created PNG to a file
    void outputPNG(const char *outputFileName);

    //Function for outputing the decoded PNG to a file
    void decode(const char *outputFileName);

private:
    png_bytep* row_pointers;
    png_infop info_ptr;
    png_structp read_ptr;
    png_structp write_ptr;
};  

Don't worry too much about understanding what each bit means just yet. We'll go through it.

Reading in the PNG Image

So the first thing we need to do is uncompress and unfilter our PNG image. This all happens in the PNG_file constructor:

C++
 /* PNG Constructor
 * Constructor for the PNG_file class Simply reads in a PNG file
 */
PNG_file::PNG_file(const char *inputFileName) {

    FILE * inputFile;

    unsigned char header[BYTE_SIZE];

    inputFile = fopen (inputFileName,"rb");

    //Check if the file opened
    if(!inputFile)
        exit(1);

    // START READING HERE

    fread(header, 1, PNG_SIG_LENGTH, inputFile);

    //Check if it is a PNG
    if(png_sig_cmp(header, 0, PNG_SIG_LENGTH))
        exit(1);


    //Set up libPNG data structures and error handling
    read_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);

    if (!read_ptr)
        exit(1);

    info_ptr = png_create_info_struct(read_ptr);

    if (!info_ptr) {
        png_destroy_read_struct(&read_ptr,
            (png_infopp)NULL, (png_infopp)NULL);
        exit(1);
    }

    png_infop end_info = png_create_info_struct(read_ptr);

    if (!end_info) {
        png_destroy_read_struct(&read_ptr, &info_ptr,
            (png_infopp)NULL);
        exit(1);
    }
    //End data structure/error handling setup

    //Initialize IO for PNG
    png_init_io(read_ptr, inputFile);

    //Alert libPNG that we read PNG_SIG_LENGTH bytes at the beginning
    png_set_sig_bytes(read_ptr, PNG_SIG_LENGTH);

    //Read the entire PNG image into memory
    png_read_png(read_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);

    row_pointers = png_get_rows(read_ptr, info_ptr);

        //Make sure the bit depth is correct
    if(read_ptr->bit_depth != BYTE_SIZE)
        exit(1);

    fclose(inputFile);
} 

Explanation: First we open up a file stream on the PNG file that we want to encode our hidden data into and declare a variable header that will contain the PNG file signature. The lines below read in the PNG signature and then check to make sure that the signature is valid using the libpng function png_sig_cmp:

C++
fread(header, 1, PNG_SIG_LENGTH, inputFile);

//Check if it is a PNG
if(png_sig_cmp(header, 0, PNG_SIG_LENGTH))
    exit(1);

Following that we set up some necessary libpng data structures. The primary takeaway from this is that the data pointed to by read_ptr will end up containing all of the PNG image data structures and information. As a side note info_ptr will contain the IHDR header chunk data. If you wanted to you could perform general image transformations by manipulating the IHDR header. Here's the code:

C++
//Set up libPNG data structures and error handling
read_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);

if (!read_ptr)
    exit(1);

info_ptr = png_create_info_struct(read_ptr);

if (!info_ptr) {
    png_destroy_read_struct(&read_ptr,
        (png_infopp)NULL, (png_infopp)NULL);
    exit(1);
}

png_infop end_info = png_create_info_struct(read_ptr);

if (!end_info) {
    png_destroy_read_struct(&read_ptr, &info_ptr,
        (png_infopp)NULL);
    exit(1);
}
//End data structure/error handling setup 

The following line initializes IO on the PNG: 

C++
//Initialize IO for PNG
png_init_io(read_ptr, inputFile); 

libpng requires us to tell it if we've already read any data from the filestream before we read the image so we tell in with the following line:

C++
//Alert libPNG that we read PNG_SIG_LENGTH bytes at the beginning
png_set_sig_bytes(read_ptr, PNG_SIG_LENGTH); 

After that we read the entire PNG into memory (efficiency again wasn't a prime concern on this one Wink | <img src=) and then set row_pointers to point to an array of pointers. Each pointer in that array points to one row of image data. It would look something like this:

row_pointers -> row1ptr -> row1data

row2ptr -> row2data 

row3ptr -> row3data

etc... 

C++
//Read the entire PNG image into memory
png_read_png(read_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);

row_pointers = png_get_rows(read_ptr, info_ptr);

The final line at least checks if the bit depth is correct. We really should check a lot more things to ensure we have a compatible image, but this is just a POC. 

C++
//TODO ADD A CHECK SO WE ONLY USE COMPATIBLE PNG IMAGES
if(read_ptr->bit_depth != BYTE_SIZE)
    exit(1);

Encoding Data Into the Image

The next thing to do is encode data into the image we read into memory. This is performed by the following function: 

C++
void PNG_file::encode(const char *fileToEncodeName) {
    //BEGIN ENCODING HERE

    FILE * fileToEncode;

    unsigned char buffer = 0;

    fileToEncode = fopen (fileToEncodeName,"rb");

    //Check if the file opened
    if(!fileToEncode)
        exit(1);

    //TODO CONSIDER ADDING CHECK FOR FILES THAT ARE TOO BIG
    unsigned long size = filesize(fileToEncodeName);

    //This section of code encodes the input file into the picture
    //It encodes the input file bit by bit into the least significant
    //bits of the original picture file
    for(int y=0; y < read_ptr->height; y++) {
        int x=0;
        //Write the file size into the file y==0 ensures that it only happens
        //once
        if(y == 0)
            for(x; x < SIZE_WIDTH; x++) {
                if((size & ipow(2,x)))
                    *(row_pointers[y]+x) |= 1;
                else
                    *(row_pointers[y]+x) &= 0xFE;
            }
        for(x; x < read_ptr->width*3; x++) {
            if(x%BYTE_SIZE == 0) {
                if(!fread(&buffer, 1, 1, fileToEncode)) 
                    goto loop_end;
            }
            //png_bytep here = row_pointers[y]+x; for debugging
            if((buffer & ipow(2,x%BYTE_SIZE)))
                *(row_pointers[y]+x) |= 1;
            else
                *(row_pointers[y]+x) &= 0xFE;
        }
        //Make sure that we did not use a file too large that it can't be encoded
        if(y >= read_ptr->height)
            exit(1);
    }

    //goto jumps here to break out of multiple loops
    loop_end:

    fclose(fileToEncode);

} 

There isn't much to mention about the first part other then that the variable buffer will contain the individual bytes being encoded from the hidden message file into the PNG image in question. The size variable contains the size of the file to be encoded. Here's the code:

C++
//BEGIN ENCODING HERE

FILE * fileToEncode;

unsigned char buffer = 0;

fileToEncode = fopen (fileToEncodeName,"rb");

//Check if the file opened
if(!fileToEncode)
    exit(1);

//TODO CONSIDER ADDING CHECK FOR FILES THAT ARE TOO BIG
unsigned long size = filesize(fileToEncodeName); 

The filesize function is just a helper function that calculates the size of a file. Now the meet of the encode function is a bit more complex so I'll do my best to break it down line for line:

C++
//This section of code encodes the input file into the picture
//It encodes the input file bit by bit into the least significant
//bits of the original picture file
for(int y=0; y < read_ptr->height; y++) {
    int x=0;
    //Write the file size into the file y==0 ensures that it only happens
    //once
    if(y == 0)
        for(x; x < SIZE_WIDTH; x++) {
            if((size & ipow(2,x)))
                *(row_pointers[y]+x) |= 1;
            else
                *(row_pointers[y]+x) &= 0xFE;
        }
    for(x; x < read_ptr->width*3; x++) {
        if(x%BYTE_SIZE == 0) {
            if(!fread(&buffer, 1, 1, fileToEncode)) 
                goto loop_end;
        }
        //png_bytep here = row_pointers[y]+x; For debugging
        if((buffer & ipow(2,x%BYTE_SIZE)))
            *(row_pointers[y]+x) |= 1;
        else
            *(row_pointers[y]+x) &= 0xFE;
    }
    //Make sure that we did not use a file too large that it can't be encoded
    if(y >= read_ptr->height)
        exit(1);
}

//goto jumps here to break out of multiple loops
loop_end: 

The outer loop (primary variable is y)  controls the row of image data we're encoding into. The following piece of code is responsible for encoding the size of the file that we're encoding into the PNG image:

C++
if(y == 0)
    for(x; x < SIZE_WIDTH; x++) {
        if((size & ipow(2,x)))
            *(row_pointers[y]+x) |= 1;
        else
            *(row_pointers[y]+x) &= 0xFE;
    } 

You may notice that I set x to 0 outside of the loop. I'll explain why in a second. The

C++
if(y == 0)

portion ensures that the for loop only runs during encoding into the first row. If this weren't here we'd encode the size into every row, which we don't want to do. The for loop goes from 0 up to SIZE_WIDTH. Here SIZE_WIDTH is the number of bytes used to contain the size. In this case I wanted to have 32 bits for the size so SIZE_WIDTH is 32. Remember that Each one bit of hidden message requires a byte of PNG file. So the 32 bit size will be stored over 32 bytes of PNG image. The next part is: 

C++
if((size & ipow(2,x)))
    *(row_pointers[y]+x) |= 1;
else
    *(row_pointers[y]+x) &= 0xFE;

You may have to stare at it for a moment, but what this is doing is iterating over each of the 32 bits of the size, checking if they are a 1, if they are or-ing that PNG byte with 1 to encode that one into the least significant bit and if the bit isn't one and-ing the PNG byte with 0xFE, which has the effect of setting the least significant bit to 0. It may be worth noting that ipow is just a helper function that is an integer implementation of the pow function. The next part is where it can get confusing:

C++
for(x; x < read_ptr->width*3; x++) {
    if(x%BYTE_SIZE == 0) {
        if(!fread(&buffer, 1, 1, fileToEncode)) 
            goto loop_end;
    }
    //png_bytep here = row_pointers[y]+x; for debugging
    if((buffer & ipow(2,x%BYTE_SIZE)))
        *(row_pointers[y]+x) |= 1;
    else
        *(row_pointers[y]+x) &= 0xFE;
}

The loop starts at either 0 or 32 depending on whether this is the first row of image data. Remember that in the first row of image data we encoded the size of our hidden message in bytes. This is why I initialized x outside the loop. I needed to allow it to be 32 on that first run and 0 on every subsequent run. The loop ends after read_ptr->width*3. The *3 is there because the width is in pixels and each pixel has 3 bytes.

C++
if(x%BYTE_SIZE == 0) {
    if(!fread(&buffer, 1, 1, fileToEncode)) 
        goto loop_end;
} 

This part checks to see if x is a multiple of 8 (remember BYTE_SIZE == 8). If it is a multiple of 8 that means we've encoded 8 bits and we're ready to read another byte from our file to encode. If fread comes back with 0 it means we've reached the end of the file and we have to break out of the nested loop. 

C++
//png_bytep here = row_pointers[y]+x; for debugging
if((buffer & ipow(2,x%BYTE_SIZE)))
    *(row_pointers[y]+x) |= 1;
else
    *(row_pointers[y]+x) &= 0xFE; 

This section does the actual encoding. It iterates over each bit from right to left of the byte from of the byte read from the file to encode. If that bit is a 1 it ors a byte from the image with 1 to set the LSB to 1 otherwise, it sets the LSB of the image byte to 0. The final section of the loop is just a rudimentary check to see if we don't have any more rows of image to put data into, which means our hidden message is too big.

C++
//Make sure that we did not use a file too large that it can't be encoded
if(y >= read_ptr->height)
    exit(1); 

 Decoding the Image Data 

The last thing to do is decode the hidden data  from an encoded image. This will examine the LSB of each byte of image data (until we reach the size), extract it, and reassemble it. The decode function is essentially just the inverse of the encode function. Here it is:

C++
    void PNG_file::decode(const char *outputFileName) {
    //BEGIN DECODING HERE

    FILE * outputFile;

    unsigned char buffer = 0;

    outputFile = fopen (outputFileName,"wb");

    //Check if the file opened
    if(!outputFile)
        exit(1);

    unsigned int size = 0;

    //
    for(int y=0; y < read_ptr->height; y++) {
        int x=0;
        //Write the file size into the file y==0 ensures that it only happens
        //once
        if(y == 0)
            for(x; x < SIZE_WIDTH; x++) {
                size |= ((*(row_pointers[0]+x) & 1 ) << x);
            }
        for(x; x < read_ptr->width*3; x++) {
            if((x > SIZE_WIDTH || y > 0) && x%BYTE_SIZE == 0) {
                fwrite(&buffer, 1, 1, outputFile);
                buffer = 0;
            }
            //png_bytep here = row_pointers[y]+x; for debugging
            if(((read_ptr->width*y)*3+x) == size*BYTE_SIZE+SIZE_WIDTH)
                goto loop_end;
            buffer |= ((*(row_pointers[y]+x) & 1) << x%BYTE_SIZE);
        }
    }

    //goto jumps here to break out of multiple loops
    loop_end:

    fclose(outputFile);
}

By this point I expect you know what the first pieces do so I'll skip to the meat:

C++
for(int y=0; y < read_ptr->height; y++) {
    int x=0;
    //Write the file size into the file y==0 ensures that it only happens
    //once
    if(y == 0)
        for(x; x < SIZE_WIDTH; x++) {
            size |= ((*(row_pointers[0]+x) & 1 ) << x);
        }
    for(x; x < read_ptr->width*3; x++) {
        if((x > SIZE_WIDTH || y > 0) && x%BYTE_SIZE == 0) {
            fwrite(&buffer, 1, 1, outputFile);
            buffer = 0;
        }
        //png_bytep here = row_pointers[y]+x; for debugging
        if(((read_ptr->width*y)*3+x) == size*BYTE_SIZE+SIZE_WIDTH)
            goto loop_end;
        buffer |= ((*(row_pointers[y]+x) & 1) << x%BYTE_SIZE);
    }
} 

The first 8 lines are probably fairly self-explanatory at this juncture. The if statement and the for loop extract the length from the first 32 bytes of the first row. The least significant bit of each of these 32 bytes is combined into one 32 bit unsigned int that represents the size of the encoded file. We need to know this so we know when to stop reading. The inner for loop works the same way as the inner for loop encode. Where it gets different is inside the inner for loop. Here's the first part: 

C++
if((x > SIZE_WIDTH || y > 0) && x%BYTE_SIZE == 0) {
    fwrite(&buffer, 1, 1, outputFile);
    buffer = 0;
}

The if statement is only true when x is a multiple of 8, but is not on the very first iteration. What I mean by this is that we don't want to come in on the first iteration (where we've just finished extracting the size) and have this conditional result to true because at that point in execution nothing would be in our buffer. It would just be 0 (because that's what we initialized it to earlier). So we say that x must be greater than  SIZE_WIDTH, which makes sure it doesn't run on that first iteration. Alternatively, to satisfy the conditional y could also be greater than 0 because we do want this conditional to be checked when x == 0 on every iteration after the first. Sorry, I realize that was confusing. You may just have to stare at it for a moment. The inside of the if statement writes out the current decoded byte to our output file and resets the buffer to 0.  

Next is a check to make sure we haven't reached the end of the encoded data:

C++
if(((read_ptr->width*y)*3+x) == size*BYTE_SIZE+SIZE_WIDTH)
    goto loop_end; 

Observe! The use of the goto function in its proper habitat. goto can be legitimately used for few things, but breaking out of nested loops is one of them. Anyway the if statement is checking to see if we've decoded all of the hidden data. read_ptr->width is multiplied by y because that's the total number of rows we've read. Remember that number is in pixels so we have to multiply it by 3 to get the number of bytes. Finally we add however far into the current row of image data we are and that accounts for the +x. On the other side you get size multiplied by BYTE_SIZE. This is because the size variable is in bytes. In order to read in one byte of hidden data we have to read 8 bytes of image data so we multiply by BYTE_SIZE (which is 8). Finally you add on SIZE_WIDTH because in addition to the hidden data you read you also read in the size of that data. Remember that SIZE_WIDTH was 32. Finally, all that's left to do is actually place each bit of decoded data into the buffer:

C++
buffer |= ((*(row_pointers[y]+x) & 1) << x%BYTE_SIZE);

Again this kind of code can be hard to read so I'll do my best to explain it. In the middle part:

C++
((*(row_pointers[y]+x) & 1)

All this is doing is extracting the LSB of the byte of image data, which is our encoded bit. Now we have to align that properly in our buffer. Because that bit of data might have been the LSB of our encoded byte or it may have been the 4th bit in our encoded byte. We have to shift it left the appropriate number of spaces. That's where the:

C++
<< x%BYTE_SIZE)

comes into play. It shifts the bit left the appropriate number of spots in the byte. Finally we or that with buffer up to this point. Visually it may look like this:

Image byte is: 11100011

Reconstructed encoded byte is: 11001101  

This means that our encoded bit is 1

so we and that out to get the temporary byte:

00000001 

Let's say that this particular bit is supposed to be the third bit (counting from the left) of our reconstructed encoded byte. That means we need to shift it left three places. (Which we did with the left shift operator.) That gives us the temporary byte:

00000100

Now we or that with whatever buffer is up to this point. Since we read the encoded bits from most significant bit to least significant bit our buffer would look like this before the or:

11001000

and after we or with the temporary byte

00000100

11001000 or

_______________  

11001100 <- buffer at the end of the operation. 

If we kept iterating through the process we'd end up with the fully decoded byte:

11001101 

The header is called png_file.h and is at the beginning of the code section. Here is an example main that uses the PNG_file class:

C++
#include "PNG_file.h"

void main() {

    PNG_file link = PNG_file("link.png");
    link.encode("small.png");
    link.outputPNG("output.png");
    link.decode("decodedfile.png");
}

Points of Interest

So working with libpng was kinda awful. The windows version hasn't been updated in an eternity and the documentation was kinda confusing. (Still props to those guys who are just taking time out of their days to help everyone else out.) For that reason I've included the version that I finagled to work for Visual Studio 2012 along with zlib. You may have to play with the dependencies to get it to work for you, but feel free to reuse. Hopefully it saves someone some trouble. Finally, here is the PNG_file class in its entirety so you can look at it without having to download:

C++
 /* PNG_file
 * author: Grant Curell
 * Performs IO and encoding and decoding on PNG images
 * Feel free to reuse at your leisure. Cite me if you like, but it's no big deal.
 * Thanks to the random dudes I bummed the code for ipow and filesize from on
 * stackoverflow ;-).
 */

#include <stdio.h>
#include <stdlib.h>
#include "PNG_file.h"

#define PNG_SIG_LENGTH 8 //The signature length for PNG
#define BYTE_SIZE 8 //Size of a byte
#define SIZE_WIDTH 32 //The number of bits used for storing the length of a file
                      //Must be a multiple of 8

/* Integer power function
 * The C++ standard pow function uses doubles and I needed an integer version.
 * This is just a standard implementation using modular exponentiation.
 */
int ipow(int base, int exp) {
    int result = 1;
    while (exp)
    {
        if (exp & 1)
            result *= base;
        exp >>= 1;
        base *= base;
    }

    return result;
}


//Dirty function for calculating the size of a file
unsigned int filesize(const char *filename)
{
    FILE *f = fopen(filename,"rb");  /* open the file in read only */

    unsigned int size = 0;
    if (fseek(f,0,SEEK_END)==0) /* seek was successful */
        size = ftell(f);
    fclose(f);
    return size;
}


/* PNG Constructor
 * Constructor for the PNG_file class Simply reads in a PNG file
 */
PNG_file::PNG_file(const char *inputFileName) {

    FILE * inputFile;

    unsigned char header[BYTE_SIZE];

    inputFile = fopen (inputFileName,"rb");

    //Check if the file opened
    if(!inputFile)
        exit(1);

    // START READING HERE

    fread(header, 1, PNG_SIG_LENGTH, inputFile);

    //Check if it is a PNG
    if(png_sig_cmp(header, 0, PNG_SIG_LENGTH))
        exit(1);


    //Set up libPNG data structures and error handling
    read_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);

    if (!read_ptr)
        exit(1);

    info_ptr = png_create_info_struct(read_ptr);

    if (!info_ptr) {
        png_destroy_read_struct(&read_ptr,
            (png_infopp)NULL, (png_infopp)NULL);
        exit(1);
    }

    png_infop end_info = png_create_info_struct(read_ptr);

    if (!end_info) {
        png_destroy_read_struct(&read_ptr, &info_ptr,
            (png_infopp)NULL);
        exit(1);
    }
    //End data structure/error handling setup

    //Initialize IO for PNG
    png_init_io(read_ptr, inputFile);

    //Alert libPNG that we read PNG_SIG_LENGTH bytes at the beginning
    png_set_sig_bytes(read_ptr, PNG_SIG_LENGTH);

    //Read the entire PNG image into memory
    png_read_png(read_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);

    row_pointers = png_get_rows(read_ptr, info_ptr);

    //TODO ADD A CHECK SO WE ONLY USE COMPATIBLE PNG IMAGES
    if(read_ptr->bit_depth != BYTE_SIZE)
        exit(1);

    fclose(inputFile);
}

void PNG_file::encode(const char *fileToEncodeName) {
    //BEGIN ENCODING HERE

    FILE * fileToEncode;

    unsigned char buffer = 0;

    fileToEncode = fopen (fileToEncodeName,"rb");

    //Check if the file opened
    if(!fileToEncode)
        exit(1);

    //TODO CONSIDER ADDING CHECK FOR FILES THAT ARE TOO BIG
    unsigned long size = filesize(fileToEncodeName);

    //This section of code encodes the input file into the picture
    //It encodes the input file bit by bit into the least significant
    //bits of the original picture file
    for(int y=0; y < read_ptr->height; y++) {
        int x=0;
        //Write the file size into the file y==0 ensures that it only happens
        //once
        if(y == 0)
            for(x; x < SIZE_WIDTH; x++) {
                if((size & ipow(2,x)))
                    *(row_pointers[y]+x) |= 1;
                else
                    *(row_pointers[y]+x) &= 0xFE;
            }
        for(x; x < read_ptr->width*3; x++) {
            if(x%BYTE_SIZE == 0) {
                if(!fread(&buffer, 1, 1, fileToEncode)) 
                    goto loop_end;
            }
            //png_bytep here = row_pointers[y]+x; for debugging
            if((buffer & ipow(2,x%BYTE_SIZE)))
                *(row_pointers[y]+x) |= 1;
            else
                *(row_pointers[y]+x) &= 0xFE;
        }
        //Make sure that we did not use a file too large that it can't be encoded
        if(y >= read_ptr->height)
            exit(1);
    }

    //goto jumps here to break out of multiple loops
    loop_end:

    fclose(fileToEncode);

}

void PNG_file::decode(const char *outputFileName) {
    //BEGIN DECODING HERE

    FILE * outputFile;

    unsigned char buffer = 0;

    outputFile = fopen (outputFileName,"wb");

    //Check if the file opened
    if(!outputFile)
        exit(1);

    unsigned int size = 0;

    //
    for(int y=0; y < read_ptr->height; y++) {
        int x=0;
        //Write the file size into the file y==0 ensures that it only happens
        //once
        if(y == 0)
            for(x; x < SIZE_WIDTH; x++) {
                size |= ((*(row_pointers[0]+x) & 1 ) << x);
            }
        for(x; x < read_ptr->width*3; x++) {
            if((x > SIZE_WIDTH || y > 0) && x%BYTE_SIZE == 0) {
                fwrite(&buffer, 1, 1, outputFile);
                buffer = 0;
            }
            //png_bytep here = row_pointers[y]+x; for debugging
            if(((read_ptr->width*y)*3+x) == size*BYTE_SIZE+SIZE_WIDTH)
                goto loop_end;
            buffer |= ((*(row_pointers[y]+x) & 1) << x%BYTE_SIZE);
        }
    }

    //goto jumps here to break out of multiple loops
    loop_end:

    fclose(outputFile);

}

void PNG_file::outputPNG(const char *outputFileName) {
    //START WRITING HERE

    FILE * outputFile;
    
    outputFile = fopen (outputFileName,"wb");

    //Check if the file opened
    if(!outputFile)
        exit(1);

    //Initialize the PNG structure for writing
    write_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);

    if (!write_ptr)
        exit(1);

    png_init_io(write_ptr, outputFile);

    //Set the rows in the PNG structure
    png_set_rows(write_ptr, info_ptr, row_pointers);

    //Write the rows to the file
    png_write_png(write_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);

    fclose(outputFile);
}

There is one function I didn't explain in the code portion. That's the outputPNG function. It outputs the encoded version of the PNG file. The only two points worth mentioning in it are the function png_set_rows, which sets the rows we modified for writing. The png_write_png actually writes the image out to storage. For specifics the libpng documentation is here.  

Using libpng for Windows in other code

In the downloads section I've included the VS2012 project with libpng in it. To get it to compile it is dependent on zlib, which I also included. To get it working you open up the project in VS in a new solution. You must then add to that solution the zlib project. Once they're both in there compile zlib. Note where the zlib.lib file gets spit out. Then, in the properties of the libpng project, under C++ general, you have to add the zlib source folder to additional include directories. Then, under librarian, general, make sure zlib.lib is listed as an additional dependency and add the directory containing zlib.lib to the additional library directories. Hope this helps someone because getting libpng to work under Windows was a bear for me. 

History 

This is version 1. I've been at it for a while and I'm supposed to be studying for a test tomorrow so I haven't edited for grammar ;-D. 

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
United States United States
Grant is a specialist in computer security and networking. He holds a bachelors degree in Computer Science and Engineering from the Ohio State University. Certs: CCNA, CCNP, CCDA, CCDP, Sec+, and GCIH.

Comments and Discussions

 
QuestionMissing encode.h file Pin
Member 1253920823-May-16 7:17
Member 1253920823-May-16 7:17 
QuestionError Pin
b099ard12-Oct-14 2:24
b099ard12-Oct-14 2:24 
Questionmissing file Pin
Roger6516-Mar-14 2:03
Roger6516-Mar-14 2:03 
GeneralMy vote of 5 Pin
AliAwadh98031-May-13 11:48
AliAwadh98031-May-13 11:48 
Questiongreat article!!! Pin
seanshi17-May-13 11:38
seanshi17-May-13 11:38 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA10-May-13 18:36
professionalȘtefan-Mihai MOGA10-May-13 18:36 
GeneralMy vote of 5 Pin
AmitGajjar22-Apr-13 1:56
professionalAmitGajjar22-Apr-13 1:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.