# HG changeset patch # User magedrifaat # Date 1659815966 -7200 # Node ID f2ae7763739a4d5b38c87874dfa9d8335b5162a8 # Parent dc3d2744916d5e2a8df3c40e0f2b1c94d6d1b4f1 Tiff: added writeEncodedTile method to read tiles from image * __tiff__.cc(F__tiff_read_encoded_tile__): added internal function to process readEncodedTile arguments. * __tiff__.cc(read_tile): implemented logic for reading tile from image file. * __tiff__.cc(handle_read_strip_or_tile): refactores common logic between read_strip and read_tile into a function. * Tiff.m: added readEncodedTile method to the Tiff class and added the necessary unit tests. diff -r dc3d2744916d -r f2ae7763739a libinterp/dldfcn/__tiff__.cc --- a/libinterp/dldfcn/__tiff__.cc Fri Aug 05 22:44:55 2022 +0200 +++ b/libinterp/dldfcn/__tiff__.cc Sat Aug 06 21:59:26 2022 +0200 @@ -136,10 +136,47 @@ error ("Unsupported bit depth"); T strip_data (strip_dims); + uint8_t *strip_fvec + = reinterpret_cast (strip_data.fortran_vec ()); - if (TIFFReadEncodedStrip (tif, strip_no, - strip_data.fortran_vec (), -1) == -1) - error ("Failed to read strip data"); + 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 (TIFFReadEncodedStrip (tif, strip_no, strip_fvec, -1) == -1) + error ("Failed to read strip data"); + } + 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 strip data + // Unique pointers are faster than vectors for constant size buffers + std::unique_ptr strip_ptr + = std::make_unique (TIFFStripSize (tif)); + uint8_t *strip_buf = strip_ptr.get (); + if (TIFFReadEncodedStrip (tif, strip_no, strip_buf, -1) == -1) + error ("Failed to read strip data"); + + // According to the format specification, the row should be byte + // aligned so the number of bytes is rounded up to the nearest byte + uint32_t padded_width = (image_data->width + 7) / 8; + // Packing the pixel data into bits + for (uint32_t row = 0; row < rows_in_strip; row++) + { + for (uint32_t col = 0; col < image_data->width; col++) + { + uint8_t shift = 7 - col % 8; + strip_fvec[col] = (strip_buf[col / 8] >> shift) & 0x1; + } + strip_fvec += image_data->width; + strip_buf += padded_width; + } + } + else + error ("Unsupported bit depth"); Array perm (dim_vector (3, 1)); if (image_data->planar_configuration == PLANARCONFIG_CONTIG) @@ -161,6 +198,184 @@ template octave_value + read_tile (TIFF *tif, uint32_t tile_no, tiff_image_data *image_data) + { + // ASSUMES tiled image and tile_no is a valid zero-based tile + // index for the tif image + + // TODO(maged): check matlab behavior + // TODO(maged): refactor into a function? + uint32_t tile_width, tile_height; + if (! TIFFGetField (tif, TIFFTAG_TILELENGTH, &tile_height)) + error ("Filed to read tile length"); + + if (! TIFFGetField (tif, TIFFTAG_TILEWIDTH, &tile_width)) + error ("Filed to read tile length"); + + if (tile_height == 0 || tile_height % 16 != 0 + || tile_width == 0 || tile_width % 16 != 0) + error ("Tile dimesion tags are invalid"); + + dim_vector tile_dims; + if (image_data->planar_configuration == PLANARCONFIG_CONTIG) + { + tile_dims = dim_vector (image_data->samples_per_pixel, tile_width, + tile_height); + } + else if (image_data->planar_configuration == PLANARCONFIG_SEPARATE) + { + tile_dims = dim_vector (tile_width, tile_height, 1); + } + else + error ("Unsupported planar configuration"); + + T tile_data (tile_dims); + uint8_t *tile_fvec + = reinterpret_cast (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 (TIFFReadEncodedTile (tif, tile_no, tile_fvec, -1) == -1) + error ("Failed to read tile data"); + } + 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 (); + if (TIFFReadEncodedTile (tif, tile_no, tile_buf, -1) == -1) + error ("Failed to read tile data"); + + // unpack tile bits into output matrix cells + 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_fvec[col] = (tile_buf [col / 8] >> shift) & 0x1; + } + tile_fvec += tile_width; + tile_buf += tile_width / 8; + } + } + else + error ("Unsupported bit depth"); + + Array perm (dim_vector (3, 1)); + if (image_data->planar_configuration == PLANARCONFIG_CONTIG) + { + perm(0) = 2; + perm(1) = 1; + perm(2) = 0; + } + else if (image_data->planar_configuration == PLANARCONFIG_SEPARATE) + { + perm(0) = 1; + perm(1) = 0; + perm(2) = 2; + } + + tile_data = tile_data.permute (perm); + + // Get the actual tile dimensions + uint32_t tiles_across = (image_data->width + tile_width - 1) + / tile_width; + uint32_t tiles_down = (image_data->height + tile_height - 1) + / tile_height; + uint32_t corrected_width = tile_width; + uint32_t corrected_height = tile_height; + if (tile_no % tiles_across == tiles_across - 1) + corrected_width = image_data->width - tile_width * (tiles_across - 1); + if ((tile_no / tiles_across) % tiles_down == tiles_down - 1) + corrected_height = image_data->height - tile_height * (tiles_down - 1); + + dim_vector corrected_dims; + if (image_data->planar_configuration == PLANARCONFIG_CONTIG) + corrected_dims = dim_vector (corrected_height, corrected_width, + image_data->samples_per_pixel); + else if (image_data->planar_configuration == PLANARCONFIG_SEPARATE) + corrected_dims = dim_vector (corrected_height, corrected_width); + // TODO(maged): confirm matlab behavior + tile_data.resize (corrected_dims); + + return octave_value (tile_data); + } + + template + octave_value + read_strip_or_tile (TIFF *tif, uint32_t strip_tile_no, + tiff_image_data *image_data) + { + if (image_data->is_tiled) + return read_tile (tif, strip_tile_no, image_data); + else + return read_strip (tif, strip_tile_no, image_data); + } + + octave_value + handle_read_strip_or_tile (TIFF *tif, uint32_t strip_tile_no) + { + // Obtain all necessary tags + tiff_image_data image_data (tif); + + uint16_t sample_format; + if (! TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLEFORMAT, &sample_format)) + error ("Failed to obtain a value for sample format"); + + 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"); + } + else if (sample_format != 1 && sample_format != 4) + error ("Unsupported sample format"); + + switch (image_data.bits_per_sample) + { + case 1: + return read_strip_or_tile (tif, strip_tile_no, + &image_data); + break; + case 8: + return read_strip_or_tile (tif, strip_tile_no, + &image_data); + break; + case 16: + return read_strip_or_tile (tif, strip_tile_no, + &image_data); + break; + case 32: + if (sample_format == 3) + return read_strip_or_tile (tif, strip_tile_no, + &image_data); + else + return read_strip_or_tile (tif, strip_tile_no, + &image_data); + break; + case 64: + if (sample_format == 3) + return read_strip_or_tile (tif, strip_tile_no, + &image_data); + else + return read_strip_or_tile (tif, strip_tile_no, + &image_data); + break; + default: + error ("Unsupported bit depth"); + } + } + + template + octave_value read_stripped_image (TIFF *tif, tiff_image_data *image_data) { typedef typename T::element_type P; @@ -1187,8 +1402,9 @@ } void - process_strip_or_tile (TIFF *tif, uint32_t strip_tile_no, - octave_value data_ov, tiff_image_data *image_data) + handle_write_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 @@ -1737,7 +1953,7 @@ } DEFUN_DLD (__tiff_read_encoded_strip__, args, nargout, - "Read the image in the current IFD") + "Read a strip from the image in the current IFD") { #if defined (HAVE_TIFF) int nargin = args.length (); @@ -1766,50 +1982,43 @@ // Convert from Octave's 1-based indexing to zero-based indexing strip_no--; - // Obtain all necessary tags - tiff_image_data image_data (tif); - - uint16_t sample_format; - if (! TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLEFORMAT, &sample_format)) - error ("Failed to obtain a value for sample format"); + return octave_value_list (handle_read_strip_or_tile (tif, strip_no)); +#else + err_disabled_feature ("readEncodedStrip", "Tiff"); +#endif + } - 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"); - } - else if (sample_format != 1 && sample_format != 4) - error ("Unsupported sample format"); + DEFUN_DLD (__tiff_read_encoded_tile__, args, nargout, + "Read a tile from the image in the current IFD") + { +#if defined (HAVE_TIFF) + int nargin = args.length (); + + if (nargin != 2) + error ("rong number of arguments"); - octave_value_list retval; - switch (image_data.bits_per_sample) - { - case 1: - retval(0) = read_strip (tif, strip_no, &image_data); - break; - case 8: - retval(0) = read_strip (tif, strip_no, &image_data); - break; - case 16: - retval(0) = read_strip (tif, strip_no, &image_data); - break; - case 32: - if (sample_format == 3) - retval(0) = read_strip (tif, strip_no, &image_data); - else - retval(0) = read_strip (tif, strip_no, &image_data); - break; - case 64: - if (sample_format == 3) - retval(0) = read_strip (tif, strip_no, &image_data); - else - retval(0) = read_strip (tif, strip_no, &image_data); - break; - default: - error ("Unsupported bit depth"); - } + TIFF *tif = (TIFF *)(args(0).uint64_value ()); + + // TODO(maged): nargout and ycbcr + octave_unused_parameter (nargout); + + if (! TIFFIsTiled (tif)) + error ("The image is stripped not tiled"); - return retval; + // TODO(maged): what is the behavior in matlab + uint32_t tile_no; + if (args(1).is_scalar_type ()) + tile_no = args(1).uint32_scalar_value (); + else + error ("Expected scalar for tile number"); + + if (tile_no < 1 || tile_no > TIFFNumberOfTiles (tif)) + error ("Tile number out of bounds"); + + // Convert from Octave's 1-based indexing to zero-based indexing + tile_no--; + + return octave_value_list (handle_read_strip_or_tile (tif, tile_no)); #else err_disabled_feature ("readEncodedStrip", "Tiff"); #endif @@ -1934,7 +2143,7 @@ if (strip_no < 1 || strip_no > TIFFNumberOfStrips (tif)) error ("Strip number out of range"); - process_strip_or_tile (tif, strip_no, args(2), &image_data); + handle_write_strip_or_tile (tif, strip_no, args(2), &image_data); return octave_value_list (); #else @@ -1968,7 +2177,7 @@ 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); + handle_write_strip_or_tile (tif, tile_no, args(2), &image_data); return octave_value_list (); #else diff -r dc3d2744916d -r f2ae7763739a scripts/io/Tiff.m --- a/scripts/io/Tiff.m Fri Aug 05 22:44:55 2022 +0200 +++ b/scripts/io/Tiff.m Sat Aug 06 21:59:26 2022 +0200 @@ -123,6 +123,13 @@ stripData = __tiff_read_encoded_strip__ (t.tiff_handle, stripNumber); endfunction + function tileData = readEncodedTile (t, tileNumber) + if (t.closed) + error ("Image file was closed"); + endif + tileData = __tiff_read_encoded_tile__ (t.tiff_handle, tileNumber); + endfunction + function write (t, imageData) if (t.closed) error ("Image file was closed"); @@ -1176,6 +1183,21 @@ %! endfunction %! file_wrapper (@test_fn); +## test readEncodedStrip BiLevel image +%!testif HAVE_TIFF +%! function test_fn (filename) +%! img = Tiff (filename, "w"); +%! setTag(img, struct ("ImageLength", 10, "ImageWidth", 10, +%! "RowsPerStrip", 6)); +%! data = logical (repmat ([1,0,0,0,1,0,1,1,1,0], [10, 1])); +%! write (img, data); +%! s1 = readEncodedStrip (img, 1); +%! s2 = readEncodedStrip (img, 2); +%! assert ([s1;s2], data); +%! img.close(); +%! endfunction +%! file_wrapper (@test_fn); + ## test readEncodedStrip RGB Chunky %!testif HAVE_TIFF %! function test_fn (filename) @@ -1203,16 +1225,80 @@ %! "PlanarConfiguration", 2)); %! data = uint16 (reshape (1:300, [10, 10, 3])); %! write (img, data); -%! s1 = readEncodedStrip (img, 1); -%! s2 = readEncodedStrip (img, 2); -%! s3 = readEncodedStrip (img, 3); -%! s4 = readEncodedStrip (img, 4); -%! s5 = readEncodedStrip (img, 5); -%! s6 = readEncodedStrip (img, 6); -%! s = [s1;s2]; -%! s(:, :, 2) = [s3;s4]; -%! s(:, :, 3) = [s5;s6]; -%! assert (s, data); +%! s = cell (6, 1); +%! for i=1:6 +%! s{i} = readEncodedStrip (img, i); +%! endfor +%! data2 = cat (3, [s{1}; s{2}], [s{3}; s{4}], [s{5}; s{6}]); +%! assert (data2, data); +%! img.close(); +%! endfunction +%! file_wrapper (@test_fn); + +## test readEncodedTile BiLevel image +%!testif HAVE_TIFF +%! function test_fn (filename) +%! img = Tiff (filename, "w"); +%! setTag(img, struct ("ImageLength", 40, "ImageWidth", 40, +%! "TileWidth", 16, "TileLength", 16)); +%! data = logical (repmat ([1,0,0,0,1,0,1,1,1,0], [40, 4])); +%! write (img, data); +%! t = cell (9, 1); +%! for i=1:9 +%! t{i} = readEncodedTile (img, i); +%! endfor +%! data2 = cat(1, cat (2, t{1}, t{2}, t{3}), cat (2, t{4}, t{5}, t{6}), +%! cat (2, t{7}, t{8}, t{9})); +%! assert (data2, data); %! img.close(); %! endfunction %! file_wrapper (@test_fn); + +## test readEncodedTile RGB Chunky +%!testif HAVE_TIFF +%! function test_fn (filename) +%! img = Tiff (filename, "w"); +%! setTag(img, struct ("ImageLength", 40, "ImageWidth", 40, +%! "TileWidth", 16, "TileLength", 16, +%! "BitsPerSample", 16, "SamplesPerPixel", 3, +%! "PhotometricInterpretation", 2, +%! "PlanarConfiguration", 1)); +%! data = uint16 (reshape (1:4800, [40, 40, 3])); +%! write (img, data); +%! t = cell (9, 1); +%! for i=1:9 +%! t{i} = readEncodedTile (img, i); +%! endfor +%! data2 = cat(1, cat (2, t{1}, t{2}, t{3}), cat (2, t{4}, t{5}, t{6}), +%! cat (2, t{7}, t{8}, t{9})); +%! assert (data2, data); +%! img.close(); +%! endfunction +%! file_wrapper (@test_fn); + +## test readEncodedTile RGB Separated +%!testif HAVE_TIFF +%! function test_fn (filename) +%! img = Tiff (filename, "w"); +%! setTag(img, struct ("ImageLength", 40, "ImageWidth", 40, +%! "TileWidth", 16, "TileLength", 16, +%! "BitsPerSample", 16, "SamplesPerPixel", 3, +%! "PhotometricInterpretation", 2, +%! "PlanarConfiguration", 2)); +%! data = uint16 (reshape (1:4800, [40, 40, 3])); +%! write (img, data); +%! data2 = uint16 (zeros (40, 40, 3)); +%! tile = 1; +%! for sample=1:3 +%! for row=1:16:40 +%! for col=1:16:40 +%! data2(row: min(40, row + 15), col: min(40, col + 15), sample)... +%! = readEncodedTile(img, tile); +%! tile += 1; +%! endfor +%! endfor +%! endfor +%! assert (data2, data); +%! img.close(); +%! endfunction +%! file_wrapper (@test_fn);