Skip to content

Commit

Permalink
non-visible-tiles-optimization for multi-tile-composition (#78)
Browse files Browse the repository at this point in the history
* clang-tidy fixes

* cosmetic

* first steps

* allow to save the bitmaps, add some timing

* update

* fix bug in "Fill_Gray8", add unittest with random rectangles

* cosmetic

* cosmetic

* cosmetic

* update - still wip

* typo

* update (still WIP)

* update

* cosmetic

* cosmetic

* cosmetic

* add option, add unittest

* ...

* clear background

* fix

* cosmetic

* shuffle the subblocks randomly

* cosmetic

* cosmetic

* start with "scaling-tile-accessor" - wip

* configure curl-build

* update

* refactor

* cosmetic

* update

* cleanup repo

* cleanup

* cleanup

* bump version

* update version-history

* cosmetic

* minor improvement

* typo

* cosmetic

* fix test

* documentation

* cosmetic

* Update Src/libCZI/SingleChannelAccessorBase.h

Co-authored-by: DaveyJonesBitPail <119518234+DaveyJonesBitPail@users.noreply.github.com>

* Update Src/libCZI/SingleChannelAccessorBase.h

Co-authored-by: DaveyJonesBitPail <119518234+DaveyJonesBitPail@users.noreply.github.com>

* Update Src/libCZI/SingleChannelAccessorBase.h

Co-authored-by: DaveyJonesBitPail <119518234+DaveyJonesBitPail@users.noreply.github.com>

* Update Src/libCZI/SingleChannelAccessorBase.h

Co-authored-by: DaveyJonesBitPail <119518234+DaveyJonesBitPail@users.noreply.github.com>

* Update Src/libCZI/SingleChannelAccessorBase.h

Co-authored-by: DaveyJonesBitPail <119518234+DaveyJonesBitPail@users.noreply.github.com>

* wip

* add test

* wip

* cosmetic

* cosmetic

* udate - still wip

* some more tests

* cosmetic

* cosmetic

* cosmetc, fix unrelated bug in CZICmd

* bump version, update version history

* cosmetic

* cosmetic

* cleanup

* cosmetic

* wording

* documentation

* cosmetic

* fix failing test (on Linux) because of non-idempotent sorting

---------

Co-authored-by: DaveyJonesBitPail <119518234+DaveyJonesBitPail@users.noreply.github.com>
  • Loading branch information
ptahmose and DaveyJonesBitPail committed Nov 20, 2023
1 parent c0ff6f9 commit 6497366
Show file tree
Hide file tree
Showing 23 changed files with 1,267 additions and 92 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.54.3
VERSION 0.55.0
HOMEPAGE_URL "https://github.com/ZEISS/libczi"
DESCRIPTION "libCZI is an Open Source Cross-Platform C++ library to read and write CZI")

Expand Down
4 changes: 3 additions & 1 deletion Src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ if (LIBCZI_BUILD_CURL_BASED_STREAM)
# configure libcurl-build as a static library, for possible options -> c.f. https://github.com/curl/curl/blob/master/CMakeLists.txt
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(BUILD_STATIC_LIBS ON CACHE BOOL "" FORCE)
set(BUILD_STATIC_CURL ON CACHE BOOL "" FORCE)
set(BUILD_CURL_EXE OFF CACHE BOOL "" FORCE)
set(CMAKE_POSITION_INDEPENDENT_CODE ON CACHE BOOL "" FORCE)
set(ENABLE_UNICODE ON CACHE BOOL "" FORCE)

Expand All @@ -59,4 +61,4 @@ endif(LIBCZI_BUILD_CZICMD)

if (LIBCZI_BUILD_UNITTESTS)
add_subdirectory(libCZI_UnitTests)
endif(LIBCZI_BUILD_UNITTESTS)
endif(LIBCZI_BUILD_UNITTESTS)
2 changes: 1 addition & 1 deletion Src/CZICmd/cmdlineoptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1263,7 +1263,7 @@ bool CCmdLineOptions::TryParseDisplaySettings(const std::string& s, std::map<int
{
for (const auto& it : vecChNoAndChannelInfo)
{
multiChannelCompositeChannelInfos->at(get<0>(it)) = get<1>(it);
multiChannelCompositeChannelInfos->operator[](get<0>(it)) = get<1>(it);
}
}

Expand Down
2 changes: 1 addition & 1 deletion Src/libCZI/BitmapOperations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ using namespace std;
for (int y = 0; y < h; ++y)
{
void* p = static_cast<char*>(ptr) + (y * static_cast<ptrdiff_t>(stride));
memset(p, val, stride);
memset(p, val, w);
}
}

