Commit 9a5c8dec authored by twanvl's avatar twanvl

Added functions for blurring and scaling images and changing the alpha value

parent 791506a7
......@@ -34,6 +34,12 @@ SymbolVariation::SymbolVariation()
{}
SymbolVariation::~SymbolVariation() {}
bool SymbolVariation::operator == (const SymbolVariation& that) const {
return name == that.name
&& border_radius == that.border_radius
&& *filter == *that.filter;
}
IMPLEMENT_REFLECTION_NO_SCRIPT(SymbolVariation) {
REFLECT(name);
REFLECT(border_radius);
......
......@@ -54,6 +54,9 @@ class SymbolVariation : public IntrusivePtrBase<SymbolVariation> {
String name; ///< Name of this variation
SymbolFilterP filter; ///< Filter to color the symbol
double border_radius; ///< Border radius for the symbol
bool operator == (const SymbolVariation&) const;
DECLARE_REFLECTION();
};
......
......@@ -86,3 +86,13 @@ void set_alpha(Image& img, const Image& img_alpha) {
im[i] = (im[i] * al[i*3]) / 255;
}
}
void set_alpha(Image& img, double alpha) {
if (!img.HasAlpha()) img.InitAlpha();
Byte b_alpha = alpha * 255;
Byte *im = img.GetAlpha();
UInt size = img.GetWidth() * img.GetHeight();
for (UInt i = 0 ; i < size ; ++i) {
im[i] = (im[i] * b_alpha) / 255;
}
}
......@@ -152,6 +152,20 @@ bool SetMaskImage::operator == (const GeneratedImage& that) const {
&& *mask == *that2->mask;
}
Image SetAlphaImage::generate(const Options& opt) const {
Image img = image->generate(opt);
set_alpha(img, alpha);
return img;
}
ImageCombine SetAlphaImage::combine() const {
return image->combine();
}
bool SetAlphaImage::operator == (const GeneratedImage& that) const {
const SetAlphaImage* that2 = dynamic_cast<const SetAlphaImage*>(&that);
return that2 && *image == *that2->image
&& alpha == that2->alpha;
}
// ----------------------------------------------------------------------------- : SetCombineImage
Image SetCombineImage::generate(const Options& opt) const {
......@@ -166,6 +180,146 @@ bool SetCombineImage::operator == (const GeneratedImage& that) const {
&& image_combine == that2->image_combine;
}
// ----------------------------------------------------------------------------- : EnlargeImage
Image EnlargeImage::generate(const Options& opt) const {
// generate 'sub' image
Options sub_opt
( opt.width * (border_size < 0.5 ? 1 - 2 * border_size : 0)
, opt.height * (border_size < 0.5 ? 1 - 2 * border_size : 0)
, opt.package
, opt.local_package
, opt.preserve_aspect);
Image img = image->generate(sub_opt);
// size of generated image
int w = img.GetWidth(), h = img.GetHeight(); // original image size
int dw = w * border_size, dh = h * border_size; // delta
int w2 = w + dw + dw, h2 = h + dh + dh; // new image size
Image larger(w2,h2);
larger.InitAlpha();
memset(larger.GetAlpha(),0,w2*h2); // blank
// copy to sub-part of larger image
Byte* data1 = img.GetData(), *data2 = larger.GetData();
for (int y = 0 ; y < h ; ++y) {
memcpy(data2 + 3*(dw + (y+dh)*w2), data1 + 3*y*w, 3*w); // copy a line
}
if (img.HasAlpha()) {
data1 = img.GetAlpha(), data2 = larger.GetAlpha();
for (int y = 0 ; y < h ; ++y) {
memcpy(data2 + dw + (y+dh)*w2, data1 + y*w, w); // copy a line
}
}
// done
return larger;
}
ImageCombine EnlargeImage::combine() const {
return image->combine();
}
bool EnlargeImage::operator == (const GeneratedImage& that) const {
const EnlargeImage* that2 = dynamic_cast<const EnlargeImage*>(&that);
return that2 && *image == *that2->image
&& border_size == that2->border_size;
}
// ----------------------------------------------------------------------------- : DropShadowImage
/// Preform a gaussian blur, from the image in of w*h bytes to out
/** out is scaled some scaling, this is the return value */
UInt gaussian_blur(Byte* in, UInt* out, int w, int h, double radius) {
// blur horizontally
UInt* blur_x = new UInt[w*h]; // scaled by total_x, so in [0..255*total_x]
memset(blur_x, 0, w*h*sizeof(UInt));
UInt total_x = 0;
{
double sigma = radius * w;
double mult = (1 << 8) / (sqrt(2 * M_PI) * sigma);
double sigsqr2 = 1 / (2 * sigma * sigma);
int range = min(w, (int)(3*sigma));
for (int d = -range ; d <= range ; ++d) {
UInt factor = (int)( mult * exp(-d * d * sigsqr2) );
total_x += factor;
if (factor > 0) {
int x_start = max(0, -d), x_end = min(w, w-d);
for (int y = 0 ; y < h ; ++y) {
for (int x = x_start ; x < x_end ; ++x) {
blur_x[x + y*w] += in[x + d + y*w] * factor;
}
}
}
}
}
// blur vertically
memset(out, 0, w*h*sizeof(UInt));
UInt total_y = 0;
{
double sigma = radius * h;
double mult = (1 << 8) / (sqrt(2 * M_PI) * sigma);
double sigsqr2 = 1 / (2 * sigma * sigma);
int range = min(h, (int)(3*sigma));
for (int d = -range ; d <= range ; ++d) {
UInt factor = (UInt)( mult * exp(-d * d * sigsqr2) );
total_y += factor;
if (factor > 0) {
int y_start = max(0, -d), y_end = min(h, h-d);
for (int y = y_start ; y < y_end ; ++y) {
for (int x = 0 ; x < w ; ++x) {
out[x + y*w] += blur_x[x + (d + y)*w] * factor;
}
}
}
}
}
delete[] blur_x;
return total_x * total_y;
}
Image DropShadowImage::generate(const Options& opt) const {
// sub image
Image img = image->generate(opt);
if (!img.HasAlpha()) {
// no alpha, there is nothing we can do
return img;
}
int w = img.GetWidth(), h = img.GetHeight();
Byte* alpha = img.GetAlpha();
// blur
UInt* shadow = new UInt[w*h];
UInt total = 255 * gaussian_blur(alpha, shadow, w, h, shadow_blur_radius);
// combine
Byte* data = img.GetData();
int dw = w * offset_x, dh = h * offset_y;
int x_start = max(0, dw), y_start = max(0, dh);
int x_end = min(w, w+dw), y_end = min(h, h+dh);
int delta = dw + w * dh;
int sa = (int)(shadow_alpha * (1 << 16));
for (int y = y_start ; y < y_end ; ++y) {
for (int x = x_start ; x < x_end ; ++x) {
int p = x + y * w; // pixel we are working on
int a = alpha[p];
int shad = ((((255 - a)*sa)>>16) * shadow[p - delta]) / total; // amount of shadow to add
int factor = max(1, a + shad); // divide by this
data[3 * p ] = (a * data[3 * p ] + shad * shadow_color.Red() ) / factor;
data[3 * p + 1] = (a * data[3 * p + 1] + shad * shadow_color.Green()) / factor;
data[3 * p + 2] = (a * data[3 * p + 2] + shad * shadow_color.Blue() ) / factor;
alpha[p] = a + shad;
}
}
//memset(data,0,3*w*h);
// cleanup
delete[] shadow;
return img;
}
ImageCombine DropShadowImage::combine() const {
return image->combine();
}
bool DropShadowImage::operator == (const GeneratedImage& that) const {
const DropShadowImage* that2 = dynamic_cast<const DropShadowImage*>(&that);
return that2 && *image == *that2->image
&& offset_x == that2->offset_x && offset_y == that2->offset_y
&& shadow_alpha == that2->shadow_alpha && shadow_blur_radius == that2->shadow_blur_radius
&& shadow_color == that2->shadow_color;
}
// ----------------------------------------------------------------------------- : PackagedImage
Image PackagedImage::generate(const Options& opt) const {
......@@ -223,7 +377,9 @@ bool SymbolToImage::operator == (const GeneratedImage& that) const {
const SymbolToImage* that2 = dynamic_cast<const SymbolToImage*>(&that);
return that2 && filename == that2->filename
&& age == that2->age
&& variation == that2->variation;
&& (variation == that2->variation ||
*variation == *that2->variation // custom variation
);
}
// ----------------------------------------------------------------------------- : ImageValueToImage
......
......@@ -129,6 +129,20 @@ class SetMaskImage : public GeneratedImage {
GeneratedImageP image, mask;
};
/// Change the alpha channel of an image
class SetAlphaImage : public GeneratedImage {
public:
inline SetAlphaImage(const GeneratedImageP& image, double alpha)
: image(image), alpha(alpha)
{}
virtual Image generate(const Options& opt) const;
virtual ImageCombine combine() const;
virtual bool operator == (const GeneratedImage& that) const;
private:
GeneratedImageP image;
double alpha;
};
// ----------------------------------------------------------------------------- : SetCombineImage
/// Change the combine mode
......@@ -145,6 +159,42 @@ class SetCombineImage : public GeneratedImage {
ImageCombine image_combine;
};
// ----------------------------------------------------------------------------- : EnlargeImage
/// Enlarge an image by adding a border around it
class EnlargeImage : public GeneratedImage {
public:
inline EnlargeImage(const GeneratedImageP& image, double border_size)
: image(image), border_size(abs(border_size))
{}
virtual Image generate(const Options& opt) const;
virtual ImageCombine combine() const;
virtual bool operator == (const GeneratedImage& that) const;
private:
GeneratedImageP image;
double border_size;
};
// ----------------------------------------------------------------------------- : DropShadowImage
/// Add a drop shadow to an image
class DropShadowImage : public GeneratedImage {
public:
inline DropShadowImage(const GeneratedImageP& image, double offset_x, double offset_y, double shadow_alpha, double shadow_blur_radius, Color shadow_color)
: image(image), offset_x(offset_x), offset_y(offset_y)
, shadow_alpha(shadow_alpha), shadow_blur_radius(shadow_blur_radius), shadow_color(shadow_color)
{}
virtual Image generate(const Options& opt) const;
virtual ImageCombine combine() const;
virtual bool operator == (const GeneratedImage& that) const;
private:
GeneratedImageP image;
double offset_x, offset_y;
double shadow_alpha;
double shadow_blur_radius;
Color shadow_color;
};
// ----------------------------------------------------------------------------- : PackagedImage
/// Load an image from a file in a package
......
......@@ -123,6 +123,8 @@ void draw_combine_image(DC& dc, UInt x, UInt y, const Image& img, ImageCombine c
/// Use the red channel of img_alpha as alpha channel for img
void set_alpha(Image& img, const Image& img_alpha);
/// Set the transparency of img
void set_alpha(Image& img, double alpha);
/// An alpha mask is an alpha channel that can be copied to another image
/** It is created by treating black in the source image as transparent and white (red) as opaque
......
......@@ -86,6 +86,12 @@ AColor SolidFillSymbolFilter::color(double x, double y, SymbolSet point) const {
else return AColor(0,0,0,0);
}
bool SolidFillSymbolFilter::operator == (const SymbolFilter& that) const {
const SolidFillSymbolFilter* that2 = dynamic_cast<const SolidFillSymbolFilter*>(&that);
return that2 && fill_color == that2->fill_color
&& border_color == that2->border_color;
}
IMPLEMENT_REFLECTION(SolidFillSymbolFilter) {
REFLECT_BASE(SymbolFilter);
REFLECT(fill_color);
......@@ -101,6 +107,13 @@ AColor GradientSymbolFilter::color(double x, double y, SymbolSet point, const T*
else return AColor(0,0,0,0);
}
bool GradientSymbolFilter::equal(const GradientSymbolFilter& that) const {
return fill_color_1 == that.fill_color_1
&& fill_color_2 == that.fill_color_2
&& border_color_1 == that.border_color_1
&& border_color_2 == that.border_color_2;
}
IMPLEMENT_REFLECTION(GradientSymbolFilter) {
REFLECT_BASE(SymbolFilter);
REFLECT(fill_color_1);
......@@ -120,6 +133,14 @@ LinearGradientSymbolFilter::LinearGradientSymbolFilter()
: center_x(0.5), center_y(0.5)
, end_x(1), end_y(1)
{}
LinearGradientSymbolFilter::LinearGradientSymbolFilter
( const Color& fill_color_1, const Color& border_color_1
, const Color& fill_color_2, const Color& border_color_2
, double center_x, double center_y, double end_x, double end_y)
: GradientSymbolFilter(fill_color_1, border_color_1, fill_color_2, border_color_2)
, center_x(center_x), center_y(center_y)
, end_x(end_x), end_y(end_y)
{}
AColor LinearGradientSymbolFilter::color(double x, double y, SymbolSet point) const {
len = sqr(end_x - center_x) + sqr(end_y - center_y);
......@@ -132,6 +153,13 @@ double LinearGradientSymbolFilter::t(double x, double y) const {
return min(1.,max(0.,t));
}
bool LinearGradientSymbolFilter::operator == (const SymbolFilter& that) const {
const LinearGradientSymbolFilter* that2 = dynamic_cast<const LinearGradientSymbolFilter*>(&that);
return that2 && equal(*that2)
&& center_x == that2->center_x && end_x == that2->end_x
&& center_y == that2->center_y && end_y == that2->end_y;
}
IMPLEMENT_REFLECTION(LinearGradientSymbolFilter) {
REFLECT_BASE(GradientSymbolFilter);
REFLECT(center_x); REFLECT(center_y);
......@@ -149,3 +177,8 @@ AColor RadialGradientSymbolFilter::color(double x, double y, SymbolSet point) co
double RadialGradientSymbolFilter::t(double x, double y) const {
return sqrt( (sqr(x - 0.5) + sqr(y - 0.5)) * 2);
}
bool RadialGradientSymbolFilter::operator == (const SymbolFilter& that) const {
const RadialGradientSymbolFilter* that2 = dynamic_cast<const RadialGradientSymbolFilter*>(&that);
return that2 && equal(*that2);
}
......@@ -54,6 +54,8 @@ class SymbolFilter : public IntrusivePtrVirtualBase {
virtual AColor color(double x, double y, SymbolSet point) const = 0;
/// Name of this fill type
virtual String fillType() const = 0;
/// Comparision
virtual bool operator == (const SymbolFilter& that) const = 0;
DECLARE_REFLECTION_VIRTUAL();
};
......@@ -66,8 +68,13 @@ intrusive_ptr<SymbolFilter> read_new<SymbolFilter>(Reader& reader);
/// Symbol filter that returns solid colors
class SolidFillSymbolFilter : public SymbolFilter {
public:
inline SolidFillSymbolFilter() {}
inline SolidFillSymbolFilter(Color fill_color, Color border_color)
: fill_color(fill_color), border_color(border_color)
{}
virtual AColor color(double x, double y, SymbolSet point) const;
virtual String fillType() const;
virtual bool operator == (const SymbolFilter& that) const;
private:
Color fill_color, border_color;
DECLARE_REFLECTION();
......@@ -75,11 +82,18 @@ class SolidFillSymbolFilter : public SymbolFilter {
/// Symbol filter that returns some gradient
class GradientSymbolFilter : public SymbolFilter {
public:
inline GradientSymbolFilter() {}
inline GradientSymbolFilter(const Color& fill_color_1, const Color& border_color_1, const Color& fill_color_2, const Color& border_color_2)
: fill_color_1(fill_color_1), border_color_1(border_color_1)
, fill_color_2(fill_color_2), border_color_2(border_color_2)
{}
protected:
Color fill_color_1, border_color_1;
Color fill_color_2, border_color_2;
template <typename T>
AColor color(double x, double y, SymbolSet point, const T* t) const;
bool equal(const GradientSymbolFilter& that) const;
DECLARE_REFLECTION();
};
......@@ -88,9 +102,12 @@ class GradientSymbolFilter : public SymbolFilter {
class LinearGradientSymbolFilter : public GradientSymbolFilter {
public:
LinearGradientSymbolFilter();
LinearGradientSymbolFilter(const Color& fill_color_1, const Color& border_color_1, const Color& fill_color_2, const Color& border_color_2
,double center_x, double center_y, double end_x, double end_y);
virtual AColor color(double x, double y, SymbolSet point) const;
virtual String fillType() const;
virtual bool operator == (const SymbolFilter& that) const;
/// return time on the gradient, used by GradientSymbolFilter::color
inline double t(double x, double y) const;
......@@ -105,8 +122,14 @@ class LinearGradientSymbolFilter : public GradientSymbolFilter {
/// Symbol filter that returns a radial gradient
class RadialGradientSymbolFilter : public GradientSymbolFilter {
public:
inline RadialGradientSymbolFilter() {}
inline RadialGradientSymbolFilter(const Color& fill_color_1, const Color& border_color_1, const Color& fill_color_2, const Color& border_color_2)
: GradientSymbolFilter(fill_color_1, border_color_1, fill_color_2, border_color_2)
{}
virtual AColor color(double x, double y, SymbolSet point) const;
virtual String fillType() const;
virtual bool operator == (const SymbolFilter& that) const;
/// return time on the gradient, used by GradientSymbolFilter::color
inline double t(double x, double y) const;
......
......@@ -180,7 +180,7 @@ void SymbolViewer::drawSymbolPart(const SymbolPart& part, DC* border, DC* interi
segment_subdivide(*part.getPoint((int)i), *part.getPoint((int)i+1), rotation, points);
}
// draw border
if (border) {
if (border && border_radius > 0) {
// white/black or, if directB white/green
border->SetBrush(Color(borderCol, (directB && borderCol == 0 ? 128 : borderCol), borderCol));
border->SetPen(wxPen(*wxWHITE, (int) rotation.trS(border_radius)));
......
......@@ -16,6 +16,7 @@
#include <data/symbol.hpp>
#include <data/field/symbol.hpp>
#include <gfx/generated_image.hpp>
#include <render/symbol/filter.hpp>
DECLARE_TYPEOF_COLLECTION(SymbolVariationP);
......@@ -61,6 +62,12 @@ SCRIPT_FUNCTION(set_mask) {
return new_intrusive2<SetMaskImage>(image, mask);
}
SCRIPT_FUNCTION(set_alpha) {
SCRIPT_PARAM(GeneratedImageP, input);
SCRIPT_PARAM(double, alpha);
return new_intrusive2<SetAlphaImage>(input, alpha);
}
SCRIPT_FUNCTION(set_combine) {
SCRIPT_PARAM(String, combine);
SCRIPT_PARAM(GeneratedImageP, input);
......@@ -71,24 +78,72 @@ SCRIPT_FUNCTION(set_combine) {
return new_intrusive2<SetCombineImage>(input, image_combine);
}
SCRIPT_FUNCTION(enlarge) {
SCRIPT_PARAM(GeneratedImageP, input);
SCRIPT_PARAM_N(double, _("border size"), border_size);
return new_intrusive2<EnlargeImage>(input, border_size);
}
SCRIPT_FUNCTION(drop_shadow) {
SCRIPT_PARAM(GeneratedImageP, input);
SCRIPT_OPTIONAL_PARAM_N_(double, _("offset x"), offset_x);
SCRIPT_OPTIONAL_PARAM_N_(double, _("offset y"), offset_y);
SCRIPT_OPTIONAL_PARAM_N_(double, _("alpha"), alpha);
SCRIPT_OPTIONAL_PARAM_N_(double, _("blur radius"), blur_radius);
SCRIPT_OPTIONAL_PARAM_N_(Color, _("color"), color);
return new_intrusive6<DropShadowImage>(input, offset_x, offset_y, alpha, blur_radius, color);
}
SCRIPT_FUNCTION(symbol_variation) {
// find symbol
SCRIPT_PARAM(ValueP, symbol);
SymbolValueP value = dynamic_pointer_cast<SymbolValue>(symbol);
SCRIPT_PARAM(String, variation);
// find style
SCRIPT_PARAM(Set*, set);
SCRIPT_OPTIONAL_PARAM_(CardP, card);
SymbolStyleP style = dynamic_pointer_cast<SymbolStyle>(set->stylesheetForP(card)->styleFor(value->fieldP));
if (!style) throw InternalError(_("Symbol value has a style of the wrong type"));
// find variation
FOR_EACH(v, style->variations) {
if (v->name == variation) {
// found it
return new_intrusive3<SymbolToImage>(value->filename, value->last_update, v);
SCRIPT_OPTIONAL_PARAM(String, variation) {
// find style
SCRIPT_PARAM(Set*, set);
SCRIPT_OPTIONAL_PARAM_(CardP, card);
SymbolStyleP style = dynamic_pointer_cast<SymbolStyle>(set->stylesheetForP(card)->styleFor(value->fieldP));
if (!style) throw InternalError(_("Symbol value has a style of the wrong type"));
// find variation
FOR_EACH(v, style->variations) {
if (v->name == variation) {
// found it
return new_intrusive3<SymbolToImage>(value->filename, value->last_update, v);
}
}
throw ScriptError(_("Variation of symbol not found ('") + variation + _("')"));
} else {
// custom variation
SCRIPT_PARAM_N(double, _("border radius"), border_radius);
SCRIPT_OPTIONAL_PARAM_N_(String, _("fill type"), fill_type);
SymbolVariationP var(new SymbolVariation);
var->border_radius = border_radius;
if (fill_type == _("solid") || fill_type.empty()) {
SCRIPT_PARAM_N(Color, _("fill color"), fill_color);
SCRIPT_PARAM_N(Color, _("border color"), border_color);
var->filter = new_intrusive2<SolidFillSymbolFilter>(fill_color, border_color);
} else if (fill_type == _("linear gradient")) {
SCRIPT_PARAM_N(Color, _("fill color 1"), fill_color_1);
SCRIPT_PARAM_N(Color, _("border color 1"), border_color_1);
SCRIPT_PARAM_N(Color, _("fill color 2"), fill_color_2);
SCRIPT_PARAM_N(Color, _("border color 2"), border_color_2);
SCRIPT_PARAM_N(double, _("center x"), center_x);
SCRIPT_PARAM_N(double, _("center y"), center_y);
SCRIPT_PARAM_N(double, _("end x"), end_x);
SCRIPT_PARAM_N(double, _("end y"), end_y);
var->filter = new_intrusive8<LinearGradientSymbolFilter>(fill_color_1, border_color_1, fill_color_2, border_color_2
,center_x, center_y, end_x, end_y);
} else if (fill_type == _("radial gradient")) {
SCRIPT_PARAM_N(Color, _("fill color 1"), fill_color_1);
SCRIPT_PARAM_N(Color, _("border color 1"), border_color_1);
SCRIPT_PARAM_N(Color, _("fill color 2"), fill_color_2);
SCRIPT_PARAM_N(Color, _("border color 2"), border_color_2);
var->filter = new_intrusive4<RadialGradientSymbolFilter>(fill_color_1, border_color_1, fill_color_2, border_color_2);
} else {
throw ScriptError(_("Unknown fill type for symbol_variation: ") + fill_type);
}
return new_intrusive3<SymbolToImage>(value->filename, value->last_update, var);
}
throw ScriptError(_("Variation of symbol not found ('") + variation + _("')"));
}
SCRIPT_FUNCTION(built_in_image) {
......@@ -103,7 +158,10 @@ void init_script_image_functions(Context& ctx) {
ctx.setVariable(_("masked blend"), script_masked_blend);
ctx.setVariable(_("combine blend"), script_combine_blend);
ctx.setVariable(_("set mask"), script_set_mask);
ctx.setVariable(_("set alpha"), script_set_alpha);
ctx.setVariable(_("set combine"), script_set_combine);
ctx.setVariable(_("enlarge"), script_enlarge);
ctx.setVariable(_("drop shadow"), script_drop_shadow);
ctx.setVariable(_("symbol variation"), script_symbol_variation);
ctx.setVariable(_("built in image"), script_built_in_image);
}
......@@ -42,7 +42,10 @@ 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) {
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;
}
......
......@@ -274,6 +274,7 @@ template <> inline String from_script<String> (const ScriptValueP& va
template <> inline int from_script<int> (const ScriptValueP& value) { return *value; }
template <> inline double from_script<double> (const ScriptValueP& value) { return *value; }
template <> inline bool from_script<bool> (const ScriptValueP& value) { return *value; }
template <> inline Color from_script<Color> (const ScriptValueP& value) { return *value; }
// ----------------------------------------------------------------------------- : EOF
#endif
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