diff --git a/src/data/set.cpp b/src/data/set.cpp index e219b916..36cb1841 100644 --- a/src/data/set.cpp +++ b/src/data/set.cpp @@ -46,11 +46,10 @@ IMPLEMENT_REFLECTION(Set) { if (tag.reading()) { data.init(game->set_fields); } - WITH_DYNAMIC_ARG(game_for_reading, game.get()) { - REFLECT(stylesheet); - REFLECT_N("set_info", data); - REFLECT(cards); - } + WITH_DYNAMIC_ARG(game_for_reading, game.get()); + REFLECT(stylesheet); + REFLECT_N("set_info", data); + REFLECT(cards); } REFLECT(apprentice_code); } diff --git a/src/gfx/blend_image.cpp b/src/gfx/blend_image.cpp new file mode 100644 index 00000000..7cbd4eab --- /dev/null +++ b/src/gfx/blend_image.cpp @@ -0,0 +1,88 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include +#include + +// ----------------------------------------------------------------------------- : Linear Blend + +// sqr(x) = x^2 +template inline T sqr(T x) { return x * x; } + +void linear_blend(Image& img1, const Image& img2, double x1,double y1, double x2,double y2) { + int width = img1.GetWidth(), height = img1.GetHeight(); + if (img2.GetWidth() != width || img2.GetHeight() != height) { + throw Error(_("Images used for blending must have the same size")); + } + + const int fixed = 1<<16; // fixed point multiplier + // equation: + // x * xm + y * ym + d == fixed * f(x,y) + // xm and ym are multiples of delta x/y: + // xm = a w (x2-x1); ym = a h (y2-y1) + // known values + // f(x1*w, y1*h) = 0 + // f(x2*w, y2*h) = 1 + // filling in: + // x1 * w * a * w * (x2-x1) + y1 * h * a * h * (y2-y1) + d == 0 + // x2 * w * a * w * (x2-x1) + y2 * h * a * h * (y2-y1) + d == fixed + // solving for a and d: + // (using dx = x1-x2, dy = y1-y2) + // a = fixed / (w^2 dx^2 + h^2 dy^2) + // d = a * (w^2 x1 dx + h^2 y1 dy) + if (x1==x2 && y1==y2) throw Error(_("Coordinates for blending overlap")); + double a = fixed / (sqr(width) * sqr(x1-x2) + sqr(height) * sqr(y1-y2)); + int xm = (x2 - x1) * width * a; + int ym = (y2 - y1) * height * a; + int d = - (x1 * width * xm + y1 * width * ym); + + Byte *data1 = img1.GetData(), *data2 = img2.GetData(); + // blend pixels + for (int y = 0 ; y < height ; ++y) { + for (int x = 0 ; x < width ; ++x) { + int mult = x * xm + y * ym + d; + if (mult < 0) mult = 0; + if (mult > fixed) mult = fixed; + data1[0] = data1[0] + mult * (data2[0] - data1[0]) / fixed; + data1[1] = data1[1] + mult * (data2[1] - data1[1]) / fixed; + data1[2] = data1[2] + mult * (data2[2] - data1[2]) / fixed; + data1 += 3; + data2 += 3; + } + } +} + +// ----------------------------------------------------------------------------- : Mask Blend + +void mask_blend(Image& img1, const Image& img2, const Image& mask) { + if (img2.GetWidth() != img1.GetWidth() || img2.GetHeight() != img1.GetHeight() + || mask.GetWidth() != img1.GetWidth() || mask.GetHeight() != img1.GetHeight()) { + throw Error(_("Images used for blending must have the same size")); + } + + UInt size = img1.GetWidth() * img1.GetHeight() * 3; + Byte *data1 = img1.GetData(), *data2 = img2.GetData(), *dataM = mask.GetData(); + // for each subpixel... + for (UInt i = 0 ; i < size ; ++i) { + data1[i] = (data1[i] * dataM[i] + data2[i] * (255 - dataM[i])) / 255; + } +} + +// ----------------------------------------------------------------------------- : Alpha + +void set_alpha(Image& img, const Image& img_alpha) { + if (img.GetWidth() != img_alpha.GetWidth() || img.GetHeight() != img_alpha.GetHeight()) { + throw InternalError(_("Image used with maks must have same size as mask")); + } + if (!img.HasAlpha()) img.InitAlpha(); + Byte *im = img.GetAlpha(), *al = img_alpha.GetData(); + UInt size = img.GetWidth() * img.GetHeight(); + for (UInt i = 0 ; i < size ; ++i) { + im[i] = (im[i] * al[i*3]) / 255; + } +} diff --git a/src/gfx/combine_image.cpp b/src/gfx/combine_image.cpp index 79b22445..119f84ff 100644 --- a/src/gfx/combine_image.cpp +++ b/src/gfx/combine_image.cpp @@ -6,9 +6,8 @@ // ----------------------------------------------------------------------------- : Includes -#include "../util/prec.hpp" -#include "../util/reflect.hpp" -#include "gfx.hpp" +#include +#include #include using namespace std; diff --git a/src/gfx/gfx.hpp b/src/gfx/gfx.hpp index 92a2e02c..dc6d3060 100644 --- a/src/gfx/gfx.hpp +++ b/src/gfx/gfx.hpp @@ -19,23 +19,11 @@ // ----------------------------------------------------------------------------- : Resampling /// Resample (resize) an image, uses bilenear filtering -/** The algorithm first resizes in horizontally, then vertically, - * the two passes are essentially the same: - * - for each row: - * - each input pixel becomes a fixed amount of output (in 1< +#include + +// ----------------------------------------------------------------------------- : Saturation + +void saturate(Image& image, int amount) { + if (amount == 0) return; // nothing to do + int factor = 300 / amount; + int div = factor - 2; + // for each pixel... + Byte* pix = image.GetData(); + Byte* end = pix + image.GetWidth() * image.GetHeight() * 3; + while (pix != end) { + int r = pix[0], g = pix[1], b = pix[2]; + int r2 = (factor * r - g - b) / div; + int g2 = (factor * g - r - b) / div; + int b2 = (factor * b - r - g) / div; + pix[0] = col(r2); + pix[1] = col(g2); + pix[2] = col(b2); + pix += 3; + } +} diff --git a/src/gfx/resample_image.cpp b/src/gfx/resample_image.cpp new file mode 100644 index 00000000..0726110c --- /dev/null +++ b/src/gfx/resample_image.cpp @@ -0,0 +1,166 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2006 Twan van Laarhoven | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +// ----------------------------------------------------------------------------- : Includes + +#include +#include + +// ----------------------------------------------------------------------------- : Resample passes + +// bitshift for fixed point numbers +// higher is less error +// we will get errors if 2^shift * imagesize becomes too large +const int shift = 32-10-8; // => max size = 1024, max alpha = 255 + +// Resample an image only in a single direction, either horizontally or vertically +/* Terms are based on x resampling (keeping the same number of lines): + * offset = number of elements to skip at the start + * length = length of a line + * delta = number of elements between pixels in a lines + * lines = number of lines + * line_delta = number of elements between the the first pixel of two lines + * 1 element = 3 bytes in data, 1 byte in alpha + */ +void resample_pass(const Image& img_in, Image& img_out, int offset_in, int offset_out, + int length_in, int delta_in, int length_out, int delta_out, + int lines, int line_delta_in, int line_delta_out) +{ + bool alpha = img_in.HasAlpha(); + if (alpha) img_out.InitAlpha(); + int out_fact = (length_out << shift) / length_in; // how much to output for 256 input = 1 pixel + int out_rest = (length_out << shift) % length_in; + // for each line + for (int l = 0 ; l < lines ; ++l) { + Byte* in = img_in .GetData() + 3 * (offset_in + line_delta_in); + Byte* out = img_out.GetData() + 3 * (offset_out + line_delta_out); + UInt in_rem = out_fact + out_rest; // remaining to input from the current input pixel + + if (alpha) { + Byte* in_a = img_in .GetAlpha() + (offset_in + line_delta_in); + Byte* out_a = img_out.GetAlpha() + (offset_out + line_delta_out); + + for (int x = 0 ; x < length_out ; ++x) { + UInt out_rem = 1 << shift; + UInt totR = 0, totG = 0, totB = 0, totA = 0; + while (out_rem >= in_rem) { + // eat a whole input pixel + totR += in[0] * in_rem * in_a[0]; // multiply by alpha + totG += in[1] * in_rem * in_a[0]; + totB += in[2] * in_rem * in_a[0]; + totA += in_a[0] * in_rem; + out_rem -= in_rem; + in_rem = out_fact; + in += 3*delta_in; in_a += delta_in; + } + if (out_rem > 0) { + // eat a partial input pixel + totR += in[0] * out_rem * in_a[0]; + totG += in[1] * out_rem * in_a[0]; + totB += in[2] * out_rem * in_a[0]; + totA += in_a[0] * out_rem; + in_rem -= out_rem; + } + // store + if (totA) { + out[0] = totR / totA; + out[1] = totG / totA; + out[2] = totB / totA; + out_a[0] = totA >> shift; + } else { + out[0] = out[1] = out[2] = out_a[0] = 0; // div by 0 is bad + } + out += 3*delta_out; out_a += delta_out; + } + + } else { + // no alpha + for (int x = 0 ; x < length_out ; ++x) { + UInt out_rem = 1 << shift; + UInt totR = 0, totG = 0, totB = 0; + while (out_rem >= in_rem) { + // eat a whole input pixel + totR += in[0] * in_rem; + totG += in[1] * in_rem; + totB += in[2] * in_rem; + out_rem -= in_rem; + in_rem = out_fact; + in += 3*delta_in; + } + if (out_rem > 0) { + // eat a partial input pixel + totR += in[0] * out_rem; + totR += in[1] * out_rem; + totR += in[2] * out_rem; + in_rem -= out_rem; + } + // store + out[0] = totR >> shift; + out[1] = totG >> shift; + out[2] = totB >> shift; + out += 3*delta_out; + } + } + } +} + +// ----------------------------------------------------------------------------- : Resample + +/* The algorithm first resizes in horizontally, then vertically, + * the two passes are essentially the same: + * - for each row: + * - each input pixel becomes a fixed amount of output (in 1< // ----------------------------------------------------------------------------- : Implementation diff --git a/src/mse.vcproj b/src/mse.vcproj index 823c3ef3..9603ddba 100644 --- a/src/mse.vcproj +++ b/src/mse.vcproj @@ -1052,6 +1052,9 @@ + + + + + + (Image(1,1)); + } +} + +ScriptImageP ScriptableImage::generate(Context& ctx, UInt width, UInt height, PreserveAspect preserve_aspect, bool saturate) const { + ScriptImageP image = generate(ctx); + if (!image->image.Ok()) { + // return an image so we don't fail + image->image = Image(1,1); + } + UInt iw = image->image.GetWidth(), ih = image->image.GetHeight(); + if ((iw == width && ih == height) || width == 0) { + // already the right size + } else if (preserve_aspect == ASPECT_FIT) { + // determine actual size of resulting image + UInt w, h; + if (iw * height > ih * width) { // too much height requested + w = width; + h = width * ih / iw; + } else { + w = height * iw / ih; + h = height; + } + Image resampled_image(w, h, false); + resample(image->image, resampled_image); + image->image = resampled_image; + } else { + Image resampled_image(width, height, false); + if (preserve_aspect == ASPECT_BORDER && (width < height * 3) && (height < width * 3)) { + // preserve the aspect ratio if there is not too much difference + resample_preserve_aspect(image->image, resampled_image); + } else { + resample(image->image, resampled_image); + } + image->image = resampled_image; + } + if (saturate) { + ::saturate(image->image, 40); + } + return image; +} + ScriptImageP ScriptableImage::update(Context& ctx, UInt width, UInt height, PreserveAspect preserve_aspect, bool saturate) { // up to date? - if (!cache || (UInt)cache->image.GetWidth() != width || (UInt)cache->image.GetHeight() == height) { + if (!cache || (UInt)cache->image.GetWidth() != width || (UInt)cache->image.GetHeight() == height || !upToDate(ctx, last_update)) { // cache must be updated cache = generate(ctx, width, height, preserve_aspect, saturate); last_update.update(); @@ -68,6 +119,10 @@ ScriptImageP ScriptableImage::update(Context& ctx, UInt width, UInt height, Pres return cache; } +bool ScriptableImage::upToDate(Context& ctx, Age age) const { + WITH_DYNAMIC_ARG(last_update_age, age.get()); + return (int)*script.invoke(ctx); +} // ----------------------------------------------------------------------------- : Reflection diff --git a/src/util/age.hpp b/src/util/age.hpp index 56fe5ec2..aa8eabcf 100644 --- a/src/util/age.hpp +++ b/src/util/age.hpp @@ -41,6 +41,9 @@ class Age { /// Compare two ages, smaller means earlier inline bool operator < (Age a) const { return age < a.age; } + /// A number corresponding to the age + inline LONG get() const { return age; } + private: /// This age LONG age; diff --git a/src/util/dynamic_arg.hpp b/src/util/dynamic_arg.hpp index f95d104d..82890616 100644 --- a/src/util/dynamic_arg.hpp +++ b/src/util/dynamic_arg.hpp @@ -27,7 +27,7 @@ /// Declare a dynamic argument. /** The value of the argument can be got with: name() - * To change the value use WITH_DYNAMIC_ARG(name, newValue) { ... } + * To change the value use WITH_DYNAMIC_ARG(name, newValue) * To be used in a header file. Use IMPLEMENT_DYN_ARG in a source file */ #define DECLARE_DYNAMIC_ARG(Type, name) \ @@ -42,7 +42,6 @@ inline ~name##_changer() { \ name##_private = oldValue; \ } \ - inline operator bool() { return true; } \ private: \ Type oldValue; \ } @@ -55,14 +54,15 @@ /** Usage: * @code * // here name() == old value - * WITH_DYNAMIC_ARG(name, newValue) { + * { + * WITH_DYNAMIC_ARG(name, newValue); * // here name() == newValue * } * // here name() == old value * @endcode */ #define WITH_DYNAMIC_ARG(name, value) \ - if (name##_changer name##_dummmy = value) // hack: variable in if guard scopes over the following block + name##_changer name##_dummmy(value) // ----------------------------------------------------------------------------- : EOF #endif