Expand Down
2 changes: 1 addition & 1 deletion Src/libCZI/CreateBitmap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ static std::shared_ptr<libCZI::IBitmapData> CreateBitmapFromSubBlock_Uncompresse

const auto& sbBlkInfo = subBlk->GetSubBlockInfo();

// TODO: How exactly shoud the stride be derived? It seems that stride must be exactly linesize.
// The stride with an uncompressed bitmap in CZI is exactly the linesize.
const std::uint32_t stride = sbBlkInfo.physicalSize.w * CziUtils::GetBytesPerPel(sbBlkInfo.pixelType);
if (static_cast<size_t>(stride) * sbBlkInfo.physicalSize.h > size)
{
Expand Down
3 changes: 3 additions & 0 deletions Src/libCZI/Doc/version-history.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ version history {#version_history}
0.53.1 | [68](https://github.com/ZEISS/libczi/pull/68) | preserve the order of entries in attachment-directory (as they were added to the writer)
0.53.2 | [70](https://github.com/ZEISS/libczi/pull/70) | add option to write 'duplicate' subblocks
0.54.0 | [71](https://github.com/ZEISS/libczi/pull/71) | introduce 'streamsLib', add curl-based stream class
0.54.2 | [74](https://github.com/ZEISS/libczi/pull/74) | minor bug fix
0.54.3 | [79](https://github.com/ZEISS/libczi/pull/79) | add option _kCurlHttp_FollowLocation_ to follow HTTP redirects tp curl_http_inputstream
0.55.0 | [78](https://github.com/ZEISS/libczi/pull/78) | optimization: for multi-tile-composition, check relevant tiles for visibility before loading them (and do not load/decode non-visible tiles)
56 changes: 55 additions & 1 deletion Src/libCZI/SingleChannelAccessorBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "SingleChannelAccessorBase.h"
#include "BitmapOperations.h"
#include "utilities.h"

using namespace std;
using namespace libCZI;
Expand All @@ -14,7 +15,7 @@ bool CSingleChannelAccessorBase::TryGetPixelType(const libCZI::IDimCoordinate* p
planeCoordinate->TryGetPosition(libCZI::DimensionIndex::C, &c);

// the idea is: for the cornerstone-case where we do not have a C-index, the call to "TryGetSubBlockInfoOfArbitrarySubBlockInChannel"
// will igonore the specified index _if_ there are no C-indices at all
// will ignore the specified index _if_ there are no C-indices at all
pixeltype = Utils::TryDeterminePixelTypeForChannel(this->sbBlkRepository.get(), c);
return (pixeltype != PixelType::Invalid) ? true : false;
}
Expand Down Expand Up @@ -80,3 +81,56 @@ void CSingleChannelAccessorBase::CheckPlaneCoordinates(const libCZI::IDimCoordin
}
}
}

std::vector<int> CSingleChannelAccessorBase::CheckForVisibility(const libCZI::IntRect& roi, int count, const std::function<int(int)>& get_subblock_index) const
{
return CSingleChannelAccessorBase::CheckForVisibilityCore(
roi,
count,
get_subblock_index,
[this](int subblock_index) -> IntRect
{
SubBlockInfo subblock_info;
bool b = this->sbBlkRepository->TryGetSubBlockInfo(subblock_index, &subblock_info);
return subblock_info.logicalRect;
});
}

/*static*/std::vector<int> CSingleChannelAccessorBase::CheckForVisibilityCore(const libCZI::IntRect& roi, int count, const std::function<int(int)>& get_subblock_index, const std::function<libCZI::IntRect(int)>& get_rect_of_subblock)
{
std::vector<int> result;

// handle the trivial cases
if (count == 0 || !roi.IsNonEmpty())
{
return result;
}

const int64_t total_pixel_count = static_cast<int64_t>(roi.w) * roi.h;
result.reserve(count);
RectangleCoverageCalculator coverage_calculator;
int64_t covered_pixel_count = 0;
for (int i = count -1; i >= 0; --i) // we start at the end, because that is the subblock which is rendered last (and thus is on top)
{
const int subblock_index = get_subblock_index(i);
coverage_calculator.AddRectangle(get_rect_of_subblock(subblock_index));
const int64_t new_covered_pixel_count = coverage_calculator.CalcAreaOfIntersectionWithRectangle(roi);
if (new_covered_pixel_count > covered_pixel_count) // if the covered pixel count has increased, it means that this subblock covers some new pixels,
{ // some pixels which were not overdrawn by all the previous ones
// this means - when drawing this subblock, some new pixels will be covered which were not covered before,
// so we need to draw this subblock, therefore we add it to our result vector
result.push_back(subblock_index);

covered_pixel_count = new_covered_pixel_count;
if (new_covered_pixel_count == total_pixel_count)
{
// if the whole ROI is covered now, then we are done
break;
}
}
}

// now, reverse the result vector, so that the subblocks are in the order in which they are to be rendered
std::reverse(result.begin(), result.end());
return result;
}
47 changes: 46 additions & 1 deletion Src/libCZI/SingleChannelAccessorBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class CSingleChannelAccessorBase
protected:
std::shared_ptr<libCZI::ISubBlockRepository> sbBlkRepository;

explicit CSingleChannelAccessorBase(std::shared_ptr<libCZI::ISubBlockRepository> sbBlkRepository)
explicit CSingleChannelAccessorBase(const std::shared_ptr<libCZI::ISubBlockRepository>& sbBlkRepository)
: sbBlkRepository(sbBlkRepository)
{}

Expand All @@ -21,4 +21,49 @@ class CSingleChannelAccessorBase
static void Clear(libCZI::IBitmapData* bm, const libCZI::RgbFloatColor& floatColor);

void CheckPlaneCoordinates(const libCZI::IDimCoordinate* planeCoordinate) const;

/// This method is used to do a visibility test of a list of subblocks. The mode of operation is as follows:
/// - The method is given a ROI, and the number of subblocks to check.
/// - The functor 'get_subblock_index' is called with the argument being a counter, starting with count-1 and counting down to zero.
/// If called with value count-1, the subblock is the **last** one to be rendered; if called with value count-2, the subblock is the
/// second-to-the-last one to be rendered, and so on. If called with 0, the subblock is the **first** one to be rendered.
/// The value it returns is the subblock index (in the subblock repository) to check.
/// - The subblocks are assumed to be rendered in the order given, so the one we get by calling 'get_subblock_index' with
/// argument 0 is the first one to be rendered, the one with argument 1 is the second one, and so on. The rendering
/// is assumed to be done with the 'painter's algorithm", so what is rendered last is on top.
/// - We return a list of indices which are to be rendered, potentially leaving out some which have been determined
/// as not being visible. The indices returned are "indices as used by the 'get_subblock_index' functor", i.e.
/// it is **not** the subblock-index, but the argument that was passed to the functor.
/// - The caller can then use this list to render the subblocks (in the order as given in this vector).
///
/// \param roi The roi - if this is empty or invalid, then an empty vector is returned.
/// \param count Number of subblocks (specifying how many times the get_subblock_index-functor is being called).
/// \param get_subblock_index Functor which gives the subblock index to check. This index is the index in the subblock repository.
///
/// \returns A list of indices of "arguments to the functor which delivered a visible subblock". If the subblocks are rendered in the order
/// given here, then the result is guaranteed to be the same as if all subblocks were rendered.
std::vector<int> CheckForVisibility(const libCZI::IntRect& roi, int count, const std::function<int(int)>& get_subblock_index) const;

/// Do a visibility check for a list of subblocks. This is the core method, which is used by the public method 'CheckForVisibility'.
/// What this function does, is:
/// - The method is given a ROI, and the number of subblocks to check.
/// - The function 'get_subblock_index' will be called with the argument being a counter, starting with count-1 and counting down to zero.
/// If called with value count-1, the subblock is the **last** one to be rendered; if called with value count-2, the subblock is the
/// second-to-the-last one to be rendered, and so on. If called with 0, the subblock is the **first** one to be rendered.
/// - The value it returned by the functor 'get_subblock_index' is then used with the functor 'get_rect_of_subblock' to get the rectangle. The
/// index returned by 'get_subblock_index' is passed in to the functor 'get_rect_of_subblock'. The rectangle returned by 'get_rect_of_subblock'
/// is the rectangle of the subblock, the region where this subblock is rendered.
/// - We return a list of indices which are to be rendered, potentially leaving out some which have been determined
/// as not being visible. The indices returned are "indices as used by the 'get_subblock_index' functor", i.e.
/// it is **not** the subblock-index, but the argument that was passed to the functor.
///
/// \param roi The roi - if this is empty or invalid, then an empty vector is returned.
/// \param count Number of subblocks (specifying how many times the get_subblock_index-functor is being called).
/// \param get_subblock_index Functor which gives the subblock index for a given counter. The counter starts with count-1 and counts down to zero.
/// \param get_rect_of_subblock Functor which gives the subblock rectangle for a subblock-index (as returned by 'get_subblock_index').
///
/// \returns A list of indices of "arguments to the functor which delivered a visible subblock". If the subblocks are rendered in the order
/// given here, then the result is guaranteed to be the same as if all subblocks were rendered. Non-visible subblocks are not
/// part of this list.
static std::vector<int> CheckForVisibilityCore(const libCZI::IntRect& roi, int count, const std::function<int(int)>& get_subblock_index, const std::function<libCZI::IntRect(int)>& get_rect_of_subblock);
};
58 changes: 38 additions & 20 deletions Src/libCZI/SingleChannelPyramidLevelTileAccessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,20 @@
using namespace libCZI;
using namespace std;

CSingleChannelPyramidLevelTileAccessor::CSingleChannelPyramidLevelTileAccessor(std::shared_ptr<ISubBlockRepository> sbBlkRepository)
CSingleChannelPyramidLevelTileAccessor::CSingleChannelPyramidLevelTileAccessor(const std::shared_ptr<ISubBlockRepository>& sbBlkRepository)
: CSingleChannelAccessorBase(sbBlkRepository)
{
}

/*virtual*/std::shared_ptr<libCZI::IBitmapData> CSingleChannelPyramidLevelTileAccessor::Get(const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const ISingleChannelPyramidLayerTileAccessor::Options* pOptions)
{
if (pOptions == nullptr) { Options opt; opt.Clear(); return this->Get(roi, planeCoordinate, pyramidInfo, &opt); }
if (pOptions == nullptr)
{
Options opt;
opt.Clear();
return this->Get(roi, planeCoordinate, pyramidInfo, &opt);
}

libCZI::PixelType pixelType;
const bool b = this->TryGetPixelType(planeCoordinate, pixelType);
if (b == false)
Expand All @@ -30,7 +36,13 @@ CSingleChannelPyramidLevelTileAccessor::CSingleChannelPyramidLevelTileAccessor(s

/*virtual*/std::shared_ptr<libCZI::IBitmapData> CSingleChannelPyramidLevelTileAccessor::Get(libCZI::PixelType pixeltype, const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const libCZI::ISingleChannelPyramidLayerTileAccessor::Options* pOptions)
{
if (pOptions == nullptr) { Options opt; opt.Clear(); return this->Get(pixeltype, roi, planeCoordinate, pyramidInfo, &opt); }
if (pOptions == nullptr)
{
Options opt;
opt.Clear();
return this->Get(pixeltype, roi, planeCoordinate, pyramidInfo, &opt);
}

const int sizeOfPixel = CalcSizeOfPixelOnLayer0(pyramidInfo);
const IntSize sizeOfBitmap{ static_cast<std::uint32_t>(roi.w / sizeOfPixel),static_cast<std::uint32_t>(roi.h / sizeOfPixel) };
if (sizeOfBitmap.w == 0 || sizeOfBitmap.h == 0)
Expand All @@ -46,7 +58,14 @@ CSingleChannelPyramidLevelTileAccessor::CSingleChannelPyramidLevelTileAccessor(s

/*virtual*/void CSingleChannelPyramidLevelTileAccessor::Get(libCZI::IBitmapData* pDest, int xPos, int yPos, 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); return; }
if (pOptions == nullptr)
{
Options opt;
opt.Clear();
this->Get(pDest, xPos, yPos, planeCoordinate, pyramidInfo, &opt);
return;
}

const int sizeOfPixel = CalcSizeOfPixelOnLayer0(pyramidInfo);
this->InternalGet(pDest, xPos, yPos, sizeOfPixel, planeCoordinate, pyramidInfo, *pOptions);
}
Expand Down Expand Up @@ -74,7 +93,7 @@ void CSingleChannelPyramidLevelTileAccessor::InternalGet(libCZI::IBitmapData* pD
});
}

void CSingleChannelPyramidLevelTileAccessor::ComposeTiles(libCZI::IBitmapData* bm, int xPos, int yPos, int sizeOfPixel, int bitmapCnt, const Options& options, std::function<SbInfo(int)> getSbInfo)
void CSingleChannelPyramidLevelTileAccessor::ComposeTiles(libCZI::IBitmapData* bm, int xPos, int yPos, int sizeOfPixel, int bitmapCnt, const Options& options, const std::function<SbInfo(int)>& getSbInfo)
{
Compositors::ComposeSingleTileOptions composeOptions; composeOptions.Clear();
composeOptions.drawTileBorder = options.drawTileBorder;
Expand All @@ -92,13 +111,12 @@ void CSingleChannelPyramidLevelTileAccessor::ComposeTiles(libCZI::IBitmapData* b
return true;
}

return false;
return false;
},
bm,
0,
0,
&composeOptions);

0,
0,
&composeOptions);
}

libCZI::IntRect CSingleChannelPyramidLevelTileAccessor::CalcDestinationRectFromSourceRect(const libCZI::IntRect& roi, const PyramidLayerInfo& pyramidInfo)
Expand Down Expand Up @@ -226,16 +244,16 @@ void CSingleChannelPyramidLevelTileAccessor::GetAllSubBlocks(const libCZI::IntRe
}
}

if (Utilities::DoIntersect(roi, info.logicalRect))
{
SbInfo sbinfo;
sbinfo.logicalRect = info.logicalRect;
sbinfo.physicalSize = info.physicalSize;
sbinfo.mIndex = info.mIndex;
sbinfo.index = idx;
appender(sbinfo);
}
if (Utilities::DoIntersect(roi, info.logicalRect))
{
SbInfo sbinfo;
sbinfo.logicalRect = info.logicalRect;
sbinfo.physicalSize = info.physicalSize;
sbinfo.mIndex = info.mIndex;
sbinfo.index = idx;
appender(sbinfo);
}

return true;
return true;
});
}
4 changes: 2 additions & 2 deletions Src/libCZI/SingleChannelPyramidLevelTileAccessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class CSingleChannelPyramidLevelTileAccessor : public CSingleChannelAccessorBase
};

