From c95b3937fef3e2c63768f1b3b1dc2c898f23d91d Mon Sep 17 00:00:00 2001 From: B Stack Date: Wed, 22 Jul 2020 11:37:03 -0400 Subject: add upstream 11.0 --- wx+/image_resources.cpp | 223 ++++++++++++++++++++++++++---------------------- 1 file changed, 119 insertions(+), 104 deletions(-) (limited to 'wx+/image_resources.cpp') diff --git a/wx+/image_resources.cpp b/wx+/image_resources.cpp index 5328b845..d09e5dfa 100644 --- a/wx+/image_resources.cpp +++ b/wx+/image_resources.cpp @@ -5,10 +5,8 @@ // ***************************************************************************** #include "image_resources.h" -#include #include #include -#include #include #include #include @@ -27,20 +25,19 @@ using namespace zen; namespace { -ImageHolder dpiScale(int width, int height, int dpiWidth, int dpiHeight, const unsigned char* imageRgb, const unsigned char* imageAlpha, int hqScale) +ImageHolder xbrzScale(int width, int height, const unsigned char* imageRgb, const unsigned char* imageAlpha, int hqScale) { - assert(imageRgb && imageAlpha); //see convertToVanillaImage() - if (width <= 0 || height <= 0 || dpiWidth <= 0 || dpiHeight <= 0) + assert(imageRgb && imageAlpha && width > 0 && height > 0); //see convertToVanillaImage() + if (width <= 0 || height <= 0) return ImageHolder(0, 0, true /*withAlpha*/); const int hqWidth = width * hqScale; const int hqHeight = height * hqScale; //get rid of allocation and buffer std::vector<> at thread-level? => no discernable perf improvement - std::vector buf(hqWidth * hqHeight + std::max(width * height, dpiWidth * dpiHeight)); + std::vector buf(hqWidth * hqHeight + width * height); uint32_t* const argbSrc = &buf[0] + hqWidth * hqHeight; uint32_t* const xbrTrg = &buf[0]; - uint32_t* const dpiTrg = argbSrc; //convert RGB (RGB byte order) to ARGB (BGRA byte order) { @@ -60,18 +57,13 @@ ImageHolder dpiScale(int width, int height, int dpiWidth, int dpiHeight, const u xbrz::ColorFormat::ARGB_UNBUFFERED); //ColorFormat colFmt, //test: total xBRZ scaling time with ARGB: 300ms, ARGB_UNBUFFERED: 50ms //----------------------------------------------------- - xbrz::bilinearScale(xbrTrg, //const uint32_t* src, - hqWidth, hqHeight, //int srcWidth, int srcHeight, - dpiTrg, //uint32_t* trg, - dpiWidth, dpiHeight); //int trgWidth, int trgHeight - //----------------------------------------------------- //convert BGRA to RGB + alpha - ImageHolder trgImg(dpiWidth, dpiHeight, true /*withAlpha*/); + ImageHolder trgImg(hqWidth, hqHeight, true /*withAlpha*/); { unsigned char* rgb = trgImg.getRgb(); unsigned char* alpha = trgImg.getAlpha(); - std::for_each(dpiTrg, dpiTrg + dpiWidth * dpiHeight, [&](uint32_t col) + std::for_each(xbrTrg, xbrTrg + hqWidth * hqHeight, [&](uint32_t col) { *alpha++ = xbrz::getAlpha(col); *rgb++ = xbrz::getRed (col); @@ -86,28 +78,24 @@ ImageHolder dpiScale(int width, int height, int dpiWidth, int dpiHeight, const u auto getScalerTask(const std::string& imageName, const wxImage& img, int hqScale, Protected>>& result) { return [imageName, - width = img.GetWidth(), - height = img.GetHeight(), - dpiWidth = fastFromDIP(img.GetWidth()), - dpiHeight = fastFromDIP(img.GetHeight()), //don't call (wxWidgets function!) fastFromDIP() from worker thread - rgb = img.GetData(), - alpha = img.GetAlpha(), + width = img.GetWidth(), // + height = img.GetHeight(), //don't call wxWidgets functions from worker thread + rgb = img.GetData(), // + alpha = img.GetAlpha(), // hqScale, &result] { - ImageHolder ih = dpiScale(width, height, - dpiWidth, dpiHeight, - rgb, alpha, hqScale); + ImageHolder ih = xbrzScale(width, height, rgb, alpha, hqScale); result.access([&](std::vector>& r) { r.emplace_back(imageName, std::move(ih)); }); }; } -class DpiParallelScaler +class HqParallelScaler { public: - DpiParallelScaler(int hqScale) : hqScale_(hqScale) { assert(hqScale > 1); } + HqParallelScaler(int hqScale) : hqScale_(hqScale) { assert(hqScale > 1); } - ~DpiParallelScaler() { threadGroup_ = {}; } //DpiParallelScaler must out-live threadGroup!!! + ~HqParallelScaler() { threadGroup_ = {}; } //imgKeeper_ must out-live threadGroup!!! void add(const std::string& imageName, const wxImage& img) { @@ -115,11 +103,11 @@ public: threadGroup_->run(getScalerTask(imageName, img, hqScale_, result_)); } - std::map waitAndGetResult() + std::unordered_map waitAndGetResult() { threadGroup_->wait(); - std::map output; + std::unordered_map output; result_.access([&](std::vector>& r) { @@ -147,46 +135,48 @@ private: //================================================================================================ //================================================================================================ -class GlobalBitmaps +class ImageBuffer { public: - static std::shared_ptr instance() - { - static FunStatGlobal inst; - inst.initOnce([] { return std::make_unique(); }); - assert(runningMainThread()); //wxWidgets is not thread-safe! - return inst.get(); - } + explicit ImageBuffer(const Zstring& filePath); //throw FileError - GlobalBitmaps() {} - ~GlobalBitmaps() { assert(bitmaps_.empty() && anims_.empty()); } //don't leave wxWidgets objects for static destruction! + const wxImage& getImage(const std::string& name, int maxWidth /*optional*/, int maxHeight /*optional*/); - void init(const Zstring& filePath); - void cleanup() - { - bitmaps_.clear(); - anims_ .clear(); - dpiScaler_.reset(); - } +private: + ImageBuffer (const ImageBuffer&) = delete; + ImageBuffer& operator=(const ImageBuffer&) = delete; - const wxBitmap& getImage (const std::string& name); - const wxAnimation& getAnimation(const std::string& name) const; + const wxImage& getRawImage (const std::string& name); + const wxImage& getScaledImage(const std::string& name); -private: - GlobalBitmaps (const GlobalBitmaps&) = delete; - GlobalBitmaps& operator=(const GlobalBitmaps&) = delete; + std::unordered_map imagesRaw_; + std::unordered_map imagesScaled_; + + std::unique_ptr hqScaler_; + + using OutImageKey = std::tuple; + + struct OutImageKeyHash + { + size_t operator()(const OutImageKey& imKey) const + { + const auto& [name, height] = imKey; + + FNV1aHash hash; + for (const char c : name) + hash.add(c); - std::map bitmaps_; - std::map anims_; + hash.add(height); - std::unique_ptr dpiScaler_; + return hash.get(); + } + }; + std::unordered_map imagesOut_; }; -void GlobalBitmaps::init(const Zstring& zipPath) //throw FileError +ImageBuffer::ImageBuffer(const Zstring& zipPath) //throw FileError { - assert(bitmaps_.empty() && anims_.empty()); - std::vector> streams; try //to load from ZIP first: @@ -223,13 +213,12 @@ void GlobalBitmaps::init(const Zstring& zipPath) //throw FileError const int hqScale = std::clamp(std::ceil(fastFromDIP(1000) / 1000.0), 1, xbrz::SCALE_FACTOR_MAX); //even for 125% DPI scaling, "2xBRZ + bilinear downscale" gives a better result than mere "125% bilinear upscale"! if (hqScale > 1) - dpiScaler_ = std::make_unique(hqScale); + hqScaler_ = std::make_unique(hqScale); for (const auto& [fileName, stream] : streams) if (endsWith(fileName, Zstr(".png"))) { wxMemoryInputStream wxstream(stream.c_str(), stream.size()); //stream does not take ownership of data - //bonus: work around wxWidgets bug: wxAnimation::Load() requires seekable input stream (zip-input stream is not seekable) wxImage img(wxstream, wxBITMAP_TYPE_PNG); assert(img.IsOk()); @@ -239,88 +228,114 @@ void GlobalBitmaps::init(const Zstring& zipPath) //throw FileError convertToVanillaImage(img); const std::string imageName = utfTo(beforeLast(fileName, Zstr("."), IF_MISSING_RETURN_NONE)); - if (dpiScaler_) - dpiScaler_->add(imageName, img); //scale in parallel! + + imagesRaw_.emplace(imageName, img); + if (hqScaler_) + hqScaler_->add(imageName, img); //scale in parallel! else - bitmaps_.emplace(imageName, img); + imagesScaled_.emplace(imageName, img); - //alternative: wxBitmap::NewFromPNGData(stream.c_str(), stream.size())? - // => Windows: just a (slow!) wrapper for wxBitmpat(wxImage())! + //wxBitmap::NewFromPNGData(stream.c_str(), stream.size())? + // => Windows: just a (slow!) wrapper for wxBitmap(wxImage())! } -#if 0 - else if (endsWith(fileName, Zstr(".gif"))) - { - [[maybe_unused]] const bool loadSuccess = anims_[fileName].Load(wxstream, wxANIMATION_TYPE_GIF); - assert(loadSuccess); - } -#endif else assert(false); } -const wxBitmap& GlobalBitmaps::getImage(const std::string& name) +const wxImage& ImageBuffer::getRawImage(const std::string& name) +{ + if (auto it = imagesRaw_.find(name); + it != imagesRaw_.end()) + return it->second; + + assert(false); + return wxNullImage; +} + + +const wxImage& ImageBuffer::getScaledImage(const std::string& name) { - //test: this function is first called about 220ms after GlobalBitmaps::init() has ended + //test: this function is first called about 220ms after ImageBuffer::ImageBuffer() has ended // => should be enough time to finish xBRZ scaling in parallel (which takes 50ms) //debug perf: extra 800-1000ms during startup - if (dpiScaler_) + if (hqScaler_) { - bitmaps_ = dpiScaler_->waitAndGetResult(); - dpiScaler_.reset(); + imagesScaled_ = hqScaler_->waitAndGetResult(); + hqScaler_.reset(); } - if (auto it = bitmaps_.find(name); - it != bitmaps_.end()) + if (auto it = imagesScaled_.find(name); + it != imagesScaled_.end()) return it->second; assert(false); - return wxNullBitmap; + return wxNullImage; } -const wxAnimation& GlobalBitmaps::getAnimation(const std::string& name) const +const wxImage& ImageBuffer::getImage(const std::string& name, int maxWidth /*optional*/, int maxHeight /*optional*/) { - if (auto it = anims_.find(name); - it != anims_.end()) - return it->second; - assert(false); - return wxNullAnimation; + const wxImage& rawImg = getRawImage(name); + + const wxSize dpiSize(fastFromDIP(rawImg.GetWidth ()), + fastFromDIP(rawImg.GetHeight())); + + int outHeight = dpiSize.y; + if (maxWidth >= 0 && maxWidth < dpiSize.x) + outHeight = rawImg.GetHeight() * maxWidth / rawImg.GetWidth(); + + if (maxHeight >= 0 && maxHeight < outHeight) + outHeight = maxHeight; + + const OutImageKey imkey{name, outHeight}; + + auto it = imagesOut_.find(imkey); + if (it == imagesOut_.end()) + { + if (rawImg.GetHeight() >= outHeight) //=> skip needless xBRZ upscaling + it = imagesOut_.emplace(imkey, shrinkImage(rawImg, -1 /*maxWidth*/, outHeight)).first; + else if (rawImg.GetHeight() >= 0.9 * outHeight) //almost there: also no need for xBRZ-scale + //however: for 125% DPI scaling, "2xBRZ + bilinear downscale" gives a better result than mere "125% bilinear upscale"! + it = imagesOut_.emplace(imkey, rawImg.Scale(rawImg.GetWidth() * outHeight / rawImg.GetHeight(), outHeight, wxIMAGE_QUALITY_BILINEAR)).first; + else + it = imagesOut_.emplace(imkey, shrinkImage(getScaledImage(name), -1 /*maxWidth*/, outHeight)).first; + } + return it->second; } + + +std::unique_ptr globalImageBuffer; } -void zen::initResourceImages(const Zstring& zipPath) +void zen::imageResourcesInit(const Zstring& zipPath) //throw FileError { - if (std::shared_ptr inst = GlobalBitmaps::instance()) - inst->init(zipPath); - else - assert(false); + assert(runningMainThread()); //wxWidgets is not thread-safe! + assert(!globalImageBuffer); + globalImageBuffer = std::make_unique(zipPath); //throw FileError } -void zen::cleanupResourceImages() +void zen::ImageResourcesCleanup() { - if (std::shared_ptr inst = GlobalBitmaps::instance()) - inst->cleanup(); - else - assert(false); + assert(runningMainThread()); //wxWidgets is not thread-safe! + assert(globalImageBuffer); + globalImageBuffer.reset(); } -const wxBitmap& zen::getResourceImage(const std::string& name) +const wxImage& zen::loadImage(const std::string& name, int maxWidth /*optional*/, int maxHeight /*optional*/) { - if (std::shared_ptr inst = GlobalBitmaps::instance()) - return inst->getImage(name); - assert(false); - return wxNullBitmap; + assert(runningMainThread()); //wxWidgets is not thread-safe! + assert(globalImageBuffer); + if (globalImageBuffer) + return globalImageBuffer->getImage(name, maxWidth, maxHeight); + return wxNullImage; } -const wxAnimation& zen::getResourceAnimation(const std::string& name) +const wxImage& zen::loadImage(const std::string& name, int maxSize) { - if (std::shared_ptr inst = GlobalBitmaps::instance()) - return inst->getAnimation(name); - assert(false); - return wxNullAnimation; + return loadImage(name, maxSize, maxSize); } -- cgit