5

The program i am creating a steganography program that hides a secret message inside a .ppm image by changing random red pixel values to ascii characters. The program is based on code that is on stackoverflow for reading and writing ppm images (read PPM file and store it in an array; coded with C), all other code is my own work. I have completed all the necessary functions to do this such as writing,reading,encoding and decoding the files but i am struggling to grasp the fwrite function.

Currently when the program encodes an image it takes in the .ppm converts it to its rgb values in a struct. Then it hides the secret message by editing the red values to ascii characters. The issue arises when it comes to "printing" the image to a file. When the program has completed the image produced is around 90% of what it should be printing. Example show below: Example of the unfinished image

I have checked it is storing all the values are being stored correctly by printing all the rgb values and it is. (used the showPPM method). Is there not enough memory to write the image? is the image to large for the write function? these are my guesses.

Any information on how i should go about changing the writePPM function so that i correctly print 100% of the image to the file would be great.

Here is the code below:

#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<string.h>
#include<time.h>

typedef struct {
    unsigned char red,green,blue;
} PPMPixel;

typedef struct {
int x, y;
PPMPixel *data;
} PPMImage;

void writePPM(PPMImage *img);

static PPMImage *getPPM(const char *filename)
{

    char buff[16];
     PPMImage *img;
     FILE *fp;
     int c, rgb_comp_color;
     //open PPM file for reading
     fp = fopen(filename, "rb");
     if (!fp) {
          fprintf(stderr, "Unable to open file '%s'\n", filename);
          exit(1);
     }

     //read image format
     if (!fgets(buff, sizeof(buff), fp)) {
          perror(filename);
          exit(1);
     }

//check the image format
if (buff[0] != 'P' || buff[1] != '3') {
     fprintf(stderr, "Invalid image format (must be 'P3')\n");
     exit(1);
}else{
    printf("P3\n");
}

//alloc memory form image
img = (PPMImage *)malloc(sizeof(PPMImage));
if (!img) {
     fprintf(stderr, "Unable to allocate memory\n");
     exit(1);
}


   c = getc(fp);
   while (c == '#') {
   while (getc(fp) != '\n') ;
     c = getc(fp);

}
ungetc(c, fp);
//read image size information
if (fscanf(fp, "%d %d", &img->x, &img->y) != 2) {
     fprintf(stderr, "Invalid image size (error loading '%s')\n", filename);
     exit(1);
}else{
    printf("Height: %d\n",img->x);
    printf("Width: %d\n",img->y);

}

//read rgb component
if (fscanf(fp, "%d", &rgb_comp_color) != 1) {
     fprintf(stderr, "Invalid rgb component (error loading '%s')\n", filename);
     exit(1);
}else{
    printf("%d\n",rgb_comp_color );
}

//check rgb component depth
if (rgb_comp_color!= 255) {
     fprintf(stderr, "'%s' does not have 8-bits components\n", filename);
     exit(1);
}

while (fgetc(fp) != '\n') ;
//memory allocation for pixel data
img->data = (PPMPixel*)malloc(24*img->x * img->y * sizeof(PPMPixel));

if (!img) {
     fprintf(stderr, "Unable to allocate memory\n");
     exit(1);
}

//read pixel data from file
if (fread(img->data, 10*img->x, img->y, fp) != img->y) {
     fprintf(stderr, "Error loading image '%s'\n", filename);
     exit(1);
}

fclose(fp);
return img;
}



struct PPMImage * encode(char * text, PPMImage * img)
{
    //convert secret message to ascii code

    int i,ascii,height,width;
    int total = 0;
    int rolling = 0;
    int original = 0;
    time_t t;
    srand((unsigned) time(&t));
    height=img->y;
    width=img->x;

    for(i = 0; text[i]; i++){

        ascii = text[i];

        //create random number between 0 and max the width
        total = total + rand() % width;
        original = total;
        //printf("Random Number: %d\n",total);

        if(total >= width){
            rolling = rolling + 1;
            total = total - width;
        }

        //printf("Before R: %d \n",img->data[0].red );
        img->x=rolling;
        img->y=total;

        printf("X: %d ",rolling );
        printf("Y: %d ",total );

        //set img position
        //at position random we set the red bit equal to ascii number 
        printf("Old R:  %d ",img->data[i].red );                    
        img->data[i].red=ascii; 
        printf("New R: %d\n ",img->data[i].red );   
    }

    //take img then print it out
    //setting the img values again for printing
    img->x=width;
    img->y=height;
    writePPM(img);

}

void writePPM(PPMImage *img)
{
FILE *fp;
//open file to be written
fp = fopen("encoded.ppm", "wb");
if (!fp) {
     fprintf(stderr, "Unable to open file \n");
     exit(1);
}

//image format
fprintf(fp, "P3\n");

//comments
//need to store comments to be outputted
fprintf(fp, "# Created by Sean \n");

//image size
fprintf(fp,"%d %d\n",img->x,img->y);

// rgb component depth
fprintf(fp, "%d\n",255);

//write pixels currently not fully working
fwrite(img->data, sizeof(img->data), 3*img->y*img->x, fp);

//close file stream
fclose(fp);
}

