From 5c1b7b9dfc1123060217b149bb4d7a65f1a6a097 Mon Sep 17 00:00:00 2001 From: GenevensiS <66968533+G-e-n-e-v-e-n-s-i-S@users.noreply.github.com> Date: Sun, 28 Dec 2025 13:59:23 +0100 Subject: [PATCH] add download_image, refactor import_image --- doc/function/download_image.txt | 13 ++ doc/function/index.txt | 1 + src/gfx/generated_image.cpp | 121 ++++++++++++------- src/gfx/generated_image.hpp | 34 ++++-- src/gui/bulk_modification_window.cpp | 22 ++-- src/gui/control/card_list.cpp | 2 +- src/gui/web_request_window.cpp | 4 +- src/gui/web_request_window.hpp | 2 +- src/script/functions/construction_helper.hpp | 13 +- src/script/functions/image.cpp | 13 +- src/util/io/package.cpp | 6 +- src/util/io/package.hpp | 10 +- src/util/io/package_manager.cpp | 2 +- 13 files changed, 157 insertions(+), 86 deletions(-) create mode 100644 doc/function/download_image.txt diff --git a/doc/function/download_image.txt b/doc/function/download_image.txt new file mode 100644 index 00000000..bad0bde9 --- /dev/null +++ b/doc/function/download_image.txt @@ -0,0 +1,13 @@ +Function: download_image + +--Usage-- +> download_image(image_url) + +Load an image from the internet. + +--Parameters-- +! Parameter Type Description +| @input@ [[type:string]] URL of the image to load + +--Examples-- +> new_card([image: download_image("https://cards.scryfall.io/art_crop/front/c/6/c634273a-94b0-4104-9d10-ae522ece1fc7.jpg?1755341328"), name: "Adagia, Windswept Bastion"]) diff --git a/doc/function/index.txt b/doc/function/index.txt index b14b614e..143f79a3 100644 --- a/doc/function/index.txt +++ b/doc/function/index.txt @@ -101,6 +101,7 @@ These functions are built into the program, other [[type:function]]s can be defi | [[fun:dimensions_of]] Get the width and height of an image. | [[fun:symbol_variation]] Render a variation of a [[type:symbol]]. | [[fun:import_image]] Load an image from outside the data folder. +| [[fun:download_image]] Download an image from a URL. | [[fun:built_in_image]] Return an image built into the program. ! Cards <<< diff --git a/src/gfx/generated_image.cpp b/src/gfx/generated_image.cpp index 6cca8bfd..b8291087 100644 --- a/src/gfx/generated_image.cpp +++ b/src/gfx/generated_image.cpp @@ -10,10 +10,12 @@ #include #include #include +#include #include #include #include #include // load_resource_image +#include #include // ----------------------------------------------------------------------------- : GeneratedImage @@ -673,55 +675,88 @@ bool ImageValueToImage::operator == (const GeneratedImage& that) const { && age == that2->age; } -// ----------------------------------------------------------------------------- : ExternalImage - -ExternalImage::ExternalImage(const String& filepath) - : filepath(filepath), loaded(false) -{ - filepathSanitized = filepath; - filepathSanitized.Replace(":", "-"); - filepathSanitized.Replace("\\", "-"); - filepathSanitized.Replace("/", "-"); -} +// ----------------------------------------------------------------------------- : ImportedImage -Image ExternalImage::generate(const Options& opt) { - // has a pre-existing .mse-set file been loaded? - if (opt.local_package->needSaveAs()) throw ScriptError(_ERROR_1_("can't import image without set", filepath)); +ImportedImage::ImportedImage(Set* set, const String& filepath) +{ + loadpath = filepath; + + // has the set already been saved at least once? + if (set->needSaveAs()) throw ScriptError(_ERROR_1_("can't import image without set", loadpath)); // does the file pointed to by filepath exist? - wxFileName fname(filepath, wxPATH_UNIX); - if (!fname.FileExists()) throw ScriptError(_ERROR_1_("import not found", filepath)); - - // add the file to the package (or overwrite it if pre-existing) - if (!loaded || !opt.local_package->existsIn(filepathSanitized)) { - auto outStream = opt.local_package->openOut(filepathSanitized); - wxFileInputStream inStream(filepath); - if (!inStream.IsOk()) throw ScriptError(_ERROR_1_("can't create file stream", filepath)); - outStream->Write(inStream); - if (!outStream->IsOk()) throw ScriptError(_ERROR_1_("can't write image to set", filepath)); - outStream->Close(); - loaded = true; - } - - // save the package with the new image - opt.local_package->save(false); + if (!wxFileName(loadpath, wxPATH_UNIX).FileExists()) throw ScriptError(_ERROR_1_("import not found", loadpath)); - // generate Image - String fileExt = fname.GetExt(); - wxBitmapType bitmapType; - if (fileExt == _("png")) bitmapType = wxBITMAP_TYPE_PNG; - else if (fileExt == _("jpg") || fileExt == _("jpeg")) bitmapType = wxBITMAP_TYPE_JPEG; - else bitmapType = wxBITMAP_TYPE_BMP; + // is the file an image? + Image img; + img.LoadFile(loadpath); + if (!img.IsOk()) throw ScriptError(_ERROR_1_("import not image", loadpath)); - auto imageInputStream = opt.local_package->openIn(filepathSanitized); - Image img(*imageInputStream, bitmapType); + // add the file to the set (or overwrite it if pre-existing), save set + savename = normalize_internal_filename(loadpath); + savename.Replace(":", "-"); + savename.Replace("/", "-"); + auto outStream = set->openOut(savename); + img.SaveFile(*outStream, wxBITMAP_TYPE_PNG); + if (!outStream->IsOk()) throw ScriptError(_ERROR_1_("can't write image to set", loadpath)); + outStream->Close(); + set->save(false); +} - if (!img.IsOk()) throw ScriptError(_ERROR_1_("can't import image", filepath)); +Image ImportedImage::generate(const Options& opt) { + auto imageInputStream = opt.local_package->openIn(savename); + Image img(*imageInputStream, wxBITMAP_TYPE_PNG); + + if (!img.IsOk()) throw ScriptError(_ERROR_1_("can't import image", loadpath)); return img; -} - -bool ExternalImage::operator == (const GeneratedImage& that) const { - const ExternalImage* that2 = dynamic_cast(&that); - return that2 && that2->filepath == filepath; +} + +bool ImportedImage::operator == (const GeneratedImage& that) const { + const ImportedImage* that2 = dynamic_cast(&that); + return that2 && that2->loadpath == loadpath; +} + +// ----------------------------------------------------------------------------- : DownloadedImage + +DownloadedImage::DownloadedImage(Set* set, const String& url) +{ + loadpath = url; + + // has the set already been saved at least once? + if (set->needSaveAs()) throw ScriptError(_ERROR_1_("can't download image without set", loadpath)); + + // can we download the data? + WebRequestWindow wnd(loadpath); + if (wnd.ShowModal() != wxID_OK) throw ScriptError(_ERROR_1_("can't download image", loadpath)); + + // is the data an image? + const String& content_type = wnd.out.GetContentType(); + if (!content_type.StartsWith(_("image"))) throw ScriptError(_ERROR_1_("download not image", loadpath)); + Image img(*wnd.out.GetStream()); + if (!img.IsOk()) throw ScriptError(_ERROR_("web request corrupted")); + + // add the file to the set (or overwrite it if pre-existing), save set + savename = normalize_internal_filename(loadpath); + savename.Replace(":", "-"); + savename.Replace("/", "-"); + auto outStream = set->openOut(savename); + img.SaveFile(*outStream, wxBITMAP_TYPE_PNG); + if (!outStream->IsOk()) throw ScriptError(_ERROR_1_("can't write image to set", loadpath)); + outStream->Close(); + set->save(false); +} + +Image DownloadedImage::generate(const Options& opt) { + auto imageInputStream = opt.local_package->openIn(savename); + Image img(*imageInputStream, wxBITMAP_TYPE_PNG); + + if (!img.IsOk()) throw ScriptError(_ERROR_1_("can't download image", loadpath)); + + return img; +} + +bool DownloadedImage::operator == (const GeneratedImage& that) const { + const DownloadedImage* that2 = dynamic_cast(&that); + return that2 && that2->loadpath == loadpath; } diff --git a/src/gfx/generated_image.hpp b/src/gfx/generated_image.hpp index 590fe67d..06418a38 100644 --- a/src/gfx/generated_image.hpp +++ b/src/gfx/generated_image.hpp @@ -17,6 +17,7 @@ DECLARE_POINTER_TYPE(GeneratedImage); DECLARE_POINTER_TYPE(SymbolVariation); class Package; +class Set; // ----------------------------------------------------------------------------- : GeneratedImage @@ -450,16 +451,33 @@ private: // ----------------------------------------------------------------------------- : ExternalImage -/// Load an image from the filesystem +/// Load an image from outside the data folder class ExternalImage : public GeneratedImage { public: - ExternalImage(const String& filepath); + inline String toString() { return savename; } + inline String toCode() const override { return _(""); } + +protected: + String loadpath; + String savename; +}; + +// ----------------------------------------------------------------------------- : ImportedImage + +/// Load an image from the filesystem +class ImportedImage : public ExternalImage { +public: + ImportedImage(Set* set, const String& filepath); + Image generate(const Options&) override; + bool operator == (const GeneratedImage& that) const override; +}; + +// ----------------------------------------------------------------------------- : DownloadedImage + +/// Load an image from the internet +class DownloadedImage : public ExternalImage { +public: + DownloadedImage(Set* set, const String& url); Image generate(const Options&) override; bool operator == (const GeneratedImage& that) const override; - inline String toString() { return filepath; } - inline String toCode() const override { return _(""); } -private: - String filepath; - String filepathSanitized; - bool loaded; ///< Make sure we at least load the image from outside the package once, as it may have been updated externally }; diff --git a/src/gui/bulk_modification_window.cpp b/src/gui/bulk_modification_window.cpp index f2773cba..f01cbfd4 100644 --- a/src/gui/bulk_modification_window.cpp +++ b/src/gui/bulk_modification_window.cpp @@ -297,21 +297,23 @@ void BulkModificationWindow::onOk(wxCommandEvent&) { else if (dynamic_cast(values.front())) { for (int i = 0; i < count; ++i) { ImageValue* value = dynamic_cast(values[i]); - wxFileName fname(static_cast(new_values[i].get())->toString()); - ImageValue::ValueType new_value = LocalFileName::fromReadString(fname.GetName(), ""); - shared_ptr> action = make_shared>(value, new_value); - action->setCard(cards[i]); - actions.push_back(action); + if (ExternalImage* img = dynamic_cast(new_values[i].get())) { + ImageValue::ValueType new_value = LocalFileName::fromReadString(img->toString(), ""); + shared_ptr> action = make_shared>(value, new_value); + action->setCard(cards[i]); + actions.push_back(action); + } } } else if (dynamic_cast(values.front())) { for (int i = 0; i < count; ++i) { SymbolValue* value = dynamic_cast(values[i]); - wxFileName fname(static_cast(new_values[i].get())->toString()); - SymbolValue::ValueType new_value = LocalFileName::fromReadString(fname.GetName(), ""); - shared_ptr> action = make_shared>(value, new_value); - action->setCard(cards[i]); - actions.push_back(action); + if (ExternalImage* img = dynamic_cast(new_values[i].get())) { + SymbolValue::ValueType new_value = LocalFileName::fromReadString(img->toString(), ""); + shared_ptr> action = make_shared>(value, new_value); + action->setCard(cards[i]); + actions.push_back(action); + } } } else { diff --git a/src/gui/control/card_list.cpp b/src/gui/control/card_list.cpp index 3650fd10..b9f91e80 100644 --- a/src/gui/control/card_list.cpp +++ b/src/gui/control/card_list.cpp @@ -250,7 +250,7 @@ bool CardListBase::parseUrl(String& url, vector& out) { } if (!url.StartsWith(_("http"))) return false; - WebRequestWindow wnd(this, url); + WebRequestWindow wnd(url); if (wnd.ShowModal() == wxID_OK) { const String& content_type = wnd.out.GetContentType(); if (content_type.StartsWith(_("image"))) { diff --git a/src/gui/web_request_window.cpp b/src/gui/web_request_window.cpp index f000a594..ae126aae 100644 --- a/src/gui/web_request_window.cpp +++ b/src/gui/web_request_window.cpp @@ -12,8 +12,8 @@ // ----------------------------------------------------------------------------- : WebRequestWindow -WebRequestWindow::WebRequestWindow(Window* parent, const String& url, bool sizer) - : wxDialog(parent, wxID_ANY, _TITLE_("web request"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +WebRequestWindow::WebRequestWindow(const String& url, bool sizer) + : wxDialog(wxTheApp->GetTopWindow(), wxID_ANY, _TITLE_("web request"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { // init controls info = new wxStaticText(this, -1, _LABEL_("web request address")); diff --git a/src/gui/web_request_window.hpp b/src/gui/web_request_window.hpp index 39cd0104..2289effa 100644 --- a/src/gui/web_request_window.hpp +++ b/src/gui/web_request_window.hpp @@ -17,7 +17,7 @@ /// A window for displaying the progression of a WebRequest and returning the WebResponse class WebRequestWindow : public wxDialog { public: - WebRequestWindow(Window* parent, const String& url, bool sizer=true); + WebRequestWindow(const String& url, bool sizer=true); wxWebResponse out; diff --git a/src/script/functions/construction_helper.hpp b/src/script/functions/construction_helper.hpp index c719409e..dd30e95a 100644 --- a/src/script/functions/construction_helper.hpp +++ b/src/script/functions/construction_helper.hpp @@ -68,9 +68,8 @@ inline static void set_container(Value* container, ScriptValueP& value, String k cvalue->value = value->toColor(); } else if (ImageValue* ivalue = dynamic_cast(container)) { - if (ExternalImage* image = dynamic_cast(value.get())) { - wxFileName fname(image->toString()); - ivalue->filename = LocalFileName::fromReadString(fname.GetName(), ""); + if (ExternalImage* img = dynamic_cast(value.get())) { + ivalue->filename = LocalFileName::fromReadString(img->toString(), ""); } else if (value->type() == SCRIPT_STRING) { ivalue->filename = LocalFileName::fromReadString(value->toString(), ""); } else { @@ -105,15 +104,15 @@ inline static bool set_stylesheet_container(const Game& game, CardP& card, Scrip inline static bool set_builtin_container(const Game& game, CardP& card, ScriptValueP& value, String key_name, bool ignore_field_not_found) { // check if the given value is for a built-in field, if found set it and return true key_name = unified_form(key_name); - if (key_name == _("notes") || key_name == _("note")) { + if (key_name == _("card_notes") || key_name == _("notes") || key_name == _("note")) { card->notes = value->toString(); return true; } - else if (key_name == _("id") || key_name == _("uid")) { + else if (key_name == _("id") || key_name == _("uid") || key_name == _("uuid")) { card->uid = value->toString(); return true; } - else if (key_name == _("linked_card") || key_name == _("linked_card_1")) { + else if (key_name == _("linked_card_1") || key_name == _("linked_card")) { card->linked_card_1 = value->toString(); return true; } @@ -129,7 +128,7 @@ inline static bool set_builtin_container(const Game& game, CardP& card, ScriptVa card->linked_card_4 = value->toString(); return true; } - else if (key_name == _("linked_relation") || key_name == _("linked_relation_1")) { + else if (key_name == _("linked_relation_1") || key_name == _("linked_relation")) { card->linked_relation_1 = value->toString(); return true; } diff --git a/src/script/functions/image.cpp b/src/script/functions/image.cpp index 3731cb73..e2089d2f 100644 --- a/src/script/functions/image.cpp +++ b/src/script/functions/image.cpp @@ -19,7 +19,6 @@ #include #include #include -#include // for MSE_CLI void parse_enum(const String&, ImageCombine& out); @@ -54,10 +53,13 @@ SCRIPT_FUNCTION(to_card_image) { SCRIPT_FUNCTION(import_image) { SCRIPT_PARAM(Set*, set); SCRIPT_PARAM(String, input); - auto extImg = make_intrusive(input); - if (cli.haveConsole()) // makes sure generate() is called, but only once, when using the CLI - extImg->generate(GeneratedImage::Options(0, 0, set->stylesheet.get(), set)); - return extImg; + return make_intrusive(set, input); +} + +SCRIPT_FUNCTION(download_image) { + SCRIPT_PARAM(Set*, set); + SCRIPT_PARAM(String, input); + return make_intrusive(set, input); } // ----------------------------------------------------------------------------- : Image functions @@ -316,4 +318,5 @@ void init_script_image_functions(Context& ctx) { ctx.setVariable(_("symbol_variation"), script_symbol_variation); ctx.setVariable(_("built_in_image"), script_built_in_image); ctx.setVariable(_("import_image"), script_import_image); + ctx.setVariable(_("download_image"), script_download_image); } diff --git a/src/util/io/package.cpp b/src/util/io/package.cpp index 28d2cb8d..63423dc9 100644 --- a/src/util/io/package.cpp +++ b/src/util/io/package.cpp @@ -189,7 +189,7 @@ public: // ----------------------------------------------------------------------------- : Package : inside -bool Package::existsIn(const String& file) { +bool Package::contains(const String& file) { FileInfos::iterator it = files.find(normalize_internal_filename(file)); if (it == files.end()) { // does it look like a relative filename? @@ -650,11 +650,11 @@ void Packaged::loadFully() { } } -void Packaged::save() { +void Packaged::save(bool remove_unused) { WITH_DYNAMIC_ARG(writing_package, this); writeFile(typeName(), *this, fileVersion()); referenceFile(typeName()); - Package::save(); + Package::save(remove_unused); } void Packaged::saveAs(const String& package, bool remove_unused, bool as_directory) { WITH_DYNAMIC_ARG(writing_package, this); diff --git a/src/util/io/package.hpp b/src/util/io/package.hpp index 43c841d0..05802eac 100644 --- a/src/util/io/package.hpp +++ b/src/util/io/package.hpp @@ -178,15 +178,15 @@ public: // --------------------------------------------------- : Managing the inside of the package /// Check if a file is in the package. - bool existsIn(const String& file); - inline bool existsIn(const LocalFileName& file) { - return existsIn(file.fn); + bool contains(const String& file); + inline bool contains(const LocalFileName& file) { + return contains(file.fn); } /// Open an input stream for a file in the package. unique_ptr openIn(const String& file); inline unique_ptr openIn(const LocalFileName& file) { - return openIn(file.fn); + return openIn(file.fn); } /// Open an output stream for a file in the package. @@ -331,7 +331,7 @@ public: void open(const String& package, bool just_header = false); /// Ensure the package is fully loaded. void loadFully(); - void save(); + void save(bool remove_unused = true); void saveAs(const String& package, bool remove_unused = true, bool as_directory = false); void saveCopy(const String& package); diff --git a/src/util/io/package_manager.cpp b/src/util/io/package_manager.cpp index 6fe3264b..4e4a3e11 100644 --- a/src/util/io/package_manager.cpp +++ b/src/util/io/package_manager.cpp @@ -103,7 +103,7 @@ bool PackageManager::existsInPackage(const String& name) { if (start < pos && pos != String::npos) { // open package PackagedP p = openAny(name.substr(start, pos - start)); - return p->existsIn(name.substr(pos + 1)); + return p->contains(name.substr(pos + 1)); } } throw FileNotFoundError(name, _("No package name specified, use '/package/filename'"));