diff --git a/CMakeLists.txt b/CMakeLists.txt index c22e41e..106dd50 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,25 @@ include_directories("${CMAKE_SOURCE_DIR}/src/utils") include_directories("${CMAKE_SOURCE_DIR}/src/isyntax") include_directories("${CMAKE_SOURCE_DIR}/src/third_party") +# Find LibTIFF library +find_package(TIFF REQUIRED) +if (NOT TIFF_FOUND) + message(WARNING "LibTIFF not found") + message(WARNING "Will not compile `isyntax-to-tiff` utility") +endif() +include_directories(${TIFF_INCLUDE_DIR}) +find_package(Threads REQUIRED) +if (UNIX AND NOT APPLE) # needed for sem_wait, etc. + find_library(RT_LIBRARY rt) + set(EXTRA_LIBS ${RT_LIBRARY}) +elseif (APPLE) + # No extra libraries needed for macOS +elseif (WIN32) + # No extra libraries needed for Windows +endif () + + + set(LIBISYNTAX_COMMON_SOURCE_FILES src/libisyntax.c src/isyntax/isyntax.c @@ -34,6 +53,7 @@ set(LIBISYNTAX_COMMON_SOURCE_FILES src/third_party/ltalloc.cc ) + if (WIN32) set(LIBISYNTAX_COMMON_SOURCE_FILES ${LIBISYNTAX_COMMON_SOURCE_FILES} src/platform/win32_utils.c) else() @@ -49,9 +69,35 @@ add_executable(isyntax_example ${LIBISYNTAX_COMMON_SOURCE_FILES} ) + + +add_executable(isyntax_example2 + src/isyntax_example2.c + ${LIBISYNTAX_COMMON_SOURCE_FILES} +) + +if (TIFF_FOUND) + message(STATUS "Will compile `isyntax-to-tiff` utility") + add_executable(isyntax-to-tiff + src/isyntax_to_tiff.c + ${LIBISYNTAX_COMMON_SOURCE_FILES} ${TIFF_LIBRARIES}) + +endif() + +target_include_directories(isyntax-to-tiff PRIVATE ${TIFF_INCLUDE_DIRS}) + if (WIN32) - target_link_libraries(libisyntax winmm) - target_link_libraries(isyntax_example winmm) -else() + target_link_libraries(libisyntax winmm Threads::Threads) + target_link_libraries(isyntax_example winmm Threads::Threads) + target_link_libraries(isyntax_example2 winmm Threads::Threads) + target_link_libraries(isyntax-to-tiff winmm Threads::Threads) + +else() + target_link_libraries(libisyntax Threads::Threads ${EXTRA_LIBS}) + target_link_libraries(isyntax_example Threads::Threads ${EXTRA_LIBS}) + target_link_libraries(isyntax_example2 Threads::Threads ${EXTRA_LIBS}) + if (TIFF_FOUND) + target_link_libraries(isyntax-to-tiff PRIVATE ${TIFF_LIBRARIES} Threads::Threads ${EXTRA_LIBS}) + endif() endif() diff --git a/src/isyntax/isyntax.c b/src/isyntax/isyntax.c index 509c8fc..55ecb82 100644 --- a/src/isyntax/isyntax.c +++ b/src/isyntax/isyntax.c @@ -3069,11 +3069,13 @@ bool isyntax_open(isyntax_t* isyntax, const char* filename, bool init_allocators // (I guess this is related to the way the wavelet transform works.) // Put another way: the highest (zoomed out levels) are shifted the to the bottom right // (this is also reflected in the x and y coordinates of the codeblocks in the iSyntax header). - for (i32 scale = 1; scale < wsi_image->level_count; ++scale) { - isyntax_level_t* level = wsi_image->levels + scale; + for (i32 scale = 0; scale < wsi_image->level_count; ++scale) { + isyntax_level_t* level = wsi_image->levels + scale; + level->origin_offset_in_pixels = ((PER_LEVEL_PADDING << wsi_image->level_count) - PER_LEVEL_PADDING) >> scale; + level->width = wsi_image->width >> scale; + level->height = wsi_image->height >> scale; float offset_in_pixels = (float) (get_first_valid_coef_pixel(scale - 1)); - level->origin_offset_in_pixels = offset_in_pixels; - float offset_in_um_x = offset_in_pixels * wsi_image->levels[0].um_per_pixel_x; + float offset_in_um_x = offset_in_pixels * wsi_image->levels[0].um_per_pixel_x; float offset_in_um_y = offset_in_pixels * wsi_image->levels[0].um_per_pixel_y; level->origin_offset = (v2f){offset_in_um_x, offset_in_um_y}; } @@ -3297,4 +3299,40 @@ void isyntax_destroy(isyntax_t* isyntax) { file_handle_close(isyntax->file_handle); } - +void bgra_to_rgba(uint32_t *pixels, int tile_width, int tile_height) { + int num_pixels = tile_width * tile_height; + int num_pixels_aligned = (num_pixels / 4) * 4; + +#if defined(__ARM_NEON) + for (int i = 0; i < num_pixels_aligned; i += 4) { + uint32x4_t bgra = vld1q_u32(pixels + i); + uint32x4_t b_mask = vdupq_n_u32(0x000000FF); + uint32x4_t r_mask = vdupq_n_u32(0x00FF0000); + uint32x4_t b = vandq_u32(bgra, b_mask); + uint32x4_t r = vandq_u32(bgra, r_mask); + uint32x4_t br_swapped = vorrq_u32(vshlq_n_u32(b, 16), vshrq_n_u32(r, 16)); + uint32x4_t ga_alpha_mask = vdupq_n_u32(0xFF00FF00); + uint32x4_t ga_alpha = vandq_u32(bgra, ga_alpha_mask); + uint32x4_t rgba = vorrq_u32(ga_alpha, br_swapped); + vst1q_u32(pixels + i, rgba); + } +#elif defined(__SSE2__) + for (int i = 0; i < num_pixels_aligned; i += 4) { + __m128i bgra = _mm_loadu_si128((__m128i*)(pixels + i)); + __m128i b_mask = _mm_set1_epi32(0x000000FF); + __m128i r_mask = _mm_set1_epi32(0x00FF0000); + __m128i b = _mm_and_si128(bgra, b_mask); + __m128i r = _mm_and_si128(bgra, r_mask); + __m128i br_swapped = _mm_or_si128(_mm_slli_epi32(b, 16), _mm_srli_epi32(r, 16)); + __m128i ga_alpha_mask = _mm_set1_epi32(0xFF00FF00); + __m128i ga_alpha = _mm_and_si128(bgra, ga_alpha_mask); + __m128i rgba = _mm_or_si128(ga_alpha, br_swapped); + _mm_storeu_si128((__m128i*)(pixels + i), rgba); + } +#else + for (int i = num_pixels_aligned; i < num_pixels; ++i) { + uint32_t val = pixels[i]; + pixels[i] = ((val & 0xff) << 16) | (val & 0x00ff00) | ((val & 0xff0000) >> 16) | (val & 0xff000000); + } +#endif +} diff --git a/src/isyntax/isyntax.h b/src/isyntax/isyntax.h index 7f9d359..52ac1d5 100644 --- a/src/isyntax/isyntax.h +++ b/src/isyntax/isyntax.h @@ -275,13 +275,15 @@ typedef struct isyntax_level_t { i32 scale; i32 width_in_tiles; i32 height_in_tiles; + u64 width; + u64 height; float downsample_factor; float um_per_pixel_x; float um_per_pixel_y; float x_tile_side_in_um; float y_tile_side_in_um; u64 tile_count; - float origin_offset_in_pixels; + i32 origin_offset_in_pixels; v2f origin_offset; isyntax_tile_t* tiles; bool is_fully_loaded; diff --git a/src/isyntax_example.c b/src/isyntax_example.c index 5bba620..3c63070 100644 --- a/src/isyntax_example.c +++ b/src/isyntax_example.c @@ -6,13 +6,8 @@ #define STB_IMAGE_WRITE_IMPLEMENTATION #include "third_party/stb_image_write.h" // for png export - - #define LOG_VAR(fmt, var) printf("%s: %s=" fmt "\n", __FUNCTION__, #var, var) -uint32_t bgra_to_rgba(uint32_t val) { - return ((val & 0xff) << 16) | (val & 0x00ff00) | ((val & 0xff0000) >> 16) | (val & 0xff000000); -} void print_isyntax_levels(isyntax_t* isyntax) { int wsi_image_idx = libisyntax_get_wsi_image_index(isyntax); @@ -74,9 +69,8 @@ int main(int argc, char** argv) { assert(libisyntax_tile_read(isyntax, isyntax_cache, level, tile_x, tile_y, &pixels) == LIBISYNTAX_OK); // convert data to the correct pixel format (bgra->rgba). - for (int i = 0; i < tile_height * tile_width; ++i) { - pixels[i] = bgra_to_rgba(pixels[i]); - } + bgra_to_rgba(pixels, tile_width, tile_height); + printf("Writing %s...\n", output_png); stbi_write_png(output_png, tile_width, tile_height, 4, pixels, tile_width * 4); printf("Done writing %s.\n", output_png); @@ -87,4 +81,4 @@ int main(int argc, char** argv) { libisyntax_close(isyntax); return 0; -} +} \ No newline at end of file diff --git a/src/isyntax_example2.c b/src/isyntax_example2.c new file mode 100644 index 0000000..aa56f7a --- /dev/null +++ b/src/isyntax_example2.c @@ -0,0 +1,69 @@ +#include "libisyntax.h" +#include +#include + +#define STB_IMAGE_WRITE_IMPLEMENTATION + +#include "third_party/stb_image_write.h" // for png export + +#define LOG_VAR(fmt, var) printf("%s: %s=" fmt "\n", __FUNCTION__, #var, var) + + +int main(int argc, char **argv) { + + if (argc <= 7) { + printf("Usage: %s - write a tile to output.png", + argv[0], argv[0]); + return 0; + } + + char *filename = argv[1]; + + libisyntax_init(); + + isyntax_t *isyntax; + if (libisyntax_open(filename, /*is_init_allocators=*/0, &isyntax) != LIBISYNTAX_OK) { + printf("Failed to open %s\n", filename); + return -1; + } + printf("Successfully opened %s\n", filename); + + int32_t level = atoi(argv[2]); + int32_t x_coord = atoi(argv[3]); + int32_t y_coord = atoi(argv[4]); + int32_t region_width = atoi(argv[5]); + int32_t region_height = atoi(argv[6]); + const char *output_png = argv[7]; + + LOG_VAR("%d", level); + LOG_VAR("%d", x_coord); + LOG_VAR("%d", y_coord); + LOG_VAR("%d", region_width); + LOG_VAR("%d", region_height); + LOG_VAR("%s", output_png); + + int32_t tile_width = libisyntax_get_tile_width(isyntax); + int32_t tile_height = libisyntax_get_tile_height(isyntax); + LOG_VAR("%d", tile_width); + LOG_VAR("%d", tile_height); + + isyntax_cache_t *isyntax_cache = NULL; + assert(libisyntax_cache_create("example cache", 2000, &isyntax_cache) == LIBISYNTAX_OK); + assert(libisyntax_cache_inject(isyntax_cache, isyntax) == LIBISYNTAX_OK); + + uint32_t *pixels = NULL; + assert(libisyntax_read_region(isyntax, isyntax_cache, level, x_coord, y_coord, region_width, region_height, &pixels) == + LIBISYNTAX_OK); + + // convert data to the correct pixel format (bgra->rgba). + bgra_to_rgba(pixels, region_width, region_height); + + printf("Writing %s...\n", output_png); + stbi_write_png(output_png, region_width, region_height, 4, pixels, region_width * 4); + printf("Done writing %s.\n", output_png); + + libisyntax_tile_free_pixels(pixels); + libisyntax_cache_destroy(isyntax_cache); + libisyntax_close(isyntax); + return 0; +} diff --git a/src/isyntax_to_tiff.c b/src/isyntax_to_tiff.c new file mode 100644 index 0000000..cbd4cca --- /dev/null +++ b/src/isyntax_to_tiff.c @@ -0,0 +1,308 @@ +#include "libisyntax.h" +#include "tiffio.h" +#include +#include +#include +#include +#include + + +#define LOG_VAR(fmt, var) printf("%s: %s=" fmt "\n", __FUNCTION__, #var, var) + +void update_progress(int32_t total_progress, int32_t page_progress, int32_t page_number, double eta) { + printf("\rProgress: %3d%% | Page %d progress: %3d%% | ETA: %.0fs", total_progress, page_number, page_progress, eta); + fflush(stdout); +} + + +void write_page_to_tiff(TIFF *output_tiff, isyntax_t *isyntax, isyntax_cache_t *isyntax_cache, isyntax_level_t *level, + int32_t tile_width, int32_t tile_height, int32_t *total_tiles_written, int32_t total_tiles, + clock_t global_start_time, uint16_t compression_type, uint16_t quality) { + int32_t width = libisyntax_level_get_width(level); + int32_t height = libisyntax_level_get_height(level); + int32_t scale = libisyntax_level_get_scale(level); + + // Set the TIFF properties for the current level. + TIFFSetField(output_tiff, TIFFTAG_IMAGEWIDTH, width); + TIFFSetField(output_tiff, TIFFTAG_IMAGELENGTH, height); + TIFFSetField(output_tiff, TIFFTAG_BITSPERSAMPLE, 8); + TIFFSetField(output_tiff, TIFFTAG_SAMPLESPERPIXEL, 4); + + if (compression_type == COMPRESSION_JPEG) { + TIFFSetField(output_tiff, TIFFTAG_COMPRESSION, COMPRESSION_JPEG); + TIFFSetField(output_tiff, TIFFTAG_JPEGQUALITY, quality); + } else if (compression_type == COMPRESSION_LZW) { + TIFFSetField(output_tiff, TIFFTAG_COMPRESSION, COMPRESSION_LZW); + } + + TIFFSetField(output_tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField(output_tiff, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); + TIFFSetField(output_tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(output_tiff, TIFFTAG_TILEWIDTH, tile_width); + TIFFSetField(output_tiff, TIFFTAG_TILELENGTH, tile_height); + + // Set the resolution + double res_x = 10000.0 / libisyntax_level_get_mpp_x(level); + TIFFSetField(output_tiff, TIFFTAG_XRESOLUTION, res_x); + double res_y = 10000.0 / libisyntax_level_get_mpp_y(level); + TIFFSetField(output_tiff, TIFFTAG_YRESOLUTION, res_y); + TIFFSetField(output_tiff, TIFFTAG_RESOLUTIONUNIT, RESUNIT_CENTIMETER); + TIFFSetField(output_tiff, TIFFTAG_EXTRASAMPLES, 1, (uint16_t[]) {EXTRASAMPLE_UNASSALPHA}); + + if (level == 0) { + TIFFSetField(output_tiff, TIFFTAG_SUBFILETYPE, 0); + } else { + TIFFSetField(output_tiff, TIFFTAG_SUBFILETYPE, FILETYPE_REDUCEDIMAGE); + } + + int32_t tile_progress = 0; + int32_t tiles_in_page = ((height - 1) / tile_height) * ((width - 1) / tile_width) + 2; + + for (int32_t y_coord = 0; y_coord < height; y_coord += tile_height) { + for (int32_t x_coord = 0; x_coord < width; x_coord += tile_width) { + // At the borders the tile size can be smaller + int32_t region_width = (x_coord + tile_width > width) ? width - x_coord : tile_width; + int32_t region_height = (y_coord + tile_height > height) ? height - y_coord : tile_height; + + uint32_t *pixels = NULL; + assert(libisyntax_read_region(isyntax, isyntax_cache, scale, x_coord, y_coord, region_width, region_height, + &pixels) == LIBISYNTAX_OK); + + // Convert data to the correct pixel format (bgra->rgba). + bgra_to_rgba(pixels, region_width, region_height); + + uint32_t *tile_pixels = pixels; + + if (region_width != tile_width || region_height != tile_height) { + tile_pixels = calloc(tile_width * tile_height, sizeof(uint32_t)); + for (int32_t row = 0; row < region_height; ++row) { + memcpy(tile_pixels + row * tile_width, pixels + row * region_width, + region_width * sizeof(uint32_t)); + } + } + + // Write the tile to the output TIFF. + TIFFWriteTile(output_tiff, tile_pixels, x_coord, y_coord, 0, 0); + + ++tile_progress; + int32_t tile_percent = (tile_progress * 100) / tiles_in_page; + int32_t total_progress = ((*total_tiles_written + tile_progress) * 100) / total_tiles; + + // Calculate ETA + clock_t current_global_time = clock(); + double elapsed_global_time = (double) (current_global_time - global_start_time) / CLOCKS_PER_SEC; + double avg_time_per_tile = elapsed_global_time / (*total_tiles_written + tile_progress); + double eta = avg_time_per_tile * (total_tiles - (*total_tiles_written + tile_progress)); + update_progress(total_progress, tile_percent, scale, eta); + + libisyntax_tile_free_pixels(pixels); + } + } + + // Write the directory for the current level. + TIFFWriteDirectory(output_tiff); +} + +uint64_t parse_cache_size(const char *size_str) { + uint64_t size; + char unit; + + if (sscanf(size_str, "%lld%c", &size, &unit) == 2) { + if (unit == 'M') { + if (size > INT64_MAX / (1024)) { + printf("Error: Cache size too large.\n"); + return -1; + } + size *= 1024; + } else if (unit == 'G') { + if (size > INT64_MAX / (1024 * 1024)) { + printf("Error: Cache size too large.\n"); + return -1; + } + size *= 1024 * 1024; + } else { + printf("Error: Invalid unit for cache size. Use 'M' for megabytes or 'G' for gigabytes.\n"); + return -1; + } + } else if (sscanf(size_str, "%lld", &size) != 1) { + printf("Error: Invalid cache size format.\n"); + return -1; + } + + return size; +} + + +int main(int argc, char **argv) { + const char *usage_string = + "Usage: isyntax-to-tiff [OPTIONS] INPUT OUTPUT\n\n" + "Converts Philips iSyntax files to multi-resolution TIFF files.\n\n" + "Positional arguments:\n" + " INPUT Path to the input iSyntax file.\n" + " OUTPUT Path to the output TIFF file.\n\n" + "Options:\n" + " --tile-size SIZE Specifies the tile size for the output TIFF (default: 1024).\n" + " Must be a positive integer.\n\n" + " --compression TYPE Specifies the compression type for the output TIFF.\n" + " Supported types: JPEG, LZW, NONE (default: JPEG).\n\n" + " --quality VALUE Specifies the quality for JPEG compression (0-100).\n" + " Only applicable when using JPEG compression (default: 80).\n\n" + " --cache-size SIZE Specifies the cache size for the iSyntax library.\n" + " Accepts a number followed by 'M' (for megabytes) or 'G' (for gigabytes),\n" + " or just a number for kilobytes (default: 2000).\n\n" + "Example:\n\n" + " isyntax-to-tiff --tile-size 512 --compression JPEG --quality 90 --cache-size 1G input.isyntax output.tiff\n\n" + "This command will convert the input.isyntax file into an output.tiff file with a tile size of 512, JPEG compression at 90 quality, and a cache size of 1 gigabyte.\n"; + + if (argc < 3) { + printf("Error: Missing input and/or output file arguments.\n\n"); + printf("%s", usage_string); + return -1; + } + + char *filename = argv[1]; + char *output_tiffname = argv[2]; + + uint64_t cache_size = 2000; + int32_t tile_size = 1024; + + int compression_type = COMPRESSION_JPEG; + int quality = 80; + + for (int i = 3; i < argc; ++i) { + if (strcmp(argv[i], "--tile-size") == 0) { + if (i + 1 < argc) { + tile_size = atoi(argv[i + 1]); + if (tile_size <= 0) { + printf("Error: Invalid tile size. Please provide a positive integer value for the tile size.\n"); + return -1; + } + i++; // Skip the next argument (tile size value) + } else { + printf("Error: Missing value for --tile-size option.\n"); + return -1; + } + } else if (strcmp(argv[i], "--compression") == 0) { + if (i + 1 < argc) { + if (strcmp(argv[i + 1], "JPEG") == 0) { + compression_type = COMPRESSION_JPEG; + } else if (strcmp(argv[i + 1], "LZW") == 0) { + compression_type = COMPRESSION_LZW; + } else if (strcmp(argv[i + 1], "NONE") == 0) { + compression_type = COMPRESSION_NONE; + } else { + printf("Error: Invalid compression type. Supported types are JPEG, LZW, and NONE.\n"); + return -1; + } + i++; // Skip the next argument (compression type value) + } else { + printf("Error: Missing value for --compression option.\n"); + return -1; + } + } else if (strcmp(argv[i], "--quality") == 0) { + if (i + 1 < argc) { + quality = atoi(argv[i + 1]); + if (quality < 0 || quality > 100) { + printf("Error: Invalid quality value. Please provide an integer value between 0 and 100 for the quality.\n"); + return -1; + } + if (compression_type != COMPRESSION_JPEG) { + printf("Warning: The --quality flag is ignored with the current compression type. Quality is only applicable to JPEG compressions.\n"); + } + i++; // Skip the next argument (quality value) + } else { + printf("Error: Missing value for --quality option.\n"); + return -1; + } + } else if (strcmp(argv[i], "--cache-size") == 0) { + if (i + 1 < argc) { + cache_size = parse_cache_size(argv[i + 1]); + if (cache_size >= INT64_MAX || cache_size < 0) { + printf("Error: Cache size not suitable for the system.\n"); + return -1; + } + i++; // Skip the next argument (cache size value) + } else { + printf("Error: Missing value for --cache-size option.\n"); + return -1; + } + + } else { + printf("Error: Unknown option %s\n", argv[i]); + return -1; + } + } + int32_t tile_width = tile_size; + int32_t tile_height = tile_size; + + libisyntax_init(); + + isyntax_t *isyntax; + if (libisyntax_open(filename, /*is_init_allocators=*/0, &isyntax) != LIBISYNTAX_OK) { + fprintf(stderr, "Failed to open %s\n", filename); + return -1; + } + + int32_t internal_tile_width = libisyntax_get_tile_width(isyntax); + int32_t internal_tile_height = libisyntax_get_tile_height(isyntax); + LOG_VAR("%d", internal_tile_width); + LOG_VAR("%d", internal_tile_height); + LOG_VAR("%llu", cache_size); + LOG_VAR("%d", compression_type); + LOG_VAR("%d", quality); + LOG_VAR("%d", tile_size); + + isyntax_cache_t *isyntax_cache = NULL; + if (libisyntax_cache_create("isyntax-to-tiff cache", cache_size, &isyntax_cache) != LIBISYNTAX_OK) { + fprintf(stderr, "Failed to create iSyntax cache with size %llu.\n", cache_size); + libisyntax_close(isyntax); + return -1; + } + if (libisyntax_cache_inject(isyntax_cache, isyntax) != LIBISYNTAX_OK) { + fprintf(stderr, "Failed to inject iSyntax cache into iSyntax instance.\n"); + libisyntax_cache_destroy(isyntax_cache); + libisyntax_close(isyntax); + return -1; + } + + // Initialize the output TIFF file. + TIFF *output_tiff; + output_tiff = TIFFOpen(output_tiffname, "w8"); + if (!output_tiff) { + fprintf(stderr, "Failed to create %s\n", output_tiffname); + return -1; + } + + // Write all levels to the output TIFF. + int start_at_page = 0; + + const isyntax_image_t *image = libisyntax_get_image(isyntax, 0); + int32_t num_levels = libisyntax_image_get_level_count(image); + int32_t total_tiles = 0; + + // Let's find the total number of tiles so we can have a progress counter + for (int32_t level = start_at_page; level < num_levels; ++level) { + isyntax_level_t *current_level = libisyntax_image_get_level(image, level); + int32_t width = libisyntax_level_get_width(current_level); + int32_t height = libisyntax_level_get_height(current_level); + int32_t tiles_in_page = ((height - 1) / tile_height) * ((width - 1) / tile_width) + 2; + total_tiles += tiles_in_page; + } + + int32_t total_tiles_written = 0; + clock_t global_start_time = clock(); + for (int32_t level = start_at_page; level < num_levels; ++level) { + isyntax_level_t *current_level = libisyntax_image_get_level(image, level); + write_page_to_tiff(output_tiff, isyntax, isyntax_cache, current_level, tile_width, tile_height, + &total_tiles_written, total_tiles, global_start_time, compression_type, quality); + } + + // Close the output TIFF file. + TIFFClose(output_tiff); + + // Clean up. + libisyntax_cache_destroy(isyntax_cache); + libisyntax_close(isyntax); + + return 0; +} \ No newline at end of file diff --git a/src/libisyntax.c b/src/libisyntax.c index 599576a..4a7408a 100644 --- a/src/libisyntax.c +++ b/src/libisyntax.c @@ -218,6 +218,23 @@ int32_t libisyntax_level_get_scale(const isyntax_level_t* level) { return level->scale; } +float libisyntax_level_get_mpp_x(const isyntax_level_t* level) { + return level->um_per_pixel_x; +} + +float libisyntax_level_get_mpp_y(const isyntax_level_t* level) { + return level->um_per_pixel_y; +} + +// TODO(jt): Signature equivalent with openslide would be openslide_get_level_dimensions(isyntax_image_t *isyntax, int32_t level, int64_t *w, int64_t *h) { +int32_t libisyntax_level_get_width(const isyntax_level_t* level) { + return level->width; +} + +int32_t libisyntax_level_get_height(const isyntax_level_t* level) { + return level->height; +} + int32_t libisyntax_level_get_width_in_tiles(const isyntax_level_t* level) { return level->width_in_tiles; } @@ -293,6 +310,79 @@ isyntax_error_t libisyntax_tile_read(isyntax_t* isyntax, isyntax_cache_t* isynta return LIBISYNTAX_OK; } +isyntax_error_t libisyntax_read_region(isyntax_t* isyntax, isyntax_cache_t* isyntax_cache, int32_t level, + int64_t x, int64_t y, int64_t width, int64_t height, uint32_t** out_pixels) { + + // Get the level + assert(level < &isyntax->images[0].level_count); + isyntax_level_t* current_level = &isyntax->images[0].levels[level]; + + // Setup the origin offset + int32_t offset = current_level->origin_offset_in_pixels; + x += offset; + y += offset; + + // Check bounds + // TODO: Figure out if the bounds are starting from x = 0 or from the offset + assert(x + width - offset <= current_level->width); + assert(y + height - offset <= current_level->height); + + int32_t tile_width = isyntax->tile_width; + int32_t tile_height = isyntax->tile_height; + + int64_t start_tile_x = x / tile_width; + int64_t end_tile_x = (x + width - 1) / tile_width; + int64_t start_tile_y = y / tile_height; + int64_t end_tile_y = (y + height - 1) / tile_height; + + // Allocate memory for region + *out_pixels = (uint32_t*)malloc(width * height * sizeof(uint32_t)); + + // Read tiles and copy the relevant portion of each tile to the region + for (int64_t tile_y = start_tile_y; tile_y <= end_tile_y; ++tile_y) { + for (int64_t tile_x = start_tile_x; tile_x <= end_tile_x; ++tile_x) { + // Calculate the portion of the tile to be copied + int64_t src_x = (tile_x == start_tile_x) ? x % tile_width : 0; + int64_t src_y = (tile_y == start_tile_y) ? y % tile_height : 0; + int64_t dest_x = (tile_x == start_tile_x) ? 0 : (tile_x - start_tile_x) * tile_width - (x % tile_width); + int64_t dest_y = (tile_y == start_tile_y) ? 0 : (tile_y - start_tile_y) * tile_height - + (y % tile_height); + int64_t copy_width = (tile_x == end_tile_x) ? (x + width) % tile_width : tile_width - src_x; + int64_t copy_height = (tile_y == end_tile_y) ? (y + height) % tile_height : tile_height - src_y; + + uint32_t *pixels = NULL; + + // Check if tile exists, if not, don't use the function to read the tile and immediately return an empty + // tile. + int64_t tile_index = tile_y * current_level->width_in_tiles + tile_x; + bool tile_exists = (isyntax->images[0].levels[level].tiles + tile_index)->exists; + if (tile_exists) { + // Read tile + assert(libisyntax_tile_read(isyntax, isyntax_cache, level, tile_x, tile_y, &pixels) == LIBISYNTAX_OK); + // Copy the relevant portion of the tile to the region + for (int64_t i = 0; i < copy_height; ++i) { + memcpy((*out_pixels) + (dest_y + i) * width + dest_x, + pixels + (src_y + i) * tile_width + src_x, + copy_width * sizeof(uint32_t)); + } + } else { + // Fill up with transparent white pixels (R=255, G=255, B=255, A=0) + for (int64_t i = 0; i < copy_height; ++i) { + for (int64_t j = 0; j < copy_width; ++j) { + (*out_pixels)[(dest_y + i) * width + dest_x + j] = 0x00FFFFFFu; + } + } + } + + // Free the tile data + free(pixels); + } + } + + return LIBISYNTAX_OK; +} + + void libisyntax_tile_free_pixels(uint32_t* pixels) { free(pixels); } diff --git a/src/libisyntax.h b/src/libisyntax.h index 4053641..4277c82 100644 --- a/src/libisyntax.h +++ b/src/libisyntax.h @@ -46,6 +46,10 @@ const isyntax_level_t* libisyntax_image_get_level(const isyntax_image_t* image, int32_t libisyntax_level_get_scale(const isyntax_level_t* level); int32_t libisyntax_level_get_width_in_tiles(const isyntax_level_t* level); int32_t libisyntax_level_get_height_in_tiles(const isyntax_level_t* level); +int32_t libisyntax_level_get_width(const isyntax_level_t* level); +int32_t libisyntax_level_get_height(const isyntax_level_t* level); +float libisyntax_level_get_mpp_x(const isyntax_level_t* level); +float libisyntax_level_get_mpp_y(const isyntax_level_t* level); //== Cache API == isyntax_error_t libisyntax_cache_create(const char* debug_name_or_null, int32_t cache_size, @@ -57,12 +61,16 @@ isyntax_error_t libisyntax_cache_create(const char* debug_name_or_null, int32_t isyntax_error_t libisyntax_cache_inject(isyntax_cache_t* isyntax_cache, isyntax_t* isyntax); void libisyntax_cache_destroy(isyntax_cache_t* isyntax_cache); +//== Helpers === +void bgra_to_rgba(uint32_t *pixels, int tile_width, int tile_height); //== Tile API == -// Note: pixels are in BGRA layout. +// Note: pixels are in BGRA layout. Use bgra_to_rgba to convert. // Note: must use libisyntax_tile_free_pixels() to free out_pixels. isyntax_error_t libisyntax_tile_read(isyntax_t* isyntax, isyntax_cache_t* isyntax_cache, int32_t level, int64_t tile_x, int64_t tile_y, uint32_t** out_pixels); +isyntax_error_t libisyntax_read_region(isyntax_t* isyntax, isyntax_cache_t* isyntax_cache, int32_t level, + int64_t x, int64_t y, int64_t width, int64_t height, uint32_t** out_pixels); void libisyntax_tile_free_pixels(uint32_t* pixels);