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:
Width | 4 bytes |
Height | 4 bytes |
Bit depth | 1 byte |
Colour type | 1 byte |
Compression method | 1 byte |
Filter method | 1 byte |
Interlace method | 1 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 ).
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 .
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:
#include <png.h>
class PNG_file {
public:
PNG_file(const char *inputFileName);
void encode(const char *fileToEncodeName);
void outputPNG(const char *outputFileName);
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:
PNG_file::PNG_file(const char *inputFileName) {
FILE * inputFile;
unsigned char header[BYTE_SIZE];
inputFile = fopen (inputFileName,"rb");
if(!inputFile)
exit(1);
fread(header, 1, PNG_SIG_LENGTH, inputFile);
if(png_sig_cmp(header, 0, PNG_SIG_LENGTH))
exit(1);
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);
}
png_init_io(read_ptr, inputFile);
png_set_sig_bytes(read_ptr, PNG_SIG_LENGTH);
png_read_png(read_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
row_pointers = png_get_rows(read_ptr, info_ptr);
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
:
fread(header, 1, PNG_SIG_LENGTH, inputFile);
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:
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);
}
The following line initializes IO on the 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:
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 ) 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...
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.
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:
void PNG_file::encode(const char *fileToEncodeName) {
FILE * fileToEncode;
unsigned char buffer = 0;
fileToEncode = fopen (fileToEncodeName,"rb");
if(!fileToEncode)
exit(1);
unsigned long size = filesize(fileToEncodeName);
for(int y=0; y < read_ptr->height; y++) {
int x=0;
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;
}
if((buffer & ipow(2,x%BYTE_SIZE)))
*(row_pointers[y]+x) |= 1;
else
*(row_pointers[y]+x) &= 0xFE;
}
if(y >= read_ptr->height)
exit(1);
}
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:
FILE * fileToEncode;
unsigned char buffer = 0;
fileToEncode = fopen (fileToEncodeName,"rb");
if(!fileToEncode)
exit(1);
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:
for(int y=0; y < read_ptr->height; y++) {
int x=0;
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;
}
if((buffer & ipow(2,x%BYTE_SIZE)))
*(row_pointers[y]+x) |= 1;
else
*(row_pointers[y]+x) &= 0xFE;
}
if(y >= read_ptr->height)
exit(1);
}
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:
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
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:
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:
for(x; x < read_ptr->width*3; x++) {
if(x%BYTE_SIZE == 0) {
if(!fread(&buffer, 1, 1, fileToEncode))
goto loop_end;
}
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.
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.
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.
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:
void PNG_file::decode(const char *outputFileName) {
FILE * outputFile;
unsigned char buffer = 0;
outputFile = fopen (outputFileName,"wb");
if(!outputFile)
exit(1);
unsigned int size = 0;
for(int y=0; y < read_ptr->height; y++) {
int x=0;
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;
}
if(((read_ptr->width*y)*3+x) == size*BYTE_SIZE+SIZE_WIDTH)
goto loop_end;
buffer |= ((*(row_pointers[y]+x) & 1) << x%BYTE_SIZE);
}
}
loop_end:
fclose(outputFile);
}
By this point I expect you know what the first pieces do so I'll skip to the meat:
for(int y=0; y < read_ptr->height; y++) {
int x=0;
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;
}
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:
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:
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:
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:
((*(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:
<< 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:
#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:
#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
int ipow(int base, int exp) {
int result = 1;
while (exp)
{
if (exp & 1)
result *= base;
exp >>= 1;
base *= base;
}
return result;
}
unsigned int filesize(const char *filename)
{
FILE *f = fopen(filename,"rb");
unsigned int size = 0;
if (fseek(f,0,SEEK_END)==0)
size = ftell(f);
fclose(f);
return size;
}
PNG_file::PNG_file(const char *inputFileName) {
FILE * inputFile;
unsigned char header[BYTE_SIZE];
inputFile = fopen (inputFileName,"rb");
if(!inputFile)
exit(1);
fread(header, 1, PNG_SIG_LENGTH, inputFile);
if(png_sig_cmp(header, 0, PNG_SIG_LENGTH))
exit(1);
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);
}
png_init_io(read_ptr, inputFile);
png_set_sig_bytes(read_ptr, PNG_SIG_LENGTH);
png_read_png(read_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
row_pointers = png_get_rows(read_ptr, info_ptr);
if(read_ptr->bit_depth != BYTE_SIZE)
exit(1);
fclose(inputFile);
}
void PNG_file::encode(const char *fileToEncodeName) {
FILE * fileToEncode;
unsigned char buffer = 0;
fileToEncode = fopen (fileToEncodeName,"rb");
if(!fileToEncode)
exit(1);
unsigned long size = filesize(fileToEncodeName);
for(int y=0; y < read_ptr->height; y++) {
int x=0;
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;
}
if((buffer & ipow(2,x%BYTE_SIZE)))
*(row_pointers[y]+x) |= 1;
else
*(row_pointers[y]+x) &= 0xFE;
}
if(y >= read_ptr->height)
exit(1);
}
loop_end:
fclose(fileToEncode);
}
void PNG_file::decode(const char *outputFileName) {
FILE * outputFile;
unsigned char buffer = 0;
outputFile = fopen (outputFileName,"wb");
if(!outputFile)
exit(1);
unsigned int size = 0;
for(int y=0; y < read_ptr->height; y++) {
int x=0;
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;
}
if(((read_ptr->width*y)*3+x) == size*BYTE_SIZE+SIZE_WIDTH)
goto loop_end;
buffer |= ((*(row_pointers[y]+x) & 1) << x%BYTE_SIZE);
}
}
loop_end:
fclose(outputFile);
}
void PNG_file::outputPNG(const char *outputFileName) {
FILE * outputFile;
outputFile = fopen (outputFileName,"wb");
if(!outputFile)
exit(1);
write_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!write_ptr)
exit(1);
png_init_io(write_ptr, outputFile);
png_set_rows(write_ptr, info_ptr, row_pointers);
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.