add download_image, refactor import_image

This commit is contained in:
GenevensiS
2025-12-28 13:59:23 +01:00
parent 3d0f21d483
commit 5c1b7b9dfc
13 changed files with 157 additions and 86 deletions
+13
View File
@@ -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"])
+1
View File
@@ -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 <<<
+78 -43
View File
@@ -10,10 +10,12 @@
#include <gfx/generated_image.hpp>
#include <util/io/package.hpp>
#include <util/error.hpp>
#include <data/set.hpp>
#include <data/symbol.hpp>
#include <data/field/symbol.hpp>
#include <render/symbol/filter.hpp>
#include <gui/util.hpp> // load_resource_image
#include <gui/web_request_window.hpp>
#include <wx/wfstream.h>
// ----------------------------------------------------------------------------- : 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<const ExternalImage*>(&that);
return that2 && that2->filepath == filepath;
}
bool ImportedImage::operator == (const GeneratedImage& that) const {
const ImportedImage* that2 = dynamic_cast<const ImportedImage*>(&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<const DownloadedImage*>(&that);
return that2 && that2->loadpath == loadpath;
}
+26 -8
View File
@@ -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 _("<image>"); }
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 _("<image>"); }
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
};
+12 -10
View File
@@ -297,21 +297,23 @@ void BulkModificationWindow::onOk(wxCommandEvent&) {
else if (dynamic_cast<ImageValue*>(values.front())) {
for (int i = 0; i < count; ++i) {
ImageValue* value = dynamic_cast<ImageValue*>(values[i]);
wxFileName fname(static_cast<ExternalImage*>(new_values[i].get())->toString());
ImageValue::ValueType new_value = LocalFileName::fromReadString(fname.GetName(), "");
shared_ptr<SimpleValueAction<ImageValue, false>> action = make_shared<SimpleValueAction<ImageValue, false>>(value, new_value);
action->setCard(cards[i]);
actions.push_back(action);
if (ExternalImage* img = dynamic_cast<ExternalImage*>(new_values[i].get())) {
ImageValue::ValueType new_value = LocalFileName::fromReadString(img->toString(), "");
shared_ptr<SimpleValueAction<ImageValue, false>> action = make_shared<SimpleValueAction<ImageValue, false>>(value, new_value);
action->setCard(cards[i]);
actions.push_back(action);
}
}
}
else if (dynamic_cast<SymbolValue*>(values.front())) {
for (int i = 0; i < count; ++i) {
SymbolValue* value = dynamic_cast<SymbolValue*>(values[i]);
wxFileName fname(static_cast<ExternalImage*>(new_values[i].get())->toString());
SymbolValue::ValueType new_value = LocalFileName::fromReadString(fname.GetName(), "");
shared_ptr<SimpleValueAction<SymbolValue, false>> action = make_shared<SimpleValueAction<SymbolValue, false>>(value, new_value);
action->setCard(cards[i]);
actions.push_back(action);
if (ExternalImage* img = dynamic_cast<ExternalImage*>(new_values[i].get())) {
SymbolValue::ValueType new_value = LocalFileName::fromReadString(img->toString(), "");
shared_ptr<SimpleValueAction<SymbolValue, false>> action = make_shared<SimpleValueAction<SymbolValue, false>>(value, new_value);
action->setCard(cards[i]);
actions.push_back(action);
}
}
}
else {
+1 -1
View File
@@ -250,7 +250,7 @@ bool CardListBase::parseUrl(String& url, vector<CardP>& 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"))) {
+2 -2
View File
@@ -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"));
+1 -1
View File
@@ -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;
+6 -7
View File
@@ -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<ImageValue*>(container)) {
if (ExternalImage* image = dynamic_cast<ExternalImage*>(value.get())) {
wxFileName fname(image->toString());
ivalue->filename = LocalFileName::fromReadString(fname.GetName(), "");
if (ExternalImage* img = dynamic_cast<ExternalImage*>(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;
}
+8 -5
View File
@@ -19,7 +19,6 @@
#include <data/format/formats.hpp>
#include <gfx/generated_image.hpp>
#include <render/symbol/filter.hpp>
#include <cli/text_io_handler.hpp> // 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<ExternalImage>(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<ImportedImage>(set, input);
}
SCRIPT_FUNCTION(download_image) {
SCRIPT_PARAM(Set*, set);
SCRIPT_PARAM(String, input);
return make_intrusive<DownloadedImage>(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);
}
+3 -3
View File
@@ -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);
+5 -5
View File
@@ -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<wxInputStream> openIn(const String& file);
inline unique_ptr<wxInputStream> 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);
+1 -1
View File
@@ -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'"));