Compare commits
53 Commits
0bd448a8a9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 35a33e2fa6 | |||
| 1c08881dfb | |||
| 7593eb4732 | |||
| ec2835e900 | |||
| 53c70156a6 | |||
| ad4b7b40aa | |||
| aa12b62e7e | |||
| 5762ac33b0 | |||
| efad22f21e | |||
| d278d7ee55 | |||
| 1c667f4128 | |||
| c2391df946 | |||
| b2f002b12f | |||
| cd424bf074 | |||
| 11798a0683 | |||
| c2a55279fd | |||
| 53f53874a6 | |||
| ba21bcb2f4 | |||
| 5bf78d3e01 | |||
| 38862899d0 | |||
| f0d4108a64 | |||
| 3e3c4d27fe | |||
| ed2737d330 | |||
| 9b7e07b9ad | |||
| 71b7c682db | |||
| a9143642a0 | |||
| d195086af8 | |||
| 3f022acf9c | |||
| 484d30d84c | |||
| de0235c0af | |||
| b546ce9fa7 | |||
| c98802542d | |||
| 9fdee24672 | |||
| cd698a3921 | |||
| 9312c0fb6b | |||
| 51f79275de | |||
| a508048211 | |||
| 8f9edb441a | |||
| 4b52630c1e | |||
| c46535c20b | |||
| 55720ac331 | |||
| 8f20c7b30a | |||
| 69c13b83ea | |||
| d508af1809 | |||
| e371ca7160 | |||
| eaa6e52fbe | |||
| 3c6e5defb9 | |||
| a00e737652 | |||
| 45276ffcc1 | |||
| 2c1b844c64 | |||
| ce7f30f7e3 | |||
| 38988120a3 | |||
| 8040dd7381 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,6 +1,9 @@
|
||||
.cache
|
||||
refs
|
||||
file_example*
|
||||
big_endian*
|
||||
float32_example.tiff
|
||||
*.py
|
||||
tiffread
|
||||
test*
|
||||
compile_commands.json
|
||||
|
||||
41
compile
41
compile
@@ -1,22 +1,41 @@
|
||||
#!/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
|
||||
CFLAGS="-g -Wall -Werror -pedantic -fsanitize=address -fsanitize=undefined"
|
||||
|
||||
if [[ $BUILD_TYPE == "release" ]]; then
|
||||
CFLAGS="-O3 -Wall -Werror -pedantic"
|
||||
else
|
||||
CFLAGS="-g -Wall -Werror -pedantic -fsanitize=address -fsanitize=undefined -DDEBUG"
|
||||
fi
|
||||
|
||||
INCLUDES="\
|
||||
-Isrc \
|
||||
-Iintern/wizapp/aliases \
|
||||
-Iintern/wizapp/cpath/include \
|
||||
-Iintern/wizapp/dstr/include \
|
||||
$(find intern/wizapp/mem/include -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{} ") \
|
||||
$(pkg-config --cflags sdl2) \
|
||||
"
|
||||
LIBS="\
|
||||
-lm \
|
||||
-lm \
|
||||
-pthread \
|
||||
$(pkg-config --libs sdl2) \
|
||||
"
|
||||
SRC="\
|
||||
./src/*.c \
|
||||
intern/wizapp/cpath/src/*.c \
|
||||
intern/wizapp/dstr/src/*.c \
|
||||
intern/wizapp/mem/src/*/*.c \
|
||||
$(find ./src -name "*.c" | xargs -I{} echo -n "{} ") \
|
||||
$(find intern/wizapp/src -type f -name "*.c" | xargs -I{} echo -n "{} ") \
|
||||
"
|
||||
OUT=tiffread
|
||||
|
||||
|
||||
Submodule intern/wizapp updated: be64571b0e...b59aedad89
45
src/image.c
45
src/image.c
@@ -1,45 +0,0 @@
|
||||
#include "image.h"
|
||||
#include "mem_allocator.h"
|
||||
#include "mem_libc.h"
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
Image *create_image(u64 width, u64 height, Pixel *data,
|
||||
const Allocator *allocator) {
|
||||
Allocator alloc;
|
||||
if (!allocator) {
|
||||
alloc = wapp_mem_libc_allocator();
|
||||
} else {
|
||||
alloc = *allocator;
|
||||
}
|
||||
|
||||
u64 buf_length = width * height;
|
||||
u64 total_size = sizeof(Image) + sizeof(Pixel) * buf_length;
|
||||
|
||||
Image *img = wapp_mem_allocator_alloc(&alloc, total_size);
|
||||
if (!img) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
img->width = width;
|
||||
img->height = height;
|
||||
img->buf_length = buf_length;
|
||||
memcpy(img->data, data, buf_length);
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
void destroy_image(Image **img, const Allocator *allocator) {
|
||||
if (!img || !(*img)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Allocator alloc;
|
||||
if (!allocator) {
|
||||
alloc = wapp_mem_libc_allocator();
|
||||
} else {
|
||||
alloc = *allocator;
|
||||
}
|
||||
|
||||
wapp_mem_allocator_free(&alloc, (void **)img);
|
||||
}
|
||||
26
src/image/image.c
Normal file
26
src/image/image.c
Normal file
@@ -0,0 +1,26 @@
|
||||
#include "image.h"
|
||||
#include "mem_arena.h"
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
Image *create_image(u64 width, u64 height, Pixel *data, Arena *arena) {
|
||||
if (!arena) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
u64 pixel_count = width * height;
|
||||
u64 buf_length = sizeof(Pixel) * pixel_count;
|
||||
u64 alloc_size = sizeof(Image) + buf_length;
|
||||
|
||||
Image *img = wapp_mem_arena_alloc(arena, alloc_size);
|
||||
if (!img) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
img->width = width;
|
||||
img->height = height;
|
||||
img->pixel_count = pixel_count;
|
||||
memcpy(img->data, data, buf_length);
|
||||
|
||||
return img;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifndef IMAGE_H
|
||||
#define IMAGE_H
|
||||
|
||||
#include "mem_allocator.h"
|
||||
#include "mem_arena.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif // __cplusplus
|
||||
@@ -20,13 +20,11 @@ typedef struct image Image;
|
||||
struct image {
|
||||
u64 width;
|
||||
u64 height;
|
||||
u64 buf_length;
|
||||
u64 pixel_count;
|
||||
Pixel data[];
|
||||
};
|
||||
|
||||
Image *create_image(u64 width, u64 height, Pixel *data,
|
||||
const Allocator *allocator);
|
||||
void destroy_image(Image **img, const Allocator *allocator);
|
||||
Image *create_image(u64 width, u64 height, Pixel *data, Arena *arena);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
212
src/main.c
212
src/main.c
@@ -1,54 +1,202 @@
|
||||
#include "aliases.h"
|
||||
#include "mem_allocator.h"
|
||||
#include "mem_ctx.h"
|
||||
#include "image.h"
|
||||
#include "mem_arena.h"
|
||||
#include "tiffread.h"
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_events.h>
|
||||
#include <SDL2/SDL_rect.h>
|
||||
#include <SDL2/SDL_render.h>
|
||||
#include <SDL2/SDL_surface.h>
|
||||
#include <SDL2/SDL_video.h>
|
||||
#include <netinet/in.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MAIN_ARENA_CAPACITY 50 * 1024 * 1024
|
||||
|
||||
#define WINDOW_WIDTH 800
|
||||
#define WINDOW_HEIGHT 600
|
||||
#define VIEW_AREA_WIDTH 700
|
||||
#define VIEW_AREA_HEIGHT 500
|
||||
|
||||
#define AMASK 0xff000000
|
||||
#define BMASK 0x00ff0000
|
||||
#define GMASK 0x0000ff00
|
||||
#define RMASK 0x000000ff
|
||||
|
||||
#define WHITE 0xff
|
||||
|
||||
typedef struct point Point;
|
||||
struct point {
|
||||
u32 x;
|
||||
u32 y;
|
||||
};
|
||||
|
||||
internal void write_debug_ppm6(const Image *img);
|
||||
internal void *load_tiff_image(void *args);
|
||||
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[]) {
|
||||
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);
|
||||
int exit_code = EXIT_SUCCESS;
|
||||
|
||||
Arena *arena = NULL;
|
||||
if (!wapp_mem_arena_init(&arena, MAIN_ARENA_CAPACITY)) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
const char *file_to_open = argc > 1 ? argv[1] : "./resources/test.tif";
|
||||
|
||||
read_baseline_tiff(file_to_open, &ctx_main_allocator);
|
||||
pthread_t img_thread;
|
||||
ImgThreadArgs args = {.filename = file_to_open, .arena = arena};
|
||||
if (pthread_create(&img_thread, NULL, load_tiff_image, &args) != 0) {
|
||||
exit_code = EXIT_FAILURE;
|
||||
goto MAIN_DESTROY_ARENA;
|
||||
}
|
||||
|
||||
// FILE *fp = fopen(file_to_open, "rb");
|
||||
if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
|
||||
exit_code = EXIT_FAILURE;
|
||||
goto MAIN_DESTROY_ARENA;
|
||||
};
|
||||
|
||||
// TiffHdr header;
|
||||
// fread(&header, sizeof(header), 1, fp);
|
||||
SDL_Window *window =
|
||||
SDL_CreateWindow("Window", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||
WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_SHOWN);
|
||||
if (!window) {
|
||||
exit_code = EXIT_FAILURE;
|
||||
goto MAIN_QUIT_SDL;
|
||||
}
|
||||
|
||||
// TiffIFD ifd = {0};
|
||||
SDL_Renderer *renderer = SDL_CreateRenderer(
|
||||
window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
|
||||
if (!renderer) {
|
||||
exit_code = EXIT_FAILURE;
|
||||
goto MAIN_DESTROY_WINDOW;
|
||||
}
|
||||
|
||||
// printf("ORDER: %04x\n", header.order);
|
||||
// printf("MAGIC: %04x\n", header.magic);
|
||||
SDL_Surface *surface = NULL;
|
||||
if (pthread_join(img_thread, (void **)&surface) != 0 || !surface) {
|
||||
exit_code = EXIT_FAILURE;
|
||||
goto MAIN_DESTROY_RENDERER;
|
||||
}
|
||||
|
||||
// fseek(fp, header.first_ifd_offset, SEEK_SET);
|
||||
SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface);
|
||||
if (!texture) {
|
||||
exit_code = EXIT_FAILURE;
|
||||
goto MAIN_DESTROY_SURFACE;
|
||||
}
|
||||
|
||||
// fread(&(ifd.count), sizeof(ifd.count), 1, fp);
|
||||
SDL_Rect dest = get_dest_rect(surface);
|
||||
|
||||
// printf("COUNT: %u\n", ifd.count);
|
||||
bool running = true;
|
||||
|
||||
// TiffField fields[ifd.count];
|
||||
// memset(fields, 0, sizeof(TiffField) * ifd.count);
|
||||
SDL_Event event = {0};
|
||||
|
||||
// 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);
|
||||
// }
|
||||
while (running) {
|
||||
while (SDL_PollEvent(&event)) {
|
||||
switch (event.type) {
|
||||
case SDL_QUIT:
|
||||
running = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
wapp_mem_ctx_free();
|
||||
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
|
||||
|
||||
return 0;
|
||||
SDL_RenderClear(renderer);
|
||||
|
||||
SDL_RenderCopy(renderer, texture, NULL, &dest);
|
||||
|
||||
SDL_RenderPresent(renderer);
|
||||
}
|
||||
|
||||
SDL_DestroyTexture(texture);
|
||||
|
||||
MAIN_DESTROY_SURFACE:
|
||||
SDL_FreeSurface(surface);
|
||||
|
||||
MAIN_DESTROY_RENDERER:
|
||||
SDL_DestroyRenderer(renderer);
|
||||
|
||||
MAIN_DESTROY_WINDOW:
|
||||
SDL_DestroyWindow(window);
|
||||
|
||||
MAIN_QUIT_SDL:
|
||||
SDL_Quit();
|
||||
|
||||
MAIN_DESTROY_ARENA:
|
||||
wapp_mem_arena_destroy(&arena);
|
||||
|
||||
return exit_code;
|
||||
}
|
||||
|
||||
internal void write_debug_ppm6(const Image *img) {
|
||||
FILE *out = fopen("test.ppm", "wb");
|
||||
|
||||
char magic[] = {'P', '6', '\n'};
|
||||
fwrite(magic, sizeof(magic), 1, out);
|
||||
|
||||
char size[128];
|
||||
sprintf(size, "%lu %lu\n", img->width, img->height);
|
||||
u64 size_length = strlen(size);
|
||||
fwrite(size, size_length, 1, out);
|
||||
|
||||
char max[] = {'2', '5', '5', '\n'};
|
||||
fwrite(max, sizeof(max), 1, out);
|
||||
|
||||
for (u64 i = 0; i < img->pixel_count; ++i) {
|
||||
fwrite(&(img->data[i]), sizeof(u8), 3, out);
|
||||
}
|
||||
|
||||
fclose(out);
|
||||
}
|
||||
|
||||
internal void *load_tiff_image(void *args) {
|
||||
ImgThreadArgs *img_args = (ImgThreadArgs *)args;
|
||||
Image *img = read_baseline_tiff(img_args->filename, img_args->arena);
|
||||
if (!img) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
write_debug_ppm6(img);
|
||||
|
||||
SDL_Surface *surface = SDL_CreateRGBSurface(0, img->width, img->height, 32,
|
||||
RMASK, GMASK, BMASK, AMASK);
|
||||
if (!surface) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_LockSurface(surface);
|
||||
u64 position = 0;
|
||||
for (u64 i = 0; i < img->pixel_count; i += surface->w) {
|
||||
void *start = (void *)((uptr)(surface->pixels) + position);
|
||||
|
||||
memcpy(start, &(img->data[i]), surface->w * sizeof(Pixel));
|
||||
|
||||
position += surface->pitch;
|
||||
}
|
||||
SDL_UnlockSurface(surface);
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
internal SDL_Rect get_dest_rect(SDL_Surface *surface) {
|
||||
f64 ratio = (f64)(surface->h) / (f64)(surface->w);
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
936
src/tiff/tiffread.c
Normal file
936
src/tiff/tiffread.c
Normal file
@@ -0,0 +1,936 @@
|
||||
#include "tiffread.h"
|
||||
#include "aliases.h"
|
||||
#include "endianness.h"
|
||||
#include "image.h"
|
||||
#include "mem_arena.h"
|
||||
#include <assert.h>
|
||||
#include <bits/pthreadtypes.h>
|
||||
#include <math.h>
|
||||
#include <netinet/in.h>
|
||||
#include <pthread.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 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;
|
||||
struct tiff_reader {
|
||||
const char *filename;
|
||||
FILE *fp;
|
||||
TiffHdr header;
|
||||
TiffIFD ifd;
|
||||
TiffImage img;
|
||||
Pixel *pixels;
|
||||
};
|
||||
|
||||
typedef struct strip_thread_args StripThreadArgs;
|
||||
struct strip_thread_args {
|
||||
FILE *fp;
|
||||
const TiffStrip *strip;
|
||||
u64 pixel_count;
|
||||
u64 samples_per_pixel;
|
||||
u64 main_samples;
|
||||
TiffAlpha alpha;
|
||||
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 *img_out = NULL;
|
||||
|
||||
if (!file || !arena || !valid_tiff_file(file)) {
|
||||
goto READ_BASELINE_RETURN_IMG;
|
||||
}
|
||||
|
||||
TiffReader reader = {.filename = file, .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;
|
||||
}
|
||||
|
||||
return read_strips(reader);
|
||||
}
|
||||
|
||||
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 bool read_strips(TiffReader *reader) {
|
||||
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) {
|
||||
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]),
|
||||
};
|
||||
|
||||
if (!args[i].fp) {
|
||||
for (u64 j = 0; j < i; ++j) {
|
||||
fclose(args[j].fp);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
++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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
internal f32 u8_normalise(u8 value) { return (f32)value / (f32)UINT8_MAX; }
|
||||
|
||||
internal u8 u8_denormalise(f32 value) { return (u8)(value * UINT8_MAX); }
|
||||
17
src/tiff/tiffread.h
Normal file
17
src/tiff/tiffread.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef TIFFREAD_H
|
||||
#define TIFFREAD_H
|
||||
|
||||
#include "mem_arena.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif // __cplusplus
|
||||
|
||||
#include "image.h"
|
||||
|
||||
Image *read_baseline_tiff(const char *file, Arena *arena);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif // __cplusplus
|
||||
|
||||
#endif // !TIFFREAD_H
|
||||
455
src/tiffread.c
455
src/tiffread.c
@@ -1,455 +0,0 @@
|
||||
#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 <math.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stddef.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_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);
|
||||
}
|
||||
109
src/tiffread.h
109
src/tiffread.h
@@ -1,109 +0,0 @@
|
||||
#ifndef TIFFREAD_H
|
||||
#define TIFFREAD_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
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
|
||||
// types specified in the spec, so UINT8_MAX should be sufficient
|
||||
#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,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
typedef struct hdr TiffHdr;
|
||||
struct hdr {
|
||||
u16 order;
|
||||
u16 magic;
|
||||
u32 first_ifd_offset;
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
enum tiff_public_tags {
|
||||
#include "tiff_public_tags.inc"
|
||||
};
|
||||
|
||||
internal const char *tag_names[TAG_MAX_COUNT] = {
|
||||
#define TIFF_TAG_LOOKUP
|
||||
#include "tiff_public_tags.inc"
|
||||
#undef TIFF_TAG_LOOKUP
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
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_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
|
||||
|
||||
Image *read_baseline_tiff(const char *file, const Allocator *allocator);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif // __cplusplus
|
||||
|
||||
#endif // !TIFFREAD_H
|
||||
Reference in New Issue
Block a user