Compare commits

...

31 Commits

Author SHA1 Message Date
35a33e2fa6 Reformat 2024-05-26 17:56:32 +01:00
1c08881dfb Ensure pthread_join succeeded before returning true 2024-05-26 17:51:28 +01:00
7593eb4732 Add release build 2024-05-26 15:54:01 +01:00
ec2835e900 Read the image in a separate thread and ensure each strip is read in its own thread (#1)
Reviewed-on: #1
Co-authored-by: Abdelrahman <said.abdelrahman89@gmail.com>
Co-committed-by: Abdelrahman <said.abdelrahman89@gmail.com>
2024-05-26 14:44:12 +00:00
53c70156a6 Update .gitignore 2024-05-26 14:52:42 +01:00
ad4b7b40aa Update .gitignore 2024-05-26 14:52:00 +01:00
aa12b62e7e Fix compile script 2024-05-26 14:51:53 +01:00
5762ac33b0 Reorganise type definitions 2024-05-12 17:34:03 +01:00
efad22f21e Fix alpha multiplication for images with unassociated alpha 2024-05-06 00:54:25 +01:00
d278d7ee55 Multiply RGB when reading image with unassociated alpha 2024-05-06 00:37:23 +01:00
1c667f4128 Move all TIFF type definitions to the source file 2024-05-06 00:31:14 +01:00
c2391df946 Update main.c 2024-05-05 23:42:38 +01:00
b2f002b12f Increase MAIN_ARENA_CAPACITY 2024-05-05 23:41:14 +01:00
cd424bf074 Remove NULL header, ifd and image definitions 2024-05-05 23:33:53 +01:00
11798a0683 Main read functions return boolean 2024-05-05 23:26:02 +01:00
c2a55279fd Rename load_image_pixels to read_image_pixels 2024-05-05 22:57:47 +01:00
53f53874a6 Update 8-bit assertion 2024-05-05 22:51:17 +01:00
ba21bcb2f4 Switch internal reading functions to directly manipulate reader object 2024-05-05 22:46:13 +01:00
5bf78d3e01 Add comment 2024-05-05 22:13:37 +01:00
38862899d0 Read bits per sample 2024-05-05 22:05:39 +01:00
f0d4108a64 Add reading alpha 2024-05-05 21:21:48 +01:00
3e3c4d27fe Reorder type definitions 2024-05-05 19:47:20 +01:00
ed2737d330 Check field sizes against field type sizes instead of C primitives 2024-05-05 18:42:24 +01:00
9b7e07b9ad Update .gitignore 2024-05-05 16:28:30 +01:00
71b7c682db Rename read_fields to read_ifd_fields 2024-05-05 16:25:39 +01:00
a9143642a0 Declare TiffReader before attempting to open the file 2024-05-04 23:52:15 +01:00
d195086af8 Make functions internal 2024-05-04 23:48:01 +01:00
3f022acf9c Reorder functions 2024-05-04 23:46:24 +01:00
484d30d84c Add MAIN_ARENA_CAPACITY constant 2024-05-04 23:34:21 +01:00
de0235c0af Refactor reading the strip data fields into a function 2024-05-04 23:31:20 +01:00
b546ce9fa7 Calculate strip field size once 2024-05-04 22:47:23 +01:00
5 changed files with 651 additions and 400 deletions

3
.gitignore vendored
View File

@@ -2,7 +2,8 @@
refs refs
file_example* file_example*
big_endian* big_endian*
float32_example.tiff
*.py *.py
tiffread tiffread
test.ppm test*
compile_commands.json compile_commands.json

28
compile
View File

@@ -1,7 +1,28 @@
#!/bin/bash #!/bin/bash
BUILD_TYPE="debug"
while [[ $# > 0 ]];do
case $1 in
--release)
BUILD_TYPE="release"
shift
;;
*|-*|--*)
echo "Unknown option $1"
exit 1
;;
esac
done
CC=clang CC=clang
CFLAGS="-g -Wall -Werror -pedantic -fsanitize=address -fsanitize=undefined -DDEBUG"
if [[ $BUILD_TYPE == "release" ]]; then
CFLAGS="-O3 -Wall -Werror -pedantic"
else
CFLAGS="-g -Wall -Werror -pedantic -fsanitize=address -fsanitize=undefined -DDEBUG"
fi
INCLUDES="\ INCLUDES="\
-I$(find ./src -type d | xargs -I{} echo -n "-I{} ") \ -I$(find ./src -type d | xargs -I{} echo -n "-I{} ") \
$(find intern/wizapp/src -type d | xargs -I{} echo -n "-I{} ") \ $(find intern/wizapp/src -type d | xargs -I{} echo -n "-I{} ") \
@@ -9,11 +30,12 @@ INCLUDES="\
" "
LIBS="\ LIBS="\
-lm \ -lm \
-pthread \
$(pkg-config --libs sdl2) \ $(pkg-config --libs sdl2) \
" "
SRC="\ SRC="\
$(find ./src -name *.c | xargs -I{} echo -n "{} ") \ $(find ./src -name "*.c" | xargs -I{} echo -n "{} ") \
$(find intern/wizapp/src -type f -name *.c | xargs -I{} echo -n "{} ") \ $(find intern/wizapp/src -type f -name "*.c" | xargs -I{} echo -n "{} ") \
" "
OUT=tiffread OUT=tiffread

View File

@@ -4,17 +4,23 @@
#include "tiffread.h" #include "tiffread.h"
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <SDL2/SDL_events.h> #include <SDL2/SDL_events.h>
#include <SDL2/SDL_rect.h>
#include <SDL2/SDL_render.h> #include <SDL2/SDL_render.h>
#include <SDL2/SDL_surface.h> #include <SDL2/SDL_surface.h>
#include <SDL2/SDL_video.h> #include <SDL2/SDL_video.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <pthread.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#define MAIN_ARENA_CAPACITY 50 * 1024 * 1024
#define WINDOW_WIDTH 800 #define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600 #define WINDOW_HEIGHT 600
#define VIEW_AREA_WIDTH 700
#define VIEW_AREA_HEIGHT 500
#define AMASK 0xff000000 #define AMASK 0xff000000
#define BMASK 0x00ff0000 #define BMASK 0x00ff0000
@@ -29,28 +35,33 @@ struct point {
u32 y; u32 y;
}; };
Point point_from_index(u32 index, u32 w); internal void write_debug_ppm6(const Image *img);
void write_debug_ppm6(const Image *img); internal void *load_tiff_image(void *args);
SDL_Texture *image_to_texture(SDL_Renderer *renderer, const Image *img); internal SDL_Rect get_dest_rect(SDL_Surface *surface);
typedef struct img_thread_args ImgThreadArgs;
struct img_thread_args {
const char *filename;
Arena *arena;
};
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
int exit_code = EXIT_SUCCESS; int exit_code = EXIT_SUCCESS;
Arena *arena = NULL; Arena *arena = NULL;
if (!wapp_mem_arena_init(&arena, 10 * 1024 * 1024)) { if (!wapp_mem_arena_init(&arena, MAIN_ARENA_CAPACITY)) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
const char *file_to_open = argc > 1 ? argv[1] : "./resources/test.tif"; const char *file_to_open = argc > 1 ? argv[1] : "./resources/test.tif";
Image *img = read_baseline_tiff(file_to_open, arena); pthread_t img_thread;
if (!img) { ImgThreadArgs args = {.filename = file_to_open, .arena = arena};
if (pthread_create(&img_thread, NULL, load_tiff_image, &args) != 0) {
exit_code = EXIT_FAILURE; exit_code = EXIT_FAILURE;
goto MAIN_DESTROY_ARENA; goto MAIN_DESTROY_ARENA;
} }
write_debug_ppm6(img);
if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
exit_code = EXIT_FAILURE; exit_code = EXIT_FAILURE;
goto MAIN_DESTROY_ARENA; goto MAIN_DESTROY_ARENA;
@@ -71,18 +82,19 @@ int main(int argc, char *argv[]) {
goto MAIN_DESTROY_WINDOW; goto MAIN_DESTROY_WINDOW;
} }
SDL_Texture *texture = image_to_texture(renderer, img); SDL_Surface *surface = NULL;
if (!texture) { if (pthread_join(img_thread, (void **)&surface) != 0 || !surface) {
exit_code = EXIT_FAILURE; exit_code = EXIT_FAILURE;
goto MAIN_DESTROY_RENDERER; goto MAIN_DESTROY_RENDERER;
} }
SDL_Rect dest = { SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface);
.h = img->height, if (!texture) {
.w = img->width, exit_code = EXIT_FAILURE;
.x = (WINDOW_WIDTH - img->width) / 2, goto MAIN_DESTROY_SURFACE;
.y = (WINDOW_HEIGHT - img->height) / 2, }
};
SDL_Rect dest = get_dest_rect(surface);
bool running = true; bool running = true;
@@ -108,6 +120,9 @@ int main(int argc, char *argv[]) {
SDL_DestroyTexture(texture); SDL_DestroyTexture(texture);
MAIN_DESTROY_SURFACE:
SDL_FreeSurface(surface);
MAIN_DESTROY_RENDERER: MAIN_DESTROY_RENDERER:
SDL_DestroyRenderer(renderer); SDL_DestroyRenderer(renderer);
@@ -123,17 +138,7 @@ MAIN_DESTROY_ARENA:
return exit_code; return exit_code;
} }
Point point_from_index(u32 index, u32 w) { internal void write_debug_ppm6(const Image *img) {
Point out = {0};
out.x = index % w;
out.y = (index - (index % w)) / w;
return out;
}
void write_debug_ppm6(const Image *img) {
FILE *out = fopen("test.ppm", "wb"); FILE *out = fopen("test.ppm", "wb");
char magic[] = {'P', '6', '\n'}; char magic[] = {'P', '6', '\n'};
@@ -154,16 +159,19 @@ void write_debug_ppm6(const Image *img) {
fclose(out); fclose(out);
} }
SDL_Texture *image_to_texture(SDL_Renderer *renderer, const Image *img) { internal void *load_tiff_image(void *args) {
SDL_Texture *output = NULL; ImgThreadArgs *img_args = (ImgThreadArgs *)args;
if (!renderer || !img) { Image *img = read_baseline_tiff(img_args->filename, img_args->arena);
goto IMAGE_TO_TEXTURE_RETURN; if (!img) {
return NULL;
} }
write_debug_ppm6(img);
SDL_Surface *surface = SDL_CreateRGBSurface(0, img->width, img->height, 32, SDL_Surface *surface = SDL_CreateRGBSurface(0, img->width, img->height, 32,
RMASK, GMASK, BMASK, AMASK); RMASK, GMASK, BMASK, AMASK);
if (!surface) { if (!surface) {
goto IMAGE_TO_TEXTURE_RETURN; return NULL;
} }
SDL_LockSurface(surface); SDL_LockSurface(surface);
@@ -177,10 +185,18 @@ SDL_Texture *image_to_texture(SDL_Renderer *renderer, const Image *img) {
} }
SDL_UnlockSurface(surface); SDL_UnlockSurface(surface);
output = SDL_CreateTextureFromSurface(renderer, surface); return surface;
}
SDL_FreeSurface(surface);
internal SDL_Rect get_dest_rect(SDL_Surface *surface) {
IMAGE_TO_TEXTURE_RETURN: f64 ratio = (f64)(surface->h) / (f64)(surface->w);
return output; u64 width = surface->w <= VIEW_AREA_WIDTH ? surface->w : VIEW_AREA_WIDTH;
u64 height = width * ratio;
return (SDL_Rect){
.w = width,
.h = height,
.x = (WINDOW_WIDTH - width) / 2,
.y = (WINDOW_HEIGHT - height) / 2,
};
} }

