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); +}