Added JPEG and EXIF parsing.
authorPat Thoyts <patthoyts@users.sourceforge.net>
Thu, 18 Sep 2025 15:00:29 +0000 (16:00 +0100)
committerPat Thoyts <patthoyts@users.sourceforge.net>
Thu, 18 Sep 2025 15:00:29 +0000 (16:00 +0100)
CMakeLists.txt
exif.c [new file with mode: 0644]
exif.h [new file with mode: 0644]
jpegrdr.c [new file with mode: 0644]

index 3bfd4c436df26f1771476ec7b39f3140f60442c3..63eb0e1393083a10d6aa8be46134ca6313605f65 100644 (file)
@@ -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 (file)
index 0000000..c673030
--- /dev/null
+++ b/exif.c
@@ -0,0 +1,280 @@
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#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 (file)
index 0000000..79cb54f
--- /dev/null
+++ b/exif.h
@@ -0,0 +1,59 @@
+#ifndef _exif_h_INCLUDE
+#define _exif_h_INCLUDE
+
+#include <stdint.h>
+
+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 (file)
index 0000000..ccffad2
--- /dev/null
+++ b/jpegrdr.c
@@ -0,0 +1,204 @@
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <crtdbg.h>
+#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;
+}