# HG changeset patch # User Sebastien Jodogne # Date 1726063876 -7200 # Node ID 8ad12abde290a6234618c6fd004846942e95fb55 # Parent f611fb47d0e8e0e9e5bb86a0d692b67549864741 sparse re-encoding with OpenSlide (notably for MIRAX format) diff -r f611fb47d0e8 -r 8ad12abde290 Framework/Algorithms/PyramidReader.cpp --- a/Framework/Algorithms/PyramidReader.cpp Wed Sep 11 13:43:39 2024 +0200 +++ b/Framework/Algorithms/PyramidReader.cpp Wed Sep 11 16:11:16 2024 +0200 @@ -43,6 +43,7 @@ bool hasRawTile_; std::string rawTile_; ImageCompression rawTileCompression_; + bool isEmpty_; std::unique_ptr decoded_; @@ -107,11 +108,13 @@ that_.source_.ReadRawTile(rawTile_, rawTileCompression_, that_.level_, tileX, tileY)) { hasRawTile_ = true; + isEmpty_ = false; } else { hasRawTile_ = false; - decoded_.reset(that_.source_.DecodeTile(that_.level_, tileX, tileY)); + + decoded_.reset(that_.source_.DecodeTile(isEmpty_, that_.level_, tileX, tileY)); if (decoded_.get() == NULL) { throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); @@ -166,6 +169,11 @@ return *decoded_; } + + bool IsEmpty() const + { + return isEmpty_; + } }; @@ -290,6 +298,7 @@ void PyramidReader::GetDecodedTile(Orthanc::ImageAccessor& target, + bool& isEmpty, unsigned int tileX, unsigned int tileY) { @@ -298,6 +307,7 @@ { // Accessing a tile out of the source image GetOutsideTile().GetReadOnlyAccessor(target); + isEmpty = true; } else { @@ -319,7 +329,8 @@ target.AssignReadOnly(tile.GetFormat(), targetTileWidth_, targetTileHeight_, - tile.GetPitch(), bytes); + tile.GetPitch(), bytes); + isEmpty = source.IsEmpty(); } } } diff -r f611fb47d0e8 -r 8ad12abde290 Framework/Algorithms/PyramidReader.h --- a/Framework/Algorithms/PyramidReader.h Wed Sep 11 13:43:39 2024 +0200 +++ b/Framework/Algorithms/PyramidReader.h Wed Sep 11 16:11:16 2024 +0200 @@ -93,6 +93,7 @@ unsigned int tileY); void GetDecodedTile(Orthanc::ImageAccessor& target, + bool& isEmpty, unsigned int tileX, unsigned int tileY); }; diff -r f611fb47d0e8 -r 8ad12abde290 Framework/Algorithms/ReconstructPyramidCommand.cpp --- a/Framework/Algorithms/ReconstructPyramidCommand.cpp Wed Sep 11 13:43:39 2024 +0200 +++ b/Framework/Algorithms/ReconstructPyramidCommand.cpp Wed Sep 11 16:11:16 2024 +0200 @@ -36,7 +36,8 @@ namespace OrthancWSI { - Orthanc::ImageAccessor* ReconstructPyramidCommand::Explore(unsigned int level, + Orthanc::ImageAccessor* ReconstructPyramidCommand::Explore(bool& isEmpty, + unsigned int level, unsigned int offsetX, unsigned int offsetY) { @@ -56,20 +57,25 @@ if (level == 0) { result.reset(new Orthanc::ImageAccessor); - source_.GetDecodedTile(*result, x, y); + source_.GetDecodedTile(*result, isEmpty, x, y); - ImageCompression compression; - const std::string* rawTile = source_.GetRawTile(compression, x, y); - - if (rawTile != NULL) + if ((x == 0 && y == 0) || // Make sure to have at least 1 tile at each level + !isEmpty || + level == upToLevel_) { - // Simple transcoding - target_.WriteRawTile(*rawTile, compression, level + shiftTargetLevel_, x, y); - } - else - { - // Re-encoding the file - target_.EncodeTile(*result, level + shiftTargetLevel_, x, y); + ImageCompression compression; + const std::string* rawTile = source_.GetRawTile(compression, x, y); + + if (rawTile != NULL) + { + // Simple transcoding + target_.WriteRawTile(*rawTile, compression, level + shiftTargetLevel_, x, y); + } + else + { + // Re-encoding the file + target_.EncodeTile(*result, level + shiftTargetLevel_, x, y); + } } } else @@ -82,35 +88,57 @@ source_.GetParameters().GetBackgroundColorGreen(), source_.GetParameters().GetBackgroundColorBlue()); + isEmpty = true; + { - std::unique_ptr subTile(Explore(level - 1, 2 * offsetX, 2 * offsetY)); + bool tmpIsEmpty; + std::unique_ptr subTile(Explore(tmpIsEmpty, level - 1, 2 * offsetX, 2 * offsetY)); if (subTile.get() != NULL) { ImageToolbox::Embed(*mosaic, *subTile, 0, 0); + if (!tmpIsEmpty) + { + isEmpty = false; + } } } { - std::unique_ptr subTile(Explore(level - 1, 2 * offsetX + 1, 2 * offsetY)); + bool tmpIsEmpty; + std::unique_ptr subTile(Explore(tmpIsEmpty, level - 1, 2 * offsetX + 1, 2 * offsetY)); if (subTile.get() != NULL) { ImageToolbox::Embed(*mosaic, *subTile, target_.GetTileWidth(), 0); + if (!tmpIsEmpty) + { + isEmpty = false; + } } } { - std::unique_ptr subTile(Explore(level - 1, 2 * offsetX, 2 * offsetY + 1)); + bool tmpIsEmpty; + std::unique_ptr subTile(Explore(tmpIsEmpty, level - 1, 2 * offsetX, 2 * offsetY + 1)); if (subTile.get() != NULL) { ImageToolbox::Embed(*mosaic, *subTile, 0, target_.GetTileHeight()); + if (!tmpIsEmpty) + { + isEmpty = false; + } } } { - std::unique_ptr subTile(Explore(level - 1, 2 * offsetX + 1, 2 * offsetY + 1)); + bool tmpIsEmpty; + std::unique_ptr subTile(Explore(tmpIsEmpty, level - 1, 2 * offsetX + 1, 2 * offsetY + 1)); if (subTile.get() != NULL) { ImageToolbox::Embed(*mosaic, *subTile, target_.GetTileWidth(), target_.GetTileHeight()); + if (!tmpIsEmpty) + { + isEmpty = false; + } } } @@ -121,7 +149,12 @@ result.reset(Orthanc::ImageProcessing::Halve(*mosaic, false /* don't force minimal pitch */)); - target_.EncodeTile(*result, level + shiftTargetLevel_, x, y); + if ((x == 0 && y == 0) || // Make sure to have at least 1 tile at each level + !isEmpty || + level == upToLevel_) + { + target_.EncodeTile(*result, level + shiftTargetLevel_, x, y); + } } return result.release(); @@ -157,7 +190,8 @@ bool ReconstructPyramidCommand::Execute() { - std::unique_ptr root(Explore(upToLevel_, 0, 0)); + bool isEmpty; // Unused + std::unique_ptr root(Explore(isEmpty, upToLevel_, 0, 0)); return true; } diff -r f611fb47d0e8 -r 8ad12abde290 Framework/Algorithms/ReconstructPyramidCommand.h --- a/Framework/Algorithms/ReconstructPyramidCommand.h Wed Sep 11 13:43:39 2024 +0200 +++ b/Framework/Algorithms/ReconstructPyramidCommand.h Wed Sep 11 16:11:16 2024 +0200 @@ -41,7 +41,8 @@ unsigned int y_; unsigned int shiftTargetLevel_; - Orthanc::ImageAccessor* Explore(unsigned int level, + Orthanc::ImageAccessor* Explore(bool& isEmpty, + unsigned int level, unsigned int offsetX, unsigned int offsetY); diff -r f611fb47d0e8 -r 8ad12abde290 Framework/Algorithms/TranscodeTileCommand.cpp --- a/Framework/Algorithms/TranscodeTileCommand.cpp Wed Sep 11 13:43:39 2024 +0200 +++ b/Framework/Algorithms/TranscodeTileCommand.cpp Wed Sep 11 16:11:16 2024 +0200 @@ -75,11 +75,19 @@ } else { + bool isEmpty; Orthanc::ImageAccessor tile; - source_.GetDecodedTile(tile, x, y); + source_.GetDecodedTile(tile, isEmpty, x, y); - // Re-encoding the file - target_.EncodeTile(tile, level_, x, y); + if (!isEmpty) + { + // Re-encoding the file + target_.EncodeTile(tile, level_, x, y); + } + else + { + printf("ICI\n"); + } } } } diff -r f611fb47d0e8 -r 8ad12abde290 Framework/ImageToolbox.cpp --- a/Framework/ImageToolbox.cpp Wed Sep 11 13:43:39 2024 +0200 +++ b/Framework/ImageToolbox.cpp Wed Sep 11 16:11:16 2024 +0200 @@ -240,8 +240,9 @@ { for (unsigned int x = 0; x < width; x += pyramid.GetTileWidth(level)) { + bool isEmpty; // Unused in this case std::unique_ptr tile( - pyramid.DecodeTile(level, + pyramid.DecodeTile(isEmpty, level, x / pyramid.GetTileWidth(level), y / pyramid.GetTileHeight(level))); Embed(*result, *tile, x, y); diff -r f611fb47d0e8 -r 8ad12abde290 Framework/Inputs/CytomineImage.cpp --- a/Framework/Inputs/CytomineImage.cpp Wed Sep 11 13:43:39 2024 +0200 +++ b/Framework/Inputs/CytomineImage.cpp Wed Sep 11 16:11:16 2024 +0200 @@ -156,10 +156,13 @@ void CytomineImage::ReadRegion(Orthanc::ImageAccessor& target, + bool& isEmpty, unsigned int level, unsigned int x, unsigned int y) { + isEmpty = false; + if (level != 0 || x >= fullWidth_ || y >= fullHeight_) diff -r f611fb47d0e8 -r 8ad12abde290 Framework/Inputs/CytomineImage.h --- a/Framework/Inputs/CytomineImage.h Wed Sep 11 13:43:39 2024 +0200 +++ b/Framework/Inputs/CytomineImage.h Wed Sep 11 16:11:16 2024 +0200 @@ -48,6 +48,7 @@ protected: virtual void ReadRegion(Orthanc::ImageAccessor& target, + bool& isEmpty, unsigned int level, unsigned int x, unsigned int y) ORTHANC_OVERRIDE; diff -r f611fb47d0e8 -r 8ad12abde290 Framework/Inputs/DecodedTiledPyramid.cpp --- a/Framework/Inputs/DecodedTiledPyramid.cpp Wed Sep 11 13:43:39 2024 +0200 +++ b/Framework/Inputs/DecodedTiledPyramid.cpp Wed Sep 11 16:11:16 2024 +0200 @@ -59,7 +59,8 @@ } - Orthanc::ImageAccessor* DecodedTiledPyramid::DecodeTile(unsigned int level, + Orthanc::ImageAccessor* DecodedTiledPyramid::DecodeTile(bool& isEmpty, + unsigned int level, unsigned int tileX, unsigned int tileY) { @@ -72,6 +73,7 @@ if (x >= GetLevelWidth(level) || y >= GetLevelHeight(level)) // (*) { + isEmpty = true; ImageToolbox::Set(*tile, backgroundColor_[0], backgroundColor_[1], backgroundColor_[2]); return tile.release(); } @@ -104,14 +106,14 @@ if (fit) { // The tile entirely lies inside the image - ReadRegion(*tile, level, x, y); + ReadRegion(*tile, isEmpty, level, x, y); } else { // The tile exceeds the size of image, decode it to a temporary buffer std::unique_ptr cropped (ImageToolbox::Allocate(GetPixelFormat(), regionWidth, regionHeight)); - ReadRegion(*cropped, level, x, y); + ReadRegion(*cropped, isEmpty, level, x, y); // Create a white tile, and fill it with the cropped content ImageToolbox::Set(*tile, backgroundColor_[0], backgroundColor_[1], backgroundColor_[2]); diff -r f611fb47d0e8 -r 8ad12abde290 Framework/Inputs/DecodedTiledPyramid.h --- a/Framework/Inputs/DecodedTiledPyramid.h Wed Sep 11 13:43:39 2024 +0200 +++ b/Framework/Inputs/DecodedTiledPyramid.h Wed Sep 11 16:11:16 2024 +0200 @@ -43,6 +43,7 @@ // the image, and that target has the proper size to store the // region. Pay attention to implement mutual exclusion in subclasses. virtual void ReadRegion(Orthanc::ImageAccessor& target, + bool& isEmpty, unsigned int level, unsigned int x, unsigned int y) = 0; @@ -58,7 +59,8 @@ uint8_t& green, uint8_t& blue) const; - virtual Orthanc::ImageAccessor* DecodeTile(unsigned int level, + virtual Orthanc::ImageAccessor* DecodeTile(bool& isEmpty, + unsigned int level, unsigned int tileX, unsigned int tileY) ORTHANC_OVERRIDE; diff -r f611fb47d0e8 -r 8ad12abde290 Framework/Inputs/ITiledPyramid.h --- a/Framework/Inputs/ITiledPyramid.h Wed Sep 11 13:43:39 2024 +0200 +++ b/Framework/Inputs/ITiledPyramid.h Wed Sep 11 16:11:16 2024 +0200 @@ -61,7 +61,8 @@ unsigned int tileX, unsigned int tileY) = 0; - virtual Orthanc::ImageAccessor* DecodeTile(unsigned int level, + virtual Orthanc::ImageAccessor* DecodeTile(bool& isEmpty, + unsigned int level, unsigned int tileX, unsigned int tileY) = 0; diff -r f611fb47d0e8 -r 8ad12abde290 Framework/Inputs/OpenSlidePyramid.cpp --- a/Framework/Inputs/OpenSlidePyramid.cpp Wed Sep 11 13:43:39 2024 +0200 +++ b/Framework/Inputs/OpenSlidePyramid.cpp Wed Sep 11 16:11:16 2024 +0200 @@ -36,6 +36,7 @@ namespace OrthancWSI { void OpenSlidePyramid::ReadRegion(Orthanc::ImageAccessor& target, + bool& isEmpty, unsigned int level, unsigned int x, unsigned int y) @@ -51,6 +52,29 @@ const unsigned int width = source->GetWidth(); const unsigned int height = source->GetHeight(); + if (source->GetFormat() == Orthanc::PixelFormat_BGRA32) + { + isEmpty = true; + + for (unsigned int y = 0; y < height && isEmpty; y++) + { + const uint8_t* p = reinterpret_cast(source->GetConstRow(y)); + for (unsigned int x = 0; x < width && isEmpty; x++) + { + if (p[3] != 0) + { + isEmpty = false; + } + + p += 4; + } + } + } + else + { + isEmpty = false; + } + if (target.GetFormat() == Orthanc::PixelFormat_RGB24 && source->GetFormat() == Orthanc::PixelFormat_BGRA32) { diff -r f611fb47d0e8 -r 8ad12abde290 Framework/Inputs/OpenSlidePyramid.h --- a/Framework/Inputs/OpenSlidePyramid.h Wed Sep 11 13:43:39 2024 +0200 +++ b/Framework/Inputs/OpenSlidePyramid.h Wed Sep 11 16:11:16 2024 +0200 @@ -37,6 +37,7 @@ protected: virtual void ReadRegion(Orthanc::ImageAccessor& target, + bool& isEmpty, unsigned int level, unsigned int x, unsigned int y) ORTHANC_OVERRIDE; diff -r f611fb47d0e8 -r 8ad12abde290 Framework/Inputs/PyramidWithRawTiles.cpp --- a/Framework/Inputs/PyramidWithRawTiles.cpp Wed Sep 11 13:43:39 2024 +0200 +++ b/Framework/Inputs/PyramidWithRawTiles.cpp Wed Sep 11 16:11:16 2024 +0200 @@ -28,10 +28,13 @@ namespace OrthancWSI { - Orthanc::ImageAccessor* PyramidWithRawTiles::DecodeTile(unsigned int level, + Orthanc::ImageAccessor* PyramidWithRawTiles::DecodeTile(bool& isEmpty, + unsigned int level, unsigned int tileX, unsigned int tileY) { + isEmpty = false; + std::string tile; ImageCompression compression; diff -r f611fb47d0e8 -r 8ad12abde290 Framework/Inputs/PyramidWithRawTiles.h --- a/Framework/Inputs/PyramidWithRawTiles.h Wed Sep 11 13:43:39 2024 +0200 +++ b/Framework/Inputs/PyramidWithRawTiles.h Wed Sep 11 16:11:16 2024 +0200 @@ -30,7 +30,8 @@ class PyramidWithRawTiles : public ITiledPyramid { public: - virtual Orthanc::ImageAccessor* DecodeTile(unsigned int level, + virtual Orthanc::ImageAccessor* DecodeTile(bool& isEmpty, + unsigned int level, unsigned int tileX, unsigned int tileY) ORTHANC_OVERRIDE; }; diff -r f611fb47d0e8 -r 8ad12abde290 Framework/Inputs/SingleLevelDecodedPyramid.cpp --- a/Framework/Inputs/SingleLevelDecodedPyramid.cpp Wed Sep 11 13:43:39 2024 +0200 +++ b/Framework/Inputs/SingleLevelDecodedPyramid.cpp Wed Sep 11 16:11:16 2024 +0200 @@ -31,10 +31,13 @@ namespace OrthancWSI { void SingleLevelDecodedPyramid::ReadRegion(Orthanc::ImageAccessor& target, + bool& isEmpty, unsigned int level, unsigned int x, unsigned int y) { + isEmpty = false; + Orthanc::ImageAccessor region; image_.GetRegion(region, x, y, target.GetWidth(), target.GetHeight()); Orthanc::ImageProcessing::Copy(target, region); diff -r f611fb47d0e8 -r 8ad12abde290 Framework/Inputs/SingleLevelDecodedPyramid.h --- a/Framework/Inputs/SingleLevelDecodedPyramid.h Wed Sep 11 13:43:39 2024 +0200 +++ b/Framework/Inputs/SingleLevelDecodedPyramid.h Wed Sep 11 16:11:16 2024 +0200 @@ -41,6 +41,7 @@ } virtual void ReadRegion(Orthanc::ImageAccessor& target, + bool& isEmpty, unsigned int level, unsigned int x, unsigned int y) ORTHANC_OVERRIDE; diff -r f611fb47d0e8 -r 8ad12abde290 Framework/Inputs/TiledPyramidStatistics.cpp --- a/Framework/Inputs/TiledPyramidStatistics.cpp Wed Sep 11 13:43:39 2024 +0200 +++ b/Framework/Inputs/TiledPyramidStatistics.cpp Wed Sep 11 16:11:16 2024 +0200 @@ -64,7 +64,8 @@ } - Orthanc::ImageAccessor* TiledPyramidStatistics::DecodeTile(unsigned int level, + Orthanc::ImageAccessor* TiledPyramidStatistics::DecodeTile(bool& isEmpty, + unsigned int level, unsigned int tileX, unsigned int tileY) { @@ -73,6 +74,6 @@ countDecodedTiles_++; } - return source_.DecodeTile(level, tileX, tileY); + return source_.DecodeTile(isEmpty, level, tileX, tileY); } } diff -r f611fb47d0e8 -r 8ad12abde290 Framework/Inputs/TiledPyramidStatistics.h --- a/Framework/Inputs/TiledPyramidStatistics.h Wed Sep 11 13:43:39 2024 +0200 +++ b/Framework/Inputs/TiledPyramidStatistics.h Wed Sep 11 16:11:16 2024 +0200 @@ -78,7 +78,8 @@ unsigned int tileX, unsigned int tileY) ORTHANC_OVERRIDE; - virtual Orthanc::ImageAccessor* DecodeTile(unsigned int level, + virtual Orthanc::ImageAccessor* DecodeTile(bool& isEmpty, + unsigned int level, unsigned int tileX, unsigned int tileY) ORTHANC_OVERRIDE; diff -r f611fb47d0e8 -r 8ad12abde290 Framework/Outputs/InMemoryTiledImage.cpp --- a/Framework/Outputs/InMemoryTiledImage.cpp Wed Sep 11 13:43:39 2024 +0200 +++ b/Framework/Outputs/InMemoryTiledImage.cpp Wed Sep 11 16:11:16 2024 +0200 @@ -107,10 +107,13 @@ } - Orthanc::ImageAccessor* InMemoryTiledImage::DecodeTile(unsigned int level, + Orthanc::ImageAccessor* InMemoryTiledImage::DecodeTile(bool& isEmpty, + unsigned int level, unsigned int tileX, unsigned int tileY) { + isEmpty = false; + CheckLevel(level); if (tileX >= countTilesX_ || diff -r f611fb47d0e8 -r 8ad12abde290 Framework/Outputs/InMemoryTiledImage.h --- a/Framework/Outputs/InMemoryTiledImage.h Wed Sep 11 13:43:39 2024 +0200 +++ b/Framework/Outputs/InMemoryTiledImage.h Wed Sep 11 16:11:16 2024 +0200 @@ -99,7 +99,8 @@ unsigned int tileX, unsigned int tileY) ORTHANC_OVERRIDE; - virtual Orthanc::ImageAccessor* DecodeTile(unsigned int level, + virtual Orthanc::ImageAccessor* DecodeTile(bool& isEmpty, + unsigned int level, unsigned int tileX, unsigned int tileY) ORTHANC_OVERRIDE; diff -r f611fb47d0e8 -r 8ad12abde290 NEWS --- a/NEWS Wed Sep 11 13:43:39 2024 +0200 +++ b/NEWS Wed Sep 11 16:11:16 2024 +0200 @@ -1,7 +1,7 @@ Pending changes in the mainline =============================== -* Support of transparency in OpenSlide +* Support of sparse encoding of tiles in OpenSlide (notably for MIRAX format) * OrthancWSIDicomizer supports plain TIFF, besides hierarchical TIFF * New option: "tiff-alignment" to control deep zoom of plain TIFF over IIIF * Force version of Mirador to 3.3.0 diff -r f611fb47d0e8 -r 8ad12abde290 ViewerPlugin/IIIF.cpp --- a/ViewerPlugin/IIIF.cpp Wed Sep 11 13:43:39 2024 +0200 +++ b/ViewerPlugin/IIIF.cpp Wed Sep 11 16:11:16 2024 +0200 @@ -231,7 +231,9 @@ for (unsigned int tx = 0; tx < nx; tx++) { const unsigned int x = tx * pyramid.GetTileWidth(level); - std::unique_ptr tile(pyramid.DecodeTile(level, tx, ty)); + + bool isEmpty; // Unused + std::unique_ptr tile(pyramid.DecodeTile(isEmpty, level, tx, ty)); const unsigned int width = std::min(pyramid.GetTileWidth(level), full.GetWidth() - x); @@ -328,8 +330,12 @@ regionX / GetPhysicalTileWidth(pyramid, level), regionY / GetPhysicalTileHeight(pyramid, level))); - if (static_cast(cropWidth) < pyramid.GetTileWidth(level) || - static_cast(cropHeight) < pyramid.GetTileHeight(level)) + assert(rawTile->GetTileWidth() == pyramid.GetTileWidth(level)); + assert(rawTile->GetTileHeight() == pyramid.GetTileHeight(level)); + + if (!rawTile->IsEmpty() && + (static_cast(cropWidth) < pyramid.GetTileWidth(level) || + static_cast(cropHeight) < pyramid.GetTileHeight(level))) { toCrop.reset(rawTile->Decode()); rawTile.reset(NULL); @@ -341,8 +347,23 @@ { assert(toCrop.get() == NULL); - // Level 0 Compliance of IIIF expects JPEG files - rawTile->Answer(output, Orthanc::MimeType_Jpeg); + if (rawTile->IsEmpty()) + { + if (static_cast(cropWidth) < rawTile->GetTileWidth() || + static_cast(cropHeight) < rawTile->GetTileHeight()) + { + OrthancWSI::RawTile::AnswerBackgroundTile(output, static_cast(cropWidth), static_cast(cropHeight)); + } + else + { + OrthancWSI::RawTile::AnswerBackgroundTile(output, rawTile->GetTileWidth(), rawTile->GetTileHeight()); + } + } + else + { + // Level 0 Compliance of IIIF expects JPEG files + rawTile->Answer(output, Orthanc::MimeType_Jpeg); + } } else if (toCrop.get() != NULL) { diff -r f611fb47d0e8 -r 8ad12abde290 ViewerPlugin/Plugin.cpp --- a/ViewerPlugin/Plugin.cpp Wed Sep 11 13:43:39 2024 +0200 +++ b/ViewerPlugin/Plugin.cpp Wed Sep 11 16:11:16 2024 +0200 @@ -43,26 +43,6 @@ #define ORTHANC_PLUGIN_NAME "wsi" -static void AnswerSparseTile(OrthancPluginRestOutput* output, - unsigned int tileWidth, - unsigned int tileHeight) -{ - Orthanc::Image tile(Orthanc::PixelFormat_RGB24, tileWidth, tileHeight, false); - - // Black (TODO parameter) - uint8_t red = 0; - uint8_t green = 0; - uint8_t blue = 0; - Orthanc::ImageProcessing::Set(tile, red, green, blue, 255); - - // TODO Cache the tile - OrthancPluginCompressAndAnswerPngImage(OrthancPlugins::GetGlobalContext(), - output, OrthancPluginPixelFormat_RGB24, - tile.GetWidth(), tile.GetHeight(), - tile.GetPitch(), tile.GetBuffer()); -} - - static bool DisplayPerformanceWarning() { (void) DisplayPerformanceWarning; // Disable warning about unused function @@ -165,6 +145,12 @@ static_cast(tileY))); } + if (rawTile->IsEmpty()) + { + OrthancWSI::RawTile::AnswerBackgroundTile(output, rawTile->GetTileWidth(), rawTile->GetTileHeight()); + return; + } + Orthanc::MimeType mime; if (rawTile->GetCompression() == OrthancWSI::ImageCompression_Jpeg) diff -r f611fb47d0e8 -r 8ad12abde290 ViewerPlugin/RawTile.cpp --- a/ViewerPlugin/RawTile.cpp Wed Sep 11 13:43:39 2024 +0200 +++ b/ViewerPlugin/RawTile.cpp Wed Sep 11 16:11:16 2024 +0200 @@ -29,8 +29,8 @@ #include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" #include // For std::unique_ptr +#include #include -#include #include #include #include @@ -119,11 +119,19 @@ tileHeight_(pyramid.GetTileHeight(level)), photometric_(pyramid.GetPhotometricInterpretation()) { - if (!pyramid.ReadRawTile(tile_, compression_, level, tileX, tileY)) + isEmpty_ = !pyramid.ReadRawTile(tile_, compression_, level, tileX, tileY); + } + + + ImageCompression RawTile::GetCompression() const + { + if (isEmpty_) { - // Handling of missing tile (for sparse tiling): TODO parameter? - // AnswerSparseTile(output, tileWidth, tileHeight); return; - throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return compression_; } } @@ -131,6 +139,11 @@ void RawTile::Answer(OrthancPluginRestOutput* output, Orthanc::MimeType encoding) { + if (isEmpty_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + if ((compression_ == ImageCompression_Jpeg && encoding == Orthanc::MimeType_Jpeg) || (compression_ == ImageCompression_Jpeg2000 && encoding == Orthanc::MimeType_Jpeg2000)) { @@ -158,6 +171,11 @@ Orthanc::ImageAccessor* RawTile::Decode() { + if (isEmpty_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + Orthanc::Semaphore::Locker locker(*transcoderSemaphore_); return DecodeInternal(); } @@ -182,4 +200,41 @@ { transcoderSemaphore_.reset(NULL); } + + + void RawTile::AnswerBackgroundTile(OrthancPluginRestOutput* output, + unsigned int tileWidth, + unsigned int tileHeight) + { + std::string answer; + + { + static boost::mutex mutex; + static std::string cachedTile; + static unsigned int cachedWidth = 0; + static unsigned int cachedHeight = 0; + + boost::mutex::scoped_lock lock(mutex); + + if (cachedTile.empty() || + cachedWidth != tileWidth || + cachedHeight != tileHeight) + { + Orthanc::Image tile(Orthanc::PixelFormat_RGBA32, tileWidth, tileHeight, false); + + Orthanc::ImageProcessing::Set(tile, 255 /* red */, 255 /* green */, + 255 /* blue */, 0 /* alpha - fully transparent */); + + Orthanc::PngWriter writer; + Orthanc::IImageWriter::WriteToMemory(writer, cachedTile, tile); + cachedWidth = tileWidth; + cachedHeight = tileHeight; + } + + answer = cachedTile; // Make a private copy to avoid mutex on "OrthancPluginAnswerBuffer()" + } + + OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, answer.c_str(), + answer.size(), Orthanc::EnumerationToString(Orthanc::MimeType_Png)); + } } diff -r f611fb47d0e8 -r 8ad12abde290 ViewerPlugin/RawTile.h --- a/ViewerPlugin/RawTile.h Wed Sep 11 13:43:39 2024 +0200 +++ b/ViewerPlugin/RawTile.h Wed Sep 11 16:11:16 2024 +0200 @@ -34,6 +34,7 @@ class RawTile : public boost::noncopyable { private: + bool isEmpty_; Orthanc::PixelFormat format_; unsigned int tileWidth_; unsigned int tileHeight_; @@ -53,11 +54,23 @@ unsigned int tileX, unsigned int tileY); - ImageCompression GetCompression() const + bool IsEmpty() const + { + return isEmpty_; + } + + unsigned int GetTileWidth() const { - return compression_; + return tileWidth_; } + unsigned int GetTileHeight() const + { + return tileHeight_; + } + + ImageCompression GetCompression() const; + void Answer(OrthancPluginRestOutput* output, Orthanc::MimeType encoding); @@ -72,5 +85,9 @@ static void InitializeTranscoderSemaphore(unsigned int maxThreads); static void FinalizeTranscoderSemaphore(); + + static void AnswerBackgroundTile(OrthancPluginRestOutput* output, + unsigned int tileWidth, + unsigned int tileHeight); }; }