I need to extract the dimensions (width and height) of a jpeg file for inserting in an html document. According to the JPEG image compression FAQ, cautions against what's commonly out there,
Some people have recommended just searching for the byte pair representing SOFn, without paying attention to the marker block structure. This is unsafe because a prior marker might contain the SOFn pattern, either by chance or because it contains a JPEG-compressed thumbnail image.
It said to look up rdjpgcom.c, which is part of the open-source ImageMagick. It seems to agree with the advice on this answer. However, this file prints out jpeg comments, so I wrote one to specifically get the dimensions.
#include <stdlib.h> /* EXIT */
#include <stdio.h> /* [f]printf fopen fclose fread */
#include <assert.h> /* assert */
#include <errno.h> /* errno */
/** Attempt to read the size of a jpeg.
@param[file] File that has been opened in binary mode and rewound; required.
@param[width, height] Pointers that get overwritten on success; required.
@return Success, otherwise `errno` will (probably) be set.
@license <http://www.faqs.org/faqs/jpeg-faq/part1/> said to look up
[rdjpgcom.c](https://github.com/ImageMagick/jpeg-turbo/blob/master/rdjpgcom.c),
which has
[IJG License](https://github.com/ImageMagick/jpeg-turbo/blob/master/README.ijg)
and possibly related [ImageMagick](https://imagemagick.org/script/license.php).
I used as a reference to write this function. */
static int jpeg_dim(FILE *const fp, unsigned *const width,
unsigned *const height) {
unsigned char f[8];
unsigned skip;
assert(fp && width && height);
/* The start of the file has to be an `SOI`. */
if(fread(f, 2, 1, fp) != 1) return 0;
if(f[0] != 0xFF || f[1] != 0xD8) return errno = EDOM, 0;
for( ; ; ) {
/* Discard up until the last `0xFF`, then that is the marker type. */
do { if(fread(f, 1, 1, fp) != 1) return 0; } while(f[0] != 0xFF);
do { if(fread(f, 1, 1, fp) != 1) return 0; } while(f[0] == 0xFF);
switch(f[0]) {
case 0xC0: case 0xC1: case 0xC2: case 0xC3: case 0xC5: /* _sic_ */
case 0xC6: case 0xC7: case 0xC9: /* _sic_ */ case 0xCA: case 0xCB:
case 0xCD: /* _sic_ */ case 0xCE: case 0xCF:
/* `SOF` markers. */
if(fread(f, 8, 1, fp) != 1) return 0;
if((skip = (f[0] << 8) | f[1]) != 8u + 3 * f[7])
return errno = EDOM, 0;
*width = (f[5] << 8) | f[6];
*height = (f[3] << 8) | f[4];
return 1;
case 0xD8: case 0xD9:
/* Image data `SOS, EOI` without image size. */
return errno = EDOM, 0;
default:
/* Skip the rest by reading it's size. */
if(fread(f, 2, 1, fp) != 1) return 0;
if((skip = (f[0] << 8) | f[1]) < 2) return errno = EDOM, 0;
if(fseek(fp, skip - 2, SEEK_CUR) != 0) return 0;
}
}
}
int main(int argc, char **argv) {
FILE *fp = 0;
unsigned width, height;
int success = EXIT_FAILURE;
if(argc != 2 || !(fp = fopen(argv[1], "rb"))
|| !jpeg_dim(fp, &width, &height)) goto catch;
printf("Jpeg width: %u, height: %u.\n", width, height);
success = EXIT_SUCCESS;
goto finally;
catch:
if(errno) {
perror("jpeg size");
} else {
fprintf(stderr, "Use with jpeg filename to see the size.\n");
}
finally:
if(fp) fclose(fp);
return success;
}
It seems to work, but I'd like to review it to make sure that it's correct.