Main Page   Class Hierarchy   Alphabetical List   Compound List   File List   Compound Members   File Members  

panda/src/pnmimage/pnm-image-filter.cxx

Go to the documentation of this file.
00001 // Filename: pnm-image-filter.cxx
00002 // Created by:  
00003 //
00004 ////////////////////////////////////////////////////////////////////
00005 //
00006 // PANDA 3D SOFTWARE
00007 // Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
00008 //
00009 // All use of this software is subject to the terms of the Panda 3d
00010 // Software license.  You should have received a copy of this license
00011 // along with this source code; you will also find a current copy of
00012 // the license at http://www.panda3d.org/license.txt .
00013 //
00014 // To contact the maintainers of this program write to
00015 // panda3d@yahoogroups.com .
00016 //
00017 ////////////////////////////////////////////////////////////////////
00018 
00019 // The functions in this module support spatial filtering of an image by
00020 // convolution with an (almost) arbitrary kernel.  There are a broad class of
00021 // image filters to which this principle applies, including box filtering,
00022 // Bartlett, and Gaussian.
00023 
00024 // This particular approach breaks the 2-d kernel into two 1-d kernels, which
00025 // improves performance at the expense of limiting the types of filters that
00026 // may be applied.  In general, any of the square-based kernels are still
00027 // applicable; the only circle-based kernel that still works with this
00028 // approach is Gaussian.  Furthermore, the implementation assume that the
00029 // kernel is symmetric about zero.
00030 
00031 // The image is filtered first along one axis, then along the other.  This
00032 // decreases the complexity of the convolution operation: it is faster to
00033 // convolve twice with a one-dimensional kernel than once with a two-
00034 // dimensional kernel.  In the interim, a temporary matrix of type StoreType
00035 // (a numeric type, described below) is built which contains the results
00036 // from the first convolution.  The entire process is then repeated for
00037 // each channel in the image.
00038 
00039 #include <pandabase.h>
00040 #include <math.h>
00041 #include <cmath.h>
00042 
00043 #include "pnmImage.h"
00044 
00045 // WorkType is an abstraction that allows the filtering process to be
00046 // recompiled to use either floating-point or integer arithmetic.  On SGI
00047 // machines, there doesn't seem to be much of a performance difference--
00048 // if anything, the integer arithmetic is slower--though certainly other
00049 // architectures may differ.
00050 
00051 // A StoreType is a numeric type that is used to store the intermediate values
00052 // of the filtering computation; temporary calculations are performed using
00053 // WorkTypes.  source_max represents the largest value that will be stored in
00054 // a StoreType, while filter_max represents the largest value that will
00055 // multiplied by it as a weighted filter (source_max * filter_max must fit
00056 // comfortably within a WorkType, with room to spare).
00057 
00058 // Floating-point arithmetic is slightly faster and slightly more precise,
00059 // but the main reason to use it is that it is conceptually easy to understand.
00060 // All values are scaled in the range 0..1, as they should be.
00061 
00062 // The biggest reason to use integer arithmetic is space.  A table of
00063 // StoreTypes must be allocated to match the size of the image.  On an SGI,
00064 // sizeof(float) is 4, while sizeof(short) is 2 and sizeof(char) is, of
00065 // course, 1.  Since the source precision is probably 8 bits anyway (there
00066 // are usually 8 bits per channel), it doesn't cost any precision at all to
00067 // use shorts, and not very much to use chars.
00068 
00069 /*
00070 // To use double-precision floating point, 8 bytes: (strictly for the neurotic)
00071 typedef double WorkType;
00072 typedef double StoreType;
00073 static const WorkType source_max = 1.0;
00074 static const WorkType filter_max = 1.0;
00075 */
00076 
00077 // To use single-precision floating point, 4 bytes:
00078 typedef double WorkType;
00079 typedef float StoreType;
00080 static const WorkType source_max = 1.0;
00081 static const WorkType filter_max = 1.0;
00082 
00083 /*
00084 // To use 16-bit integer arithmetic, 2 bytes:
00085 typedef unsigned long WorkType;
00086 typedef unsigned short StoreType;
00087 static const WorkType source_max = 65535;
00088 static const WorkType filter_max = 255;
00089 */
00090 
00091 /*
00092 // To use 8-bit integer arithmetic, 1 byte:
00093 typedef unsigned long WorkType;
00094 typedef unsigned char StoreType;
00095 static const WorkType source_max = 255;
00096 static const WorkType filter_max = 255;
00097 */
00098 
00099 
00100 
00101 // filter_row() filters a single row by convolving with a one-dimensional
00102 // kernel filter.  The kernel is defined by an array of weights in filter[],
00103 // where the ith element of filter corresponds to abs(d * scale), if scale>1.0,
00104 // and abs(d), if scale<=1.0, where d is the offset from the center and varies
00105 // from -filter_width to filter_width.
00106 
00107 // Note that filter_width is not necessarily the length of the array; it is
00108 // the radius of interest of the filter function.  The array may need to be
00109 // larger (by a factor of scale), to adequately cover all the values.
00110 
00111 static void
00112 filter_row(StoreType dest[], int dest_len,
00113            const StoreType source[], int source_len,
00114            double scale,                    //  == dest_len / source_len
00115            const WorkType filter[],
00116            double filter_width) {
00117   // If we are expanding the row (scale>1.0), we need to look at a fractional
00118   // granularity.  Hence, we scale our filter index by scale.  If we are
00119   // compressing (scale<1.0), we don't need to fiddle with the filter index, so
00120   // we leave it at one.
00121   double iscale = max(scale, 1.0);
00122 
00123   // Similarly, if we are expanding the row, we want to start the new row at
00124   // the far left edge of the original pixel, not in the center.  So we will
00125   // have a non-zero offset.
00126   int offset = (int)cfloor(iscale*0.5);
00127 
00128   for (int dest_x=0; dest_x<dest_len; dest_x++) {
00129     double center = (dest_x-offset)/scale;
00130 
00131     // left and right are the starting and ending ranges of the radius of
00132     // interest of the filter function.  We need to apply the filter to each
00133     // value in this range.
00134     int left = max((int)cfloor(center - filter_width), 0);
00135     int right = min((int)cceil(center + filter_width), source_len-1);
00136 
00137     // right_center is the point just to the right of the center.  This
00138     // allows us to flip the sign of the offset when we cross the center point.
00139     int right_center = (int)cceil(center);
00140 
00141     WorkType net_weight = 0;
00142     WorkType net_value = 0;
00143 
00144     int index, source_x;
00145 
00146     // This loop is broken into two pieces--the left of center and the right
00147     // of center--so we don't have to incur the overhead of calling fabs()
00148     // each time through the loop.
00149     for (source_x=left; source_x<right_center; source_x++) {
00150       index = (int)(iscale*(center-source_x));
00151       net_value += filter[index] * source[source_x];
00152       net_weight += filter[index];
00153     }
00154 
00155     for (; source_x<=right; source_x++) {
00156       index = (int)(iscale*(source_x-center));
00157       net_value += filter[index] * source[source_x];
00158       net_weight += filter[index];
00159     }
00160 
00161     if (net_weight>0) {
00162       dest[dest_x] = (StoreType)(net_value / net_weight);
00163     } else {
00164       dest[dest_x] = 0;
00165     }
00166   }
00167 }
00168 
00169 
00170 // The various filter functions are called before each axis scaling to build
00171 // an kernel array suitable for the given scaling factor.  Given a scaling
00172 // ratio of the axis (dest_len / source_len), and a width parameter supplied
00173 // by the user, they must build an array of filter values (described above)
00174 // and also set the radius of interest of the filter function.
00175 
00176 // The values of the elements of filter must completely cover the range
00177 // 0..filter_max; the array must have enough elements to include all indices
00178 // corresponding to values in the range -filter_width to filter_width.
00179 
00180 typedef void FilterFunction(double scale, double width,
00181                             WorkType *&filter, double &filter_width);
00182 
00183 static void
00184 box_filter_impl(double scale, double width,
00185                 WorkType *&filter, double &filter_width) {
00186   double fscale;
00187   if (scale < 1.0) {
00188     // If we are compressing the image, we want to expand the range of
00189     // the filter function to prevent dropping below the Nyquist rate.
00190     // Hence, we divide by scale.
00191     fscale = 1.0 / scale;
00192   } else {
00193 
00194     // If we are expanding the image, we want to increase the granularity
00195     // of the filter function since we will need to access fractional cel
00196     // values.  Hence, we multiply by scale.
00197     fscale = scale;
00198   }
00199   filter_width = width;
00200   int actual_width = (int)cceil((filter_width+1) * fscale);
00201 
00202   filter = new WorkType[actual_width];
00203 
00204   for (int i=0; i<actual_width; i++) {
00205     filter[i] = (i<=filter_width*fscale) ? filter_max : 0;
00206   }
00207 }
00208 
00209 static void
00210 gaussian_filter_impl(double scale, double width,
00211                      WorkType *&filter, double &filter_width) {
00212   double fscale;
00213   if (scale < 1.0) {
00214     // If we are compressing the image, we want to expand the range of
00215     // the filter function to prevent dropping below the Nyquist rate.
00216     // Hence, we divide by scale (to make fscale larger).
00217     fscale = 1.0 / scale;
00218   } else {
00219 
00220     // If we are expanding the image, we want to increase the granularity
00221     // of the filter function since we will need to access fractional cel
00222     // values.  Hence, we multiply by scale (to make fscale larger).
00223     fscale = scale;
00224   }
00225   double sigma = width/2;
00226   filter_width = 3.0 * sigma;
00227   int actual_width = (int)cceil((filter_width+1) * fscale);
00228 
00229   // G(x, y) = (1/(2 pi sigma^2)) * exp( - (x^2 + y^2) / (2 sigma^2))
00230 
00231   // (We can throw away the initial factor, since these weights will all
00232   // be normalized; and we're only computing a 1-dimensional function,
00233   // so we can ignore the y^2.)
00234 
00235   filter = new WorkType[actual_width];
00236   double div = 2*sigma*sigma;
00237 
00238   for (int i=0; i<actual_width; i++) {
00239     double x = i/fscale;
00240     filter[i] = (WorkType)(filter_max * exp(-x*x / div));
00241     // The highest value of the exp function in this range is always 1.0,
00242     // at index value 0.  Thus, we scale the whole range by filter_max,
00243     // to produce a filter in the range [0..filter_max].
00244   }
00245 }
00246 
00247 
00248 // We have a function, defined in pnm-image-filter-core.cxx, that will scale
00249 // an image in both X and Y directions for a particular channel, by setting
00250 // up the temporary matrix appropriately and calling the above functions.
00251 
00252 // What we really need is a series of such functions, one for each channel,
00253 // and also one to scale by X first, and one to scale by Y first.  This sounds
00254 // a lot like a C++ template: we want to compile the same function several
00255 // times to work on slightly different sorts of things each time.  However,
00256 // the things we want to vary are the particular member functions of PNMImage
00257 // that we call (e.g. Red(), Green(), etc.), and we can't declare a template
00258 // of member functions, only of types.
00259 
00260 // It's doable using templates.  It would involve the declaration of
00261 // lots of silly little functor objects.  This is much more compact
00262 // and no more difficult to read.
00263 
00264 // The function in pnm-image-filter-core.cxx uses macros to access the member
00265 // functions of PNMImage.  Hence, we only need to redefine those macros
00266 // with each instance of the function to cause each instance to operate on
00267 // the correct member.
00268 
00269 
00270 // These instances scale by X first, then by Y.
00271 
00272 #define FUNCTION_NAME filter_red_xy
00273 #define ASIZE get_x_size
00274 #define BSIZE get_y_size
00275 #define GETVAL(a, b) get_red(a, b)
00276 #define SETVAL(a, b, v) set_red(a, b, v)
00277 #include "pnm-image-filter-core.cxx"
00278 #undef SETVAL
00279 #undef GETVAL
00280 #undef BSIZE
00281 #undef ASIZE
00282 #undef FUNCTION_NAME
00283 
00284 #define FUNCTION_NAME filter_green_xy
00285 #define ASIZE get_x_size
00286 #define BSIZE get_y_size
00287 #define GETVAL(a, b) get_green(a, b)
00288 #define SETVAL(a, b, v) set_green(a, b, v)
00289 #include "pnm-image-filter-core.cxx"
00290 #undef SETVAL
00291 #undef GETVAL
00292 #undef BSIZE
00293 #undef ASIZE
00294 #undef FUNCTION_NAME
00295 
00296 #define FUNCTION_NAME filter_blue_xy
00297 #define ASIZE get_x_size
00298 #define BSIZE get_y_size
00299 #define GETVAL(a, b) get_blue(a, b)
00300 #define SETVAL(a, b, v) set_blue(a, b, v)
00301 #include "pnm-image-filter-core.cxx"
00302 #undef SETVAL
00303 #undef GETVAL
00304 #undef BSIZE
00305 #undef ASIZE
00306 #undef FUNCTION_NAME
00307 
00308 #define FUNCTION_NAME filter_gray_xy
00309 #define ASIZE get_x_size
00310 #define BSIZE get_y_size
00311 #define GETVAL(a, b) get_bright(a, b)
00312 #define SETVAL(a, b, v) set_xel(a, b, v)
00313 #include "pnm-image-filter-core.cxx"
00314 #undef SETVAL
00315 #undef GETVAL
00316 #undef BSIZE
00317 #undef ASIZE
00318 #undef FUNCTION_NAME
00319 
00320 #define FUNCTION_NAME filter_alpha_xy
00321 #define ASIZE get_x_size
00322 #define BSIZE get_y_size
00323 #define GETVAL(a, b) get_alpha(a, b)
00324 #define SETVAL(a, b, v) set_alpha(a, b, v)
00325 #include "pnm-image-filter-core.cxx"
00326 #undef SETVAL
00327 #undef GETVAL
00328 #undef BSIZE
00329 #undef ASIZE
00330 #undef FUNCTION_NAME
00331 
00332 
00333 // These instances scale by Y first, then by X.
00334 
00335 #define FUNCTION_NAME filter_red_yx
00336 #define ASIZE get_y_size
00337 #define BSIZE get_x_size
00338 #define GETVAL(a, b) get_red(b, a)
00339 #define SETVAL(a, b, v) set_red(b, a, v)
00340 #include "pnm-image-filter-core.cxx"
00341 #undef SETVAL
00342 #undef GETVAL
00343 #undef BSIZE
00344 #undef ASIZE
00345 #undef FUNCTION_NAME
00346 
00347 #define FUNCTION_NAME filter_green_yx
00348 #define ASIZE get_y_size
00349 #define BSIZE get_x_size
00350 #define GETVAL(a, b) get_green(b, a)
00351 #define SETVAL(a, b, v) set_green(b, a, v)
00352 #include "pnm-image-filter-core.cxx"
00353 #undef SETVAL
00354 #undef GETVAL
00355 #undef BSIZE
00356 #undef ASIZE
00357 #undef FUNCTION_NAME
00358 
00359 #define FUNCTION_NAME filter_blue_yx
00360 #define ASIZE get_y_size
00361 #define BSIZE get_x_size
00362 #define GETVAL(a, b) get_blue(b, a)
00363 #define SETVAL(a, b, v) set_blue(b, a, v)
00364 #include "pnm-image-filter-core.cxx"
00365 #undef SETVAL
00366 #undef GETVAL
00367 #undef BSIZE
00368 #undef ASIZE
00369 #undef FUNCTION_NAME
00370 
00371 #define FUNCTION_NAME filter_gray_yx
00372 #define ASIZE get_y_size
00373 #define BSIZE get_x_size
00374 #define GETVAL(a, b) get_bright(b, a)
00375 #define SETVAL(a, b, v) set_xel(b, a, v)
00376 #include "pnm-image-filter-core.cxx"
00377 #undef SETVAL
00378 #undef GETVAL
00379 #undef BSIZE
00380 #undef ASIZE
00381 #undef FUNCTION_NAME
00382 
00383 #define FUNCTION_NAME filter_alpha_yx
00384 #define ASIZE get_y_size
00385 #define BSIZE get_x_size
00386 #define GETVAL(a, b) get_alpha(b, a)
00387 #define SETVAL(a, b, v) set_alpha(b, a, v)
00388 #include "pnm-image-filter-core.cxx"
00389 #undef SETVAL
00390 #undef GETVAL
00391 #undef BSIZE
00392 #undef ASIZE
00393 #undef FUNCTION_NAME
00394 
00395 
00396 // filter_image pulls everything together, and filters one image into
00397 // another.  Both images can be the same with no ill effects.
00398 static void
00399 filter_image(PNMImage &dest, const PNMImage &source,
00400              double width, FilterFunction *make_filter) {
00401 
00402   // We want to scale by the smallest destination axis first, for a
00403   // slight performance gain.
00404 
00405   if (dest.get_x_size() <= dest.get_y_size()) {
00406     if (dest.is_grayscale() || source.is_grayscale()) {
00407       filter_gray_xy(dest, source, width, make_filter);
00408     } else {
00409       filter_red_xy(dest, source, width, make_filter);
00410       filter_green_xy(dest, source, width, make_filter);
00411       filter_blue_xy(dest, source, width, make_filter);
00412     }
00413 
00414     if (dest.has_alpha() && source.has_alpha()) {
00415       filter_alpha_xy(dest, source, width, make_filter);
00416     }
00417 
00418   } else {
00419     if (dest.is_grayscale() || source.is_grayscale()) {
00420       filter_gray_yx(dest, source, width, make_filter);
00421     } else {
00422       filter_red_yx(dest, source, width, make_filter);
00423       filter_green_yx(dest, source, width, make_filter);
00424       filter_blue_yx(dest, source, width, make_filter);
00425     }
00426 
00427     if (dest.has_alpha() && source.has_alpha()) {
00428       filter_alpha_yx(dest, source, width, make_filter);
00429     }
00430   }
00431 }
00432 
00433 
00434 
00435 ////////////////////////////////////////////////////////////////////
00436 //     Function: PNMImage::box_filter_from
00437 //       Access: Public
00438 //  Description: Makes a resized copy of the indicated image into this
00439 //               one using the indicated filter.  The image to be
00440 //               copied is squashed and stretched to match the
00441 //               dimensions of the current image, applying the
00442 //               appropriate filter to perform the stretching.
00443 ////////////////////////////////////////////////////////////////////
00444 void PNMImage::
00445 box_filter_from(double width, const PNMImage &copy) {
00446   filter_image(*this, copy, width, &box_filter_impl);
00447 }
00448 
00449 ////////////////////////////////////////////////////////////////////
00450 //     Function: PNMImage::gaussian_filter_from
00451 //       Access: Public
00452 //  Description: Makes a resized copy of the indicated image into this
00453 //               one using the indicated filter.  The image to be
00454 //               copied is squashed and stretched to match the
00455 //               dimensions of the current image, applying the
00456 //               appropriate filter to perform the stretching.
00457 ////////////////////////////////////////////////////////////////////
00458 void PNMImage::
00459 gaussian_filter_from(double width, const PNMImage &copy) {
00460   filter_image(*this, copy, width, &gaussian_filter_impl);
00461 }
00462 
00463 
00464 //
00465 // The following functions are support for quick_box_filter().
00466 //
00467 
00468 INLINE void
00469 box_filter_xel(const PNMImage &image,
00470                int x, int y, double x_contrib, double y_contrib,
00471                double &red, double &grn, double &blu, double &alpha,
00472                double &pixel_count) {
00473   double contrib = x_contrib * y_contrib;
00474   red += image.get_red_val(x, y) * contrib;
00475   grn += image.get_green_val(x, y) * contrib;
00476   blu += image.get_blue_val(x, y) * contrib;
00477   if (image.has_alpha()) {
00478     alpha += image.get_alpha_val(x, y) * contrib;
00479   }
00480 
00481   pixel_count += contrib;
00482 }
00483 
00484 
00485 INLINE void
00486 box_filter_line(const PNMImage &image,
00487                 double x0, int y, double x1, double y_contrib,
00488                 double &red, double &grn, double &blu, double &alpha,
00489                 double &pixel_count) {
00490   int x = (int)x0;
00491   // Get the first (partial) xel
00492   box_filter_xel(image, x, y, (double)(x+1)-x0, y_contrib,
00493                  red, grn, blu, alpha, pixel_count);
00494 
00495   int x_last = (int)x1;
00496   if (x < x_last) {
00497     x++;
00498     while (x < x_last) {
00499       // Get each consecutive (complete) xel
00500       box_filter_xel(image, x, y, 1.0, y_contrib,
00501                      red, grn, blu, alpha, pixel_count);
00502       x++;
00503     }
00504 
00505     // Get the final (partial) xel
00506     double x_contrib = x1 - (double)x_last;
00507     if (x_contrib > 0.0001) {
00508       box_filter_xel(image, x, y, x_contrib, y_contrib,
00509                      red, grn, blu, alpha, pixel_count);
00510     }
00511   }
00512 }
00513 
00514 static void
00515 box_filter_region(const PNMImage &image,
00516                   double x0, double y0, double x1, double y1,
00517                   xel &result, xelval &alpha_result) {
00518   double red = 0.0, grn = 0.0, blu = 0.0, alpha = 0.0;
00519   double pixel_count = 0.0;
00520 
00521   assert(y0 >=0 && y1 >=0);
00522 
00523   int y = (int)y0;
00524   // Get the first (partial) row
00525   box_filter_line(image, x0, y, x1, (double)(y+1)-y0,
00526                   red, grn, blu, alpha, pixel_count);
00527 
00528   int y_last = (int)y1;
00529   if (y < y_last) {
00530     y++;
00531     while (y < y_last) {
00532       // Get each consecutive (complete) row
00533       box_filter_line(image, x0, y, x1, 1.0,
00534                       red, grn, blu, alpha, pixel_count);
00535       y++;
00536     }
00537 
00538     // Get the final (partial) row
00539     double y_contrib = y1 - (double)y_last;
00540     if (y_contrib > 0.0001) {
00541       box_filter_line(image, x0, y, x1, y_contrib,
00542                       red, grn, blu, alpha, pixel_count);
00543     }
00544   }
00545 
00546   PPM_ASSIGN(result,
00547              (xelval)(red / pixel_count + 0.5),
00548              (xelval)(grn / pixel_count + 0.5),
00549              (xelval)(blu / pixel_count + 0.5));
00550 
00551   alpha_result = (xelval)(alpha / pixel_count + 0.5);
00552 }
00553 
00554 ////////////////////////////////////////////////////////////////////
00555 //     Function: PNMImage::quick_filter_from
00556 //       Access: Public
00557 //  Description: Resizes from the given image, with a fixed radius of
00558 //               0.5. This is a very specialized and simple algorithm
00559 //               that doesn't handle dropping below the Nyquist rate
00560 //               very well, but is quite a bit faster than the more
00561 //               general box_filter(), above.  If borders are
00562 //               specified, they will further restrict the size of the
00563 //               resulting image. There's no point in using
00564 //               quick_box_filter() on a single image.
00565 ////////////////////////////////////////////////////////////////////
00566 void PNMImage::
00567 quick_filter_from(const PNMImage &from, int xborder, int yborder) {
00568   int from_xs = from.get_x_size();
00569   int from_ys = from.get_y_size();
00570 
00571   int to_xs = get_x_size() - xborder;
00572   int to_ys = get_y_size() - yborder;
00573 
00574   int to_xoff = xborder / 2;
00575   int to_yoff = yborder / 2;
00576 
00577   double from_x0, from_x1, from_y0, from_y1;
00578   int to_x, to_y;
00579 
00580   double x_scale = (double)from_xs / (double)to_xs;
00581   double y_scale = (double)from_ys / (double)to_ys;
00582 
00583   from_y0 = max(0, -to_yoff) * y_scale;
00584   for (to_y = max(0, -to_yoff);
00585        to_y < min(to_ys, get_y_size()-to_yoff);
00586        to_y++) {
00587     from_y1 = (to_y+1) * y_scale;
00588 
00589     from_x0 = max(0, -to_xoff) * x_scale;
00590     for (to_x = max(0, -to_xoff);
00591          to_x < min(to_xs, get_x_size()-to_xoff);
00592          to_x++) {
00593       from_x1 = (to_x+1) * x_scale;
00594 
00595       // Now the box from (from_x0, from_y0) - (from_x1, from_y1)
00596       // but not including (from_x1, from_y1) maps to the pixel (to_x, to_y).
00597       xelval alpha_result;
00598       box_filter_region(from,
00599                         from_x0, from_y0, from_x1, from_y1,
00600                         (*this)[to_yoff + to_y][to_xoff + to_x],
00601                         alpha_result);
00602       if (has_alpha()) {
00603         set_alpha_val(to_xoff+to_x, to_yoff+to_y, alpha_result);
00604       }
00605 
00606       from_x0 = from_x1;
00607     }
00608     from_y0 = from_y1;
00609   }
00610 }

Generated on Fri May 2 00:43:05 2003 for Panda by doxygen1.3