From d64dee78347a864bb41ea623efc056a4e34aaed8 Mon Sep 17 00:00:00 2001 From: Twan van Laarhoven Date: Tue, 28 Apr 2020 22:46:34 +0200 Subject: [PATCH] Fix: images were not being loaded from zip files. wxImage::LoadFile requires the file to be seekable to peek at the header. We work around this by buffering the header in a wrapper class. --- src/gui/util.cpp | 90 ++++++++++++++++++++++++++++++++++++++++- src/util/io/package.cpp | 8 ++-- 2 files changed, 93 insertions(+), 5 deletions(-) diff --git a/src/gui/util.cpp b/src/gui/util.cpp index 64b0b2d0..13692c7f 100644 --- a/src/gui/util.cpp +++ b/src/gui/util.cpp @@ -133,9 +133,95 @@ void draw_checker(RotatedDC& dc, const RealRect& rect) { // ----------------------------------------------------------------------------- : Image related +/// A wxInputStream in which we can seek, even if the underlying stream can't +/** Seeking is used by the wxImage format detection code, to peek at the header. + * So we only need to be able to seek back to the beginning. + * The solution is to keep a small buffer of the file header. + */ +class SeekableInputStream : public wxInputStream { +public: + SeekableInputStream(wxInputStream& stream) + : stream(stream) { + } + bool IsOk() const override { + return stream.IsOk(); + } + bool IsSeekable() const override { + return pos < BUFFER_SIZE; + } + size_t LastRead() const override { + return last_read; + } + char Peek() override { + if (pos < stream_pos) { + return buffer[pos]; + } else { + return stream.Peek(); + } + } + wxInputStream& Read(void* out_buffer, size_t size) override { + last_read = 0; + if (pos < stream_pos) { + // take from buffer + size_t n = min(size, min((size_t)stream_pos, BUFFER_SIZE) - (size_t)pos); + memcpy(out_buffer, buffer + pos, n); + size -= n; + out_buffer = (char*)out_buffer + n; + last_read += n; + pos += n; + } + if (size > 0) { + assert(pos == stream_pos); + // take from stream + stream.Read(out_buffer, size); + size_t read = stream.LastRead(); + last_read += read; + if (pos < BUFFER_SIZE) { + // update buffer + memcpy(buffer + pos, out_buffer, min(BUFFER_SIZE - (size_t)pos, read)); + } + stream_pos += read; + pos += read; + } + return *this; + } + wxFileOffset SeekI(wxFileOffset target, wxSeekMode mode = wxFromStart) override { + assert(mode == wxFromStart); + assert(target < BUFFER_SIZE); + if (target < BUFFER_SIZE) { + pos = target; + return target; + } else { + return wxInvalidOffset; + } + } + wxFileOffset TellI() const override { + return pos; + } +protected: + size_t OnSysRead(void* out_buffer, size_t size) override { + Read(out_buffer, size); + return last_read; + } +private: + wxInputStream& stream; + static const size_t BUFFER_SIZE = 16; + Byte buffer[BUFFER_SIZE]; + // positions: + // Invariant: pos < stream_pos ==> pos < BUFFER_SIZE && buffer[i] is valid for i<=pos + wxFileOffset stream_pos = 0; // position in underlying stream + wxFileOffset pos = 0; // position taking into account any seeking done. + size_t last_read = 0; +}; + bool image_load_file(Image& image, wxInputStream &stream) { - wxLogNull noLog; - return image.LoadFile(stream); + wxLogNull noLog; // prevent wx from showing popups about sRGB profiles and other notices + if (!stream.IsSeekable()) { + SeekableInputStream seek_stream(stream); + return image.LoadFile(seek_stream); + } else { + return image.LoadFile(stream); + } } bool image_load_file(Image& image, const wxString &name) { diff --git a/src/util/io/package.cpp b/src/util/io/package.cpp index 81af6a85..6ce9f061 100644 --- a/src/util/io/package.cpp +++ b/src/util/io/package.cpp @@ -144,8 +144,9 @@ void Package::clearKeepFlag() { } } -// ----------------------------------------------------------------------------- : Package : inside +// ----------------------------------------------------------------------------- : Streams +/// Class to use as a superclass class FileInputStream_aux { protected: wxFileInputStream file_stream; @@ -164,8 +165,7 @@ public: , wxZipInputStream(file_stream) {} ZipFileInputStream(const String& filename, wxZipEntry* entry) - : FileInputStream_aux(filename) - , wxZipInputStream(file_stream) + : ZipFileInputStream(filename) { OpenEntry(*entry); } @@ -184,6 +184,8 @@ public: {} }; +// ----------------------------------------------------------------------------- : Package : inside + unique_ptr Package::openIn(const String& file) { if (!file.empty() && file.GetChar(0) == _('/')) { // absolute path, open file from another package