Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C: Writing structs of RGB values to file to create ppm image - premature end of file

Tags:

c

struct

rgb

ppm

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");
}
}
like image 667
Sean Turnbull Avatar asked Nov 08 '22 02:11

Sean Turnbull


1 Answers

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

like image 139
samgak Avatar answered Nov 15 '22 06:11

samgak