Commit 5257a369 authored by twanvl's avatar twanvl

Images are now cached as wxBitmap, not wxImage. This should improve performance.

Fixed some more corner cases of rotation+zoom.
parent 4a603cac
...@@ -120,7 +120,7 @@ template <> StyleP read_new<Style>(Reader&) { ...@@ -120,7 +120,7 @@ template <> StyleP read_new<Style>(Reader&) {
throw InternalError(_("IndexMap contains nullptr StyleP the application should have crashed already")); throw InternalError(_("IndexMap contains nullptr StyleP the application should have crashed already"));
} }
bool Style::update(Context& ctx) { int Style::update(Context& ctx) {
bool changed = bool changed =
left .update(ctx) left .update(ctx)
| width .update(ctx) | width .update(ctx)
...@@ -196,8 +196,8 @@ void Style::removeListener(StyleListener* listener) { ...@@ -196,8 +196,8 @@ void Style::removeListener(StyleListener* listener) {
listeners.end() listeners.end()
); );
} }
void Style::tellListeners(bool already_prepared) { void Style::tellListeners(int changes) {
FOR_EACH(l, listeners) l->onStyleChange(already_prepared); FOR_EACH(l, listeners) l->onStyleChange(changes);
} }
StyleListener::StyleListener(const StyleP& style) StyleListener::StyleListener(const StyleP& style)
......
...@@ -119,9 +119,11 @@ class Style : public IntrusivePtrVirtualBase { ...@@ -119,9 +119,11 @@ class Style : public IntrusivePtrVirtualBase {
/** thisP is a smart pointer to this */ /** thisP is a smart pointer to this */
virtual ValueViewerP makeEditor(DataEditor& parent, const StyleP& thisP) = 0; virtual ValueViewerP makeEditor(DataEditor& parent, const StyleP& thisP) = 0;
/// Update scripted values of this style, return true if anything has changed. /// Update scripted values of this style, return nonzero if anything has changed.
/** The caller should tellListeners() */ /** The caller should tellListeners()
virtual bool update(Context&); * The result is a combination of StyleChange flags
*/
virtual int update(Context&);
/// Add the given dependency to the dependent_scripts list for the variables this style depends on /// Add the given dependency to the dependent_scripts list for the variables this style depends on
/** Only use for things that need invalidate() */ /** Only use for things that need invalidate() */
virtual void initDependencies(Context&, const Dependency&) const; virtual void initDependencies(Context&, const Dependency&) const;
...@@ -140,7 +142,8 @@ class Style : public IntrusivePtrVirtualBase { ...@@ -140,7 +142,8 @@ class Style : public IntrusivePtrVirtualBase {
/// Remove a StyleListener /// Remove a StyleListener
void removeListener(StyleListener*); void removeListener(StyleListener*);
/// Tell the StyleListeners that this style has changed /// Tell the StyleListeners that this style has changed
void tellListeners(bool already_prepared); /** change_info is a subset of StyleChange flags */
void tellListeners(int changes);
private: private:
DECLARE_REFLECTION_VIRTUAL(); DECLARE_REFLECTION_VIRTUAL();
...@@ -148,6 +151,15 @@ class Style : public IntrusivePtrVirtualBase { ...@@ -148,6 +151,15 @@ class Style : public IntrusivePtrVirtualBase {
vector<StyleListener*> listeners; vector<StyleListener*> listeners;
}; };
/// What changed in a style update?
enum StyleChange
{ CHANGE_NONE = 0x00 // nothing changed
, CHANGE_OTHER = 0x01 // some other change (note: result of casting from bool)
, CHANGE_DEFAULT = 0x02 // only the 'default' state is affected
, CHANGE_MASK = 0x04 // a mask image changed, must be reloaded
, CHANGE_ALREADY_PREPARED = 0x80 // hint that the change was the result of a content property change, viewers are already prepared
};
void init_object(const FieldP&, StyleP&); void init_object(const FieldP&, StyleP&);
inline const FieldP& get_key (const StyleP& s) { return s->fieldP; } inline const FieldP& get_key (const StyleP& s) { return s->fieldP; }
inline const String& get_key_name(const StyleP& s) { return s->fieldP->name; } inline const String& get_key_name(const StyleP& s) { return s->fieldP->name; }
...@@ -168,8 +180,8 @@ class StyleListener : public IntrusivePtrVirtualBase { ...@@ -168,8 +180,8 @@ class StyleListener : public IntrusivePtrVirtualBase {
virtual ~StyleListener(); virtual ~StyleListener();
/// Called when a (scripted) property of the viewed style has changed /// Called when a (scripted) property of the viewed style has changed
/** already_prepared indicates that this change happend after preparing text for content properties */ /** changes is a combination of StyleChange flags */
virtual void onStyleChange(bool already_prepared) {} virtual void onStyleChange(int changes) {}
protected: protected:
const StyleP styleP; ///< The style we are listening to const StyleP styleP; ///< The style we are listening to
}; };
......
...@@ -216,17 +216,17 @@ void ChoiceStyle::initImage() { ...@@ -216,17 +216,17 @@ void ChoiceStyle::initImage() {
script.addInstruction(I_RET); script.addInstruction(I_RET);
} }
bool ChoiceStyle::update(Context& ctx) { int ChoiceStyle::update(Context& ctx) {
// Don't update the choice images, leave that to invalidate() // Don't update the choice images, leave that to invalidate()
bool change = Style ::update(ctx) int change = Style ::update(ctx)
| font .update(ctx) | font .update(ctx) * CHANGE_OTHER
| mask_filename.update(ctx); | mask_filename.update(ctx) * CHANGE_MASK;
if (!choice_images_initialized) { if (!choice_images_initialized) {
// we only want to do this once because it is rather slow, other updates are handled by dependencies // we only want to do this once because it is rather slow, other updates are handled by dependencies
choice_images_initialized = true; choice_images_initialized = true;
FOR_EACH(ci, choice_images) { FOR_EACH(ci, choice_images) {
if (ci.second.update(ctx)) { if (ci.second.update(ctx)) {
change = true; change |= CHANGE_OTHER;
// TODO : remove this thumbnail // TODO : remove this thumbnail
} }
} }
...@@ -253,7 +253,7 @@ void ChoiceStyle::invalidate(Context& ctx) { ...@@ -253,7 +253,7 @@ void ChoiceStyle::invalidate(Context& ctx) {
thumbnails_status[i] = THUMB_CHANGED; thumbnails_status[i] = THUMB_CHANGED;
} }
} }
if (change) tellListeners(false); if (change) tellListeners(CHANGE_OTHER);
} }
void ChoiceStyle::loadMask(Package& pkg) { void ChoiceStyle::loadMask(Package& pkg) {
......
...@@ -145,7 +145,7 @@ class ChoiceStyle : public Style { ...@@ -145,7 +145,7 @@ class ChoiceStyle : public Style {
ChoicePopupStyle popup_style; ///< Style of popups/menus ChoicePopupStyle popup_style; ///< Style of popups/menus
ChoiceRenderStyle render_style; ///< Style of rendering ChoiceRenderStyle render_style; ///< Style of rendering
Font font; ///< Font for drawing text (when RENDER_TEXT) Font font; ///< Font for drawing text (when RENDER_TEXT)
ScriptableImage image; ///< Image to draw (when RENDER_IMAGE) CachedScriptableImage image; ///< Image to draw (when RENDER_IMAGE)
map<String,ScriptableImage> choice_images; ///< Images for the various choices (when RENDER_IMAGE) map<String,ScriptableImage> choice_images; ///< Images for the various choices (when RENDER_IMAGE)
bool choice_images_initialized; bool choice_images_initialized;
Scriptable<String> mask_filename; ///< Filename of an additional mask over the images Scriptable<String> mask_filename; ///< Filename of an additional mask over the images
...@@ -163,7 +163,7 @@ class ChoiceStyle : public Style { ...@@ -163,7 +163,7 @@ class ChoiceStyle : public Style {
/// Initialize image from choice_images /// Initialize image from choice_images
void initImage(); void initImage();
virtual bool update(Context&); virtual int update(Context&);
virtual void initDependencies(Context&, const Dependency&) const; virtual void initDependencies(Context&, const Dependency&) const;
virtual void invalidate(Context&); virtual void invalidate(Context&);
......
...@@ -67,9 +67,9 @@ IMPLEMENT_REFLECTION(ColorStyle) { ...@@ -67,9 +67,9 @@ IMPLEMENT_REFLECTION(ColorStyle) {
REFLECT_N("mask", mask_filename); REFLECT_N("mask", mask_filename);
} }
bool ColorStyle::update(Context& ctx) { int ColorStyle::update(Context& ctx) {
return Style ::update(ctx) return Style ::update(ctx)
| mask_filename.update(ctx); | mask_filename.update(ctx) * CHANGE_MASK;
} }
// ----------------------------------------------------------------------------- : ColorValue // ----------------------------------------------------------------------------- : ColorValue
......
...@@ -66,7 +66,7 @@ class ColorStyle : public Style { ...@@ -66,7 +66,7 @@ class ColorStyle : public Style {
double bottom_width; ///< Width of the colored region on the bottom side double bottom_width; ///< Width of the colored region on the bottom side
Scriptable<String> mask_filename; ///< Filename of an additional mask over the images Scriptable<String> mask_filename; ///< Filename of an additional mask over the images
virtual bool update(Context&); virtual int update(Context&);
private: private:
DECLARE_REFLECTION(); DECLARE_REFLECTION();
......
...@@ -25,14 +25,16 @@ IMPLEMENT_REFLECTION(ImageField) { ...@@ -25,14 +25,16 @@ IMPLEMENT_REFLECTION(ImageField) {
IMPLEMENT_REFLECTION(ImageStyle) { IMPLEMENT_REFLECTION(ImageStyle) {
REFLECT_BASE(Style); REFLECT_BASE(Style);
REFLECT(angle);
REFLECT_N("mask", mask_filename); REFLECT_N("mask", mask_filename);
REFLECT_N("default", default_image); REFLECT_N("default", default_image);
} }
bool ImageStyle::update(Context& ctx) { int ImageStyle::update(Context& ctx) {
return Style ::update(ctx) return Style ::update(ctx)
| mask_filename.update(ctx) | angle .update(ctx) * CHANGE_OTHER
| default_image.update(ctx); | mask_filename.update(ctx) * CHANGE_MASK
| default_image.update(ctx) * CHANGE_DEFAULT;
} }
// ----------------------------------------------------------------------------- : ImageValue // ----------------------------------------------------------------------------- : ImageValue
......
...@@ -38,10 +38,11 @@ class ImageStyle : public Style { ...@@ -38,10 +38,11 @@ class ImageStyle : public Style {
inline ImageStyle(const ImageFieldP& field) : Style(field) {} inline ImageStyle(const ImageFieldP& field) : Style(field) {}
DECLARE_STYLE_TYPE(Image); DECLARE_STYLE_TYPE(Image);
Scriptable<int> angle; ///< Rotation of images
Scriptable<String> mask_filename; ///< Filename for a mask image Scriptable<String> mask_filename; ///< Filename for a mask image
ScriptableImage default_image; ///< Placeholder ScriptableImage default_image; ///< Placeholder
virtual bool update(Context&); virtual int update(Context&);
private: private:
DECLARE_REFLECTION(); DECLARE_REFLECTION();
......
...@@ -40,9 +40,9 @@ InfoStyle::InfoStyle(const InfoFieldP& field) ...@@ -40,9 +40,9 @@ InfoStyle::InfoStyle(const InfoFieldP& field)
, background_color(255,255,255) , background_color(255,255,255)
{} {}
bool InfoStyle::update(Context& ctx) { int InfoStyle::update(Context& ctx) {
return Style ::update(ctx) return Style ::update(ctx)
| font .update(ctx); | font .update(ctx) * CHANGE_OTHER;
} }
void InfoStyle::initDependencies(Context& ctx, const Dependency& dep) const { void InfoStyle::initDependencies(Context& ctx, const Dependency& dep) const {
Style ::initDependencies(ctx, dep); Style ::initDependencies(ctx, dep);
......
...@@ -51,7 +51,7 @@ class InfoStyle : public Style { ...@@ -51,7 +51,7 @@ class InfoStyle : public Style {
double padding_top, padding_bottom; double padding_top, padding_bottom;
Color background_color; Color background_color;
virtual bool update(Context&); virtual int update(Context&);
virtual void initDependencies(Context&, const Dependency&) const; virtual void initDependencies(Context&, const Dependency&) const;
private: private:
......
...@@ -68,12 +68,12 @@ double TextStyle::getStretch() const { ...@@ -68,12 +68,12 @@ double TextStyle::getStretch() const {
return 1.0; return 1.0;
} }
bool TextStyle::update(Context& ctx) { int TextStyle::update(Context& ctx) {
return Style ::update(ctx) return Style ::update(ctx)
| font .update(ctx) | font .update(ctx) * CHANGE_OTHER
| symbol_font.update(ctx) | symbol_font.update(ctx) * CHANGE_OTHER
| alignment .update(ctx) | alignment .update(ctx) * CHANGE_OTHER
| angle .update(ctx); | angle .update(ctx) * CHANGE_OTHER;
} }
void TextStyle::initDependencies(Context& ctx, const Dependency& dep) const { void TextStyle::initDependencies(Context& ctx, const Dependency& dep) const {
Style ::initDependencies(ctx, dep); Style ::initDependencies(ctx, dep);
......
...@@ -75,7 +75,7 @@ class TextStyle : public Style { ...@@ -75,7 +75,7 @@ class TextStyle : public Style {
double content_width, content_height; ///< Size of the rendered text double content_width, content_height; ///< Size of the rendered text
int content_lines; ///< Number of rendered lines int content_lines; ///< Number of rendered lines
virtual bool update(Context&); virtual int update(Context&);
virtual void initDependencies(Context&, const Dependency&) const; virtual void initDependencies(Context&, const Dependency&) const;
virtual void checkContentDependencies(Context&, const Dependency&) const; virtual void checkContentDependencies(Context&, const Dependency&) const;
......
...@@ -110,6 +110,7 @@ void combine_image(Image& a, const Image& b, ImageCombine combine) { ...@@ -110,6 +110,7 @@ void combine_image(Image& a, const Image& b, ImageCombine combine) {
// Combine image data, by dispatching to combineImageDo // Combine image data, by dispatching to combineImageDo
switch(combine) { switch(combine) {
#define DISPATCH(comb) case comb: combine_image_do<comb>(a,b); return #define DISPATCH(comb) case comb: combine_image_do<comb>(a,b); return
case COMBINE_DEFAULT:
case COMBINE_NORMAL: a = b; return; // no need to do a per pixel operation case COMBINE_NORMAL: a = b; return; // no need to do a per pixel operation
DISPATCH(COMBINE_ADD); DISPATCH(COMBINE_ADD);
DISPATCH(COMBINE_SUBTRACT); DISPATCH(COMBINE_SUBTRACT);
...@@ -138,7 +139,7 @@ void combine_image(Image& a, const Image& b, ImageCombine combine) { ...@@ -138,7 +139,7 @@ void combine_image(Image& a, const Image& b, ImageCombine combine) {
} }
void draw_combine_image(DC& dc, UInt x, UInt y, const Image& img, ImageCombine combine) { void draw_combine_image(DC& dc, UInt x, UInt y, const Image& img, ImageCombine combine) {
if (combine == COMBINE_NORMAL) { if (combine <= COMBINE_NORMAL) {
dc.DrawBitmap(img, x, y); dc.DrawBitmap(img, x, y);
} else { } else {
// Capture the current image in the target rectangle // Capture the current image in the target rectangle
......
...@@ -68,6 +68,10 @@ Image conform_image(const Image& img, const GeneratedImage::Options& options) { ...@@ -68,6 +68,10 @@ Image conform_image(const Image& img, const GeneratedImage::Options& options) {
if (options.saturate) { if (options.saturate) {
saturate(image, 40); saturate(image, 40);
} }
// rotate?
if (options.angle != 0) {
image = rotate_image(image, options.angle);
}
return image; return image;
} }
......
...@@ -28,10 +28,13 @@ class GeneratedImage : public ScriptValue { ...@@ -28,10 +28,13 @@ class GeneratedImage : public ScriptValue {
/// Options for generating the image /// Options for generating the image
struct Options { struct Options {
Options(int width = 0, int height = 0, Package* package = nullptr, Package* local_package = nullptr, PreserveAspect preserve_aspect = ASPECT_STRETCH, bool saturate = false) Options(int width = 0, int height = 0, Package* package = nullptr, Package* local_package = nullptr, PreserveAspect preserve_aspect = ASPECT_STRETCH, bool saturate = false)
: width(width), height(height), preserve_aspect(preserve_aspect), saturate(saturate), package(package), local_package(local_package) : width(width), height(height), angle(0)
, preserve_aspect(preserve_aspect), saturate(saturate)
, package(package), local_package(local_package)
{} {}
int width, height; ///< Width to force the image to, or 0 to keep the width of the input int width, height; ///< Width to force the image to, or 0 to keep the width of the input
int angle; ///< Angle to rotate image by afterwards
PreserveAspect preserve_aspect; PreserveAspect preserve_aspect;
bool saturate; bool saturate;
Package* package; ///< Package to load images from Package* package; ///< Package to load images from
...@@ -43,7 +46,7 @@ class GeneratedImage : public ScriptValue { ...@@ -43,7 +46,7 @@ class GeneratedImage : public ScriptValue {
/// Generate the image /// Generate the image
virtual Image generate(const Options&) const = 0; virtual Image generate(const Options&) const = 0;
/// How must the image be combined with the background? /// How must the image be combined with the background?
virtual ImageCombine combine() const { return COMBINE_NORMAL; } virtual ImageCombine combine() const { return COMBINE_DEFAULT; }
/// Equality should mean that every pixel in the generated images is the same if the same options are used /// Equality should mean that every pixel in the generated images is the same if the same options are used
virtual bool operator == (const GeneratedImage& that) const = 0; virtual bool operator == (const GeneratedImage& that) const = 0;
inline bool operator != (const GeneratedImage& that) const { return !(*this == that); } inline bool operator != (const GeneratedImage& that) const { return !(*this == that); }
......
...@@ -56,6 +56,10 @@ extern const int text_scaling; ...@@ -56,6 +56,10 @@ extern const int text_scaling;
// ----------------------------------------------------------------------------- : Image rotation // ----------------------------------------------------------------------------- : Image rotation
/// Is an angle sideways (90 or 270 degrees)?
// Note: angle & 2 == 0 for angle in {0, 180} and != 0 for angle in {90, 270)
inline bool sideways(int angle) { return (angle & 2) != 0; }
/// Rotates an image counter clockwise /// Rotates an image counter clockwise
/// angle must be a multiple of 90, i.e. {0,90,180,270} /// angle must be a multiple of 90, i.e. {0,90,180,270}
Image rotate_image(const Image& image, int angle); Image rotate_image(const Image& image, int angle);
...@@ -85,7 +89,9 @@ void saturate(Image& image, int amount); ...@@ -85,7 +89,9 @@ void saturate(Image& image, int amount);
/// Ways in which images can be combined, similair to what Photoshop supports /// Ways in which images can be combined, similair to what Photoshop supports
enum ImageCombine enum ImageCombine
{ COMBINE_NORMAL { COMBINE_DEFAULT // normal combine, but with a low priority, i.e. "apply default instead of add" == "add"
// it is not representable in scripting/files, so should only be used internally
, COMBINE_NORMAL
, COMBINE_ADD , COMBINE_ADD
, COMBINE_SUBTRACT , COMBINE_SUBTRACT
, COMBINE_STAMP , COMBINE_STAMP
......
...@@ -163,7 +163,7 @@ void draw_resampled_text(DC& dc, const RealRect& rect, double stretch, int wc, i ...@@ -163,7 +163,7 @@ void draw_resampled_text(DC& dc, const RealRect& rect, double stretch, int wc, i
mdc.SelectObject(wxNullBitmap); mdc.SelectObject(wxNullBitmap);
Image img_large = buffer.ConvertToImage(); Image img_large = buffer.ConvertToImage();
// step 2. sample down // step 2. sample down
if ((angle & 2) == 0) w *= stretch; if (!sideways(angle)) w *= stretch;
else h *= stretch; else h *= stretch;
Image img_small(w, h, false); Image img_small(w, h, false);
fill_image(img_small, dc.GetTextForeground()); fill_image(img_small, dc.GetTextForeground());
......
...@@ -29,8 +29,7 @@ wxSize CardViewer::DoGetBestSize() const { ...@@ -29,8 +29,7 @@ wxSize CardViewer::DoGetBestSize() const {
if (!stylesheet) stylesheet = set->stylesheet; if (!stylesheet) stylesheet = set->stylesheet;
StyleSheetSettings& ss = settings.stylesheetSettingsFor(*stylesheet); StyleSheetSettings& ss = settings.stylesheetSettingsFor(*stylesheet);
wxSize size(stylesheet->card_width * ss.card_zoom(), stylesheet->card_height * ss.card_zoom()); wxSize size(stylesheet->card_width * ss.card_zoom(), stylesheet->card_height * ss.card_zoom());
bool sideways = (ss.card_angle() & 2) != 0; if (sideways(ss.card_angle())) swap(size.x, size.y);
if (sideways) swap(size.x, size.y);
return size + ws - cs; return size + ws - cs;
} }
return cs; return cs;
......
...@@ -56,7 +56,7 @@ Image ChoiceThumbnailRequest::generate() { ...@@ -56,7 +56,7 @@ Image ChoiceThumbnailRequest::generate() {
String name = cannocial_name_form(s.field().choices->choiceName(id)); String name = cannocial_name_form(s.field().choices->choiceName(id));
ScriptableImage& img = s.choice_images[name]; ScriptableImage& img = s.choice_images[name];
return img.isReady() return img.isReady()
? img.generate(GeneratedImage::Options(16,16, stylesheet.get(), viewer().viewer.getSet().get(), ASPECT_BORDER, true), false) ? img.generate(GeneratedImage::Options(16,16, stylesheet.get(), viewer().viewer.getSet().get(), ASPECT_BORDER, true))
: wxImage(); : wxImage();
} }
......
...@@ -1183,7 +1183,7 @@ bool TextValueEditor::wordListDropDown(const WordListPosP& wl) { ...@@ -1183,7 +1183,7 @@ bool TextValueEditor::wordListDropDown(const WordListPosP& wl) {
} else { } else {
drop_down.reset(new DropDownWordList(&editor(), false, *this, wl, wl->word_list)); drop_down.reset(new DropDownWordList(&editor(), false, *this, wl, wl->word_list));
} }
RealRect rect = wl->rect.move(style().left, style().top - 1, 0, 2); RealRect rect = style().getRotation().tr(wl->rect).move(0, -1, 0, 2);
drop_down->show(false, wxPoint(0,0), &rect); drop_down->show(false, wxPoint(0,0), &rect);
return true; return true;
} }
...@@ -15,30 +15,33 @@ ...@@ -15,30 +15,33 @@
bool ChoiceValueViewer::prepare(RotatedDC& dc) { bool ChoiceValueViewer::prepare(RotatedDC& dc) {
if (style().render_style & RENDER_IMAGE) { if (style().render_style & RENDER_IMAGE) {
style().initImage(); style().initImage();
ScriptableImage& img = style().image; CachedScriptableImage& img = style().image;
Context& ctx = viewer.getContext(); Context& ctx = viewer.getContext();
ctx.setVariable(_("input"), to_script(value().value())); ctx.setVariable(_("input"), to_script(value().value()));
img.update(ctx); // generate to determine the size
//generate if (img.update(ctx) && img.isReady()) {
if (img.isReady()) { GeneratedImage::Options img_options;
GeneratedImage::Options img_options(0,0, viewer.stylesheet.get(), &getSet()); getOptions(dc, img_options);
if (nativeLook()) { // Generate image/bitmap (whichever is available)
img_options.width = img_options.height = 16; // don't worry, we cache the image
img_options.preserve_aspect = ASPECT_BORDER; ImageCombine combine = style().combine;
} else if(style().render_style & RENDER_TEXT) { style().loadMask(*viewer.stylesheet);
// also drawing text, use original size Bitmap bitmap; Image image;
img.generateCached(img_options, &style().mask, &combine, &bitmap, &image);
int w, h;
if (bitmap.Ok()) {
w = bitmap.GetWidth();
h = bitmap.GetHeight();
} else { } else {
img_options.width = (int) dc.trX(style().width); assert(image.Ok());
img_options.height = (int) dc.trY(style().height); w = image.GetWidth();
img_options.preserve_aspect = (style().alignment & ALIGN_STRETCH) ? ASPECT_STRETCH : ASPECT_FIT; h = image.GetHeight();
} }
// don't worry we cache the image if (sideways(img_options.angle)) swap(w,h);
Image image = img.generate(img_options, true);
// store content properties // store content properties
if (style().content_width != image.GetWidth() || if (style().content_width != w || style().content_height != h) {
style().content_height != image.GetHeight()) { style().content_width = w;
style().content_width = image.GetWidth(); style().content_height = h;
style().content_height = image.GetHeight();
return true; return true;
} }
} }
...@@ -53,33 +56,29 @@ void ChoiceValueViewer::draw(RotatedDC& dc) { ...@@ -53,33 +56,29 @@ void ChoiceValueViewer::draw(RotatedDC& dc) {
double margin = 0; double margin = 0;
if (style().render_style & RENDER_IMAGE) { if (style().render_style & RENDER_IMAGE) {
// draw image // draw image
ScriptableImage& img = style().image; CachedScriptableImage& img = style().image;
if (img.isReady()) { if (img.isReady()) {
GeneratedImage::Options img_options(0,0, viewer.stylesheet.get(), &getSet()); GeneratedImage::Options img_options;
if (nativeLook()) { getOptions(dc, img_options);
img_options.width = img_options.height = 16; // Generate image/bitmap
img_options.preserve_aspect = ASPECT_BORDER; ImageCombine combine = style().combine;
} else if(style().render_style & RENDER_TEXT) {
// also drawing text, use original size
} else {
img_options.width = (int) dc.trX(style().width);
img_options.height = (int) dc.trY(style().height);
img_options.preserve_aspect = (style().alignment & ALIGN_STRETCH) ? ASPECT_STRETCH : ASPECT_FIT;
}
Image image = img.generate(img_options, true);
ImageCombine combine = img.combine();
// apply mask?
style().loadMask(*viewer.stylesheet); style().loadMask(*viewer.stylesheet);
if (style().mask.Ok()) { Bitmap bitmap; Image image;
set_alpha(image, style().mask); img.generateCached(img_options, &style().mask, &combine, &bitmap, &image);
if (bitmap.Ok()) {
// just draw it
dc.DrawPreRotatedBitmap(bitmap,
align_in_rect(style().alignment, dc.trInvNoNeg(RealSize(bitmap)), style().getRect())
);
margin = dc.trInv(RealSize(bitmap)).width + 1;
} else {
// use combine mode
dc.DrawPreRotatedImage(image,
align_in_rect(style().alignment, dc.trInvNoNeg(RealSize(image)), style().getRect()),
combine
);
margin = dc.trInv(RealSize(image)).width + 1;
} }
// draw
dc.DrawImage(image,
align_in_rect(style().alignment, dc.trInvS(RealSize(image.GetWidth(), image.GetHeight())), style().getRect()),
combine == COMBINE_NORMAL ? style().combine : combine,
style().angle
);
margin = dc.trInvS(image.GetWidth()) + 1;
} else if (nativeLook()) { } else if (nativeLook()) {
// always have the margin // always have the margin
margin = 17; margin = 17;
...@@ -99,6 +98,23 @@ void ChoiceValueViewer::draw(RotatedDC& dc) { ...@@ -99,6 +98,23 @@ void ChoiceValueViewer::draw(RotatedDC& dc) {
} }
} }
void ChoiceValueViewer::onStyleChange(bool already_prepared) { void ChoiceValueViewer::onStyleChange(int changes) {
if (!already_prepared) viewer.redraw(*this); if (changes & CHANGE_MASK) style().image.clearCache();
ValueViewer::onStyleChange(changes);
}
void ChoiceValueViewer::getOptions(Rotation& rot, GeneratedImage::Options& opts) {
opts.package = viewer.stylesheet.get();
opts.local_package = &getSet();
opts.angle = rot.trAngle(style().angle);
if (nativeLook()) {
opts.width = opts.height = 16;
opts.preserve_aspect = ASPECT_BORDER;
} else if(style().render_style & RENDER_TEXT) {
// also drawing text, use original size
} else {
opts.width = (int) rot.trX(style().width);
opts.height = (int) rot.trY(style().height);
opts.preserve_aspect = (style().alignment & ALIGN_STRETCH) ? ASPECT_STRETCH : ASPECT_FIT;
}
} }
...@@ -22,7 +22,9 @@ class ChoiceValueViewer : public ValueViewer { ...@@ -22,7 +22,9 @@ class ChoiceValueViewer : public ValueViewer {
virtual bool prepare(RotatedDC& dc); virtual bool prepare(RotatedDC& dc);
virtual void draw(RotatedDC& dc); virtual void draw(RotatedDC& dc);
virtual void onStyleChange(bool); virtual void onStyleChange(int);
private:
void getOptions(Rotation& rot, GeneratedImage::Options& opts);
}; };
// ----------------------------------------------------------------------------- : EOF // ----------------------------------------------------------------------------- : EOF
......
...@@ -86,9 +86,9 @@ bool ColorValueViewer::containsPoint(const RealPoint& p) const { ...@@ -86,9 +86,9 @@ bool ColorValueViewer::containsPoint(const RealPoint& p) const {
} }
} }
void ColorValueViewer::onStyleChange(bool already_prepared) { void ColorValueViewer::onStyleChange(int changes) {
alpha_mask = AlphaMaskP(); if (changes & CHANGE_MASK) alpha_mask = AlphaMaskP();
if (!already_prepared) viewer.redraw(*this); ValueViewer::onStyleChange(changes);
} }
void ColorValueViewer::loadMask(const Rotation& rot) const { void ColorValueViewer::loadMask(const Rotation& rot) const {
......
...@@ -25,7 +25,7 @@ class ColorValueViewer : public ValueViewer { ...@@ -25,7 +25,7 @@ class ColorValueViewer : public ValueViewer {
virtual void draw(RotatedDC& dc); virtual void draw(RotatedDC& dc);
virtual bool containsPoint(const RealPoint& p) const; virtual bool containsPoint(const RealPoint& p) const;
virtual void onStyleChange(bool); virtual void onStyleChange(int);
private: private:
mutable AlphaMaskP alpha_mask; mutable AlphaMaskP alpha_mask;
......
...@@ -16,22 +16,64 @@ ...@@ -16,22 +16,64 @@
void ImageValueViewer::draw(RotatedDC& dc) { void ImageValueViewer::draw(RotatedDC& dc) {
drawFieldBorder(dc); drawFieldBorder(dc);
// reset?
int w = (int)dc.trX(style().width), h = (int)dc.trY(style().height);
int a = dc.trAngle(style().angle);
if (bitmap.Ok() && (a != angle || bitmap.GetWidth() != w || bitmap.GetHeight() != h)) {
bitmap = Bitmap();
}
// try to load image // try to load image
if (!bitmap.Ok() && !value().filename.empty()) { if (!bitmap.Ok()) {
try { angle = a;
InputStreamP image_file = getSet().openIn(value().filename); is_default = false;
Image image; Image image;
if (image.LoadFile(*image_file)) { loadMask(dc);
image.Rescale((int)dc.trX(style().width), (int)dc.trY(style().height)); // load from file
// apply mask to image if (!value().filename.empty()) {
loadMask(dc); try {
if (alpha_mask) alpha_mask->setAlpha(image); InputStreamP image_file = getSet().openIn(value().filename);
bitmap = Bitmap(image); if (image.LoadFile(*image_file)) {
image.Rescale(w, h);
}
} catch (Error e) {
handle_error(e, false, false); // don't handle now, we are in onPaint
}
}
// nice placeholder
if (!image.Ok() && style().default_image.isReady()) {
image = style().default_image.generate(GeneratedImage::Options(w, h, viewer.stylesheet.get(), &getSet()));
is_default = true;
if (viewer.drawEditing()) {
bitmap = imagePlaceholder(dc, w, h, image, viewer.drawEditing());
if (alpha_mask || a) {
image = bitmap.ConvertToImage(); // we need to convert back to an image
} else {
image = Image();
}
} }
} catch (Error e) { }
handle_error(e, false, false); // don't handle now, we are in onPaint // checkerboard placeholder
if (!image.Ok() && !bitmap.Ok() && style().width > 40) {
// placeholder bitmap
bitmap = imagePlaceholder(dc, w, h, wxNullImage, viewer.drawEditing());
if (alpha_mask || a) {
// we need to convert back to an image
image = bitmap.ConvertToImage();
}
}
// done
if (image.Ok()) {
// apply mask and rotate
if (alpha_mask) alpha_mask->setAlpha(image);
image = rotate_image(image, angle);
bitmap = Bitmap(image);
} }
} }
// draw image, if any
if (bitmap.Ok()) {
dc.DrawPreRotatedBitmap(bitmap, style().getPos());
}
/*
// if there is no image, generate a placeholder // if there is no image, generate a placeholder
if (!bitmap.Ok()) { if (!bitmap.Ok()) {
UInt w = (UInt)dc.trX(style().width), h = (UInt)dc.trY(style().height); UInt w = (UInt)dc.trX(style().width), h = (UInt)dc.trY(style().height);
...@@ -56,6 +98,7 @@ void ImageValueViewer::draw(RotatedDC& dc) { ...@@ -56,6 +98,7 @@ void ImageValueViewer::draw(RotatedDC& dc) {
if (bitmap.Ok()) { if (bitmap.Ok()) {
dc.DrawBitmap(bitmap, style().getPos()); dc.DrawBitmap(bitmap, style().getPos());
} }
*/
} }
bool ImageValueViewer::containsPoint(const RealPoint& p) const { bool ImageValueViewer::containsPoint(const RealPoint& p) const {
...@@ -77,10 +120,13 @@ void ImageValueViewer::onValueChange() { ...@@ -77,10 +120,13 @@ void ImageValueViewer::onValueChange() {
bitmap = Bitmap(); bitmap = Bitmap();
} }
void ImageValueViewer::onStyleChange(bool already_prepared) { void ImageValueViewer::onStyleChange(int changes) {
bitmap = Bitmap(); if ((changes & CHANGE_MASK) ||
alpha_mask = AlphaMaskP(); // TODO: only reload whatever has changed ((changes & CHANGE_DEFAULT) && is_default)) {
if (!already_prepared) viewer.redraw(*this); bitmap = Bitmap();
}
if (changes & CHANGE_MASK) alpha_mask = AlphaMaskP();
ValueViewer::onStyleChange(changes);
} }
void ImageValueViewer::loadMask(const Rotation& rot) const { void ImageValueViewer::loadMask(const Rotation& rot) const {
......
...@@ -27,10 +27,12 @@ class ImageValueViewer : public ValueViewer { ...@@ -27,10 +27,12 @@ class ImageValueViewer : public ValueViewer {
virtual bool containsPoint(const RealPoint& p) const; virtual bool containsPoint(const RealPoint& p) const;
virtual void onValueChange(); virtual void onValueChange();
virtual void onStyleChange(bool); virtual void onStyleChange(int);
private: private:
Bitmap bitmap; Bitmap bitmap; ///< Cached bitmap
int angle; ///< Angle of cached bitmap
int is_default; ///< Is the default placeholder image used?
mutable AlphaMaskP alpha_mask; mutable AlphaMaskP alpha_mask;
void loadMask(const Rotation& rot) const; void loadMask(const Rotation& rot) const;
......
...@@ -44,36 +44,32 @@ void MultipleChoiceValueViewer::draw(RotatedDC& dc) { ...@@ -44,36 +44,32 @@ void MultipleChoiceValueViewer::draw(RotatedDC& dc) {
if (style().render_style & RENDER_IMAGE) { if (style().render_style & RENDER_IMAGE) {
// draw image // draw image
style().initImage(); style().initImage();
ScriptableImage& img = style().image; CachedScriptableImage& img = style().image;
Context& ctx = viewer.getContext(); Context& ctx = viewer.getContext();
ctx.setVariable(_("input"), to_script(value().value())); ctx.setVariable(_("input"), to_script(value().value()));
img.update(ctx); img.update(ctx);
if (img.isReady()) { if (img.isReady()) {
GeneratedImage::Options img_options(0,0, viewer.stylesheet.get(), &getSet()); GeneratedImage::Options img_options;
if (nativeLook()) { getOptions(dc, img_options);
img_options.width = img_options.height = 16; // Generate image/bitmap
img_options.preserve_aspect = ASPECT_BORDER; ImageCombine combine = style().combine;
} else if(style().render_style & RENDER_TEXT) {
// also drawing text, use original size
} else {
img_options.width = (int) dc.trX(style().width);
img_options.height = (int) dc.trY(style().height);
img_options.preserve_aspect = style().alignment == ALIGN_STRETCH ? ASPECT_STRETCH : ASPECT_FIT;
}
Image image = img.generate(img_options, true);
ImageCombine combine = img.combine();
// apply mask?
style().loadMask(*viewer.stylesheet); style().loadMask(*viewer.stylesheet);
if (style().mask.Ok()) { Bitmap bitmap; Image image;
set_alpha(image, style().mask); img.generateCached(img_options, &style().mask, &combine, &bitmap, &image);
if (bitmap.Ok()) {
// just draw it
dc.DrawPreRotatedBitmap(bitmap,
align_in_rect(style().alignment, dc.trInvNoNeg(RealSize(bitmap)), style().getRect())
);
margin = dc.trInv(RealSize(bitmap)).width + 1;
} else {
// use combine mode
dc.DrawPreRotatedImage(image,
align_in_rect(style().alignment, dc.trInvNoNeg(RealSize(image)), style().getRect()),
combine
);
margin = dc.trInv(RealSize(image)).width + 1;
} }
// draw
dc.DrawImage(image,
align_in_rect(style().alignment, RealSize(image.GetWidth(), image.GetHeight()), style().getRect()),
combine == COMBINE_NORMAL ? style().combine : combine,
style().angle
);
margin = dc.trInvS(image.GetWidth()) + 1;
} }
} }
if (style().render_style & RENDER_TEXT) { if (style().render_style & RENDER_TEXT) {
...@@ -96,10 +92,11 @@ void MultipleChoiceValueViewer::drawChoice(RotatedDC& dc, RealPoint& pos, const ...@@ -96,10 +92,11 @@ void MultipleChoiceValueViewer::drawChoice(RotatedDC& dc, RealPoint& pos, const
if (style().render_style & RENDER_IMAGE) { if (style().render_style & RENDER_IMAGE) {
map<String,ScriptableImage>::iterator it = style().choice_images.find(cannocial_name_form(choice)); map<String,ScriptableImage>::iterator it = style().choice_images.find(cannocial_name_form(choice));
if (it != style().choice_images.end() && it->second.isReady()) { if (it != style().choice_images.end() && it->second.isReady()) {
Image image = it->second.generate(GeneratedImage::Options(0,0, viewer.stylesheet.get(),&getSet()), true); // TODO: scaling, caching
Image image = it->second.generate(GeneratedImage::Options(0,0, viewer.stylesheet.get(),&getSet()));
ImageCombine combine = it->second.combine(); ImageCombine combine = it->second.combine();
// TODO : alignment? // TODO : alignment?
dc.DrawImage(image, pos + RealSize(size.width, 0), combine == COMBINE_NORMAL ? style().combine : combine); dc.DrawImage(image, pos + RealSize(size.width, 0), combine == COMBINE_DEFAULT ? style().combine : combine);
size = add_horizontal(size, dc.trInv(RealSize(image.GetWidth() + 1, image.GetHeight()))); size = add_horizontal(size, dc.trInv(RealSize(image.GetWidth() + 1, image.GetHeight())));
} }
} }
...@@ -114,3 +111,20 @@ void MultipleChoiceValueViewer::drawChoice(RotatedDC& dc, RealPoint& pos, const ...@@ -114,3 +111,20 @@ void MultipleChoiceValueViewer::drawChoice(RotatedDC& dc, RealPoint& pos, const
// next position // next position
pos = move_in_direction(style().direction, pos, size, style().spacing); pos = move_in_direction(style().direction, pos, size, style().spacing);
} }
// COPY from ChoiceValueViewer
void MultipleChoiceValueViewer::getOptions(Rotation& rot, GeneratedImage::Options& opts) {
opts.package = viewer.stylesheet.get();
opts.local_package = &getSet();
opts.angle = rot.trAngle(style().angle);
if (nativeLook()) {
opts.width = opts.height = 16;
opts.preserve_aspect = ASPECT_BORDER;
} else if(style().render_style & RENDER_TEXT) {
// also drawing text, use original size
} else {
opts.width = (int) rot.trX(style().width);
opts.height = (int) rot.trY(style().height);
opts.preserve_aspect = (style().alignment & ALIGN_STRETCH) ? ASPECT_STRETCH : ASPECT_FIT;
}
}
...@@ -25,6 +25,7 @@ class MultipleChoiceValueViewer : public ValueViewer { ...@@ -25,6 +25,7 @@ class MultipleChoiceValueViewer : public ValueViewer {
double item_height; ///< Height of a single item, or 0 if non uniform double item_height; ///< Height of a single item, or 0 if non uniform
private: private:
void drawChoice(RotatedDC& dc, RealPoint& pos, const String& choice, bool active = true); void drawChoice(RotatedDC& dc, RealPoint& pos, const String& choice, bool active = true);
void getOptions(Rotation& rot, GeneratedImage::Options& opts);
}; };
// ----------------------------------------------------------------------------- : EOF // ----------------------------------------------------------------------------- : EOF
......
...@@ -42,9 +42,9 @@ void TextValueViewer::onValueChange() { ...@@ -42,9 +42,9 @@ void TextValueViewer::onValueChange() {
v.reset(false); v.reset(false);
} }
void TextValueViewer::onStyleChange(bool already_prepared) { void TextValueViewer::onStyleChange(int changes) {
v.reset(true); v.reset(true);
if (!already_prepared) viewer.redraw(*this); ValueViewer::onStyleChange(changes);
} }
void TextValueViewer::onAction(const Action&, bool undone) { void TextValueViewer::onAction(const Action&, bool undone) {
......
...@@ -24,7 +24,7 @@ class TextValueViewer : public ValueViewer { ...@@ -24,7 +24,7 @@ class TextValueViewer : public ValueViewer {
virtual bool prepare(RotatedDC& dc); virtual bool prepare(RotatedDC& dc);
virtual void draw(RotatedDC& dc); virtual void draw(RotatedDC& dc);
virtual void onValueChange(); virtual void onValueChange();
virtual void onStyleChange(bool); virtual void onStyleChange(int);
virtual void onAction(const Action&, bool undone); virtual void onAction(const Action&, bool undone);
protected: protected:
......
...@@ -26,6 +26,7 @@ Set& ValueViewer::getSet() const { return *viewer.getSet(); } ...@@ -26,6 +26,7 @@ Set& ValueViewer::getSet() const { return *viewer.getSet(); }
void ValueViewer::setValue(const ValueP& value) { void ValueViewer::setValue(const ValueP& value) {
assert(value->fieldP == styleP->fieldP); // matching field assert(value->fieldP == styleP->fieldP); // matching field
if (valueP == value) return;
valueP = value; valueP = value;
onValueChange(); onValueChange();
} }
...@@ -55,6 +56,12 @@ bool ValueViewer::isCurrent() const { ...@@ -55,6 +56,12 @@ bool ValueViewer::isCurrent() const {
return viewer.focusedViewer() == this; return viewer.focusedViewer() == this;
} }
void ValueViewer::onStyleChange(int changes) {
if (!(changes & CHANGE_ALREADY_PREPARED)) {
viewer.redraw(*this);
}
}
// ----------------------------------------------------------------------------- : Type dispatch // ----------------------------------------------------------------------------- : Type dispatch
#define IMPLEMENT_MAKE_VIEWER(Type) \ #define IMPLEMENT_MAKE_VIEWER(Type) \
......
...@@ -57,8 +57,8 @@ class ValueViewer : public StyleListener { ...@@ -57,8 +57,8 @@ class ValueViewer : public StyleListener {
*/ */
virtual void onValueChange() {} virtual void onValueChange() {}
/// Called when a (scripted) property of the associated style has changed /// Called when a (scripted) property of the associated style has changed
/** If alread_prepared, should make sure the viewer stays in a state similair to that after prepare() */ /** Default: redraws the viewer if needed */
virtual void onStyleChange(bool already_prepared) {} virtual void onStyleChange(int changes);
/// Called when an action is performed on the associated value /// Called when an action is performed on the associated value
virtual void onAction(const Action&, bool undone) { onValueChange(); } virtual void onAction(const Action&, bool undone) { onValueChange(); }
......
...@@ -41,14 +41,7 @@ GeneratedImageP image_from_script(const ScriptValueP& value) { ...@@ -41,14 +41,7 @@ GeneratedImageP image_from_script(const ScriptValueP& value) {
// ----------------------------------------------------------------------------- : ScriptableImage // ----------------------------------------------------------------------------- : ScriptableImage
Image ScriptableImage::generate(const GeneratedImage::Options& options, bool cache) const { Image ScriptableImage::generate(const GeneratedImage::Options& options) const {
if (cached.Ok() && (cached.GetWidth() == options.width && cached.GetHeight() == options.height
|| (options.preserve_aspect == ASPECT_FIT && // only one dimension has to fit
(cached.GetWidth() == options.width || cached.GetHeight() == options.height)
))) {
// cached, so we are done
return cached;
}
// generate // generate
Image image; Image image;
if (isReady()) { if (isReady()) {
...@@ -64,14 +57,11 @@ Image ScriptableImage::generate(const GeneratedImage::Options& options, bool cac ...@@ -64,14 +57,11 @@ Image ScriptableImage::generate(const GeneratedImage::Options& options, bool cac
i.SetAlpha(0,0,0); i.SetAlpha(0,0,0);
image = i; image = i;
} }
image = conform_image(image, options); return conform_image(image, options);
// cache? and return
if (cache) cached = image;
return image;
} }
ImageCombine ScriptableImage::combine() const { ImageCombine ScriptableImage::combine() const {
if (!isReady()) return COMBINE_NORMAL; if (!isReady()) return COMBINE_DEFAULT;
return value->combine(); return value->combine();
} }
...@@ -80,7 +70,6 @@ bool ScriptableImage::update(Context& ctx) { ...@@ -80,7 +70,6 @@ bool ScriptableImage::update(Context& ctx) {
GeneratedImageP new_value = image_from_script(script.invoke(ctx)); GeneratedImageP new_value = image_from_script(script.invoke(ctx));
if (!new_value || !value || *new_value != *value) { if (!new_value || !value || *new_value != *value) {
value = new_value; value = new_value;
cached = Image();
return true; return true;
} else { } else {
return false; return false;
...@@ -118,3 +107,97 @@ template <> void Writer::handle(const ScriptableImage& s) { ...@@ -118,3 +107,97 @@ template <> void Writer::handle(const ScriptableImage& s) {
template <> void GetDefaultMember::handle(const ScriptableImage& s) { template <> void GetDefaultMember::handle(const ScriptableImage& s) {
handle(s.script.unparsed); handle(s.script.unparsed);
} }
// ----------------------------------------------------------------------------- : CachedScriptableImage
void CachedScriptableImage::generateCached(const GeneratedImage::Options& options,
Image* mask,
ImageCombine* combine, wxBitmap* bitmap, wxImage* image) {
// ready?
if (!isReady()) {
// error, return blank image
Image i(1,1);
i.InitAlpha();
i.SetAlpha(0,0,0);
*image = i;
return;
}
// find combine mode
ImageCombine combine_i = value->combine();
if (combine_i != COMBINE_DEFAULT) *combine = combine_i;
// desired size
int ow = options.width, oh = options.height;
if (sideways(options.angle)) swap(ow,oh);
// image or bitmap?
if (*combine <= COMBINE_NORMAL) {
// bitmap
if (cached_b.Ok() && options.angle == cached_angle) {
bool w_ok = cached_b.GetWidth() == ow,
h_ok = cached_b.GetHeight() == oh;
if ((w_ok && h_ok) || (options.preserve_aspect == ASPECT_FIT && (w_ok || h_ok))) { // only one dimension has to fit when fitting
// cached, we are done
*bitmap = cached_b;
return;
}
}
} else {
// image
if (cached_i.Ok()) {
bool w_ok = cached_i.GetWidth() == options.width,
h_ok = cached_i.GetHeight() == options.height;
if ((w_ok && h_ok) || (options.preserve_aspect == ASPECT_FIT && (w_ok || h_ok))) { // only one dimension has to fit when fitting
if (options.angle != cached_angle) {
// rotate cached image
cached_i = rotate_image(cached_i, options.angle - cached_angle + 360);
cached_angle = options.angle;
}
*image = cached_i;
return;
}
}
}
// generate
cached_i = generate(options);
cached_angle = options.angle;
if (mask && mask->Ok()) {
// apply mask
if (mask->GetWidth() == cached_i.GetWidth() && mask->GetHeight() == cached_i.GetHeight()) {
set_alpha(cached_i, *mask);
} else {
Image mask_scaled(cached_i.GetWidth(),cached_i.GetHeight(), false);
resample(mask,mask_scaled);
set_alpha(cached_i, mask_scaled);
}
}
if (*combine <= COMBINE_NORMAL) {
*bitmap = cached_b = Bitmap(cached_i);
cached_i = Image();
} else {
*image = cached_i;
}
}
bool CachedScriptableImage::update(Context& ctx) {
bool change = ScriptableImage::update(ctx);
if (change) {
clearCache();
}
return change;
}
void CachedScriptableImage::clearCache() {
cached_i = Image();
cached_b = Bitmap();
}
template <> void Reader::handle(CachedScriptableImage& s) {
handle((ScriptableImage&)s);
}
template <> void Writer::handle(const CachedScriptableImage& s) {
handle((const ScriptableImage&)s);
}
template <> void GetDefaultMember::handle(const CachedScriptableImage& s) {
handle((const ScriptableImage&)s);
}
...@@ -20,7 +20,6 @@ ...@@ -20,7 +20,6 @@
/// An image that can also be scripted /// An image that can also be scripted
/** Differs from Scriptable<Image> in that: /** Differs from Scriptable<Image> in that:
* - A script is always used * - A script is always used
* - Age is checked, chached images are used if possible
* - The image can be scaled * - The image can be scaled
*/ */
class ScriptableImage { class ScriptableImage {
...@@ -37,7 +36,7 @@ class ScriptableImage { ...@@ -37,7 +36,7 @@ class ScriptableImage {
inline bool isSet() const { return script || value; } inline bool isSet() const { return script || value; }
/// Generate an image. /// Generate an image.
Image generate(const GeneratedImage::Options& options, bool cache = false) const; Image generate(const GeneratedImage::Options& options) const;
/// How should images be combined with the background? /// How should images be combined with the background?
ImageCombine combine() const; ImageCombine combine() const;
...@@ -56,10 +55,9 @@ class ScriptableImage { ...@@ -56,10 +55,9 @@ class ScriptableImage {
/// Get access to the script, always returns a valid script /// Get access to the script, always returns a valid script
ScriptP getScriptP(); ScriptP getScriptP();
private: protected:
OptionalScript script; ///< The script, not really optional OptionalScript script; ///< The script, not really optional
GeneratedImageP value; ///< The image generator GeneratedImageP value; ///< The image generator
mutable Image cached; ///< The cached actual image
DECLARE_REFLECTION(); DECLARE_REFLECTION();
}; };
...@@ -70,5 +68,38 @@ inline ScriptValueP to_script(const ScriptableImage&) { return script_nil; } ...@@ -70,5 +68,38 @@ inline ScriptValueP to_script(const ScriptableImage&) { return script_nil; }
/// Convert a script value to a GeneratedImageP /// Convert a script value to a GeneratedImageP
GeneratedImageP image_from_script(const ScriptValueP& value); GeneratedImageP image_from_script(const ScriptValueP& value);
// ----------------------------------------------------------------------------- : CachedScriptableImage
/// A version of ScriptableImage that does caching
class CachedScriptableImage : public ScriptableImage {
public:
inline CachedScriptableImage() {}
inline CachedScriptableImage(const String& script) : ScriptableImage(script) {}
inline CachedScriptableImage(const GeneratedImageP& gen) : ScriptableImage(gen) {}
/// Generate an image, using caching if possible.
/** *combine should be set to the combine value of the style.
* It will be overwritten if the image specifies a non-default combine.
* After this call, either:
* - combine <= COMBINE_NORMAL && bitmap->Ok()
* - or combine > COMBINE_NORMAL && image->Ok()
* Optionally, an alpha mask is applied to the image.
*/
void generateCached(const GeneratedImage::Options& img_options,
Image* mask,
ImageCombine* combine, wxBitmap* bitmap, wxImage* image);
/// Update the script, returns true if the value has changed
bool update(Context& ctx);
/// Clears the cache
void clearCache();
private:
Image cached_i; ///< The cached image
Bitmap cached_b; ///< *or* the cached bitmap
int cached_angle;
};
// ----------------------------------------------------------------------------- : EOF // ----------------------------------------------------------------------------- : EOF
#endif #endif
...@@ -224,9 +224,9 @@ void SetScriptManager::updateStyles(Context& ctx, const IndexMap<FieldP,StyleP>& ...@@ -224,9 +224,9 @@ void SetScriptManager::updateStyles(Context& ctx, const IndexMap<FieldP,StyleP>&
FOR_EACH_CONST(s, styles) { FOR_EACH_CONST(s, styles) {
if (only_content_dependent && !s->content_dependent) continue; if (only_content_dependent && !s->content_dependent) continue;
try { try {
if (s->update(ctx)) { if (int change = s->update(ctx)) {
// style has changed, tell listeners // style has changed, tell listeners
s->tellListeners(only_content_dependent); s->tellListeners(change | (only_content_dependent ? CHANGE_ALREADY_PREPARED : 0) );
} }
} catch (const ScriptError& e) { } catch (const ScriptError& e) {
// NOTE: don't handle errors now, we are likely in an onPaint handler // NOTE: don't handle errors now, we are likely in an onPaint handler
......
...@@ -46,6 +46,10 @@ class RealSize { ...@@ -46,6 +46,10 @@ class RealSize {
inline explicit RealSize(const wxImage& img) inline explicit RealSize(const wxImage& img)
: width(img.GetWidth()), height(img.GetHeight()) : width(img.GetWidth()), height(img.GetHeight())
{} {}
/// size of a bitmap
inline explicit RealSize(const wxBitmap& img)
: width(img.GetWidth()), height(img.GetHeight())
{}
/// Negation of a size, negates both components /// Negation of a size, negates both components
inline RealSize operator - () const { inline RealSize operator - () const {
......
...@@ -160,9 +160,17 @@ void RotatedDC::DrawBitmap(const Bitmap& bitmap, const RealPoint& pos) { ...@@ -160,9 +160,17 @@ void RotatedDC::DrawBitmap(const Bitmap& bitmap, const RealPoint& pos) {
} }
void RotatedDC::DrawImage (const Image& image, const RealPoint& pos, ImageCombine combine, int angle) { void RotatedDC::DrawImage (const Image& image, const RealPoint& pos, ImageCombine combine, int angle) {
Image rotated = rotate_image(image, angle + this->angle); Image rotated = rotate_image(image, angle + this->angle);
wxRect r = trNoNegNoZoom(RealRect(pos, RealSize(image.GetWidth(), image.GetHeight()))); wxRect r = trNoNegNoZoom(RealRect(pos, RealSize(image)));
draw_combine_image(dc, r.x, r.y, rotated, combine); draw_combine_image(dc, r.x, r.y, rotated, combine);
} }
void RotatedDC::DrawPreRotatedBitmap(const Bitmap& bitmap, const RealPoint& pos) {
RealPoint p_ext = tr(pos) - RealSize(revX()?bitmap.GetWidth():0, revY()?bitmap.GetHeight():0);
dc.DrawBitmap(bitmap, (int) p_ext.x, (int) p_ext.y, true);
}
void RotatedDC::DrawPreRotatedImage (const Image& image, const RealPoint& pos, ImageCombine combine) {
RealPoint p_ext = tr(pos) - RealSize(revX()?image.GetWidth():0, revY()?image.GetHeight():0);
draw_combine_image(dc, p_ext.x, p_ext.y, image, combine);
}
void RotatedDC::DrawLine (const RealPoint& p1, const RealPoint& p2) { void RotatedDC::DrawLine (const RealPoint& p1, const RealPoint& p2) {
wxPoint p1_ext = tr(p1), p2_ext = tr(p2); wxPoint p1_ext = tr(p1), p2_ext = tr(p2);
......
...@@ -51,6 +51,9 @@ class Rotation { ...@@ -51,6 +51,9 @@ class Rotation {
inline double trX(double s) const { return s * zoomX; } inline double trX(double s) const { return s * zoomX; }
inline double trY(double s) const { return s * zoomY; } inline double trY(double s) const { return s * zoomY; }
/// Translate an angle
inline int trAngle(int a) { return (angle + a) % 360; }
/// Translate a single point /// Translate a single point
RealPoint tr(const RealPoint& p) const; RealPoint tr(const RealPoint& p) const;
/// Translate a single size, the result may be negative /// Translate a single size, the result may be negative
...@@ -94,8 +97,7 @@ class Rotation { ...@@ -94,8 +97,7 @@ class Rotation {
public: public:
/// Is the rotation sideways (90 or 270 degrees)? /// Is the rotation sideways (90 or 270 degrees)?
// Note: angle & 2 == 0 for angle in {0, 180} and != 0 for angle in {90, 270) inline bool sideways() const { return ::sideways(angle); }
inline bool sideways() const { return (angle & 2) != 0; }
protected: protected:
/// Is the x axis 'reversed' (after turning sideways)? /// Is the x axis 'reversed' (after turning sideways)?
...@@ -153,7 +155,11 @@ class RotatedDC : public Rotation { ...@@ -153,7 +155,11 @@ class RotatedDC : public Rotation {
/// Draw abitmap, it must already be zoomed! /// Draw abitmap, it must already be zoomed!
void DrawBitmap(const Bitmap& bitmap, const RealPoint& pos); void DrawBitmap(const Bitmap& bitmap, const RealPoint& pos);
/// Draw an image using the given combining mode, the image must already be zoomed! /// Draw an image using the given combining mode, the image must already be zoomed!
void DrawImage (const Image& image, const RealPoint& pos, ImageCombine combine = COMBINE_NORMAL, int angle = 0); void DrawImage (const Image& image, const RealPoint& pos, ImageCombine combine = COMBINE_DEFAULT, int angle = 0);
/// Draw a bitmap that is already zoomed and rotated
void DrawPreRotatedBitmap(const Bitmap& bitmap, const RealPoint& pos);
/// Draw an image that is already zoomed and rotated
void DrawPreRotatedImage(const Image& image, const RealPoint& pos, ImageCombine combine = COMBINE_DEFAULT);
void DrawLine (const RealPoint& p1, const RealPoint& p2); void DrawLine (const RealPoint& p1, const RealPoint& p2);
void DrawRectangle(const RealRect& r); void DrawRectangle(const RealRect& r);
void DrawRoundedRectangle(const RealRect& r, double radius); void DrawRoundedRectangle(const RealRect& r, double radius);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment