tiffread/src/tiff/tiffread.c

860 lines
24 KiB
C

#include "tiffread.h"
#include "aliases.h"
#include "endianness.h"
#include "image.h"
#include "mem_arena.h"
#include <assert.h>
#include <math.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#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;
}