View File

@@ -4,10 +4,13 @@
#include "image.h" #include "image.h"
#include "mem_arena.h" #include "mem_arena.h"
#include <assert.h> #include <assert.h>
#include <bits/pthreadtypes.h>
#include <math.h> #include <math.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <pthread.h>
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@@ -18,14 +21,7 @@
#define IS_TIFF_EXTENSION(EXT) \ #define IS_TIFF_EXTENSION(EXT) \
(strncmp(EXT, ".tif", 4) == 0 || strncmp(EXT, ".tiff", 5) == 0) (strncmp(EXT, ".tif", 4) == 0 || strncmp(EXT, ".tiff", 5) == 0)
#define NULL_TIFF_HEADER ((TiffHdr){0}) #define NULL_TIFF_ALPHA ((TiffAlpha){0})
#define IS_NULL_HEADER(HDR) (HDR.order == 0)
#define NULL_TIFF_IFD ((TiffIFD){0})
#define IS_NULL_IFD(IFD) (IFD.count == 0)
#define NULL_TIFF_IMAGE ((TiffImage){0})
#define IS_NULL_IMAGE(IMG) (IMG.type == TIFF_IMAGE_TYPE_INVALID)
#define INVALID_SAMPLE_COUNT 0 #define INVALID_SAMPLE_COUNT 0
#define INVALID_ROWS_PER_STRIP 0 #define INVALID_ROWS_PER_STRIP 0
@@ -33,10 +29,180 @@
#define INVALID_ALPHA_OFFSET -1 #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 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 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;
};
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; typedef struct tiff_reader TiffReader;
struct tiff_reader { struct tiff_reader {
const char *filename;
FILE *fp; FILE *fp;
TiffHdr header; TiffHdr header;
TiffIFD ifd; TiffIFD ifd;
@@ -44,43 +210,44 @@ struct tiff_reader {
Pixel *pixels; Pixel *pixels;
}; };
TiffHdr read_tiff_header(const TiffReader *reader); typedef struct strip_thread_args StripThreadArgs;
TiffIFD read_ifd(const TiffReader *reader, Arena *arena); struct strip_thread_args {
TiffImage read_fields(const TiffReader *reader); FILE *fp;
Pixel *load_image_pixels(TiffReader *reader, Arena *arena); const TiffStrip *strip;
bool read_strip_data(TiffReader *reader, Arena *arena); u64 pixel_count;
void read_strips(const TiffReader *reader, Pixel *buf); u64 samples_per_pixel;
bool read_field(const TiffField *field, TiffImage *img); u64 main_samples;
bool validate_image_type(const TiffImage *img); TiffAlpha alpha;
void read_from_file_with_offset(FILE *fp, void *dst, u64 count, u64 offset); Pixel *pixel_write_start;
};
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 bool read_strips(TiffReader *reader);
internal void *read_strip(void *arg);
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);
internal f32 u8_normalise(u8 value);
internal u8 u8_denormalise(f32 value);
Image *read_baseline_tiff(const char *file, Arena *arena) { Image *read_baseline_tiff(const char *file, Arena *arena) {
Image *img_out = NULL; Image *img_out = NULL;
if (!file || !arena) { if (!file || !arena || !valid_tiff_file(file)) {
goto READ_BASELINE_RETURN_IMG; goto READ_BASELINE_RETURN_IMG;
} }
u64 name_length = strlen(file); TiffReader reader = {.filename = file, .fp = fopen(file, "rb")};
if (name_length < TIFF_FILENAME_MIN_LENGTH) { if (!reader.fp) {
goto READ_BASELINE_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 READ_BASELINE_RETURN_IMG;
}
FILE *fp = fopen(file, "rb");
if (!fp) {
goto READ_BASELINE_RETURN_IMG; goto READ_BASELINE_RETURN_IMG;
} }
@@ -89,28 +256,29 @@ Image *read_baseline_tiff(const char *file, Arena *arena) {
goto READ_BASELINE_FILE_CLEANUP; goto READ_BASELINE_FILE_CLEANUP;
} }
TiffReader reader = {.fp = fp}; if (!read_tiff_header(&reader)) {
reader.header = read_tiff_header(&reader);
if (IS_NULL_HEADER(reader.header)) {
goto READ_BASELINE_DESTROY_ARENA; goto READ_BASELINE_DESTROY_ARENA;
} }
reader.ifd = read_ifd(&reader, temp_arena); if (!read_ifd(&reader, temp_arena)) {
if (IS_NULL_IFD(reader.ifd)) {
goto READ_BASELINE_DESTROY_ARENA; goto READ_BASELINE_DESTROY_ARENA;
} }
reader.img = read_fields(&reader); if (!read_ifd_fields(&reader)) {
if (IS_NULL_IMAGE(reader.img)) {
goto READ_BASELINE_DESTROY_ARENA; goto READ_BASELINE_DESTROY_ARENA;
} }
assert((reader.img.type == TIFF_IMAGE_TYPE_RGB) && assert((reader.img.type == TIFF_IMAGE_TYPE_RGB) &&
"Currently, only RGB images are supported"); "Currently, only RGB images are supported");
reader.pixels = load_image_pixels(&reader, temp_arena); bool image_is_8_bit =
if (!reader.pixels) { (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; goto READ_BASELINE_DESTROY_ARENA;
} }
@@ -121,77 +289,75 @@ READ_BASELINE_DESTROY_ARENA:
wapp_mem_arena_destroy(&temp_arena); wapp_mem_arena_destroy(&temp_arena);
READ_BASELINE_FILE_CLEANUP: READ_BASELINE_FILE_CLEANUP:
fclose(fp); fclose(reader.fp);
READ_BASELINE_RETURN_IMG: READ_BASELINE_RETURN_IMG:
return img_out; return img_out;
} }
TiffHdr read_tiff_header(const TiffReader *reader) { internal bool read_tiff_header(TiffReader *reader) {
TiffHdr header = NULL_TIFF_HEADER; fread_with_offset(reader->fp, &(reader->header), sizeof(TiffHdr), 0);
read_from_file_with_offset(reader->fp, &header, sizeof(TiffHdr), 0);
switch (header.order) { switch (reader->header.order) {
case TIFF_ORDER_LITTLE_ENDIAN: case TIFF_ORDER_LITTLE_ENDIAN:
if (IS_BIG_ENDIAN) { if (IS_BIG_ENDIAN) {
header.magic = htons(header.magic); reader->header.magic = htons(reader->header.magic);
header.first_ifd_offset = htonl(header.first_ifd_offset); reader->header.first_ifd_offset = htonl(reader->header.first_ifd_offset);
} }
break; break;
case TIFF_ORDER_BIG_ENDIAN: case TIFF_ORDER_BIG_ENDIAN:
if (IS_LITTLE_ENDIAN) { if (IS_LITTLE_ENDIAN) {
header.magic = ntohs(header.magic); reader->header.magic = ntohs(reader->header.magic);
header.first_ifd_offset = ntohl(header.first_ifd_offset); reader->header.first_ifd_offset = ntohl(reader->header.first_ifd_offset);
} }
break; break;
default: default:
return NULL_TIFF_HEADER; return false;
} }
if (header.magic != TIFF_MAGIC) { if (reader->header.magic != TIFF_MAGIC) {
return NULL_TIFF_HEADER; return false;
} }
return header; return true;
} }
TiffIFD read_ifd(const TiffReader *reader, Arena *arena) { internal bool read_ifd(TiffReader *reader, Arena *arena) {
TiffIFD ifd = NULL_TIFF_IFD; fread_with_offset(reader->fp, &(reader->ifd.count), sizeof(reader->ifd.count),
read_from_file_with_offset(reader->fp, &(ifd.count), sizeof(ifd.count),
reader->header.first_ifd_offset); reader->header.first_ifd_offset);
switch (reader->header.order) { switch (reader->header.order) {
case TIFF_ORDER_LITTLE_ENDIAN: case TIFF_ORDER_LITTLE_ENDIAN:
if (IS_BIG_ENDIAN) { if (IS_BIG_ENDIAN) {
ifd.count = htons(ifd.count); reader->ifd.count = htons(reader->ifd.count);
} }
break; break;
case TIFF_ORDER_BIG_ENDIAN: case TIFF_ORDER_BIG_ENDIAN:
if (IS_LITTLE_ENDIAN) { if (IS_LITTLE_ENDIAN) {
ifd.count = ntohs(ifd.count); reader->ifd.count = ntohs(reader->ifd.count);
} }
break; break;
} }
u64 field_byte_count = sizeof(TiffField) * ifd.count; u64 field_byte_count = sizeof(TiffField) * reader->ifd.count;
ifd.fields = (TiffField *)wapp_mem_arena_alloc(arena, field_byte_count); reader->ifd.fields =
if (!(ifd.fields)) { (TiffField *)wapp_mem_arena_alloc(arena, field_byte_count);
return NULL_TIFF_IFD; if (!(reader->ifd.fields)) {
return false;
} }
fread(ifd.fields, field_byte_count, 1, reader->fp); fread(reader->ifd.fields, field_byte_count, 1, reader->fp);
fread(&(ifd.next_ifd), sizeof(ifd.next_ifd), 1, reader->fp); fread(&(reader->ifd.next_ifd), sizeof(reader->ifd.next_ifd), 1, reader->fp);
return ifd; return true;
} }
TiffImage read_fields(const TiffReader *reader) { internal bool read_ifd_fields(TiffReader *reader) {
TiffImage img_out = NULL_TIFF_IMAGE; reader->img.type = TIFF_IMAGE_TYPE_BILEVEL;
img_out.type = TIFF_IMAGE_TYPE_BILEVEL;
for (u64 i = 0; i < reader->ifd.count; ++i) { for (u64 i = 0; i < reader->ifd.count; ++i) {
TiffField *field = &(reader->ifd.fields[i]); TiffField *field = &(reader->ifd.fields[i]);
@@ -227,41 +393,148 @@ TiffImage read_fields(const TiffReader *reader) {
break; break;
} }
if (!read_field(field, &img_out)) { if (!read_field(field, &(reader->img))) {
img_out = NULL_TIFF_IMAGE; return false;
goto READ_FIELDS_RETURN_IMAGE;
} }
} }
if (!validate_image_type(&img_out)) { if (!validate_image_type(&(reader->img))) {
img_out = NULL_TIFF_IMAGE; return false;
goto READ_FIELDS_RETURN_IMAGE;
} }
read_alpha(reader);
read_bits_per_sample(reader);
#ifdef DEBUG #ifdef DEBUG
// clang-format off // clang-format off
printf("SIZE (width, height) : %u, %u\n", img_out.image_width, img_out.image_length); 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", img_out.bits_per_sample, img_out.sample_count, img_out.bits_per_sample_offset); 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", img_out.extra_samples, img_out.extra_samples_count, img_out.extra_samples_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("SAMPLES PER PIXEL : %u\n", img_out.sample_count); printf("ALPHA (type, offset) : %u, %u\n", reader->img.alpha.type, reader->img.alpha.sample_offset);
printf("PHOTOMETRIC INTERPRETATION : %u\n", img_out.photometric_interpretation); printf("SAMPLES PER PIXEL : %u\n", reader->img.sample_count);
printf("ROWS PER STRIP (rows, strip count) : %u, %u\n", img_out.rows_per_strip, img_out.strip_count); printf("PHOTOMETRIC INTERPRETATION : %u\n", reader->img.photometric_interpretation);
printf("STRIP OFFSETS (offsets, is offset) : %u, %u\n", img_out.strip_offsets.long_val, img_out.strip_offsets_offset); printf("ROWS PER STRIP (rows, strip count) : %u, %u\n", reader->img.rows_per_strip, reader->img.strip_count);
printf("STRIP BYTES (byte count, is offset) : %u, %u\n", img_out.strip_byte_counts.long_val, img_out.strip_byte_counts_offset); 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 // clang-format on
#endif #endif
READ_FIELDS_RETURN_IMAGE: return true;
return img_out;
} }
Pixel *load_image_pixels(TiffReader *reader, Arena *arena) { internal void read_alpha(TiffReader *reader) {
Pixel *buf = NULL; 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 = u64 img_byte_count =
sizeof(Pixel) * reader->img.image_width * reader->img.image_length; sizeof(Pixel) * reader->img.image_width * reader->img.image_length;
if (!read_strip_data(reader, arena)) { if (!read_strip_data(reader, arena)) {
goto LOAD_IMAGE_PIXELS_RETURN; return false;
} }
#ifdef DEBUG #ifdef DEBUG
@@ -271,44 +544,15 @@ Pixel *load_image_pixels(TiffReader *reader, Arena *arena) {
} }
#endif #endif
buf = wapp_mem_arena_alloc(arena, img_byte_count); reader->pixels = wapp_mem_arena_alloc(arena, img_byte_count);
if (!buf) { if (!reader->pixels) {
goto LOAD_IMAGE_PIXELS_RETURN; return false;
} }
read_strips(reader, buf); return read_strips(reader);
LOAD_IMAGE_PIXELS_RETURN:
return buf;
} }
#if 0 internal bool read_strip_data(TiffReader *reader, Arena *arena) {
void read_strip_field(FILE *fp, const TiffHdr *header, const TiffImage *img, TiffStrip *strip) {
if (offsets_total_bytes > sizeof(u32)) {
u32 offset = img->strip_offsets.long_val + offset_size * i;
read_from_file_with_offset(fp, &(strip->offset), offset_size, offset);
switch (header->order) {
case TIFF_ORDER_BIG_ENDIAN:
if (IS_LITTLE_ENDIAN) {
strip->offset = offset_size > sizeof(u16) ? ntohl(strip->offset)
: ntohs(strip->offset);
}
break;
case TIFF_ORDER_LITTLE_ENDIAN:
if (IS_BIG_ENDIAN) {
strip->offset = offset_size > sizeof(u16) ? htonl(strip->offset)
: htons(strip->offset);
}
break;
}
} else {
memcpy(&(strip->offset), &(img->strip_offsets.long_val), offset_size);
}
}
#endif
bool read_strip_data(TiffReader *reader, Arena *arena) {
reader->img.strips = reader->img.strips =
wapp_mem_arena_alloc(arena, sizeof(TiffStrip) * reader->img.strip_count); wapp_mem_arena_alloc(arena, sizeof(TiffStrip) * reader->img.strip_count);
if (!(reader->img.strips)) { if (!(reader->img.strips)) {
@@ -321,93 +565,169 @@ bool read_strip_data(TiffReader *reader, Arena *arena) {
reader->img.strip_count * reader->img.strip_byte_count_type_byte_count; reader->img.strip_count * reader->img.strip_byte_count_type_byte_count;
if ((!(reader->img.strip_offsets_offset) && if ((!(reader->img.strip_offsets_offset) &&
offsets_total_bytes > sizeof(u32)) || offsets_total_bytes > TIFF_LONG_BYTE_COUNT) ||
(!(reader->img.strip_byte_counts_offset) && (!(reader->img.strip_byte_counts_offset) &&
byte_count_total_bytes > sizeof(u32))) { byte_count_total_bytes > TIFF_LONG_BYTE_COUNT)) {
return false; return false;
} }
for (u32 i = 0; i < reader->img.strip_count; ++i) {
TiffStrip *strip = &(reader->img.strips[i]);
u16 offset_size = reader->img.strip_offsets_type_byte_count; u16 offset_size = reader->img.strip_offsets_type_byte_count;
u16 counts_size = reader->img.strip_byte_count_type_byte_count; u16 counts_size = reader->img.strip_byte_count_type_byte_count;
if (offsets_total_bytes > sizeof(u32)) { for (u32 i = 0; i < reader->img.strip_count; ++i) {
u32 offset = reader->img.strip_offsets.long_val + offset_size * i; TiffStrip *strip = &(reader->img.strips[i]);
read_from_file_with_offset(reader->fp, &(strip->offset), offset_size, StripDataField offsets = {
offset); .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,
};
switch (reader->header.order) { read_strip_data_field(reader, &offsets);
case TIFF_ORDER_BIG_ENDIAN: read_strip_data_field(reader, &counts);
if (IS_LITTLE_ENDIAN) {
strip->offset = offset_size > sizeof(u16) ? ntohl(strip->offset)
: ntohs(strip->offset);
}
break;
case TIFF_ORDER_LITTLE_ENDIAN:
if (IS_BIG_ENDIAN) {
strip->offset = offset_size > sizeof(u16) ? htonl(strip->offset)
: htons(strip->offset);
}
break;
}
} else {
memcpy(&(strip->offset), &(reader->img.strip_offsets.long_val),
offset_size);
}
if (byte_count_total_bytes > sizeof(u32)) {
u32 offset = reader->img.strip_byte_counts.long_val + counts_size * i;
read_from_file_with_offset(reader->fp, &(strip->byte_count), counts_size,
offset);
switch (reader->header.order) {
case TIFF_ORDER_BIG_ENDIAN:
if (IS_LITTLE_ENDIAN) {
strip->byte_count = counts_size > sizeof(u16)
? ntohl(strip->byte_count)
: ntohs(strip->byte_count);
}
break;
case TIFF_ORDER_LITTLE_ENDIAN:
if (IS_BIG_ENDIAN) {
strip->byte_count = counts_size > sizeof(u16)
? htonl(strip->byte_count)
: htons(strip->byte_count);
}
break;
}
} else {
memcpy(&(strip->byte_count), &(reader->img.strip_byte_counts.long_val),
counts_size);
}
} }
return true; return true;
} }
void read_strips(const TiffReader *reader, Pixel *buf) { internal bool read_strips(TiffReader *reader) {
u64 position = 0; u64 position = 0;
StripThreadArgs args[reader->img.strip_count];
pthread_t threads[reader->img.strip_count];
for (u64 i = 0; i < reader->img.strip_count; ++i) { for (u64 i = 0; i < reader->img.strip_count; ++i) {
const TiffStrip *strip = &(reader->img.strips[i]); const TiffStrip *strip = &(reader->img.strips[i]);
u64 pixel_count = strip->byte_count / reader->img.sample_count;
args[i] = (StripThreadArgs){
.fp = fopen(reader->filename, "rb"),
.strip = strip,
.pixel_count = pixel_count,
.samples_per_pixel = reader->img.sample_count,
.main_samples =
reader->img.sample_count - reader->img.extra_samples_count,
.alpha = reader->img.alpha,
.pixel_write_start = &(reader->pixels[position]),
};
for (u64 j = 0; j < strip->byte_count / reader->img.sample_count; ++j) { if (!args[i].fp) {
Pixel *p = &(buf[position]); for (u64 j = 0; j < i; ++j) {
fclose(args[j].fp);
}
read_from_file_with_offset(reader->fp, p, reader->img.sample_count, return false;
strip->offset + j * reader->img.sample_count); }
if (reader->img.sample_count == 3) { position += pixel_count;
}
for (u64 i = 0; i < reader->img.strip_count; ++i) {
if (pthread_create(&(threads[i]), NULL, read_strip, &(args[i])) != 0) {
for (u64 j = 0; j < i; ++j) {
pthread_cancel(threads[i]);
fclose(args[j].fp);
}
// Close the remaining files
for (u64 j = i; j < reader->img.strip_count; ++j) {
fclose(args[j].fp);
}
return false;
}
}
bool image_loaded = true;
for (u64 i = 0; i < reader->img.strip_count; ++i) {
if (pthread_join(threads[i], NULL) != 0) {
image_loaded = false;
}
fclose(args[i].fp);
}
return image_loaded;
}
internal void *read_strip(void *arg) {
StripThreadArgs *args = (StripThreadArgs *)arg;
Pixel *p;
u64 write_offset = 0;
u64 read_offset;
u64 alpha_offset;
for (u64 j = 0; j < args->pixel_count; ++j) {
p = args->pixel_write_start + write_offset;
read_offset = args->strip->offset + j * args->samples_per_pixel;
alpha_offset = read_offset + args->main_samples + args->alpha.sample_offset;
fread_with_offset(args->fp, p, args->main_samples, read_offset);
if (args->alpha.type == ALPHA_TYPE_UNDEFINED) {
p->a = 255; p->a = 255;
} else {
fread_with_offset(args->fp, &(p->a), 1, alpha_offset);
if (args->alpha.type == ALPHA_TYPE_UNASSOCIATED) {
f32 a_norm = u8_normalise(p->a);
p->r = u8_denormalise(u8_normalise(p->r) * a_norm);
p->g = u8_denormalise(u8_normalise(p->g) * a_norm);
p->b = u8_denormalise(u8_normalise(p->b) * a_norm);
}
} }
++position; ++write_offset;
} }
return NULL;
}
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;
} }
} }
bool read_field(const TiffField *field, TiffImage *img) { internal bool read_field(const TiffField *field, TiffImage *img) {
switch (field->tag) { switch (field->tag) {
case TIFF_PUBLIC_TAG_IMAGE_WIDTH: case TIFF_PUBLIC_TAG_IMAGE_WIDTH:
if (field->count > 1) { if (field->count > 1) {
@@ -460,9 +780,9 @@ bool read_field(const TiffField *field, TiffImage *img) {
case TIFF_PUBLIC_TAG_STRIP_OFFSETS: case TIFF_PUBLIC_TAG_STRIP_OFFSETS:
img->strip_offsets_type_byte_count = field_types[field->type].byte_count; img->strip_offsets_type_byte_count = field_types[field->type].byte_count;
if (img->strip_offsets_type_byte_count == sizeof(u16)) { if (img->strip_offsets_type_byte_count == TIFF_SHORT_BYTE_COUNT) {
img->strip_offsets.short_val = field->value_offset; img->strip_offsets.short_val = field->value_offset;
} else if (img->strip_offsets_type_byte_count == sizeof(u32)) { } else if (img->strip_offsets_type_byte_count == TIFF_LONG_BYTE_COUNT) {
img->strip_offsets.long_val = field->value_offset; img->strip_offsets.long_val = field->value_offset;
} }
@@ -505,9 +825,9 @@ bool read_field(const TiffField *field, TiffImage *img) {
case TIFF_PUBLIC_TAG_STRIP_BYTE_COUNTS: case TIFF_PUBLIC_TAG_STRIP_BYTE_COUNTS:
img->strip_byte_count_type_byte_count = field_types[field->type].byte_count; img->strip_byte_count_type_byte_count = field_types[field->type].byte_count;
if (img->strip_byte_count_type_byte_count == sizeof(u16)) { if (img->strip_byte_count_type_byte_count == TIFF_SHORT_BYTE_COUNT) {
img->strip_byte_counts.short_val = field->value_offset; img->strip_byte_counts.short_val = field->value_offset;
} else if (img->strip_byte_count_type_byte_count == sizeof(u32)) { } else if (img->strip_byte_count_type_byte_count == TIFF_LONG_BYTE_COUNT) {
img->strip_byte_counts.long_val = field->value_offset; img->strip_byte_counts.long_val = field->value_offset;
} }
@@ -544,7 +864,7 @@ bool read_field(const TiffField *field, TiffImage *img) {
return true; return true;
} }
bool validate_image_type(const TiffImage *img) { internal bool validate_image_type(const TiffImage *img) {
if (img->type < TIFF_IMAGE_TYPE_BILEVEL || img->type > TIFF_IMAGE_TYPE_RGB) { if (img->type < TIFF_IMAGE_TYPE_BILEVEL || img->type > TIFF_IMAGE_TYPE_RGB) {
return false; return false;
} }
@@ -579,7 +899,7 @@ bool validate_image_type(const TiffImage *img) {
return true; return true;
} }
void read_from_file_with_offset(FILE *fp, void *dst, u64 count, u64 offset) { internal void fread_with_offset(FILE *fp, void *dst, u64 count, u64 offset) {
if (!fp || !dst) { if (!fp || !dst) {
return; return;
} }
@@ -588,3 +908,29 @@ void read_from_file_with_offset(FILE *fp, void *dst, u64 count, u64 offset) {
fread(dst, count, 1, fp); 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;
}
internal f32 u8_normalise(u8 value) { return (f32)value / (f32)UINT8_MAX; }
internal u8 u8_denormalise(f32 value) { return (u8)(value * UINT8_MAX); }

View File

@@ -6,141 +6,7 @@
extern "C" { extern "C" {
#endif // __cplusplus #endif // __cplusplus
#include "aliases.h"
#include "image.h" #include "image.h"
#include <stdbool.h>
#define TYPE_MAX_COUNT (UINT8_MAX * sizeof(const char *))
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;
};
// 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 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;
TiffStrip *strips;
};
Image *read_baseline_tiff(const char *file, Arena *arena); Image *read_baseline_tiff(const char *file, Arena *arena);