From 392b3c56ce74a636e74957a404b938bf4b9c5fd1 Mon Sep 17 00:00:00 2001 From: Pat Thoyts Date: Thu, 18 Sep 2025 16:00:29 +0100 Subject: [PATCH] Added JPEG and EXIF parsing. --- CMakeLists.txt | 31 ++++-- exif.c | 280 +++++++++++++++++++++++++++++++++++++++++++++++++ exif.h | 59 +++++++++++ jpegrdr.c | 204 +++++++++++++++++++++++++++++++++++ 4 files changed, 563 insertions(+), 11 deletions(-) create mode 100644 exif.c create mode 100644 exif.h create mode 100644 jpegrdr.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 3bfd4c4..63eb0e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,17 +1,26 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.20) project(srfdump VERSION 1.0) -if (MSVC) - add_definitions(-D_CRT_SECURE_NO_WARNINGS) +add_executable(srfdump srfdump.c surfacefile.h) +target_compile_features(srfdump PRIVATE c_std_11) +if (WIN32) + target_compile_definitions(srfdump PRIVATE -DWIN32 -D_CRT_SECURE_NO_WARNINGS) else() - add_definitions(-pedantic) + target_compile_definitions(srfdump PRIVATE -pedantic) endif() -set(CMAKE_C_STANDARD 11) - -set(SOURCE srfdump.c) -add_executable(${PROJECT_NAME} ${SOURCE}) -set_property(TARGET ${PROJECT_NAME} PROPERTY C_STANDARD 11) +add_executable(srfinfo srfinfo.c surfacefile.h) +target_compile_features(srfinfo PRIVATE c_std_11) +if (WIN32) + target_compile_definitions(srfinfo PRIVATE -DWIN32 -D_CRT_SECURE_NO_WARNINGS) +else() + target_compile_definitions(srfinfo PRIVATE -pedantic) +endif() -add_executable(srfinfo srfinfo.c) -set_property(TARGET srfinfo PROPERTY C_STANDARD 11) +add_executable(jpegrdr jpegrdr.c exif.c exif.h) +target_compile_features(jpegrdr PRIVATE c_std_11) +if (WIN32) + target_compile_definitions(jpegrdr PRIVATE -DWIN32 -D_CRT_SECURE_NO_WARNINGS) +else() + target_compile_definitions(jpegrdr PRIVATE -pedantic) +endif() diff --git a/exif.c b/exif.c new file mode 100644 index 0000000..c673030 --- /dev/null +++ b/exif.c @@ -0,0 +1,280 @@ +#include +#include +#include +#include +#include +#include "exif.h" + +#define EXIF_IFD_TAG 0x8769U // special tag that is a pointer to more tags +#define EXIF_IFD_SIZE 12 // size in bytes of an IFD entry +#define EXIF_BOM_BE 0x4d4d +#define EXIF_BOM_LE 0x4949 +#define EXIF_HDR_MAGIC 0x2a + +static const char *EXIF_TYPE_NAMES[11] = { + // 0 1 2 3 4 5 + "?", "byte", "ascii", "short", "long", "rational", + // 6 7 8 9 10 + "char", "binary", "sshort", "slong", "srational" +}; + +static bool big_endian_data = false; + +inline int32_t get_int32(const uint8_t* data) +{ + if (big_endian_data) + return (data[3] << 0) | (data[2] << 8) | (data[1] << 16) | (data[0] << 24); + return (data[0] << 0) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); +} + +inline uint32_t get_uint32(const uint8_t* data) +{ + if (big_endian_data) + return (data[3] << 0) | (data[2] << 8) | (data[1] << 16) | (data[0] << 24); + return (data[0] << 0) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); +} + +inline uint16_t get_int16(const uint8_t* data) +{ + if (big_endian_data) + return (data[1] << 0) | (data[0] << 8); + return (data[0] << 0) | (data[1] << 8); +} + +inline uint16_t get_uint16(const uint8_t* data) +{ + if (big_endian_data) + return (data[1] << 0) | (data[0] << 8); + return (data[0] << 0) | (data[1] << 8); +} + +// Append an EXIF item to the tail of the list pointed to by head. +static void append_item(exif_item_t** headPtrPtr, exif_item_t* itemPtr) +{ + exif_item_t** tmpPtrPtr = headPtrPtr; + while (*tmpPtrPtr) { + tmpPtrPtr = &(*tmpPtrPtr)->nextPtr; + } + itemPtr->nextPtr = *tmpPtrPtr; + *tmpPtrPtr = itemPtr; +} + +static size_t exif_parse_item(const uint8_t *buffer, const uint8_t *entry, exif_item_t **headPtrPtr) +{ + size_t item_count = 0; + uint16_t tag = get_uint16(&entry[0]); + uint16_t type = get_uint16(&entry[2]); + uint32_t count = get_uint32(&entry[4]); + uint32_t offset = get_uint32(&entry[8]); + uint32_t offset_offset = (uint32_t)(&entry[8] - buffer); + + size_t item_size = sizeof(exif_item_t); + if (type == EXIF_TYPE_ASCII) + item_size += sizeof(exif_value_t) + count; + else + item_size += sizeof(exif_value_t) * count; + + exif_item_t* value = (exif_item_t*)calloc(1, item_size); + assert(value != NULL); + value->tag = tag; + value->type = type; + value->count = count; + + // to be annoying, if the data size is < 4 then it gets stored in the offset value directly. + switch (type) + { + case EXIF_TYPE_BYTE: + case EXIF_TYPE_UNDEFINED: + if (count < 5) + offset = offset_offset; + for (uint32_t n = 0; n < count; ++n) + { + value->u.values[n].byteVal = buffer[offset + n]; + } + break; + + case EXIF_TYPE_ASCII: + if (count < 5) + offset = offset_offset; + memcpy(value->u.strVal, &buffer[offset], value->count); + break; + + case EXIF_TYPE_SHORT: + if (count < 3) // handle small values being stored in the offset area + offset = offset_offset; + for (uint32_t n = 0; n < count; ++n) + { + value->u.values[n].uhVal = get_uint16(&buffer[offset + (n * sizeof(uint16_t))]); + } + break; + + case EXIF_TYPE_LONG: + if (tag == EXIF_IFD_TAG) + { + uint16_t ifd_count = get_uint16(&buffer[offset]); + for (uint16_t n = 0; n < ifd_count; ++n) + item_count += exif_parse_item(buffer, &buffer[offset + 2 + (n * EXIF_IFD_SIZE)], headPtrPtr); + free(value); + value = NULL; + } + else + { + if (count == 1) // handle small values being stored in the offset area + offset = offset_offset; + for (uint32_t n = 0; n < count; ++n) + { + value->u.values[n].ulVal = get_uint32(&buffer[offset + (n * sizeof(uint32_t))]); + } + } + break; + + case EXIF_TYPE_SBYTE: + if (count < 5) + offset = offset_offset; + for (uint32_t n = 0; n < count; ++n) + { + value->u.values[n].cVal = (int8_t)buffer[offset + n]; + } + break; + + case EXIF_TYPE_SSHORT: + if (count < 3) // handle small values being stored in the offset area + offset = offset_offset; + for (uint32_t n = 0; n < count; ++n) + { + value->u.values[n].hVal = get_int16(&buffer[offset + (n * sizeof(uint16_t))]); + } + break; + + case EXIF_TYPE_SLONG: + if (count == 1) // handle small values being stored in the offset area + offset = offset_offset; + for (uint32_t n = 0; n < count; ++n) + { + value->u.values[n].lVal = get_int32(&buffer[offset + (n * sizeof(uint32_t))]); + } + break; + + case EXIF_TYPE_RATIONAL: + for (uint32_t n = 0; n < count; ++n) + { + const uint8_t *p = &buffer[offset + (n * (2 * sizeof(uint32_t)))]; + value->u.values[n].rationalVal.numerator = get_uint32(p); + value->u.values[n].rationalVal.denominator = get_uint32(p + sizeof(uint32_t)); + } + break; + + case EXIF_TYPE_SRATIONAL: + for (uint32_t n = 0; n < count; ++n) + { + const uint8_t* p = &buffer[offset + (n * (2 * sizeof(int32_t)))]; + value->u.values[n].srationalVal.numerator = get_int32(p); + value->u.values[n].srationalVal.denominator = get_int32(p + sizeof(int32_t)); + } + break; + default: + assert("unhandled EXIF type"); + } + + if (value) + { + append_item(headPtrPtr, value); + ++item_count; + } + + return item_count; +} + +void exif_print_item(const exif_item_t* item) +{ + fprintf(stderr, " %04x %-10s %u: ", + item->tag, EXIF_TYPE_NAMES[item->type], item->count); + if (item->type == EXIF_TYPE_ASCII) + fprintf(stderr, "'%s'", item->u.strVal); + else if (item->type == EXIF_TYPE_UNDEFINED && item->count > 8) + fprintf(stderr, "[%hu bytes]", item->count); + else + { + for (uint16_t n = 0; n < item->count; ++n) + { + switch (item->type) + { + case EXIF_TYPE_BYTE: + case EXIF_TYPE_UNDEFINED: + fprintf(stderr, "%02x", item->u.values[n].byteVal); + break; + case EXIF_TYPE_SBYTE: + fprintf(stderr, "%d", item->u.values[n].cVal); + break; + case EXIF_TYPE_SSHORT: + fprintf(stderr, "%hd", item->u.values[0].hVal); + break; + case EXIF_TYPE_SHORT: + fprintf(stderr, "%hu", item->u.values[0].uhVal); + break; + case EXIF_TYPE_LONG: + fprintf(stderr, "%u", item->u.values[0].ulVal); + break; + case EXIF_TYPE_SLONG: + fprintf(stderr, "%d", item->u.values[0].lVal); + break; + case EXIF_TYPE_RATIONAL: + fprintf(stderr, "%u/%u", + item->u.values[n].rationalVal.numerator, item->u.values[n].rationalVal.denominator); + break; + case EXIF_TYPE_SRATIONAL: + fprintf(stderr, "%d/%d", + item->u.values[n].srationalVal.numerator, item->u.values[n].srationalVal.denominator); + break; + case EXIF_TYPE_FLOAT32: + fprintf(stderr, "%f", item->u.values[n].fltVal); + break; + case EXIF_TYPE_FLOAT64: + fprintf(stderr, "%lf", item->u.values[n].dblVal); + break; + case EXIF_TYPE_ASCII: + break; + } + if (n < item->count - 1) + fprintf(stderr, ", "); + } + } + fprintf(stderr, "\n"); +} + +void exif_free(exif_item_t *items) +{ + exif_item_t* item = items; + while (item != NULL) + { + exif_item_t* prev = item; + item = item->nextPtr; + free(prev); + } +} + +exif_item_t *exif_parse(const uint8_t *data, size_t datalen) +{ + exif_item_t* items = NULL; + + // read the EXIF header and get the byte order + big_endian_data = get_uint16(data) == EXIF_BOM_BE; + uint16_t magic = get_uint16(data + 2); + assert(magic == EXIF_HDR_MAGIC); + + uint32_t offset = get_uint32(data + 4); + while (offset) + { + // read the number of EXIF items stored (big endian) + uint16_t count = get_uint16(data + offset); + + // process each EXIF tag from the buffer + const uint8_t* entry = data + sizeof(uint16_t) + offset; + for (uint16_t n = 0; n < count; ++n, entry += EXIF_IFD_SIZE) + exif_parse_item(data, entry, &items); + + // after the IFD table is an offset to the next IFD table (if any) + offset = get_uint16(data + offset + sizeof(uint16_t) + (count * EXIF_IFD_SIZE)); + } + return items; +} diff --git a/exif.h b/exif.h new file mode 100644 index 0000000..79cb54f --- /dev/null +++ b/exif.h @@ -0,0 +1,59 @@ +#ifndef _exif_h_INCLUDE +#define _exif_h_INCLUDE + +#include + +typedef enum exif_type_t { + EXIF_TYPE_BYTE = 1, // uint8_t + EXIF_TYPE_ASCII = 2, // ASCII NUL terminated + EXIF_TYPE_SHORT = 3, // uint16_t + EXIF_TYPE_LONG = 4, // uint32_t + EXIF_TYPE_RATIONAL = 5, // 2 LONGs numerator : denominator + EXIF_TYPE_SBYTE = 6, // int8_t + EXIF_TYPE_UNDEFINED = 7, // uint8_t + EXIF_TYPE_SSHORT = 8, // int16_t + EXIF_TYPE_SLONG = 9, // int32_t + EXIF_TYPE_SRATIONAL = 10, // 2 SLONGs numerator : denominator + EXIF_TYPE_FLOAT32 = 11, // IEEE 32 bit floating point + EXIF_TYPE_FLOAT64 = 12 // IEEE 64 bit floating point +} exif_type_t; + +typedef struct rational_t { + uint32_t numerator; + uint32_t denominator; +} rational_t; + +typedef struct srational_t { + int32_t numerator; + int32_t denominator; +} srational_t; + +typedef union exif_value_t { + int8_t cVal; + uint8_t byteVal; + int16_t hVal; + uint16_t uhVal; + int32_t lVal; + uint32_t ulVal; + rational_t rationalVal; + srational_t srationalVal; + float fltVal; + double dblVal; +} exif_value_t; + +typedef struct exif_item_t { + uint16_t tag; + uint16_t count; + enum exif_type_t type; + struct exif_item_t* nextPtr; + union { + exif_value_t values[0]; + char strVal[0]; + } u; +} exif_item_t; + +void exif_print_item(const exif_item_t* item); +exif_item_t *exif_parse(const uint8_t *data, size_t datalen); +void exif_free(exif_item_t *items); + +#endif /* _exif_h_INCLUDE */ diff --git a/jpegrdr.c b/jpegrdr.c new file mode 100644 index 0000000..ccffad2 --- /dev/null +++ b/jpegrdr.c @@ -0,0 +1,204 @@ +#include +#include +#include +#include +#include +#include +#include +#include "exif.h" + +typedef uint16_t marker_t; + +enum jfif_mark_t { + JFIF_SOI = 0xd8ff, // start of image + JFIF_SOF0 = 0xc0ff, // start of frame (baseline DCT) + JFIF_SOF2 = 0xc2ff, // start of frame (progressive DCT) + JFIF_DHT = 0xc4ff, // define Huffman table + JFIF_DQT = 0xdbff, // define quantization table + JFIF_DRI = 0xddff, // define restart interval + JFIF_SOS = 0xdaff, // start of scan + JFIF_RST0 = 0xd0ff, // restart (d0 .. d7) + JFIF_APP0 = 0xe0ff, // Application specific (e0..ef) + JFIF_COM = 0xfeff, // comment + JFIF_EOI = 0xd9ff, // end of image +} jfif_mark_t; + +#pragma pack(push, 1) + +typedef struct jfif_header_t { + uint8_t major_version; + uint8_t minor_version; + uint8_t units; // 0 none, 1 dots per inch, 2 dots per cm + uint16_t x_density; + uint16_t y_density; + uint8_t thumbnail_pixel_count_x; + uint8_t thumbnail_pixel_count_y; + uint8_t thumbnail[0]; +} jfif_header_t; + +typedef struct jfif_frame_t { + uint8_t precision; // number of bits per pixel? + uint16_t height; // number of lines + uint16_t width; // number of pixels per line + uint8_t components; // number of components (3) + uint8_t data[9]; // component definitions (constant) 4:2:2 or 4:2:0 +} jfif_frame_t; + +#pragma pack(pop) + +#define JFIF_IS_APP(x) (((x) & 0xF0FF) == JFIF_APP0) +#define JFIF_GET_APP_N(x) (((x) >> 8) & 0x0f) + +#define JFIF_IS_RST(x) (((x) & 0xF0FF) == JFIF_RST0) +#define JFIF_GET_RST_N(x) (((x) >> 8) & 0x0f) + +#define ntohs(x) ((x) << 8 | ((x >> 8) & 0xff)) + +static uint32_t ntohl(const uint32_t value) +{ + uint8_t data[4] = {0}; + memcpy(&data, &value, sizeof(data)); + + return ((uint32_t)data[3] << 0) + | ((uint32_t)data[2] << 8) + | ((uint32_t)data[1] << 16) + | ((uint32_t)data[0] << 24); +} + +size_t jfif_get_size(FILE *fp) +{ + // always big endian + uint8_t data[2]; + fread(data, 1, 2, fp); + return ((data[0] << 8) | (data[1] << 0)) - 2; +} + +jfif_header_t *jfif_read_header(FILE *fp, size_t len) +{ + jfif_header_t *hdr = (jfif_header_t *)calloc(len, 1); + fread(hdr, len, 1, fp); + hdr->x_density = ntohs(hdr->x_density); + hdr->y_density = ntohs(hdr->y_density); + return hdr; +} + +jfif_frame_t* jfif_read_frame(FILE* fp, size_t len) +{ + assert(len == sizeof(jfif_frame_t)); + jfif_frame_t* frame = (jfif_frame_t*)calloc(1, sizeof(jfif_frame_t)); + fread(frame, sizeof(jfif_frame_t), 1, fp); + frame->width = ntohs(frame->width); + frame->height = ntohs(frame->height); + return frame; +} + +void jfif_read_app0(FILE *fp, size_t len) +{ + char label[6] = {0}; + size_t count = fread(label, 1, 5, fp); + if (strncmp("JFIF", label, 4) == 0) + { + jfif_header_t *hdr = jfif_read_header(fp, len - 5); + fprintf(stderr, " %s version %u.%u density %hu,%hu %s\n", + label, hdr->major_version, hdr->minor_version, + hdr->x_density, hdr->y_density, + hdr->units == 1 ? "dpi" : "???"); + free(hdr); + } + else if (strncmp("Exif", label, 4) == 0) + { + uint8_t pad = 0; + fprintf(stderr, " %s\n", label); + fread(&pad, 1, 1, fp); // read the pad char + + // read the EXIF data into a buffer + uint8_t *data = (uint8_t*)calloc(len - 6, 1); + assert(data != NULL); + fread(data, len - 6, 1, fp); + exif_item_t *items = exif_parse(data, len - 6); + free(data); + + // print the items + for (exif_item_t* item = items; item; item = item->nextPtr) + exif_print_item(item); + exif_free(items); + } + else + { + fprintf(stderr, " %s\n", label); + fseek(fp, (long)(len - 5), SEEK_CUR); + } +} + +// +// JFIF images have SOI APP0[JFIF] ... +// EXIF sections use APP0 as well and in JFIF must follow the JFIF segment. +// +void get_jpeg_exif(FILE *fp) +{ + size_t start = ftell(fp); + size_t len = 0; + marker_t mark; + while (!feof(fp)) + { + fread(&mark, sizeof(mark), 1, fp); + switch (mark) + { + case JFIF_SOI: + len = 0; + fprintf(stderr, "%08lx SOI\n", ftell(fp)); + break; + case JFIF_SOF0: + { + len = jfif_get_size(fp); + fprintf(stderr, "%08lx SOF0 %04zx\n", ftell(fp), len); + jfif_frame_t* frame = jfif_read_frame(fp, len); + fprintf(stderr, " image size %u,%u\n", frame->width, frame->height); + free(frame); + len = 0; + break; + } + case JFIF_DQT: + len = jfif_get_size(fp); + fprintf(stderr, "%08lx DQT %04zx\n", ftell(fp), len); + break; + case JFIF_DHT: + len = jfif_get_size(fp); + fprintf(stderr, "%08lx DHT %04zx\n", ftell(fp), len); + break; + case JFIF_SOS: + len = jfif_get_size(fp); + fprintf(stderr, "%08lx SOS %04zx\n", ftell(fp), len); + return; + default: + { + if (JFIF_IS_RST(mark) && JFIF_GET_RST_N(mark) < 8) + { + len = jfif_get_size(fp); + fprintf(stderr, "%08lx RST%d %04zx\n", ftell(fp), JFIF_GET_RST_N(mark), len); + } + else if (JFIF_IS_APP(mark)) + { + len = jfif_get_size(fp); + fprintf(stderr, "%08lx APP%u %04zx\n", ftell(fp), JFIF_GET_APP_N(mark), len); + jfif_read_app0(fp, len); + len = 0; + } + else + { + fprintf(stderr, "OOPS %04x\n", mark); + exit(1); + } + } + } + fseek(fp, (long)len, SEEK_CUR); + } +} + +int main(int argc, char const * const argv[]) +{ + FILE *fp = fopen(argv[1], "rb"); + get_jpeg_exif(fp); + fclose(fp); + return 0; +} -- 2.23.0