void showPPM(PPMImage *img)
{
    int i;
    if(img){

    for(i=-1;i<img->x*img->y;i++){
        printf("Number: %d\n",i);
        printf("R: %d ",img->data[i].red );
        printf("G: %d ",img->data[i].green );
        printf("B: %d\n ",img->data[i].blue );

     }
}
}


char * decode(PPMImage * i1,PPMImage * i2){

//compare difference in number of bits in red pixels
//if there is a different then take the red pixel value from the encrypted image
//then translate it from ascii to chars then print.
printf("Decoding......\n");

int i;
     for(i=-1;i<i1->x*i1->y;i++){
            if(i1->data[i].red != i2->data[i].red){
                printf("%c",i1->data[i].red );
            }
     }

//to be able to test and finish this need to write code for encoding

}

int main(int argc, char *argv[]){

//input statements
if(argc == 3){
    PPMImage *image;
    image = getPPM(argv[2]);
    //uncomment the showPPM to display all rgb values in the encoded files
    //showPPM(image);
    if(argv[1] = "e"){
    printf("Please enter your secret message to be encoded estimated max characters: %d\n",image->y);   

        //need to add user input
    encode("test output!",image);
    }
}else if(argc == 4){
    PPMImage *i1;
    PPMImage *i2;
    i1 = getPPM(argv[2]);
    i2 = getPPM(argv[3]);

    if(argv[1] = "d"){
        decode(i1,i2);
    }
}else{
    printf("Wrong arguments");
}
}
Community
  • 1
  • 1
Sean Turnbull
  • 53
  • 1
  • 4
  • 1
    `sizeof(img->data)` is not what you want. That gives you the size of the *pointer* not the size of the allocated memory. You have magic numbers in your code such as `24` and `3` which make it non-obvious what the various sizes represent. So I'm not sure exactly what size you need in the `fwrite`. But it is definetely not `sizeof(img->data)`. – kaylum Feb 09 '17 at 00:26
  • You should edit this to remove the anti-calum sailor talk, we're trying to run a respectable joint here. – samgak Feb 09 '17 at 09:21
  • Thanks for the advice forgot that was there :) – Sean Turnbull Feb 09 '17 at 16:49

1 Answers1

1

The problem is actually in the code for reading in the PPM, which you have modified in a way that appears to work, but actually doesn't because the file format is different to what you think it is.

The code that you linked to is for reading PPM files in the "raw" format. These files start with the "P6" code. In these files, each RGB value is stored as either 1 or 2 bytes (depending on whether the RGB component depth is less than 256). So, if the max value is 255, it's 1 bytes per value, so the file size is width * height * 3.

However, you have modified the code to read "plain" PPM files, which start with the "P3" code, by checking for P3 and reading in more data. These files don't store the RGB values as raw binary data, but as ASCII text specifying the value in decimal format, separated by whitespace. So for example if you had the value 93 in raw format, it would just be 1 byte with a value of 93, but in the "plain" format it would be 3 (or more) bytes: one or more bytes with the ASCII value for a space (or tab), then the ASCII value for "9" (which is 57) then the ASCII value for "3" (which is 51). It's impossible to calculate the size of the file based on the width and height because the whitespace can be variable, and each value could be represented between 1 and 3 digits.

Despite the fact that you are not parsing the data as ASCII-encoded text, your PPM-reading code seems to work because you are just reading in a chunk of data, (optionally) modifying a few random bytes and then writing it out again either totally or mostly unchanged.

So, your possible solutions are:

  • Change the getPPM code back to what it was and use an actual P6 file.
  • Write a PPM reader that parses the data properly as ASCII text containing whitespace-separated decimal numbers (you can write out as either P3 or P6).

More info: PPM Format Specification

samgak
  • 23,944
  • 4
  • 60
  • 82
  • Thanks for the help, i would like to change the program to read/write P3 images so that would require changing the reader, how would i go about changing it to the right requirements. Adding in whitespaces for each rgb value? – Sean Turnbull Feb 09 '17 at 16:46
  • When you are reading it in, call `fscanf(fp, " %d %d %d ", &red, &green, &blue);` inside a loop, until you have read width * height values or reached the end of the file (test using `feof(fp)`). Read into ints and then set the 8 bit values in your structs. To write it out you can just use fprintf. Note you can only have 70 values on each line. Read the format spec carefully first. – samgak Feb 09 '17 at 21:50
  • 1
    This code handles reading ppm files but might be a bit over-complicated: https://sourceforge.net/p/netpbm/code/HEAD/tree/stable/lib/libppm1.c – samgak Feb 09 '17 at 21:51
  • Thanks for the tips and help :) – Sean Turnbull Feb 10 '17 at 19:36