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&) {
throw InternalError(_("IndexMap contains nullptr StyleP the application should have crashed already"));
}
bool Style::update(Context& ctx) {
int Style::update(Context& ctx) {
bool changed =
left .update(ctx)
| width .update(ctx)
......@@ -196,8 +196,8 @@ void Style::removeListener(StyleListener* listener) {
listeners.end()
);
}
void Style::tellListeners(bool already_prepared) {
FOR_EACH(l, listeners) l->onStyleChange(already_prepared);
void Style::tellListeners(int changes) {
FOR_EACH(l, listeners) l->onStyleChange(changes);
}
StyleListener::StyleListener(const StyleP& style)
......
......@@ -119,9 +119,11 @@ class Style : public IntrusivePtrVirtualBase {
/** thisP is a smart pointer to this */
virtual ValueViewerP makeEditor(DataEditor& parent, const StyleP& thisP) = 0;
/// Update scripted values of this style, return true if anything has changed.
/** The caller should tellListeners() */
virtual bool update(Context&);
/// Update scripted values of this style, return nonzero if anything has changed.
/** The caller should tellListeners()
* 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
/** Only use for things that need invalidate() */
virtual void initDependencies(Context&, const Dependency&) const;
......@@ -140,7 +142,8 @@ class Style : public IntrusivePtrVirtualBase {
/// Remove a StyleListener
void removeListener(StyleListener*);
/// 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:
DECLARE_REFLECTION_VIRTUAL();
......@@ -148,6 +151,15 @@ class Style : public IntrusivePtrVirtualBase {
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&);
inline const FieldP& get_key (const StyleP& s) { return s->fieldP; }
inline const String& get_key_name(const StyleP& s) { return s->fieldP->name; }
......@@ -168,8 +180,8 @@ class StyleListener : public IntrusivePtrVirtualBase {
virtual ~StyleListener();
/// Called when a (scripted) property of the viewed style has changed
/** already_prepared indicates that this change happend after preparing text for content properties */
virtual void onStyleChange(bool already_prepared) {}
/** changes is a combination of StyleChange flags */
virtual void onStyleChange(int changes) {}
protected:
const StyleP styleP; ///< The style we are listening to
};
......
......@@ -216,17 +216,17 @@ void ChoiceStyle::initImage() {
script.addInstruction(I_RET);
}
bool ChoiceStyle::update(Context& ctx) {
int ChoiceStyle::update(Context& ctx) {
// Don't update the choice images, leave that to invalidate()
bool change = Style ::update(ctx)
| font .update(ctx)
| mask_filename.update(ctx);
int change = Style ::update(ctx)
| font .update(ctx) * CHANGE_OTHER
| mask_filename.update(ctx) * CHANGE_MASK;
if (!choice_images_initialized) {
// we only want to do this once because it is rather slow, other updates are handled by dependencies
choice_images_initialized = true;
FOR_EACH(ci, choice_images) {
if (ci.second.update(ctx)) {
change = true;
change |= CHANGE_OTHER;
// TODO : remove this thumbnail
}
}
......@@ -253,7 +253,7 @@ void ChoiceStyle::invalidate(Context& ctx) {
thumbnails_status[i] = THUMB_CHANGED;
}
}
if (change) tellListeners(false);
if (change) tellListeners(CHANGE_OTHER);
}
void ChoiceStyle::loadMask(Package& pkg) {
......
......@@ -145,7 +145,7 @@ class ChoiceStyle : public Style {
ChoicePopupStyle popup_style; ///< Style of popups/menus
ChoiceRenderStyle render_style; ///< Style of rendering
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)
bool choice_images_initialized;
Scriptable<String> mask_filename; ///< Filename of an additional mask over the images
......@@ -163,7 +163,7 @@ class ChoiceStyle : public Style {
/// Initialize image from choice_images
void initImage();
virtual bool update(Context&);
virtual int update(Context&);
virtual void initDependencies(Context&, const Dependency&) const;
virtual void invalidate(Context&);
......
......@@ -67,9 +67,9 @@ IMPLEMENT_REFLECTION(ColorStyle) {
REFLECT_N("mask", mask_filename);
}
bool ColorStyle::update(Context& ctx) {
int ColorStyle::update(Context& ctx) {
return Style ::update(ctx)
| mask_filename.update(ctx);
| mask_filename.update(ctx) * CHANGE_MASK;
}
// ----------------------------------------------------------------------------- : ColorValue
......
......@@ -66,7 +66,7 @@ class ColorStyle : public Style {
double bottom_width; ///< Width of the colored region on the bottom side
Scriptable<String> mask_filename; ///< Filename of an additional mask over the images
virtual bool update(Context&);
virtual int update(Context&);
private:
DECLARE_REFLECTION();
......
......@@ -25,14 +25,16 @@ IMPLEMENT_REFLECTION(ImageField) {
IMPLEMENT_REFLECTION(ImageStyle) {
REFLECT_BASE(Style);
REFLECT(angle);
REFLECT_N("mask", mask_filename);
REFLECT_N("default", default_image);
}
bool ImageStyle::update(Context& ctx) {
int ImageStyle::update(Context& ctx) {
return Style ::update(ctx)
| mask_filename.update(ctx)
| default_image.update(ctx);
| angle .update(ctx) * CHANGE_OTHER
| mask_filename.update(ctx) * CHANGE_MASK
| default_image.update(ctx) * CHANGE_DEFAULT;
}
// ----------------------------------------------------------------------------- : ImageValue
......
......@@ -38,10 +38,11 @@ class ImageStyle : public Style {
inline ImageStyle(const ImageFieldP& field) : Style(field) {}
DECLARE_STYLE_TYPE(Image);
Scriptable<int> angle; ///< Rotation of images
Scriptable<String> mask_filename; ///< Filename for a mask image
ScriptableImage default_image; ///< Placeholder
virtual bool update(Context&);
virtual int update(Context&);
private:
DECLARE_REFLECTION();
......
......@@ -40,9 +40,9 @@ InfoStyle::InfoStyle(const InfoFieldP& field)
, background_color(255,255,255)
{}
bool InfoStyle::update(Context& ctx) {
int InfoStyle::update(Context& ctx) {
return Style ::update(ctx)
| font .update(ctx);
| font .update(ctx) * CHANGE_OTHER;
}
void InfoStyle::initDependencies(Context& ctx, const Dependency& dep) const {
Style ::initDependencies(ctx, dep);
......
......@@ -51,7 +51,7 @@ class InfoStyle : public Style {
double padding_top, padding_bottom;
Color background_color;
virtual bool update(Context&);
virtual int update(Context&);
virtual void initDependencies(Context&, const Dependency&) const;
private:
......
......@@ -68,12 +68,12 @@ double TextStyle::getStretch() const {
return 1.0;
}
bool TextStyle::update(Context& ctx) {
int TextStyle::update(Context& ctx) {
return Style ::update(ctx)
| font .update(ctx)
| symbol_font.update(ctx)
| alignment .update(ctx)
| angle .update(ctx);
| font .update(ctx) * CHANGE_OTHER
| symbol_font.update(ctx) * CHANGE_OTHER
| alignment .update(ctx) * CHANGE_OTHER
| angle .update(ctx) * CHANGE_OTHER;
}
void TextStyle::initDependencies(Context& ctx, const Dependency& dep) const {
Style ::initDependencies(ctx, dep);
......
......@@ -75,7 +75,7 @@ class TextStyle : public Style {
double content_width, content_height; ///< Size of the rendered text
int content_lines; ///< Number of rendered lines
virtual bool update(Context&);
virtual int update(Context&);
virtual void initDependencies(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) {
// Combine image data, by dispatching to combineImageDo
switch(combine) {
#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
DISPATCH(COMBINE_ADD);
DISPATCH(COMBINE_SUBTRACT);
......@@ -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) {
if (combine == COMBINE_NORMAL) {
if (combine <= COMBINE_NORMAL) {
dc.DrawBitmap(img, x, y);
} else {
// Capture the current image in the target rectangle
......
......@@ -68,6 +68,10 @@ Image conform_image(const Image& img, const GeneratedImage::Options& options) {
if (options.saturate) {
saturate(image, 40);
}
// rotate?
if (options.angle != 0) {
image = rotate_image(image, options.angle);
}
return image;
}
......
......@@ -28,10 +28,13 @@ class GeneratedImage : public ScriptValue {
/// Options for generating the image
struct Options {
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 angle; ///< Angle to rotate image by afterwards
PreserveAspect preserve_aspect;
bool saturate;
Package* package; ///< Package to load images from
......@@ -43,7 +46,7 @@ class GeneratedImage : public ScriptValue {
/// Generate the image
virtual Image generate(const Options&) const = 0;
/// 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
virtual bool operator == (const GeneratedImage& that) const = 0;
inline bool operator != (const GeneratedImage& that) const { return !(*this == that); }
......
......@@ -56,6 +56,10 @@ extern const int text_scaling;
// ----------------------------------------------------------------------------- : 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
/// angle must be a multiple of 90, i.e. {0,90,180,270}
Image rotate_image(const Image& image, int angle);
......@@ -85,7 +89,9 @@ void saturate(Image& image, int amount);
/// Ways in which images can be combined, similair to what Photoshop supports
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_SUBTRACT
, COMBINE_STAMP
......
......@@ -163,7 +163,7 @@ void draw_resampled_text(DC& dc, const RealRect& rect, double stretch, int wc, i
mdc.SelectObject(wxNullBitmap);
Image img_large = buffer.ConvertToImage();
// step 2. sample down
if ((angle & 2) == 0) w *= stretch;
if (!sideways(angle)) w *= stretch;
else h *= stretch;
Image img_small(w, h, false);
fill_image(img_small, dc.GetTextForeground());
......
......@@ -29,8 +29,7 @@ wxSize CardViewer::DoGetBestSize() const {
if (!stylesheet) stylesheet = set->stylesheet;
StyleSheetSettings& ss = settings.stylesheetSettingsFor(*stylesheet);
wxSize size(stylesheet->card_width * ss.card_zoom(), stylesheet->card_height * ss.card_zoom());
bool sideways = (ss.card_angle() & 2) != 0;
if (sideways) swap(size.x, size.y);
if (sideways(ss.card_angle())) swap(size.x, size.y);
return size + ws - cs;
}
return cs;
......
......@@ -56,7 +56,7 @@ Image ChoiceThumbnailRequest::generate() {
String name = cannocial_name_form(s.field().choices->choiceName(id));
ScriptableImage& img = s.choice_images[name];
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();
}
......
......@@ -1183,7 +1183,7 @@ bool TextValueEditor::wordListDropDown(const WordListPosP& wl) {
} else {
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);
return true;
}
......@@ -15,30 +15,33 @@
bool ChoiceValueViewer::prepare(RotatedDC& dc) {
if (style().render_style & RENDER_IMAGE) {
style().initImage();
ScriptableImage& img = style().image;
CachedScriptableImage& img = style().image;
Context& ctx = viewer.getContext();
ctx.setVariable(_("input"), to_script(value().value()));
img.update(ctx);
//generate
if (img.isReady()) {
GeneratedImage::Options img_options(0,0, viewer.stylesheet.get(), &getSet());
if (nativeLook()) {
img_options.width = img_options.height = 16;
img_options.preserve_aspect = ASPECT_BORDER;
} else if(style().render_style & RENDER_TEXT) {
// also drawing text, use original size
// generate to determine the size
if (img.update(ctx) && img.isReady()) {
GeneratedImage::Options img_options;
getOptions(dc, img_options);
// Generate image/bitmap (whichever is available)
// don't worry, we cache the image
ImageCombine combine = style().combine;
style().loadMask(*viewer.stylesheet);
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 {
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;
assert(image.Ok());
w = image.GetWidth();
h = image.GetHeight();
}
// don't worry we cache the image
Image image = img.generate(img_options, true);
if (sideways(img_options.angle)) swap(w,h);
// store content properties
if (style().content_width != image.GetWidth() ||
style().content_height != image.GetHeight()) {
style().content_width = image.GetWidth();
style().content_height = image.GetHeight();
if (style().content_width != w || style().content_height != h) {
style().content_width = w;
style().content_height = h;
return true;
}
}
......@@ -53,33 +56,29 @@ void ChoiceValueViewer::draw(RotatedDC& dc) {
double margin = 0;
if (style().render_style & RENDER_IMAGE) {
// draw image
ScriptableImage& img = style().image;
CachedScriptableImage& img = style().image;
if (img.isReady()) {
GeneratedImage::Options img_options(0,0, viewer.stylesheet.get(), &getSet());
if (nativeLook()) {
img_options.width = img_options.height = 16;
img_options.preserve_aspect = ASPECT_BORDER;
} 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?
GeneratedImage::Options img_options;
getOptions(dc, img_options);
// Generate image/bitmap
ImageCombine combine = style().combine;
style().loadMask(*viewer.stylesheet);
if (style().mask.Ok()) {
set_alpha(image, style().mask);
Bitmap bitmap; Image image;
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()) {
// always have the margin
margin = 17;
......@@ -99,6 +98,23 @@ void ChoiceValueViewer::draw(RotatedDC& dc) {
}
}
void ChoiceValueViewer::onStyleChange(bool already_prepared) {
if (!already_prepared) viewer.redraw(*this);
void ChoiceValueViewer::onStyleChange(int changes) {
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 {
virtual bool prepare(RotatedDC& dc);
virtual void draw(RotatedDC& dc);
virtual void onStyleChange(bool);
virtual void onStyleChange(int);
private:
void getOptions(Rotation& rot, GeneratedImage::Options& opts);
};
// ----------------------------------------------------------------------------- : EOF
......
......@@ -86,9 +86,9 @@ bool ColorValueViewer::containsPoint(const RealPoint& p) const {
}
}
void ColorValueViewer::onStyleChange(bool already_prepared) {
alpha_mask = AlphaMaskP();
if (!already_prepared) viewer.redraw(*this);
void ColorValueViewer::onStyleChange(int changes) {
if (changes & CHANGE_MASK) alpha_mask = AlphaMaskP();
ValueViewer::onStyleChange(changes);
}
void ColorValueViewer::loadMask(const Rotation& rot) const {
......
......@@ -25,7 +25,7 @@ class ColorValueViewer : public ValueViewer {
virtual void draw(RotatedDC& dc);
virtual bool containsPoint(const RealPoint& p) const;
virtual void onStyleChange(bool);
virtual void onStyleChange(int);
private:
mutable AlphaMaskP alpha_mask;
......
......@@ -16,22 +16,64 @@
void ImageValueViewer::draw(RotatedDC& 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
if (!bitmap.Ok() && !value().filename.empty()) {
try {
InputStreamP image_file = getSet().openIn(value().filename);
Image image;
if (image.LoadFile(*image_file)) {
image.Rescale((int)dc.trX(style().width), (int)dc.trY(style().height));
// apply mask to image
loadMask(dc);
if (alpha_mask) alpha_mask->setAlpha(image);
bitmap = Bitmap(image);
if (!bitmap.Ok()) {
angle = a;
is_default = false;
Image image;
loadMask(dc);
// load from file
if (!value().filename.empty()) {
try {
InputStreamP image_file = getSet().openIn(value().filename);
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 (!bitmap.Ok()) {
UInt w = (UInt)dc.trX(style().width), h = (UInt)dc.trY(style().height);
......@@ -56,6 +98,7 @@ void ImageValueViewer::draw(RotatedDC& dc) {
if (bitmap.Ok()) {
dc.DrawBitmap(bitmap, style().getPos());
}
*/
}
bool ImageValueViewer::containsPoint(const RealPoint& p) const {
......@@ -77,10 +120,13 @@ void ImageValueViewer::onValueChange() {
bitmap = Bitmap();
}
void ImageValueViewer::onStyleChange(bool already_prepared) {
bitmap = Bitmap();
alpha_mask = AlphaMaskP(); // TODO: only reload whatever has changed
if (!already_prepared) viewer.redraw(*this);
void ImageValueViewer::onStyleChange(int changes) {
if ((changes & CHANGE_MASK) ||
((changes & CHANGE_DEFAULT) && is_default)) {
bitmap = Bitmap();
}
if (changes & CHANGE_MASK) alpha_mask = AlphaMaskP();
ValueViewer::onStyleChange(changes);
}
void ImageValueViewer::loadMask(const Rotation& rot) const {
......
......@@ -27,10 +27,12 @@ class ImageValueViewer : public ValueViewer {
virtual bool containsPoint(const RealPoint& p) const;
virtual void onValueChange();
virtual void onStyleChange(bool);
virtual void onStyleChange(int);
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;
void loadMask(const Rotation& rot) const;
......
......@@ -44,36 +44,32 @@ void MultipleChoiceValueViewer::draw(RotatedDC& dc) {
if (style().render_style & RENDER_IMAGE) {
// draw image
style().initImage();
ScriptableImage& img = style().image;
CachedScriptableImage& img = style().image;
Context& ctx = viewer.getContext();
ctx.setVariable(_("input"), to_script(value().value()));
img.update(ctx);
if (img.isReady()) {
GeneratedImage::Options img_options(0,0, viewer.stylesheet.get(), &getSet());
if (nativeLook()) {
img_options.width = img_options.height = 16;
img_options.preserve_aspect = ASPECT_BORDER;
} 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?
GeneratedImage::Options img_options;
getOptions(dc, img_options);
// Generate image/bitmap
ImageCombine combine = style().combine;
style().loadMask(*viewer.stylesheet);
if (style().mask.Ok()) {
set_alpha(image, style().mask);
Bitmap bitmap; Image image;
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) {
......@@ -96,10 +92,11 @@ void MultipleChoiceValueViewer::drawChoice(RotatedDC& dc, RealPoint& pos, const
if (style().render_style & RENDER_IMAGE) {
map<String,ScriptableImage>::iterator it = style().choice_images.find(cannocial_name_form(choice));
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();
// 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())));
}
}
......@@ -114,3 +111,20 @@ void MultipleChoiceValueViewer::drawChoice(RotatedDC& dc, RealPoint& pos, const
// next position
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 {
double item_height; ///< Height of a single item, or 0 if non uniform
private:
void drawChoice(RotatedDC& dc, RealPoint& pos, const String& choice, bool active = true);
void getOptions(Rotation& rot, GeneratedImage::Options& opts);
};
// ----------------------------------------------------------------------------- : EOF
......
......@@ -42,9 +42,9 @@ void TextValueViewer::onValueChange() {
v.reset(false);
}
void TextValueViewer::onStyleChange(bool already_prepared) {
void TextValueViewer::onStyleChange(int changes) {
v.reset(true);
if (!already_prepared) viewer.redraw(*this);
ValueViewer::onStyleChange(changes);
}
void TextValueViewer::onAction(const Action&, bool undone) {
......
......@@ -24,7 +24,7 @@ class TextValueViewer : public ValueViewer {
virtual bool prepare(RotatedDC& dc);
virtual void draw(RotatedDC& dc);
virtual void onValueChange();
virtual void onStyleChange(bool);
virtual void onStyleChange(int);
virtual void onAction(const Action&, bool undone);
protected:
......
......@@ -26,6 +26,7 @@ Set& ValueViewer::getSet() const { return *viewer.getSet(); }
void ValueViewer::setValue(const ValueP& value) {
assert(value->fieldP == styleP->fieldP); // matching field
if (valueP == value) return;
valueP = value;
onValueChange();
}
......@@ -55,6 +56,12 @@ bool ValueViewer::isCurrent() const {
return viewer.focusedViewer() == this;
}
void ValueViewer::onStyleChange(int changes) {
if (!(changes & CHANGE_ALREADY_PREPARED)) {
viewer.redraw(*this);
}
}
// ----------------------------------------------------------------------------- : Type dispatch
#define IMPLEMENT_MAKE_VIEWER(Type) \
......
......@@ -57,8 +57,8 @@ class ValueViewer : public StyleListener {
*/
virtual void onValueChange() {}
/// 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() */
virtual void onStyleChange(bool already_prepared) {}
/** Default: redraws the viewer if needed */
virtual void onStyleChange(int changes);
/// Called when an action is performed on the associated value
virtual void onAction(const Action&, bool undone) { onValueChange(); }
......
......@@ -41,14 +41,7 @@ GeneratedImageP image_from_script(const ScriptValueP& value) {
// ----------------------------------------------------------------------------- : ScriptableImage
Image ScriptableImage::generate(const GeneratedImage::Options& options, bool cache) 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;
}
Image ScriptableImage::generate(const GeneratedImage::Options& options) const {
// generate
Image image;
if (isReady()) {
......@@ -64,14 +57,11 @@ Image ScriptableImage::generate(const GeneratedImage::Options& options, bool cac
i.SetAlpha(0,0,0);
image = i;
}
image = conform_image(image, options);
// cache? and return
if (cache) cached = image;
return image;
return conform_image(image, options);
}
ImageCombine ScriptableImage::combine() const {
if (!isReady()) return COMBINE_NORMAL;
if (!isReady()) return COMBINE_DEFAULT;
return value->combine();
}
......@@ -80,7 +70,6 @@ bool ScriptableImage::update(Context& ctx) {
GeneratedImageP new_value = image_from_script(script.invoke(ctx));
if (!new_value || !value || *new_value != *value) {
value = new_value;
cached = Image();
return true;
} else {
return false;
......@@ -118,3 +107,97 @@ template <> void Writer::handle(const ScriptableImage& s) {
template <> void GetDefaultMember::handle(const ScriptableImage& s) {
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 @@
/// An image that can also be scripted
/** Differs from Scriptable<Image> in that:
* - A script is always used
* - Age is checked, chached images are used if possible
* - The image can be scaled
*/
class ScriptableImage {
......@@ -37,7 +36,7 @@ class ScriptableImage {
inline bool isSet() const { return script || value; }
/// 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?
ImageCombine combine() const;
......@@ -56,10 +55,9 @@ class ScriptableImage {
/// Get access to the script, always returns a valid script
ScriptP getScriptP();
private:
protected:
OptionalScript script; ///< The script, not really optional
GeneratedImageP value; ///< The image generator
mutable Image cached; ///< The cached actual image
DECLARE_REFLECTION();
};
......@@ -70,5 +68,38 @@ inline ScriptValueP to_script(const ScriptableImage&) { return script_nil; }
/// Convert a script value to a GeneratedImageP
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
#endif
......@@ -224,9 +224,9 @@ void SetScriptManager::updateStyles(Context& ctx, const IndexMap<FieldP,StyleP>&
FOR_EACH_CONST(s, styles) {
if (only_content_dependent && !s->content_dependent) continue;
try {
if (s->update(ctx)) {
if (int change = s->update(ctx)) {
// style has changed, tell listeners
s->tellListeners(only_content_dependent);
s->tellListeners(change | (only_content_dependent ? CHANGE_ALREADY_PREPARED : 0) );
}
} catch (const ScriptError& e) {
// NOTE: don't handle errors now, we are likely in an onPaint handler
......
......@@ -46,6 +46,10 @@ class RealSize {
inline explicit RealSize(const wxImage& img)
: 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
inline RealSize operator - () const {
......
......@@ -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) {
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);
}
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) {
wxPoint p1_ext = tr(p1), p2_ext = tr(p2);
......
......@@ -51,6 +51,9 @@ class Rotation {
inline double trX(double s) const { return s * zoomX; }
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
RealPoint tr(const RealPoint& p) const;
/// Translate a single size, the result may be negative
......@@ -94,8 +97,7 @@ class Rotation {
public:
/// 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 (angle & 2) != 0; }
inline bool sideways() const { return ::sideways(angle); }
protected:
/// Is the x axis 'reversed' (after turning sideways)?
......@@ -153,7 +155,11 @@ class RotatedDC : public Rotation {
/// Draw abitmap, it must already be zoomed!
void DrawBitmap(const Bitmap& bitmap, const RealPoint& pos);
/// 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 DrawRectangle(const RealRect& r);
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