Commit 7fa3d02f authored by twanvl's avatar twanvl

New class CachedScriptableMask: like CachedScriptableImage, only containing an...

New class CachedScriptableMask: like CachedScriptableImage, only containing an AlphaMask instead of an Image/Bitmap.
Use CachedScriptableMask for all masks.

TODO: This introduces some duplicate code in ValueViewers that could be fixed by moving mask to the Style base class.
parent 1098ce52
......@@ -214,11 +214,8 @@ void ChoiceStyle::initImage() {
int ChoiceStyle::update(Context& ctx) {
// Don't update the choice images, leave that to invalidate()
int change = Style ::update(ctx)
| font .update(ctx) * CHANGE_OTHER;
if (mask_filename.update(ctx)) {
change |= CHANGE_MASK;
mask = Image();
}
| font .update(ctx) * CHANGE_OTHER
| mask .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;
......@@ -255,13 +252,6 @@ void ChoiceStyle::invalidate() {
tellListeners(CHANGE_OTHER);
}
void ChoiceStyle::loadMask(Package& pkg) {
if (mask.Ok() || mask_filename().empty()) return;
// load file
InputStreamP image_file = pkg.openIn(mask_filename);
mask.LoadFile(*image_file);
}
IMPLEMENT_REFLECTION_ENUM(ChoicePopupStyle) {
VALUE_N("dropdown", POPUP_DROPDOWN);
VALUE_N("menu", POPUP_MENU);
......@@ -293,7 +283,7 @@ IMPLEMENT_REFLECTION(ChoiceStyle) {
REFLECT_BASE(Style);
REFLECT(popup_style);
REFLECT(render_style);
REFLECT_N("mask",mask_filename);
REFLECT(mask);
REFLECT(combine);
REFLECT(alignment);
REFLECT(font);
......
......@@ -143,20 +143,17 @@ 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)
CachedScriptableImage 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
CachedScriptableMask mask; ///< Mask image
ImageCombine combine; ///< Combining mode for drawing the images
Alignment alignment; ///< Alignment of images
Image mask; ///< The actual mask image
wxImageList* thumbnails; ///< Thumbnails for the choices
vector<ThumbnailStatus> thumbnails_status; ///< Which thumbnails are up to date?
vector<ThumbnailStatus> thumbnails_status; ///< Which thumbnails are up to date?
// information from image rendering
double content_width, content_height; ///< Size of the rendered image/text
/// Load the mask image, if it's not already done
void loadMask(Package& pkg);
/// Initialize image from choice_images
void initImage();
......
......@@ -62,13 +62,13 @@ IMPLEMENT_REFLECTION(ColorStyle) {
REFLECT(right_width);
REFLECT(top_width);
REFLECT(bottom_width);
REFLECT_N("mask", mask_filename);
REFLECT(mask);
REFLECT(combine);
}
int ColorStyle::update(Context& ctx) {
return Style ::update(ctx)
| mask_filename.update(ctx) * CHANGE_MASK;
return Style::update(ctx)
| mask.update(ctx) * CHANGE_MASK;
}
// ----------------------------------------------------------------------------- : ColorValue
......
......@@ -13,6 +13,7 @@
#include <util/defaultable.hpp>
#include <data/field.hpp>
#include <script/scriptable.hpp>
#include <script/image.hpp>
// ----------------------------------------------------------------------------- : ColorField
......@@ -56,13 +57,13 @@ class ColorStyle : public Style {
ColorStyle(const ColorFieldP& field);
DECLARE_STYLE_TYPE(Color);
double radius; ///< Radius of round corners
double left_width; ///< Width of the colored region on the left side
double right_width; ///< Width of the colored region on the right side
double top_width; ///< Width of the colored region on the top 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
ImageCombine combine; ///< How to combine image with the background
double radius; ///< Radius of round corners
double left_width; ///< Width of the colored region on the left side
double right_width; ///< Width of the colored region on the right side
double top_width; ///< Width of the colored region on the top side
double bottom_width; ///< Width of the colored region on the bottom side
CachedScriptableMask mask; ///< Mask image
ImageCombine combine; ///< How to combine image with the background
virtual int update(Context&);
};
......
......@@ -23,13 +23,13 @@ IMPLEMENT_REFLECTION(ImageField) {
IMPLEMENT_REFLECTION(ImageStyle) {
REFLECT_BASE(Style);
REFLECT_N("mask", mask_filename);
REFLECT(mask);
REFLECT_N("default", default_image);
}
int ImageStyle::update(Context& ctx) {
return Style ::update(ctx)
| mask_filename.update(ctx) * CHANGE_MASK
| mask .update(ctx) * CHANGE_MASK
| default_image.update(ctx) * CHANGE_DEFAULT;
}
......
......@@ -35,8 +35,8 @@ class ImageStyle : public Style {
inline ImageStyle(const ImageFieldP& field) : Style(field) {}
DECLARE_STYLE_TYPE(Image);
Scriptable<String> mask_filename; ///< Filename for a mask image
ScriptableImage default_image; ///< Placeholder
CachedScriptableMask mask; ///< Mask image
ScriptableImage default_image; ///< Placeholder
virtual int update(Context&);
};
......
......@@ -35,7 +35,7 @@ class GeneratedImage : public ScriptValue {
mutable int width, height; ///< Width to force the image to, or 0 to keep the width of the input
///< In that case, width and height will be later set to the actual size
double zoom; ///< Zoom factor to use, when witdth=height=0
double zoom; ///< Zoom factor to use, when width=height=0
int angle; ///< Angle to rotate image by afterwards
PreserveAspect preserve_aspect;
bool saturate;
......@@ -55,9 +55,10 @@ class GeneratedImage : public ScriptValue {
/// Can this image be generated safely from another thread?
virtual bool threadSafe() const { return true; }
/// Is this image specific to the set (the local_package)?
virtual bool local() const { return false; }
/// Is this image blank?
virtual bool isBlank() const { return false; }
virtual ScriptType type() const;
virtual String typeName() const;
......@@ -88,6 +89,7 @@ class BlankImage : public GeneratedImage {
public:
virtual Image generate(const Options&) const;
virtual bool operator == (const GeneratedImage& that) const;
virtual bool isBlank() const { return true; }
// Why is this not thread safe? What is GTK smoking?
#ifdef __WXGTK__
......
......@@ -167,8 +167,9 @@ class AlphaMask : public IntrusivePtrBase<AlphaMask> {
/// Apply the alpha mask to a bitmap
void setAlpha(Bitmap& b) const;
/// Is the given location fully transparent?
bool isTransparent(int x, int y) const;
/// Is the given location opaque (not fully transparent)? when the mask were stretched to size
bool isOpaque(const RealPoint& p, const RealSize& size) const;
bool isOpaque(int x, int y) const;
/// Determine a convex hull polygon *around* the mask
void convexHull(vector<wxPoint>& points) const;
......@@ -178,9 +179,9 @@ class AlphaMask : public IntrusivePtrBase<AlphaMask> {
/// Returns the start of a row, when the mask were stretched to size
/** This is: the x coordinate of the first non-transparent pixel */
double rowLeft (double y, RealSize size) const;
double rowLeft (double y, const RealSize& size) const;
/// Returns the end of a row, when the mask were stretched to size
double rowRight(double y, RealSize size) const;
double rowRight(double y, const RealSize& size) const;
/// Does this mask have the given size?
inline bool hasSize(const wxSize& compare_size) const { return size == compare_size; }
......
......@@ -17,9 +17,13 @@ AlphaMask::AlphaMask(const Image& img) : alpha(nullptr), lefts(nullptr), rights(
load(img);
}
AlphaMask::~AlphaMask() {
delete[] alpha;
delete[] lefts;
delete[] rights;
clear();
}
void AlphaMask::clear() {
delete[] alpha; alpha = nullptr;
delete[] lefts; lefts = nullptr;
delete[] rights; rights = nullptr;
}
void AlphaMask::load(const Image& img) {
......@@ -54,10 +58,14 @@ void AlphaMask::setAlpha(Bitmap& bmp) const {
bmp = Bitmap(img);
}
bool AlphaMask::isTransparent(int x, int y) const {
bool AlphaMask::isOpaque(int x, int y) const {
if (x < 0 || y < 0 || x >= size.x || y >= size.y) return false;
if (!alpha) return true;
return alpha[x + y * size.x] < 20;
return alpha[x + y * size.x] >= 20;
}
bool AlphaMask::isOpaque(const RealPoint& p, const RealSize& resize) const {
return isOpaque((int)(p.x * size.x / resize.width)
,(int)(p.y * size.y / resize.height));
}
/// Do the points form a (counter??)clockwise angle?
......@@ -72,6 +80,10 @@ void make_convex(vector<wxPoint>& points) {
points.erase(points.end() - 2);
}
}
void add_convex_point(vector<wxPoint>& points, int x, int y) {
points.push_back(wxPoint(x,y));
make_convex(points);
}
void AlphaMask::convexHull(vector<wxPoint>& points) const {
if (!alpha) throw InternalError(_("AlphaMask::convexHull"));
......@@ -84,36 +96,31 @@ void AlphaMask::convexHull(vector<wxPoint>& points) const {
miny = min(miny,y);
maxy = y;
if (y == miny) {
points.push_back(wxPoint(x-1,y-1));
add_convex_point(points, x-1, y-1);
}
points.push_back(wxPoint(x-1,y));
make_convex(points);
add_convex_point(points, x-1, y);
lastx = x;
break;
}
}
}
if (maxy == -1) return; // No image
points.push_back(wxPoint(lastx-1,maxy+1));
make_convex(points);
add_convex_point(points, lastx-1, maxy+1);
// Right side, bottom to top
for (int y = maxy ; y >= miny ; --y) {
for (int x = size.x - 1 ; x >= 0 ; --x) {
if (alpha[x + y * size.x] >= 20) {
// opaque pixel
if (y == maxy) {
points.push_back(wxPoint(x+1,y+1));
make_convex(points);
add_convex_point(points, x+1, y+1);
}
points.push_back(wxPoint(x+1,y));
make_convex(points);
add_convex_point(points, x+1, y);
lastx = x;
break;
}
}
}
points.push_back(wxPoint(lastx+1,miny-1));
make_convex(points);
add_convex_point(points, lastx+1, miny-1);
}
Image AlphaMask::colorImage(const Color& color) const {
......@@ -142,7 +149,7 @@ void AlphaMask::loadRowSizes() const {
}
}
double AlphaMask::rowLeft (double y, RealSize resize) const {
double AlphaMask::rowLeft (double y, const RealSize& resize) const {
loadRowSizes();
if (!lefts || y < 0 || y >= resize.height) {
// no mask, or outside it
......@@ -151,7 +158,7 @@ double AlphaMask::rowLeft (double y, RealSize resize) const {
return lefts[(int)(y * resize.height / size.y)] * resize.width / size.x;
}
double AlphaMask::rowRight(double y, RealSize resize) const {
double AlphaMask::rowRight(double y, const RealSize& resize) const {
loadRowSizes();
if (!rights || y < 0 || y >= resize.height) {
// no mask, or outside it
......
......@@ -54,7 +54,7 @@ void ImageSlice::constrain(PreferedProperty prefer) {
Image ImageSlice::getSlice() const {
if (selection.width == target_size.GetWidth() && selection.height == target_size.GetHeight() && selection.x == 0 && selection.y == 0) {
// exactly the right size
return source;
return source.GetSubImage(selection);
}
Image target(target_size.GetWidth(), target_size.GetHeight(), false);
if (sharpen && sharpen_amount > 0 && sharpen_amount <= 100) {
......@@ -75,7 +75,7 @@ DEFINE_EVENT_TYPE(EVENT_SLICE_CHANGED);
// ----------------------------------------------------------------------------- : ImageSliceWindow
ImageSliceWindow::ImageSliceWindow(Window* parent, const Image& source, const wxSize& target_size, const AlphaMaskP& mask)
ImageSliceWindow::ImageSliceWindow(Window* parent, const Image& source, const wxSize& target_size, const AlphaMask& mask)
: wxDialog(parent,wxID_ANY,_TITLE_("slice image"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE)
, slice(source, target_size)
{
......@@ -277,6 +277,7 @@ void ImageSliceWindow::updateControls() {
size->SetSelection(2); // force to fit
} else if (slice.selection.width <= slice.source.GetWidth() &&
slice.selection.height <= slice.source.GetHeight() &&
fabs(slice.zoomX() - slice.zoomY()) < 0.01 &&
( (slice.selection.x == 0 && slice.selection.width == slice.source.GetWidth())
||(slice.selection.y == 0 && slice.selection.height == slice.source.GetHeight()))) {
size->SetSelection(1); // size to fit
......@@ -334,7 +335,7 @@ END_EVENT_TABLE ()
// ----------------------------------------------------------------------------- : ImageSlicePreview
ImageSlicePreview::ImageSlicePreview(Window* parent, int id, ImageSlice& slice, const AlphaMaskP& mask)
ImageSlicePreview::ImageSlicePreview(Window* parent, int id, ImageSlice& slice, const AlphaMask& mask)
: wxControl(parent, id)
, slice(slice)
, mask(mask)
......@@ -359,9 +360,8 @@ void ImageSlicePreview::onPaint(wxPaintEvent&) {
void ImageSlicePreview::draw(DC& dc) {
if (!bitmap.Ok()) {
Image image = slice.getSlice();
if (mask && mask->hasSize(slice.target_size)) {
mask->setAlpha(image);
}
assert(image.GetWidth() == slice.target_size.GetWidth() && image.GetHeight() == slice.target_size.GetHeight());
mask.setAlpha(image);
if (image.HasAlpha()) {
// create bitmap
bitmap = Bitmap(image.GetWidth(), image.GetHeight());
......
......@@ -59,7 +59,7 @@ class ImageSlice {
/// Dialog for selecting a slice of an image
class ImageSliceWindow : public wxDialog {
public:
ImageSliceWindow(Window* parent, const Image& source, const wxSize& target_size, const AlphaMaskP& target_mask);
ImageSliceWindow(Window* parent, const Image& source, const wxSize& target_size, const AlphaMask& target_mask);
/// Return the sliced image
Image getImage() const;
......@@ -115,7 +115,7 @@ class ImageSliceWindow : public wxDialog {
/// A preview of the sliced image
class ImageSlicePreview : public wxControl {
public:
ImageSlicePreview(Window* parent, int id, ImageSlice& slice, const AlphaMaskP& mask);
ImageSlicePreview(Window* parent, int id, ImageSlice& slice, const AlphaMask& mask);
/// Notify that the slice was updated
void update();
......@@ -124,7 +124,7 @@ class ImageSlicePreview : public wxControl {
private:
Bitmap bitmap;
ImageSlice& slice;
AlphaMaskP mask;
const AlphaMask& mask;
bool mouse_down;
int mouseX, mouseY; ///< starting mouse position
......
......@@ -29,17 +29,10 @@ bool ImageValueEditor::onLeftDClick(const RealPoint&, wxMouseEvent&) {
void ImageValueEditor::sliceImage(const Image& image) {
if (!image.Ok()) return;
// mask?
AlphaMaskP mask;
if (!style().mask_filename().empty()) {
Image mask_image;
InputStreamP image_file = getStylePackage().openIn(style().mask_filename);
if (mask_image.LoadFile(*image_file)) {
Image resampled(style().width, style().height);
resample(mask_image, resampled);
mask = new_intrusive1<AlphaMask>(resampled);
}
}
// mask
GeneratedImage::Options options((int)style().width, (int)style().height, &viewer.getStylePackage(), &viewer.getLocalPackage());
AlphaMask mask;
style().mask.getNoCache(options,mask);
// slice
ImageSliceWindow s(wxGetTopLevelParent(&editor()), image, style().getSize(), mask);
// clicked ok?
......
......@@ -10,6 +10,8 @@
#include <render/value/choice.hpp>
#include <render/card/viewer.hpp>
DECLARE_TYPEOF_COLLECTION(wxPoint);
// ----------------------------------------------------------------------------- : ChoiceValueViewer
IMPLEMENT_VALUE_VIEWER(Choice);
......@@ -20,11 +22,48 @@ bool ChoiceValueViewer::prepare(RotatedDC& dc) {
return prepare_choice_viewer(dc, *this, style(), value().value());
}
void ChoiceValueViewer::draw(RotatedDC& dc) {
drawFieldBorder(dc);
int w = max(0,(int)dc.trX(style().width)), h = max(0,(int)dc.trY(style().height));
const AlphaMask& alpha_mask = getMask(w,h);
drawFieldBorder(dc, alpha_mask);
if (style().render_style & RENDER_HIDDEN) return;
draw_choice_viewer(dc, *this, style(), value().value());
}
void ChoiceValueViewer::drawFieldBorder(RotatedDC& dc, const AlphaMask& alpha_mask) {
if (!alpha_mask.isLoaded()) {
ValueViewer::drawFieldBorder(dc);
} else if (setFieldBorderPen(dc)) {
dc.SetBrush(*wxTRANSPARENT_BRUSH);
vector<wxPoint> points;
alpha_mask.convexHull(points);
if (points.size() < 3) return;
FOR_EACH(p, points) p = dc.trPixelNoZoom(RealPoint(p.x,p.y));
dc.getDC().DrawPolygon((int)points.size(), &points[0]);
}
}
bool ChoiceValueViewer::containsPoint(const RealPoint& p) const {
// check against mask
return getMask(0,0).isOpaque(p, style().getSize());
}
void ChoiceValueViewer::onStyleChange(int changes) {
if (changes & CHANGE_MASK) style().image.clearCache();
ValueViewer::onStyleChange(changes);
}
const AlphaMask& ChoiceValueViewer::getMask(int w, int h) const {
GeneratedImage::Options opts;
opts.package = &viewer.getStylePackage();
opts.local_package = &viewer.getLocalPackage();
opts.angle = 0;
opts.width = w;
opts.height = h;
return style().mask.get(opts);
}
// ----------------------------------------------------------------------------- : Generic draw/prepare
bool prepare_choice_viewer(RotatedDC& dc, ValueViewer& viewer, ChoiceStyle& style, const String& value) {
if (style.render_style & RENDER_IMAGE) {
style.initImage();
......@@ -38,7 +77,6 @@ bool prepare_choice_viewer(RotatedDC& dc, ValueViewer& viewer, ChoiceStyle& styl
// Generate image/bitmap (whichever is available)
// don't worry, we cache the image
ImageCombine combine = style.combine;
style.loadMask(viewer.getStylePackage());
Bitmap bitmap; Image image;
RealSize size;
img.generateCached(img_options, &style.mask, &combine, &bitmap, &image, &size);
......@@ -70,7 +108,6 @@ void draw_choice_viewer(RotatedDC& dc, ValueViewer& viewer, ChoiceStyle& style,
get_options(dc, viewer, style, img_options);
// Generate image/bitmap
ImageCombine combine = style.combine;
style.loadMask(viewer.getStylePackage());
Bitmap bitmap; Image image;
RealSize size;
img.generateCached(img_options, &style.mask, &combine, &bitmap, &image, &size);
......@@ -112,8 +149,3 @@ void get_options(Rotation& rot, ValueViewer& viewer, const ChoiceStyle& style, G
opts.preserve_aspect = (style.alignment & ALIGN_STRETCH) ? ASPECT_STRETCH : ASPECT_FIT;
}
}
void ChoiceValueViewer::onStyleChange(int changes) {
if (changes & CHANGE_MASK) style().image.clearCache();
ValueViewer::onStyleChange(changes);
}
......@@ -23,10 +23,19 @@ class ChoiceValueViewer : public ValueViewer {
virtual bool prepare(RotatedDC& dc);
virtual void draw(RotatedDC& dc);
virtual void onStyleChange(int);
virtual bool containsPoint(const RealPoint& p) const;
private:
/// Draws a border around the field
void drawFieldBorder(RotatedDC& dc, const AlphaMask& alpha_mask);
/// Load the AlphaMask for this field
const AlphaMask& getMask(int w, int h) const;
};
bool prepare_choice_viewer(RotatedDC& dc, ValueViewer& viewer, ChoiceStyle& style, const String& value);
void draw_choice_viewer(RotatedDC& dc, ValueViewer& viewer, ChoiceStyle& style, const String& value);
const AlphaMask& get_mask(RotatedDC& dc, ValueViewer& viewer, ChoiceStyle& style, int w, int h);
// ----------------------------------------------------------------------------- : EOF
#endif
......@@ -11,6 +11,7 @@
#include <render/card/viewer.hpp>
DECLARE_TYPEOF_COLLECTION(ColorField::ChoiceP);
DECLARE_TYPEOF_COLLECTION(wxPoint);
// ----------------------------------------------------------------------------- : ColorValueViewer
......@@ -43,9 +44,10 @@ void ColorValueViewer::draw(RotatedDC& dc) {
dc.DrawText(color_name, RealPoint(43, 3));
} else {
// is there a mask?
loadMask(dc);
if (alpha_mask) {
dc.DrawImage(alpha_mask->colorImage(value().value()), RealPoint(0,0), style().combine);
int w = max(0,(int)dc.trX(style().width)), h = max(0,(int)dc.trY(style().height));
const AlphaMask& alpha_mask = getMask(w,h);
if (alpha_mask.isLoaded()) {
dc.DrawImage(alpha_mask.colorImage(value().value()), RealPoint(0,0), style().combine);
} else {
// do we need clipping?
bool clip = style().left_width < style().width && style().right_width < style().width &&
......@@ -64,43 +66,45 @@ void ColorValueViewer::draw(RotatedDC& dc) {
dc.DrawRoundedRectangle(style().getInternalRect(), style().radius);
if (clip) dc.getDC().DestroyClippingRegion();
}
drawFieldBorder(dc, alpha_mask);
}
}
void ColorValueViewer::drawFieldBorder(RotatedDC& dc, const AlphaMask& alpha_mask) {
if (!alpha_mask.isLoaded()) {
ValueViewer::drawFieldBorder(dc);
} else if (setFieldBorderPen(dc)) {
dc.SetBrush(*wxTRANSPARENT_BRUSH);
vector<wxPoint> points;
alpha_mask.convexHull(points);
if (points.size() < 3) return;
FOR_EACH(p, points) p = dc.trPixelNoZoom(RealPoint(p.x,p.y));
dc.getDC().DrawPolygon((int)points.size(), &points[0]);
}
}
bool ColorValueViewer::containsPoint(const RealPoint& p) const {
// distance to each side
double left = p.x, right = style().width - p.x - 1;
double top = p.y, bottom = style().height - p.y - 1;
if (left < 0 || right < 0 || top < 0 || bottom < 0) return false; // outside bounding box
// check against mask
if (!style().mask_filename().empty()) loadMask(getRotation());
if (alpha_mask) {
return !alpha_mask->isTransparent((int)left, (int)top);
const AlphaMask& alpha_mask = getMask(0,0);
if (alpha_mask.isLoaded()) {
// check against mask
return alpha_mask.isOpaque(p, style().getSize());
} else {
double left = p.x, right = style().width - p.x - 1;
double top = p.y, bottom = style().height - p.y - 1;
if (left < 0 || right < 0 || top < 0 || bottom < 0) return false; // outside bounding box
// check against border
if (left >= style().left_width && right >= style().right_width && // outside horizontal border
top >= style().top_width && bottom >= style().bottom_width) { // outside vertical border
return false;
}
return true;
return left < style().left_width || right < style().right_width // inside horizontal border
|| top < style().top_width || bottom < style().bottom_width; // inside vertical border
}
}
void ColorValueViewer::onStyleChange(int changes) {
if (changes & CHANGE_MASK) alpha_mask = AlphaMaskP();
ValueViewer::onStyleChange(changes);
}
void ColorValueViewer::loadMask(const Rotation& rot) const {
if (style().mask_filename().empty()) return; // no mask
int w = (int) rot.trX(rot.getWidth()), h = (int) rot.trY(rot.getHeight());
if (alpha_mask && alpha_mask->hasSize(wxSize(w,h))) return; // mask loaded and right size
// (re) load the mask
Image image;
InputStreamP image_file = getStylePackage().openIn(style().mask_filename);
if (image.LoadFile(*image_file)) {
Image resampled(w,h);
resample(image, resampled);
alpha_mask = new_intrusive1<AlphaMask>(resampled);
}
const AlphaMask& ColorValueViewer::getMask(int w, int h) const {
GeneratedImage::Options opts;
opts.package = &viewer.getStylePackage();
opts.local_package = &viewer.getLocalPackage();
opts.angle = 0;
opts.width = w;
opts.height = h;
return style().mask.get(opts);
}
......@@ -25,11 +25,11 @@ class ColorValueViewer : public ValueViewer {
virtual void draw(RotatedDC& dc);
virtual bool containsPoint(const RealPoint& p) const;
virtual void onStyleChange(int);
private:
mutable AlphaMaskP alpha_mask;
void loadMask(const Rotation& rot) const;
/// Draws a border around the field
void drawFieldBorder(RotatedDC& dc, const AlphaMask& alpha_mask);
/// Load the AlphaMask for this field
const AlphaMask& getMask(int w, int h) const;
};
// ----------------------------------------------------------------------------- : EOF
......
......@@ -22,6 +22,7 @@ void ImageValueViewer::draw(RotatedDC& dc) {
// reset?
int w = max(0,(int)dc.trX(style().width)), h = max(0,(int)dc.trY(style().height));
int a = dc.trAngle(0); //% TODO : Add getAngle()?
const AlphaMask& alpha_mask = getMask(w,h);
if (bitmap.Ok() && (a != angle || size.width != w || size.height != h)) {
bitmap = Bitmap();
}
......@@ -30,7 +31,6 @@ void ImageValueViewer::draw(RotatedDC& dc) {
angle = a;
is_default = false;
Image image;
loadMask(dc);
// load from file
if (!value().filename.empty()) {
try {
......@@ -48,7 +48,7 @@ void ImageValueViewer::draw(RotatedDC& dc) {
is_default = true;
if (what & DRAW_EDITING) {
bitmap = imagePlaceholder(dc, w, h, image, what & DRAW_EDITING);
if (alpha_mask || a) {
if (alpha_mask.isLoaded() || a) {
image = bitmap.ConvertToImage(); // we need to convert back to an image
} else {
image = Image();
......@@ -59,7 +59,7 @@ void ImageValueViewer::draw(RotatedDC& dc) {
if (!image.Ok() && !bitmap.Ok() && style().width > 40) {
// placeholder bitmap
bitmap = imagePlaceholder(dc, w, h, wxNullImage, what & DRAW_EDITING);
if (alpha_mask || a) {
if (alpha_mask.isLoaded() || a) {
// we need to convert back to an image
image = bitmap.ConvertToImage();
}
......@@ -67,27 +67,27 @@ void ImageValueViewer::draw(RotatedDC& dc) {
// done
if (image.Ok()) {
// apply mask and rotate
if (alpha_mask) alpha_mask->setAlpha(image);
alpha_mask.setAlpha(image);
size = RealSize(image);
image = rotate_image(image, angle);
bitmap = Bitmap(image);
}
}
// border
drawFieldBorder(dc);
drawFieldBorder(dc, alpha_mask);
// draw image, if any
if (bitmap.Ok()) {
dc.DrawPreRotatedBitmap(bitmap, dc.getInternalRect());
}
}
void ImageValueViewer::drawFieldBorder(RotatedDC& dc) {
if (!alpha_mask) {
void ImageValueViewer::drawFieldBorder(RotatedDC& dc, const AlphaMask& alpha_mask) {
if (!alpha_mask.isLoaded()) {
ValueViewer::drawFieldBorder(dc);
} else if (setFieldBorderPen(dc)) {
dc.SetBrush(*wxTRANSPARENT_BRUSH);
vector<wxPoint> points;
alpha_mask->convexHull(points);
alpha_mask.convexHull(points);
if (points.size() < 3) return;
FOR_EACH(p, points) p = dc.trPixelNoZoom(RealPoint(p.x,p.y));
dc.getDC().DrawPolygon((int)points.size(), &points[0]);
......@@ -95,14 +95,8 @@ void ImageValueViewer::drawFieldBorder(RotatedDC& dc) {
}
bool ImageValueViewer::containsPoint(const RealPoint& p) const {
if (!ValueViewer::containsPoint(p)) return false;
// check against mask
if (!style().mask_filename().empty()) {
loadMask(getRotation());
return !alpha_mask || !alpha_mask->isTransparent((int)p.x, (int)p.y);
} else {
return true;
}
return getMask(0,0).isOpaque(p, style().getSize());
}
void ImageValueViewer::onValueChange() {
......@@ -114,20 +108,17 @@ void ImageValueViewer::onStyleChange(int changes) {
((changes & CHANGE_DEFAULT) && is_default)) {
bitmap = Bitmap();
}
if (changes & CHANGE_MASK) alpha_mask = AlphaMaskP();
ValueViewer::onStyleChange(changes);
}
void ImageValueViewer::loadMask(const Rotation& rot) const {
if (style().mask_filename().empty()) return; // no mask
int w = (int) rot.trX(style().width), h = (int) rot.trY(style().height);
if (alpha_mask && alpha_mask->hasSize(wxSize(w,h))) return; // mask loaded and right size
// (re) load the mask
Image image;
InputStreamP image_file = getStylePackage().openIn(style().mask_filename);
if (image.LoadFile(*image_file)) {
alpha_mask = new_intrusive1<AlphaMask>(resample(image,w,h));
}
const AlphaMask& ImageValueViewer::getMask(int w, int h) const {
GeneratedImage::Options opts;
opts.package = &viewer.getStylePackage();
opts.local_package = &viewer.getLocalPackage();
opts.angle = 0;
opts.width = w;
opts.height = h;
return style().mask.get(opts);
}
// is an image very light?
......
......@@ -34,15 +34,15 @@ class ImageValueViewer : public ValueViewer {
RealSize size; ///< Size of 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;
/// Generate a placeholder image
static Bitmap imagePlaceholder(const Rotation& rot, UInt w, UInt h, const Image& background, bool editing);
/// Draws a border around the field
void drawFieldBorder(RotatedDC& dc);
void drawFieldBorder(RotatedDC& dc, const AlphaMask& alpha_mask);
/// Load the AlphaMask for this field
const AlphaMask& getMask(int w, int h) const;
};
// ----------------------------------------------------------------------------- : EOF
......
......@@ -13,6 +13,7 @@
#include <gui/util.hpp>
DECLARE_TYPEOF_COLLECTION(String);
DECLARE_TYPEOF_COLLECTION(wxPoint);
// ----------------------------------------------------------------------------- : MultipleChoiceValueViewer
......@@ -24,7 +25,9 @@ bool MultipleChoiceValueViewer::prepare(RotatedDC& dc) {
}
void MultipleChoiceValueViewer::draw(RotatedDC& dc) {
drawFieldBorder(dc);
int w = max(0,(int)dc.trX(style().width)), h = max(0,(int)dc.trY(style().height));
const AlphaMask& alpha_mask = getMask(w,h);
drawFieldBorder(dc, alpha_mask);
if (style().render_style & RENDER_HIDDEN) return;
RealPoint pos = align_in_rect(style().alignment, RealSize(0,0), style().getInternalRect());
// selected choices
......@@ -83,7 +86,35 @@ void MultipleChoiceValueViewer::drawChoice(RotatedDC& dc, RealPoint& pos, const
pos = move_in_direction(style().direction, pos, size, style().spacing);
}
void MultipleChoiceValueViewer::drawFieldBorder(RotatedDC& dc, const AlphaMask& alpha_mask) {
if (!alpha_mask.isLoaded()) {
ValueViewer::drawFieldBorder(dc);
} else if (setFieldBorderPen(dc)) {
dc.SetBrush(*wxTRANSPARENT_BRUSH);
vector<wxPoint> points;
alpha_mask.convexHull(points);
if (points.size() < 3) return;
FOR_EACH(p, points) p = dc.trPixelNoZoom(RealPoint(p.x,p.y));
dc.getDC().DrawPolygon((int)points.size(), &points[0]);
}
}
bool MultipleChoiceValueViewer::containsPoint(const RealPoint& p) const {
// check against mask
return getMask(0,0).isOpaque(p, style().getSize());
}
void MultipleChoiceValueViewer::onStyleChange(int changes) {
if (changes & CHANGE_MASK) style().image.clearCache();
ValueViewer::onStyleChange(changes);
}
const AlphaMask& MultipleChoiceValueViewer::getMask(int w, int h) const {
GeneratedImage::Options opts;
opts.package = &viewer.getStylePackage();
opts.local_package = &viewer.getLocalPackage();
opts.angle = 0;
opts.width = w;
opts.height = h;
return style().mask.get(opts);
}
......@@ -23,10 +23,15 @@ class MultipleChoiceValueViewer : public ValueViewer {
virtual bool prepare(RotatedDC& dc);
virtual void draw(RotatedDC& dc);
virtual void onStyleChange(int);
virtual bool containsPoint(const RealPoint& p) const;
protected:
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);
/// Draws a border around the field
void drawFieldBorder(RotatedDC& dc, const AlphaMask& alpha_mask);
/// Load the AlphaMask for this field
const AlphaMask& getMask(int w, int h) const;
};
// ----------------------------------------------------------------------------- : EOF
......
......@@ -94,7 +94,7 @@ template <> void GetDefaultMember::handle(const ScriptableImage& s) {
// ----------------------------------------------------------------------------- : CachedScriptableImage
void CachedScriptableImage::generateCached(const GeneratedImage::Options& options,
Image* mask,
CachedScriptableMask* mask,
ImageCombine* combine, wxBitmap* bitmap, wxImage* image, RealSize* size) {
// ready?
if (!isReady()) {
......@@ -137,13 +137,24 @@ void CachedScriptableImage::generateCached(const GeneratedImage::Options& option
}
}
}
// hack: temporarily set angle to 0, do actual rotation after applying mask
int a = options.angle;
const_cast<GeneratedImage::Options&>(options).angle = 0;
// generate
cached_i = generate(options);
cached_angle = options.angle;
const_cast<GeneratedImage::Options&>(options).angle = cached_angle = a;
*size = cached_size = RealSize(options.width, options.height);
if (mask && mask->Ok()) {
if (mask) {
// apply mask
set_alpha(cached_i, *mask);
GeneratedImage::Options mask_opts(options);
mask_opts.width = cached_i.GetWidth();
mask_opts.height = cached_i.GetHeight();
mask_opts.angle = 0;
mask->get(mask_opts).setAlpha(cached_i);
}
if (options.angle != 0) {
// hack(pt2) do the actual rotation now
cached_i = rotate_image(cached_i, options.angle);
}
if (*combine <= COMBINE_NORMAL) {
*bitmap = cached_b = Bitmap(cached_i);
......@@ -176,3 +187,45 @@ template <> void Writer::handle(const CachedScriptableImage& s) {
template <> void GetDefaultMember::handle(const CachedScriptableImage& s) {
handle((const ScriptableImage&)s);
}
// ----------------------------------------------------------------------------- : CachedScriptableMask
bool CachedScriptableMask::update(Context& ctx) {
if (script.update(ctx)) {
mask.clear();
return true;
} else {
return false;
}
}
const AlphaMask& CachedScriptableMask::get(const GeneratedImage::Options& img_options) {
if (mask.isLoaded()) {
// already loaded?
if (img_options.width == 0 && img_options.height == 0) return mask;
if (mask.hasSize(wxSize(img_options.width,img_options.height))) return mask;
}
// load?
getNoCache(img_options,mask);
return mask;
}
void CachedScriptableMask::getNoCache(const GeneratedImage::Options& img_options, AlphaMask& other_mask) {
if (script.isBlank()) {
other_mask.clear();
} else {
Image image = script.generate(img_options);
other_mask.load(image);
}
}
template <> void Reader::handle(CachedScriptableMask& i) {
handle(i.script);
}
template <> void Writer::handle(const CachedScriptableMask& i) {
handle(i.script);
}
template <> void GetDefaultMember::handle(const CachedScriptableMask& i) {
handle(i.script);
}
......@@ -15,6 +15,8 @@
#include <script/scriptable.hpp>
#include <gfx/generated_image.hpp>
class CachedScriptableMask;
// ----------------------------------------------------------------------------- : ScriptableImage
/// An image that can also be scripted
......@@ -49,9 +51,10 @@ class ScriptableImage {
/// Can this be safely generated from another thread?
inline bool threadSafe() const { return !value || value->threadSafe(); }
/// Is this image specific to the set (the local_package)?
inline bool local() const { return value && value->local(); }
/// Is this image blank?
inline bool isBlank() const { return !value || value->isBlank(); }
/// Get access to the script, be careful
inline Script& getMutableScript() { return script.getMutableScript(); }
......@@ -89,7 +92,7 @@ class CachedScriptableImage : public ScriptableImage {
* Optionally, an alpha mask is applied to the image.
*/
void generateCached(const GeneratedImage::Options& img_options,
Image* mask,
CachedScriptableMask* mask,
ImageCombine* combine, wxBitmap* bitmap, wxImage* image, RealSize* size);
/// Update the script, returns true if the value has changed
......@@ -105,5 +108,29 @@ class CachedScriptableImage : public ScriptableImage {
int cached_angle;
};
// ----------------------------------------------------------------------------- : CachedScriptableMask
/// A version of ScriptableImage that caches an AlphaMask
class CachedScriptableMask {
public:
/// Update the script, returns true if the value has changed
bool update(Context& ctx);
/// Get the alpha mask; with the given options
/** if img_options.width == 0 and the mask is already loaded, just returns it. */
const AlphaMask& get(const GeneratedImage::Options& img_options);
/// Get a mask that is not cached
void getNoCache(const GeneratedImage::Options& img_options, AlphaMask& mask);
private:
ScriptableImage script;
AlphaMask mask;
friend class Reader;
friend class Writer;
friend class GetDefaultMember;
};
// ----------------------------------------------------------------------------- : EOF
#endif
......@@ -277,7 +277,11 @@ class ScriptString : public ScriptValue {
return c;
}
virtual GeneratedImageP toImage(const ScriptValueP&) const {
return new_intrusive1<PackagedImage>(value);
if (value.empty()) {
return new_intrusive<BlankImage>();
} else {
return new_intrusive1<PackagedImage>(value);
}
}
virtual int itemCount() const { return (int)value.size(); }
virtual ScriptValueP getMember(const String& name) const {
......
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