diff --git a/src/main.c b/src/main.c index 0252f58..5828036 100644 --- a/src/main.c +++ b/src/main.c @@ -1,44 +1,54 @@ #include "aliases.h" +#include "mem_allocator.h" +#include "mem_ctx.h" #include "tiffread.h" #include #include #include int main(int argc, char *argv[]) { + wapp_mem_ctx_init(10 * 1024 * 1024, 2 * 1024 * 1024); + Allocator ctx_main_allocator = wapp_mem_ctx_allocator(CTX_DEST_BUFFER_MAIN); + Allocator ctx_temp_allocator = wapp_mem_ctx_allocator(CTX_DEST_BUFFER_TEMP); + (void)(ctx_temp_allocator); + const char *file_to_open = argc > 1 ? argv[1] : "./resources/test.tif"; - FILE *fp = fopen(file_to_open, "r"); + read_baseline_tiff(file_to_open, &ctx_main_allocator); - TiffHdr header; - TiffIFD ifd = {0}; + // FILE *fp = fopen(file_to_open, "rb"); - fread(&header, sizeof(TiffHdr), 1, fp); + // TiffHdr header; + // fread(&header, sizeof(header), 1, fp); - printf("ORDER: %04x\n", header.order); - printf("MAGIC: %04x\n", header.magic); + // TiffIFD ifd = {0}; - fseek(fp, header.first_ifd_offset, SEEK_SET); + // printf("ORDER: %04x\n", header.order); + // printf("MAGIC: %04x\n", header.magic); - fread(&(ifd.count), sizeof(ifd.count), 1, fp); + // fseek(fp, header.first_ifd_offset, SEEK_SET); - printf("COUNT: %u\n", ifd.count); + // fread(&(ifd.count), sizeof(ifd.count), 1, fp); - TiffField fields[ifd.count]; - memset(fields, 0, sizeof(TiffField) * ifd.count); + // printf("COUNT: %u\n", ifd.count); - for (u32 i = 0; i < ifd.count; ++i) { - fread(fields + i, sizeof(TiffField), 1, fp); - printf("ENTRY %02u\n", i); - printf("\t TAG: %04u (%s)\n", fields[i].tag, - tag_names[fields[i].tag] ? tag_names[fields[i].tag] : "UNKNOWN"); - printf("\t TYPE: 0x%04x (%s)\n", fields[i].type, - filed_types[fields[i].type].name ? filed_types[fields[i].type].name - : "UNKNOWN"); - printf("\t COUNT: %04u\n", fields[i].count); - printf("\tVAL/OFF: %04u\n", fields[i].value_offset); - } + // TiffField fields[ifd.count]; + // memset(fields, 0, sizeof(TiffField) * ifd.count); - fclose(fp); + // for (u32 i = 0; i < ifd.count; ++i) { + // fread(fields + i, sizeof(TiffField), 1, fp); + // printf("ENTRY %02u (%lu)\n", i, ftell(fp)); + // printf("\t TAG: %04u (%s)\n", fields[i].tag, + // tag_names[fields[i].tag] ? tag_names[fields[i].tag] : "UNKNOWN"); + // printf("\t TYPE: 0x%04x (%s)\n", fields[i].type, + // field_types[fields[i].type].name ? + // field_types[fields[i].type].name + // : "UNKNOWN"); + // printf("\t COUNT: %04u\n", fields[i].count); + // printf("\tVAL/OFF: %04u\n", fields[i].value_offset); + // } + + wapp_mem_ctx_free(); return 0; } diff --git a/src/tiffread.c b/src/tiffread.c new file mode 100644 index 0000000..af51b0b --- /dev/null +++ b/src/tiffread.c @@ -0,0 +1,455 @@ +#include "tiffread.h" +#include "aliases.h" +#include "endianness.h" +#include "image.h" +#include "mem_allocator.h" +#include "mem_ctx.h" +#include "mem_libc.h" +#include +#include +#include +#include +#include + +#define TIFF_MAGIC 0x002a + +#define TIFF_FILENAME_MIN_LENGTH 6 +#define TIFF_EXT_MAX_LENGTH 5 +#define IS_TIFF_EXTENSION(EXT) \ + (strncmp(EXT, ".tif", 4) == 0 || strncmp(EXT, ".tiff", 5) == 0) + +#define NULL_TIFF_HEADER ((TiffHdr){0}) +#define IS_NULL_HEADER(HDR) (HDR.order == 0) + +#define NULL_TIFF_IFD ((TiffIFD){0}) +#define IS_NULL_IFD(IFD) (IFD.count == 0) + +enum tiff_image_types { + TIFF_IMAGE_BILEVEL, + TIFF_IMAGE_GRAYSCALE, + TIFF_IMAGE_PALETTE, + TIFF_IMAGE_RGB, + + COUNT_TIFF_IMAGE, +}; + +// clang-format off +internal u16 tiff_image_identifiers[COUNT_TIFF_IMAGE] = { + [TIFF_IMAGE_BILEVEL] = 0, + [TIFF_IMAGE_GRAYSCALE] = TIFF_PUBLIC_TAG_BITS_PER_SAMPLE, + [TIFF_IMAGE_PALETTE] = TIFF_PUBLIC_TAG_BITS_PER_SAMPLE | TIFF_PUBLIC_TAG_COLOR_MAP, + [TIFF_IMAGE_RGB] = TIFF_PUBLIC_TAG_BITS_PER_SAMPLE | TIFF_PUBLIC_TAG_SAMPLES_PER_PIXEL, +}; +// clang-format on + +TiffHdr read_tiff_header(FILE *fp); +TiffIFD read_ifd(FILE *fp, const TiffHdr *header, u32 offset, + const Allocator *allocator); +Image *read_fields(FILE *fp, const TiffHdr *header, const TiffIFD *ifd, + const Allocator *allocator); +void read_from_file_with_offset(FILE *fp, void *dst, u64 count, u64 offset); + +Image *read_baseline_tiff(const char *file, const Allocator *allocator) { + Image *img_out = NULL; + + if (!file) { + goto RETURN_IMG; + } + + u64 name_length = strlen(file); + if (name_length < TIFF_FILENAME_MIN_LENGTH) { + goto RETURN_IMG; + } + + const char *ext = NULL; + for (u32 i = name_length - 1; i >= name_length - TIFF_FILENAME_MIN_LENGTH; + --i) { + if (file[i] == '.') { + ext = &(file[i]); + break; + } + } + + if (!ext || !IS_TIFF_EXTENSION(ext)) { + goto RETURN_IMG; + } + + FILE *fp = fopen(file, "rb"); + if (!fp) { + goto RETURN_IMG; + } + + TiffHdr header = read_tiff_header(fp); + if (IS_NULL_HEADER(header)) { + goto FILE_CLEANUP; + } + + Allocator alloc; + if (!allocator) { + alloc = wapp_mem_libc_allocator(); + } else { + alloc = *allocator; + } + + TiffIFD ifd = read_ifd(fp, &header, header.first_ifd_offset, &alloc); + read_fields(fp, &header, &ifd, &alloc); + +FILE_CLEANUP: + fclose(fp); + +RETURN_IMG: + return img_out; +} + +TiffHdr read_tiff_header(FILE *fp) { + if (!fp) { + return NULL_TIFF_HEADER; + } + + fseek(fp, 0, SEEK_SET); + + TiffHdr header = NULL_TIFF_HEADER; + fread((void *)&header, sizeof(TiffHdr), 1, fp); + + switch (header.order) { + case TIFF_ORDER_LITTLE_ENDIAN: + if (IS_BIG_ENDIAN) { + header.magic = htons(header.magic); + header.first_ifd_offset = htonl(header.first_ifd_offset); + } + + break; + case TIFF_ORDER_BIG_ENDIAN: + if (IS_LITTLE_ENDIAN) { + header.magic = ntohs(header.magic); + header.first_ifd_offset = ntohl(header.first_ifd_offset); + } + + break; + default: + return NULL_TIFF_HEADER; + } + + if (header.magic != TIFF_MAGIC) { + return NULL_TIFF_HEADER; + } + + return header; +} + +TiffIFD read_ifd(FILE *fp, const TiffHdr *header, u32 offset, + const Allocator *allocator) { + if (!fp || !header) { + return NULL_TIFF_IFD; + } + + fseek(fp, offset, SEEK_SET); + + TiffIFD ifd = NULL_TIFF_IFD; + + fread(&(ifd.count), sizeof(ifd.count), 1, fp); + + switch (header->order) { + case TIFF_ORDER_LITTLE_ENDIAN: + if (IS_BIG_ENDIAN) { + ifd.count = htons(ifd.count); + } + + break; + case TIFF_ORDER_BIG_ENDIAN: + if (IS_LITTLE_ENDIAN) { + ifd.count = ntohs(ifd.count); + } + + break; + } + + u64 field_byte_count = sizeof(TiffField) * ifd.count; + ifd.fields = + (TiffField *)wapp_mem_allocator_alloc(allocator, field_byte_count); + + fread(ifd.fields, field_byte_count, 1, fp); + fread(&(ifd.next_ifd), sizeof(ifd.next_ifd), 1, fp); + + return ifd; +} + +Image *read_fields(FILE *fp, const TiffHdr *header, const TiffIFD *ifd, + const Allocator *allocator) { + if (!fp || !header || !ifd) { + return NULL; + } + + u16 identifier = 0; + + u16 photometric_interpretation = TIFF_PHOTOMETRIC_INTERPRETATION_INVALID; + + u16 samples_per_pixel = 1; + u16 *bits_per_sample = NULL; + + u32 strip_count = 1; + u8 *strip_offsets = NULL; + u16 strip_offsets_type = TIFF_FIELD_TYPE_LONG; + u8 *strip_byte_counts = NULL; + u16 strip_byte_counts_type = TIFF_FIELD_TYPE_LONG; + + u64 width = 0; + u64 height = 0; + + for (u64 i = 0; i < ifd->count; ++i) { + TiffField *field = &(ifd->fields[i]); + switch (header->order) { + case TIFF_ORDER_LITTLE_ENDIAN: + if (IS_BIG_ENDIAN) { + field->tag = htons(field->tag); + field->type = htons(field->type); + field->count = htonl(field->count); + field->value_offset = htonl(field->value_offset); + } + + break; + case TIFF_ORDER_BIG_ENDIAN: + if (IS_LITTLE_ENDIAN) { + field->tag = ntohs(field->tag); + field->type = ntohs(field->type); + field->count = ntohl(field->count); + field->value_offset = ntohl(field->value_offset); + } + + break; + } + + switch (field->tag) { + case TIFF_PUBLIC_TAG_IMAGE_WIDTH: + if (field->count != 1) { + return NULL; + } + + width = field->value_offset; + + break; + case TIFF_PUBLIC_TAG_IMAGE_LENGTH: + if (field->count != 1) { + return NULL; + } + + height = field->value_offset; + + break; + case TIFF_PUBLIC_TAG_BITS_PER_SAMPLE: + identifier |= field->tag; + + u64 byte_count = field->count * sizeof(u16); + bits_per_sample = wapp_mem_ctx_alloc(CTX_DEST_BUFFER_TEMP, byte_count); + if (!bits_per_sample) { + break; + } + + if (byte_count <= 4) { + bits_per_sample = (u16 *)&(field->value_offset); + } else { + read_from_file_with_offset(fp, bits_per_sample, byte_count, + field->value_offset); + + for (u64 i = 0; i < field->count; ++i) { + switch (header->order) { + case TIFF_ORDER_LITTLE_ENDIAN: + if (IS_BIG_ENDIAN) { + bits_per_sample[i] = htons(bits_per_sample[i]); + } + + break; + case TIFF_ORDER_BIG_ENDIAN: + if (IS_LITTLE_ENDIAN) { + bits_per_sample[i] = ntohs(bits_per_sample[i]); + } + + break; + } + } + } + + break; + case TIFF_PUBLIC_TAG_COMPRESSION: + if (field->count != 1) { + return NULL; + } + + switch (field->value_offset) { + case TIFF_COMPRESSION_UNCOMPRESSED: + break; + case TIFF_COMPRESSION_CCITT_1D: + case TIFF_COMPRESSION_GROUP_3_FAX: + case TIFF_COMPRESSION_GROUP_4_FAX: + case TIFF_COMPRESSION_LZW: + case TIFF_COMPRESSION_JPEG: + case TIFF_COMPRESSION_PACK_BITS: + return NULL; + } + + break; + case TIFF_PUBLIC_TAG_PHOTOMETRIC_INTERPRETATION: + photometric_interpretation = field->value_offset; + break; + case TIFF_PUBLIC_TAG_STRIP_OFFSETS: { + strip_offsets_type = field->type; + u16 field_type_byte_count = field_types[strip_offsets_type].byte_count; + u32 total_byte_count = field->count * field_type_byte_count; + strip_count = field->count; + + strip_offsets = + (u8 *)wapp_mem_ctx_alloc(CTX_DEST_BUFFER_TEMP, total_byte_count); + if (!strip_offsets) { + return NULL; + } + + if (total_byte_count <= 4) { + memcpy(strip_offsets, &(field->value_offset), 4); + + switch (header->order) { + case TIFF_ORDER_LITTLE_ENDIAN: + if (IS_BIG_ENDIAN) { + u32 val = htonl(*(u32 *)strip_offsets); + strip_offsets = (u8 *)&val; + } + + break; + case TIFF_ORDER_BIG_ENDIAN: + if (IS_LITTLE_ENDIAN) { + u32 val = ntohl(*(u32 *)strip_offsets); + strip_offsets = (u8 *)&val; + } + + break; + } + } else { + read_from_file_with_offset(fp, strip_offsets, total_byte_count, + field->value_offset); + } + + break; + } + + case TIFF_PUBLIC_TAG_SAMPLES_PER_PIXEL: + if (field->count != 1) { + return NULL; + } + + identifier |= field->tag; + samples_per_pixel = field->value_offset; + + break; + case TIFF_PUBLIC_TAG_ROWS_PER_STRIP: + if (field->count != 1 || height == 0) { + return NULL; + } + + strip_count = + floor((f64)(height + field->value_offset - 1) / field->value_offset); + + break; + case TIFF_PUBLIC_TAG_STRIP_BYTE_COUNTS: { + strip_byte_counts_type = field->type; + u16 field_type_byte_count = field_types[strip_offsets_type].byte_count; + u32 total_byte_count = field->count * field_type_byte_count; + strip_count = field->count; + + strip_byte_counts = + (u8 *)wapp_mem_ctx_alloc(CTX_DEST_BUFFER_TEMP, total_byte_count); + if (!strip_byte_counts) { + return NULL; + } + + if (total_byte_count <= 4) { + memcpy(strip_byte_counts, &(field->value_offset), 4); + + switch (header->order) { + case TIFF_ORDER_LITTLE_ENDIAN: + if (IS_BIG_ENDIAN) { + u32 val = htonl(*(u32 *)strip_byte_counts); + strip_byte_counts = (u8 *)&val; + } + + break; + case TIFF_ORDER_BIG_ENDIAN: + if (IS_LITTLE_ENDIAN) { + u32 val = ntohl(*(u32 *)strip_byte_counts); + strip_byte_counts = (u8 *)&val; + } + + break; + } + } else { + read_from_file_with_offset(fp, strip_byte_counts, total_byte_count, + field->value_offset); + } + + break; + } + case TIFF_PUBLIC_TAG_PLANAR_CONFIGURATION: + if (field->count != 1 || + field->value_offset != TIFF_PLANAR_CONFIG_CHUNKY) { + return NULL; + } + + break; + case TIFF_PUBLIC_TAG_COLOR_MAP: + identifier |= field->tag; + break; + default: + break; + } + } + + if (identifier == tiff_image_identifiers[TIFF_IMAGE_BILEVEL]) { + printf("BILEVEL\n"); + } else if (identifier == tiff_image_identifiers[TIFF_IMAGE_GRAYSCALE]) { + printf("GRAYSCALE\n"); + } else if (identifier == tiff_image_identifiers[TIFF_IMAGE_PALETTE]) { + printf("PALETTE\n"); + } else if (identifier == tiff_image_identifiers[TIFF_IMAGE_RGB]) { + printf("RGB\n"); + } else { + printf("UNKNOWN\n"); + } + + printf("WIDTH: %lu, HEIGHT: %lu\n", width, height); + printf("SAMPLES: %u\n", samples_per_pixel); + printf("STRIPS: %u\n", strip_count); + printf("PHOTOMETRIC INTERPRETATION: %u\n", photometric_interpretation); + for (u64 i = 0; i < samples_per_pixel; ++i) { + printf("%u\n", bits_per_sample[i]); + } + + printf("OFFSETS: "); + for (u64 i = 0; i < strip_count; ++i) { + if (strip_offsets_type == TIFF_FIELD_TYPE_LONG) { + printf("%u ", ((u32 *)strip_offsets)[i]); + } else { + printf("%u ", ((u16 *)strip_offsets)[i]); + } + } + printf("\n"); + + printf("COUNTS: "); + for (u64 i = 0; i < strip_count; ++i) { + if (strip_byte_counts_type == TIFF_FIELD_TYPE_LONG) { + printf("%u ", ((u32 *)strip_byte_counts)[i]); + } else { + printf("%u ", ((u16 *)strip_byte_counts)[i]); + } + } + printf("\n"); + + return NULL; +} + +void read_from_file_with_offset(FILE *fp, void *dst, u64 count, u64 offset) { + if (!fp || !dst) { + return; + } + + fseek(fp, offset, SEEK_SET); + + fread(dst, count, 1, fp); +} diff --git a/src/tiffread.h b/src/tiffread.h index 7b6ac1c..832deb1 100644 --- a/src/tiffread.h +++ b/src/tiffread.h @@ -6,6 +6,7 @@ extern "C" { #endif // __cplusplus #include "aliases.h" +#include "image.h" #define TAG_MAX_COUNT (UINT16_MAX * sizeof(const char *)) // Technically, the type parameter is 2-bytes long, but there aren't so many @@ -13,7 +14,7 @@ extern "C" { #define TYPE_MAX_COUNT (UINT8_MAX * sizeof(const char *)) // clang-format off -enum { +enum tiff_byte_order { TIFF_ORDER_LITTLE_ENDIAN = 0x4949, TIFF_ORDER_BIG_ENDIAN = 0x4d4d, }; @@ -31,7 +32,7 @@ enum tiff_public_tags { #include "tiff_public_tags.inc" }; -const char *tag_names[TAG_MAX_COUNT] = { +internal const char *tag_names[TAG_MAX_COUNT] = { #define TIFF_TAG_LOOKUP #include "tiff_public_tags.inc" #undef TIFF_TAG_LOOKUP @@ -43,11 +44,11 @@ enum tiff_field_types { typedef struct field_type TiffFieldType; struct field_type { - const char *name; - u16 byte_count; + const char *name; + u16 byte_count; }; -TiffFieldType filed_types[TYPE_MAX_COUNT] = { +internal TiffFieldType field_types[TYPE_MAX_COUNT] = { #define TIFF_TYPE_LOOKUP #include "tiff_field_types.inc" #undef TIFF_TYPE_LOOKUP @@ -79,9 +80,7 @@ enum tiff_compression { TIFF_COMPRESSION_JPEG = 0x0006, TIFF_COMPRESSION_PACK_BITS = 0x8005, }; -// clang-format on -// clang-format off enum tiff_photometric_interpretation { TIFF_PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO = 0x0000, TIFF_PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO = 0x0001, @@ -91,9 +90,18 @@ enum tiff_photometric_interpretation { TIFF_PHOTOMETRIC_INTERPRETATION_CMYK = 0x0005, TIFF_PHOTOMETRIC_INTERPRETATION_YCbCr = 0x0006, TIFF_PHOTOMETRIC_INTERPRETATION_CIELab = 0x0008, + + TIFF_PHOTOMETRIC_INTERPRETATION_INVALID, +}; + +enum tiff_planar_configuration { + TIFF_PLANAR_CONFIG_CHUNKY = 0x0001, + TIFF_PLANAR_CONFIG_PLANAR = 0x0002, }; // clang-format on +Image *read_baseline_tiff(const char *file, const Allocator *allocator); + #ifdef __cplusplus } #endif // __cplusplus