mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-10 04:57:00 -04:00
ensure image script functions preserve metadata
This commit is contained in:
+2
-2
@@ -180,13 +180,13 @@ int Style::update(Context& ctx) {
|
||||
else if (automatic_side & AUTO_BOTTOM) bottom = top + height;
|
||||
else {int tb = int(top + bottom); top = (tb - height) / 2; bottom = (tb + height) / 2; }
|
||||
// adjust rotation point
|
||||
if (angle != 0 && (automatic_side & (AUTO_LEFT | AUTO_TOP))) {
|
||||
if (!almost_equal(angle, 0.0) && (automatic_side & (AUTO_LEFT | AUTO_TOP))) {
|
||||
double s = sin(deg_to_rad(angle)), c = cos(deg_to_rad(angle));
|
||||
if (automatic_side & AUTO_LEFT) { // attach right corner instead of left
|
||||
left = left + width * (1 - c);
|
||||
top = top + width * s;
|
||||
}
|
||||
if (automatic_side & AUTO_TOP) { // attach botom corner instead of top
|
||||
if (automatic_side & AUTO_TOP) { // attach bottom corner instead of top
|
||||
left = left - height * s;
|
||||
top = top + height * (1 - c);
|
||||
}
|
||||
|
||||
+32
-4
@@ -126,10 +126,38 @@ public:
|
||||
inline RealPoint getPos() const { return RealPoint(left, top); }
|
||||
inline RealSize getSize() const { return RealSize(width, height); }
|
||||
inline RealRect getExternalRect() const { return RealRect(left, top, width, height); }
|
||||
inline std::string getExternalRectString(double scale, Radians angle, int offset_x, int offset_y, int img_width, int img_height) { ///< update the style before calling this
|
||||
RealRect rect(left, top, width, height);
|
||||
int degrees = lround(rad_to_deg(this->angle));
|
||||
return transformAndEncodeRectInString(rect, degrees, scale, angle, offset_x, offset_y, img_width, img_height);
|
||||
inline RealRect getCanonicalExternalRect() const {
|
||||
if (almost_equal(angle, 90.0)) {
|
||||
if (automatic_side & Style::AutomaticSide::AUTO_LEFT) {
|
||||
if (automatic_side & Style::AutomaticSide::AUTO_TOP) return RealRect(left + width - height, top + height, height, width); //bottom right
|
||||
else return RealRect(left + width, top, height, width); //top right
|
||||
}
|
||||
else {
|
||||
if (automatic_side & Style::AutomaticSide::AUTO_TOP) return RealRect(left - height, top + height - width, height, width); //bottom left
|
||||
else return RealRect(left, top - width, height, width); //top left
|
||||
}
|
||||
}
|
||||
else if (almost_equal(angle, 270.0)) {
|
||||
if (automatic_side & Style::AutomaticSide::AUTO_LEFT) {
|
||||
if (automatic_side & Style::AutomaticSide::AUTO_TOP) return RealRect(left + width, top + height - width, height, width); //bottom right
|
||||
else return RealRect(left + width - height, top - width, height, width); //top right
|
||||
}
|
||||
else {
|
||||
if (automatic_side & Style::AutomaticSide::AUTO_TOP) return RealRect(left, top + height, height, width); //bottom left
|
||||
else return RealRect(left - height, top, height, width); //top left
|
||||
}
|
||||
}
|
||||
else if (almost_equal(angle, 180.0)) {
|
||||
if (automatic_side & Style::AutomaticSide::AUTO_LEFT) {
|
||||
if (automatic_side & Style::AutomaticSide::AUTO_TOP) return RealRect(left + width, top + height, width, height); //bottom right
|
||||
else return RealRect(left + width, top - height, width, height); //top right
|
||||
}
|
||||
else {
|
||||
if (automatic_side & Style::AutomaticSide::AUTO_TOP) return RealRect(left - width, top + height, width, height); //bottom left
|
||||
else return RealRect(left - width, top - height, width, height); //top left
|
||||
}
|
||||
}
|
||||
return getExternalRect();
|
||||
}
|
||||
|
||||
/// Does this style have a non-zero size (or is it scripted)?
|
||||
|
||||
@@ -39,6 +39,13 @@ public:
|
||||
inline ImageValue(const ImageFieldP& field) : Value(field) {}
|
||||
DECLARE_VALUE_TYPE(Image, LocalFileName);
|
||||
|
||||
inline Image getImage(const SetP& set) {
|
||||
auto imageInputStream = set->openIn(filename);
|
||||
Image img(*imageInputStream, wxBITMAP_TYPE_PNG);
|
||||
if (!img.IsOk()) throw ScriptError(_ERROR_2_("file not found", filename.toStringForKey(), set));
|
||||
return img;
|
||||
}
|
||||
|
||||
ValueType filename; ///< Filename of the image (in the current package), or ""
|
||||
Age last_update; ///< When was the image last changed?
|
||||
};
|
||||
@@ -55,11 +62,4 @@ public:
|
||||
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));
|
||||
return encodeImageInString(img);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -89,13 +89,19 @@ FileFormatP mtg_editor_file_format();
|
||||
// ----------------------------------------------------------------------------- : Other ways to export
|
||||
|
||||
/// Generate a wxImage of one or more cards
|
||||
Image export_image(const SetP& set, const CardP& card, const bool write_metadata = true, const double zoom = 1.0, const Radians angle_radians = 0.0, const double bleed_pixels = 0.0);
|
||||
Image export_image(const SetP& set, const vector<CardP>& cards, const int padding = 2, const double global_zoom = 1.0, const bool use_zoom_setting = true, const bool use_rotation_setting = true, const bool use_bleed_setting = false);
|
||||
Image export_image(const SetP& set, const CardP& card, bool write_metadata = true, double zoom = 1.0, Radians angle_radians = 0.0, double bleed_pixels = 0.0);
|
||||
Image export_image(const SetP& set, const vector<CardP>& cards, int padding = 2, double global_zoom = 1.0, bool use_zoom_setting = true, bool use_rotation_setting = true, bool use_bleed_setting = false);
|
||||
|
||||
/// Export the image of one or more cards to a given filename, using the app's zoom, rotation and bleed settings, and including metadata
|
||||
void export_image(const SetP& set, const CardP& card, const String& filename);
|
||||
void export_image(const SetP& set, const vector<CardP>& cards, const String& path, const String& filename_template, FilenameConflicts conflicts);
|
||||
|
||||
/// Write the metadata for a card
|
||||
// Assuming first the zoom is applied, then the rotation, then the offset.
|
||||
// This means that width and height need to be already scaled by a factor of zoom, but not already rotated
|
||||
// while offset_x and offset_y need to be already scaled and already rotated.
|
||||
String export_metadata(const SetP& set, const CardP& card, double zoom, Radians angle_radians, int width, int height, double offset_x, double offset_y);
|
||||
|
||||
/// Export a set to Magic Workstation format
|
||||
void export_mws(Window* parent, const SetP& set);
|
||||
|
||||
|
||||
+55
-63
@@ -35,7 +35,7 @@ Rotation ZoomedUnrotatedDataViewer::getRotation() const {
|
||||
|
||||
// ----------------------------------------------------------------------------- : wxImage export
|
||||
|
||||
Image export_image(const SetP& set, const CardP& card, const bool write_metadata, const double zoom, const Radians angle_radians, const double bleed_pixels) {
|
||||
Image export_image(const SetP& set, const CardP& card, bool write_metadata, double zoom, Radians angle_radians, double bleed_pixels) {
|
||||
if (!set) throw Error(_("no set"));
|
||||
/// create and zoom
|
||||
ZoomedUnrotatedDataViewer viewer = ZoomedUnrotatedDataViewer(zoom);
|
||||
@@ -130,46 +130,23 @@ Image export_image(const SetP& set, const CardP& card, const bool write_metadata
|
||||
|
||||
/// add metadata
|
||||
if (write_metadata) {
|
||||
String metadata = _("<mse-card-data>[");
|
||||
IndexMap<FieldP, ValueP>& card_data = card->data;
|
||||
boost::json::object cardv = mse_to_json(card, set.get());
|
||||
boost::json::object& cardv_data = cardv["data"].as_object();
|
||||
StyleSheetP stylesheet = set->stylesheetForP(card);
|
||||
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) {
|
||||
ImageValue* value = dynamic_cast<ImageValue*>(it->get());
|
||||
if (value && !value->filename.empty()) {
|
||||
FieldP field = (*it)->fieldP;
|
||||
ImageStyle* style = dynamic_cast<ImageStyle*>(stylesheet->card_style.at(field->index).get());
|
||||
if (style) {
|
||||
style->update(set->getContext(card));
|
||||
// store the entire image in the metadata
|
||||
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, bleed_pixels, width, height);
|
||||
cardv_data[field->name.ToStdString()] = rect;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
metadata += json_ugly_print(cardv) + _("]</mse-card-data>");
|
||||
bool rotated = is_rad90(angle_radians) || is_rad270(angle_radians); // we stored width and height after rotation, but export_metadata expects them before rotation
|
||||
String metadata = _("<mse-card-data>[")
|
||||
+ export_metadata(set, card, zoom, angle_radians, rotated ? height : width, rotated ? width : height, bleed_pixels, bleed_pixels)
|
||||
+ _("]</mse-card-data>");
|
||||
img.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, metadata);
|
||||
}
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
Image export_image( const SetP& set, const vector<CardP>& cards,
|
||||
const int padding,
|
||||
const double global_zoom,
|
||||
const bool use_zoom_setting,
|
||||
const bool use_rotation_setting,
|
||||
const bool use_bleed_setting) {
|
||||
Image export_image(const SetP& set,
|
||||
const vector<CardP>& cards,
|
||||
int padding,
|
||||
double global_zoom,
|
||||
bool use_zoom_setting,
|
||||
bool use_rotation_setting,
|
||||
bool use_bleed_setting) {
|
||||
if (!set) throw Error(_("no set"));
|
||||
if (cards.size() == 0) throw Error(_("no cards"));
|
||||
vector<Image> imgs;
|
||||
@@ -224,32 +201,8 @@ Image export_image( const SetP& set, const vector<CardP>& cards,
|
||||
for (int i = 0; i < cards.size(); ++i) {
|
||||
if (i > 0) metadata += _(",");
|
||||
CardP card = cards[i];
|
||||
IndexMap<FieldP, ValueP>& card_data = card->data;
|
||||
boost::json::object cardv = mse_to_json(card, set.get());
|
||||
boost::json::object& cardv_data = cardv["data"].as_object();
|
||||
StyleSheetP stylesheet = set->stylesheetForP(card);
|
||||
if (!settings.stylesheetSettingsFor(*stylesheet).card_notes_export()) cardv["notes"] = "";
|
||||
for(IndexMap<FieldP, ValueP>::iterator it = card_data.begin() ; it != card_data.end() ; ++it) {
|
||||
ImageValue* value = dynamic_cast<ImageValue*>(it->get());
|
||||
if (value && !value->filename.empty()) {
|
||||
FieldP field = (*it)->fieldP;
|
||||
ImageStyle* style = dynamic_cast<ImageStyle*>(stylesheet->card_style.at(field->index).get());
|
||||
if (style) {
|
||||
style->update(set->getContext(card));
|
||||
// store the entire image in the metadata
|
||||
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(zooms[i], angles[i], bleeds[i] + offsets[i], bleeds[i], widths[i], heights[i]);
|
||||
cardv_data[field->name.ToStdString()] = rect;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
metadata += json_ugly_print(cardv);
|
||||
bool rotated = is_rad90(angles[i]) || is_rad270(angles[i]); // we stored width and height after rotation, but export_metadata expects them before rotation
|
||||
metadata += export_metadata(set, card, zooms[i], angles[i], rotated ? heights[i] : widths[i], rotated ? widths[i] : heights[i], bleeds[i] + offsets[i], bleeds[i]);
|
||||
}
|
||||
metadata += _("]</mse-card-data>");
|
||||
global_img.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, metadata);
|
||||
@@ -265,8 +218,7 @@ void export_image(const SetP& set, const CardP& card, const String& filename) {
|
||||
img.SaveFile(filename);
|
||||
}
|
||||
|
||||
void export_image(const SetP& set, const vector<CardP>& cards,
|
||||
const String& path, const String& filename_template, FilenameConflicts conflicts)
|
||||
void export_image(const SetP& set, const vector<CardP>& cards, const String& path, const String& filename_template, FilenameConflicts conflicts)
|
||||
{
|
||||
wxBusyCursor busy;
|
||||
// Script
|
||||
@@ -290,3 +242,43 @@ void export_image(const SetP& set, const vector<CardP>& cards,
|
||||
export_image(set, card, filename);
|
||||
}
|
||||
}
|
||||
|
||||
String export_metadata(const SetP& set, const CardP& card, double zoom, Radians angle_radians, int width, int height, double offset_x, double offset_y)
|
||||
{
|
||||
IndexMap<FieldP, ValueP>& card_data = card->data;
|
||||
boost::json::object cardv = mse_to_json(card, set.get());
|
||||
boost::json::object& cardv_data = cardv["data"].as_object();
|
||||
StyleSheetP stylesheet = set->stylesheetForP(card);
|
||||
if (!settings.stylesheetSettingsFor(*stylesheet).card_notes_export()) cardv["notes"] = "";
|
||||
RealRect bounds_rect = RealRect(0, 0, width, height);
|
||||
int bounds_degrees = 0;
|
||||
RealRect::rotate(bounds_rect, bounds_degrees, width, height, lround(rad_to_deg(angle_radians)));
|
||||
RealRect::translate(bounds_rect, bounds_degrees, offset_x, offset_y);
|
||||
cardv.emplace("bounds", encodeRectInStdString(bounds_rect, bounds_degrees));
|
||||
// iterate over all image fields
|
||||
for (IndexMap<FieldP, ValueP>::iterator it = card_data.begin(); it != card_data.end(); ++it) {
|
||||
ImageValue* value = dynamic_cast<ImageValue*>(it->get());
|
||||
if (value && !value->filename.empty()) {
|
||||
FieldP field = (*it)->fieldP;
|
||||
ImageStyle* style = dynamic_cast<ImageStyle*>(stylesheet->card_style.at(field->index).get());
|
||||
if (style) {
|
||||
style->update(set->getContext(card));
|
||||
// store the entire image in the metadata
|
||||
if (style->store_in_metadata()) {
|
||||
Image img = value->getImage(set);
|
||||
cardv_data[field->name.ToStdString()] = encodeImageInString(img);
|
||||
}
|
||||
// store only crop coordinates
|
||||
else {
|
||||
RealRect rect = style->getCanonicalExternalRect();
|
||||
int degrees = lround(style->angle());
|
||||
RealRect::scale(rect, degrees, zoom, zoom);
|
||||
RealRect::rotate(rect, degrees, width, height, lround(rad_to_deg(angle_radians))); // width and height are already scaled
|
||||
RealRect::translate(rect, degrees, offset_x, offset_y); // offset_x and offset_y are already scaled and rotated
|
||||
cardv_data[field->name.ToStdString()] = encodeRectInStdString(rect, degrees);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return json_ugly_print(cardv);
|
||||
}
|
||||
|
||||
@@ -9,13 +9,15 @@
|
||||
// ----------------------------------------------------------------------------- : Includes
|
||||
|
||||
#include <util/prec.hpp>
|
||||
#include <util/real_point.hpp>
|
||||
#include <boost/json.hpp>
|
||||
#include <wx/filename.h>
|
||||
#include <fstream>
|
||||
|
||||
// ----------------------------------------------------------------------------- : Crop Rect Encoding
|
||||
|
||||
/// Encode a rect in a std::string
|
||||
inline static std::string encodeRectInStdString(wxRect rect, int degrees) {
|
||||
inline static std::string encodeRectInStdString(RealRect rect, int degrees) {
|
||||
return "<mse-crop-data>" + std::to_string((int)std::ceil (rect.x)) +
|
||||
";" + std::to_string((int)std::ceil (rect.y)) +
|
||||
";" + std::to_string((int)std::floor(rect.width)) +
|
||||
@@ -25,7 +27,7 @@ inline static std::string encodeRectInStdString(wxRect rect, int degrees) {
|
||||
}
|
||||
|
||||
/// Encode a rect in a wxString
|
||||
inline static String encodeRectInWxString(wxRect rect, int degrees) {
|
||||
inline static String encodeRectInWxString(RealRect rect, int degrees) {
|
||||
return _("<mse-crop-data>") + wxString::Format(wxT("%i"), (int)std::ceil (rect.x)) +
|
||||
_(";") + wxString::Format(wxT("%i"), (int)std::ceil (rect.y)) +
|
||||
_(";") + wxString::Format(wxT("%i"), (int)std::floor(rect.width)) +
|
||||
@@ -35,7 +37,7 @@ inline static String encodeRectInWxString(wxRect rect, int degrees) {
|
||||
}
|
||||
|
||||
/// Retreive a rect encoded in a string, return true if successful
|
||||
inline static bool decodeRectFromString(const String& rectString, wxRect& rect_out, int& degrees_out) {
|
||||
inline static bool decodeRectFromString(const String& rectString, RealRect& rect_out, int& degrees_out) {
|
||||
size_t start = rectString.find(_("<mse-crop-data>"));
|
||||
if (start == String::npos) return false;
|
||||
size_t end = rectString.find(_("</mse-crop-data>"), start + 15);
|
||||
@@ -73,44 +75,22 @@ inline static bool decodeRectFromString(const String& rectString, wxRect& rect_o
|
||||
|
||||
if(!string.ToInt(°rees_out)) return false;
|
||||
|
||||
rect_out = wxRect(x, y, width, height);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Apply a transformation to a rect, return true if successful
|
||||
inline static bool transformEncodedRect(wxRect& rect, int& degrees, double scale, Radians angle, int offset_x, int offset_y, int img_width, int img_height) {
|
||||
if (degrees != 0 && degrees != 90 && degrees != 180 && degrees != 270) return false;
|
||||
rect = wxRect(rect.x * scale, rect.y * scale, rect.width * scale, rect.height * scale);
|
||||
if (is_rad0(angle)) {
|
||||
} else if (is_rad180(angle)) {
|
||||
rect = wxRect(img_width - rect.x - rect.width, img_height - rect.y - rect.height, rect.width, rect.height);
|
||||
degrees += 180;
|
||||
} else if (is_rad90(angle)) {
|
||||
rect = wxRect(rect.y, img_height - rect.x - rect.width, rect.height, rect.width);
|
||||
degrees += 90;
|
||||
} else if (is_rad270(angle)) {
|
||||
rect = wxRect(img_width - rect.y - rect.height, rect.x, rect.height, rect.width);
|
||||
degrees += 270;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
rect = wxRect(rect.x + offset_x, rect.y + offset_y, rect.width, rect.height);
|
||||
if (degrees >= 360) degrees -= 360;
|
||||
rect_out = RealRect(x, y, width, height);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Retreive a rect encoded in a string, apply a transformation, then encode it back
|
||||
inline static String transformEncodedRect(const String& rectString, double scale, Radians angle, int offset_x, int offset_y, int img_width, int img_height) {
|
||||
wxRect rect;
|
||||
inline static String transformEncodedRect(const String& rectString, RectTransform transform, double param_x, double param_y, int mode) {
|
||||
RealRect rect(0,0,0,0);
|
||||
int degrees;
|
||||
if (!decodeRectFromString(rectString, rect, degrees)) return _("");
|
||||
if (!transformEncodedRect(rect, degrees, scale, angle, offset_x, offset_y, img_width, img_height)) return _("");
|
||||
transform(rect, degrees, param_x, param_y, mode);
|
||||
return encodeRectInWxString(rect, degrees);
|
||||
}
|
||||
|
||||
/// Retreive all rects encoded in a string, apply a transformation, then encode them back
|
||||
inline static String transformAllEncodedRects(const String& rectString, double scale, Radians angle, int offset_x, int offset_y, int img_width, int img_height) {
|
||||
wxRect rect;
|
||||
inline static String transformAllEncodedRects(const String& rectString, RectTransform transform, double param_x, double param_y, int mode = 0) {
|
||||
RealRect rect(0,0,0,0);
|
||||
int degrees;
|
||||
size_t start = rectString.find(_("<mse-crop-data>"));
|
||||
if (start == String::npos) return rectString;
|
||||
@@ -121,19 +101,13 @@ inline static String transformAllEncodedRects(const String& rectString, double s
|
||||
end = rectString.find(_("</mse-crop-data>"), start + 15);
|
||||
if (end == String::npos) return rectString;
|
||||
end += 16;
|
||||
result = result + transformEncodedRect(rectString.substr(start, end - start), scale, angle, offset_x, offset_y, img_width, img_height);
|
||||
result = result + transformEncodedRect(rectString.substr(start, end - start), transform, param_x, param_y, mode);
|
||||
start = rectString.find(_("<mse-crop-data>"), end);
|
||||
}
|
||||
result = result + rectString.substr(end);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Apply a transformation to a rect, then encode it in a string
|
||||
inline static std::string transformAndEncodeRectInString(wxRect rect, int degrees, double scale, Radians angle, int offset_x, int offset_y, int img_width, int img_height) {
|
||||
if (!transformEncodedRect(rect, degrees, scale, angle, offset_x, offset_y, img_width, img_height)) return "";
|
||||
return encodeRectInStdString(rect, degrees);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : File to UTF8 Encoding
|
||||
|
||||
/// Encode a file in a string
|
||||
@@ -227,3 +201,52 @@ inline static Image decodeImageFromString(const String& string) {
|
||||
wxRemoveFile(temppath.substr(0, temppath.size() - 4));
|
||||
return img;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Metadata manipulation
|
||||
|
||||
inline static String metadata_merge(const Image& img1, const Image& img2, int offset_x1 = 0, int offset_y1 = 0, int offset_x2 = 0, int offset_y2 = 0)
|
||||
{
|
||||
if (img1.HasOption(wxIMAGE_OPTION_PNG_DESCRIPTION)) {
|
||||
String metadata1 = img1.GetOption(wxIMAGE_OPTION_PNG_DESCRIPTION);
|
||||
if (offset_x1 != 0 || offset_y1 != 0) metadata1 = transformAllEncodedRects(metadata1, RealRect::translate, offset_x1, offset_y1);
|
||||
if (img2.HasOption(wxIMAGE_OPTION_PNG_DESCRIPTION)) {
|
||||
String metadata2 = img2.GetOption(wxIMAGE_OPTION_PNG_DESCRIPTION);
|
||||
if (offset_x2 != 0 || offset_y2 != 0) metadata2 = transformAllEncodedRects(metadata2, RealRect::translate, offset_x2, offset_y2);
|
||||
size_t end1 = metadata1.find(_("</mse-card-data>"));
|
||||
size_t start2 = metadata2.find(_("<mse-card-data>"));
|
||||
if (end1 != String::npos && start2 != String::npos && end1 > 0 && start2 + 16 < metadata2.size()) {
|
||||
metadata1 = metadata1.substr(0, end1 - 1) + "," + metadata2.substr(start2 + 16);
|
||||
}
|
||||
}
|
||||
return metadata1;
|
||||
}
|
||||
else if (img2.HasOption(wxIMAGE_OPTION_PNG_DESCRIPTION)) {
|
||||
String metadata2 = img2.GetOption(wxIMAGE_OPTION_PNG_DESCRIPTION);
|
||||
if (offset_x2 != 0 || offset_y2 != 0) metadata2 = transformAllEncodedRects(metadata2, RealRect::translate, offset_x2, offset_y2);
|
||||
return metadata2;
|
||||
}
|
||||
return _("");
|
||||
}
|
||||
|
||||
inline static boost::json::array metadata_to_json(const String& metadata) {
|
||||
size_t start = metadata.find(_("<mse-card-data>"));
|
||||
if (start == String::npos) return boost::json::array();
|
||||
size_t end = metadata.find(_("</mse-card-data>"), start + 15);
|
||||
if (end == String::npos) return boost::json::array();
|
||||
String string = metadata.substr(start + 15, end - (start + 15));
|
||||
try {
|
||||
boost::system::error_code ec;
|
||||
boost::json::parse_options options;
|
||||
options.allow_invalid_utf8 = true;
|
||||
boost::json::value jv = boost::json::parse(string.ToStdString(), ec, {}, options);
|
||||
if(ec || !jv.is_array()) {
|
||||
queue_message(MESSAGE_ERROR, _ERROR_("json cant parse"));
|
||||
return boost::json::array();
|
||||
}
|
||||
return jv.as_array();
|
||||
}
|
||||
catch (...) {
|
||||
queue_message(MESSAGE_ERROR, _ERROR_("json cant parse"));
|
||||
return boost::json::array();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
// ----------------------------------------------------------------------------- : Includes
|
||||
|
||||
#include <util/prec.hpp>
|
||||
#include <data/format/image_encoding.hpp>
|
||||
#include <gfx/gfx.hpp>
|
||||
#include <util/error.hpp>
|
||||
|
||||
@@ -71,6 +72,9 @@ void linear_blend(Image& img1, const Image& img2, double x1,double y1, double x2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//transfer metadata
|
||||
img1.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, metadata_merge(img1, img2));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Mask Blend
|
||||
@@ -102,6 +106,9 @@ void mask_blend(Image& img1, const Image& img2, const Image& mask) {
|
||||
alpha1[i] = (alpha1[i] * dataM[i * 3] + alpha2[i] * (255 - dataM[i * 3])) / 255;
|
||||
}
|
||||
}
|
||||
|
||||
//transfer metadata
|
||||
img1.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, metadata_merge(img1, img2));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Alpha
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
// ----------------------------------------------------------------------------- : Includes
|
||||
|
||||
#include <util/prec.hpp>
|
||||
#include <data/format/image_encoding.hpp>
|
||||
#include <gfx/gfx.hpp>
|
||||
#include <util/reflect.hpp>
|
||||
#include <algorithm>
|
||||
@@ -576,6 +577,9 @@ void combine_image(Image& a, const Image& b, ImageCombine combine) {
|
||||
DISPATCH(COMBINE_SMALLER_THAN_245);
|
||||
DISPATCH(COMBINE_SMALLER_THAN_250);
|
||||
}
|
||||
|
||||
//transfer metadata
|
||||
a.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, metadata_merge(a, b));
|
||||
}
|
||||
|
||||
void draw_combine_image(DC& dc, UInt x, UInt y, const Image& img, ImageCombine combine) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <data/set.hpp>
|
||||
#include <data/symbol.hpp>
|
||||
#include <data/field/symbol.hpp>
|
||||
#include <script/functions/json.hpp>
|
||||
#include <render/symbol/filter.hpp>
|
||||
#include <gui/util.hpp> // load_resource_image
|
||||
#include <gui/web_request_window.hpp>
|
||||
@@ -298,6 +299,11 @@ Image EnlargeImage::generate(const Options& opt) {
|
||||
memcpy(data2 + dw + (y+dh)*w2, data1 + y*w, w); // copy a line
|
||||
}
|
||||
}
|
||||
// transfer metadata
|
||||
if (img.HasOption(wxIMAGE_OPTION_PNG_DESCRIPTION)) {
|
||||
String metadata = transformAllEncodedRects(img.GetOption(wxIMAGE_OPTION_PNG_DESCRIPTION), RealRect::translate, dw, dh);
|
||||
larger.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, metadata);
|
||||
}
|
||||
// done
|
||||
return larger;
|
||||
}
|
||||
@@ -436,8 +442,8 @@ Image BleedEdgedImage::generate(const Options& opt) {
|
||||
}
|
||||
// transfer metadata
|
||||
if (base_img.HasOption(wxIMAGE_OPTION_PNG_DESCRIPTION)) {
|
||||
String desc = transformAllEncodedRects(base_img.GetOption(wxIMAGE_OPTION_PNG_DESCRIPTION), 1.0, 0.0, dw, dh, width, height);
|
||||
img.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, desc);
|
||||
String metadata = transformAllEncodedRects(base_img.GetOption(wxIMAGE_OPTION_PNG_DESCRIPTION), RealRect::translate, dw, dh);
|
||||
img.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, metadata);
|
||||
}
|
||||
// done
|
||||
return img;
|
||||
@@ -482,8 +488,11 @@ Image InsertedImage::generate(const Options& opt) {
|
||||
}
|
||||
img.Paste(base_img, base_x, base_y, wxIMAGE_ALPHA_BLEND_COMPOSE);
|
||||
img.Paste(inserted_img, inserted_x, inserted_y, wxIMAGE_ALPHA_BLEND_COMPOSE);
|
||||
// transfer metadata
|
||||
img.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, metadata_merge(base_img, inserted_img, base_x, base_y, inserted_x, inserted_y));
|
||||
return img;
|
||||
}
|
||||
|
||||
ImageCombine InsertedImage::combine() const {
|
||||
return base_image->combine();
|
||||
}
|
||||
@@ -519,7 +528,29 @@ Image CropImage::generate(const Options& opt) {
|
||||
alpha += 1;
|
||||
}
|
||||
Image base_img = image->generate(opt);
|
||||
img.Paste(base_img, -(int)offset_x, -(int)offset_y, wxIMAGE_ALPHA_BLEND_OVER);
|
||||
img.Paste(base_img, -(int)offset_x, -(int)offset_y, wxIMAGE_ALPHA_BLEND_COMPOSE);
|
||||
// transfer metadata
|
||||
if (base_img.HasOption(wxIMAGE_OPTION_PNG_DESCRIPTION)) {
|
||||
String metadata = transformAllEncodedRects(base_img.GetOption(wxIMAGE_OPTION_PNG_DESCRIPTION), RealRect::translate, -offset_x, -offset_y);
|
||||
// prune out of bounds cards
|
||||
boost::json::array cardsv = metadata_to_json(metadata);
|
||||
boost::json::array inbounds_cardsv;
|
||||
for (int i = 0; i < cardsv.size(); i++) {
|
||||
boost::json::object cardv = cardsv[i].as_object();
|
||||
if (cardv.contains("bounds")) {
|
||||
String bounds = String(cardv["bounds"].as_string().c_str());
|
||||
RealRect rect(0.0, 0.0, 0.0, 0.0);
|
||||
int degrees = 0;
|
||||
if (decodeRectFromString(bounds, rect, degrees)) {
|
||||
rect = rect.intersect(RealRect(0.0, 0.0, width, height));
|
||||
if (rect.width <= 0.0 || rect.height <= 0.0 ) continue;
|
||||
}
|
||||
}
|
||||
inbounds_cardsv.emplace_back(cardv);
|
||||
}
|
||||
metadata = "<mse-card-data>" + json_ugly_print(inbounds_cardsv) + "</mse-card-data>";
|
||||
img.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, metadata);
|
||||
}
|
||||
return img;
|
||||
}
|
||||
bool CropImage::operator == (const GeneratedImage& that) const {
|
||||
@@ -730,6 +761,19 @@ bool ImageValueToImage::operator == (const GeneratedImage& that) const {
|
||||
&& age == that2->age;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : SetMetadataImage
|
||||
|
||||
Image SetMetadataImage::generate(const Options& opt) {
|
||||
Image img = image->generate(opt);
|
||||
img.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, metadata);
|
||||
return img;
|
||||
}
|
||||
bool SetMetadataImage::operator == (const GeneratedImage& that) const {
|
||||
const SetMetadataImage* that2 = dynamic_cast<const SetMetadataImage*>(&that);
|
||||
return that2 && *image == *that2->image
|
||||
&& metadata == that2->metadata;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : ImportedImage
|
||||
|
||||
ImportedImage::ImportedImage(Set* set, const String& filepath)
|
||||
|
||||
@@ -467,6 +467,20 @@ private:
|
||||
Age age; ///< Age the image was last updated
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : SetMetadataImage
|
||||
|
||||
/// Change the alpha channel of an image
|
||||
class SetMetadataImage : public SimpleFilterImage {
|
||||
public:
|
||||
inline SetMetadataImage(const GeneratedImageP& image, const String& metadata)
|
||||
: SimpleFilterImage(image), metadata(metadata)
|
||||
{}
|
||||
Image generate(const Options& opt) override;
|
||||
bool operator == (const GeneratedImage& that) const override;
|
||||
private:
|
||||
String metadata;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : ExternalImage
|
||||
|
||||
/// Load an image from outside the data folder
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
// ----------------------------------------------------------------------------- : Includes
|
||||
|
||||
#include <util/prec.hpp>
|
||||
#include <data/format/image_encoding.hpp>
|
||||
#include <gfx/gfx.hpp>
|
||||
#include <util/error.hpp>
|
||||
#if defined(__WXMSW__) && wxUSE_WXDIB
|
||||
@@ -403,6 +404,11 @@ Image make_stroke_image(Image& img, Color stroke_color, int stroke_radius, int b
|
||||
for (int i = 0 ; i < blur_radius ; ++i) {
|
||||
blur_image_alpha(s_img, 3);
|
||||
}
|
||||
// transfer metadata
|
||||
if (img.HasOption(wxIMAGE_OPTION_PNG_DESCRIPTION)) {
|
||||
String metadata = transformAllEncodedRects(img.GetOption(wxIMAGE_OPTION_PNG_DESCRIPTION), RealRect::translate, margin, margin);
|
||||
s_img.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, metadata);
|
||||
}
|
||||
return s_img;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <util/prec.hpp>
|
||||
#include <gfx/gfx.hpp>
|
||||
#include <data/format/image_encoding.hpp>
|
||||
#include <util/error.hpp>
|
||||
|
||||
// ----------------------------------------------------------------------------- : Resample passes
|
||||
@@ -153,6 +154,13 @@ void resample_and_clip(const Image& img_in, Image& img_out, wxRect rect) {
|
||||
resample_pass(img_in, img_temp, offset_in, 0, rect.width, 1, img_temp.GetWidth(), 1, rect .GetHeight(), img_in.GetWidth(), img_temp.GetWidth());
|
||||
resample_pass(img_temp, img_out, 0, 0, rect.height, img_temp.GetWidth(), img_out .GetHeight(), img_temp.GetWidth(), img_temp.GetWidth(), 1, 1);
|
||||
}
|
||||
// transfer metadata
|
||||
if (img_in.HasOption(wxIMAGE_OPTION_PNG_DESCRIPTION)) {
|
||||
double scale_x = (double)img_out.GetWidth() / img_in.GetWidth();
|
||||
double scale_y = (double)img_out.GetHeight() / img_in.GetHeight();
|
||||
String metadata = transformAllEncodedRects(img_in.GetOption(wxIMAGE_OPTION_PNG_DESCRIPTION), RealRect::scale, scale_x, scale_y);
|
||||
img_out.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -42,11 +42,8 @@ Image rotate_image_impl(const Image& img) {
|
||||
}
|
||||
// transfer metadata
|
||||
if (img.HasOption(wxIMAGE_OPTION_PNG_DESCRIPTION)) {
|
||||
if (!almost_equal(Rotater::angle(), rad180)) {
|
||||
swap(width, height);
|
||||
}
|
||||
String desc = transformAllEncodedRects(img.GetOption(wxIMAGE_OPTION_PNG_DESCRIPTION), 1.0, Rotater::angle(), 0, 0, width, height);
|
||||
ret.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, desc);
|
||||
String metadata = transformAllEncodedRects(img.GetOption(wxIMAGE_OPTION_PNG_DESCRIPTION), RealRect::rotate, width, height, lround(rad_to_deg(Rotater::angle())));
|
||||
ret.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, metadata);
|
||||
}
|
||||
// ret is rotated image
|
||||
return ret;
|
||||
@@ -103,7 +100,9 @@ Image rotate_image(const Image& image, Radians angle) {
|
||||
if (is_rad270(a)) return rotate_image_impl<Rotate270deg>(image);
|
||||
else {
|
||||
if (!image.HasAlpha()) const_cast<Image&>(image).InitAlpha();
|
||||
return image.Rotate(angle, wxPoint(0,0));
|
||||
Image ret = image.Rotate(angle, wxPoint(0,0));
|
||||
ret.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, image.GetOption(wxIMAGE_OPTION_PNG_DESCRIPTION));
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +132,10 @@ Image flip_image_horizontal(Image const& img) {
|
||||
out.InitAlpha();
|
||||
do_flip(img.GetAlpha(), out.GetAlpha(), 1, w, h);
|
||||
}
|
||||
if (img.HasOption(wxIMAGE_OPTION_PNG_DESCRIPTION)) {
|
||||
String metadata = transformAllEncodedRects(img.GetOption(wxIMAGE_OPTION_PNG_DESCRIPTION), RealRect::flip, w, h, 1);
|
||||
out.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, metadata);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -144,5 +147,9 @@ Image flip_image_vertical(Image const& img) {
|
||||
out.InitAlpha();
|
||||
do_flip(img.GetAlpha(), out.GetAlpha(), 1 * w, h);
|
||||
}
|
||||
if (img.HasOption(wxIMAGE_OPTION_PNG_DESCRIPTION)) {
|
||||
String metadata = transformAllEncodedRects(img.GetOption(wxIMAGE_OPTION_PNG_DESCRIPTION), RealRect::flip, w, h, 0);
|
||||
out.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, metadata);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -242,6 +242,44 @@ bool CardListBase::doBulkModification() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void CardListBase::parseImageMetadata(CardP& card, const Image& image)
|
||||
{
|
||||
for (IndexMap<FieldP, ValueP>::iterator it = card->data.begin(); it != card->data.end(); it++) {
|
||||
ImageValue* value = dynamic_cast<ImageValue*>(it->get());
|
||||
if (value && !value->filename.empty()) {
|
||||
RealRect rect(0.0, 0.0, 0.0, 0.0);
|
||||
int degrees = 0;
|
||||
decodeRectFromString(value->filename.toStringForKey(), rect, degrees);
|
||||
rect = rect.intersect(RealRect(0.0, 0.0, image.GetWidth(), image.GetHeight()));
|
||||
if (rect.width > 0.0 && rect.height > 0.0) {
|
||||
Image img = image.GetSubImage(rect);
|
||||
img = rotate_image(img, deg_to_rad(360 - degrees));
|
||||
LocalFileName filename = set->newFileName("cropped_image", _(".png")); // a new unique name in the package
|
||||
img.SaveFile(set->nameOut(filename), wxBITMAP_TYPE_PNG);
|
||||
value->filename = filename;
|
||||
}
|
||||
else {
|
||||
value->filename = LocalFileName();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CardListBase::parseImageMetadata(CardP& card)
|
||||
{
|
||||
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 = decodeImageFromString(value->filename.toStringForKey());
|
||||
if (img.IsOk()) {
|
||||
LocalFileName filename = set->newFileName(_("decoded_image"), _(".png")); // a new unique name in the package
|
||||
img.SaveFile(set->nameOut(filename), wxBITMAP_TYPE_PNG);
|
||||
value->filename = filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CardListBase::parseUrl(String& url, vector<CardP>& out) {
|
||||
size_t j = out.size();
|
||||
size_t pos = url.find("URL=");
|
||||
@@ -298,28 +336,11 @@ bool CardListBase::parseImage(Image& image, vector<CardP>& out) {
|
||||
if (image.HasOption(wxIMAGE_OPTION_PNG_DESCRIPTION)) {
|
||||
auto text = image.GetOption(wxIMAGE_OPTION_PNG_DESCRIPTION);
|
||||
parseText(text, out);
|
||||
|
||||
// crop image rects to populate image fields
|
||||
for (; j < out.size(); j++) {
|
||||
CardP& card = out[j];
|
||||
for (IndexMap<FieldP, ValueP>::iterator it = card->data.begin(); it != card->data.end(); it++) {
|
||||
ImageValue* value = dynamic_cast<ImageValue*>(it->get());
|
||||
if (value && !value->filename.empty()) {
|
||||
wxRect rect = wxRect(0,0,0,0);
|
||||
int degrees = 0;
|
||||
value->filename.getExternalRect(rect, degrees);
|
||||
rect.x = max(0, rect.x);
|
||||
rect.y = max(0, rect.y);
|
||||
rect.width -= max(0, (rect.x + rect.width) - image.GetWidth());
|
||||
rect.height -= max(0, (rect.y + rect.height) - image.GetHeight());
|
||||
if (rect.width > 0 && rect.height > 0) {
|
||||
Image img = image.GetSubImage(rect);
|
||||
img = rotate_image(img, deg_to_rad(360-degrees));
|
||||
LocalFileName filename = set->newFileName("cropped_image", _(".png")); // a new unique name in the package
|
||||
img.SaveFile(set->nameOut(filename), wxBITMAP_TYPE_PNG);
|
||||
value->filename = filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int k = j; k < out.size(); k++) {
|
||||
CardP& card = out[k];
|
||||
parseImageMetadata(card, image);
|
||||
}
|
||||
}
|
||||
return j < out.size();
|
||||
@@ -345,20 +366,10 @@ bool CardListBase::parseText(String& text, vector<CardP>& out) {
|
||||
}
|
||||
} catch (...) {}
|
||||
|
||||
// recreate images to populate image fields
|
||||
// decode 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"), _(".png")); // a new unique name in the package
|
||||
img.SaveFile(set->nameOut(filename), wxBITMAP_TYPE_PNG);
|
||||
value->filename = filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
parseImageMetadata(card);
|
||||
}
|
||||
|
||||
return j < out.size();
|
||||
|
||||
@@ -97,6 +97,10 @@ public:
|
||||
bool parseText (String& text, vector<CardP>& out);
|
||||
bool parseImage(Image& image, vector<CardP>& out);
|
||||
|
||||
/// Recreate the images for image fields from the metadata
|
||||
void parseImageMetadata(CardP& card, const Image& image);
|
||||
void parseImageMetadata(CardP& card);
|
||||
|
||||
// --------------------------------------------------- : Card linking
|
||||
|
||||
bool canLink() const;
|
||||
|
||||
@@ -76,9 +76,7 @@ SCRIPT_FUNCTION(set_metadata) {
|
||||
SCRIPT_PARAM(Set*, set);
|
||||
SCRIPT_PARAM(GeneratedImageP, input);
|
||||
SCRIPT_PARAM(String, metadata);
|
||||
Image img = input->generate(GeneratedImage::Options(0, 0, set));
|
||||
img.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, metadata);
|
||||
return make_intrusive<ArbitraryImage>(img);
|
||||
return make_intrusive<SetMetadataImage>(input, metadata);
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(width_of) {
|
||||
|
||||
@@ -54,15 +54,6 @@ public:
|
||||
|
||||
inline String const& toStringForKey() const { return fn; }
|
||||
|
||||
/// Retreive a rect from a filename
|
||||
inline void getExternalRect(wxRect& rect_out, int& degrees_out) {
|
||||
decodeRectFromString(fn, rect_out, degrees_out);
|
||||
}
|
||||
/// Retreive an image from a filename
|
||||
inline Image getExternalImage() {
|
||||
return decodeImageFromString(fn);
|
||||
}
|
||||
|
||||
private:
|
||||
LocalFileName(const wxString& fn) : fn(fn) {}
|
||||
String fn;
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
/// A point using real (double) coordinates
|
||||
typedef Vector2D RealPoint;
|
||||
|
||||
/// A transformation function on a rect and an angle in degrees
|
||||
class RealRect;
|
||||
typedef std::function<void(RealRect&,int&,double,double,int)> RectTransform;
|
||||
|
||||
// ----------------------------------------------------------------------------- : Size using doubles
|
||||
|
||||
/// A size (width,height) using real (double) coordinates
|
||||
@@ -166,6 +170,43 @@ public:
|
||||
inline RealRect move(double dx, double dy, double dw, double dh) const {
|
||||
return RealRect(x + dx, y + dy, width + dw, height + dh);
|
||||
}
|
||||
/// Return a rectangle that is the intersection between this and the given rectangle
|
||||
inline RealRect intersect(RealRect rect) const {
|
||||
double left = max(x, rect.x);
|
||||
double top = max(y, rect.y);
|
||||
double right = min(x + width, rect.x + rect.width);
|
||||
double bottom = min(y + height, rect.y + rect.height);
|
||||
return RealRect(left, top, right - left, bottom - top);
|
||||
}
|
||||
|
||||
/// Transformation methods with the same signature for encoded rects
|
||||
inline static void scale(RealRect& rect, int& degrees, double scale_x, double scale_y, int unused = 0) {
|
||||
rect = RealRect(scale_x * rect.x, scale_y * rect.y, scale_x * rect.width, scale_y * rect.height);
|
||||
}
|
||||
inline static void translate(RealRect& rect, int& degrees, double offset_x, double offset_y, int unused = 0) {
|
||||
rect = RealRect(offset_x + rect.x, offset_y + rect.y, rect.width, rect.height);
|
||||
}
|
||||
inline static void flip(RealRect& rect, int& degrees, double width, double height, int horizontal) {
|
||||
if (horizontal) {
|
||||
rect = RealRect(width - rect.x - rect.width, rect.y, rect.width, rect.height);
|
||||
}
|
||||
else {
|
||||
rect = RealRect(rect.x, height - rect.y - rect.height, rect.width, rect.height);
|
||||
}
|
||||
}
|
||||
inline static void rotate(RealRect& rect, int& degrees, double width, double height, int angle) { // only handles right angle rotations
|
||||
degrees += angle;
|
||||
if (degrees >= 360) degrees -= 360;
|
||||
if (angle == 180) {
|
||||
rect = RealRect(width - rect.x - rect.width, height - rect.y - rect.height, rect.width, rect.height);
|
||||
}
|
||||
else if (angle == 90) {
|
||||
rect = RealRect(rect.y, width - rect.x - rect.width, rect.height, rect.width);
|
||||
}
|
||||
else if (angle == 270) {
|
||||
rect = RealRect(height - rect.y - rect.height, rect.x, rect.height, rect.width);
|
||||
}
|
||||
}
|
||||
|
||||
inline operator wxRect() const {
|
||||
// Prevent rounding errors, for example if
|
||||
|
||||
Reference in New Issue
Block a user