#include "tiffread.h" #include "aliases.h" #include "endianness.h" #include "image.h" #include "mem_arena.h" #include #include #include #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_ALPHA ((TiffAlpha){0}) #define INVALID_SAMPLE_COUNT 0 #define INVALID_ROWS_PER_STRIP 0 #define INVALID_STRIP_COUNT 0 #define INVALID_ALPHA_OFFSET -1 #define TIFF_SHORT_BYTE_COUNT field_types[TIFF_FIELD_TYPE_SHORT].byte_count #define TIFF_LONG_BYTE_COUNT field_types[TIFF_FIELD_TYPE_LONG].byte_count #define RGB_SAMPLE_COUNT 3 #define MIN_BITS_PER_SAMPLE 8 #define TEMP_ARENA_CAPACITY (20 * 1024 * 1024) #define TYPE_MAX_COUNT (UINT8_MAX * sizeof(const char *)) // clang-format off enum tiff_byte_order { TIFF_ORDER_LITTLE_ENDIAN = 0x4949, TIFF_ORDER_BIG_ENDIAN = 0x4d4d, }; enum tiff_public_tags { #include "tiff_public_tags.inc" }; enum tiff_field_types { #include "tiff_field_types.inc" }; typedef struct field_type TiffFieldType; struct field_type { const char *name; u16 byte_count; }; internal TiffFieldType field_types[TYPE_MAX_COUNT] = { #define TIFF_TYPE_LOOKUP #include "tiff_field_types.inc" #undef TIFF_TYPE_LOOKUP }; typedef enum tiff_image_type_names { TIFF_IMAGE_TYPE_INVALID = 0, TIFF_IMAGE_TYPE_BILEVEL = 1, TIFF_IMAGE_TYPE_GRAYSCALE = TIFF_PUBLIC_TAG_BITS_PER_SAMPLE, TIFF_IMAGE_TYPE_PALETTE = TIFF_PUBLIC_TAG_BITS_PER_SAMPLE | TIFF_PUBLIC_TAG_COLOR_MAP, TIFF_IMAGE_TYPE_RGB = TIFF_PUBLIC_TAG_BITS_PER_SAMPLE | TIFF_PUBLIC_TAG_SAMPLES_PER_PIXEL, } TiffImageType; enum tiff_extra_samples { TIFF_EXTRA_SAMPLE_UNSPECIFIED = 0x0000, TIFF_EXTRA_SAMPLE_ASSOCIATED_ALPHA = 0x0001, TIFF_EXTRA_SAMPLE_UNASSOCIATED_ALPHA = 0x0002, }; enum tiff_compression { TIFF_COMPRESSION_UNCOMPRESSED = 0x0001, TIFF_COMPRESSION_CCITT_1D = 0x0002, TIFF_COMPRESSION_GROUP_3_FAX = 0x0003, TIFF_COMPRESSION_GROUP_4_FAX = 0x0004, TIFF_COMPRESSION_LZW = 0x0005, TIFF_COMPRESSION_JPEG = 0x0006, TIFF_COMPRESSION_PACK_BITS = 0x8005, }; enum tiff_photometric_interpretation { TIFF_PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO = 0x0000, TIFF_PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO = 0x0001, TIFF_PHOTOMETRIC_INTERPRETATION_RGB = 0x0002, TIFF_PHOTOMETRIC_INTERPRETATION_RGB_PALETTE = 0x0003, TIFF_PHOTOMETRIC_INTERPRETATION_TRANSPARENCY_MASK = 0x0004, 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 typedef struct short_long_value ShortLongValue; struct short_long_value { union { u16 short_val; u32 long_val; }; }; typedef struct hdr TiffHdr; struct hdr { u16 order; u16 magic; u32 first_ifd_offset; }; typedef struct field TiffField; struct field { u16 tag; u16 type; u32 count; u32 value_offset; }; typedef struct IFD TiffIFD; struct IFD { u16 count; TiffField *fields; u32 next_ifd; }; typedef enum { ALPHA_TYPE_UNDEFINED = 0, ALPHA_TYPE_ASSOCIATED, ALPHA_TYPE_UNASSOCIATED, } AlphaType; typedef struct tiff_alpha TiffAlpha; struct tiff_alpha { AlphaType type; u32 sample_offset; }; typedef struct tiff_sample_bits TiffSampleBits; struct tiff_sample_bits { u16 r; u16 g; u16 b; u16 a; }; typedef struct tiff_strip TiffStrip; struct tiff_strip { u32 offset; u32 byte_count; }; typedef struct tiff_image TiffImage; struct tiff_image { TiffImageType type; u32 image_width; u32 image_length; u32 bits_per_sample; u32 sample_count; bool bits_per_sample_offset; u16 compression; u16 photometric_interpretation; u16 strip_offsets_type_byte_count; ShortLongValue strip_offsets; bool strip_offsets_offset; u32 rows_per_strip; u16 strip_byte_count_type_byte_count; ShortLongValue strip_byte_counts; bool strip_byte_counts_offset; u32 strip_count; u32 color_map; u32 extra_samples; u32 extra_samples_count; bool extra_samples_offset; TiffAlpha alpha; TiffSampleBits rgba_bits_per_sample; TiffStrip *strips; }; typedef struct tiff_reader TiffReader; struct tiff_reader { FILE *fp; TiffHdr header; TiffIFD ifd; TiffImage img; Pixel *pixels; }; typedef struct strip_data_field StripDataField; struct strip_data_field { u32 *strip_value; const ShortLongValue *value_from_file; u32 strip_index; u32 length_in_bytes; u16 type_byte_count; }; internal bool read_tiff_header(TiffReader *reader); internal bool read_ifd(TiffReader *reader, Arena *arena); internal bool read_ifd_fields(TiffReader *reader); internal void read_alpha(TiffReader *reader); internal void read_bits_per_sample(TiffReader *reader); internal bool read_image_pixels(TiffReader *reader, Arena *arena); internal bool read_strip_data(TiffReader *reader, Arena *arena); internal void read_strips(TiffReader *reader); internal void read_strip_data_field(const TiffReader *reader, StripDataField *field); internal bool read_field(const TiffField *field, TiffImage *img); internal bool validate_image_type(const TiffImage *img); internal void fread_with_offset(FILE *fp, void *dst, u64 count, u64 offset); internal bool valid_tiff_file(const char *file); Image *read_baseline_tiff(const char *file, Arena *arena) { Image *img_out = NULL; if (!file || !arena || !valid_tiff_file(file)) { goto READ_BASELINE_RETURN_IMG; } TiffReader reader = {.fp = fopen(file, "rb")}; if (!reader.fp) { goto READ_BASELINE_RETURN_IMG; } Arena *temp_arena = NULL; if (!wapp_mem_arena_init(&temp_arena, TEMP_ARENA_CAPACITY)) { goto READ_BASELINE_FILE_CLEANUP; } if (!read_tiff_header(&reader)) { goto READ_BASELINE_DESTROY_ARENA; } if (!read_ifd(&reader, temp_arena)) { goto READ_BASELINE_DESTROY_ARENA; } if (!read_ifd_fields(&reader)) { goto READ_BASELINE_DESTROY_ARENA; } assert((reader.img.type == TIFF_IMAGE_TYPE_RGB) && "Currently, only RGB images are supported"); bool image_is_8_bit = (reader.img.rgba_bits_per_sample.r == MIN_BITS_PER_SAMPLE && reader.img.rgba_bits_per_sample.g == MIN_BITS_PER_SAMPLE && reader.img.rgba_bits_per_sample.b == MIN_BITS_PER_SAMPLE && reader.img.rgba_bits_per_sample.a == MIN_BITS_PER_SAMPLE); assert(image_is_8_bit && "Currently, only 8-bit images are supported"); if (!read_image_pixels(&reader, temp_arena)) { goto READ_BASELINE_DESTROY_ARENA; } img_out = create_image(reader.img.image_width, reader.img.image_length, reader.pixels, arena); READ_BASELINE_DESTROY_ARENA: wapp_mem_arena_destroy(&temp_arena); READ_BASELINE_FILE_CLEANUP: fclose(reader.fp); READ_BASELINE_RETURN_IMG: return img_out; } internal bool read_tiff_header(TiffReader *reader) { fread_with_offset(reader->fp, &(reader->header), sizeof(TiffHdr), 0); switch (reader->header.order) { case TIFF_ORDER_LITTLE_ENDIAN: if (IS_BIG_ENDIAN) { reader->header.magic = htons(reader->header.magic); reader->header.first_ifd_offset = htonl(reader->header.first_ifd_offset); } break; case TIFF_ORDER_BIG_ENDIAN: if (IS_LITTLE_ENDIAN) { reader->header.magic = ntohs(reader->header.magic); reader->header.first_ifd_offset = ntohl(reader->header.first_ifd_offset); } break; default: return false; } if (reader->header.magic != TIFF_MAGIC) { return false; } return true; } internal bool read_ifd(TiffReader *reader, Arena *arena) { fread_with_offset(reader->fp, &(reader->ifd.count), sizeof(reader->ifd.count), reader->header.first_ifd_offset); switch (reader->header.order) { case TIFF_ORDER_LITTLE_ENDIAN: if (IS_BIG_ENDIAN) { reader->ifd.count = htons(reader->ifd.count); } break; case TIFF_ORDER_BIG_ENDIAN: if (IS_LITTLE_ENDIAN) { reader->ifd.count = ntohs(reader->ifd.count); } break; } u64 field_byte_count = sizeof(TiffField) * reader->ifd.count; reader->ifd.fields = (TiffField *)wapp_mem_arena_alloc(arena, field_byte_count); if (!(reader->ifd.fields)) { return false; } fread(reader->ifd.fields, field_byte_count, 1, reader->fp); fread(&(reader->ifd.next_ifd), sizeof(reader->ifd.next_ifd), 1, reader->fp); return true; } internal bool read_ifd_fields(TiffReader *reader) { reader->img.type = TIFF_IMAGE_TYPE_BILEVEL; for (u64 i = 0; i < reader->ifd.count; ++i) { TiffField *field = &(reader->ifd.fields[i]); switch (reader->header.order) { case TIFF_ORDER_BIG_ENDIAN: if (IS_LITTLE_ENDIAN) { field->tag = ntohs(field->tag); field->type = ntohs(field->type); field->count = ntohl(field->count); if (field_types[field->type].byte_count > 2 || field_types[field->type].byte_count * field->count > 4) { field->value_offset = ntohl(field->value_offset); } else { field->value_offset = ntohs(field->value_offset); } } break; case TIFF_ORDER_LITTLE_ENDIAN: if (IS_BIG_ENDIAN) { field->tag = htons(field->tag); field->type = htons(field->type); field->count = htonl(field->count); if (field_types[field->type].byte_count > 2 || field_types[field->type].byte_count * field->count > 4) { field->value_offset = htonl(field->value_offset); } else { field->value_offset = htons(field->value_offset); } } break; } if (!read_field(field, &(reader->img))) { return false; } } if (!validate_image_type(&(reader->img))) { return false; } read_alpha(reader); read_bits_per_sample(reader); #ifdef DEBUG // clang-format off printf("SIZE (width, height) : %u, %u\n", reader->img.image_width, reader->img.image_length); printf("SAMPLES (bits per sample, count, is offset) : %u, %u, %u\n", reader->img.bits_per_sample, reader->img.sample_count, reader->img.bits_per_sample_offset); printf("EXTRA SAMPLES (samples, count, is offset) : %u, %u, %u\n", reader->img.extra_samples, reader->img.extra_samples_count, reader->img.extra_samples_offset); printf("ALPHA (type, offset) : %u, %u\n", reader->img.alpha.type, reader->img.alpha.sample_offset); printf("SAMPLES PER PIXEL : %u\n", reader->img.sample_count); printf("PHOTOMETRIC INTERPRETATION : %u\n", reader->img.photometric_interpretation); printf("ROWS PER STRIP (rows, strip count) : %u, %u\n", reader->img.rows_per_strip, reader->img.strip_count); printf("STRIP OFFSETS (offsets, is offset) : %u, %u\n", reader->img.strip_offsets.long_val, reader->img.strip_offsets_offset); printf("STRIP BYTES (byte count, is offset) : %u, %u\n", reader->img.strip_byte_counts.long_val, reader->img.strip_byte_counts_offset); // clang-format on #endif return true; } internal void read_alpha(TiffReader *reader) { reader->img.alpha = NULL_TIFF_ALPHA; if (reader->img.extra_samples_count == 0) { return; } u64 byte_count = TIFF_SHORT_BYTE_COUNT * reader->img.extra_samples_count; u16 samples[reader->img.extra_samples_count]; memset(samples, 0, byte_count); if (reader->img.extra_samples_offset) { fread_with_offset(reader->fp, samples, byte_count, reader->img.extra_samples); switch (reader->header.order) { case TIFF_ORDER_BIG_ENDIAN: if (IS_LITTLE_ENDIAN) { for (u64 i = 0; i < reader->img.extra_samples_count; ++i) { samples[i] = ntohs(samples[i]); } } break; case TIFF_ORDER_LITTLE_ENDIAN: if (IS_BIG_ENDIAN) { for (u64 i = 0; i < reader->img.extra_samples_count; ++i) { samples[i] = htons(samples[i]); } } break; } } else { memcpy(samples, &(reader->img.extra_samples), byte_count); } for (u32 i = 0; i < reader->img.extra_samples_count; ++i) { u16 *sample = &(samples[i]); if (*sample == TIFF_EXTRA_SAMPLE_ASSOCIATED_ALPHA || *sample == TIFF_EXTRA_SAMPLE_UNASSOCIATED_ALPHA) { reader->img.alpha.type = *sample == TIFF_EXTRA_SAMPLE_ASSOCIATED_ALPHA ? ALPHA_TYPE_ASSOCIATED : ALPHA_TYPE_UNASSOCIATED; reader->img.alpha.sample_offset = i; break; } } } internal void read_bits_per_sample(TiffReader *reader) { TiffAlpha alpha = reader->img.alpha; u64 main_samples = reader->img.sample_count - reader->img.extra_samples_count; u64 byte_count = TIFF_SHORT_BYTE_COUNT * reader->img.sample_count; u16 bits_per_sample[reader->img.sample_count]; memset(bits_per_sample, 0, byte_count); if (reader->img.bits_per_sample_offset) { fread_with_offset(reader->fp, bits_per_sample, byte_count, reader->img.bits_per_sample); switch (reader->header.order) { case TIFF_ORDER_BIG_ENDIAN: if (IS_LITTLE_ENDIAN) { for (u64 i = 0; i < reader->img.sample_count; ++i) { bits_per_sample[i] = ntohs(bits_per_sample[i]); } } break; case TIFF_ORDER_LITTLE_ENDIAN: if (IS_BIG_ENDIAN) { for (u64 i = 0; i < reader->img.sample_count; ++i) { bits_per_sample[i] = htons(bits_per_sample[i]); } } break; } } else { memcpy(bits_per_sample, &(reader->img.bits_per_sample), byte_count); } TiffSampleBits *bits = &(reader->img.rgba_bits_per_sample); *bits = (TiffSampleBits){0}; memcpy(bits, bits_per_sample, TIFF_SHORT_BYTE_COUNT * main_samples); // Set missing samples if image doesn't have all RGB samples if (main_samples < RGB_SAMPLE_COUNT) { u64 count = RGB_SAMPLE_COUNT - main_samples; for (u64 i = 0; i < count; ++i) { u16 *value = &(((u16 *)(&bits))[main_samples + i]); *value = MIN_BITS_PER_SAMPLE; } } if (alpha.type == ALPHA_TYPE_UNDEFINED) { bits->a = MIN_BITS_PER_SAMPLE; } else { void *alpha_sample = &(bits_per_sample[main_samples + alpha.sample_offset]); memcpy(&(bits->a), alpha_sample, TIFF_SHORT_BYTE_COUNT); } bits->r = bits->r < MIN_BITS_PER_SAMPLE ? MIN_BITS_PER_SAMPLE : bits->r; bits->g = bits->g < MIN_BITS_PER_SAMPLE ? MIN_BITS_PER_SAMPLE : bits->g; bits->b = bits->b < MIN_BITS_PER_SAMPLE ? MIN_BITS_PER_SAMPLE : bits->b; bits->a = bits->a < MIN_BITS_PER_SAMPLE ? MIN_BITS_PER_SAMPLE : bits->a; } internal bool read_image_pixels(TiffReader *reader, Arena *arena) { reader->pixels = NULL; u64 img_byte_count = sizeof(Pixel) * reader->img.image_width * reader->img.image_length; if (!read_strip_data(reader, arena)) { return false; } #ifdef DEBUG for (u32 i = 0; i < reader->img.strip_count; ++i) { printf("%u, %u\n", reader->img.strips[i].offset, reader->img.strips[i].byte_count); } #endif reader->pixels = wapp_mem_arena_alloc(arena, img_byte_count); if (!reader->pixels) { return false; } read_strips(reader); return true; } internal bool read_strip_data(TiffReader *reader, Arena *arena) { reader->img.strips = wapp_mem_arena_alloc(arena, sizeof(TiffStrip) * reader->img.strip_count); if (!(reader->img.strips)) { return false; } u32 offsets_total_bytes = reader->img.strip_count * reader->img.strip_offsets_type_byte_count; u32 byte_count_total_bytes = reader->img.strip_count * reader->img.strip_byte_count_type_byte_count; if ((!(reader->img.strip_offsets_offset) && offsets_total_bytes > TIFF_LONG_BYTE_COUNT) || (!(reader->img.strip_byte_counts_offset) && byte_count_total_bytes > TIFF_LONG_BYTE_COUNT)) { return false; } u16 offset_size = reader->img.strip_offsets_type_byte_count; u16 counts_size = reader->img.strip_byte_count_type_byte_count; for (u32 i = 0; i < reader->img.strip_count; ++i) { TiffStrip *strip = &(reader->img.strips[i]); StripDataField offsets = { .strip_value = &(strip->offset), .value_from_file = &(reader->img.strip_offsets), .strip_index = i, .length_in_bytes = offsets_total_bytes, .type_byte_count = offset_size, }; StripDataField counts = { .strip_value = &(strip->byte_count), .value_from_file = &(reader->img.strip_byte_counts), .strip_index = i, .length_in_bytes = byte_count_total_bytes, .type_byte_count = counts_size, }; read_strip_data_field(reader, &offsets); read_strip_data_field(reader, &counts); } return true; } internal void read_strips(TiffReader *reader) { u64 position = 0; u64 main_samples = reader->img.sample_count - reader->img.extra_samples_count; TiffAlpha alpha = reader->img.alpha; Pixel *p; u64 start_offset; u64 alpha_offset; for (u64 i = 0; i < reader->img.strip_count; ++i) { const TiffStrip *strip = &(reader->img.strips[i]); for (u64 j = 0; j < strip->byte_count / reader->img.sample_count; ++j) { p = &(reader->pixels[position]); start_offset = strip->offset + j * reader->img.sample_count; alpha_offset = start_offset + main_samples + alpha.sample_offset; fread_with_offset(reader->fp, p, main_samples, start_offset); if (alpha.type == ALPHA_TYPE_UNDEFINED) { p->a = 255; } else { fread_with_offset(reader->fp, &(p->a), 1, alpha_offset); if (alpha.type == ALPHA_TYPE_UNASSOCIATED) { p->r *= p->a; p->g *= p->a; p->b *= p->a; } } ++position; } } } internal void read_strip_data_field(const TiffReader *reader, StripDataField *field) { u16 tiff_long_byte_count = field_types[TIFF_FIELD_TYPE_LONG].byte_count; bool value_is_file_offset = field->length_in_bytes > tiff_long_byte_count; u32 offset = field->type_byte_count * field->strip_index; if (!value_is_file_offset) { u8 *value = (u8 *)&(field->value_from_file->long_val); memcpy(field->strip_value, value + offset, field->type_byte_count); return; } fread_with_offset(reader->fp, field->strip_value, field->type_byte_count, field->value_from_file->long_val + offset); switch (reader->header.order) { case TIFF_ORDER_BIG_ENDIAN: if (IS_LITTLE_ENDIAN) { *(field->strip_value) = field->type_byte_count > TIFF_SHORT_BYTE_COUNT ? ntohl(*(field->strip_value)) : ntohs(*(field->strip_value)); } break; case TIFF_ORDER_LITTLE_ENDIAN: if (IS_BIG_ENDIAN) { *(field->strip_value) = field->type_byte_count > TIFF_SHORT_BYTE_COUNT ? htonl(*(field->strip_value)) : htons(*(field->strip_value)); } break; } } internal bool read_field(const TiffField *field, TiffImage *img) { switch (field->tag) { case TIFF_PUBLIC_TAG_IMAGE_WIDTH: if (field->count > 1) { return false; } img->image_width = field->value_offset; break; case TIFF_PUBLIC_TAG_IMAGE_LENGTH: if (field->count > 1) { return false; } img->image_length = field->value_offset; break; case TIFF_PUBLIC_TAG_BITS_PER_SAMPLE: img->type |= field->tag; if (img->sample_count == INVALID_SAMPLE_COUNT) { img->sample_count = field->count; } else if (img->sample_count != field->count) { return false; } img->bits_per_sample = field->value_offset; img->bits_per_sample_offset = img->sample_count * field_types[field->type].byte_count > 4; break; case TIFF_PUBLIC_TAG_COMPRESSION: if (field->count > 1 || field->value_offset != TIFF_COMPRESSION_UNCOMPRESSED) { return false; } img->compression = field->value_offset; break; case TIFF_PUBLIC_TAG_PHOTOMETRIC_INTERPRETATION: if (field->count > 1) { return false; } img->photometric_interpretation = field->value_offset; break; case TIFF_PUBLIC_TAG_STRIP_OFFSETS: img->strip_offsets_type_byte_count = field_types[field->type].byte_count; if (img->strip_offsets_type_byte_count == TIFF_SHORT_BYTE_COUNT) { img->strip_offsets.short_val = field->value_offset; } else if (img->strip_offsets_type_byte_count == TIFF_LONG_BYTE_COUNT) { img->strip_offsets.long_val = field->value_offset; } if (img->strip_count == INVALID_STRIP_COUNT) { img->strip_count = field->count; } else if (img->strip_count != field->count) { return false; } img->strip_offsets_offset = img->strip_count * img->strip_offsets_type_byte_count > 4; break; case TIFF_PUBLIC_TAG_SAMPLES_PER_PIXEL: img->type |= field->tag; if (field->count > 1) { return false; } img->sample_count = field->value_offset; break; case TIFF_PUBLIC_TAG_ROWS_PER_STRIP: if (field->count > 1) { return false; } img->rows_per_strip = field->value_offset; if (img->image_length > 0) { img->strip_count = floor((f64)(img->image_length + img->rows_per_strip - 1) / img->rows_per_strip); } else { img->strip_count = INVALID_STRIP_COUNT; } break; case TIFF_PUBLIC_TAG_STRIP_BYTE_COUNTS: img->strip_byte_count_type_byte_count = field_types[field->type].byte_count; if (img->strip_byte_count_type_byte_count == TIFF_SHORT_BYTE_COUNT) { img->strip_byte_counts.short_val = field->value_offset; } else if (img->strip_byte_count_type_byte_count == TIFF_LONG_BYTE_COUNT) { img->strip_byte_counts.long_val = field->value_offset; } if (img->strip_count == INVALID_STRIP_COUNT) { img->strip_count = field->count; } else if (img->strip_count != field->count) { return false; } img->strip_byte_counts_offset = img->strip_count * img->strip_byte_count_type_byte_count > 4; break; case TIFF_PUBLIC_TAG_COLOR_MAP: img->type |= field->tag; break; case TIFF_PUBLIC_TAG_EXTRA_SAMPLES: img->extra_samples_count = field->count; img->extra_samples = field->value_offset; img->extra_samples_offset = img->extra_samples_count * field_types[field->type].byte_count > 4; break; case TIFF_PUBLIC_TAG_PLANAR_CONFIGURATION: if (field->count > 1 || field->value_offset != TIFF_PLANAR_CONFIG_CHUNKY) { return false; } break; } return true; } internal bool validate_image_type(const TiffImage *img) { if (img->type < TIFF_IMAGE_TYPE_BILEVEL || img->type > TIFF_IMAGE_TYPE_RGB) { return false; } switch (img->type) { case TIFF_IMAGE_TYPE_BILEVEL: case TIFF_IMAGE_TYPE_GRAYSCALE: if (img->photometric_interpretation > TIFF_PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO) { return false; } break; case TIFF_IMAGE_TYPE_PALETTE: if (img->photometric_interpretation != TIFF_PHOTOMETRIC_INTERPRETATION_RGB_PALETTE) { return false; } break; case TIFF_IMAGE_TYPE_RGB: if (img->photometric_interpretation != TIFF_PHOTOMETRIC_INTERPRETATION_RGB) { return false; } break; default: return false; } return true; } internal void fread_with_offset(FILE *fp, void *dst, u64 count, u64 offset) { if (!fp || !dst) { return; } fseek(fp, offset, SEEK_SET); fread(dst, count, 1, fp); } internal bool valid_tiff_file(const char *file) { u64 name_length = strlen(file); if (name_length < TIFF_FILENAME_MIN_LENGTH) { return false; } 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)) { return false; } return true; }