diff options
Diffstat (limited to 'wx+/image_resources.cpp')
-rwxr-xr-x | wx+/image_resources.cpp | 258 |
1 files changed, 235 insertions, 23 deletions
diff --git a/wx+/image_resources.cpp b/wx+/image_resources.cpp index d4547d35..e87b245c 100755 --- a/wx+/image_resources.cpp +++ b/wx+/image_resources.cpp @@ -9,12 +9,17 @@ #include <map> #include <zen/utf.h> #include <zen/globals.h> +#include <zen/perf.h> #include <zen/thread.h> #include <wx/wfstream.h> #include <wx/zipstrm.h> #include <wx/image.h> #include <wx/mstream.h> +#include <xBRZ/src/xbrz.h> +#include <xBRZ/src/xbrz_tools.h> #include "image_tools.h" +#include "image_holder.h" +#include "dc.h" using namespace zen; @@ -22,6 +27,195 @@ using namespace zen; namespace { + +ImageHolder dpiScale(int width, int height, int dpiWidth, int dpiHeight, const unsigned char* imageRgb, const unsigned char* imageAlpha, int hqScale) +{ + assert(imageRgb && imageAlpha); //see convertToVanillaImage() + if (width <= 0 || height <= 0 || dpiWidth <= 0 || dpiHeight <= 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)); + 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) + { + const unsigned char* rgb = imageRgb; + const unsigned char* rgbEnd = rgb + 3 * width * height; + const unsigned char* alpha = imageAlpha; + uint32_t* out = argbSrc; + + for (; rgb < rgbEnd; rgb += 3) + *out++ = xbrz::makePixel(*alpha++, rgb[0], rgb[1], rgb[2]); + } + //----------------------------------------------------- + xbrz::scale(hqScale, //size_t factor, //valid range: 2 - SCALE_FACTOR_MAX + argbSrc, //const uint32_t* src, + xbrTrg, //uint32_t* trg, + width, height, //int srcWidth, int srcHeight, + 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*/); + { + unsigned char* rgb = trgImg.getRgb(); + unsigned char* alpha = trgImg.getAlpha(); + + std::for_each(dpiTrg, dpiTrg + dpiWidth * dpiHeight, [&](uint32_t col) + { + *alpha++ = xbrz::getAlpha(col); + *rgb++ = xbrz::getRed (col); + *rgb++ = xbrz::getGreen(col); + *rgb++ = xbrz::getBlue (col); + }); + } + return trgImg; +} + + +struct WorkItem +{ + Zbase<wchar_t> name; + int width = 0; + int height = 0; + int dpiWidth = 0; + int dpiHeight = 0; + const unsigned char* rgb = nullptr; + const unsigned char* alpha = nullptr; +}; + + +class WorkLoad +{ +public: + void add(const WorkItem& wi) //context of main thread + { + assert(std::this_thread::get_id() == mainThreadId); + { + std::lock_guard<std::mutex> dummy(lockWork_); + workLoad_.push_back(wi); + } + conditionNewWork_.notify_all(); + } + + void noMoreWork() + { + assert(std::this_thread::get_id() == mainThreadId); + { + std::lock_guard<std::mutex> dummy(lockWork_); + expectMoreWork_ = false; + } + conditionNewWork_.notify_all(); + } + + //context of worker thread, blocking: + Opt<WorkItem> extractNext() //throw ThreadInterruption + { + assert(std::this_thread::get_id() != mainThreadId); + std::unique_lock<std::mutex> dummy(lockWork_); + + interruptibleWait(conditionNewWork_, dummy, [this] { return !workLoad_.empty() || !expectMoreWork_; }); //throw ThreadInterruption + + if (workLoad_.empty()) + return NoValue(); + + WorkItem wi = workLoad_.back(); // + workLoad_.pop_back(); //yes, no strong exception guarantee (std::bad_alloc) + return wi; // + } + +private: + bool expectMoreWork_ = true; + std::vector<WorkItem> workLoad_; + std::mutex lockWork_; + std::condition_variable conditionNewWork_; //signal event: data for processing available +}; + + +class DpiParallelScaler +{ +public: + DpiParallelScaler(int hqScale) + { + assert(hqScale > 1); + const int threadCount = std::max<int>(std::thread::hardware_concurrency(), 1); //hardware_concurrency() == 0 if "not computable or well defined" + + for (int i = 0; i < threadCount; ++i) + worker_.push_back([hqScale, &workload = workload_, &result = result_] + { + setCurrentThreadName("xBRZ Scaler"); + while (Opt<WorkItem> wi = workload.extractNext()) //throw ThreadInterruption + { + ImageHolder ih = dpiScale(wi->width, wi->height, + wi->dpiWidth, wi->dpiHeight, + wi->rgb, wi->alpha, hqScale); + result.access([&](std::vector<std::pair<Zbase<wchar_t>, ImageHolder>>& r) { r.emplace_back(wi->name, std::move(ih)); }); + } + }); + } + + ~DpiParallelScaler() + { + for (InterruptibleThread& w : worker_) + w.interrupt(); + + for (InterruptibleThread& w : worker_) + if (w.joinable()) + w.join(); + } + + void add(const wxString& name, const wxImage& img) + { + imgKeeper_.push_back(img); //retain (ref-counted) wxImage so that the rgb/alpha pointers remain valid after passed to threads + workload_.add({ copyStringTo<Zbase<wchar_t>>(name), + img.GetWidth(), img.GetHeight(), + fastFromDIP(img.GetWidth()), fastFromDIP(img.GetHeight()), //don't call fastFromDIP() from worker thread (wxWidgets function!) + img.GetData(), img.GetAlpha() }); + } + + std::map<wxString, wxBitmap> waitAndGetResult() + { + workload_.noMoreWork(); + + for (InterruptibleThread& w : worker_) + w.join(); + + std::map<wxString, wxBitmap> output; + + result_.access([&](std::vector<std::pair<Zbase<wchar_t>, ImageHolder>>& r) + { + for (auto& item : r) + { + ImageHolder& ih = item.second; + + wxImage img(ih.getWidth(), ih.getHeight(), ih.releaseRgb(), false /*static_data*/); //pass ownership + img.SetAlpha(ih.releaseAlpha(), false /*static_data*/); + + output[utfTo<wxString>(item.first)] = wxBitmap(img); + } + }); + return output; + } + +private: + std::vector<InterruptibleThread> worker_; + WorkLoad workload_; + Protected<std::vector<std::pair<Zbase<wchar_t>, ImageHolder>>> result_; + std::vector<wxImage> imgKeeper_; +}; + + void loadAnimFromZip(wxZipInputStream& zipInput, wxAnimation& anim) { //work around wxWidgets bug: @@ -39,6 +233,8 @@ void loadAnimFromZip(wxZipInputStream& zipInput, wxAnimation& anim) anim.Load(seekAbleStream, wxANIMATION_TYPE_GIF); } +//================================================================================================ +//================================================================================================ class GlobalBitmaps { @@ -51,32 +247,35 @@ public: } GlobalBitmaps() {} - ~GlobalBitmaps() { assert(bitmaps.empty() && anims.empty()); } //don't leave wxWidgets objects for static destruction! + ~GlobalBitmaps() { assert(bitmaps_.empty() && anims_.empty()); } //don't leave wxWidgets objects for static destruction! void init(const Zstring& filepath); void cleanup() { - bitmaps.clear(); - anims.clear(); + bitmaps_.clear(); + anims_ .clear(); + dpiScaler_.reset(); } - const wxBitmap& getImage (const wxString& name) const; + const wxBitmap& getImage (const wxString& name); const wxAnimation& getAnimation(const wxString& name) const; private: GlobalBitmaps (const GlobalBitmaps&) = delete; GlobalBitmaps& operator=(const GlobalBitmaps&) = delete; - std::map<wxString, wxBitmap> bitmaps; - std::map<wxString, wxAnimation> anims; + std::map<wxString, wxBitmap> bitmaps_; + std::map<wxString, wxAnimation> anims_; + + std::unique_ptr<DpiParallelScaler> dpiScaler_; }; -void GlobalBitmaps::init(const Zstring& filepath) +void GlobalBitmaps::init(const Zstring& filePath) { - assert(bitmaps.empty() && anims.empty()); + assert(bitmaps_.empty() && anims_.empty()); - wxFFileInputStream input(utfTo<wxString>(filepath)); + wxFFileInputStream input(utfTo<wxString>(filePath)); if (input.IsOk()) //if not... we don't want to react too harsh here { //activate support for .png files @@ -85,36 +284,49 @@ void GlobalBitmaps::init(const Zstring& filepath) wxZipInputStream streamIn(input, wxConvUTF8); //do NOT rely on wxConvLocal! On failure shows unhelpful popup "Cannot convert from the charset 'Unknown encoding (-1)'!" - for (;;) - { - std::unique_ptr<wxZipEntry> entry(streamIn.GetNextEntry()); //take ownership! - if (!entry) - break; + //do we need xBRZ scaling for high quality DPI images? + const int hqScale = numeric::clampCpy<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); + while (const auto& entry = std::unique_ptr<wxZipEntry>(streamIn.GetNextEntry())) //take ownership!) + { const wxString name = entry->GetName(); - //generic image loading if (endsWith(name, L".png")) { wxImage img(streamIn, wxBITMAP_TYPE_PNG); //end this alpha/no-alpha/mask/wxDC::DrawBitmap/RTL/high-contrast-scheme interoperability nightmare here and now!!!! - //=> there's only one type of png image: with alpha channel, no mask!!! + //=> there's only one type of wxImage: with alpha channel, no mask!!! convertToVanillaImage(img); - bitmaps.emplace(name, img); + if (dpiScaler_) + dpiScaler_->add(name, img); //scale in parallel! + else + bitmaps_.emplace(name, img); } else if (endsWith(name, L".gif")) - loadAnimFromZip(streamIn, anims[name]); + loadAnimFromZip(streamIn, anims_[name]); } } } -const wxBitmap& GlobalBitmaps::getImage(const wxString& name) const +const wxBitmap& GlobalBitmaps::getImage(const wxString& name) { - auto it = bitmaps.find(contains(name, L'.') ? name : name + L".png"); //assume .png ending if nothing else specified - if (it != bitmaps.end()) + //test: this function is first called about 220ms after GlobalBitmaps::init() has ended + // => should be enough time to finish xBRZ scaling in parallel (which takes 50ms) + //debug perf: extra 800-1000ms during startup + if (dpiScaler_) + { + bitmaps_ = dpiScaler_->waitAndGetResult(); + dpiScaler_.reset(); + } + + auto it = bitmaps_.find(contains(name, L'.') ? name : name + L".png"); //assume .png ending if nothing else specified + if (it != bitmaps_.end()) return it->second; assert(false); return wxNullBitmap; @@ -123,8 +335,8 @@ const wxBitmap& GlobalBitmaps::getImage(const wxString& name) const const wxAnimation& GlobalBitmaps::getAnimation(const wxString& name) const { - auto it = anims.find(contains(name, L'.') ? name : name + L".gif"); - if (it != anims.end()) + auto it = anims_.find(contains(name, L'.') ? name : name + L".gif"); + if (it != anims_.end()) return it->second; assert(false); return wxNullAnimation; |