From 1127eb04c1f4e44a5db496e5d8f9f92af0a4a63f Mon Sep 17 00:00:00 2001 From: ptahmose Date: Fri, 13 Dec 2024 02:08:58 +0100 Subject: [PATCH] introduce "frame-of-reference" (#123) * Add coordinate systems documentation and diagram Added a new documentation file `coordinate_systems.markdown` to the Doxyfile's INPUT section. Introduced a "Coordinate Systems" section in the new file with a header and placeholder description for CZI and libCZI coordinate systems. Also, added a new diagram in `coordinate_systems.drawio` named "Page-1" that includes graphical elements representing a coordinate system with labeled axes. * Update coordinate systems documentation in libCZI * Improve documentation clarity for coordinate systems Rephrased a sentence in `coordinate_systems.markdown` for clarity. Updated comments in `libCZI.h` to clarify bounding boxes and coordinate systems. Enhanced `libCZI_Compositor.h` comments to specify ROI and positions in the 'raw-subblock-coordinate-system'. Removed summary tags and redundant information to streamline documentation. These changes provide clearer and more precise descriptions of coordinate systems and bounding boxes in libCZI. * Update project to version 0.62.7 Updated CMakeLists.txt to change project version from 0.62.6 to 0.62.7. Added a new entry in version-history.markdown for version 0.62.7, documenting a "documentation update" linked to pull request #122. * remove "high-res-bitmaps" * wip * fix table * cosmetic * Refactor coordinate transformation methods Added TransformPoint to CCZIReader for point transformations. Updated CZIReader.h to declare TransformPoint method. Modified CSingleChannelTileAccessor::Get to use TransformRectangle. Added TransformRectangle to ISubBlockRepository in libCZI.h. Introduced IntPoint and IntPointAndFrameOfReference structures. Removed ConvertToFrameOfReference function from libCZI_Utilities. Minor formatting changes in libCZI.h and libCZI_Utilities.h. Improved code design, readability, and maintainability. * Update TransformPoint to return IntPointAndFrameOfReference Updated TransformPoint method to return libCZI::IntPointAndFrameOfReference instead of libCZI::IntPoint. This change is reflected across various classes and interfaces, including CCZIReader, CziReaderWriter, and libCZI::ISubBlockRepository. Added a new member variable default_frame_of_reference to CCZIReader and libCZI::Options to handle cases where the frame of reference is set to Default. Updated TransformRectangle method to handle the new return type of TransformPoint. Implemented changes in test_TileAccessorCoverageOptimization.cpp to reflect the new return type. * Add frame of reference handling and related tests - Initialize `default_frame_of_reference` to `CZIFrameOfReference::Invalid` in `CCZIReader` constructor. - Update `CCZIReader::Open` to use a switch statement for `options->default_frame_of_reference`. - Modify `ISubBlockRepository::TransformRectangle` to return `IntRectAndFrameOfReference`. - Update `CSingleChannelTileAccessor::Get` to use the `rectangle` member of `IntRectAndFrameOfReference`. - Add `test_frame_of_reference_transform.cpp` with tests for point and rectangle transformations. - Include new test file in `libCZI_UnitTests` target in `CMakeLists.txt`. * linter * cosmetic * Refactor TransformPoint and add new tests Refactored `CCZIReader::TransformPoint` for robust frame-of-reference transformations. Updated `AccessorType` enum in `libCZI_Compositor.h` to use `std::uint8_t`. Corrected typo in `backGroundColor` comment. Added `Check2x2Gray8Bitmap` function and new tests in `test_frame_of_reference_transform.cpp` for point transformations and tile composite verification. * Add TransformPoint method and default frame of reference Implemented TransformPoint in CCziReaderWriter for frame-of-reference transformations. Added GetDefaultFrameOfReference method and updated ICziReaderWriterInfo interface. Fixed formatting issues and improved type safety. Added test case for TransformPoint. Updated documentation and constructor initialization. Handled unsupported transformations with exceptions. * Update bounding-box and transformation expectations Updated the comment to reflect the correct bounding-box (0, -1, 1, 1) and transformation result (0, -1) after removing subblocks. Adjusted the test assertion for the y-coordinate of the transformed point to expect -1 instead of 0. * Refactor and cleanup in SingleChannelTileAccessor Modified the constructor in SingleChannelTileAccessor.cpp to make roi_raw_sub_block_cs a const IntRect, ensuring immutability. Removed a commented-out Get method declaration in SingleChannelTileAccessor.h for improved readability. * Refactor to use IntPointAndFrameOfReference type Updated method signatures in SingleChannelTileAccessor.cpp and .h to use IntPointAndFrameOfReference instead of separate xPos and yPos parameters. Adjusted libCZI_Compositor.h to reflect these changes and added inline methods for backward compatibility. Updated ISingleChannelTileAccessor destructor to use override keyword for modern C++ standards adherence. * linter * Improve comment formatting in Z-order description Added a space after the period in the comment describing the Z-order behavior when `sortByM` is false. This change enhances readability and consistency of the comments in the codebase. * Refactor Get methods and update documentation - Updated `Get` methods in `CSingleChannelScalingTileAccessor` and `libCZI::ISingleChannelScalingTileAccessor` to use `IntRectAndFrameOfReference` instead of `IntRect`. - Added overloads to handle `IntRect` by converting to `IntRectAndFrameOfReference`. - Modified `InternalCalcSize` to use the transformed rectangle in the raw sub-block coordinate system. - Corrected typo in `Options` struct documentation ("back ground color" to "background color"). - Updated `SingleChannelScalingTileAccessorHandler` in `test_TileAccessorCoverageOptimization.cpp` to use a shared pointer to `ISingleChannelScalingTileAccessor`. - Added documentation to `libCZI::ISingleChannelScalingTileAccessor` interface for new `Get` methods and their parameters. * Refactor coordinate handling to use explicit frames Updated SingleChannelPyramidLevelTileAccessor to use IntRectAndFrameOfReference and IntPointAndFrameOfReference for coordinates, ensuring explicit frame of reference handling. Transformed input coordinates to RawSubBlockCoordinateSystem in Get methods for consistency. Updated InternalGet method accordingly. Modified libCZI_Compositor.h to reflect new method signatures and retained old methods with internal transformations. Improved clarity and correctness of coordinate handling. * Refactor ROI coordinate calculation and formatting Updated ROI calculation to use boundingBoxLayer0Only instead of boundingBox for relative coordinates in execute.cpp and executeBase.cpp. Applied consistent changes across multiple sections. Added spaces after commas in ROI initialization for better readability. * Update project to version 0.63.0 Updated CMakeLists.txt to change project version from 0.62.7 to 0.63.0. Added a new entry in version-history.markdown for version 0.63.0, documenting the introduction of "frames-of-reference". The entry for version 0.62.7 remains unchanged. * add documentation * fix typos * Update Src/CZICmd/execute.cpp Co-authored-by: m-aXimilian <56168660+m-aXimilian@users.noreply.github.com> * Update Src/libCZI/libCZI_ReadWrite.h Co-authored-by: m-aXimilian <56168660+m-aXimilian@users.noreply.github.com> * Simplify return statements with brace-enclosed initializer Simplified the return statements in two conditional blocks by removing the explicit type `libCZI::IntPointAndFrameOfReference` and using the brace-enclosed initializer list directly. This change makes the code more concise and leverages the compiler's ability to deduce the return type. --------- Co-authored-by: m-aXimilian <56168660+m-aXimilian@users.noreply.github.com> --- CMakeLists.txt | 2 +- Src/CZICmd/execute.cpp | 14 +- Src/CZICmd/executeBase.cpp | 4 +- Src/libCZI/CZIReader.cpp | 69 +++- Src/libCZI/CZIReader.h | 2 + Src/libCZI/CziReaderWriter.cpp | 80 ++++- Src/libCZI/CziReaderWriter.h | 2 + Src/libCZI/Doc/version-history.markdown | 3 +- .../SingleChannelPyramidLevelTileAccessor.cpp | 16 +- .../SingleChannelPyramidLevelTileAccessor.h | 6 +- .../SingleChannelScalingTileAccessor.cpp | 18 +- Src/libCZI/SingleChannelScalingTileAccessor.h | 6 +- Src/libCZI/SingleChannelTileAccessor.cpp | 14 +- Src/libCZI/SingleChannelTileAccessor.h | 6 +- Src/libCZI/libCZI.h | 32 +- Src/libCZI/libCZI_Compositor.h | 193 +++++++++-- Src/libCZI/libCZI_Pixels.h | 36 +- Src/libCZI/libCZI_ReadWrite.h | 26 +- Src/libCZI/libCZI_Utilities.cpp | 4 +- Src/libCZI_UnitTests/CMakeLists.txt | 3 +- .../test_TileAccessorCoverageOptimization.cpp | 8 +- .../test_frame_of_reference_transform.cpp | 324 ++++++++++++++++++ 22 files changed, 764 insertions(+), 104 deletions(-) create mode 100644 Src/libCZI_UnitTests/test_frame_of_reference_transform.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b92d5ce1..3c569ec4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.15) cmake_policy(SET CMP0091 NEW) # enable new "MSVC runtime library selection" (https://cmake.org/cmake/help/latest/variable/CMAKE_MSVC_RUNTIME_LIBRARY.html) project(libCZI - VERSION 0.62.7 + VERSION 0.63.0 HOMEPAGE_URL "https://github.com/ZEISS/libczi" DESCRIPTION "libCZI is an Open Source Cross-Platform C++ library to read and write CZI") diff --git a/Src/CZICmd/execute.cpp b/Src/CZICmd/execute.cpp index 27592515..b79b7a9c 100644 --- a/Src/CZICmd/execute.cpp +++ b/Src/CZICmd/execute.cpp @@ -556,12 +556,12 @@ class CExecuteSingleChannelTileAccessor : CExecuteBase sctaOptions.sceneFilter = options.GetSceneIndexSet(); sctaOptions.useVisibilityCheckOptimization = options.GetUseVisibilityCheckOptimization(); - IntRect roi{ options.GetRectX() ,options.GetRectY(),options.GetRectW(),options.GetRectH() }; + IntRect roi{ options.GetRectX(), options.GetRectY(), options.GetRectW(), options.GetRectH() }; if (options.GetIsRelativeRectCoordinate()) { auto statistics = spReader->GetStatistics(); - roi.x += statistics.boundingBox.x; - roi.y += statistics.boundingBox.y; + roi.x += statistics.boundingBoxLayer0Only.x; + roi.y += statistics.boundingBoxLayer0Only.y; } auto re = accessor->Get(roi, &coordinate, &sctaOptions); @@ -676,8 +676,8 @@ class CExecuteChannelComposite : CExecuteBase IntRect roi{ options.GetRectX() ,options.GetRectY() ,options.GetRectW(),options.GetRectH() }; if (options.GetIsRelativeRectCoordinate()) { - roi.x += subBlockStatistics.boundingBox.x; - roi.y += subBlockStatistics.boundingBox.y; + roi.x += subBlockStatistics.boundingBoxLayer0Only.x; + roi.y += subBlockStatistics.boundingBoxLayer0Only.y; } auto accessor = reader->CreateSingleChannelTileAccessor(); @@ -861,8 +861,8 @@ class CExecuteScalingChannelComposite : CExecuteBase IntRect roi{ options.GetRectX() ,options.GetRectY() ,options.GetRectW(),options.GetRectH() }; if (options.GetIsRelativeRectCoordinate()) { - roi.x += subBlockStatistics.boundingBox.x; - roi.y += subBlockStatistics.boundingBox.y; + roi.x += subBlockStatistics.boundingBoxLayer0Only.x; + roi.y += subBlockStatistics.boundingBoxLayer0Only.y; } auto accessor = reader->CreateSingleChannelScalingTileAccessor(); diff --git a/Src/CZICmd/executeBase.cpp b/Src/CZICmd/executeBase.cpp index 216ce2c7..154e37d7 100644 --- a/Src/CZICmd/executeBase.cpp +++ b/Src/CZICmd/executeBase.cpp @@ -61,8 +61,8 @@ IntRect CExecuteBase::GetRoiFromOptions(const CCmdLineOptions& options, const Su IntRect roi{ options.GetRectX(), options.GetRectY(), options.GetRectW(), options.GetRectH() }; if (options.GetIsRelativeRectCoordinate()) { - roi.x += subBlockStatistics.boundingBox.x; - roi.y += subBlockStatistics.boundingBox.y; + roi.x += subBlockStatistics.boundingBoxLayer0Only.x; + roi.y += subBlockStatistics.boundingBoxLayer0Only.y; } return roi; diff --git a/Src/libCZI/CZIReader.cpp b/Src/libCZI/CZIReader.cpp index cb19dd10..2f6cccc4 100644 --- a/Src/libCZI/CZIReader.cpp +++ b/Src/libCZI/CZIReader.cpp @@ -32,7 +32,7 @@ static CCZIParse::SubblockDirectoryParseOptions GetParseOptionsFromOpenOptions(c return parse_options; } -CCZIReader::CCZIReader() : isOperational(false) +CCZIReader::CCZIReader() : isOperational(false), default_frame_of_reference(CZIFrameOfReference::Invalid) { } @@ -60,6 +60,17 @@ CCZIReader::CCZIReader() : isOperational(false) } this->stream = stream; + switch (options->default_frame_of_reference) + { + case CZIFrameOfReference::Invalid: + case CZIFrameOfReference::Default: + this->default_frame_of_reference = CZIFrameOfReference::RawSubBlockCoordinateSystem; + break; + default: + this->default_frame_of_reference = options->default_frame_of_reference; + break; + } + this->SetOperationalState(true); } @@ -96,6 +107,58 @@ CCZIReader::CCZIReader() : isOperational(false) return this->subBlkDir.GetPyramidStatistics(); } +/*virtual*/libCZI::IntPointAndFrameOfReference CCZIReader::TransformPoint(const libCZI::IntPointAndFrameOfReference& source_point, libCZI::CZIFrameOfReference destination_frame_of_reference) +{ + CZIFrameOfReference source_frame_of_reference_consolidated; + switch (source_point.frame_of_reference) + { + case CZIFrameOfReference::RawSubBlockCoordinateSystem: + case CZIFrameOfReference::PixelCoordinateSystem: + source_frame_of_reference_consolidated = source_point.frame_of_reference; + break; + case CZIFrameOfReference::Default: + source_frame_of_reference_consolidated = this->default_frame_of_reference; + break; + default: + throw invalid_argument("Unsupported frame-of-reference."); + } + + CZIFrameOfReference destination_frame_of_reference_consolidated; + switch (destination_frame_of_reference) + { + case CZIFrameOfReference::RawSubBlockCoordinateSystem: + case CZIFrameOfReference::PixelCoordinateSystem: + destination_frame_of_reference_consolidated = destination_frame_of_reference; + break; + case CZIFrameOfReference::Default: + destination_frame_of_reference_consolidated = this->default_frame_of_reference; + break; + default: + throw invalid_argument("Unsupported frame-of-reference."); + } + + if (destination_frame_of_reference_consolidated == source_frame_of_reference_consolidated) + { + return { source_frame_of_reference_consolidated, source_point.point }; + } + + if (source_frame_of_reference_consolidated == CZIFrameOfReference::PixelCoordinateSystem && + destination_frame_of_reference_consolidated == CZIFrameOfReference::RawSubBlockCoordinateSystem) + { + const auto& statistics = this->subBlkDir.GetStatistics(); + return { CZIFrameOfReference::RawSubBlockCoordinateSystem, {source_point.point.x + statistics.boundingBoxLayer0Only.x, source_point.point.y + statistics.boundingBoxLayer0Only.y} }; + } + + if (source_frame_of_reference_consolidated == CZIFrameOfReference::RawSubBlockCoordinateSystem && + destination_frame_of_reference_consolidated == CZIFrameOfReference::PixelCoordinateSystem) + { + const auto& statistics = this->subBlkDir.GetStatistics(); + return { CZIFrameOfReference::PixelCoordinateSystem, {source_point.point.x - statistics.boundingBoxLayer0Only.x, source_point.point.y - statistics.boundingBoxLayer0Only.y} }; + } + + throw logic_error("Unsupported frame-of-reference transformation."); +} + /*virtual*/void CCZIReader::EnumerateSubBlocks(const std::function& funcEnum) { this->ThrowIfNotOperational(); @@ -205,8 +268,8 @@ CCZIReader::CCZIReader() : isOperational(false) this->ThrowIfNotOperational(); CziReaderCommon::EnumerateSubset( std::bind(&CCziAttachmentsDirectory::EnumAttachments, &this->attachmentDir, std::placeholders::_1), - contentFileType, - name, + contentFileType, + name, funcEnum); } diff --git a/Src/libCZI/CZIReader.h b/Src/libCZI/CZIReader.h index 8acf62f2..fc43c4ab 100644 --- a/Src/libCZI/CZIReader.h +++ b/Src/libCZI/CZIReader.h @@ -21,6 +21,7 @@ class CCZIReader : public libCZI::ICZIReader, public std::enable_shared_from_thi CCziSubBlockDirectory subBlkDir; CCziAttachmentsDirectory attachmentDir; bool isOperational; ///< If true, then stream, hdrSegmentData and subBlkDir can be considered valid and operational + libCZI::CZIFrameOfReference default_frame_of_reference; public: CCZIReader(); ~CCZIReader() override = default; @@ -33,6 +34,7 @@ class CCZIReader : public libCZI::ICZIReader, public std::enable_shared_from_thi bool TryGetSubBlockInfo(int index, libCZI::SubBlockInfo* info) const override; libCZI::SubBlockStatistics GetStatistics() override; libCZI::PyramidStatistics GetPyramidStatistics() override; + libCZI::IntPointAndFrameOfReference TransformPoint(const libCZI::IntPointAndFrameOfReference& source_point, libCZI::CZIFrameOfReference destination_frame_of_reference) override; // interface ISubBlockRepositoryEx void EnumerateSubBlocksEx(const std::function& funcEnum) override; diff --git a/Src/libCZI/CziReaderWriter.cpp b/Src/libCZI/CziReaderWriter.cpp index c8241179..1b375502 100644 --- a/Src/libCZI/CziReaderWriter.cpp +++ b/Src/libCZI/CziReaderWriter.cpp @@ -33,7 +33,8 @@ struct ReplaceHelper int key; ICziReaderWriter* t; ReplaceHelper(int key, ICziReaderWriter* t) - :key(key), t(t) {} + :key(key), t(t) + {} void operator()(const AddSubBlockInfo& addSbBlkInfo) const { @@ -60,17 +61,17 @@ void ICziReaderWriter::SyncAddSubBlock(const libCZI::AddSubBlockInfoStridedBitma void ICziReaderWriter::ReplaceSubBlock(int key, const libCZI::AddSubBlockInfoMemPtr& addSbBlkInfoMemPtr) { struct ReplaceHelper f(key, this); - AddSubBlockHelper::SyncAddSubBlock(f, addSbBlkInfoMemPtr); + AddSubBlockHelper::SyncAddSubBlock(f, addSbBlkInfoMemPtr); } void ICziReaderWriter::ReplaceSubBlock(int key, const libCZI::AddSubBlockInfoLinewiseBitmap& addSbInfoLinewise) { struct ReplaceHelper f(key, this); - AddSubBlockHelper::SyncAddSubBlock(f, addSbInfoLinewise); + AddSubBlockHelper::SyncAddSubBlock(f, addSbInfoLinewise); } void ICziReaderWriter::ReplaceSubBlock(int key, const libCZI::AddSubBlockInfoStridedBitmap& addSbBlkInfoStrideBitmap) { struct ReplaceHelper f(key, this); - AddSubBlockHelper::SyncAddSubBlock(f, addSbBlkInfoStrideBitmap); + AddSubBlockHelper::SyncAddSubBlock(f, addSbBlkInfoStrideBitmap); } //-------------------------------------------------------------------------------------------------------- @@ -335,7 +336,7 @@ void CCziReaderWriter::Finish() void CCziReaderWriter::UpdateFileHeader() { - FileHeaderSegment fhs = { 0 }; + FileHeaderSegment fhs = {}; fhs.header.AllocatedSize = fhs.header.UsedSize = sizeof(fhs.data); memcpy(&fhs.header.Id, &CCZIParse::FILEHDRMAGIC, 16); @@ -430,7 +431,7 @@ void CCziReaderWriter::ReadCziStructure() else { // if there is no valid CZI-file-header, we now write one - FileHeaderSegment fhs = { 0 }; + FileHeaderSegment fhs = {}; fhs.header.AllocatedSize = fhs.header.UsedSize = sizeof(fhs.data); memcpy(&fhs.header.Id, &CCZIParse::FILEHDRMAGIC, 16); @@ -489,7 +490,7 @@ void CCziReaderWriter::DetermineNextSubBlockOffset() this->attachmentDirectory.EnumEntries( [&](size_t index, const CCziAttachmentsDirectoryBase::AttachmentEntry& attEntry)->bool { - if (uint64_t(attEntry.FilePosition) > lastSegmentPos) + if (static_cast(attEntry.FilePosition) > lastSegmentPos) { lastSegmentPos = attEntry.FilePosition; } @@ -712,6 +713,58 @@ void CCziReaderWriter::WriteToOutputStream(std::uint64_t offset, const void* pv, return this->sbBlkDirectory.GetPyramidStatistics(); } +/*virtual*/libCZI::IntPointAndFrameOfReference CCziReaderWriter::TransformPoint(const libCZI::IntPointAndFrameOfReference& source_point, libCZI::CZIFrameOfReference destination_frame_of_reference) +{ + CZIFrameOfReference source_frame_of_reference_consolidated; + switch (source_point.frame_of_reference) + { + case CZIFrameOfReference::RawSubBlockCoordinateSystem: + case CZIFrameOfReference::PixelCoordinateSystem: + source_frame_of_reference_consolidated = source_point.frame_of_reference; + break; + case CZIFrameOfReference::Default: + source_frame_of_reference_consolidated = this->GetDefaultFrameOfReference(); + break; + default: + throw invalid_argument("Unsupported frame-of-reference."); + } + + CZIFrameOfReference destination_frame_of_reference_consolidated; + switch (destination_frame_of_reference) + { + case CZIFrameOfReference::RawSubBlockCoordinateSystem: + case CZIFrameOfReference::PixelCoordinateSystem: + destination_frame_of_reference_consolidated = destination_frame_of_reference; + break; + case CZIFrameOfReference::Default: + destination_frame_of_reference_consolidated = this->GetDefaultFrameOfReference(); + break; + default: + throw invalid_argument("Unsupported frame-of-reference."); + } + + if (destination_frame_of_reference_consolidated == source_frame_of_reference_consolidated) + { + return { source_frame_of_reference_consolidated, source_point.point }; + } + + if (source_frame_of_reference_consolidated == CZIFrameOfReference::PixelCoordinateSystem && + destination_frame_of_reference_consolidated == CZIFrameOfReference::RawSubBlockCoordinateSystem) + { + const auto& statistics = this->GetStatistics(); + return libCZI::IntPointAndFrameOfReference{ CZIFrameOfReference::RawSubBlockCoordinateSystem, {source_point.point.x + statistics.boundingBoxLayer0Only.x, source_point.point.y + statistics.boundingBoxLayer0Only.y} }; + } + + if (source_frame_of_reference_consolidated == CZIFrameOfReference::RawSubBlockCoordinateSystem && + destination_frame_of_reference_consolidated == CZIFrameOfReference::PixelCoordinateSystem) + { + const auto& statistics = this->GetStatistics(); + return libCZI::IntPointAndFrameOfReference{ CZIFrameOfReference::PixelCoordinateSystem, {source_point.point.x - statistics.boundingBoxLayer0Only.x, source_point.point.y - statistics.boundingBoxLayer0Only.y} }; + } + + throw logic_error("Unsupported frame-of-reference transformation."); +} + /*virtual*/void CCziReaderWriter::EnumerateAttachments(const std::function& funcEnum) { this->ThrowIfNotOperational(); @@ -874,3 +927,16 @@ void CCziReaderWriter::ThrowIfAlreadyInitialized() const throw logic_error("CCziReaderWriter is already operational."); } } + +libCZI::CZIFrameOfReference CCziReaderWriter::GetDefaultFrameOfReference() const +{ + const auto default_frame_of_reference_from_info = this->info->GetDefaultFrameOfReference(); + switch (default_frame_of_reference_from_info) + { + case CZIFrameOfReference::RawSubBlockCoordinateSystem: + case CZIFrameOfReference::PixelCoordinateSystem: + return default_frame_of_reference_from_info; + default: + return CZIFrameOfReference::RawSubBlockCoordinateSystem; + } +} diff --git a/Src/libCZI/CziReaderWriter.h b/Src/libCZI/CziReaderWriter.h index 8cf21c10..08536b51 100644 --- a/Src/libCZI/CziReaderWriter.h +++ b/Src/libCZI/CziReaderWriter.h @@ -47,6 +47,7 @@ class CCziReaderWriter : public libCZI::ICziReaderWriter bool TryGetSubBlockInfo(int index, libCZI::SubBlockInfo* info) const override; libCZI::SubBlockStatistics GetStatistics() override; libCZI::PyramidStatistics GetPyramidStatistics() override; + libCZI::IntPointAndFrameOfReference TransformPoint(const libCZI::IntPointAndFrameOfReference& source_point, libCZI::CZIFrameOfReference destination_frame_of_reference) override; // interface IAttachmentRepository void EnumerateAttachments(const std::function& funcEnum) override; @@ -80,6 +81,7 @@ class CCziReaderWriter : public libCZI::ICziReaderWriter void UpdateFileHeader(); void ThrowIfNotOperational() const; void ThrowIfAlreadyInitialized() const; + libCZI::CZIFrameOfReference GetDefaultFrameOfReference() const; private: class CNextSegment { diff --git a/Src/libCZI/Doc/version-history.markdown b/Src/libCZI/Doc/version-history.markdown index 218e0ec5..79785202 100644 --- a/Src/libCZI/Doc/version-history.markdown +++ b/Src/libCZI/Doc/version-history.markdown @@ -33,4 +33,5 @@ version history {#version_history} 0.62.4 | [117](https://github.com/ZEISS/libczi/pull/117) | fix build with private RapidJSON library 0.62.5 | [119](https://github.com/ZEISS/libczi/pull/119) | fix a discrepancy between code and documentation 0.62.6 | [120](https://github.com/ZEISS/libczi/pull/120) | fix workload identity in the azure blob inputstream - 0.62.7 | [122](https://github.com/ZEISS/libczi/pull/122) | documentation update \ No newline at end of file + 0.62.7 | [122](https://github.com/ZEISS/libczi/pull/122) | documentation update + 0.63.0 | [123](https://github.com/ZEISS/libczi/pull/123) | introduce "frames-of-reference" \ No newline at end of file diff --git a/Src/libCZI/SingleChannelPyramidLevelTileAccessor.cpp b/Src/libCZI/SingleChannelPyramidLevelTileAccessor.cpp index 50a0b50f..b536774d 100644 --- a/Src/libCZI/SingleChannelPyramidLevelTileAccessor.cpp +++ b/Src/libCZI/SingleChannelPyramidLevelTileAccessor.cpp @@ -15,7 +15,7 @@ CSingleChannelPyramidLevelTileAccessor::CSingleChannelPyramidLevelTileAccessor(c { } -/*virtual*/std::shared_ptr CSingleChannelPyramidLevelTileAccessor::Get(const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const ISingleChannelPyramidLayerTileAccessor::Options* pOptions) +/*virtual*/std::shared_ptr CSingleChannelPyramidLevelTileAccessor::Get(const libCZI::IntRectAndFrameOfReference& roi, const libCZI::IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const ISingleChannelPyramidLayerTileAccessor::Options* pOptions) { if (pOptions == nullptr) { @@ -34,7 +34,7 @@ CSingleChannelPyramidLevelTileAccessor::CSingleChannelPyramidLevelTileAccessor(c return this->Get(pixelType, roi, planeCoordinate, pyramidInfo, pOptions); } -/*virtual*/std::shared_ptr CSingleChannelPyramidLevelTileAccessor::Get(libCZI::PixelType pixeltype, const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const libCZI::ISingleChannelPyramidLayerTileAccessor::Options* pOptions) +/*virtual*/std::shared_ptr CSingleChannelPyramidLevelTileAccessor::Get(libCZI::PixelType pixeltype, const libCZI::IntRectAndFrameOfReference& roi, const libCZI::IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const libCZI::ISingleChannelPyramidLayerTileAccessor::Options* pOptions) { if (pOptions == nullptr) { @@ -43,8 +43,9 @@ CSingleChannelPyramidLevelTileAccessor::CSingleChannelPyramidLevelTileAccessor(c return this->Get(pixeltype, roi, planeCoordinate, pyramidInfo, &opt); } + const IntRect roi_raw_sub_block_cs = this->sbBlkRepository->TransformRectangle(roi, CZIFrameOfReference::RawSubBlockCoordinateSystem).rectangle; const int sizeOfPixel = CalcSizeOfPixelOnLayer0(pyramidInfo); - const IntSize sizeOfBitmap{ static_cast(roi.w / sizeOfPixel),static_cast(roi.h / sizeOfPixel) }; + const IntSize sizeOfBitmap{ static_cast(roi_raw_sub_block_cs.w / sizeOfPixel),static_cast(roi_raw_sub_block_cs.h / sizeOfPixel) }; if (sizeOfBitmap.w == 0 || sizeOfBitmap.h == 0) { // TODO @@ -52,22 +53,23 @@ CSingleChannelPyramidLevelTileAccessor::CSingleChannelPyramidLevelTileAccessor(c } auto bmDest = GetSite()->CreateBitmap(pixeltype, sizeOfBitmap.w, sizeOfBitmap.h); - this->InternalGet(bmDest.get(), roi.x, roi.y, sizeOfPixel, planeCoordinate, pyramidInfo, *pOptions); + this->InternalGet(bmDest.get(), roi_raw_sub_block_cs.x, roi_raw_sub_block_cs.y, sizeOfPixel, planeCoordinate, pyramidInfo, *pOptions); return bmDest; } -/*virtual*/void CSingleChannelPyramidLevelTileAccessor::Get(libCZI::IBitmapData* pDest, int xPos, int yPos, const libCZI::IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const Options* pOptions) +/*virtual*/void CSingleChannelPyramidLevelTileAccessor::Get(libCZI::IBitmapData* pDest, const libCZI::IntPointAndFrameOfReference& position, const libCZI::IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const Options* pOptions) { if (pOptions == nullptr) { Options opt; opt.Clear(); - this->Get(pDest, xPos, yPos, planeCoordinate, pyramidInfo, &opt); + this->Get(pDest, position, planeCoordinate, pyramidInfo, &opt); return; } + const IntPoint point_raw_sub_block_cs = this->sbBlkRepository->TransformPoint(position, CZIFrameOfReference::RawSubBlockCoordinateSystem).point; const int sizeOfPixel = CalcSizeOfPixelOnLayer0(pyramidInfo); - this->InternalGet(pDest, xPos, yPos, sizeOfPixel, planeCoordinate, pyramidInfo, *pOptions); + this->InternalGet(pDest, point_raw_sub_block_cs.x, point_raw_sub_block_cs.y, sizeOfPixel, planeCoordinate, pyramidInfo, *pOptions); } void CSingleChannelPyramidLevelTileAccessor::InternalGet(libCZI::IBitmapData* pDest, int xPos, int yPos, int sizeOfPixelOnLayer0, const libCZI::IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const Options& options) diff --git a/Src/libCZI/SingleChannelPyramidLevelTileAccessor.h b/Src/libCZI/SingleChannelPyramidLevelTileAccessor.h index 6f62ecfb..df608824 100644 --- a/Src/libCZI/SingleChannelPyramidLevelTileAccessor.h +++ b/Src/libCZI/SingleChannelPyramidLevelTileAccessor.h @@ -31,11 +31,11 @@ class CSingleChannelPyramidLevelTileAccessor : public CSingleChannelAccessorBase explicit CSingleChannelPyramidLevelTileAccessor(const std::shared_ptr& sbBlkRepository); public: // interface ISingleChannelTileAccessor - std::shared_ptr Get(const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const Options* pOptions) override; + std::shared_ptr Get(const libCZI::IntRectAndFrameOfReference& roi, const libCZI::IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const Options* pOptions) override; - std::shared_ptr Get(libCZI::PixelType pixeltype, const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const libCZI::ISingleChannelPyramidLayerTileAccessor::Options* pOptions) override; + std::shared_ptr Get(libCZI::PixelType pixeltype, const libCZI::IntRectAndFrameOfReference& roi, const libCZI::IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const libCZI::ISingleChannelPyramidLayerTileAccessor::Options* pOptions) override; - void Get(libCZI::IBitmapData* pDest, int xPos, int yPos, const libCZI::IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const Options* pOptions) override; + void Get(libCZI::IBitmapData* pDest, const libCZI::IntPointAndFrameOfReference& position, const libCZI::IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const Options* pOptions) override; private: libCZI::IntRect CalcDestinationRectFromSourceRect(const libCZI::IntRect& roi, const PyramidLayerInfo& pyramidInfo); diff --git a/Src/libCZI/SingleChannelScalingTileAccessor.cpp b/Src/libCZI/SingleChannelScalingTileAccessor.cpp index 95df5de3..6891ada8 100644 --- a/Src/libCZI/SingleChannelScalingTileAccessor.cpp +++ b/Src/libCZI/SingleChannelScalingTileAccessor.cpp @@ -20,7 +20,7 @@ CSingleChannelScalingTileAccessor::CSingleChannelScalingTileAccessor(const std:: return InternalCalcSize(roi, zoom); } -/*virtual*/ std::shared_ptr CSingleChannelScalingTileAccessor::Get(const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const libCZI::ISingleChannelScalingTileAccessor::Options* pOptions) +/*virtual*/ std::shared_ptr CSingleChannelScalingTileAccessor::Get(const libCZI::IntRectAndFrameOfReference& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const libCZI::ISingleChannelScalingTileAccessor::Options* pOptions) { if (pOptions == nullptr) { @@ -38,7 +38,7 @@ CSingleChannelScalingTileAccessor::CSingleChannelScalingTileAccessor(const std:: return this->Get(pixelType, roi, planeCoordinate, zoom, pOptions); } -/*virtual*/std::shared_ptr CSingleChannelScalingTileAccessor::Get(libCZI::PixelType pixeltype, const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const libCZI::ISingleChannelScalingTileAccessor::Options* pOptions) +/*virtual*/std::shared_ptr CSingleChannelScalingTileAccessor::Get(libCZI::PixelType pixeltype, const libCZI::IntRectAndFrameOfReference& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const libCZI::ISingleChannelScalingTileAccessor::Options* pOptions) { if (pOptions == nullptr) { @@ -46,13 +46,14 @@ CSingleChannelScalingTileAccessor::CSingleChannelScalingTileAccessor(const std:: return this->Get(pixeltype, roi, planeCoordinate, zoom, &opt); } - const IntSize sizeOfBitmap = InternalCalcSize(roi, zoom); + const IntRect roi_raw_sub_block_cs = this->sbBlkRepository->TransformRectangle(roi, CZIFrameOfReference::RawSubBlockCoordinateSystem).rectangle; + const IntSize sizeOfBitmap = InternalCalcSize(roi_raw_sub_block_cs, zoom); auto bmDest = GetSite()->CreateBitmap(pixeltype, sizeOfBitmap.w, sizeOfBitmap.h); - this->InternalGet(bmDest.get(), roi, planeCoordinate, zoom, *pOptions); + this->InternalGet(bmDest.get(), roi_raw_sub_block_cs, planeCoordinate, zoom, *pOptions); return bmDest; } -/*virtual*/void CSingleChannelScalingTileAccessor::Get(libCZI::IBitmapData* pDest, const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const libCZI::ISingleChannelScalingTileAccessor::Options* pOptions) +/*virtual*/void CSingleChannelScalingTileAccessor::Get(libCZI::IBitmapData* pDest, const libCZI::IntRectAndFrameOfReference& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const libCZI::ISingleChannelScalingTileAccessor::Options* pOptions) { if (pOptions == nullptr) { @@ -60,7 +61,8 @@ CSingleChannelScalingTileAccessor::CSingleChannelScalingTileAccessor(const std:: return this->Get(pDest, roi, planeCoordinate, zoom, &opt); } - const IntSize sizeOfBitmap = InternalCalcSize(roi, zoom); + const IntRect roi_raw_sub_block_cs = this->sbBlkRepository->TransformRectangle(roi, CZIFrameOfReference::RawSubBlockCoordinateSystem).rectangle; + const IntSize sizeOfBitmap = InternalCalcSize(roi_raw_sub_block_cs, zoom); if (sizeOfBitmap.w != pDest->GetWidth() || sizeOfBitmap.h != pDest->GetHeight()) { stringstream ss; @@ -68,7 +70,7 @@ CSingleChannelScalingTileAccessor::CSingleChannelScalingTileAccessor(const std:: throw invalid_argument(ss.str().c_str()); } - this->InternalGet(pDest, roi, planeCoordinate, zoom, *pOptions); + this->InternalGet(pDest, roi_raw_sub_block_cs, planeCoordinate, zoom, *pOptions); } // ---------------------------------------------------------------------------------------------------------------------- @@ -164,7 +166,7 @@ int CSingleChannelScalingTileAccessor::GetIdxOf1stSubBlockWithZoomGreater(const } /// -/// Create an vector with indices (into the specified vector with SubBlock-infos) so that the indices give +/// Create a vector with indices (into the specified vector with SubBlock-infos) so that the indices give /// the items sorted by their "zoom"-factor. (A zoom of "1" means that the subblock is on layer-0). Subblocks of /// a higher pyramid-layer are at the end of the list. /// diff --git a/Src/libCZI/SingleChannelScalingTileAccessor.h b/Src/libCZI/SingleChannelScalingTileAccessor.h index 5c852e88..7c0fbfc0 100644 --- a/Src/libCZI/SingleChannelScalingTileAccessor.h +++ b/Src/libCZI/SingleChannelScalingTileAccessor.h @@ -29,9 +29,9 @@ class CSingleChannelScalingTileAccessor : public CSingleChannelAccessorBase, pub public: // interface ISingleChannelScalingTileAccessor libCZI::IntSize CalcSize(const libCZI::IntRect& roi, float zoom) const override; - std::shared_ptr Get(const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const libCZI::ISingleChannelScalingTileAccessor::Options* pOptions) override; - std::shared_ptr Get(libCZI::PixelType pixeltype, const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const libCZI::ISingleChannelScalingTileAccessor::Options* pOptions) override; - void Get(libCZI::IBitmapData* pDest, const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const libCZI::ISingleChannelScalingTileAccessor::Options* pOptions) override; + std::shared_ptr Get(const libCZI::IntRectAndFrameOfReference& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const libCZI::ISingleChannelScalingTileAccessor::Options* pOptions) override; + std::shared_ptr Get(libCZI::PixelType pixeltype, const libCZI::IntRectAndFrameOfReference& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const libCZI::ISingleChannelScalingTileAccessor::Options* pOptions) override; + void Get(libCZI::IBitmapData* pDest, const libCZI::IntRectAndFrameOfReference& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const libCZI::ISingleChannelScalingTileAccessor::Options* pOptions) override; private: static libCZI::IntSize InternalCalcSize(const libCZI::IntRect& roi, float zoom); diff --git a/Src/libCZI/SingleChannelTileAccessor.cpp b/Src/libCZI/SingleChannelTileAccessor.cpp index 02a44a57..36da0cf6 100644 --- a/Src/libCZI/SingleChannelTileAccessor.cpp +++ b/Src/libCZI/SingleChannelTileAccessor.cpp @@ -17,7 +17,7 @@ CSingleChannelTileAccessor::CSingleChannelTileAccessor(const std::shared_ptr CSingleChannelTileAccessor::Get(const libCZI::IntRect& roi, const IDimCoordinate* planeCoordinate, const Options* pOptions) +/*virtual*/std::shared_ptr CSingleChannelTileAccessor::Get(const libCZI::IntRectAndFrameOfReference& roi, const IDimCoordinate* planeCoordinate, const Options* pOptions) { // first, we need to determine the pixeltype, which we do from the repository libCZI::PixelType pixelType; @@ -30,16 +30,18 @@ CSingleChannelTileAccessor::CSingleChannelTileAccessor(const std::shared_ptrGet(pixelType, roi, planeCoordinate, pOptions); } -/*virtual*/std::shared_ptr CSingleChannelTileAccessor::Get(libCZI::PixelType pixeltype, const libCZI::IntRect& roi, const IDimCoordinate* planeCoordinate, const Options* pOptions) +/*virtual*/std::shared_ptr CSingleChannelTileAccessor::Get(libCZI::PixelType pixeltype, const libCZI::IntRectAndFrameOfReference& roi, const IDimCoordinate* planeCoordinate, const Options* pOptions) { - auto bmDest = GetSite()->CreateBitmap(pixeltype, roi.w, roi.h); - this->InternalGet(roi.x, roi.y, bmDest.get(), planeCoordinate, pOptions); + const IntRect roi_raw_sub_block_cs = this->sbBlkRepository->TransformRectangle(roi, CZIFrameOfReference::RawSubBlockCoordinateSystem).rectangle; + auto bmDest = GetSite()->CreateBitmap(pixeltype, roi_raw_sub_block_cs.w, roi_raw_sub_block_cs.h); + this->InternalGet(roi_raw_sub_block_cs.x, roi_raw_sub_block_cs.y, bmDest.get(), planeCoordinate, pOptions); return bmDest; } -/*virtual*/void CSingleChannelTileAccessor::Get(libCZI::IBitmapData* pDest, int xPos, int yPos, const IDimCoordinate* planeCoordinate, const Options* pOptions) +/*virtual*/void CSingleChannelTileAccessor::Get(libCZI::IBitmapData* pDest, const IntPointAndFrameOfReference& position, const IDimCoordinate* planeCoordinate, const Options* pOptions) { - this->InternalGet(xPos, yPos, pDest, planeCoordinate, pOptions); + const IntPoint point_raw_sub_block_cs = this->sbBlkRepository->TransformPoint(position, CZIFrameOfReference::RawSubBlockCoordinateSystem).point; + this->InternalGet(point_raw_sub_block_cs.x, point_raw_sub_block_cs.y, pDest, planeCoordinate, pOptions); } void CSingleChannelTileAccessor::ComposeTiles(libCZI::IBitmapData* pBm, int xPos, int yPos, const std::vector& subBlocksSet, const ISingleChannelTileAccessor::Options& options) diff --git a/Src/libCZI/SingleChannelTileAccessor.h b/Src/libCZI/SingleChannelTileAccessor.h index b9007e3b..007550c0 100644 --- a/Src/libCZI/SingleChannelTileAccessor.h +++ b/Src/libCZI/SingleChannelTileAccessor.h @@ -16,9 +16,9 @@ class CSingleChannelTileAccessor : public CSingleChannelAccessorBase, public lib explicit CSingleChannelTileAccessor(const std::shared_ptr& sbBlkRepository); public: // interface ISingleChannelTileAccessor - std::shared_ptr Get(const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, const libCZI::ISingleChannelTileAccessor::Options* pOptions) override; - std::shared_ptr Get(libCZI::PixelType pixeltype, const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, const Options* pOptions) override; - void Get(libCZI::IBitmapData* pDest, int xPos, int yPos, const libCZI::IDimCoordinate* planeCoordinate, const Options* pOptions) override; + std::shared_ptr Get(const libCZI::IntRectAndFrameOfReference& roi, const libCZI::IDimCoordinate* planeCoordinate, const libCZI::ISingleChannelTileAccessor::Options* pOptions) override; + std::shared_ptr Get(libCZI::PixelType pixeltype, const libCZI::IntRectAndFrameOfReference& roi, const libCZI::IDimCoordinate* planeCoordinate, const Options* pOptions) override; + void Get(libCZI::IBitmapData* pDest, const libCZI::IntPointAndFrameOfReference& position, const libCZI::IDimCoordinate* planeCoordinate, const Options* pOptions) override; private: void InternalGet(int xPos, int yPos, libCZI::IBitmapData* pBm, const libCZI::IDimCoordinate* planeCoordinate, const libCZI::ISingleChannelTileAccessor::Options* pOptions); void GetAllSubBlocks(const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, const std::function& appender) const; diff --git a/Src/libCZI/libCZI.h b/Src/libCZI/libCZI.h index b07eada7..3758b895 100644 --- a/Src/libCZI/libCZI.h +++ b/Src/libCZI/libCZI.h @@ -587,7 +587,29 @@ namespace libCZI /// \return The pyramid statistics. virtual PyramidStatistics GetPyramidStatistics() = 0; + virtual libCZI::IntPointAndFrameOfReference TransformPoint(const libCZI::IntPointAndFrameOfReference& source_point, libCZI::CZIFrameOfReference destination_frame_of_reference) = 0; + virtual ~ISubBlockRepository() = default; + + libCZI::IntRectAndFrameOfReference TransformRectangle(const libCZI::IntRectAndFrameOfReference& source_rectangle, libCZI::CZIFrameOfReference destination_frame_of_reference) + { + libCZI::IntPointAndFrameOfReference source_point_and_frame_of_reference; + source_point_and_frame_of_reference.frame_of_reference = source_rectangle.frame_of_reference; + source_point_and_frame_of_reference.point = { source_rectangle.rectangle.x, source_rectangle.rectangle.y }; + libCZI::IntPoint transformed_point_upper_left = this->TransformPoint(source_point_and_frame_of_reference, destination_frame_of_reference).point; + source_point_and_frame_of_reference.point = { source_rectangle.rectangle.x + source_rectangle.rectangle.w, source_rectangle.rectangle.y + source_rectangle.rectangle.h }; + libCZI::IntPointAndFrameOfReference transformed_point_lower_right = this->TransformPoint(source_point_and_frame_of_reference, destination_frame_of_reference); + return + { + transformed_point_lower_right.frame_of_reference, + { + transformed_point_upper_left.x, + transformed_point_upper_left.y, + transformed_point_lower_right.point.x - transformed_point_upper_left.x, + transformed_point_lower_right.point.y - transformed_point_upper_left.y + } + }; + } }; /// Additional functionality for the subblock-repository, providing some specialized and not commonly used functionality. @@ -667,12 +689,20 @@ namespace libCZI /// This is useful as some versions of software creating CZI-files used to write bogus values for size-M, and those files /// would otherwise not be usable with strict validation enabled. If this bogus size-M is ignored, then the files can be used /// without problems. - bool ignore_sizem_for_pyramid_subblocks{ false }; + bool ignore_sizem_for_pyramid_subblocks{ false }; + + /// The default frame-of-reference which is to be used by the reader-object. This determines which frame-of-reference + /// is used when the enum value "CZIFrameOfReference::Default" is used with an operation of the reader-object. + /// If the value specified here is "CZIFrameOfReference::Invalid" or "CZIFrameOfReference::Default", then + /// "CZIFrameOfReference::RawSubBlockCoordinateSystem" will be used. + libCZI::CZIFrameOfReference default_frame_of_reference{ libCZI::CZIFrameOfReference::Invalid }; /// Sets the the default. void SetDefault() { this->lax_subblock_coordinate_checks = true; + this->ignore_sizem_for_pyramid_subblocks = false; + this->default_frame_of_reference = libCZI::CZIFrameOfReference::Invalid; } }; diff --git a/Src/libCZI/libCZI_Compositor.h b/Src/libCZI/libCZI_Compositor.h index f7864777..cc99663f 100644 --- a/Src/libCZI/libCZI_Compositor.h +++ b/Src/libCZI/libCZI_Compositor.h @@ -18,10 +18,10 @@ namespace libCZI class IDimCoordinate; /// Values that represent the accessor types. - enum class AccessorType + enum class AccessorType : std::uint8_t { SingleChannelTileAccessor, ///< The single-channel-tile accessor (associated interface: ISingleChannelTileAccessor). - SingleChannelPyramidLayerTileAccessor, ///< The single-channel-pyramidlayer-tile accessor (associated interface: ISingleChannelPyramidLayerTileAccessor). + SingleChannelPyramidLayerTileAccessor, ///< The single-channel-pyramid-layer-tile accessor (associated interface: ISingleChannelPyramidLayerTileAccessor). SingleChannelScalingTileAccessor ///< The scaling-single-channel-tile accessor (associated interface: ISingleChannelScalingTileAccessor). }; @@ -174,7 +174,7 @@ namespace libCZI /// Options for controlling the composition operation. struct Options { - /// The back ground color. If the destination bitmap is a grayscale-type, then the mean from R, G and B is calculated and multiplied + /// The background color. If the destination bitmap is a grayscale-type, then the mean from R, G and B is calculated and multiplied /// with the maximum pixel value (of the specific pixeltype). If it is a RGB-color type, then R, G and B are separately multiplied with /// the maximum pixel value. /// If any of R, G or B is NaN, then the background is not cleared. @@ -222,38 +222,34 @@ namespace libCZI public: /// Gets the tile composite of the specified plane and the specified ROI. The pixeltype is - /// determined by examing the first subblock found in the specified plane (which is an + /// determined by examining the first subblock found in the specified plane (which is an /// arbitrary subblock). A newly allocated bitmap is returned. /// - /// \param roi The ROI (given in _raw-subblock-coordinate-system_, c.f. @ref coordinatesystems). + /// \param roi The ROI and the coordinate system it is defined in. /// \param planeCoordinate The plane coordinate. /// \param pOptions Options for controlling the operation. /// /// \returns A std::shared_ptr containing the tile-composite. - /// - /// ### remarks It needs to be defined what is supposed to happen if there is no subblock found - /// in the specified plane. - virtual std::shared_ptr Get(const libCZI::IntRect& roi, const IDimCoordinate* planeCoordinate, const Options* pOptions) = 0; + virtual std::shared_ptr Get(const libCZI::IntRectAndFrameOfReference& roi, const IDimCoordinate* planeCoordinate, const Options* pOptions) = 0; /// Gets the tile composite of the specified plane and the specified ROI. /// /// \param pixeltype The pixeltype. - /// \param roi The ROI (given in _raw-subblock-coordinate-system_, c.f. @ref coordinatesystems). + /// \param roi The ROI and the coordinate system it is defined in. /// \param planeCoordinate The plane coordinate. /// \param pOptions Options for controlling the operation. /// /// \return A std::shared_ptr containing the tile-composite. - virtual std::shared_ptr Get(libCZI::PixelType pixeltype, const libCZI::IntRect& roi, const IDimCoordinate* planeCoordinate, const Options* pOptions) = 0; + virtual std::shared_ptr Get(libCZI::PixelType pixeltype, const libCZI::IntRectAndFrameOfReference& roi, const IDimCoordinate* planeCoordinate, const Options* pOptions) = 0; /// Copy the tile composite into the specified bitmap. The bitmap passed in here determines the width and the height of the ROI /// (and the pixeltype). /// /// \param [in] pDest The destination bitmap. - /// \param xPos The x-position of the ROI (width and height are given by pDest) - given in _raw-subblock-coordinate-system_, c.f. @ref coordinatesystems. - /// \param yPos The y-position of the ROI (width and height are given by pDest) - given in _raw-subblock-coordinate-system_, c.f. @ref coordinatesystems. + /// \param position The x-position and y-position of the ROI (width and height are given by pDest), and the coordinate system they are defined in. /// \param planeCoordinate The plane coordinate. /// \param pOptions Options for controlling the operation. - virtual void Get(libCZI::IBitmapData* pDest, int xPos, int yPos, const IDimCoordinate* planeCoordinate, const Options* pOptions) = 0; + virtual void Get(libCZI::IBitmapData* pDest, const IntPointAndFrameOfReference& position, const IDimCoordinate* planeCoordinate, const Options* pOptions) = 0; /// Gets the tile composite of the specified plane and the specified ROI. /// The pixeltype is determined by examining the first subblock found in the @@ -261,25 +257,72 @@ namespace libCZI /// bitmap is returned. /// \param xPos The x-position (given in _raw-subblock-coordinate-system_, c.f. @ref coordinatesystems). /// \param yPos The y-position (given in _raw-subblock-coordinate-system_, c.f. @ref coordinatesystems). - /// \param width The width (given in _raw-subblock-coordinate-system_, c.f. @ref coordinatesystems). - /// \param height The height (given in _raw-subblock-coordinate-system_, c.f. @ref coordinatesystems). + /// \param width The width. + /// \param height The height. /// \param planeCoordinate The plane coordinate. /// \param pOptions Options for controlling the operation. /// \return A std::shared_ptr containing the tile-composite. - inline std::shared_ptr Get(int xPos, int yPos, int width, int height, const IDimCoordinate* planeCoordinate, const Options* pOptions) { return this->Get(libCZI::IntRect{ xPos,yPos,width,height }, planeCoordinate, pOptions); } + std::shared_ptr Get(int xPos, int yPos, int width, int height, const IDimCoordinate* planeCoordinate, const Options* pOptions) + { + return this->Get(libCZI::IntRect{ xPos,yPos,width,height }, planeCoordinate, pOptions); + } /// Gets the tile composite of the specified plane and the specified ROI. /// \param pixeltype The pixeltype. - /// \param xPos The x-position. - /// \param yPos The y-position. + /// \param xPos The x-position (given in _raw-subblock-coordinate-system_, c.f. @ref coordinatesystems). + /// \param yPos The y-position (given in _raw-subblock-coordinate-system_, c.f. @ref coordinatesystems). /// \param width The width. /// \param height The height. /// \param planeCoordinate The plane coordinate. /// \param pOptions Options for controlling the operation. /// \return A std::shared_ptr containing the tile-composite. - inline std::shared_ptr Get(libCZI::PixelType pixeltype, int xPos, int yPos, int width, int height, const IDimCoordinate* planeCoordinate, const Options* pOptions) { return this->Get(pixeltype, libCZI::IntRect{ xPos,yPos,width,height }, planeCoordinate, pOptions); } + std::shared_ptr Get(libCZI::PixelType pixeltype, int xPos, int yPos, int width, int height, const IDimCoordinate* planeCoordinate, const Options* pOptions) + { + return this->Get(pixeltype, libCZI::IntRect{ xPos,yPos,width,height }, planeCoordinate, pOptions); + } + + /// Gets the tile composite of the specified plane and the specified ROI. The pixeltype is + /// determined by examining the first subblock found in the specified plane (which is an + /// arbitrary subblock). A newly allocated bitmap is returned. + /// + /// \param roi The ROI (given in _raw-subblock-coordinate-system_, c.f. @ref coordinatesystems). + /// \param planeCoordinate The plane coordinate. + /// \param pOptions Options for controlling the operation. + /// + /// \returns A std::shared_ptr containing the tile-composite. + std::shared_ptr Get(const libCZI::IntRect& roi, const IDimCoordinate* planeCoordinate, const Options* pOptions) + { + return this->Get(libCZI::IntRectAndFrameOfReference{ libCZI::CZIFrameOfReference::RawSubBlockCoordinateSystem, roi }, planeCoordinate, pOptions); + } + + /// Gets the tile composite of the specified plane and the specified ROI. + /// + /// \param pixeltype The pixeltype. + /// \param roi The ROI (given in _raw-subblock-coordinate-system_, c.f. @ref coordinatesystems). + /// \param planeCoordinate The plane coordinate. + /// \param pOptions Options for controlling the operation. + /// + /// \return A std::shared_ptr containing the tile-composite. + std::shared_ptr Get(libCZI::PixelType pixeltype, const libCZI::IntRect& roi, const IDimCoordinate* planeCoordinate, const Options* pOptions) + { + return this->Get(pixeltype, libCZI::IntRectAndFrameOfReference{ libCZI::CZIFrameOfReference::RawSubBlockCoordinateSystem, roi }, planeCoordinate, pOptions); + } + + /// Copy the tile composite into the specified bitmap. The bitmap passed in here determines the width and the height of the ROI + /// (and the pixeltype). + /// + /// \param [in] pDest The destination bitmap. + /// \param xPos The x-position of the ROI (width and height are given by pDest) - given in _raw-subblock-coordinate-system_, c.f. @ref coordinatesystems. + /// \param yPos The y-position of the ROI (width and height are given by pDest) - given in _raw-subblock-coordinate-system_, c.f. @ref coordinatesystems. + /// \param planeCoordinate The plane coordinate. + /// \param pOptions Options for controlling the operation. + void Get(libCZI::IBitmapData* pDest, int xPos, int yPos, const IDimCoordinate* planeCoordinate, const Options* pOptions) + { + this->Get(pDest, libCZI::IntPointAndFrameOfReference{ libCZI::CZIFrameOfReference::RawSubBlockCoordinateSystem, { xPos, yPos } }, planeCoordinate, pOptions); + } + protected: - virtual ~ISingleChannelTileAccessor() = default; + ~ISingleChannelTileAccessor() override = default; }; /// Interface for single-channel-pyramidlayer tile accessors. @@ -343,30 +386,67 @@ namespace libCZI /// The pixeltype is determined by examining the first subblock found in the /// specified plane (which is an arbitrary subblock). A newly allocated /// bitmap is returned. - /// \param roi The ROI. + /// \param roi The ROI and the coordinate system it is defined in. + /// \param planeCoordinate The plane coordinate. + /// \param pyramidInfo Information describing the pyramid-layer. + /// \param pOptions Options for controlling the operation. + /// \return A std::shared_ptr containing the tile-composite. + virtual std::shared_ptr Get(const libCZI::IntRectAndFrameOfReference& roi, const libCZI::IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const libCZI::ISingleChannelPyramidLayerTileAccessor::Options* pOptions) = 0; + + /// Gets the tile composite of the specified plane and the specified ROI and the specified pyramid-layer. + /// \param pixeltype The pixeltype (of the destination bitmap). + /// \param roi The ROI and the coordinate system it is defined in. + /// \param planeCoordinate The plane coordinate. + /// \param pyramidInfo Information describing the pyramid-layer. + /// \param pOptions Options for controlling the operation. + /// \return A std::shared_ptr containing the tile-composite. + virtual std::shared_ptr Get(libCZI::PixelType pixeltype, const libCZI::IntRectAndFrameOfReference& roi, const libCZI::IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const libCZI::ISingleChannelPyramidLayerTileAccessor::Options* pOptions) = 0; + + /// Copy the composite to the specified bitmap. + /// \param [out] pDest The destination bitmap (also defining the width and height) + /// \param position The x- and y-position and the coordinate system it is defined in. + /// \param planeCoordinate The plane coordinate. + /// \param pyramidInfo Information describing the pyramid-layer. + /// \param pOptions Options for controlling the operation. + virtual void Get(libCZI::IBitmapData* pDest, const libCZI::IntPointAndFrameOfReference& position, const IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const Options* pOptions) = 0; + + /// Gets the tile composite of the specified plane and the specified ROI and the specified pyramid-layer. + /// The pixeltype is determined by examining the first subblock found in the + /// specified plane (which is an arbitrary subblock). A newly allocated + /// bitmap is returned. + /// \param roi The ROI (given in _raw-subblock-coordinate-system_, c.f. @ref coordinatesystems). /// \param planeCoordinate The plane coordinate. /// \param pyramidInfo Information describing the pyramid-layer. /// \param pOptions Options for controlling the operation. - /// \return A std::shared_ptr<libCZI::IBitmapData> - virtual std::shared_ptr Get(const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const libCZI::ISingleChannelPyramidLayerTileAccessor::Options* pOptions) = 0; + /// \return A std::shared_ptr containing the tile-composite. + std::shared_ptr Get(const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const libCZI::ISingleChannelPyramidLayerTileAccessor::Options* pOptions) + { + return this->Get(libCZI::IntRectAndFrameOfReference{ libCZI::CZIFrameOfReference::RawSubBlockCoordinateSystem, roi }, planeCoordinate, pyramidInfo, pOptions); + } /// Gets the tile composite of the specified plane and the specified ROI and the specified pyramid-layer. /// \param pixeltype The pixeltype (of the destination bitmap). - /// \param roi The ROI. + /// \param roi The ROI (given in _raw-subblock-coordinate-system_, c.f. @ref coordinatesystems). /// \param planeCoordinate The plane coordinate. /// \param pyramidInfo Information describing the pyramid-layer. /// \param pOptions Options for controlling the operation. - /// \return A std::shared_ptr<libCZI::IBitmapData> - virtual std::shared_ptr Get(libCZI::PixelType pixeltype, const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const libCZI::ISingleChannelPyramidLayerTileAccessor::Options* pOptions) = 0; + /// \return A std::shared_ptr containing the tile-composite. + std::shared_ptr Get(libCZI::PixelType pixeltype, const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const libCZI::ISingleChannelPyramidLayerTileAccessor::Options* pOptions) + { + return this->Get(pixeltype, libCZI::IntRectAndFrameOfReference{ libCZI::CZIFrameOfReference::RawSubBlockCoordinateSystem, roi }, planeCoordinate, pyramidInfo, pOptions); + } /// Copy the composite to the specified bitmap. /// \param [out] pDest The destination bitmap (also defining the width and height) - /// \param xPos The x-position. - /// \param yPos The y-position. + /// \param xPos The x-position (given in _raw-subblock-coordinate-system_, c.f. @ref coordinatesystems). + /// \param yPos The y-position (given in _raw-subblock-coordinate-system_, c.f. @ref coordinatesystems). /// \param planeCoordinate The plane coordinate. /// \param pyramidInfo Information describing the pyramid-layer. /// \param pOptions Options for controlling the operation. - virtual void Get(libCZI::IBitmapData* pDest, int xPos, int yPos, const IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const Options* pOptions) = 0; + void Get(libCZI::IBitmapData* pDest, int xPos, int yPos, const IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const Options* pOptions) + { + this->Get(pDest, libCZI::IntPointAndFrameOfReference{ libCZI::CZIFrameOfReference::RawSubBlockCoordinateSystem, { xPos, yPos } }, planeCoordinate, pyramidInfo, pOptions); + } }; /// Interface for single channel scaling tile accessors. @@ -380,14 +460,14 @@ namespace libCZI /// Options used for this accessor. struct Options { - /// The back ground color. If the destination bitmap is a grayscale-type, then the mean from R, G and B is calculated and multiplied - /// with the maximum pixel value (of the specific pixeltype). If it is a RGB-color type, then R, G and B are separately multiplied with + /// The background color. If the destination bitmap is a grayscale-type, then the mean from R, G and B is calculated and multiplied + /// with the maximum pixel value (of the specific pixeltype). If it is an RGB-color type, then R, G and B are separately multiplied with /// the maximum pixel value. /// If any of R, G or B is NaN, then the background is not cleared. RgbFloatColor backGroundColor; /// If true, then the tiles are sorted by their M-index (tile with highest M-index will be 'on top'). - /// Otherwise the Z-order is arbitrary. + /// Otherwise, the Z-order is arbitrary. bool sortByM; /// If true, then a one-pixel wide boundary will be drawn around @@ -428,11 +508,41 @@ namespace libCZI /// Calculates the size a bitmap will have (when created by this accessor) for the specified ROI and the specified Zoom. /// Since the exact size if subject to rounding errors, one should always use this method if the exact size must be known beforehand. /// The Get-method which operates on a pre-allocated bitmap will only work if the size (of the bitmap passed in) exactly matches. - /// \param roi The ROI (given in _raw-subblock-coordinate-system_, c.f. @ref coordinatesystems). + /// \param roi The ROI (since only the size is relevant here currently, the coordinate system it is given in does not matter). /// \param zoom The zoom factor. /// \return The size of the composite created by this accessor (for these parameters). virtual libCZI::IntSize CalcSize(const libCZI::IntRect& roi, float zoom) const = 0; + /// Gets the scaled tile composite of the specified plane and the specified ROI with the specified zoom factor.\n + /// The pixeltype is determined by examining the first subblock found in the + /// specified plane (which is an arbitrary subblock). A newly allocated + /// bitmap is returned. + /// \param roi The ROI and the coordinate system it is defined in. + /// \param planeCoordinate The plane coordinate. + /// \param zoom The zoom. + /// \param pOptions Options for controlling the operation (may be nullptr). + /// \return A `std::shared_ptr` containing the composite. + virtual std::shared_ptr Get(const libCZI::IntRectAndFrameOfReference& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const libCZI::ISingleChannelScalingTileAccessor::Options* pOptions) = 0; + + /// Gets the scaled tile composite of the specified plane and the specified ROI with the specified zoom factor. + /// \param pixeltype The pixeltype (of the destination bitmap). + /// \param roi The ROI and the coordinate system it is defined in. + /// \param planeCoordinate The plane coordinate. + /// \param zoom The zoom factor. + /// \param pOptions Options for controlling the operation (may be nullptr). + /// \return A `std::shared_ptr` containing the composite. + virtual std::shared_ptr Get(libCZI::PixelType pixeltype, const libCZI::IntRectAndFrameOfReference& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const libCZI::ISingleChannelScalingTileAccessor::Options* pOptions) = 0; + + /// Copy the composite to the specified bitmap. + /// The size of the bitmap must exactly match the size reported by the method "CalcSize" (for the same ROI and zoom), + /// otherwise an invalid_argument-exception is thrown. + /// \param [in,out] pDest The destination bitmap. + /// \param roi The ROI and the coordinate system it is defined in. + /// \param planeCoordinate The plane coordinate. + /// \param zoom The zoom factor. + /// \param pOptions Options controlling the operation. May be nullptr. + virtual void Get(libCZI::IBitmapData* pDest, const libCZI::IntRectAndFrameOfReference& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const libCZI::ISingleChannelScalingTileAccessor::Options* pOptions) = 0; + /// Gets the scaled tile composite of the specified plane and the specified ROI with the specified zoom factor.\n /// The pixeltype is determined by examining the first subblock found in the /// specified plane (which is an arbitrary subblock). A newly allocated @@ -442,7 +552,10 @@ namespace libCZI /// \param zoom The zoom. /// \param pOptions Options for controlling the operation (may be nullptr). /// \return A `std::shared_ptr` containing the composite. - virtual std::shared_ptr Get(const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const libCZI::ISingleChannelScalingTileAccessor::Options* pOptions) = 0; + std::shared_ptr Get(const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const libCZI::ISingleChannelScalingTileAccessor::Options* pOptions) + { + return this->Get(libCZI::IntRectAndFrameOfReference{ libCZI::CZIFrameOfReference::RawSubBlockCoordinateSystem, roi }, planeCoordinate, zoom, pOptions); + } /// Gets the scaled tile composite of the specified plane and the specified ROI with the specified zoom factor. /// \param pixeltype The pixeltype (of the destination bitmap). @@ -451,7 +564,10 @@ namespace libCZI /// \param zoom The zoom factor. /// \param pOptions Options for controlling the operation (may be nullptr). /// \return A `std::shared_ptr` containing the composite. - virtual std::shared_ptr Get(libCZI::PixelType pixeltype, const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const libCZI::ISingleChannelScalingTileAccessor::Options* pOptions) = 0; + std::shared_ptr Get(libCZI::PixelType pixeltype, const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const libCZI::ISingleChannelScalingTileAccessor::Options* pOptions) + { + return this->Get(pixeltype, libCZI::IntRectAndFrameOfReference{ libCZI::CZIFrameOfReference::RawSubBlockCoordinateSystem, roi }, planeCoordinate, zoom, pOptions); + } /// Copy the composite to the specified bitmap. /// The size of the bitmap must exactly match the size reported by the method "CalcSize" (for the same ROI and zoom), @@ -461,7 +577,10 @@ namespace libCZI /// \param planeCoordinate The plane coordinate. /// \param zoom The zoom factor. /// \param pOptions Options controlling the operation. May be nullptr. - virtual void Get(libCZI::IBitmapData* pDest, const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const libCZI::ISingleChannelScalingTileAccessor::Options* pOptions) = 0; + void Get(libCZI::IBitmapData* pDest, const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, float zoom, const libCZI::ISingleChannelScalingTileAccessor::Options* pOptions) + { + this->Get(pDest, libCZI::IntRectAndFrameOfReference{ libCZI::CZIFrameOfReference::RawSubBlockCoordinateSystem, roi }, planeCoordinate, zoom, pOptions); + } }; /// Composition operations are found in this class: multi-tile compositor and multi-channel compositor. diff --git a/Src/libCZI/libCZI_Pixels.h b/Src/libCZI/libCZI_Pixels.h index 1b718bfe..425546da 100644 --- a/Src/libCZI/libCZI_Pixels.h +++ b/Src/libCZI/libCZI_Pixels.h @@ -74,6 +74,36 @@ namespace libCZI } }; + /// A point (with integer coordinates). + struct IntPoint + { + int x; + int y; + }; + + /// Values that represent different frame of reference. + enum class CZIFrameOfReference : std::uint8_t + { + Invalid, ///< Invalid frame of reference. + Default, ///< The default frame of reference. + RawSubBlockCoordinateSystem, ///< The raw sub-block coordinate system. + PixelCoordinateSystem, ///< The pixel coordinate system. + }; + + /// This structure combines a rectangle with a specification of the frame of reference. + struct IntRectAndFrameOfReference + { + libCZI::CZIFrameOfReference frame_of_reference{ libCZI::CZIFrameOfReference::Invalid }; ///< The frame of reference. + libCZI::IntRect rectangle; ///< The rectangle. + }; + + /// This structure combines a point with a specification of the frame of reference. + struct IntPointAndFrameOfReference + { + libCZI::CZIFrameOfReference frame_of_reference{ libCZI::CZIFrameOfReference::Invalid }; ///< The frame of reference. + IntPoint point; ///< The point. + }; + /// A rectangle (with double coordinates). struct DblRect { @@ -172,14 +202,14 @@ namespace libCZI /// \return The pixel type. virtual PixelType GetPixelType() const = 0; - /// Gets the size of the bitmap (i. e. its width and height in pixels). + /// Gets the size of the bitmap (i.e. its width and height in pixels). /// /// \return The size (in pixels). virtual IntSize GetSize() const = 0; /// Gets a data structure allowing for direct access of the bitmap. /// - /// The BitmapLockInfo returned must only considered to be valid until Unlock is called. + /// The BitmapLockInfo returned must only be considered to be valid until Unlock is called. /// It is legal to call Lock multiple time (also from different threads concurrently). /// In any case, calls to Lock and Unlock must be balanced. It is considered to be a /// fatal error if the object is destroyed when it is locked. @@ -190,7 +220,7 @@ namespace libCZI /// Inform the bitmap object that the data (previously retrieved by a call to Lock) /// is not longer used. /// - /// The BitmapLockInfo returned must only considered to be valid until Unlock is called. + /// The BitmapLockInfo returned must only be considered to be valid until Unlock is called. virtual void Unlock() = 0; virtual ~IBitmapData() {} diff --git a/Src/libCZI/libCZI_ReadWrite.h b/Src/libCZI/libCZI_ReadWrite.h index d2a19757..acfa0601 100644 --- a/Src/libCZI/libCZI_ReadWrite.h +++ b/Src/libCZI/libCZI_ReadWrite.h @@ -23,7 +23,15 @@ namespace libCZI /// \return The file's unique identifier. virtual const GUID& GetFileGuid() const = 0; - virtual ~ICziReaderWriterInfo() {} + /// Gets the default frame-of-reference which is to be used by the reader-writer-object. This determines which frame-of-reference + /// is used when the enum value "CZIFrameOfReference::Default" is used with an operation of the reader-writer-object. + /// If the value specified here is "CZIFrameOfReference::Invalid" or "CZIFrameOfReference::Default", then + /// "CZIFrameOfReference::RawSubBlockCoordinateSystem" will be used. + /// + /// \returns The default frame of reference. + virtual libCZI::CZIFrameOfReference GetDefaultFrameOfReference() const = 0; + + virtual ~ICziReaderWriterInfo() = default; }; /// Interface for "in-place-editing" of a CZI. All write-operations immediately go into the file. If the data does not fit into the @@ -39,7 +47,6 @@ namespace libCZI class LIBCZI_API ICziReaderWriter : public ISubBlockRepository, public IAttachmentRepository { public: - /// Initialize the object. /// /// \param stream The read-write stream to operate on. @@ -151,22 +158,27 @@ namespace libCZI private: bool forceFileGuid; GUID fileGuid; ///< The GUID to be set as the CZI's file-guid. + libCZI::CZIFrameOfReference defaultFrameOfReference; public: /// Default constructor - sets all information to "invalid" and sets fileGuid to GUID_NULL. - CCziReaderWriterInfo() : CCziReaderWriterInfo(GUID{ 0,0,0,{ 0,0,0,0,0,0,0,0 } }) + CCziReaderWriterInfo() : CCziReaderWriterInfo(GUID{ 0, 0, 0, { 0, 0, 0, 0, 0, 0, 0, 0 } }) {} /// Constructor. /// /// \param fileGuid Unique identifier for the file. - explicit CCziReaderWriterInfo(const GUID& fileGuid) : forceFileGuid(false) - { - this->fileGuid = fileGuid; - } + explicit CCziReaderWriterInfo(const GUID& fileGuid) : forceFileGuid(false), fileGuid(fileGuid), defaultFrameOfReference(libCZI::CZIFrameOfReference::Invalid) + {} + /// \copydoc libCZI::ICziReaderWriterInfo::GetForceFileGuid bool GetForceFileGuid() const override { return this->forceFileGuid; } + + /// \copydoc libCZI::ICziReaderWriterInfo::GetFileGuid const GUID& GetFileGuid() const override { return this->fileGuid; } + /// \copydoc libCZI::ICziReaderWriterInfo::GetDefaultFrameOfReference + libCZI::CZIFrameOfReference GetDefaultFrameOfReference() const override { return this->defaultFrameOfReference; } + /// Sets "force file GUID" flag. /// /// \param forceFileGuid True to force the specified file-Guid. diff --git a/Src/libCZI/libCZI_Utilities.cpp b/Src/libCZI/libCZI_Utilities.cpp index 297dfcac..6f6fce6f 100644 --- a/Src/libCZI/libCZI_Utilities.cpp +++ b/Src/libCZI/libCZI_Utilities.cpp @@ -629,8 +629,8 @@ std::vector InternalCreateLookUpTableFromGamma(int tableElementCnt, tFl [&](libCZI::DimensionIndex dim, int start, int size)->bool { coord.Set(dim, start); - dims.push_back(dim); - return true; + dims.push_back(dim); + return true; }); uint64_t coordNo = 0; diff --git a/Src/libCZI_UnitTests/CMakeLists.txt b/Src/libCZI_UnitTests/CMakeLists.txt index 090b854f..d670067a 100644 --- a/Src/libCZI_UnitTests/CMakeLists.txt +++ b/Src/libCZI_UnitTests/CMakeLists.txt @@ -65,7 +65,8 @@ ADD_EXECUTABLE(libCZI_UnitTests test_rectanglecoverage.cpp test_TileAccessorCoverageOptimization.cpp test_SubBlockCache.cpp - test_azureblobstream.cpp) + test_azureblobstream.cpp + test_frame_of_reference_transform.cpp) TARGET_LINK_LIBRARIES(libCZI_UnitTests PRIVATE libCZIStatic GTest::gtest GTest::gmock) set_target_properties(libCZI_UnitTests PROPERTIES CXX_STANDARD 14) diff --git a/Src/libCZI_UnitTests/test_TileAccessorCoverageOptimization.cpp b/Src/libCZI_UnitTests/test_TileAccessorCoverageOptimization.cpp index e4e3d0a6..ecea65f6 100644 --- a/Src/libCZI_UnitTests/test_TileAccessorCoverageOptimization.cpp +++ b/Src/libCZI_UnitTests/test_TileAccessorCoverageOptimization.cpp @@ -70,6 +70,10 @@ class SubBlockRepositoryShim : public ISubBlockRepository { return this->subblock_repository_->GetPyramidStatistics(); } + libCZI::IntPointAndFrameOfReference TransformPoint(const libCZI::IntPointAndFrameOfReference& source_point, libCZI::CZIFrameOfReference destination_frame_of_reference) override + { + return this->subblock_repository_->TransformPoint(source_point, destination_frame_of_reference); + } }; /// This struct is used for creating a test CZI document - it contains the X-Y-position/width/height @@ -267,7 +271,7 @@ void RandomSubblocksAndCompareRenderingWithAndWithoutVisibilityOptimization(tAcc class SingleChannelTileAccessorHandler { - shared_ptr accessor_; + shared_ptr accessor_; bool sort_by_m_; public: explicit SingleChannelTileAccessorHandler(bool sortByM = true) : sort_by_m_(sortByM) @@ -306,7 +310,7 @@ class SingleChannelTileAccessorHandler class SingleChannelScalingTileAccessorHandler { - shared_ptr accessor_; + shared_ptr accessor_; bool sort_by_m_; public: explicit SingleChannelScalingTileAccessorHandler(bool sortByM = true) : sort_by_m_(sortByM) diff --git a/Src/libCZI_UnitTests/test_frame_of_reference_transform.cpp b/Src/libCZI_UnitTests/test_frame_of_reference_transform.cpp new file mode 100644 index 00000000..ca02a665 --- /dev/null +++ b/Src/libCZI_UnitTests/test_frame_of_reference_transform.cpp @@ -0,0 +1,324 @@ +// SPDX-FileCopyrightText: 2017-2022 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "include_gtest.h" +#include "inc_libCZI.h" +#include "MemOutputStream.h" +#include "utils.h" +#include + +using namespace libCZI; +using namespace std; + +namespace +{ + struct TileInfo + { + int32_t x; + int32_t y; + uint8_t gray8_value; + }; + + struct MosaicInfo + { + int tile_width; + int tile_height; + std::vector tiles; + }; + + tuple, size_t> CreateMosaicCzi(const MosaicInfo& mosaic_info) + { + auto writer = libCZI::CreateCZIWriter(); + auto mem_output_stream = make_shared(0); + + auto spWriterInfo = make_shared( + libCZI::GUID{ 0x1234567, 0x89ab, 0xcdef, {1, 2, 3, 4, 5, 6, 7, 8} }, // NOLINT + libCZI::CDimBounds{ {libCZI::DimensionIndex::C, 0, 1} }, // set a bounds for C + 0, static_cast(mosaic_info.tiles.size() - 1)); // set a bounds M : 0<=m<=0 + writer->Create(mem_output_stream, spWriterInfo); + + for (size_t i = 0; i < mosaic_info.tiles.size(); ++i) + { + auto bitmap = CreateGray8BitmapAndFill(mosaic_info.tile_width, mosaic_info.tile_height, mosaic_info.tiles[i].gray8_value); + libCZI::AddSubBlockInfoStridedBitmap addSbBlkInfo; + addSbBlkInfo.Clear(); + addSbBlkInfo.coordinate.Set(libCZI::DimensionIndex::C, 0); + addSbBlkInfo.mIndexValid = true; + addSbBlkInfo.mIndex = static_cast(i); + addSbBlkInfo.x = mosaic_info.tiles[i].x; + addSbBlkInfo.y = mosaic_info.tiles[i].y; + addSbBlkInfo.logicalWidth = static_cast(bitmap->GetWidth()); + addSbBlkInfo.logicalHeight = static_cast(bitmap->GetHeight()); + addSbBlkInfo.physicalWidth = static_cast(bitmap->GetWidth()); + addSbBlkInfo.physicalHeight = static_cast(bitmap->GetHeight()); + addSbBlkInfo.PixelType = bitmap->GetPixelType(); + { + const libCZI::ScopedBitmapLockerSP lock_info_bitmap{ bitmap }; + addSbBlkInfo.ptrBitmap = lock_info_bitmap.ptrDataRoi; + addSbBlkInfo.strideBitmap = lock_info_bitmap.stride; + writer->SyncAddSubBlock(addSbBlkInfo); + } + } + + const libCZI::PrepareMetadataInfo prepare_metadata_info; + auto meta_data_builder = writer->GetPreparedMetadata(prepare_metadata_info); + + // NOLINTNEXTLINE: uninitialized struct is OK b/o Clear() + libCZI::WriteMetadataInfo write_metadata_info; + write_metadata_info.Clear(); + const auto& strMetadata = meta_data_builder->GetXml(); + write_metadata_info.szMetadata = strMetadata.c_str(); + write_metadata_info.szMetadataSize = strMetadata.size() + 1; + write_metadata_info.ptrAttachment = nullptr; + write_metadata_info.attachmentSize = 0; + writer->SyncWriteMetadata(write_metadata_info); + writer->Close(); + writer.reset(); + + size_t czi_document_size = 0; + const shared_ptr czi_document_data = mem_output_stream->GetCopy(&czi_document_size); + return make_tuple(czi_document_data, czi_document_size); + } + + bool Check2x2Gray8Bitmap(IBitmapData* bitmap, const array& expected) + { + if (bitmap->GetWidth() != 2 || bitmap->GetHeight() != 2 || bitmap->GetPixelType() != PixelType::Gray8) + { + return false; + } + + ScopedBitmapLockerP locker_bitmap{ bitmap }; + const uint8_t* p = static_cast(locker_bitmap.ptrDataRoi); + if (p[0] != expected[0] || p[1] != expected[1]) + { + return false; + } + + p += locker_bitmap.stride; + if (p[0] != expected[2] || p[1] != expected[3]) + { + return false; + } + + return true; + } +} // namespace + +TEST(FrameOfReferenceTransform, UseCziWhichIsNotZeroAlignedAndCallCheckTransformPoint) +{ + // create a 2x2 mosaics, with 1x1-pixel tiles, with subblocks at (-1, -1), (0, -1), (-1, 0), (0, 0) + MosaicInfo mosaic_info; + mosaic_info.tile_width = 1; + mosaic_info.tile_height = 1; + mosaic_info.tiles = { {-1, -1, 10}, {0, -1, 20}, {-1, 0, 30}, {0, 0, 40} }; + auto czi_document_as_blob = CreateMosaicCzi(mosaic_info); + auto reader = libCZI::CreateCZIReader(); + auto mem_input_stream = make_shared(get<0>(czi_document_as_blob).get(), get<1>(czi_document_as_blob)); + + // we use the defaults here, which means that the reader's default frame-of-reference is "raw subblock coordinate system" + reader->Open(mem_input_stream); + + IntPointAndFrameOfReference point_and_frame_of_reference; + point_and_frame_of_reference.point = { 0, 0 }; + point_and_frame_of_reference.frame_of_reference = CZIFrameOfReference::PixelCoordinateSystem; + auto transformed_point = reader->TransformPoint(point_and_frame_of_reference, CZIFrameOfReference::RawSubBlockCoordinateSystem); + EXPECT_EQ(transformed_point.point.x, -1); + EXPECT_EQ(transformed_point.point.y, -1); + EXPECT_EQ(transformed_point.frame_of_reference, CZIFrameOfReference::RawSubBlockCoordinateSystem); + + transformed_point = reader->TransformPoint(point_and_frame_of_reference, CZIFrameOfReference::PixelCoordinateSystem); + EXPECT_EQ(transformed_point.point.x, 0); + EXPECT_EQ(transformed_point.point.y, 0); + EXPECT_EQ(transformed_point.frame_of_reference, CZIFrameOfReference::PixelCoordinateSystem); + + transformed_point = reader->TransformPoint(point_and_frame_of_reference, CZIFrameOfReference::Default); + EXPECT_EQ(transformed_point.point.x, -1); + EXPECT_EQ(transformed_point.point.y, -1); + EXPECT_EQ(transformed_point.frame_of_reference, CZIFrameOfReference::RawSubBlockCoordinateSystem); + + EXPECT_ANY_THROW(reader->TransformPoint(point_and_frame_of_reference, CZIFrameOfReference::Invalid)); +} + +TEST(FrameOfReferenceTransform, UseCziWhichIsNotZeroAlignedAndCallCheckTransformRectangle) +{ + MosaicInfo mosaic_info; + mosaic_info.tile_width = 1; + mosaic_info.tile_height = 1; + mosaic_info.tiles = { {-1, -1, 10}, {0, -1, 20}, {-1, 0, 30}, {0, 0, 40} }; + auto czi_document_as_blob = CreateMosaicCzi(mosaic_info); + auto reader = libCZI::CreateCZIReader(); + auto mem_input_stream = make_shared(get<0>(czi_document_as_blob).get(), get<1>(czi_document_as_blob)); + + // we use the defaults here, which means that the reader's default frame-of-reference is "raw subblock coordinate system" + reader->Open(mem_input_stream); + + IntRectAndFrameOfReference rect_and_frame_of_reference; + rect_and_frame_of_reference.rectangle = { 0, 0, 1, 1 }; + rect_and_frame_of_reference.frame_of_reference = CZIFrameOfReference::PixelCoordinateSystem; + auto transformed_rect = reader->TransformRectangle(rect_and_frame_of_reference, CZIFrameOfReference::RawSubBlockCoordinateSystem); + EXPECT_EQ(transformed_rect.rectangle.x, -1); + EXPECT_EQ(transformed_rect.rectangle.y, -1); + EXPECT_EQ(transformed_rect.frame_of_reference, CZIFrameOfReference::RawSubBlockCoordinateSystem); + + transformed_rect = reader->TransformRectangle(rect_and_frame_of_reference, CZIFrameOfReference::PixelCoordinateSystem); + EXPECT_EQ(transformed_rect.rectangle.x, 0); + EXPECT_EQ(transformed_rect.rectangle.y, 0); + EXPECT_EQ(transformed_rect.frame_of_reference, CZIFrameOfReference::PixelCoordinateSystem); + + transformed_rect = reader->TransformRectangle(rect_and_frame_of_reference, CZIFrameOfReference::Default); + EXPECT_EQ(transformed_rect.rectangle.x, -1); + EXPECT_EQ(transformed_rect.rectangle.y, -1); + EXPECT_EQ(transformed_rect.frame_of_reference, CZIFrameOfReference::RawSubBlockCoordinateSystem); + + EXPECT_ANY_THROW(reader->TransformRectangle(rect_and_frame_of_reference, CZIFrameOfReference::Invalid)); +} + +TEST(FrameOfReferenceTransform, SetDefaultFrameOfReferenceToPixelCoordinateSystemAndCheckTransform) +{ + MosaicInfo mosaic_info; + mosaic_info.tile_width = 1; + mosaic_info.tile_height = 1; + mosaic_info.tiles = { {-1, -1, 10}, {0, -1, 20}, {-1, 0, 30}, {0, 0, 40} }; + auto czi_document_as_blob = CreateMosaicCzi(mosaic_info); + auto reader = libCZI::CreateCZIReader(); + auto mem_input_stream = make_shared(get<0>(czi_document_as_blob).get(), get<1>(czi_document_as_blob)); + + ICZIReader::OpenOptions options; + options.SetDefault(); + options.default_frame_of_reference = CZIFrameOfReference::PixelCoordinateSystem; + reader->Open(mem_input_stream, &options); + + IntPointAndFrameOfReference point_and_frame_of_reference; + point_and_frame_of_reference.point = { 0, 0 }; + point_and_frame_of_reference.frame_of_reference = CZIFrameOfReference::RawSubBlockCoordinateSystem; + auto transformed_point_pixel_coordinate_system = reader->TransformPoint(point_and_frame_of_reference, CZIFrameOfReference::PixelCoordinateSystem); + EXPECT_EQ(transformed_point_pixel_coordinate_system.point.x, 1); + EXPECT_EQ(transformed_point_pixel_coordinate_system.point.y, 1); + EXPECT_EQ(transformed_point_pixel_coordinate_system.frame_of_reference, CZIFrameOfReference::PixelCoordinateSystem); + + auto transformed_point_default_coordinate_system = reader->TransformPoint(point_and_frame_of_reference, CZIFrameOfReference::Default); + EXPECT_EQ(transformed_point_default_coordinate_system.point.x, 1); + EXPECT_EQ(transformed_point_default_coordinate_system.point.y, 1); + EXPECT_EQ(transformed_point_default_coordinate_system.frame_of_reference, CZIFrameOfReference::PixelCoordinateSystem); +} + +TEST(FrameOfReferenceTransform, SetDefaultFrameOfReferenceToRawSubblockCoordinateSystemAndCheckTransform) +{ + MosaicInfo mosaic_info; + mosaic_info.tile_width = 1; + mosaic_info.tile_height = 1; + mosaic_info.tiles = { {-1, -1, 10}, {0, -1, 20}, {-1, 0, 30}, {0, 0, 40} }; + auto czi_document_as_blob = CreateMosaicCzi(mosaic_info); + auto reader = libCZI::CreateCZIReader(); + auto mem_input_stream = make_shared(get<0>(czi_document_as_blob).get(), get<1>(czi_document_as_blob)); + + ICZIReader::OpenOptions options; + options.SetDefault(); + options.default_frame_of_reference = CZIFrameOfReference::RawSubBlockCoordinateSystem; + reader->Open(mem_input_stream, &options); + + IntPointAndFrameOfReference point_and_frame_of_reference; + point_and_frame_of_reference.point = { 0, 0 }; + point_and_frame_of_reference.frame_of_reference = CZIFrameOfReference::PixelCoordinateSystem; + auto transformed_rect_pixel_coordinate_system = reader->TransformPoint(point_and_frame_of_reference, CZIFrameOfReference::RawSubBlockCoordinateSystem); + EXPECT_EQ(transformed_rect_pixel_coordinate_system.point.x, -1); + EXPECT_EQ(transformed_rect_pixel_coordinate_system.point.y, -1); + EXPECT_EQ(transformed_rect_pixel_coordinate_system.frame_of_reference, CZIFrameOfReference::RawSubBlockCoordinateSystem); + + auto transformed_point_default_coordinate_system = reader->TransformPoint(point_and_frame_of_reference, CZIFrameOfReference::Default); + EXPECT_EQ(transformed_point_default_coordinate_system.point.x, -1); + EXPECT_EQ(transformed_point_default_coordinate_system.point.y, -1); + EXPECT_EQ(transformed_point_default_coordinate_system.frame_of_reference, CZIFrameOfReference::RawSubBlockCoordinateSystem); +} + +TEST(FrameOfReferenceTransform, GetTileCompositeAndCheckResult) +{ + MosaicInfo mosaic_info; + mosaic_info.tile_width = 1; + mosaic_info.tile_height = 1; + mosaic_info.tiles = { {-1, -1, 10}, {0, -1, 20}, {-1, 0, 30}, {0, 0, 40} }; + auto czi_document_as_blob = CreateMosaicCzi(mosaic_info); + auto reader = libCZI::CreateCZIReader(); + auto mem_input_stream = make_shared(get<0>(czi_document_as_blob).get(), get<1>(czi_document_as_blob)); + + ICZIReader::OpenOptions options; + options.SetDefault(); + options.default_frame_of_reference = CZIFrameOfReference::RawSubBlockCoordinateSystem; + reader->Open(mem_input_stream, &options); + + auto accessor = reader->CreateSingleChannelTileAccessor(); + IntRectAndFrameOfReference rect_and_frame_of_reference; + rect_and_frame_of_reference.rectangle = { 0, 0, 2, 2 }; + rect_and_frame_of_reference.frame_of_reference = CZIFrameOfReference::PixelCoordinateSystem; + ISingleChannelTileAccessor::Options accessor_options; + accessor_options.Clear(); + accessor_options.backGroundColor = RgbFloatColor{ 0, 0, 0 }; + CDimCoordinate plane_coordinate{ {DimensionIndex::C, 0} }; + auto tile_composite_bitmap = accessor->Get(rect_and_frame_of_reference, &plane_coordinate, &accessor_options); + EXPECT_TRUE(Check2x2Gray8Bitmap(tile_composite_bitmap.get(), array{10, 20, 30, 40})); + + rect_and_frame_of_reference.rectangle = { -1, -1, 2, 2 }; + rect_and_frame_of_reference.frame_of_reference = CZIFrameOfReference::RawSubBlockCoordinateSystem; + tile_composite_bitmap = accessor->Get(rect_and_frame_of_reference, &plane_coordinate, &accessor_options); + EXPECT_TRUE(Check2x2Gray8Bitmap(tile_composite_bitmap.get(), array{10, 20, 30, 40})); + + rect_and_frame_of_reference.frame_of_reference = CZIFrameOfReference::Default; + tile_composite_bitmap = accessor->Get(rect_and_frame_of_reference, &plane_coordinate, &accessor_options); + EXPECT_TRUE(Check2x2Gray8Bitmap(tile_composite_bitmap.get(), array{10, 20, 30, 40})); +} + +TEST(FrameOfReferenceTransform, UseReaderWriterAndCallAndCheckTransformPoint) +{ + MosaicInfo mosaic_info; + mosaic_info.tile_width = 1; + mosaic_info.tile_height = 1; + mosaic_info.tiles = { {-1, -1, 10}, {0, -1, 20}, {-1, 0, 30}, {0, 0, 40} }; + auto czi_document_as_blob = CreateMosaicCzi(mosaic_info); + + const auto in_out_stream = make_shared(get<0>(czi_document_as_blob).get(), get<1>(czi_document_as_blob)); + const auto reader_writer = CreateCZIReaderWriter(); + reader_writer->Create(in_out_stream); + + IntPointAndFrameOfReference point_and_frame_of_reference; + point_and_frame_of_reference.point = { 0, 0 }; + point_and_frame_of_reference.frame_of_reference = CZIFrameOfReference::PixelCoordinateSystem; + auto transformed_point = reader_writer->TransformPoint(point_and_frame_of_reference, CZIFrameOfReference::RawSubBlockCoordinateSystem); + EXPECT_EQ(transformed_point.point.x, -1); + EXPECT_EQ(transformed_point.point.y, -1); + EXPECT_EQ(transformed_point.frame_of_reference, CZIFrameOfReference::RawSubBlockCoordinateSystem); + + // ok, now we remove the subblocks at (-1, -1) and (0, -1) - the bounding-box is now (0, -1, 1, 1), so the point (0, 0) should be transformed to (0, -1) + reader_writer->RemoveSubBlock(0); + reader_writer->RemoveSubBlock(2); + transformed_point = reader_writer->TransformPoint(point_and_frame_of_reference, CZIFrameOfReference::RawSubBlockCoordinateSystem); + EXPECT_EQ(transformed_point.point.x, 0); + EXPECT_EQ(transformed_point.point.y, -1); + EXPECT_EQ(transformed_point.frame_of_reference, CZIFrameOfReference::RawSubBlockCoordinateSystem); + + // and now, add a subblock at (-5,-5) and check the transformation + auto bitmap = CreateGray8BitmapAndFill(mosaic_info.tile_width, mosaic_info.tile_height, 50); + libCZI::AddSubBlockInfoStridedBitmap addSbBlkInfo; + addSbBlkInfo.Clear(); + addSbBlkInfo.coordinate.Set(libCZI::DimensionIndex::C, 0); + addSbBlkInfo.mIndexValid = true; + addSbBlkInfo.mIndex = 4; + addSbBlkInfo.x = -5; + addSbBlkInfo.y = -5; + addSbBlkInfo.logicalWidth = static_cast(bitmap->GetWidth()); + addSbBlkInfo.logicalHeight = static_cast(bitmap->GetHeight()); + addSbBlkInfo.physicalWidth = static_cast(bitmap->GetWidth()); + addSbBlkInfo.physicalHeight = static_cast(bitmap->GetHeight()); + addSbBlkInfo.PixelType = bitmap->GetPixelType(); + { + const libCZI::ScopedBitmapLockerSP lock_info_bitmap{ bitmap }; + addSbBlkInfo.ptrBitmap = lock_info_bitmap.ptrDataRoi; + addSbBlkInfo.strideBitmap = lock_info_bitmap.stride; + reader_writer->SyncAddSubBlock(addSbBlkInfo); + } + + transformed_point = reader_writer->TransformPoint(point_and_frame_of_reference, CZIFrameOfReference::RawSubBlockCoordinateSystem); + EXPECT_EQ(transformed_point.point.x, -5); + EXPECT_EQ(transformed_point.point.y, -5); + EXPECT_EQ(transformed_point.frame_of_reference, CZIFrameOfReference::RawSubBlockCoordinateSystem); +}