# HG changeset patch # User magedrifaat # Date 1659285299 -7200 # Node ID 50402b8dfb4ae6aa756971ed24b91294caaff850 # Parent 2e11f9cb30b89003f1a6ed6d1cfe388780b0ba3a Tiff: added writeEncodedTile function for writing tiled images * __tiff__.cc(F__tiff_write_encoded_tile__): added internal function for handling writeEncodedStrip. * __tiff__.cc(process_strip_or_tile): refactored common logic for strips and tiles to a function. * __tiff__.cc(write_tile): implemented logic for checking and writing tile data to image. * Tiff.m: added writeEncodedStrip method. diff -r 2e11f9cb30b8 -r 50402b8dfb4a libinterp/dldfcn/__tiff__.cc --- a/libinterp/dldfcn/__tiff__.cc Sun Jul 31 03:55:42 2022 +0200 +++ b/libinterp/dldfcn/__tiff__.cc Sun Jul 31 18:34:59 2022 +0200 @@ -299,7 +299,6 @@ uint8_t * img_u8 = reinterpret_cast (img_fvec); img_fvec[pixel]= (img_u8[pixel / 8] >> bit_number) & 0x01; } - break; } else if (image_data->bits_per_sample == 4) { @@ -322,7 +321,6 @@ uint8_t * img_u8 = reinterpret_cast (img_fvec); img_fvec[pixel] = (img_u8[pixel / 2] >> shift) & 0x0F; } - break; } else if (image_data->bits_per_sample != 8 && image_data->bits_per_sample != 16 && @@ -905,7 +903,7 @@ dim_vector strip_dimensions; // Calculate the expected number of elements in the strip data array - // All strips have equal number of rows excpet strips at the bottom + // All strips have equal number of rows except strips at the bottom // of the image can have less number of rows if (image_data->planar_configuration == PLANARCONFIG_CONTIG) { @@ -934,12 +932,12 @@ error ("Planar configuration not supported"); if (strip_data.dim1 () > rows_in_strip) - warning ("The strip is composed of %ld rows but the input has %ld rows.", + warning ("The strip is composed of %u rows but the input has %ld rows.", rows_in_strip, strip_data.dim1 ()); if (strip_data.dim2 () > image_data->width) - warning ("The image width is %ld but the input has %ld columns.", + warning ("The image width is %u but the input has %ld columns.", image_data->width, strip_data.dim2 ()); @@ -947,12 +945,12 @@ { if (image_data->planar_configuration == PLANARCONFIG_CONTIG && strip_data.dim3 () > image_data->samples_per_pixel) - warning ("The strip is composed of %ld channels but the input has %ld channels.", + warning ("The strip is composed of %u channels but the input has %ld channels.", image_data->samples_per_pixel, strip_data.dim3 ()); else if (image_data->planar_configuration == PLANARCONFIG_SEPARATE && strip_data.dim3 () > 1) - warning ("The strip is composed of %ld channel but the input has %ld channels.", + warning ("The strip is composed of %u channel but the input has %ld channels.", 1, strip_data.dim3 ()); } @@ -1017,6 +1015,201 @@ } } + template + void + write_tile (TIFF *tif, uint32_t tile_no, T tile_data, + tiff_image_data *image_data) + { + // TODO(maged): error for tiles not divisible by 16? + uint32_t tile_width, tile_height; + if (! TIFFGetField (tif, TIFFTAG_TILEWIDTH, &tile_width)) + error ("Failed to get the tile width"); + if (! TIFFGetField (tif, TIFFTAG_TILELENGTH, &tile_height)) + error ("Failed to get the tile length"); + + if (tile_no < 1 || tile_no > TIFFNumberOfTiles (tif)) + error ("Tile number out of bounds"); + + // TODO(maged): what does matlab do for boundary tiles? + if (tile_data.dim1 () > tile_height) + warning ("The tile is composed of %u rows but input has %ld rows", + tile_height, tile_data.dim1 ()); + if (tile_data.dim2 () > tile_width) + warning ("The tile is composed of %u columns but input has %ld columns", + tile_width, tile_data.dim2 ()); + if (tile_data.ndims () > 2) + { + if (image_data->planar_configuration == PLANARCONFIG_CONTIG + && tile_data.dim3 () > image_data->samples_per_pixel) + warning ("The tile is composed of %u channels but input has %ld channels", + image_data->samples_per_pixel, tile_data.dim3 ()); + else if (image_data->planar_configuration == PLANARCONFIG_SEPARATE + && tile_data.dim3 () > 1) + warning ("The tile is composed of %u channels but input has %ld channels", + 1, tile_data.dim3 ()); + } + + dim_vector tile_dimensions; + if (image_data->planar_configuration == PLANARCONFIG_CONTIG) + tile_dimensions = dim_vector (tile_height, tile_width, + image_data->samples_per_pixel); + else if (image_data->planar_configuration == PLANARCONFIG_SEPARATE) + tile_dimensions = dim_vector (tile_height, tile_width, 1); + else + error ("Planar configuration not supported"); + + tile_data.resize (tile_dimensions); + Array perm (dim_vector (3, 1)); + perm(0) = 2; + perm(1) = 1; + perm(2) = 0; + tile_data = tile_data.permute (perm); + + // Octave indexing is 1-based while LibTIFF is zero-based + tile_no--; + void *data_vec = tile_data.fortran_vec (); + if (image_data->bits_per_sample == 8 + || image_data->bits_per_sample == 16 + || image_data->bits_per_sample == 32 + || image_data->bits_per_sample == 64) + { + if (TIFFWriteEncodedTile (tif, tile_no, data_vec, + TIFFTileSize (tif)) == -1) + error ("Failed to write tile data to image"); + + } + else if (image_data->bits_per_sample == 1) + { + if (image_data->samples_per_pixel != 1) + error ("Bi-Level images must have one channel only"); + + // Create a buffer to hold the packed tile data + // Unique pointers are faster than vectors for constant size buffers + std::unique_ptr tile_ptr + = std::make_unique (TIFFTileSize (tif)); + uint8_t *tile_buf = tile_ptr.get (); + uint8_t *data_u8 = reinterpret_cast (data_vec); + // Packing the pixel data into bits + for (uint32_t row = 0; row < tile_height; row++) + { + for (uint32_t col = 0; col < tile_width; col++) + { + uint8_t shift = 7 - col % 8; + tile_buf[row * tile_width/8 + col/8] |= data_u8[col] << shift; + } + data_u8 += tile_width; + } + if (TIFFWriteEncodedTile (tif, tile_no, tile_buf, + TIFFTileSize (tif)) == -1) + error ("Failed to write tile data to image"); + } + else + { + error ("Unsupported bit depth"); + } + } + + template + void + write_strip_or_tile (TIFF *tif, uint32_t strip_tile_no, T strip_data, + tiff_image_data *image_data) + { + if (image_data->is_tiled) + write_tile (tif, strip_tile_no, strip_data, image_data); + else + write_strip (tif, strip_tile_no, strip_data, image_data); + } + + void + process_strip_or_tile (TIFF *tif, uint32_t strip_tile_no, + octave_value data_ov, tiff_image_data *image_data) + { + + // SampleFormat tag is not a required field and has a default value of 1 + // So we need to use TIFFGetFieldDefaulted in case it is not present in + // the file + uint16_t sample_format; + if (! TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLEFORMAT, &sample_format)) + error ("Failed to obtain a value for sample format"); + + // TODO(maged): add support for signed integer images + if (sample_format == 3) + { + if (image_data->bits_per_sample != 32 + && image_data->bits_per_sample != 64) + error ("Floating point images are only supported for bit depths of 32 and 64"); + } + + // The standard specifies that a SampleFormat of 4 should be treated + // the same as 1 (unsigned integer) + else if (sample_format != 1 && sample_format != 4) + error ("Unsupported sample format"); + + switch (image_data->bits_per_sample) + { + case 1: + // We need to check for both scalar and matrix types to handle single + // element strip + if (data_ov.is_bool_scalar () || data_ov.is_bool_matrix ()) + write_strip_or_tile (tif, strip_tile_no, + data_ov.bool_array_value (), + image_data); + else + error ("Expected logical matrix for BiLevel image"); + break; + case 8: + if (data_ov.is_uint8_type ()) + write_strip_or_tile (tif, strip_tile_no, + data_ov.uint8_array_value (), + image_data); + else + error ("Only uint8 data is allowed for uint images with bit depth of 8"); + break; + case 16: + if (data_ov.is_uint16_type ()) + write_strip_or_tile (tif, strip_tile_no, + data_ov.uint16_array_value (), + image_data); + else + error ("Only uint16 data is allowed for uint images with bit depth of 16"); + break; + case 32: + if (sample_format == 3) + if (data_ov.is_single_type () || data_ov.is_double_type ()) + write_strip_or_tile (tif, strip_tile_no, + data_ov.float_array_value (), + image_data); + else + error ("Only single and double data are allowed for floating-point images"); + else + if (data_ov.is_uint32_type ()) + write_strip_or_tile (tif, strip_tile_no, + data_ov.uint32_array_value (), + image_data); + else + error ("Only uint32 data is allowed for uint images with bit depth of 32"); + break; + case 64: + if (sample_format == 3) + if (data_ov.is_single_type () || data_ov.is_double_type ()) + write_strip_or_tile (tif, strip_tile_no, + data_ov.array_value (), + image_data); + else + error ("Only single and double data are allowed for floating-point images"); + else + if (data_ov.is_uint64_type ()) + write_strip_or_tile (tif, strip_tile_no, + data_ov.uint64_array_value (), + image_data); + else + error ("Only uint64 data is allowed for uint images with bit depth of 64"); + break; + default: + error ("Unsupported bit depth"); + } + } + #endif DEFUN_DLD (__open_tiff__, args, , @@ -1257,6 +1450,7 @@ #if defined (HAVE_TIFF) int nargin = args.length (); + // TODO(maged): add support for YCbCr data if (nargin < 3) error ("Too few arguments provided\n"); @@ -1276,90 +1470,45 @@ if (strip_no < 1 || strip_no > TIFFNumberOfStrips (tif)) error ("Strip number out of range"); - // SampleFormat tag is not a required field and has a default value of 1 - // So we need to use TIFFGetFieldDefaulted in case it is not present in - // the file - uint16_t sample_format; - if (! TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLEFORMAT, &sample_format)) - error ("Failed to obtain a value for sample format"); - - // TODO(maged): add support for signed integer images - if (sample_format == 3) - { - if (image_data.bits_per_sample != 32 && image_data.bits_per_sample != 64) - error ("Floating point images are only supported for bit depths of 32 and 64"); - } - // The standard specifies that a SampleFormat of 4 should be treated - // the same as 1 (unsigned integer) - else if (sample_format != 1 && sample_format != 4) - error ("Unsupported sample format"); - - switch (image_data.bits_per_sample) - { - case 1: - // We need to check for both scalar and matrix types to handle single - // element strip - if (args(2).is_bool_scalar () || args(2).is_bool_matrix ()) - write_strip (tif, strip_no, - args(2).bool_array_value (), &image_data); - else - error ("Expected logical matrix for BiLevel image"); - break; - case 8: - if (args(2).is_uint8_type ()) - write_strip (tif, strip_no, - args(2).uint8_array_value (), - &image_data); - else - error ("Only uint8 data is allowed for uint images with bit depth of 8"); - break; - case 16: - if (args(2).is_uint16_type ()) - write_strip (tif, strip_no, - args(2).uint16_array_value (), - &image_data); - else - error ("Only uint16 data is allowed for uint images with bit depth of 16"); - break; - case 32: - if (sample_format == 3) - if (args(2).is_single_type () || args(2).is_double_type ()) - write_strip (tif, strip_no, - args(2).float_array_value (), - &image_data); - else - error ("Only single and double data are allowed for floating-point images"); - else - if (args(2).is_uint32_type ()) - write_strip (tif, strip_no, - args(2).uint32_array_value (), - &image_data); - else - error ("Only uint32 data is allowed for uint images with bit depth of 32"); - break; - case 64: - if (sample_format == 3) - if (args(2).is_single_type () || args(2).is_double_type ()) - write_strip (tif, strip_no, - args(2).array_value (), - &image_data); - else - error ("Only single and double data are allowed for floating-point images"); - else - if (args(2).is_uint64_type ()) - write_strip (tif, strip_no, - args(2).uint64_array_value (), - &image_data); - else - error ("Only uint64 data is allowed for uint images with bit depth of 64"); - break; - default: - error ("Unsupported bit depth"); - } + process_strip_or_tile (tif, strip_no, args(2), &image_data); return octave_value_list (); #else err_disabled_feature ("writeEncodedStrip", "Tiff"); #endif } + + DEFUN_DLD (__tiff_write_encoded_tile__, args, , + "Write an encoded tile to the image") + { +#if defined (HAVE_TIFF) + int nargin = args.length (); + + // TODO(maged): add support for YCbCr data + if (nargin < 3) + error ("Too few arguments provided\n"); + + TIFF *tif = (TIFF *)(args (0).uint64_value ()); + + // TODO(maged): check on windows + if (TIFFGetMode (tif) == O_RDONLY) + error ("Can't write data to a file opened in read-only mode"); + + // Obtain all necessary tags + tiff_image_data image_data (tif); + + if (! image_data.is_tiled) + error ("Can't write tiles to a stripped image"); + + uint32_t tile_no = args (1).uint32_scalar_value (); + if (tile_no < 1 || tile_no > TIFFNumberOfTiles (tif)) + error ("Tile number out of range"); + + process_strip_or_tile (tif, tile_no, args(2), &image_data); + + return octave_value_list (); +#else + err_disabled_feature ("writeEncodedTile", "Tiff"); +#endif + } } diff -r 2e11f9cb30b8 -r 50402b8dfb4a scripts/io/Tiff.m --- a/scripts/io/Tiff.m Sun Jul 31 03:55:42 2022 +0200 +++ b/scripts/io/Tiff.m Sun Jul 31 18:34:59 2022 +0200 @@ -123,6 +123,13 @@ __tiff_write_encoded_strip__ (t.tiff_handle, stripNumber, imageData); endfunction + function writeEncodedTile (t, tileNumber, imageData) + if (t.closed) + error ("Image file was closed"); + endif + __tiff_write_encoded_tile__ (t.tiff_handle, tileNumber, imageData); + endfunction + % TODO(maged): add documentation and make print_usage work endmethods endclassdef