add store_in_metadata image style property

This commit is contained in:
GenevensiS
2026-01-04 00:29:00 +01:00
parent 6a1d23efc7
commit 8cca7674bd
11 changed files with 202 additions and 56 deletions
+19 -19
View File
@@ -103,15 +103,15 @@ public:
Style(const FieldP&); Style(const FieldP&);
virtual ~Style(); virtual ~Style();
const FieldP fieldP; ///< Field this style is for, should have the right type! const FieldP fieldP; ///< Field this style is for, should have the right type!
int z_index; ///< Stacking of values of this field, higher = on top int z_index; ///< Stacking of values of this field, higher = on top
int tab_index; ///< Tab index in editor int tab_index; ///< Tab index in editor
Scriptable<double> left, top; ///< Position of this field Scriptable<double> left, top; ///< Position of this field
Scriptable<double> width, height; ///< Position of this field Scriptable<double> right, bottom; ///< Position of this field
Scriptable<double> right, bottom; ///< Position of this field Scriptable<double> width, height; ///< Size of this field
Scriptable<Degrees> angle; ///< Rotation of the box Scriptable<Degrees> angle; ///< Rotation of the box
Scriptable<bool> visible; ///< Is this field visible? Scriptable<bool> visible; ///< Is this field visible?
CachedScriptableMask mask; ///< Mask image CachedScriptableMask mask; ///< Mask image
enum AutomaticSide { enum AutomaticSide {
@@ -123,10 +123,10 @@ public:
} automatic_side : 8; ///< Which of (left, width, right) and (top, height, bottom) is determined automatically? } automatic_side : 8; ///< Which of (left, width, right) and (top, height, bottom) is determined automatically?
bool content_dependent; ///< Does this style depend on content properties? bool content_dependent; ///< Does this style depend on content properties?
inline RealPoint getPos() const { return RealPoint(left, top); } inline RealPoint getPos() const { return RealPoint(left, top); }
inline RealSize getSize() const { return RealSize(width, height); } inline RealSize getSize() const { return RealSize(width, height); }
inline RealRect getExternalRect() const { return RealRect(left, top, width, height); } inline RealRect getExternalRect() const { return RealRect(left, top, width, height); }
inline String getExternalRectString(double scale, Radians angle, double bleed, int img_width, int img_height, int img_offset) { ///< update the style before calling this inline std::string getExternalRectString(double scale, Radians angle, double bleed, int img_width, int img_height, int img_offset) { ///< update the style before calling this
double x = left * scale, y = top * scale; double x = left * scale, y = top * scale;
double w = width * scale, h = height * scale; double w = width * scale, h = height * scale;
RealRect rect(x, y, w, h); RealRect rect(x, y, w, h);
@@ -142,14 +142,14 @@ public:
rect = RealRect(img_width - y - h, x, h, w); rect = RealRect(img_width - y - h, x, h, w);
degrees = 270; degrees = 270;
} else { } else {
return _(""); return "";
} }
return _("---") + wxString::Format(wxT("%i"), (int)std::ceil( rect.x + bleed + img_offset)) + return "<mse-crop-data>" + std::to_string((int)std::ceil (rect.x + bleed + img_offset)) +
_("-") + wxString::Format(wxT("%i"), (int)std::ceil( rect.y + bleed)) + "-" + std::to_string((int)std::ceil (rect.y + bleed)) +
_("-") + wxString::Format(wxT("%i"), (int)std::floor(rect.width)) + "-" + std::to_string((int)std::floor(rect.width)) +
_("-") + wxString::Format(wxT("%i"), (int)std::floor(rect.height)) + "-" + std::to_string((int)std::floor(rect.height)) +
_("-") + wxString::Format(wxT("%i"), degrees) + "-" + std::to_string(degrees) +
_("---"); "</mse-crop-data>";
} }
/// Does this style have a non-zero size (or is it scripted)? /// Does this style have a non-zero size (or is it scripted)?
+2 -1
View File
@@ -18,17 +18,18 @@ IMPLEMENT_REFLECTION(ImageField) {
REFLECT_BASE(Field); REFLECT_BASE(Field);
} }
// ----------------------------------------------------------------------------- : ImageStyle // ----------------------------------------------------------------------------- : ImageStyle
IMPLEMENT_REFLECTION(ImageStyle) { IMPLEMENT_REFLECTION(ImageStyle) {
REFLECT_BASE(Style); REFLECT_BASE(Style);
REFLECT_N("default", default_image); REFLECT_N("default", default_image);
REFLECT(store_in_metadata);
} }
int ImageStyle::update(Context& ctx) { int ImageStyle::update(Context& ctx) {
int changes = Style::update(ctx); int changes = Style::update(ctx);
changes |= default_image.update(ctx) * CHANGE_DEFAULT; changes |= default_image.update(ctx) * CHANGE_DEFAULT;
changes |= store_in_metadata.update(ctx) * CHANGE_OTHER;
return changes; return changes;
} }
+27 -14
View File
@@ -10,6 +10,7 @@
#include <util/prec.hpp> #include <util/prec.hpp>
#include <data/field.hpp> #include <data/field.hpp>
#include <data/set.hpp>
#include <script/scriptable.hpp> #include <script/scriptable.hpp>
#include <script/image.hpp> #include <script/image.hpp>
#include <util/io/package.hpp> #include <util/io/package.hpp>
@@ -30,19 +31,6 @@ public:
DECLARE_FIELD_TYPE(Image); DECLARE_FIELD_TYPE(Image);
}; };
// ----------------------------------------------------------------------------- : ImageStyle
/// The Style for a ImageField
class ImageStyle : public Style {
public:
inline ImageStyle(const ImageFieldP& field) : Style(field) {}
DECLARE_STYLE_TYPE(Image);
ScriptableImage default_image; ///< Placeholder
int update(Context&) override;
};
// ----------------------------------------------------------------------------- : ImageValue // ----------------------------------------------------------------------------- : ImageValue
/// The Value in a ImageField, i.e. an image /// The Value in a ImageField, i.e. an image
@@ -50,8 +38,33 @@ class ImageValue : public Value {
public: public:
inline ImageValue(const ImageFieldP& field) : Value(field) {} inline ImageValue(const ImageFieldP& field) : Value(field) {}
DECLARE_VALUE_TYPE(Image, LocalFileName); DECLARE_VALUE_TYPE(Image, LocalFileName);
ValueType filename; ///< Filename of the image (in the current package), or "" ValueType filename; ///< Filename of the image (in the current package), or ""
Age last_update; ///< When was the image last changed? Age last_update; ///< When was the image last changed?
}; };
// ----------------------------------------------------------------------------- : ImageStyle
/// The Style for an ImageField
class ImageStyle : public Style {
public:
inline ImageStyle(const ImageFieldP& field) : Style(field), store_in_metadata(false) {}
DECLARE_STYLE_TYPE(Image);
ScriptableImage default_image; ///< Placeholder image when the user hasn't set one.
Scriptable<bool> store_in_metadata; ///< Is the image stored in full in the metadata when exporting?
int update(Context&) override;
inline std::string getExternalImageString(const SetP& set, ImageValue* value) { ///< update the style before calling this
auto imageInputStream = set->openIn(value->filename);
Image img(*imageInputStream, wxBITMAP_TYPE_PNG);
if (!img.IsOk()) throw ScriptError(_ERROR_2_("file not found", value->filename.toStringForKey(), set));
String temppath = wxFileName::CreateTempFileName(_("mse")) + _(".png");
img.SaveFile(temppath);
std::string s = "<mse-image-data>" + fileToUTF8(temppath.ToStdString()) + "</mse-image-data>";
wxRemoveFile(temppath);
wxRemoveFile(temppath.substr(0, temppath.size() - 4));
return s;
}
};
+76
View File
@@ -0,0 +1,76 @@
//+----------------------------------------------------------------------------+
//| Description: Magic Set Editor - Program to make card games |
//| Copyright: (C) Twan van Laarhoven and the other MSE developers |
//| License: GNU General Public License 2 or later (see file COPYING) |
//+----------------------------------------------------------------------------+
#pragma once
// ----------------------------------------------------------------------------- : Includes
#include <util/prec.hpp>
#include <fstream>
// ----------------------------------------------------------------------------- : File to UTF8 Encoding
inline std::string fileToUTF8(const std::string& filepath) {
// File to char
std::ifstream file(filepath, std::ios::binary);
file.unsetf(std::ios::skipws);
std::vector<unsigned char> buffer = std::vector<unsigned char>(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
int size = buffer.size();
if (size < 2) {
queue_message(MESSAGE_WARNING, _("File too small to encode"));
return "";
}
// All bytes that have a highest bit of 0 are valid UTF8 characters, so:
// Reset the highest bit of each byte, store these bits in additional bytes at the end
const unsigned char highest_bit = 1 << 7;
unsigned char added_byte = 0;
for (int i = 0, b = 0 ; i < size ; ++i, ++b) {
if (b == 7) { // Never set the highest bit of the added byte
buffer.push_back(added_byte);
b = 0;
}
unsigned char bit = 1 << b;
if ((buffer[i] & highest_bit) != 0) { // The highest bit of the buffer is set
buffer[i] &= ~highest_bit; // Reset the highest bit of the buffer
added_byte |= bit; // Set the bit of the added byte
} else {
added_byte &= ~bit; // Reset the bit of the added byte
}
}
buffer.push_back(added_byte);
// Char to string
return std::string(buffer.begin(), buffer.end());
}
inline bool UTF8ToFile(const std::string& filepath, std::string& string) {
// String to char
std::vector<unsigned char> buffer(string.begin(), string.end());
int size = buffer.size();
if (size < 2) {
queue_message(MESSAGE_WARNING, _("File too small to decode"));
return false;
}
// Restore the highest bit of each byte
size = (size * 7) / 8;
const unsigned char highest_bit = 1 << 7;
unsigned char added_byte = buffer[size];
for (int i = 0, j = size, b = 0 ; i < size ; ++i, ++b) {
if (b == 7) {
++j;
added_byte = buffer[j];
b = 0;
}
unsigned char bit = 1 << b;
if ((added_byte & bit) != 0) { // The bit of the added byte is set
buffer[i] |= highest_bit; // Set the highest bit of the buffer
}
}
buffer.resize(size);
// Char to file
std::ofstream file(filepath, std::ios::out|std::ios::binary);
std::copy(buffer.cbegin(), buffer.cend(), std::ostream_iterator<unsigned char>(file));
return true;
}
+28 -12
View File
@@ -10,6 +10,7 @@
#include <util/tagged_string.hpp> #include <util/tagged_string.hpp>
#include <data/format/formats.hpp> #include <data/format/formats.hpp>
#include <data/format/clipboard.hpp> #include <data/format/clipboard.hpp>
#include <data/format/file_to_text.h>
#include <data/game.hpp> #include <data/game.hpp>
#include <data/field/image.hpp> #include <data/field/image.hpp>
#include <data/set.hpp> #include <data/set.hpp>
@@ -18,8 +19,7 @@
#include <data/settings.hpp> #include <data/settings.hpp>
#include <script/functions/json.hpp> #include <script/functions/json.hpp>
#include <gui/util.hpp> #include <gui/util.hpp>
#include <render/card/viewer.hpp> #include <render/card/viewer.hpp>
#include <wx/filename.h>
class ZoomedUnrotatedDataViewer : public DataViewer { class ZoomedUnrotatedDataViewer : public DataViewer {
public: public:
@@ -136,16 +136,25 @@ Image export_image(const SetP& set, const CardP& card, const bool write_metadata
boost::json::object cardv = mse_to_json(card, set.get()); boost::json::object cardv = mse_to_json(card, set.get());
boost::json::object& cardv_data = cardv["data"].as_object(); boost::json::object& cardv_data = cardv["data"].as_object();
StyleSheetP stylesheet = set->stylesheetForP(card); StyleSheetP stylesheet = set->stylesheetForP(card);
if (!settings.stylesheetSettingsFor(*stylesheet).card_notes_export()) cardv["notes"] = ""; if (!settings.stylesheetSettingsFor(*stylesheet).card_notes_export()) cardv["notes"] = "";
// iterate over all image fields
for(IndexMap<FieldP, ValueP>::iterator it = card_data.begin() ; it != card_data.end() ; ++it) { for(IndexMap<FieldP, ValueP>::iterator it = card_data.begin() ; it != card_data.end() ; ++it) {
ImageValue* value = dynamic_cast<ImageValue*>(it->get()); ImageValue* value = dynamic_cast<ImageValue*>(it->get());
if (value && !value->filename.empty()) { if (value && !value->filename.empty()) {
FieldP field = (*it)->fieldP; FieldP field = (*it)->fieldP;
StyleP style = stylesheet->card_style.at(field->index); ImageStyle* style = dynamic_cast<ImageStyle*>(stylesheet->card_style.at(field->index).get());
if (style) { if (style) {
style->update(set->getContext(card)); style->update(set->getContext(card));
std::string rect = style->getExternalRectString(zoom, angle_radians, bleed_pixels, width, height, 0).ToStdString(); // store the entire image in the metadata
cardv_data[field->name.ToStdString()] = rect; if (style->store_in_metadata()) {
std::string bytes = style->getExternalImageString(set, value);
cardv_data[field->name.ToStdString()] = bytes;
}
// store only crop coordinates
else {
std::string rect = style->getExternalRectString(zoom, angle_radians, bleed_pixels, width, height, 0);
cardv_data[field->name.ToStdString()] = rect;
}
} }
} }
} }
@@ -224,13 +233,20 @@ Image export_image( const SetP& set, const vector<CardP>& cards,
for(IndexMap<FieldP, ValueP>::iterator it = card_data.begin() ; it != card_data.end() ; ++it) { for(IndexMap<FieldP, ValueP>::iterator it = card_data.begin() ; it != card_data.end() ; ++it) {
ImageValue* value = dynamic_cast<ImageValue*>(it->get()); ImageValue* value = dynamic_cast<ImageValue*>(it->get());
if (value && !value->filename.empty()) { if (value && !value->filename.empty()) {
FieldP field = (*it)->fieldP; FieldP field = (*it)->fieldP;
StyleP style = stylesheet->card_style.at(field->index); ImageStyle* style = dynamic_cast<ImageStyle*>(stylesheet->card_style.at(field->index).get());
if (style) { if (style) {
style->update(set->getContext(card)); style->update(set->getContext(card));
Rotation rotation(); // store the entire image in the metadata
std::string rect = style->getExternalRectString(zooms[i], angles[i], bleeds[i], widths[i], heights[i], offsets[i]).ToStdString(); if (style->store_in_metadata()) {
cardv_data[field->name.ToStdString()] = rect; std::string bytes = style->getExternalImageString(set, value);
cardv_data[field->name.ToStdString()] = bytes;
}
// store only crop coordinates
else {
std::string rect = style->getExternalRectString(zooms[i], angles[i], bleeds[i], widths[i], heights[i], offsets[i]);
cardv_data[field->name.ToStdString()] = rect;
}
} }
} }
} }
+2 -2
View File
@@ -265,7 +265,7 @@ double Settings::exportScaleSettingsFor(const StyleSheet& stylesheet) {
if (export_scale == 0) return adaptiveScaleSettingsFor(stylesheet, 300.0, 50.0); if (export_scale == 0) return adaptiveScaleSettingsFor(stylesheet, 300.0, 50.0);
if (export_scale == 1) return adaptiveScaleSettingsFor(stylesheet, 300.0, 1.0); if (export_scale == 1) return adaptiveScaleSettingsFor(stylesheet, 300.0, 1.0);
if (export_scale == 2) return adaptiveScaleSettingsFor(stylesheet, 150.0, 1.0); if (export_scale == 2) return adaptiveScaleSettingsFor(stylesheet, 150.0, 1.0);
return scale_choices[export_scale - 3] / 100; return (double)scale_choices[export_scale - 3] / 100.0;
} }
double Settings::importScaleSettingsFor(const StyleSheet& stylesheet) { double Settings::importScaleSettingsFor(const StyleSheet& stylesheet) {
@@ -273,7 +273,7 @@ double Settings::importScaleSettingsFor(const StyleSheet& stylesheet) {
if (import_scale_selection == 1) return adaptiveScaleSettingsFor(stylesheet, 300.0, 50.0); if (import_scale_selection == 1) return adaptiveScaleSettingsFor(stylesheet, 300.0, 50.0);
if (import_scale_selection == 2) return adaptiveScaleSettingsFor(stylesheet, 300.0, 1.0); if (import_scale_selection == 2) return adaptiveScaleSettingsFor(stylesheet, 300.0, 1.0);
if (import_scale_selection == 3) return adaptiveScaleSettingsFor(stylesheet, 150.0, 1.0); if (import_scale_selection == 3) return adaptiveScaleSettingsFor(stylesheet, 150.0, 1.0);
return scale_choices[import_scale_selection - 4] / 100; return (double)scale_choices[import_scale_selection - 4] / 100.0;
} }
double Settings::adaptiveScaleSettingsFor(const StyleSheet& stylesheet, double dpi_target, double dpi_leeway) { double Settings::adaptiveScaleSettingsFor(const StyleSheet& stylesheet, double dpi_target, double dpi_leeway) {
+18 -1
View File
@@ -310,7 +310,7 @@ bool CardListBase::parseImage(Image& image, vector<CardP>& out) {
if (rect.width > 0 && rect.height > 0) { if (rect.width > 0 && rect.height > 0) {
Image img = image.GetSubImage(rect); Image img = image.GetSubImage(rect);
img = rotate_image(img, deg_to_rad(360-degrees)); img = rotate_image(img, deg_to_rad(360-degrees));
LocalFileName filename = set->newFileName((*it)->fieldP->name, settings.internal_image_extension ? _(".png") : _("")); // a new unique name in the package LocalFileName filename = set->newFileName("cropped_image", settings.internal_image_extension ? _(".png") : _("")); // a new unique name in the package
img.SaveFile(set->nameOut(filename), wxBITMAP_TYPE_PNG); img.SaveFile(set->nameOut(filename), wxBITMAP_TYPE_PNG);
value->filename = filename; value->filename = filename;
} }
@@ -340,6 +340,23 @@ bool CardListBase::parseText(String& text, vector<CardP>& out) {
out.push_back(make_intrusive<Card>(*c->getValue())); out.push_back(make_intrusive<Card>(*c->getValue()));
} }
} catch (...) {} } catch (...) {}
// recreate images to populate image fields
for (int k = j; k < out.size(); k++) {
CardP& card = out[k];
for (IndexMap<FieldP, ValueP>::iterator it = card->data.begin(); it != card->data.end(); it++) {
ImageValue* value = dynamic_cast<ImageValue*>(it->get());
if (value) {
Image img = value->filename.getExternalImage();
if (img.IsOk()) {
LocalFileName filename = set->newFileName(_("decoded_image"), settings.internal_image_extension ? _(".png") : _("")); // a new unique name in the package
img.SaveFile(set->nameOut(filename), wxBITMAP_TYPE_PNG);
value->filename = filename;
}
}
}
}
return j < out.size(); return j < out.size();
} }
+1 -1
View File
@@ -456,7 +456,7 @@ void ImageSlicePreview::update() {
} }
wxSize ImageSlicePreview::getBestSliceSize() const { wxSize ImageSlicePreview::getBestSliceSize() const {
float target_ratio = ((float)slice.target_size.GetWidth()) / ((float)slice.target_size.GetHeight()); double target_ratio = ((double)slice.target_size.GetWidth()) / ((double)slice.target_size.GetHeight());
if (target_ratio > 1.0) { if (target_ratio > 1.0) {
return wxSize(500, 500 / target_ratio); return wxSize(500, 500 / target_ratio);
} else { } else {
+3 -2
View File
@@ -314,9 +314,10 @@ ScriptValueP json_to_mse(const boost::json::value& jv, Set* set) {
return to_script(integer); return to_script(integer);
} }
else if (jv.is_string()) { else if (jv.is_string()) {
if (jv.as_string().empty()) return to_script(String());
std::string string = boost::json::value_to<std::string>(jv); std::string string = boost::json::value_to<std::string>(jv);
String wxstring = String(string.c_str(), wxConvUTF8); String wxstring = String(string);
if (wxstring.empty()) wxstring = String(string.c_str()); if (wxstring.empty()) wxstring = String(string.c_str(), wxConvUTF8);
return to_script(wxstring); return to_script(wxstring);
} }
else if (jv.is_array()) { else if (jv.is_array()) {
+25 -3
View File
@@ -13,6 +13,7 @@
#include <util/error.hpp> #include <util/error.hpp>
#include <util/file_utils.hpp> #include <util/file_utils.hpp>
#include <util/vcs.hpp> #include <util/vcs.hpp>
#include <data/format/file_to_text.h>
class Package; class Package;
class wxFileInputStream; class wxFileInputStream;
@@ -55,11 +56,11 @@ public:
/// Retreive a rect from a filename /// Retreive a rect from a filename
inline static void getExternalRect(const String& filename, wxRect& rect_out, int& degrees_out) { inline static void getExternalRect(const String& filename, wxRect& rect_out, int& degrees_out) {
size_t first = filename.find(_("---")); size_t first = filename.find(_("<mse-crop-data>"));
if (first == String::npos) return; if (first == String::npos) return;
size_t last = filename.find(_("---"), first+3); size_t last = filename.find(_("</mse-crop-data>"), first + 15);
if (last == String::npos) return; if (last == String::npos) return;
String string = filename.substr(first + 3, last - (first + 3)); String string = filename.substr(first + 15, last - (first + 15));
if (string.empty()) return; if (string.empty()) return;
size_t divider = string.find(_("-")); size_t divider = string.find(_("-"));
@@ -96,7 +97,28 @@ public:
} }
inline void getExternalRect(wxRect& rect_out, int& degrees_out) { inline void getExternalRect(wxRect& rect_out, int& degrees_out) {
getExternalRect(fn, rect_out, degrees_out); getExternalRect(fn, rect_out, degrees_out);
}
/// Retreive an image from a filename
inline static Image getExternalImage(const String& filename) {
Image img;
size_t first = filename.find(_("<mse-image-data>"));
if (first == String::npos) return img;
size_t last = filename.find(_("</mse-image-data>"), first + 16);
if (last == String::npos) return img;
std::string s = filename.substr(first + 16, last - (first + 16)).ToStdString();
if (s.empty()) return img;
const std::string& temppath = (wxFileName::CreateTempFileName(_("mse")) + _(".png")).ToStdString();
UTF8ToFile(temppath, s);
img.LoadFile(temppath, wxBITMAP_TYPE_PNG);
wxRemoveFile(temppath);
wxRemoveFile(temppath.substr(0, temppath.size() - 4));
return img;
} }
inline Image getExternalImage() {
return getExternalImage(fn);
}
private: private:
LocalFileName(const wxString& fn) : fn(fn) {} LocalFileName(const wxString& fn) : fn(fn) {}
+1 -1
View File
@@ -25,5 +25,5 @@ static String generate_uid() {
for (i = 0; i < 32; i++) { for (i = 0; i < 32; i++) {
ss << dis(gen); ss << dis(gen);
}; };
return String(ss.str().c_str(), wxConvUTF8); return String(ss.str());
} }