public:
explicit CSingleChannelPyramidLevelTileAccessor(std::shared_ptr<libCZI::ISubBlockRepository> sbBlkRepository);
explicit CSingleChannelPyramidLevelTileAccessor(const std::shared_ptr<libCZI::ISubBlockRepository>& sbBlkRepository);

public: // interface ISingleChannelTileAccessor
std::shared_ptr<libCZI::IBitmapData> Get(const libCZI::IntRect& roi, const libCZI::IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const Options* pOptions) override;
Expand All @@ -51,7 +51,7 @@ class CSingleChannelPyramidLevelTileAccessor : public CSingleChannelAccessorBase

int CalcPyramidLayerNo(const libCZI::IntRect& logicalRect, const libCZI::IntSize& physicalSize, int minificationFactor);

void ComposeTiles(libCZI::IBitmapData* bm, int xPos, int yPos, int sizeOfPixel, int bitmapCnt, const Options& options, std::function<SbInfo(int)> getSbInfo);
void ComposeTiles(libCZI::IBitmapData* bm, int xPos, int yPos, int sizeOfPixel, int bitmapCnt, const Options& options, const std::function<SbInfo(int)>& getSbInfo);

void InternalGet(libCZI::IBitmapData* pDest, int xPos, int yPos, int sizeOfPixelOnLayer0, const libCZI::IDimCoordinate* planeCoordinate, const PyramidLayerInfo& pyramidInfo, const Options& options);
};
Loading

0 comments on commit 6497366

Please sign in to comment.