From 2888dc4059b4367383e8ec5da08f834f76be1808 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: Mon, 8 Dec 2025 16:11:36 +0100 Subject: [PATCH] add bleed edge option, get_card_export_settings function --- data/en.mse-locale/locale | 35 +- doc/function/get_card_export_settings.txt | 13 + doc/function/index.txt | 1 + doc/function/write_image_file.txt | 13 +- src/data/field.hpp | 28 +- src/data/format/clipboard.cpp | 2 +- src/data/format/formats.hpp | 10 +- src/data/format/image.cpp | 388 +++++++++++------- src/data/settings.cpp | 137 ++++--- src/data/settings.hpp | 32 +- src/gfx/gfx.hpp | 1 + src/gfx/resample_image.cpp | 3 + src/gui/control/card_list.cpp | 9 +- src/gui/image_slice_window.cpp | 20 +- src/gui/image_slice_window.hpp | 6 +- src/gui/preferences_window.cpp | 147 +++---- src/gui/value/image.cpp | 31 +- src/script/functions/basic.cpp | 159 +++---- src/script/functions/export.cpp | 38 +- src/script/functions/image.cpp | 15 +- src/script/functions/json.hpp | 3 +- src/util/angle.hpp | 2 +- src/util/io/package.hpp | 40 +- src/util/rotation.cpp | 2 +- src/util/window_id.hpp | 2 +- .../drupal/mse-drupal-modules/highlight.inc | 1 + 26 files changed, 663 insertions(+), 475 deletions(-) create mode 100644 doc/function/get_card_export_settings.txt diff --git a/data/en.mse-locale/locale b/data/en.mse-locale/locale index 5daae400..926c361b 100644 --- a/data/en.mse-locale/locale +++ b/data/en.mse-locale/locale @@ -574,10 +574,10 @@ label: dark mode yes: Dark mode app language: Language of the user interface: card display: Card Display - storage: Storage zoom: &Zoom: - export: &Export: - scale: &Internal Scale: + import: Import + export: &Export + scale: Use the following scale: use export scale: Use Export Scale export around 300: Around 300 DPI export force 300: Force 300 DPI @@ -586,10 +586,11 @@ label: external programs: External programs apprentice: &Apprentice: apprentice exe: Apprentice Executable - internal scale desc: - Scale to internally store card images at. - Changing this may impact how Sharpening looks. - Does not retroactively apply to existing images. + export desc: When exporting cards to images: + import desc: When importing images for illustrations: + internal scale desc: + Changing the scale does not retroactively apply + to existing images. You must re-import them. check at startup: Check for new versions at startup checking requires internet: Checking for updates requires an internet connection. @@ -741,21 +742,23 @@ button: high quality: &High quality rendering show lines: Show &lines around fields show editing hints: Show boxes and hints for &editing - zoom export: - Use Viewer zoom and rotation - settings when e&xporting rotation export: - Use Viewer rotation - setting when e&xporting + Use the card preview + rotation setting + bleed export: + Add a crude print + bleed margin notes export: - Export card notes inside - image meta data + Add the card notes in + the image metadata spellcheck enabled: Show &spelling errors on cards check now: Check &Now always: Always if internet connection exists: If internet connection exists never: Never - internal image extension: Store images internally with file extension + internal image extension: + Store images internally + with file extension # column select move up: Move &Up @@ -824,7 +827,7 @@ title: global: Global display: Display directories: Directories - internal: Internal + transfers: Transfers updates: Updates update check: Update Check locate apprentice: Locate Apprentice diff --git a/doc/function/get_card_export_settings.txt b/doc/function/get_card_export_settings.txt new file mode 100644 index 00000000..b3a1f700 --- /dev/null +++ b/doc/function/get_card_export_settings.txt @@ -0,0 +1,13 @@ +Function: get_card_export_settings + +--Usage-- +> get_card_export_settings(card) + +Get the zoom, angle and bleed with which a card is to be exported, as specified by the user in the preferences menu. + +Returns and array whose first element is the zoom percentage, the second is the angle in degrees, and the third is the bleed edge size in pixels. + +--Parameters-- +! Parameter Type Description +| @input@ [[type:card]] The card you want to retrieve the export settings from. +| @set@ [[type:set]] The set the card belongs to. This can be omited since "set" is a predefined variable. diff --git a/doc/function/index.txt b/doc/function/index.txt index 44799762..b14b614e 100644 --- a/doc/function/index.txt +++ b/doc/function/index.txt @@ -108,6 +108,7 @@ These functions are built into the program, other [[type:function]]s can be defi | [[fun:add_card_to_set]] Add a [[type:card]] to a [[type:set]]. | [[fun:get_card_styling]] Get the styling data of a [[type:card]]. | [[fun:get_card_stylesheet]] Get the stylesheet of a [[type:card]]. +| [[fun:get_card_export_settings]] Get the zoom, angle and bleed size at which the card will be exported. | [[fun:get_card_from_uid]] Find the [[type:card]] with the given uid. | [[fun:get_cards_from_link]] Find all [[type:card]]s that have the given link type to the given [[type:card]]. | [[fun:get_front_face]] Find a [[type:card]] that has the link type "Front Face" to the given [[type:card]]. diff --git a/doc/function/write_image_file.txt b/doc/function/write_image_file.txt index 19de6c8d..9c394d53 100644 --- a/doc/function/write_image_file.txt +++ b/doc/function/write_image_file.txt @@ -1,9 +1,10 @@ Function: write_image_file --Usage-- -> write_image_file(some_image, file: filename) +> write_image_file(some_image, file: filename, width: 375, height: 523) +> write_image_file(some_card, file: filename, zoom: 1.0, angle: 0.0, bleed: 18.0) -Write an image to a file in the output directory. +Write a given image, or a given card's image to a file in the output directory. If a file with the given name already exists it is overwritten. Returns the name of the file written. @@ -12,10 +13,14 @@ This function can only be used in an [[type:export template]], when create d --Parameters-- ! Parameter Type Description -| @input@ [[type:image]] Image to write to the file. -| @file@ [[type:string]] Name of the file to write to +| @input@ [[type:image]] or [[type:card]] Image or Card to write to the file. +| @file@ [[type:string]] Name of the file to write to. | @width@ [[type:int]] Width in pixels to use for the image, by default the size of the image is used if available. | @height@ [[type:int]] Height in pixels to use for the image, by default the size of the image is used if available. +| @zoom@ [[type:double]] Zoom percentage to apply to the card render. +| @angle@ [[type:double]] Angle in degrees to apply to the card render. +| @bleed@ [[type:double]] Print bleed margin in pixels to apply to the card render. +| @use_user_settings@ [[type:bool]] Use app settings for zoom, angle and bleed instead. --Examples-- > write_image_file(file:"image_out.png", linear_blend(...)) == "image_out.png" # image_out.png now contains the given image diff --git a/src/data/field.hpp b/src/data/field.hpp index fd479b05..652af227 100644 --- a/src/data/field.hpp +++ b/src/data/field.hpp @@ -126,11 +126,29 @@ 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 String getExternalRectString(double scale = 1.0, int offset = 0) { ///< update the style before calling this - return _("---") + wxString::Format(wxT("%i"), (int)std::ceil(scale * left + offset)) + - _("-") + wxString::Format(wxT("%i"), (int)std::ceil(scale * top)) + - _("-") + wxString::Format(wxT("%i"), (int)std::floor(scale * width)) + - _("-") + wxString::Format(wxT("%i"), (int)std::floor(scale * 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 + double x = left * scale, y = top * scale; + double w = width * scale, h = height * scale; + RealRect rect(x, y, w, h); + int degrees = 0; + if (is_rad0(angle)) { + } else if (is_rad180(angle)) { + rect = RealRect(img_width - x - w, img_height - y - h, w, h); + degrees = 180; + } else if (is_rad90(angle)) { + rect = RealRect(y, img_height - x - w, h, w); + degrees = 90; + } else if (is_rad270(angle)) { + rect = RealRect(img_width - y - h, x, h, w); + degrees = 270; + } else { + return _(""); + } + return _("---") + wxString::Format(wxT("%i"), (int)std::ceil( rect.x + bleed + img_offset)) + + _("-") + wxString::Format(wxT("%i"), (int)std::ceil( rect.y + bleed)) + + _("-") + wxString::Format(wxT("%i"), (int)std::floor(rect.width)) + + _("-") + wxString::Format(wxT("%i"), (int)std::floor(rect.height)) + + _("-") + wxString::Format(wxT("%i"), degrees) + _("---"); } diff --git a/src/data/format/clipboard.cpp b/src/data/format/clipboard.cpp index 6b11840c..454c3925 100644 --- a/src/data/format/clipboard.cpp +++ b/src/data/format/clipboard.cpp @@ -151,7 +151,7 @@ CardsOnClipboard::CardsOnClipboard(const SetP& set, const String id, const vecto img = export_image(set, cards[0]); } else { - img = export_image(set, cards, true, 0, 1.0, 0.0); + img = export_image(set, cards); } String temp_path = wxFileName::CreateTempFileName(_("mse")) + _(".png"); img.SaveFile(temp_path, wxBITMAP_TYPE_PNG); diff --git a/src/data/format/formats.hpp b/src/data/format/formats.hpp index 54c7f5ec..60dca1dd 100644 --- a/src/data/format/formats.hpp +++ b/src/data/format/formats.hpp @@ -88,15 +88,11 @@ FileFormatP mtg_editor_file_format(); // ----------------------------------------------------------------------------- : Other ways to export -/// Generate a wxBitmap of one or more cards -Bitmap export_bitmap(const SetP& set, const CardP& card, const double zoom = 1.0, const Radians angle_radians = 0.0); -Bitmap export_bitmap(const SetP& set, const vector& cards, bool scale_to_lowest_dpi, int padding, const double zoom, const Radians angle_radians, vector& scales_out, vector& offsets_out); - /// Generate a wxImage of one or more cards -Image export_image(const SetP& set, const CardP& card, const double zoom = 1.0, const Radians angle_radians = 0.0); -Image export_image(const SetP& set, const vector& cards, bool scale_to_lowest_dpi = false, int padding = 0, const double zoom = 1.0, const Radians angle_radians = 0.0); +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& 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); -/// Export the image of one or more cards to a given filename, using the app's zoom and rotation settings +/// 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& cards, const String& path, const String& filename_template, FilenameConflicts conflicts); diff --git a/src/data/format/image.cpp b/src/data/format/image.cpp index 0cdaa7f8..696956e9 100644 --- a/src/data/format/image.cpp +++ b/src/data/format/image.cpp @@ -20,157 +20,160 @@ #include #include -// ----------------------------------------------------------------------------- : Card export - -class UnzoomedDataViewer : public DataViewer { +class ZoomedUnrotatedDataViewer : public DataViewer { public: - UnzoomedDataViewer(); - UnzoomedDataViewer(double zoom, Radians angle); - virtual ~UnzoomedDataViewer() {}; - Rotation getRotation() const override; -private: + ZoomedUnrotatedDataViewer(double zoom) : zoom(zoom) {}; + virtual ~ZoomedUnrotatedDataViewer() {}; + Rotation getRotation() const override; +private: double zoom; - double angle; - bool declared_values; }; -UnzoomedDataViewer::UnzoomedDataViewer() - : zoom(1.0) - , angle(0.0) - , declared_values(false) -{} - -UnzoomedDataViewer::UnzoomedDataViewer(const double zoom, const Radians angle = 0.0) - : zoom(zoom) - , angle(angle) - , declared_values(true) -{} - -Rotation UnzoomedDataViewer::getRotation() const { - if (!stylesheet) stylesheet = set->stylesheet; - if (declared_values) { - return Rotation(angle, stylesheet->getCardRect(), zoom, 1.0, ROTATION_ATTACH_TOP_LEFT); - } - - double export_zoom = settings.exportZoomSettingsFor(set->stylesheetFor(card)); - bool use_viewer_rotation = !settings.stylesheetSettingsFor(set->stylesheetFor(card)).card_normal_export(); - - if (use_viewer_rotation) { - return Rotation(DataViewer::getRotation().getAngle(), stylesheet->getCardRect(), export_zoom, 1.0, ROTATION_ATTACH_TOP_LEFT); - } else { - return Rotation(angle, stylesheet->getCardRect(), export_zoom, 1.0, ROTATION_ATTACH_TOP_LEFT); - } -} - -// ----------------------------------------------------------------------------- : wxBitmap export - -Bitmap export_bitmap(const SetP& set, const CardP& card, const double zoom, const Radians angle_radians) { - if (!set) throw Error(_("no set")); - UnzoomedDataViewer viewer = UnzoomedDataViewer(zoom, angle_radians); - viewer.setSet(set); - viewer.setCard(card); - // size of cards - RealSize size = viewer.getRotation().getExternalSize(); - // create bitmap & dc - Bitmap bitmap((int)size.width, (int)size.height); - if (!bitmap.Ok()) throw InternalError(_("Unable to create bitmap")); - wxMemoryDC dc; - dc.SelectObject(bitmap); - // draw - viewer.draw(dc); - dc.SelectObject(wxNullBitmap); - return bitmap; -} - -Bitmap export_bitmap(const SetP& set, const vector& cards, bool scale_to_lowest_dpi, int padding, const double zoom, const Radians angle_radians, vector& scales_out, vector& offsets_out) { - if (!set) throw Error(_("no set")); - vector bitmaps; - int width = 0; - int height = 0; - double lowest_dpi = 1200.0; - if (scale_to_lowest_dpi) { - FOR_EACH(card, cards) { - lowest_dpi = min(lowest_dpi, set->stylesheetFor(card).card_dpi); - } - lowest_dpi = max(lowest_dpi, 150.0); - } - // Draw card bitmaps - FOR_EACH(card, cards) { - double scale = zoom; - if (scale_to_lowest_dpi) { - double dpi = max(set->stylesheetFor(card).card_dpi, 150.0); - scale *= lowest_dpi / dpi; - } - scales_out.push_back(scale); - UnzoomedDataViewer viewer = UnzoomedDataViewer(scale, angle_radians); - viewer.setSet(set); - viewer.setCard(card); - RealSize size = viewer.getRotation().getExternalSize(); - Bitmap bitmap((int)size.width, (int)size.height); - if (!bitmap.Ok()) throw InternalError(_("Unable to create bitmap")); - wxMemoryDC bufferDC; - bufferDC.SelectObject(bitmap); - clearDC(bufferDC, *wxWHITE_BRUSH); - viewer.draw(bufferDC); - bufferDC.SelectObject(wxNullBitmap); - width += (int)size.width; - height = max(height, (int)size.height); - bitmaps.push_back(bitmap); - } - // Draw global bitmap - Bitmap global_bitmap(width + (bitmaps.size()-1) * padding, height); - if (!global_bitmap.Ok()) throw InternalError(_("Unable to create bitmap")); - wxMemoryDC globalDC; - globalDC.SelectObject(global_bitmap); - clearDC(globalDC, *wxWHITE_BRUSH); - int offset = 0; - FOR_EACH(bitmap, bitmaps) { - offsets_out.push_back(offset); - globalDC.SetDeviceOrigin(offset, 0); - globalDC.DrawBitmap(bitmap, 0, 0); - offset += bitmap.GetWidth() + padding; - } - globalDC.SelectObject(wxNullBitmap); - return global_bitmap; +Rotation ZoomedUnrotatedDataViewer::getRotation() const { + return Rotation(0.0, stylesheet->getCardRect(), zoom); } // ----------------------------------------------------------------------------- : wxImage export -Image export_image(const SetP& set, const CardP& card, const double zoom, const Radians angle_radians) { - Bitmap bitmap = export_bitmap(set, card, zoom, angle_radians); - Image img = bitmap.ConvertToImage(); - String data = _("["); - IndexMap& 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::iterator it = card_data.begin() ; it != card_data.end() ; ++it) { - ImageValue* value = dynamic_cast(it->get()); - if (value && !value->filename.empty()) { - FieldP field = (*it)->fieldP; - StyleP style = stylesheet->card_style.at(field->index); - if (style) { - style->update(set->getContext(card)); - std::string rect = style->getExternalRectString(zoom, 0).ToStdString(); - cardv_data[field->name.ToStdString()] = rect; +Image export_image(const SetP& set, const CardP& card, const bool write_metadata, const double zoom, const Radians angle_radians, const double bleed_pixels) { + if (!set) throw Error(_("no set")); + /// create and zoom + ZoomedUnrotatedDataViewer viewer = ZoomedUnrotatedDataViewer(zoom); + viewer.setSet(set); + viewer.setCard(card); + RealSize size = viewer.getRotation().getExternalSize(); + int bleed = lround(bleed_pixels); + Bitmap bitmap((int)size.width + 2 * bleed, (int)size.height + 2 * bleed); + if (!bitmap.Ok()) throw InternalError(_("Unable to create bitmap")); + wxMemoryDC dc; + dc.SelectObject(bitmap); + dc.SetDeviceOrigin(bleed, bleed); + viewer.draw(dc); + dc.SelectObject(wxNullBitmap); + Image img = bitmap.ConvertToImage(); + + /// rotate + img = rotate_image(img, angle_radians); + int width = img.GetWidth(), height = img.GetHeight(); + + /// add print bleed edge + if (size.width < 1.0 || size.height < 1.0) { + queue_message(MESSAGE_ERROR, _("Cannot add bleed edge to empty image")); + } + else { + if (!img.HasAlpha()) img.InitAlpha(); + Byte* pixels = img.GetData(); + Byte* alpha = img.GetAlpha(); + // fill top left corner + int pixel; + int x_start = 0; + int y_start = 0; + int ref = bleed + bleed * width; + for (int y = 0; y < bleed; ++y) { + for (int x = 0; x < bleed; ++x) { + pixel = x_start + x + (y_start + y) * width; + pixels[3 * pixel + 0] = pixels[3 * ref + 0]; + pixels[3 * pixel + 1] = pixels[3 * ref + 1]; + pixels[3 * pixel + 2] = pixels[3 * ref + 2]; + alpha[pixel] = alpha[ref]; } } - } - data += json_ugly_print(cardv) + _("]"); - img.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, data); - return img; -} - -Image export_image(const SetP& set, const vector& cards, bool scale_to_lowest_dpi, int padding, const double zoom, const Radians angle_radians) { - vector scales; - vector offsets; - Bitmap bitmap = export_bitmap(set, cards, scale_to_lowest_dpi, padding, zoom, angle_radians, scales, offsets); - Image img = bitmap.ConvertToImage(); - String data = _("["); - for (int i = 0; i < cards.size(); ++i) { - if (i > 0) data += _(","); - CardP card = cards[i]; + // fill top right corner + x_start = width - bleed; + y_start = 0; + ref = x_start - 1 + bleed * width; + for (int y = 0; y < bleed; ++y) { + for (int x = 0; x < bleed; ++x) { + pixel = x_start + x + (y_start + y) * width; + pixels[3 * pixel + 0] = pixels[3 * ref + 0]; + pixels[3 * pixel + 1] = pixels[3 * ref + 1]; + pixels[3 * pixel + 2] = pixels[3 * ref + 2]; + alpha[pixel] = alpha[ref]; + } + } + // fill bottom left corner + x_start = 0; + y_start = height - bleed; + ref = bleed + (y_start - 1) * width; + for (int y = 0; y < bleed; ++y) { + for (int x = 0; x < bleed; ++x) { + pixel = x_start + x + (y_start + y) * width; + pixels[3 * pixel + 0] = pixels[3 * ref + 0]; + pixels[3 * pixel + 1] = pixels[3 * ref + 1]; + pixels[3 * pixel + 2] = pixels[3 * ref + 2]; + alpha[pixel] = alpha[ref]; + } + } + // fill bottom right corner + x_start = width - bleed; + y_start = height - bleed; + ref = (x_start - 1) + (y_start - 1) * width; + for (int y = 0; y < bleed; ++y) { + for (int x = 0; x < bleed; ++x) { + pixel = x_start + x + (y_start + y) * width; + pixels[3 * pixel + 0] = pixels[3 * ref + 0]; + pixels[3 * pixel + 1] = pixels[3 * ref + 1]; + pixels[3 * pixel + 2] = pixels[3 * ref + 2]; + alpha[pixel] = alpha[ref]; + } + } + // fill left border + x_start = 0; + y_start = bleed; + for (int y = 0; y < height - bleed - bleed; ++y) { + ref = bleed + (y_start + y) * width; + for (int x = 0; x < bleed; ++x) { + pixel = x_start + x + (y_start + y) * width; + pixels[3 * pixel + 0] = pixels[3 * ref + 0]; + pixels[3 * pixel + 1] = pixels[3 * ref + 1]; + pixels[3 * pixel + 2] = pixels[3 * ref + 2]; + alpha[pixel] = alpha[ref]; + } + } + // fill top border + x_start = bleed; + y_start = 0; + for (int y = 0; y < bleed; ++y) { + for (int x = 0; x < width - bleed - bleed; ++x) { + pixel = x_start + x + (y_start + y) * width; + ref = x_start + x + bleed * width; + pixels[3 * pixel + 0] = pixels[3 * ref + 0]; + pixels[3 * pixel + 1] = pixels[3 * ref + 1]; + pixels[3 * pixel + 2] = pixels[3 * ref + 2]; + alpha[pixel] = alpha[ref]; + } + } + // fill right border + x_start = width - bleed; + y_start = bleed; + for (int y = 0; y < height - bleed - bleed; ++y) { + ref = width - bleed - 1 + (y_start + y) * width; + for (int x = 0; x < bleed; ++x) { + pixel = x_start + x + (y_start + y) * width; + pixels[3 * pixel + 0] = pixels[3 * ref + 0]; + pixels[3 * pixel + 1] = pixels[3 * ref + 1]; + pixels[3 * pixel + 2] = pixels[3 * ref + 2]; + alpha[pixel] = alpha[ref]; + } + } + // fill bottom border + x_start = bleed; + y_start = height - bleed; + for (int y = 0; y < bleed; ++y) { + for (int x = 0; x < width - bleed - bleed; ++x) { + pixel = x_start + x + (y_start + y) * width; + ref = x_start + x + (height - bleed - 1) * width; + pixels[3 * pixel + 0] = pixels[3 * ref + 0]; + pixels[3 * pixel + 1] = pixels[3 * ref + 1]; + pixels[3 * pixel + 2] = pixels[3 * ref + 2]; + alpha[pixel] = alpha[ref]; + } + } + } + + /// add metadata + if (write_metadata) { + String metadata = _("["); IndexMap& card_data = card->data; boost::json::object& cardv = mse_to_json(card, set.get()); boost::json::object& cardv_data = cardv["data"].as_object(); @@ -183,24 +186,109 @@ Image export_image(const SetP& set, const vector& cards, bool scale_to_lo StyleP style = stylesheet->card_style.at(field->index); if (style) { style->update(set->getContext(card)); - std::string rect = style->getExternalRectString(scales[i], offsets[i]).ToStdString(); + std::string rect = style->getExternalRectString(zoom, angle_radians, bleed_pixels, width, height, 0).ToStdString(); cardv_data[field->name.ToStdString()] = rect; } } } - data += json_ugly_print(cardv); - } - data += _("]"); - img.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, data); + metadata += json_ugly_print(cardv) + _("]"); + img.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, metadata); + } + return img; } +Image export_image( const SetP& set, const vector& cards, + const int padding, + const double global_zoom, + const bool use_zoom_setting, + const bool use_rotation_setting, + const bool use_bleed_setting) { + if (!set) throw Error(_("no set")); + if (cards.size() == 0) throw Error(_("no cards")); + vector imgs; + vector offsets; + vector zooms; + vector angles; + vector bleeds; + // Draw card images + FOR_EACH(card, cards) { + Settings::ExportSettings card_settings = settings.exportSettingsFor(set->stylesheetFor(card)); + double zoom = use_zoom_setting ? global_zoom * card_settings.zoom : global_zoom; + double angle = use_rotation_setting ? card_settings.angle_radians : 0.0; + double bleed = use_bleed_setting ? card_settings.bleed_pixels : 0.0; + imgs.push_back(export_image(set, card, false, zoom, angle, bleed)); + zooms.push_back(zoom); + angles.push_back(angle); + bleeds.push_back(bleed); + } + int global_width = 0; + int global_height = 0; + vector widths; + vector heights; + FOR_EACH(img, imgs) { + int width = img.GetWidth(); + int height = img.GetHeight(); + widths.push_back(width); + heights.push_back(height); + offsets.push_back(global_width); + global_width += padding + width; + global_height = max(global_height, height); + } + global_width -= padding; + // Draw global image + Image global_img = Image(global_width, global_height); + if (!global_img.Ok()) throw InternalError(_("Unable to create image")); + global_img.InitAlpha(); + Byte* pixels = global_img.GetData(); + Byte* alpha = global_img.GetAlpha(); + // fill with transparent + for (UInt i = 0; i < global_width*global_height; ++i) { + pixels[3 * i + 0] = 0; + pixels[3 * i + 1] = 0; + pixels[3 * i + 2] = 0; + alpha[i] = 0; + } + // Paste card images + FOR_EACH_2(img, imgs, offset, offsets) { + global_img.Paste(img, offset, 0); + } + // Write metadata + String metadata = _("["); + for (int i = 0; i < cards.size(); ++i) { + if (i > 0) metadata += _(","); + CardP card = cards[i]; + IndexMap& 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::iterator it = card_data.begin() ; it != card_data.end() ; ++it) { + ImageValue* value = dynamic_cast(it->get()); + if (value && !value->filename.empty()) { + FieldP field = (*it)->fieldP; + StyleP style = stylesheet->card_style.at(field->index); + if (style) { + style->update(set->getContext(card)); + Rotation rotation(); + std::string rect = style->getExternalRectString(zooms[i], angles[i], bleeds[i], widths[i], heights[i], offsets[i]).ToStdString(); + cardv_data[field->name.ToStdString()] = rect; + } + } + } + metadata += json_ugly_print(cardv); + } + metadata += _("]"); + global_img.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, metadata); + + return global_img; +} + void export_image(const SetP& set, const CardP& card, const String& filename) { const StyleSheet& stylesheet = set->stylesheetFor(card); - StyleSheetSettings& stylesheet_settings = settings.stylesheetSettingsFor(stylesheet); - double zoom = settings.exportZoomSettingsFor(stylesheet); - Radians angle = stylesheet_settings.card_normal_export() ? 0.0 : deg_to_rad(stylesheet_settings.card_angle()); - Image img = export_image(set, card, zoom, angle); + StyleSheetSettings& stylesheet_settings = settings.stylesheetSettingsFor(stylesheet); + Settings::ExportSettings export_settings = settings.exportSettingsFor(stylesheet); + Image img = export_image(set, card, true, export_settings.zoom, export_settings.angle_radians, export_settings.bleed_pixels); img.SaveFile(filename); } diff --git a/src/data/settings.cpp b/src/data/settings.cpp index 54cb9c63..87825325 100644 --- a/src/data/settings.cpp +++ b/src/data/settings.cpp @@ -32,8 +32,8 @@ IMPLEMENT_REFLECTION_ENUM(CheckUpdates) { } IMPLEMENT_REFLECTION_ENUM(InstallType) { - VALUE_N("default", INSTALL_DEFAULT); //default - VALUE_N("local", INSTALL_LOCAL); + VALUE_N("default", INSTALL_DEFAULT); //default + VALUE_N("local", INSTALL_LOCAL); VALUE_N("global", INSTALL_GLOBAL); } @@ -47,13 +47,13 @@ bool is_install_local(InstallType type) { } IMPLEMENT_REFLECTION_ENUM(FilenameConflicts) { - VALUE_N("keep old", CONFLICT_KEEP_OLD); - VALUE_N("overwrite", CONFLICT_OVERWRITE); - VALUE_N("number", CONFLICT_NUMBER); - VALUE_N("number overwrite", CONFLICT_NUMBER_OVERWRITE); + VALUE_N("keep old", CONFLICT_KEEP_OLD); + VALUE_N("overwrite", CONFLICT_OVERWRITE); + VALUE_N("number", CONFLICT_NUMBER); + VALUE_N("number overwrite", CONFLICT_NUMBER_OVERWRITE); } -const vector Settings::export_zoom_choices = { 50,66,75,80,100,120,125,150,175,200 }; +const vector Settings::scale_choices = { 50,66,75,80,100,120,125,150,175,200 }; const int COLUMN_NOT_INITIALIZED = -100000; @@ -71,13 +71,13 @@ IMPLEMENT_REFLECTION_NO_SCRIPT(ColumnSettings) { } GameSettings::GameSettings() - : sort_cards_ascending(true) - , images_export_filename(_("{card.name}.png")) - , images_export_conflicts(CONFLICT_NUMBER_OVERWRITE) - , use_auto_replace(true) - , pack_seed_random(true) - , pack_seed(123456) - , initialized(false) + : sort_cards_ascending (true) + , images_export_filename (_("{card.name}.png")) + , images_export_conflicts (CONFLICT_NUMBER_OVERWRITE) + , use_auto_replace (true) + , pack_seed_random (true) + , pack_seed (123456) + , initialized (false) {} void GameSettings::initDefaults(const Game& game) { @@ -128,37 +128,40 @@ IMPLEMENT_REFLECTION_NO_SCRIPT(GameSettings) { StyleSheetSettings::StyleSheetSettings() - : card_zoom (1.0, true) - , export_zoom_selection (0, true) - , card_angle (0, true) - , card_anti_alias (true, true) - , card_borders (true, true) - , card_draw_editing (true, true) - , card_normal_export (true, true) - , card_notes_export (false, true) - , card_spellcheck_enabled(true, true) + : card_zoom (1.0, true) + , export_scale_selection (0, true) + , card_angle (0, true) + , card_anti_alias (true, true) + , card_borders (true, true) + , card_draw_editing (true, true) + , card_normal_export (true, true) + , card_bleed_export (false, true) + , card_notes_export (false, true) + , card_spellcheck_enabled (true, true) {} void StyleSheetSettings::useDefault(const StyleSheetSettings& ss) { if (card_zoom .isDefault()) card_zoom .assignDefault(ss.card_zoom); - if (export_zoom_selection .isDefault()) export_zoom_selection .assignDefault(ss.export_zoom_selection); + if (export_scale_selection .isDefault()) export_scale_selection .assignDefault(ss.export_scale_selection); if (card_angle .isDefault()) card_angle .assignDefault(ss.card_angle); if (card_anti_alias .isDefault()) card_anti_alias .assignDefault(ss.card_anti_alias); if (card_borders .isDefault()) card_borders .assignDefault(ss.card_borders); if (card_draw_editing .isDefault()) card_draw_editing .assignDefault(ss.card_draw_editing); if (card_normal_export .isDefault()) card_normal_export .assignDefault(ss.card_normal_export); + if (card_bleed_export .isDefault()) card_bleed_export .assignDefault(ss.card_bleed_export); if (card_notes_export .isDefault()) card_notes_export .assignDefault(ss.card_notes_export); if (card_spellcheck_enabled.isDefault()) card_spellcheck_enabled.assignDefault(ss.card_spellcheck_enabled); } IMPLEMENT_REFLECTION_NO_SCRIPT(StyleSheetSettings) { REFLECT(card_zoom); - REFLECT(export_zoom_selection); + REFLECT(export_scale_selection); REFLECT(card_angle); REFLECT(card_anti_alias); REFLECT(card_borders); REFLECT(card_draw_editing); REFLECT(card_normal_export); + REFLECT(card_bleed_export); REFLECT(card_notes_export); REFLECT(card_spellcheck_enabled); } @@ -184,29 +187,29 @@ IMPLEMENT_REFLECTION_ENUM(DarkModeType) { Settings settings; Settings::Settings() - : locale (_("en")) - , set_window_maximized (false) - , set_window_width (790) - , set_window_height (300) - , card_notes_height (40) - , open_sets_in_new_window (true) - , symbol_grid_size (30) - , symbol_grid (true) - , symbol_grid_snap (false) - , print_spacing (0.33) - , print_cutter_lines (CUTTER_ALL) - , dark_mode_type (DARKMODE_SYSTEM) - , internal_scale_selection(0) - , internal_image_extension(true) + : locale (_("en")) + , set_window_maximized (false) + , set_window_width (790) + , set_window_height (300) + , card_notes_height (40) + , open_sets_in_new_window (true) + , symbol_grid_size (30) + , symbol_grid (true) + , symbol_grid_snap (false) + , print_spacing (0.33) + , print_cutter_lines (CUTTER_ALL) + , dark_mode_type (DARKMODE_SYSTEM) + , import_scale_selection (0) + , internal_image_extension (true) #if USE_OLD_STYLE_UPDATE_CHECKER - , updates_url (_("https://magicseteditor.boards.net/page/downloads")) + , updates_url (_("https://magicseteditor.boards.net/page/downloads")) #endif - , package_versions_url (_("https://magicseteditor.boards.net/page/downloads")) - , installer_list_url (_("https://magicseteditor.boards.net/page/downloads")) - , check_updates (CHECK_IF_CONNECTED) - , check_updates_all (true) - , website_url (_("https://magicseteditor.boards.net/")) - , install_type (INSTALL_DEFAULT) + , package_versions_url (_("https://magicseteditor.boards.net/page/downloads")) + , installer_list_url (_("https://magicseteditor.boards.net/page/downloads")) + , check_updates (CHECK_IF_CONNECTED) + , check_updates_all (true) + , website_url (_("https://magicseteditor.boards.net/")) + , install_type (INSTALL_DEFAULT) {} void Settings::addRecentFile(const String& filename) { @@ -230,7 +233,8 @@ GameSettings& Settings::gameSettingsFor(const Game& game) { if (!gs) gs = make_intrusive(); gs->initDefaults(game); return *gs; -} +} + ColumnSettings& Settings::columnSettingsFor(const Game& game, const Field& field) { // Get game info GameSettings& gs = gameSettingsFor(game); @@ -243,7 +247,8 @@ ColumnSettings& Settings::columnSettingsFor(const Game& game, const Field& field cs.width = field.card_list_width; } return cs; -} +} + StyleSheetSettings& Settings::stylesheetSettingsFor(const StyleSheet& stylesheet) { // Use the canonical form here since the stylesheet name will be used as a stored key. // This does introduce the possibility of collision if two stylesheets return the same value canonically, but I think that's just a necessary risk. @@ -253,28 +258,36 @@ StyleSheetSettings& Settings::stylesheetSettingsFor(const StyleSheet& stylesheet return *ss; } -double Settings::exportZoomSettingsFor(const StyleSheet& stylesheet) { +double Settings::exportScaleSettingsFor(const StyleSheet& stylesheet) { StyleSheetSettings& ss = stylesheetSettingsFor(stylesheet); - int export_zoom = ss.export_zoom_selection(); - if (export_zoom == 0) return adaptiveZoomSettingsFor(stylesheet, 300.0, 50.0); - if (export_zoom == 1) return adaptiveZoomSettingsFor(stylesheet, 300.0, 1.0); - if (export_zoom == 2) return adaptiveZoomSettingsFor(stylesheet, 150.0, 1.0); - return export_zoom_choices[export_zoom - 3] / 100; + int export_scale = ss.export_scale_selection(); + 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 == 2) return adaptiveScaleSettingsFor(stylesheet, 150.0, 1.0); + return scale_choices[export_scale - 3] / 100; } -double Settings::internalScaleSettingsFor(const StyleSheet& stylesheet) { - if (internal_scale_selection == 0) return exportZoomSettingsFor(stylesheet); - if (internal_scale_selection == 1) return adaptiveZoomSettingsFor(stylesheet, 300.0, 50.0); - if (internal_scale_selection == 2) return adaptiveZoomSettingsFor(stylesheet, 300.0, 1.0); - if (internal_scale_selection == 3) return adaptiveZoomSettingsFor(stylesheet, 150.0, 1.0); - return export_zoom_choices[internal_scale_selection - 4] / 100; +double Settings::importScaleSettingsFor(const StyleSheet& stylesheet) { + if (import_scale_selection == 0) return exportScaleSettingsFor(stylesheet); + 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 == 3) return adaptiveScaleSettingsFor(stylesheet, 150.0, 1.0); + return scale_choices[import_scale_selection - 4] / 100; } -double Settings::adaptiveZoomSettingsFor(const StyleSheet& stylesheet, double dpi_target, double dpi_leeway) { +double Settings::adaptiveScaleSettingsFor(const StyleSheet& stylesheet, double dpi_target, double dpi_leeway) { if (abs(stylesheet.card_dpi - dpi_target) <= dpi_leeway) return 1.0; return dpi_target / max(10.0, stylesheet.card_dpi); } +Settings::ExportSettings Settings::exportSettingsFor(const StyleSheet& stylesheet) { + StyleSheetSettings& ss = stylesheetSettingsFor(stylesheet); + double zoom = settings.exportScaleSettingsFor(stylesheet); + double angle = ss.card_normal_export() ? 0.0 : deg_to_rad(ss.card_angle()); + double bleed = ss.card_bleed_export() ? (stylesheet.card_dpi / 300.0) * 36.0 * zoom : 0.0; // 36 pixels of bleed on a 300 DPI print + return ExportSettings{zoom, angle, bleed}; +} + IndexMap& Settings::exportOptionsFor(const ExportTemplate& export_template) { return export_options.get(export_template.name(), export_template.option_fields); } @@ -324,7 +337,7 @@ IMPLEMENT_REFLECTION_NO_SCRIPT(Settings) { REFLECT(print_cutter_lines); REFLECT(dark_mode_type); REFLECT(apprentice_location); - REFLECT(internal_scale_selection); + REFLECT(import_scale_selection); REFLECT(internal_image_extension); #if USE_OLD_STYLE_UPDATE_CHECKER REFLECT(updates_url); diff --git a/src/data/settings.hpp b/src/data/settings.hpp index fd2076a0..53a9fc88 100644 --- a/src/data/settings.hpp +++ b/src/data/settings.hpp @@ -98,12 +98,13 @@ public: // Rendering/display settings Defaultable card_zoom; - Defaultable export_zoom_selection; + Defaultable export_scale_selection; Defaultable card_angle; Defaultable card_anti_alias; Defaultable card_borders; Defaultable card_draw_editing; Defaultable card_normal_export; + Defaultable card_bleed_export; Defaultable card_notes_export; Defaultable card_spellcheck_enabled; @@ -173,18 +174,23 @@ public: String default_game; // --------------------------------------------------- : Game/stylesheet specific - - /// Get the settings object for a specific game - GameSettings& gameSettingsFor (const Game& game); - /// Get the settings for a column for a specific field in a game - ColumnSettings& columnSettingsFor (const Game& game, const Field& field); - /// Get the settings object for a specific stylesheet - StyleSheetSettings& stylesheetSettingsFor (const StyleSheet& stylesheet); - double exportZoomSettingsFor (const StyleSheet& stylesheet); - double internalScaleSettingsFor(const StyleSheet& stylesheet); - double adaptiveZoomSettingsFor (const StyleSheet& stylesheet, double target_dpi, double leeway_dpi); + + struct ExportSettings { + double zoom, angle_radians, bleed_pixels; + }; - static const vector export_zoom_choices; + /// Get the settings object for a specific game + GameSettings& gameSettingsFor (const Game& game); + /// Get the settings for a column for a specific field in a game + ColumnSettings& columnSettingsFor (const Game& game, const Field& field); + /// Get the settings object for a specific stylesheet + StyleSheetSettings& stylesheetSettingsFor (const StyleSheet& stylesheet); + double exportScaleSettingsFor (const StyleSheet& stylesheet); + double importScaleSettingsFor (const StyleSheet& stylesheet); + double adaptiveScaleSettingsFor (const StyleSheet& stylesheet, double target_dpi, double leeway_dpi); + ExportSettings exportSettingsFor (const StyleSheet& stylesheet); + + static const vector scale_choices; private: map game_settings; @@ -221,7 +227,7 @@ public: // --------------------------------------------------- : Internal settings - int internal_scale_selection; + int import_scale_selection; bool internal_image_extension; // --------------------------------------------------- : Update checking diff --git a/src/gfx/gfx.hpp b/src/gfx/gfx.hpp index 037c2a66..a5b6430a 100644 --- a/src/gfx/gfx.hpp +++ b/src/gfx/gfx.hpp @@ -23,6 +23,7 @@ /// Resample (resize) an image, uses bilenear filtering void resample(const Image& img_in, Image& img_out); Image resample(const Image& img_in, int width, int height); +Image resample(const Image& img_in, double zoom); /// Resamples an image, first clips the input image to a specified rectangle /** The selected rectangle is resampled into the entire output image */ diff --git a/src/gfx/resample_image.cpp b/src/gfx/resample_image.cpp index 3fb34452..ca6bea59 100644 --- a/src/gfx/resample_image.cpp +++ b/src/gfx/resample_image.cpp @@ -134,6 +134,9 @@ Image resample(const Image& img_in, int width, int height) { return img_out; } } +Image resample(const Image& img_in, double zoom) { + return resample(img_in, (int)(img_in.GetWidth() * zoom), (int)(img_in.GetHeight() * zoom)); +} void resample_and_clip(const Image& img_in, Image& img_out, wxRect rect) { // mask to alpha diff --git a/src/gui/control/card_list.cpp b/src/gui/control/card_list.cpp index 8ae5c586..20d13d19 100644 --- a/src/gui/control/card_list.cpp +++ b/src/gui/control/card_list.cpp @@ -303,9 +303,12 @@ bool CardListBase::parseImage(Image& image, vector& out) { for (IndexMap::iterator it = card->data.begin(); it != card->data.end(); it++) { ImageValue* value = dynamic_cast(it->get()); if (value && !value->filename.empty()) { - wxRect rect = value->filename.getExternalRect(); + wxRect rect = wxRect(0,0,0,0); + int degrees = 0; + value->filename.getExternalRect(rect, degrees); 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((*it)->fieldP->name, settings.internal_image_extension ? _(".png") : _("")); // a new unique name in the package img.SaveFile(set->nameOut(filename), wxBITMAP_TYPE_PNG); value->filename = filename; @@ -319,8 +322,8 @@ bool CardListBase::parseImage(Image& image, vector& out) { bool CardListBase::parseText(String& text, vector& out) { size_t j = out.size(); - if (size_t pos = text.find("") != wxString::npos) { - text = text.substr(pos + 15, text.find("") - pos - 15); + if (size_t pos = text.find("") != wxString::npos) { + text = text.substr(pos + 14, text.find("") - pos - 14); } try { ScriptValueP& sv = json_to_mse(text, set.get()); diff --git a/src/gui/image_slice_window.cpp b/src/gui/image_slice_window.cpp index 6455ac16..1a15349c 100644 --- a/src/gui/image_slice_window.cpp +++ b/src/gui/image_slice_window.cpp @@ -71,13 +71,12 @@ void ImageSlice::centerSelectionVertically() { } } -Image ImageSlice::getSlice(double scale) const { - wxSize scaled_target_size = target_size * scale; - if (selection.width == scaled_target_size.GetWidth() && selection.height == scaled_target_size.GetHeight() && selection.x == 0 && selection.y == 0) { +Image ImageSlice::getSlice() const { + if (selection.width == target_size.GetWidth() && selection.height == target_size.GetHeight() && selection.x == 0 && selection.y == 0) { // exactly the right size return source.GetSubImage(selection); } - Image target(scaled_target_size.GetWidth(), scaled_target_size.GetHeight(), false); + Image target(target_size.GetWidth(), target_size.GetHeight(), false); if (sharpen && sharpen_amount > 0 && sharpen_amount <= 100) { sharp_resample_and_clip(source, target, selection, sharpen_amount); } else { @@ -104,7 +103,6 @@ ImageSliceWindow::ImageSliceWindow(Window* parent, const Image& source, const St // init slice pair settings_entry = { filename, cardname }; if (previously_used_settings_value.find(settings_entry) != previously_used_settings_value.end()) { - //slice.allow_outside = true; this currrently crashes slice.aspect_fixed = false; slice.sharpen = true; slice.sharpen_amount = previously_used_settings_value[settings_entry].second; @@ -242,8 +240,8 @@ void ImageSliceWindow::onOk(wxCommandEvent&) { EndModal(wxID_OK); } -Image ImageSliceWindow::getImage(double scale) const { - Image img = slice.getSlice(scale); +Image ImageSliceWindow::getImage() const { + Image img = slice.getSlice(); previously_used_settings_path[slice.card_name] = slice.source_path; previously_used_settings_value[{ slice.source_path, slice.card_name }] = { slice.selection, slice.sharpen_amount }; return img; @@ -484,11 +482,12 @@ void ImageSlicePreview::draw(DC& dc) { assert(image.GetWidth() == slice.target_size.GetWidth() && image.GetHeight() == slice.target_size.GetHeight()); mask.setAlpha(image); if (image.HasAlpha()) { - // create bitmap - bitmap = Bitmap(image.GetWidth(), image.GetHeight()); + // create bitmap + int width = image.GetWidth(), height = image.GetHeight(); + bitmap = Bitmap(width, height); wxMemoryDC mdc; mdc.SelectObject(bitmap); // draw checker pattern behind image - RealRect rect = (RealRect) GetClientSize(); + RealRect rect = RealRect(0, 0, width, height); RotatedDC rdc(mdc, 0, rect, 1, QUALITY_LOW); draw_checker(rdc, rect); rdc.DrawImage(image, RealPoint(0,0)); @@ -587,7 +586,6 @@ ImageSliceSelector::ImageSliceSelector(Window* parent, int id, ImageSlice& slice , slice(slice) , mouse_down(false) { - float target_ratio = ((float) slice.source.GetWidth()) / ((float) slice.source.GetHeight()); if (target_ratio > 1.0) { SetMinSize(wxSize(500, 500 / target_ratio)); diff --git a/src/gui/image_slice_window.hpp b/src/gui/image_slice_window.hpp index 1509d3f7..df602008 100644 --- a/src/gui/image_slice_window.hpp +++ b/src/gui/image_slice_window.hpp @@ -36,7 +36,7 @@ public: wxSize target_size; ///< Size of the target image wxRect selection; ///< Area to slice from source Color background; ///< Color for areas outside the source image - bool allow_outside; ///< Allow the slice to extend outside the source image? + bool allow_outside; ///< Allow the slice to extend outside the source image? TODO: This currently crashes bool aspect_fixed; ///< Aspect ratio lock? // Filters @@ -50,7 +50,7 @@ public: void centerSelectionHorizontally(); void centerSelectionVertically(); /// Get the sliced image - Image getSlice(double scale = 1.0) const; + Image getSlice() const; // Zoom factor inline double zoomX() const { return target_size.GetWidth() / (double)selection.width; } @@ -68,7 +68,7 @@ public: ImageSliceWindow(Window* parent, const Image& source, const String& filename, const String& cardname, const wxSize& target_size, const AlphaMask& target_mask); /// Return the sliced image - Image getImage(double scale) const; + Image getImage() const; // --------------------------------------------------- : Previously Used Settings diff --git a/src/gui/preferences_window.cpp b/src/gui/preferences_window.cpp index 2a3194fb..696f8b20 100644 --- a/src/gui/preferences_window.cpp +++ b/src/gui/preferences_window.cpp @@ -52,27 +52,25 @@ public: private: DECLARE_EVENT_TABLE(); - wxCheckBox* high_quality, *borders, *draw_editing, *spellcheck_enabled, *non_normal_export, *notes_export; + wxCheckBox* high_quality, *borders, *draw_editing, *spellcheck_enabled; wxComboBox* zoom; int zoom_int; - wxChoice* export_zoom; - void onSelectColumns(wxCommandEvent&); void onZoomChange(wxCommandEvent&); void updateZoom(); }; -class InternalPreferencesPage : public PreferencesPage { +class TransfersPreferencesPage : public PreferencesPage { public: - InternalPreferencesPage(Window* parent); + TransfersPreferencesPage(Window* parent); void store() override; private: - wxCheckBox* internal_image_extension; + wxCheckBox* non_normal_export, *bleed_export, *notes_export, *internal_image_extension; - wxChoice* internal_scale; + wxChoice* export_scale, *import_scale; }; // Preferences page for directories of programs @@ -116,7 +114,7 @@ PreferencesWindow::PreferencesWindow(Window* parent) wxNotebook* nb = new wxNotebook(this, ID_NOTEBOOK); nb->AddPage(new GlobalPreferencesPage (nb), _TITLE_("global")); nb->AddPage(new DisplayPreferencesPage(nb), _TITLE_("display")); - nb->AddPage(new InternalPreferencesPage(nb), _TITLE_("internal")); + nb->AddPage(new TransfersPreferencesPage(nb), _TITLE_("transfers")); nb->AddPage(new DirsPreferencesPage (nb), _TITLE_("directories")); nb->AddPage(new UpdatePreferencesPage (nb), _TITLE_("updates")); @@ -216,34 +214,18 @@ DisplayPreferencesPage::DisplayPreferencesPage(Window* parent) borders = new wxCheckBox(this, wxID_ANY, _BUTTON_("show lines")); draw_editing = new wxCheckBox(this, wxID_ANY, _BUTTON_("show editing hints")); spellcheck_enabled = new wxCheckBox(this, wxID_ANY, _BUTTON_("spellcheck enabled")); - non_normal_export = new wxCheckBox(this, wxID_ANY, _BUTTON_("rotation export")); - notes_export = new wxCheckBox(this, wxID_ANY, _BUTTON_("notes export")); zoom = new wxComboBox(this, ID_ZOOM); - export_zoom = new wxChoice (this, ID_EXPORT_ZOOM); - //wxButton* columns = new wxButton(this, ID_SELECT_COLUMNS, _BUTTON_("select")); // set values high_quality-> SetValue( settings.default_stylesheet_settings.card_anti_alias()); borders-> SetValue( settings.default_stylesheet_settings.card_borders()); draw_editing-> SetValue( settings.default_stylesheet_settings.card_draw_editing()); spellcheck_enabled->SetValue( settings.default_stylesheet_settings.card_spellcheck_enabled()); - non_normal_export-> SetValue(!settings.default_stylesheet_settings.card_normal_export()); - notes_export-> SetValue( settings.default_stylesheet_settings.card_notes_export()); - zoom_int = static_cast(settings.default_stylesheet_settings.card_zoom() * 100); - zoom->SetValue(String::Format(_("%d%%"),zoom_int)); - for (int i : Settings::export_zoom_choices) { - zoom->Append(String::Format(_("%d%%"), i)); - } - - export_zoom->Append(_LABEL_("export around 300")); - export_zoom->Append(_LABEL_("export force 300")); - export_zoom->Append(_LABEL_("export force 150")); - for (int i : Settings::export_zoom_choices) { - export_zoom->Append(String::Format(_("%d%%"), i)); - } - int default_export_zoom = settings.default_stylesheet_settings.export_zoom_selection(); - if (default_export_zoom < 0 || default_export_zoom > export_zoom->GetCount() - 1) default_export_zoom = 0; - export_zoom->SetSelection(default_export_zoom); + zoom_int = static_cast( settings.default_stylesheet_settings.card_zoom() * 100); + zoom->SetValue(String::Format(_("%d%%"),zoom_int)); + for (int i : Settings::scale_choices) { + zoom->Append(String::Format(_("%d%%"), i)); + } // init sizer wxSizer* s = new wxBoxSizer(wxVERTICAL); @@ -257,19 +239,8 @@ DisplayPreferencesPage::DisplayPreferencesPage(Window* parent) s3->AddSpacer(2); s3->Add(zoom); s3->Add(new wxStaticText(this, wxID_ANY, _LABEL_("percent of normal")),1, wxALL & ~wxRIGHT, 4); - wxSizer* s4 = new wxBoxSizer(wxHORIZONTAL); - s4->Add(new wxStaticText(this, wxID_ANY, _LABEL_("export")), 0, wxALL & ~wxLEFT, 4); - s4->AddSpacer(2); - s4->Add(export_zoom); - //s4->Add(new wxStaticText(this, wxID_ANY, _LABEL_("percent of normal")), 1, wxALL & ~wxRIGHT, 4); - s2->Add(s3, 0, wxEXPAND | wxALL, 4); - s2->Add(s4, 0, wxEXPAND | wxALL, 4); - s2->Add(non_normal_export, 0, wxEXPAND | wxALL, 4); - s2->Add(notes_export, 0, wxEXPAND | wxALL, 4); - s->Add(s2, 0, wxEXPAND | wxALL, 8); - s->SetSizeHints(this); SetSizer(s); } @@ -279,12 +250,9 @@ void DisplayPreferencesPage::store() { settings.default_stylesheet_settings.card_borders = borders->GetValue(); settings.default_stylesheet_settings.card_draw_editing = draw_editing->GetValue(); settings.default_stylesheet_settings.card_spellcheck_enabled = spellcheck_enabled->GetValue(); - settings.default_stylesheet_settings.card_normal_export = !non_normal_export->GetValue(); - settings.default_stylesheet_settings.card_notes_export = notes_export->GetValue(); updateZoom(); settings.default_stylesheet_settings.card_zoom = zoom_int / 100.0; - settings.default_stylesheet_settings.export_zoom_selection = export_zoom->GetSelection(); } void DisplayPreferencesPage::onSelectColumns(wxCommandEvent&) { @@ -292,7 +260,7 @@ void DisplayPreferencesPage::onSelectColumns(wxCommandEvent&) { } void DisplayPreferencesPage::onZoomChange(wxCommandEvent&) { - updateZoom(); + updateZoom(); } void DisplayPreferencesPage::updateZoom() { @@ -312,41 +280,78 @@ END_EVENT_TABLE () // ----------------------------------------------------------------------------- : Preferences page : internal -InternalPreferencesPage::InternalPreferencesPage(Window* parent) : PreferencesPage(parent) { +TransfersPreferencesPage::TransfersPreferencesPage(Window* parent) : PreferencesPage(parent) { + // init controls + non_normal_export = new wxCheckBox(this, wxID_ANY, _BUTTON_("rotation export")); + bleed_export = new wxCheckBox(this, wxID_ANY, _BUTTON_("bleed export")); + notes_export = new wxCheckBox(this, wxID_ANY, _BUTTON_("notes export")); + export_scale = new wxChoice (this, ID_EXPORT_ZOOM); + internal_image_extension = new wxCheckBox(this, wxID_ANY, _BUTTON_("internal image extension")); - internal_scale = new wxChoice(this, ID_INTERNAL_SCALE); + import_scale = new wxChoice (this, ID_IMPORT_ZOOM); + + // set values + non_normal_export-> SetValue(!settings.default_stylesheet_settings.card_normal_export()); + bleed_export-> SetValue( settings.default_stylesheet_settings.card_bleed_export()); + notes_export-> SetValue( settings.default_stylesheet_settings.card_notes_export()); + export_scale->Append(_LABEL_("export around 300")); + export_scale->Append(_LABEL_("export force 300")); + export_scale->Append(_LABEL_("export force 150")); + for (int i : Settings::scale_choices) { + export_scale->Append(String::Format(_("%d%%"), i)); + } + int default_export_scale = settings.default_stylesheet_settings.export_scale_selection(); + if (default_export_scale < 0 || default_export_scale > export_scale->GetCount() - 1) default_export_scale = 0; + export_scale->SetSelection(default_export_scale); internal_image_extension->SetValue(settings.internal_image_extension); - - internal_scale->Append(_LABEL_("use export scale")); - internal_scale->Append(_LABEL_("export around 300")); - internal_scale->Append(_LABEL_("export force 300")); - internal_scale->Append(_LABEL_("export force 150")); - for (int i : Settings::export_zoom_choices) { - internal_scale->Append(String::Format(_("%d%%"), i)); + import_scale->Append(_LABEL_("use export scale")); + import_scale->Append(_LABEL_("export around 300")); + import_scale->Append(_LABEL_("export force 300")); + import_scale->Append(_LABEL_("export force 150")); + for (int i : Settings::scale_choices) { + import_scale->Append(String::Format(_("%d%%"), i)); } - int default_internal_scale = settings.internal_scale_selection; - if (default_internal_scale < 0 || default_internal_scale > internal_scale->GetCount() - 1) default_internal_scale = 0; - internal_scale->SetSelection(default_internal_scale); - - wxSizer* s = new wxBoxSizer(wxVERTICAL); - wxSizer* s2 = new wxStaticBoxSizer(wxVERTICAL, this, _LABEL_("storage")); - wxSizer* s3 = new wxBoxSizer(wxHORIZONTAL); - s3->Add(new wxStaticText(this, wxID_ANY, _LABEL_("scale")), 0, wxALL & ~wxLEFT, 4); - s3->AddSpacer(2); - s3->Add(internal_scale); - //s3->Add(new wxStaticText(this, wxID_ANY, _LABEL_("percent of normal")), 1, wxALL & ~wxRIGHT, 4); - s2->Add(s3); - s2->Add(new wxStaticText(this, wxID_ANY, _LABEL_("internal scale desc")), 0, wxALL & ~wxLEFT, 4); - s2->Add(internal_image_extension, 0, wxEXPAND | wxALL, 4); - s->Add(s2, 0, wxEXPAND | wxALL, 8); + int default_import_scale = settings.import_scale_selection; + if (default_import_scale < 0 || default_import_scale > import_scale->GetCount() - 1) default_import_scale = 0; + import_scale->SetSelection(default_import_scale); + + // init sizers + wxSizer* s = new wxBoxSizer(wxVERTICAL); + wxSizer* s2 = new wxStaticBoxSizer(wxVERTICAL, this, _LABEL_("export")); + s2->Add(new wxStaticText(this, wxID_ANY, _LABEL_("export desc")), 0, wxALL & ~wxLEFT, 4); + wxSizer* s3 = new wxBoxSizer(wxHORIZONTAL); + s3->Add(new wxStaticText(this, wxID_ANY, _LABEL_("scale")), 0, wxALL & ~wxLEFT, 4); + s3->AddSpacer(2); + s3->Add(export_scale); + s2->Add(s3, 0, wxEXPAND | wxALL, 4); + s2->Add(non_normal_export, 0, wxEXPAND | wxALL, 4); + s2->Add(bleed_export, 0, wxEXPAND | wxALL, 4); + s2->Add(notes_export, 0, wxEXPAND | wxALL, 4); + wxSizer* s5 = new wxStaticBoxSizer(wxVERTICAL, this, _LABEL_("import")); + s5->Add(new wxStaticText(this, wxID_ANY, _LABEL_("import desc")), 0, wxALL & ~wxLEFT, 4); + wxSizer* s6 = new wxBoxSizer(wxHORIZONTAL); + s6->Add(new wxStaticText(this, wxID_ANY, _LABEL_("scale")), 0, wxALL & ~wxLEFT, 4); + s6->AddSpacer(2); + s6->Add(import_scale); + s5->Add(s6, 0, wxEXPAND | wxALL & ~wxBottom, 4); + s5->Add(new wxStaticText(this, wxID_ANY, _LABEL_("internal scale desc")), 0, wxALL & ~wxTOP, 4); + s5->Add(internal_image_extension, 0, wxEXPAND | wxALL, 4); + s->Add(s2, 0, wxEXPAND | wxALL, 8); + s->Add(s5, 0, wxEXPAND | wxALL, 8); + export_scale->SetFocus(); s->SetSizeHints(this); SetSizer(s); } -void InternalPreferencesPage::store() { - settings.internal_image_extension = internal_image_extension->GetValue(); - settings.internal_scale_selection = internal_scale->GetSelection(); +void TransfersPreferencesPage::store() { + settings.default_stylesheet_settings.card_normal_export = !non_normal_export->GetValue(); + settings.default_stylesheet_settings.card_bleed_export = bleed_export->GetValue(); + settings.default_stylesheet_settings.card_notes_export = notes_export->GetValue(); + settings.default_stylesheet_settings.export_scale_selection = export_scale->GetSelection(); + + settings.internal_image_extension = internal_image_extension->GetValue(); + settings.import_scale_selection = import_scale->GetSelection(); } // ----------------------------------------------------------------------------- : Preferences page : directories diff --git a/src/gui/value/image.cpp b/src/gui/value/image.cpp index bac4c419..675fe447 100644 --- a/src/gui/value/image.cpp +++ b/src/gui/value/image.cpp @@ -50,31 +50,24 @@ bool ImageValueEditor::onLeftDClick(const RealPoint&, wxMouseEvent&) { void ImageValueEditor::sliceImage(const Image& image, const String& filename, const String& cardname) { if (!image.Ok()) return; + // determine import scale based on the user's settings. + double import_scale = 1.0; + StyleSheetP stylesheet = editor().getCard()->stylesheet; + if (!stylesheet) stylesheet = editor().getSet()->stylesheet; + if (stylesheet) import_scale = settings.importScaleSettingsFor(*stylesheet); + RealSize target_size = RealSize(style().getSize() * import_scale); + target_size = RealSize((int)target_size.width, (int)target_size.height); // mask - GeneratedImage::Options options((int)style().width, (int)style().height, &parent.getStylePackage(), &parent.getLocalPackage()); + GeneratedImage::Options options((int)target_size.width, (int)target_size.height, &parent.getStylePackage(), &parent.getLocalPackage()); AlphaMask mask; - style().mask.getNoCache(options,mask); - // slice - ImageSliceWindow s(wxGetTopLevelParent(&editor()), image, filename, cardname, style().getSize(), mask); + style().mask.getNoCache(options, mask); + // slice + ImageSliceWindow s(wxGetTopLevelParent(&editor()), image, filename, cardname, target_size, mask); // clicked ok? if (s.ShowModal() == wxID_OK) { // store the image into the set LocalFileName new_image_file = getLocalPackage().newFileName(field().name, settings.internal_image_extension ? _(".png") : _("")); // a new unique name in the package - - // Specify a desired size based on the stylesheet and a scale multiplier defined within the user's settings. - // Storing at a greater than 100% resolution allows for better exports >100%, but may change how images look when filters (sharpen) are applied. - // It also disrupts some of the patterns in use for doing popout planeswalkers since you have to do the math at both scales. - // Additionally, this bloats the set file size as even under-resolution images are upscaled to the new minimum size. - double internal_scale = 1.0; - try { - // surrounding this in try catch to be safe for now. maybe this is overkill - StyleSheetP stylesheet = editor().getCard()->stylesheet; - if (!stylesheet) stylesheet = editor().getSet()->stylesheet; - internal_scale = settings.internalScaleSettingsFor(*stylesheet); - } catch (...) { - queue_message(MESSAGE_ERROR, _("Could not find stylesheet to determine export zoom.\nfilename: " + filename + _("\ncardname: " + cardname))); - } - Image img = s.getImage(internal_scale); + Image img = s.getImage(); img.SaveFile(getLocalPackage().nameOut(new_image_file), wxBITMAP_TYPE_PNG); // always use PNG images, see #69. Disk space is cheap anyway. addAction(value_action(valueP(), new_image_file)); } diff --git a/src/script/functions/basic.cpp b/src/script/functions/basic.cpp index 8c49e5fa..01625cc1 100644 --- a/src/script/functions/basic.cpp +++ b/src/script/functions/basic.cpp @@ -759,6 +759,22 @@ SCRIPT_FUNCTION(get_card_stylesheet) { throw ScriptError(_("invalid set or card argument")); } +SCRIPT_FUNCTION(get_card_export_settings) { + SCRIPT_PARAM_C(ScriptValueP, input); + SCRIPT_PARAM_C(ScriptValueP, set); + ScriptObject* c = dynamic_cast*>(input.get()); + ScriptObject* s = dynamic_cast*>(set.get()); + if (s && c) { + Settings::ExportSettings card_settings = settings.exportSettingsFor(s->getValue()->stylesheetFor(c->getValue())); + ScriptCustomCollectionP ret(new ScriptCustomCollection()); + ret->value.push_back(to_script(lround(card_settings.zoom * 100))); + ret->value.push_back(to_script(lround(rad_to_deg(card_settings.angle_radians)))); + ret->value.push_back(to_script(lround(card_settings.bleed_pixels))); + return ret; + } + throw ScriptError(_("invalid set or card argument")); +} + SCRIPT_FUNCTION(get_card_from_uid) { SCRIPT_PARAM_C(Set*, set); SCRIPT_PARAM_C(String, input); @@ -999,82 +1015,83 @@ SCRIPT_FUNCTION(rule) { void init_script_basic_functions(Context& ctx) { // app info - ctx.setVariable(_("get_mse_version"), script_get_mse_version); - ctx.setVariable(_("get_mse_locale"), script_get_mse_locale); - ctx.setVariable(_("get_mse_path"), script_get_mse_path); - ctx.setVariable(_("get_mse_dark_mode"), script_get_mse_dark_mode); + ctx.setVariable(_("get_mse_version"), script_get_mse_version); + ctx.setVariable(_("get_mse_locale"), script_get_mse_locale); + ctx.setVariable(_("get_mse_path"), script_get_mse_path); + ctx.setVariable(_("get_mse_dark_mode"), script_get_mse_dark_mode); // debugging - ctx.setVariable(_("trace"), script_trace); - ctx.setVariable(_("warning"), script_warning); - ctx.setVariable(_("error"), script_error); - ctx.setVariable(_("exists_in_package"), script_exists_in_package); + ctx.setVariable(_("trace"), script_trace); + ctx.setVariable(_("warning"), script_warning); + ctx.setVariable(_("error"), script_error); + ctx.setVariable(_("exists_in_package"), script_exists_in_package); // conversion - ctx.setVariable(_("to_string"), script_to_string); - ctx.setVariable(_("to_int"), script_to_int); - ctx.setVariable(_("to_real"), script_to_real); - ctx.setVariable(_("to_number"), script_to_number); - ctx.setVariable(_("to_boolean"), script_to_boolean); - ctx.setVariable(_("to_color"), script_to_color); - ctx.setVariable(_("to_date"), script_to_date); - ctx.setVariable(_("to_code"), script_to_code); - ctx.setVariable(_("to_json"), script_to_json); - ctx.setVariable(_("from_json"), script_from_json); - ctx.setVariable(_("type_name"), script_type_name); - ctx.setVariable(_("make_map"), script_make_map); - ctx.setVariable(_("get_card_styling"), script_get_card_styling); - ctx.setVariable(_("get_card_stylesheet"), script_get_card_stylesheet); + ctx.setVariable(_("to_string"), script_to_string); + ctx.setVariable(_("to_int"), script_to_int); + ctx.setVariable(_("to_real"), script_to_real); + ctx.setVariable(_("to_number"), script_to_number); + ctx.setVariable(_("to_boolean"), script_to_boolean); + ctx.setVariable(_("to_color"), script_to_color); + ctx.setVariable(_("to_date"), script_to_date); + ctx.setVariable(_("to_code"), script_to_code); + ctx.setVariable(_("to_json"), script_to_json); + ctx.setVariable(_("from_json"), script_from_json); + ctx.setVariable(_("type_name"), script_type_name); + ctx.setVariable(_("make_map"), script_make_map); + ctx.setVariable(_("get_card_styling"), script_get_card_styling); + ctx.setVariable(_("get_card_stylesheet"), script_get_card_stylesheet); + ctx.setVariable(_("get_card_export_settings"), script_get_card_export_settings); + ctx.setVariable(_("get_card_from_uid"), script_get_card_from_uid); + ctx.setVariable(_("get_cards_from_link"), script_get_cards_from_link); + ctx.setVariable(_("get_back_face"), script_get_back_face); + ctx.setVariable(_("get_front_face"), script_get_front_face); + ctx.setVariable(_("has_link"), script_has_link); // math - ctx.setVariable(_("abs"), script_abs); - ctx.setVariable(_("random_real"), script_random_real); - ctx.setVariable(_("random_int"), script_random_int); - ctx.setVariable(_("random_boolean"), script_random_boolean); - ctx.setVariable(_("sin"), script_sin); - ctx.setVariable(_("cos"), script_cos); - ctx.setVariable(_("tan"), script_tan); - ctx.setVariable(_("sin_deg"), script_sin_deg); - ctx.setVariable(_("cos_deg"), script_cos_deg); - ctx.setVariable(_("tan_deg"), script_tan_deg); - ctx.setVariable(_("exp"), script_exp); - ctx.setVariable(_("log"), script_log); - ctx.setVariable(_("log10"), script_log10); - ctx.setVariable(_("sqrt"), script_sqrt); - ctx.setVariable(_("pow"), script_pow); + ctx.setVariable(_("abs"), script_abs); + ctx.setVariable(_("random_real"), script_random_real); + ctx.setVariable(_("random_int"), script_random_int); + ctx.setVariable(_("random_boolean"), script_random_boolean); + ctx.setVariable(_("sin"), script_sin); + ctx.setVariable(_("cos"), script_cos); + ctx.setVariable(_("tan"), script_tan); + ctx.setVariable(_("sin_deg"), script_sin_deg); + ctx.setVariable(_("cos_deg"), script_cos_deg); + ctx.setVariable(_("tan_deg"), script_tan_deg); + ctx.setVariable(_("exp"), script_exp); + ctx.setVariable(_("log"), script_log); + ctx.setVariable(_("log10"), script_log10); + ctx.setVariable(_("sqrt"), script_sqrt); + ctx.setVariable(_("pow"), script_pow); // string - ctx.setVariable(_("to_upper"), script_to_upper); - ctx.setVariable(_("to_lower"), script_to_lower); - ctx.setVariable(_("to_title"), script_to_title); - ctx.setVariable(_("reverse"), script_reverse); - ctx.setVariable(_("trim"), script_trim); - ctx.setVariable(_("substring"), script_substring); - ctx.setVariable(_("contains"), script_contains); - ctx.setVariable(_("format"), script_format); - ctx.setVariable(_("format_rule"), make_intrusive(script_format)); - ctx.setVariable(_("curly_quotes"), script_curly_quotes); - ctx.setVariable(_("regex_escape"), script_regex_escape); - ctx.setVariable(_("sort_text"), script_sort_text); - ctx.setVariable(_("sort_rule"), make_intrusive(script_sort_text)); + ctx.setVariable(_("to_upper"), script_to_upper); + ctx.setVariable(_("to_lower"), script_to_lower); + ctx.setVariable(_("to_title"), script_to_title); + ctx.setVariable(_("reverse"), script_reverse); + ctx.setVariable(_("trim"), script_trim); + ctx.setVariable(_("substring"), script_substring); + ctx.setVariable(_("contains"), script_contains); + ctx.setVariable(_("format"), script_format); + ctx.setVariable(_("format_rule"), make_intrusive(script_format)); + ctx.setVariable(_("curly_quotes"), script_curly_quotes); + ctx.setVariable(_("regex_escape"), script_regex_escape); + ctx.setVariable(_("sort_text"), script_sort_text); + ctx.setVariable(_("sort_rule"), make_intrusive(script_sort_text)); // tagged string - ctx.setVariable(_("tag_contents"), script_tag_contents); - ctx.setVariable(_("remove_tag"), script_remove_tag); - ctx.setVariable(_("remove_tags"), script_remove_tags); - ctx.setVariable(_("tag_contents_rule"), make_intrusive(script_tag_contents)); - ctx.setVariable(_("tag_remove_rule"), make_intrusive(script_remove_tag)); + ctx.setVariable(_("tag_contents"), script_tag_contents); + ctx.setVariable(_("remove_tag"), script_remove_tag); + ctx.setVariable(_("remove_tags"), script_remove_tags); + ctx.setVariable(_("tag_contents_rule"), make_intrusive(script_tag_contents)); + ctx.setVariable(_("tag_remove_rule"), make_intrusive(script_remove_tag)); // collection - ctx.setVariable(_("position"), script_position_of); - ctx.setVariable(_("length"), script_length); - ctx.setVariable(_("number_of_items"), script_number_of_items); // deprecated - ctx.setVariable(_("filter_list"), script_filter_list); - ctx.setVariable(_("sort_list"), script_sort_list); - ctx.setVariable(_("random_shuffle"), script_random_shuffle); - ctx.setVariable(_("random_select"), script_random_select); - ctx.setVariable(_("random_select_many"), script_random_select_many); - ctx.setVariable(_("get_card_from_uid"), script_get_card_from_uid); - ctx.setVariable(_("get_cards_from_link"), script_get_cards_from_link); - ctx.setVariable(_("get_back_face"), script_get_back_face); - ctx.setVariable(_("get_front_face"), script_get_front_face); - ctx.setVariable(_("has_link"), script_has_link); + ctx.setVariable(_("position"), script_position_of); + ctx.setVariable(_("length"), script_length); + ctx.setVariable(_("number_of_items"), script_number_of_items); // deprecated + ctx.setVariable(_("filter_list"), script_filter_list); + ctx.setVariable(_("sort_list"), script_sort_list); + ctx.setVariable(_("random_shuffle"), script_random_shuffle); + ctx.setVariable(_("random_select"), script_random_select); + ctx.setVariable(_("random_select_many"), script_random_select_many); // keyword - ctx.setVariable(_("expand_keywords"), script_expand_keywords); - ctx.setVariable(_("expand_keywords_rule"), make_intrusive(script_expand_keywords)); - ctx.setVariable(_("keyword_usage"), script_keyword_usage); + ctx.setVariable(_("expand_keywords"), script_expand_keywords); + ctx.setVariable(_("expand_keywords_rule"), make_intrusive(script_expand_keywords)); + ctx.setVariable(_("keyword_usage"), script_keyword_usage); } diff --git a/src/script/functions/export.cpp b/src/script/functions/export.cpp index 5b26b685..bd143d3b 100644 --- a/src/script/functions/export.cpp +++ b/src/script/functions/export.cpp @@ -428,22 +428,38 @@ SCRIPT_FUNCTION(write_image_file) { SCRIPT_RETURN(file); // already written an image with this name } // get image - SCRIPT_PARAM_C(ScriptValueP, input); - SCRIPT_OPTIONAL_PARAM_(int, width); - SCRIPT_OPTIONAL_PARAM_(int, height); - ScriptObject* card = dynamic_cast*>(input.get()); // is it a card? - Image image; - GeneratedImage::Options options(width, height, ei.export_template.get(), ei.set.get()); + Image img; + SCRIPT_PARAM(Set*, set); + SCRIPT_PARAM_C(ScriptValueP, input); + ScriptObject* card = dynamic_cast*>(input.get()); // is the input a card or image? if (card) { - image = conform_image(export_image(ei.set, card->getValue()), options); + SCRIPT_PARAM_DEFAULT(double, zoom, 100.0); + SCRIPT_PARAM_DEFAULT(Degrees, angle, 0.0); + SCRIPT_PARAM_DEFAULT(double, bleed, 0.0); + SCRIPT_PARAM_DEFAULT(bool, use_user_settings, false); + if (use_user_settings) { + // Use the User's Preferences for Export Zoom, Angle and Bleed settings. + Settings::ExportSettings card_settings = settings.exportSettingsFor(set->stylesheetFor(card->getValue())); + zoom = card_settings.zoom; + angle = card_settings.angle_radians; + bleed = card_settings.bleed_pixels; + } else { + // Use the provided (or defaulted) Zoom, Angle and Bleed. + zoom = zoom / 100.0; + angle = deg_to_rad(angle); + } + img = export_image(set, card->getValue(), true, zoom, angle, bleed); } else { - image = input->toImage()->generateConform(options); + SCRIPT_OPTIONAL_PARAM_(int, width) + SCRIPT_OPTIONAL_PARAM_(int, height) + GeneratedImage::Options options(width, height, ei.export_template.get(), ei.set.get()); + img = input->toImage()->generateConform(options); } - if (!image.Ok()) throw Error(_("Unable to generate image for file ") + file); + if (!img.Ok()) throw Error(_("Unable to generate image for file ") + file); // write ensure_dir_valid(out_path); - image.SaveFile(out_path); - ei.exported_images.insert(make_pair(file, wxSize(image.GetWidth(), image.GetHeight()))); + img.SaveFile(out_path); + ei.exported_images.insert(make_pair(file, wxSize(img.GetWidth(), img.GetHeight()))); SCRIPT_RETURN(file); } diff --git a/src/script/functions/image.cpp b/src/script/functions/image.cpp index 8ca3b3c7..3731cb73 100644 --- a/src/script/functions/image.cpp +++ b/src/script/functions/image.cpp @@ -35,19 +35,20 @@ SCRIPT_FUNCTION(to_card_image) { SCRIPT_PARAM(CardP, input); SCRIPT_PARAM_DEFAULT(double, zoom, 100.0); SCRIPT_PARAM_DEFAULT(Degrees, angle, 0.0); + SCRIPT_PARAM_DEFAULT(double, bleed, 0.0); SCRIPT_PARAM_DEFAULT(bool, use_user_settings, false); if (use_user_settings) { - // Use the User's Preferences for Export Zoom and Angle settings. - const StyleSheet& stylesheet = set->stylesheetFor(input); - StyleSheetSettings& stylesheet_settings = settings.stylesheetSettingsFor(stylesheet); - zoom = settings.exportZoomSettingsFor(stylesheet); - angle = stylesheet_settings.card_normal_export() ? 0.0 : deg_to_rad(stylesheet_settings.card_angle()); + // Use the User's Preferences for Export Zoom, Angle and Bleed settings. + Settings::ExportSettings card_settings = settings.exportSettingsFor(set->stylesheetFor(input)); + zoom = card_settings.zoom; + angle = card_settings.angle_radians; + bleed = card_settings.bleed_pixels; } else { - // Use the provided (or defaulted) Zoom and Angle. + // Use the provided (or defaulted) Zoom, Angle and Bleed. zoom = zoom / 100.0; angle = deg_to_rad(angle); } - return make_intrusive(export_image(set, input, zoom, angle)); + return make_intrusive(export_image(set, input, true, zoom, angle, bleed)); } SCRIPT_FUNCTION(import_image) { diff --git a/src/script/functions/json.hpp b/src/script/functions/json.hpp index dc850dfc..4e8e61e2 100644 --- a/src/script/functions/json.hpp +++ b/src/script/functions/json.hpp @@ -363,7 +363,8 @@ inline static ScriptValueP json_to_mse(const String& string, Set* set) { boost::json::parse_options options; options.allow_invalid_utf8 = true; boost::json::value jv = boost::json::parse(string.ToStdString(), ec, {}, options); - if(ec) return script_nil; //queue_message(MESSAGE_ERROR, _ERROR_("json cant parse") + _("\n\n") + ec.message()); + //if(ec) queue_message(MESSAGE_ERROR, _ERROR_("json cant parse") + _("\n\n") + ec.message()); + if(ec) return script_nil; return json_to_mse(jv, set); } catch (...) { diff --git a/src/util/angle.hpp b/src/util/angle.hpp index 517254aa..f50d7fd5 100644 --- a/src/util/angle.hpp +++ b/src/util/angle.hpp @@ -31,7 +31,7 @@ const Radians rad360 = 2.0*M_PI; /// Are two floating point numbers equal up to a small epsilon? inline bool almost_equal(double x, double y) { - return fabs(x-y) < 1e-10; + return fabs(x-y) < 1e-5; } inline bool is_rad0(double x) { return almost_equal(x,0) || almost_equal(x,rad360); diff --git a/src/util/io/package.hpp b/src/util/io/package.hpp index 683ab26a..43c841d0 100644 --- a/src/util/io/package.hpp +++ b/src/util/io/package.hpp @@ -54,42 +54,48 @@ public: inline String const& toStringForKey() const { return fn; } /// Retreive a rect from a filename - inline static wxRect getExternalRect(const String& filename) { + inline static void getExternalRect(const String& filename, wxRect& rect_out, int& degrees_out) { size_t first = filename.find(_("---")); - if (first == String::npos) return wxRect(); + if (first == String::npos) return; size_t last = filename.find(_("---"), first+3); - if (last == String::npos) return wxRect(); + if (last == String::npos) return; String string = filename.substr(first + 3, last - (first + 3)); - if (string.empty()) return wxRect(); + if (string.empty()) return; size_t divider = string.find(_("-")); - if (divider == String::npos) return wxRect(); - if (divider == 0) return wxRect(); + if (divider == String::npos) return; + if (divider == 0) return; int x; - if(!string.substr(0, divider).ToInt(&x)) return wxRect(); + if(!string.substr(0, divider).ToInt(&x)) return; string = string.substr(divider + 1); divider = string.find(_("-")); - if (divider == String::npos) return wxRect(); - if (divider == 0) return wxRect(); + if (divider == String::npos) return; + if (divider == 0) return; int y; - if(!string.substr(0, divider).ToInt(&y)) return wxRect(); + if(!string.substr(0, divider).ToInt(&y)) return; string = string.substr(divider + 1); divider = string.find(_("-")); - if (divider == String::npos) return wxRect(); - if (divider == 0) return wxRect(); + if (divider == String::npos) return; + if (divider == 0) return; int width; - if(!string.substr(0, divider).ToInt(&width)) return wxRect(); + if(!string.substr(0, divider).ToInt(&width)) return; string = string.substr(divider + 1); + divider = string.find(_("-")); + if (divider == String::npos) return; + if (divider == 0) return; int height; - if(!string.ToInt(&height)) return wxRect(); + if(!string.substr(0, divider).ToInt(&height)) return; + string = string.substr(divider + 1); - return wxRect(x, y, width, height); + if(!string.ToInt(°rees_out)) return; + + rect_out = wxRect(x, y, width, height); } - inline wxRect getExternalRect() { - return getExternalRect(fn); + inline void getExternalRect(wxRect& rect_out, int& degrees_out) { + getExternalRect(fn, rect_out, degrees_out); } private: diff --git a/src/util/rotation.cpp b/src/util/rotation.cpp index 199f38d7..5f8d33cf 100644 --- a/src/util/rotation.cpp +++ b/src/util/rotation.cpp @@ -75,7 +75,7 @@ RealSize Rotation::trSizeToBB(const RealSize& size) const { } RealRect Rotation::trRectToBB(const RealRect& r) const { - const bool special_case_optimization = false; + const bool special_case_optimization = true; double x = r.x * zoomX, y = r.y * zoomY; double w = r.width * zoomX, h = r.height * zoomY; if (special_case_optimization && is_rad0(angle)) { diff --git a/src/util/window_id.hpp b/src/util/window_id.hpp index c8cc9099..ca7d371b 100644 --- a/src/util/window_id.hpp +++ b/src/util/window_id.hpp @@ -310,7 +310,7 @@ enum ControlID { ID_SHARPEN, ID_SHARPEN_AMOUNT, // Internal window - ID_INTERNAL_SCALE, + ID_IMPORT_ZOOM, // Updates window ID_PACKAGE_LIST, ID_KEEP, diff --git a/tools/website/drupal/mse-drupal-modules/highlight.inc b/tools/website/drupal/mse-drupal-modules/highlight.inc index 9024c1a7..7c767f8f 100644 --- a/tools/website/drupal/mse-drupal-modules/highlight.inc +++ b/tools/website/drupal/mse-drupal-modules/highlight.inc @@ -107,6 +107,7 @@ $built_in_functions = array( 'get_card_from_uid' =>'', 'get_card_styling' =>'', 'get_card_stylesheet' =>'', + 'get_card_export_settings' =>'', 'add_card_to_set' =>'', // html export 'to_html' =>'',