-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()
--- /dev/null
+#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;
+}
--- /dev/null
+#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 */
--- /dev/null
+#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;
+}