Search code examples
cjpegfwritefread

Can fread() and fwrite() be used to copy jpegs?


I'm quite new to programming, and I'm trying to write a basic program to copy a jpeg file:

#include <stdio.h>

int main (void)
{
    FILE *a = fopen("pic1.jpg", "r");
    
    FILE *b = fopen("pic2.jpg", "w");
    
    fread(a, 1, sizeof(a), b);
    fwrite(a, 1, sizeof(a), b);
    
    fclose(a);
    fclose(b);
}

The program does create a new file pic2.jpg, however, when I try to look at it, I receive the error message: Invalid or Unsupported Image Format, so this makes me think I may be trying to use this function incorrectly.


Solution

  • Yes, but you're not using them correctly.

    fread(a, 1, sizeof(a), b) says to read 1 byte from b sizeof(a) times into the FILE *a. This compiles, but doesn't make much sense. You make a similar mistake with fwrite.

    sizeof(a) gets the size of a FILE * pointer which is usually 8 bytes, not the size of the file. So you're only reading and writing 8 bytes.

    The first argument of fread and fwrite is a buffer to read into and write from, that's the key element which is missing.


    First, always check your file operations worked. If they fail, if the file doesn't exist or can't be opened, the program will continue merrily along and you'll get weird errors when it tries to write to NULL.

    Some systems make a distinction between "binary" and "text" files. You have to add the b flag to make sure they don't mangle the content.

    I'll also use more descriptive names so we don't get confused.

    FILE *in = fopen("pic1.jpg", "rb");
    if( !in ) {
        perror("can't open pic1.jpg for reading");
    }
    
    FILE *out = fopen("pic2.jpg", "wb");
    if( !out ) {
        perror("can't open pic2.jpg for writing");
    }
    

    Now we need to allocate a buffer to read into and write from. For best performance this should be something like the natural block size of the disk. 4096 or the BUFSIZ constant are good choices. We use char as a surrogate for an array of bytes. If you want to get fancy you could use a uint8_t but since we're just copying bytes there's no practical difference.

    Read into that and write from that in a loop until everything is read.

    char buf[BUFSIZ];
    size_t bytes_read;
    
    // Read a buffer sized hunk.
    while( (bytes_read = fread(buf, 1, sizeof(buf), in)) ) {
        // Write the hunk, but only as much as was read.
        fwrite(buf, 1, bytes_read, out);
    }
    
    fclose(in);
    fclose(out);
    

    We can use sizeof(buf) here only because buf is an array of a known size. It will return the size of the buffer, not the size of a char * pointer.

    We have to be careful to write only as much as we read, not the whole buffer. Otherwise if we did fwrite(buf, 1, sizeof(buf), out) and the last read only partially filled the buffer we'd print garbage.

    fread returns the number of "items" read, not the number of bytes. This is from waaay back when files were more likely to have fixed size records. To get it to return the number of bytes we tell it each "item" is 1 byte and we want sizeof(buf) "items".