@@ -883,14 +883,17 @@ static bool testReduceWithRegion()
883883 return false ;
884884 }
885885
886- // Decompress a sub-region at reduce=1
886+ // Decompress a sub-region at reduce=1.
887+ // Coordinates are in reduced output space: 64x64 reduced pixels
888+ // = 128x128 full-res pixels, yielding 64x64 output.
887889 grk_decompress_parameters params{};
888890 params.core .tile_cache_strategy = GRK_TILE_CACHE_IMAGE;
889891 params.core .reduce = 1 ;
890892 params.dw_x0 = 0 ;
891893 params.dw_y0 = 0 ;
892- params.dw_x1 = 128 ;
893- params.dw_y1 = 128 ;
894+ params.dw_x1 = 64 ;
895+ params.dw_y1 = 64 ;
896+ params.dw_reduced = true ;
894897 params.asynchronous = true ;
895898 params.simulate_synchronous = true ;
896899
@@ -919,10 +922,11 @@ static bool testReduceWithRegion()
919922 return false ;
920923 }
921924
922- // At reduce=1, the 128x128 region should produce ~64x64 output
925+ // At reduce=1, the 64x64 reduced-space region produces ~64x64 output
923926 uint32_t w = img->x1 - img->x0 ;
924927 uint32_t h = img->y1 - img->y0 ;
925- spdlog::info (" Reduce+region output: {}x{}" , w, h);
928+ spdlog::info (" Reduce+region output: {}x{} (x0={}, y0={}, x1={}, y1={})" , w, h, img->x0 , img->y0 , img->x1 , img->y1 );
929+ spdlog::info (" Reduce+region comps[0]: w={}, h={}" , img->comps [0 ].w , img->comps [0 ].h );
926930
927931 std::error_code ec;
928932 std::filesystem::remove (testFile, ec);
@@ -933,8 +937,8 @@ static bool testReduceWithRegion()
933937 return false ;
934938 }
935939
936- // Region is 128x128, reduce=1 → expect ~64x64
937- if (w > 128 || h > 128 )
940+ // Region is 64x64 in reduced space → expect ~64x64 output
941+ if (w > 64 || h > 64 )
938942 {
939943 spdlog::error (" FAIL: Output larger than region" );
940944 return false ;
@@ -944,6 +948,94 @@ static bool testReduceWithRegion()
944948 return true ;
945949}
946950
951+ // /////////////////////////////////////////////////////////////////
952+ // Test 9b: Reduced-coords API for multi-tile per-tile images
953+ //
954+ // Verifies that grk_decompress_get_tile_image returns tile images
955+ // with x0/y0/x1/y1 in reduced coordinate space (not full-res).
956+ // /////////////////////////////////////////////////////////////////
957+ static bool testReducedCoordsPerTile ()
958+ {
959+ spdlog::info (" === Test: Reduced-coords per-tile API ===" );
960+
961+ // Create 256x256 image with 128x128 tiles (2x2 grid)
962+ std::string testFile =
963+ (std::filesystem::temp_directory_path () / " grk_reduced_coords_test.j2k" ).string ();
964+ if (!createTestImage (testFile, 256 , 256 , 128 , 128 , false ))
965+ {
966+ spdlog::error (" Failed to create test image" );
967+ return false ;
968+ }
969+
970+ // Decompress at reduce=1 (output 128x128 with 64x64 tiles)
971+ grk_decompress_parameters params{};
972+ params.core .tile_cache_strategy = GRK_TILE_CACHE_IMAGE;
973+ params.core .reduce = 1 ;
974+ params.core .skip_allocate_composite = true ;
975+ params.dw_reduced = true ;
976+ params.asynchronous = true ;
977+ params.simulate_synchronous = true ;
978+
979+ grk_stream_params sp{};
980+ safe_strcpy (sp.file , testFile.data ());
981+ CodecPtr codec (grk_decompress_init (&sp, ¶ms));
982+ grk_header_info hi{};
983+ grk_decompress_read_header (codec.get (), &hi);
984+ grk_decompress_update (¶ms, codec.get ());
985+
986+ bool ok = grk_decompress (codec.get (), nullptr );
987+ if (!ok)
988+ {
989+ spdlog::error (" Decompress failed" );
990+ std::filesystem::remove (testFile);
991+ return false ;
992+ }
993+
994+ grk_decompress_wait (codec.get (), nullptr );
995+
996+ // Check each tile's image coordinates are in reduced space
997+ // Full-res tiles: (0,0,128,128), (128,0,256,128), (0,128,128,256), (128,128,256,256)
998+ // Expected reduced: (0,0,64,64), (64,0,128,64), (0,64,64,128), (64,64,128,128)
999+ struct Expected
1000+ {
1001+ uint16_t tileIndex;
1002+ uint32_t x0, y0, x1, y1;
1003+ };
1004+ Expected expected[] = {{0 , 0 , 0 , 64 , 64 }, {1 , 64 , 0 , 128 , 64 }, {2 , 0 , 64 , 64 , 128 }, {3 , 64 , 64 , 128 , 128 }};
1005+
1006+ for (auto & e : expected)
1007+ {
1008+ auto * tileImg = grk_decompress_get_tile_image (codec.get (), e.tileIndex , true );
1009+ if (!tileImg)
1010+ {
1011+ spdlog::error (" FAIL: get_tile_image returned null for tile {}" , e.tileIndex );
1012+ std::filesystem::remove (testFile);
1013+ return false ;
1014+ }
1015+ if (tileImg->x0 != e.x0 || tileImg->y0 != e.y0 || tileImg->x1 != e.x1 || tileImg->y1 != e.y1 )
1016+ {
1017+ spdlog::error (" FAIL: tile {} coords ({},{},{},{}) != expected ({},{},{},{})" , e.tileIndex ,
1018+ tileImg->x0 , tileImg->y0 , tileImg->x1 , tileImg->y1 , e.x0 , e.y0 , e.x1 , e.y1 );
1019+ std::filesystem::remove (testFile);
1020+ return false ;
1021+ }
1022+ // Also verify component dimensions match reduced tile size
1023+ uint32_t cw = tileImg->comps [0 ].w ;
1024+ uint32_t ch = tileImg->comps [0 ].h ;
1025+ if (cw != (e.x1 - e.x0 ) || ch != (e.y1 - e.y0 ))
1026+ {
1027+ spdlog::error (" FAIL: tile {} comp size {}x{} != expected {}x{}" , e.tileIndex , cw, ch,
1028+ e.x1 - e.x0 , e.y1 - e.y0 );
1029+ std::filesystem::remove (testFile);
1030+ return false ;
1031+ }
1032+ }
1033+
1034+ std::filesystem::remove (testFile);
1035+ spdlog::info (" PASS: Reduced-coords per-tile API" );
1036+ return true ;
1037+ }
1038+
9471039// /////////////////////////////////////////////////////////////////
9481040// Test 10: All tiles via decompressTile then re-read all
9491041//
@@ -2541,6 +2633,10 @@ int GrkLRUCacheTest::main(int argc, char** argv)
25412633 if (!testReduceWithRegion ())
25422634 failures++;
25432635
2636+ // Test 9b: Reduced-coords per-tile API
2637+ if (!testReducedCoordsPerTile ())
2638+ failures++;
2639+
25442640 // Test 10: All tiles via decompressTile then re-read
25452641 {
25462642 std::string interFile =
0 commit comments