summaryrefslogtreecommitdiff
path: root/wx+/image_resources.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'wx+/image_resources.cpp')
-rw-r--r--wx+/image_resources.cpp223
1 files changed, 119 insertions, 104 deletions
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 <memory>
#include <map>
#include <zen/utf.h>
-#include <zen/globals.h>
#include <zen/perf.h>
#include <zen/thread.h>
#include <zen/file_io.h>
@@ -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<uint32_t> buf(hqWidth * hqHeight + std::max(width * height, dpiWidth * dpiHeight));
+ std::vector<uint32_t> 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<std::vector<std::pair<std::string, ImageHolder>>>& 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<std::pair<std::string, ImageHolder>>& 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<std::string, wxBitmap> waitAndGetResult()
+ std::unordered_map<std::string, wxImage> waitAndGetResult()
{
threadGroup_->wait();
- std::map<std::string, wxBitmap> output;
+ std::unordered_map<std::string, wxImage> output;
result_.access([&](std::vector<std::pair<std::string, ImageHolder>>& r)
{
@@ -147,46 +135,48 @@ private:
//================================================================================================
//================================================================================================
-class GlobalBitmaps
+class ImageBuffer
{
public:
- static std::shared_ptr<GlobalBitmaps> instance()
- {
- static FunStatGlobal<GlobalBitmaps> inst;
- inst.initOnce([] { return std::make_unique<GlobalBitmaps>(); });
- 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<std::string, wxImage> imagesRaw_;
+ std::unordered_map<std::string, wxImage> imagesScaled_;
+
+ std::unique_ptr<HqParallelScaler> hqScaler_;
+
+ using OutImageKey = std::tuple<std::string /*name*/, int /*height*/>;
+
+ struct OutImageKeyHash
+ {
+ size_t operator()(const OutImageKey& imKey) const
+ {
+ const auto& [name, height] = imKey;
+
+ FNV1aHash<size_t> hash;
+ for (const char c : name)
+ hash.add(c);
- std::map<std::string, wxBitmap> bitmaps_;
- std::map<std::string, wxAnimation> anims_;
+ hash.add(height);
- std::unique_ptr<DpiParallelScaler> dpiScaler_;
+ return hash.get();
+ }
+ };
+ std::unordered_map<OutImageKey, wxImage, OutImageKeyHash> imagesOut_;
};
-void GlobalBitmaps::init(const Zstring& zipPath) //throw FileError
+ImageBuffer::ImageBuffer(const Zstring& zipPath) //throw FileError
{
- assert(bitmaps_.empty() && anims_.empty());
-
std::vector<std::pair<Zstring /*file name*/, std::string /*byte stream*/>> streams;
try //to load from ZIP first:
@@ -223,13 +213,12 @@ void GlobalBitmaps::init(const Zstring& zipPath) //throw FileError
const int hqScale = std::clamp<int>(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<DpiParallelScaler>(hqScale);
+ hqScaler_ = std::make_unique<HqParallelScaler>(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<std::string>(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<ImageBuffer> globalImageBuffer;
}
-void zen::initResourceImages(const Zstring& zipPath)
+void zen::imageResourcesInit(const Zstring& zipPath) //throw FileError
{
- if (std::shared_ptr<GlobalBitmaps> inst = GlobalBitmaps::instance())
- inst->init(zipPath);
- else
- assert(false);
+ assert(runningMainThread()); //wxWidgets is not thread-safe!
+ assert(!globalImageBuffer);
+ globalImageBuffer = std::make_unique<ImageBuffer>(zipPath); //throw FileError
}
-void zen::cleanupResourceImages()
+void zen::ImageResourcesCleanup()
{
- if (std::shared_ptr<GlobalBitmaps> 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<GlobalBitmaps> 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<GlobalBitmaps> inst = GlobalBitmaps::instance())
- return inst->getAnimation(name);
- assert(false);
- return wxNullAnimation;
+ return loadImage(name, maxSize, maxSize);
}
bgstack15