Files
MagicSetEditor2/src/gfx/generated_image.cpp
T
GenevensiS 0baa73ae7d fix bugs
- dimensions_of should now work in all contexts
- import_image now works in style files, although should be avoided, since it reloads the file every time the code in run, it should be used in one shot scripts, like import scripts and bulk modification scripts
- process_english_hints now correctly processes <singular> and <plural> tags
2026-04-24 13:18:40 +02:00

879 lines
33 KiB
C++

//+----------------------------------------------------------------------------+
//| Description: Magic Set Editor - Program to make card games |
//| Copyright: (C) Twan van Laarhoven and the other MSE developers |
//| License: GNU General Public License 2 or later (see file COPYING) |
//+----------------------------------------------------------------------------+
// ----------------------------------------------------------------------------- : Includes
#include <util/prec.hpp>
#include <gfx/generated_image.hpp>
#include <util/io/package.hpp>
#include <util/error.hpp>
#include <data/set.hpp>
#include <data/symbol.hpp>
#include <data/field/symbol.hpp>
#include <script/functions/json.hpp>
#include <render/symbol/filter.hpp>
#include <gui/util.hpp> // load_resource_image
#include <gui/web_request_window.hpp>
#include <wx/wfstream.h>
// ----------------------------------------------------------------------------- : GeneratedImage
ScriptType GeneratedImage::type() const { return SCRIPT_IMAGE; }
String GeneratedImage::typeName() const { return _TYPE_("image"); }
GeneratedImageP GeneratedImage::toImage() const {
return const_cast<GeneratedImage*>(this)->intrusive_from_this();
}
Image GeneratedImage::generateConform(const Options& options) {
return conform_image(generate(options),options);
}
Image conform_image(const Image& img, const GeneratedImage::Options& options) {
Image image = img;
// resize?
int iw = image.GetWidth(), ih = image.GetHeight();
if ((iw == options.width && ih == options.height) || (options.width == 0 && options.height == 0)) {
// zoom?
if (!almost_equal(options.zoom, 1.0)) {
image = resample(image, int(iw * options.zoom), int(ih * options.zoom));
} else {
// already the right size
}
} else if (options.height == 0) {
// width is given, determine height
int h = options.width * ih / iw;
image = resample(image, options.width, h);
} else if (options.width == 0) {
// height is given, determine width
int w = options.height * iw / ih;
image = resample(image, w, options.height);
} else if (options.preserve_aspect == ASPECT_FIT) {
// determine actual size of resulting image
int w, h;
if (iw * options.height > ih * options.width) { // too much height requested
w = options.width;
h = options.width * ih / iw;
} else {
w = options.height * iw / ih;
h = options.height;
}
image = resample(image, w, h);
} else {
if (options.preserve_aspect == ASPECT_BORDER && (options.width < options.height * 3) && (options.height < options.width * 3)) {
// preserve the aspect ratio if there is not too much difference
image = resample_preserve_aspect(image, options.width, options.height);
} else {
image = resample(image, options.width, options.height);
}
}
// saturate?
if (options.saturate) {
saturate(image, .1);
}
options.width = image.GetWidth();
options.height = image.GetHeight();
// rotate?
if (!almost_equal(options.angle, 0)) {
image = rotate_image(image, options.angle);
}
return image;
}
// ----------------------------------------------------------------------------- : BlankImage
Image BlankImage::generate(const Options& opt) {
int w = max(1, opt.width >= 0 ? opt.width : opt.height);
int h = max(1, opt.height >= 0 ? opt.height : opt.width);
Image img(w, h);
assert(img.Ok());
img.InitAlpha();
memset(img.GetAlpha(), 0, w * h);
return img;
}
bool BlankImage::operator == (const GeneratedImage& that) const {
const BlankImage* that2 = dynamic_cast<const BlankImage*>(&that);
return that2;
}
// ----------------------------------------------------------------------------- : LinearBlendImage
Image LinearBlendImage::generate(const Options& opt) {
Image img = image1->generate(opt);
linear_blend(img, image2->generate(opt), x1, y1, x2, y2);
return img;
}
ImageCombine LinearBlendImage::combine() const {
return image1->combine();
}
bool LinearBlendImage::operator == (const GeneratedImage& that) const {
const LinearBlendImage* that2 = dynamic_cast<const LinearBlendImage*>(&that);
return that2 && *image1 == *that2->image1
&& *image2 == *that2->image2
&& x1 == that2->x1 && y1 == that2->y1
&& x2 == that2->x2 && y2 == that2->y2;
}
// ----------------------------------------------------------------------------- : MaskedBlendImage
Image MaskedBlendImage::generate(const Options& opt) {
Image img = light->generate(opt);
mask_blend(img, dark->generate(opt), mask->generate(opt));
return img;
}
ImageCombine MaskedBlendImage::combine() const {
return light->combine();
}
bool MaskedBlendImage::operator == (const GeneratedImage& that) const {
const MaskedBlendImage* that2 = dynamic_cast<const MaskedBlendImage*>(&that);
return that2 && *light == *that2->light
&& *dark == *that2->dark
&& *mask == *that2->mask;
}
// ----------------------------------------------------------------------------- : CombineBlendImage
Image CombineBlendImage::generate(const Options& opt) {
Image img = image1->generate(opt);
combine_image(img, image2->generate(opt), image_combine);
return img;
}
ImageCombine CombineBlendImage::combine() const {
return image1->combine();
}
bool CombineBlendImage::operator == (const GeneratedImage& that) const {
const CombineBlendImage* that2 = dynamic_cast<const CombineBlendImage*>(&that);
return that2 && *image1 == *that2->image1
&& *image2 == *that2->image2
&& image_combine == that2->image_combine;
}
// ----------------------------------------------------------------------------- : SetMaskImage
Image SetMaskImage::generate(const Options& opt) {
Image img = image->generate(opt);
set_alpha(img, mask->generate(opt));
return img;
}
bool SetMaskImage::operator == (const GeneratedImage& that) const {
const SetMaskImage* that2 = dynamic_cast<const SetMaskImage*>(&that);
return that2 && *image == *that2->image
&& *mask == *that2->mask;
}
Image SetAlphaImage::generate(const Options& opt) {
Image img = image->generate(opt);
set_alpha(img, alpha);
return img;
}
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) {
return image->generate(opt);
}
ImageCombine SetCombineImage::combine() const {
return image_combine;
}
bool SetCombineImage::operator == (const GeneratedImage& that) const {
const SetCombineImage* that2 = dynamic_cast<const SetCombineImage*>(&that);
return that2 && *image == *that2->image
&& image_combine == that2->image_combine;
}
// ----------------------------------------------------------------------------- : FillImage
Image FillImage::generate(const Options& opt) {
Image img = image->generate(opt);
fill_image(img, color);
return img;
}
bool FillImage::operator == (const GeneratedImage& that) const {
const FillImage* that2 = dynamic_cast<const FillImage*>(&that);
return that2 && *image == *that2->image
&& color == that2->color;
}
// ----------------------------------------------------------------------------- : SaturateImage
Image SaturateImage::generate(const Options& opt) {
Image img = image->generate(opt);
saturate(img, amount);
return img;
}
bool SaturateImage::operator == (const GeneratedImage& that) const {
const SaturateImage* that2 = dynamic_cast<const SaturateImage*>(&that);
return that2 && *image == *that2->image
&& amount == that2->amount;
}
// ----------------------------------------------------------------------------- : BrightenImage
Image BrightenImage::generate(const Options& opt) {
Image img = image->generate(opt);
brighten(img, amount);
return img;
}
bool BrightenImage::operator == (const GeneratedImage& that) const {
const BrightenImage* that2 = dynamic_cast<const BrightenImage*>(&that);
return that2 && *image == *that2->image
&& amount == that2->amount;
}
// ----------------------------------------------------------------------------- : InvertImage
Image InvertImage::generate(const Options& opt) {
Image img = image->generate(opt);
invert(img);
return img;
}
bool InvertImage::operator == (const GeneratedImage& that) const {
const InvertImage* that2 = dynamic_cast<const InvertImage*>(&that);
return that2 && *image == *that2->image;
}
// ----------------------------------------------------------------------------- : RecolorImage
Image RecolorImage::generate(const Options& opt) {
Image img = image->generate(opt);
recolor(img, color);
return img;
}
bool RecolorImage::operator == (const GeneratedImage& that) const {
const RecolorImage* that2 = dynamic_cast<const RecolorImage*>(&that);
return that2 && *image == *that2->image
&& color == that2->color;
}
Image RecolorImage2::generate(const Options& opt) {
Image img = image->generate(opt);
recolor(img, red,green,blue,white);
return img;
}
bool RecolorImage2::operator == (const GeneratedImage& that) const {
const RecolorImage2* that2 = dynamic_cast<const RecolorImage2*>(&that);
return that2 && *image == *that2->image
&& red == that2->red
&& green == that2->green
&& blue == that2->blue
&& white == that2->white;
}
// ----------------------------------------------------------------------------- : FlipImage
Image FlipImageHorizontal::generate(const Options& opt) {
Image img = image->generate(opt);
return flip_image_horizontal(img);
}
bool FlipImageHorizontal::operator == (const GeneratedImage& that) const {
const FlipImageHorizontal* that2 = dynamic_cast<const FlipImageHorizontal*>(&that);
return that2 && *image == *that2->image;
}
Image FlipImageVertical::generate(const Options& opt) {
Image img = image->generate(opt);
return flip_image_vertical(img);
}
bool FlipImageVertical::operator == (const GeneratedImage& that) const {
const FlipImageVertical* that2 = dynamic_cast<const FlipImageVertical*>(&that);
return that2 && *image == *that2->image;
}
Image RotateImage::generate(const Options& opt) {
Image img = image->generate(opt);
return rotate_image(img,angle);
}
bool RotateImage::operator == (const GeneratedImage& that) const {
const RotateImage* that2 = dynamic_cast<const RotateImage*>(&that);
return that2 && *image == *that2->image
&& angle == that2->angle;
}
// ----------------------------------------------------------------------------- : EnlargeImage
Image EnlargeImage::generate(const Options& opt) {
// generate 'sub' image
Options sub_opt
( int(opt.width * (border_size < 0.5 ? 1 - 2 * border_size : 0))
, int(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 = int(w * border_size), dh = int(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
}
}
// transfer metadata
if (img.HasOption(wxIMAGE_OPTION_PNG_DESCRIPTION)) {
String metadata = transformAllEncodedRects(img.GetOption(wxIMAGE_OPTION_PNG_DESCRIPTION), RealRect::translate, dw, dh);
larger.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, metadata);
}
// done
return larger;
}
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;
}
// ----------------------------------------------------------------------------- : ResizeImage
Image ResizeImage::generate(const Options& opt) {
Image img = image->generate(opt);
return resample(img, width, height);
}
bool ResizeImage::operator == (const GeneratedImage& that) const {
const ResizeImage* that2 = dynamic_cast<const ResizeImage*>(&that);
return that2 && *image == *that2->image
&& width == that2->width
&& height == that2->height;
}
// ----------------------------------------------------------------------------- : StrokeImage
Image StrokeImage::generate(const Options& opt) {
// create enlarged image
Image base_img = base_image->generate(opt);
Image s_img = make_stroke_image(base_img, color, radius, blur);
if (include_image) {
int offset = radius + blur;
s_img.Paste(base_img, offset, offset, wxIMAGE_ALPHA_BLEND_COMPOSE);
}
return s_img;
}
bool StrokeImage::operator == (const GeneratedImage& that) const {
const StrokeImage* that2 = dynamic_cast<const StrokeImage*>(&that);
return that2 && *base_image == *that2->base_image
&& radius == that2->radius
&& blur == that2->blur
&& color == that2->color;
}
// ----------------------------------------------------------------------------- : BleedEdgedImage
Image BleedEdgedImage::generate(const Options& opt) {
// create enlarged image
Image base_img = base_image->generate(opt);
int w = base_img.GetWidth(), h = base_img.GetHeight();
if (w <= 3 || h <= 3) {
queue_message(MESSAGE_ERROR, _("Image too small to add bleed edge"));
return base_img;
}
bool is_landscape = w > h;
int dw = int(w * (horizontal_size > 0.0 ? horizontal_size : is_landscape ? 0.037 : 0.048));
int dh = int(h * (vertical_size > 0.0 ? vertical_size : is_landscape ? 0.048 : 0.037));
dw = min(w-1, max(0, dw));
dh = min(h-1, max(0, dh));
if (dw <= 0 && dh <= 0) {
return base_img;
}
int width = w + dw + dw, height = h + dh + dh;
UInt size = width * height;
Image img = wxImage(width, height, false);
img.InitAlpha();
Byte* pixels = img.GetData();
Byte* alpha = img.GetAlpha();
// fill with background color
for (UInt i = 0; i < size; ++i) {
pixels[3 * i + 0] = background_color.Red();
pixels[3 * i + 1] = background_color.Green();
pixels[3 * i + 2] = background_color.Blue();
alpha[i] = background_color.Alpha();
}
// paste original image
img.Paste(base_img, dw, dh, wxIMAGE_ALPHA_BLEND_COMPOSE);
int pixel, mirror, x_start, y_start, x_size, y_size;
// fill left border
x_start = 0;
y_start = dh;
x_size = dw;
y_size = height - dh - dh;
for (int y = 0; y < y_size; ++y) {
for (int x = 0; x < x_size; ++x) {
pixel = x_start + x + (y_start + y) * width;
mirror = 2 * dw - x + (y_start + y) * width;
pixels[3 * pixel + 0] = pixels[3 * mirror + 0];
pixels[3 * pixel + 1] = pixels[3 * mirror + 1];
pixels[3 * pixel + 2] = pixels[3 * mirror + 2];
alpha[pixel] = alpha[mirror];
}
}
// fill right border
x_start = width - dw;
y_start = dh;
x_size = dw;
y_size = height - dh - dh;
for (int y = 0; y < y_size; ++y) {
for (int x = 0; x < x_size; ++x) {
pixel = x_start + x + (y_start + y) * width;
mirror = - 2 + x_start - x + (y_start + y) * width;
pixels[3 * pixel + 0] = pixels[3 * mirror + 0];
pixels[3 * pixel + 1] = pixels[3 * mirror + 1];
pixels[3 * pixel + 2] = pixels[3 * mirror + 2];
alpha[pixel] = alpha[mirror];
}
}
// fill top border
x_start = 0;
y_start = 0;
x_size = width;
y_size = dh;
for (int y = 0; y < y_size; ++y) {
for (int x = 0; x < x_size; ++x) {
pixel = x_start + x + (y_start + y) * width;
mirror = x_start + x + ( 2 * dh - y) * width;
pixels[3 * pixel + 0] = pixels[3 * mirror + 0];
pixels[3 * pixel + 1] = pixels[3 * mirror + 1];
pixels[3 * pixel + 2] = pixels[3 * mirror + 2];
alpha[pixel] = alpha[mirror];
}
}
// fill bottom border
x_start = 0;
y_start = height - dh;
x_size = width;
y_size = dh;
for (int y = 0; y < y_size; ++y) {
for (int x = 0; x < x_size; ++x) {
pixel = x_start + x + ( y_start + y) * width;
mirror = x_start + x + (- 2 + y_start - y) * width;
pixels[3 * pixel + 0] = pixels[3 * mirror + 0];
pixels[3 * pixel + 1] = pixels[3 * mirror + 1];
pixels[3 * pixel + 2] = pixels[3 * mirror + 2];
alpha[pixel] = alpha[mirror];
}
}
// transfer metadata
if (base_img.HasOption(wxIMAGE_OPTION_PNG_DESCRIPTION)) {
String metadata = transformAllEncodedRects(base_img.GetOption(wxIMAGE_OPTION_PNG_DESCRIPTION), RealRect::translate, dw, dh);
img.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, metadata);
}
// done
return img;
}
bool BleedEdgedImage::operator == (const GeneratedImage& that) const {
const BleedEdgedImage* that2 = dynamic_cast<const BleedEdgedImage*>(&that);
return that2 && *base_image == *that2->base_image
&& horizontal_size == that2->horizontal_size
&& vertical_size == that2->vertical_size
&& background_color == that2->background_color;
}
// ----------------------------------------------------------------------------- : InsertedImage
Image InsertedImage::generate(const Options& opt) {
Image base_img = base_image->generate(opt);
Image inserted_img = inserted_image->generate(opt);
int base_x = offset_x < 0 ? -offset_x : 0;
int base_y = offset_y < 0 ? -offset_y : 0;
int inserted_x = offset_x < 0 ? 0 : offset_x;
int inserted_y = offset_y < 0 ? 0 : offset_y;
int width = max(base_x + base_img.GetWidth(), inserted_x + inserted_img.GetWidth());
int height = max(base_y + base_img.GetHeight(), inserted_y + inserted_img.GetHeight());
if (width <= 0) throw ScriptError(_ERROR_1_("negative image width", "insert_image"));
if (height <= 0) throw ScriptError(_ERROR_1_("negative image height", "insert_image"));
UInt size = width * height;
Image img = wxImage(width, height, false);
img.InitAlpha();
Byte* data = img.GetData();
Byte* alpha = img.GetAlpha();
Byte r = background_color.Red();
Byte g = background_color.Green();
Byte b = background_color.Blue();
Byte a = background_color.Alpha();
for (UInt i = 0; i < size; ++i) {
data[0] = r;
data[1] = g;
data[2] = b;
data += 3;
alpha[0] = a;
alpha += 1;
}
img.Paste(base_img, base_x, base_y, wxIMAGE_ALPHA_BLEND_COMPOSE);
img.Paste(inserted_img, inserted_x, inserted_y, wxIMAGE_ALPHA_BLEND_COMPOSE);
// transfer metadata
img.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, metadata_merge(base_img, inserted_img, base_x, base_y, inserted_x, inserted_y));
return img;
}
ImageCombine InsertedImage::combine() const {
return base_image->combine();
}
bool InsertedImage::operator == (const GeneratedImage& that) const {
const InsertedImage* that2 = dynamic_cast<const InsertedImage*>(&that);
return that2
&& *base_image == *that2->base_image
&& *inserted_image == *that2->inserted_image
&& offset_x == that2->offset_x
&& offset_y == that2->offset_y;
}
// ----------------------------------------------------------------------------- : CropImage
Image CropImage::generate(const Options& opt) {
if (width <= 0) throw ScriptError(_ERROR_1_("negative image width", "crop_image"));
if (height <= 0) throw ScriptError(_ERROR_1_("negative image height", "crop_image"));
Image base_img = image->generate(opt);
Image img = base_img.Size(wxSize((int)width, (int)height), wxPoint(-(int)offset_x, -(int)offset_y)); //Image img = base_img.Size(wxSize((int)width, (int)height), wxPoint(-(int)offset_x, -(int)offset_y), background_color.Red(), background_color.Green(), background_color.Blue());
// transfer metadata
if (base_img.HasOption(wxIMAGE_OPTION_PNG_DESCRIPTION)) {
String metadata = transformAllEncodedRects(base_img.GetOption(wxIMAGE_OPTION_PNG_DESCRIPTION), RealRect::translate, -offset_x, -offset_y);
// prune out of bounds cards
boost::json::array cardsv = metadata_to_json(metadata);
boost::json::array inbounds_cardsv;
for (size_t i = 0; i < cardsv.size(); i++) {
boost::json::object cardv = cardsv[i].as_object();
if (cardv.contains("bounds")) {
String bounds = String(cardv["bounds"].as_string().c_str());
RealRect rect(0.0, 0.0, 0.0, 0.0);
int degrees = 0;
if (decodeRectFromString(bounds, rect, degrees)) {
rect = rect.intersect(RealRect(0.0, 0.0, width, height));
if (rect.width <= 0.0 || rect.height <= 0.0 ) continue;
}
}
inbounds_cardsv.emplace_back(cardv);
}
metadata = "<mse-card-data>" + json_ugly_print(inbounds_cardsv) + "</mse-card-data>";
img.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, metadata);
}
return img;
}
bool CropImage::operator == (const GeneratedImage& that) const {
const CropImage* that2 = dynamic_cast<const CropImage*>(&that);
return that2 && *image == *that2->image
&& width == that2->width && height == that2->height
&& offset_x == that2->offset_x && offset_y == that2->offset_y
&& background_color == that2->background_color;
}
// ----------------------------------------------------------------------------- : 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
auto blur_x = make_unique<UInt[]>(w*h); // scaled by total_x, so in [0..255*total_x]
memset(blur_x.get(), 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;
}
}
}
}
}
return total_x * total_y;
}
Image DropShadowImage::generate(const Options& opt) {
// 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
auto shadow = make_unique<UInt[]>(w*h);
UInt total = 255 * gaussian_blur(alpha, shadow.get(), w, h, shadow_blur_radius);
// combine
Byte* data = img.GetData();
int dw = int(w * offset_x), dh = int(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;
}
}
return img;
}
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) {
// TODO : use opt.width and opt.height?
// open file from package
if (!opt.package) throw ScriptError(_("Can only load images in a context where an image is expected"));
auto file_stream = opt.package->openIn(filename);
Image img;
if (image_load_file(img, *file_stream)) {
if (img.HasMask()) img.InitAlpha(); // we can't handle masks
return img;
} else {
throw ScriptError(_("Unable to load image '") + filename + _("' from '") + opt.package->name() + _("'"));
}
}
bool PackagedImage::operator == (const GeneratedImage& that) const {
const PackagedImage* that2 = dynamic_cast<const PackagedImage*>(&that);
return that2 && filename == that2->filename;
}
// ----------------------------------------------------------------------------- : BuiltInImage
Image BuiltInImage::generate(const Options& opt) {
// TODO : use opt.width and opt.height?
try {
Image img = load_resource_image(name);
if (img.Ok()) return img;
} catch (...) {}
throw ScriptError(_("There is no built in image '") + name + _("'"));
}
bool BuiltInImage::operator == (const GeneratedImage& that) const {
const BuiltInImage* that2 = dynamic_cast<const BuiltInImage*>(&that);
return that2 && name == that2->name;
}
// ----------------------------------------------------------------------------- : ArbitraryImage
Image ArbitraryImage::generate(const Options& opt) {
return image;
}
bool ArbitraryImage::operator == (const GeneratedImage& that) const {
const ArbitraryImage* that2 = dynamic_cast<const ArbitraryImage*>(&that);
return that2 && image.IsSameAs(that2->image);
}
// ----------------------------------------------------------------------------- : SymbolToImage
SymbolToImage::SymbolToImage(bool is_local, const LocalFileName& filename, Age age, const SymbolVariationP& variation)
: is_local(is_local), filename(filename), age(age), variation(variation)
{}
SymbolToImage::~SymbolToImage() {}
Image SymbolToImage::generate(const Options& opt) {
// TODO : use opt.width and opt.height?
Package* package = is_local ? opt.local_package : opt.package;
if (!package) throw ScriptError(_("Can only load images in a context where an image is expected"));
SymbolP the_symbol;
if (filename.empty()) {
the_symbol = default_symbol();
} else {
the_symbol = package->readFile<SymbolP>(filename);
}
int size = max(100, 3*max(opt.width,opt.height));
if (opt.width <= 1 || opt.height <= 1) {
return render_symbol(the_symbol, *variation->filter, variation->border_radius, size, size);
} else {
int width = size * opt.width / max(opt.width,opt.height);
int height = size * opt.height / max(opt.width,opt.height);
return render_symbol(the_symbol, *variation->filter, variation->border_radius, width, height, false, true);
}
}
bool SymbolToImage::operator == (const GeneratedImage& that) const {
const SymbolToImage* that2 = dynamic_cast<const SymbolToImage*>(&that);
return that2 && is_local == that2->is_local
&& filename == that2->filename
&& age == that2->age
&& (variation == that2->variation ||
*variation == *that2->variation // custom variation
);
}
// ----------------------------------------------------------------------------- : ImageValueToImage
ImageValueToImage::ImageValueToImage(const LocalFileName& filename, Age age)
: filename(filename), age(age)
{}
ImageValueToImage::~ImageValueToImage() {}
Image ImageValueToImage::generate(const Options& opt) {
// TODO : use opt.width and opt.height?
if (!opt.local_package) throw ScriptError(_("Can only load images in a context where an image is expected"));
Image image;
if (!filename.empty()) {
auto image_file_stream = opt.local_package->openIn(filename);
image_load_file(image, *image_file_stream);
}
if (!image.Ok()) {
image = Image(max(1,opt.width), max(1,opt.height));
}
return image;
}
bool ImageValueToImage::operator == (const GeneratedImage& that) const {
const ImageValueToImage* that2 = dynamic_cast<const ImageValueToImage*>(&that);
return that2 && filename == that2->filename
&& age == that2->age;
}
// ----------------------------------------------------------------------------- : SetMetadataImage
Image SetMetadataImage::generate(const Options& opt) {
Image img = image->generate(opt);
img.SetOption(wxIMAGE_OPTION_PNG_DESCRIPTION, metadata);
return img;
}
bool SetMetadataImage::operator == (const GeneratedImage& that) const {
const SetMetadataImage* that2 = dynamic_cast<const SetMetadataImage*>(&that);
return that2 && *image == *that2->image
&& metadata == that2->metadata;
}
// ----------------------------------------------------------------------------- : ImportedImage
ImportedImage::ImportedImage(Set* set, const String& filepath)
{
// has the set already been saved at least once?
if (set->needSaveAs()) throw ScriptError(_ERROR_1_("can't import image without set", filepath));
// determine save name
loadpath = filepath;
savename = normalize_internal_filename(loadpath + _(".png"));
savename.Replace(":", "-");
savename.Replace("/", "-");
// does the file pointed to by filepath exist?
if (!wxFileName(loadpath, wxPATH_UNIX).FileExists()) {
if (set->contains(savename)) return;
else throw ScriptError(_ERROR_1_("import not found", loadpath));
}
// is the file an image?
Image img;
img.LoadFile(loadpath);
if (!img.IsOk()) throw ScriptError(_ERROR_1_("import not image", loadpath));
// add the file to the set (or overwrite it if pre-existing), save set
auto outStream = set->openOut(savename);
img.SaveFile(*outStream, wxBITMAP_TYPE_PNG);
if (!outStream->IsOk()) throw ScriptError(_ERROR_1_("can't write image to set", loadpath));
outStream->Close();
set->save(false);
}
Image ImportedImage::generate(const Options& opt) {
auto imageInputStream = opt.local_package->openIn(savename);
Image img(*imageInputStream, wxBITMAP_TYPE_PNG);
if (!img.IsOk()) throw ScriptError(_ERROR_1_("can't import image", loadpath));
return img;
}
bool ImportedImage::operator == (const GeneratedImage& that) const {
const ImportedImage* that2 = dynamic_cast<const ImportedImage*>(&that);
return that2 && that2->loadpath == loadpath;
}
// ----------------------------------------------------------------------------- : DownloadedImage
DownloadedImage::DownloadedImage(Set* set, const String& url)
{
// has the set already been saved at least once?
if (set->needSaveAs()) throw ScriptError(_ERROR_1_("can't download image without set", url));
// determine save name
loadpath = url;
savename = normalize_internal_filename(loadpath + _(".png"));
savename.Replace(":", "-");
savename.Replace("/", "-");
// can we download the data?
WebRequestWindow wnd(loadpath);
if (wnd.ShowModal() != wxID_OK) {
if (set->contains(savename)) return;
else throw ScriptError(_ERROR_1_("can't download image", loadpath));
}
// is the data an image?
const String& content_type = wnd.out.GetContentType();
if (!content_type.StartsWith(_("image"))) throw ScriptError(_ERROR_1_("download not image", loadpath));
Image img(*wnd.out.GetStream());
if (!img.IsOk()) throw ScriptError(_ERROR_("web request corrupted"));
// add the file to the set (or overwrite it if pre-existing), save set
auto outStream = set->openOut(savename);
img.SaveFile(*outStream, wxBITMAP_TYPE_PNG);
if (!outStream->IsOk()) throw ScriptError(_ERROR_1_("can't write image to set", loadpath));
outStream->Close();
set->save(false);
}
Image DownloadedImage::generate(const Options& opt) {
auto imageInputStream = opt.local_package->openIn(savename);
Image img(*imageInputStream, wxBITMAP_TYPE_PNG);
if (!img.IsOk()) throw ScriptError(_ERROR_1_("can't download image", loadpath));
return img;
}
bool DownloadedImage::operator == (const GeneratedImage& that) const {
const DownloadedImage* that2 = dynamic_cast<const DownloadedImage*>(&that);
return that2 && that2->loadpath == loadpath;
}