# HG changeset patch
# User robcast
# Date 1445362674 -7200
# Node ID 52d0730b1f32af5083bea5e21641eea71f430da3
# Parent 51587d36fd444a766f828a2a6de685516e2126da# Parent 67134a81061b31656ea6cea322d64243625f2717
Merge from new_scaling branch
67134a81061b31656ea6cea322d64243625f2717
diff -r 51587d36fd44 -r 52d0730b1f32 common/pom.xml
--- a/common/pom.xml Mon Oct 19 12:44:26 2015 +0200
+++ b/common/pom.xml Tue Oct 20 19:37:54 2015 +0200
@@ -27,24 +27,17 @@
-
-
-
-
-
- false
-
- mygrid-repository
- myGrid Repository
- http://www.mygrid.org.uk/maven/repository
-
-
-
- net.java.dev.jai-imageio
- jai-imageio-core-standalone
- 1.2-pre-dr-b04-2011-07-04
-
+
+ com.github.jai-imageio
+ jai-imageio-core
+ 1.3.0
+
+
org.devlib.schmidt
imageinfo
diff -r 51587d36fd44 -r 52d0730b1f32 common/src/main/java/digilib/conf/DigilibConfiguration.java
--- a/common/src/main/java/digilib/conf/DigilibConfiguration.java Mon Oct 19 12:44:26 2015 +0200
+++ b/common/src/main/java/digilib/conf/DigilibConfiguration.java Tue Oct 20 19:37:54 2015 +0200
@@ -57,7 +57,7 @@
/** digilib version */
public static String getClassVersion() {
- return "2.3.1";
+ return "2.3.2";
}
/* non-static getVersion for Java inheritance */
diff -r 51587d36fd44 -r 52d0730b1f32 common/src/main/java/digilib/image/ImageJobDescription.java
--- a/common/src/main/java/digilib/image/ImageJobDescription.java Mon Oct 19 12:44:26 2015 +0200
+++ b/common/src/main/java/digilib/image/ImageJobDescription.java Tue Oct 20 19:37:54 2015 +0200
@@ -1,31 +1,5 @@
package digilib.image;
-/*
- * #%L
- * A class for storing the set of parameters necessary for scaling images
- * with an ImageWorker.
- *
- * %%
- * Copyright (C) 2001 - 2013 MPIWG Berlin
- * %%
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Lesser Public License for more details.
- *
- * You should have received a copy of the GNU General Lesser Public
- * License along with this program. If not, see
- * .
- * #L%
- * Author: Robert Casties (robcast@berlios.de)
- */
-
-import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
@@ -401,7 +375,6 @@
/**
* Returns image scaling factor.
* Uses image size and user parameters.
- * Modifies scaleXY, userImgArea.
*
* @return
* @throws IOException
@@ -416,29 +389,21 @@
/*
* calculate region of interest
*/
- double areaWidth;
- double areaHeight;
- // size of the currently selected input image
- ImageSize imgSize = getInput().getSize();
- // user area is in [0,1] coordinates
- Rectangle2D relUserArea = new Rectangle2D.Float(getWx(), getWy(), getWw(), getWh());
- // transform from relative [0,1] to image coordinates.
- AffineTransform imgTrafo = AffineTransform.getScaleInstance(imgSize.getWidth(), imgSize.getHeight());
- // transform user coordinate area to image coordinate area
- userImgArea = imgTrafo.createTransformedShape(relUserArea).getBounds2D();
-
+ userImgArea = getUserImgArea();
+ double areaWidth = userImgArea.getWidth();
+ double areaHeight = userImgArea.getHeight();
+
/*
* calculate scaling factor
*/
float ws = getAsFloat("ws");
- if (isScaleToFit()) {
+ if (isScaleToFit()) {
/*
* scale to fit -- scaling factor based on destination size and user area
*/
- areaWidth = (double) userImgArea.getWidth();
- areaHeight = (double) userImgArea.getHeight();
double scaleX = getDw() / areaWidth * ws;
double scaleY = getDh() / areaHeight * ws;
+ // use the smaller factor to get fit-in-box
scaleXY = (scaleX > scaleY) ? scaleY : scaleX;
} else if (isAbsoluteScale()) {
/*
@@ -473,25 +438,26 @@
scaleXY = (double) getAsFloat("scale");
// use original size if no destination size given
if (getDw() == 0 && getDh() == 0) {
- paramDW = (int) userImgArea.getWidth();
- paramDH = (int) userImgArea.getHeight();
+ paramDW = (int) areaWidth;
+ paramDH = (int) areaHeight;
}
}
// we need to correct the factor if we use a pre-scaled image
+ ImageSize imgSize = getInput().getSize();
ImageSize hiresSize = getHiresSize();
if (imgSize.getWidth() != hiresSize.getWidth()) {
scaleXY *= (double) hiresSize.getWidth() / (double) imgSize.getWidth();
}
- areaWidth = getDw() / scaleXY * ws;
- areaHeight = getDh() / scaleXY * ws;
+ areaWidth = (int) Math.round(getDw() / scaleXY * ws);
+ areaHeight = (int) Math.round(getDh() / scaleXY * ws);
// reset user area size
userImgArea.setRect(userImgArea.getX(), userImgArea.getY(), areaWidth, areaHeight);
} else {
/*
* crop to fit -- don't scale
*/
- areaWidth = getDw() * ws;
- areaHeight = getDh() * ws;
+ areaWidth = Math.round(getDw() * ws);
+ areaHeight = Math.round(getDh() * ws);
// reset user area size
userImgArea.setRect(userImgArea.getX(), userImgArea.getY(), areaWidth, areaHeight);
scaleXY = 1d;
@@ -507,20 +473,29 @@
* @throws IOException
*/
public int getDw() throws IOException {
- logger.debug("get_paramDW()");
+ //logger.debug("get_paramDW()");
if (paramDW == null) {
paramDW = getAsInt("dw");
paramDH = getAsInt("dh");
- float imgAspect = getInput().getAspect();
if (paramDW == 0) {
- // calculate dw
- paramDW = Math.round(paramDH * imgAspect);
+ /*
+ * calculate dw using aspect ratio of image area
+ */
+ userImgArea = getUserImgArea();
+ double imgAspect = userImgArea.getWidth() / userImgArea.getHeight();
+ // round up to make sure we don't squeeze dh
+ paramDW = (int) Math.ceil(paramDH * imgAspect);
setValue("dw", paramDW);
} else if (paramDH == 0) {
- // calculate dh
- paramDH = Math.round(paramDW / imgAspect);
+ /*
+ * calculate dh using aspect ratio of image area
+ */
+ userImgArea = getUserImgArea();
+ double imgAspect = userImgArea.getWidth() / userImgArea.getHeight();
+ // round up to make sure we don't squeeze dw
+ paramDH = (int) Math.ceil(paramDW / imgAspect);
setValue("dh", paramDH);
}
}
@@ -535,20 +510,29 @@
* @throws IOException
*/
public int getDh() throws IOException {
- logger.debug("get_paramDH()");
+ //logger.debug("get_paramDH()");
if (paramDH == null) {
paramDW = getAsInt("dw");
paramDH = getAsInt("dh");
- float imgAspect = getInput().getAspect();
if (paramDW == 0) {
- // calculate dw
- paramDW = Math.round(paramDH * imgAspect);
+ /*
+ * calculate dw using aspect ratio of image area
+ */
+ userImgArea = getUserImgArea();
+ double imgAspect = userImgArea.getWidth() / userImgArea.getHeight();
+ // round up to make sure we don't squeeze dh
+ paramDW = (int) Math.ceil(paramDH * imgAspect);
setValue("dw", paramDW);
} else if (paramDH == 0) {
- // calculate dh
- paramDH = Math.round(paramDW / imgAspect);
+ /*
+ * calculate dh using aspect ratio of image area
+ */
+ userImgArea = getUserImgArea();
+ double imgAspect = userImgArea.getWidth() / userImgArea.getHeight();
+ // round up to make sure we don't squeeze dw
+ paramDH = (int) Math.ceil(paramDW / imgAspect);
setValue("dh", paramDH);
}
}
@@ -563,7 +547,7 @@
* @throws IOException
*/
public Float getWw() throws IOException {
- logger.debug("get_paramWW()");
+ //logger.debug("get_paramWW()");
if (paramWW == null) {
paramWW = getAsFloat("ww");
if (hasOption("pxarea")) {
@@ -583,7 +567,7 @@
* @throws IOException
*/
public Float getWh() throws IOException {
- logger.debug("get_paramWH()");
+ //logger.debug("get_paramWH()");
if (paramWH == null) {
paramWH = getAsFloat("wh");
if (hasOption("pxarea")) {
@@ -603,7 +587,7 @@
* @throws IOException
*/
public Float getWx() throws IOException {
- logger.debug("get_paramWX()");
+ //logger.debug("get_paramWX()");
if (paramWX == null) {
paramWX = getAsFloat("wx");
if (hasOption("pxarea")) {
@@ -623,7 +607,7 @@
* @throws IOException
*/
public Float getWy() throws IOException {
- logger.debug("get_paramWY()");
+ //logger.debug("get_paramWY()");
if (paramWY == null) {
paramWY = getAsFloat("wy");
if (hasOption("pxarea")) {
@@ -641,7 +625,7 @@
* @return
*/
public int getScaleQual() {
- logger.debug("get_scaleQual()");
+ //logger.debug("get_scaleQual()");
int qual = dlConfig.getAsInt("default-quality");
if (hasOption("q0"))
qual = 0;
@@ -676,12 +660,22 @@
*
* @return
* @throws IOException
- * @throws ImageOpException
*/
- public Rectangle2D getUserImgArea() throws IOException, ImageOpException {
+ public Rectangle2D getUserImgArea() throws IOException {
if (userImgArea == null) {
- // getScaleXY sets userImgArea
- getScaleXY();
+ // size of the currently selected input image
+ ImageSize imgSize = getInput().getSize();
+ // transform from relative [0,1] to image coordinates.
+ double areaXf = getWx() * imgSize.getWidth();
+ double areaYf = getWy() * imgSize.getHeight();
+ double areaWidthF = getWw() * imgSize.getWidth();
+ double areaHeightF = getWh() * imgSize.getHeight();
+ // round to pixels
+ long areaX = Math.round(areaXf);
+ long areaY = Math.round(areaYf);
+ long areaHeight = Math.round(areaHeightF);
+ long areaWidth = Math.round(areaWidthF);
+ userImgArea = new Rectangle2D.Double(areaX, areaY, areaWidth, areaHeight);
}
return userImgArea;
}
@@ -689,6 +683,10 @@
/**
* Returns the maximal area of the source image that will be used.
*
+ * This was meant to correct for missing pixels outside the
+ * userImgArea when rotating oblique angles but is not yet implemented.
+ * Currently returns userImgArea.
+ *
* @return
* @throws IOException
* @throws ImageOpException
@@ -699,25 +697,28 @@
// image size in pixels
ImageSize imgSize = getInput().getSize();
- Rectangle2D imgBounds = new Rectangle2D.Float(0, 0, imgSize.getWidth(), imgSize.getHeight());
+ Rectangle2D imgBounds = new Rectangle2D.Double(0, 0, imgSize.getWidth(), imgSize.getHeight());
// clip area at the image border
outerUserImgArea = outerUserImgArea.createIntersection(imgBounds);
// check image parameters sanity
scaleXY = getScaleXY();
- logger.debug("outerUserImgArea.getWidth()=" + outerUserImgArea.getWidth());
- logger.debug("get_scaleXY() * outerUserImgArea.getWidth() = " + (scaleXY * outerUserImgArea.getWidth()));
-
if ((outerUserImgArea.getWidth() < 1) || (outerUserImgArea.getHeight() < 1)
|| (scaleXY * outerUserImgArea.getWidth() < 2) || (scaleXY * outerUserImgArea.getHeight() < 2)) {
logger.error("ERROR: invalid scale parameter set!");
+ logger.debug("scaleXY="+scaleXY+" outerUserImgArea="+outerUserImgArea);
throw new ImageOpException("Invalid scale parameter set!");
}
}
return outerUserImgArea;
}
+ /**
+ * Get the RGBM parameter set.
+ *
+ * @return
+ */
public float[] getRGBM() {
float[] paramRGBM = null;// {0f,0f,0f};
Parameter p = params.get("rgbm");
@@ -727,6 +728,11 @@
return paramRGBM;
}
+ /**
+ * Get the RGBA parameter set.
+ *
+ * @return
+ */
public float[] getRGBA() {
float[] paramRGBA = null;// {0f,0f,0f};
Parameter p = params.get("rgba");
diff -r 51587d36fd44 -r 52d0730b1f32 common/src/main/java/digilib/image/ImageLoaderDocuImage.java
--- a/common/src/main/java/digilib/image/ImageLoaderDocuImage.java Mon Oct 19 12:44:26 2015 +0200
+++ b/common/src/main/java/digilib/image/ImageLoaderDocuImage.java Tue Oct 20 19:37:54 2015 +0200
@@ -72,7 +72,7 @@
public class ImageLoaderDocuImage extends ImageInfoDocuImage {
/** DocuImage version */
- public static final String version = "ImageLoaderDocuImage 2.1.6a";
+ public static final String version = "ImageLoaderDocuImage 2.1.7";
/** image object */
protected BufferedImage img;
@@ -167,18 +167,24 @@
/** the size of the current image */
protected ImageSize imageSize;
- /**
- * @return the version
+ /* (non-Javadoc)
+ * @see digilib.image.DocuImageImpl#getVersion()
*/
public String getVersion() {
return version;
}
- /* loadSubimage is supported. */
+ /*
+ * loadSubimage is supported.
+ * @see digilib.image.DocuImageImpl#isSubimageSupported()
+ */
public boolean isSubimageSupported() {
return true;
}
+ /* (non-Javadoc)
+ * @see digilib.image.DocuImageImpl#setQuality(int)
+ */
public void setQuality(int qual) {
quality = qual;
renderHint = new RenderingHints(null);
@@ -194,7 +200,10 @@
}
}
- /* returns the size of the current image */
+ /*
+ * returns the size of the current image
+ * @see digilib.image.DocuImageImpl#getSize()
+ */
public ImageSize getSize() {
if (imageSize == null) {
int h = 0;
@@ -218,13 +227,19 @@
return imageSize;
}
- /* returns a list of supported image formats */
+ /*
+ * returns a list of supported image formats
+ * @see digilib.image.DocuImageImpl#getSupportedFormats()
+ */
public Iterator getSupportedFormats() {
String[] formats = ImageIO.getReaderFormatNames();
return Arrays.asList(formats).iterator();
}
- /* Check image size and type and store in ImageInput */
+ /*
+ * Check image size and type and store in ImageInput
+ * @see digilib.image.ImageInfoDocuImage#identify(digilib.io.ImageInput)
+ */
public ImageInput identify(ImageInput input) throws IOException {
ImageInput ii = null;
if (!reuseReader) {
@@ -266,7 +281,10 @@
}
}
- /* load image file */
+ /*
+ * load image file
+ * @see digilib.image.DocuImageImpl#loadImage(digilib.io.ImageInput)
+ */
public void loadImage(ImageInput ii) throws FileOpException {
logger.debug("loadImage: " + ii);
this.input = ii;
@@ -335,7 +353,11 @@
return reader;
}
- /* Load an image file into the Object. */
+ /*
+ * Load an image file into the Object.
+ *
+ * @see digilib.image.DocuImageImpl#loadSubimage(digilib.io.ImageInput, java.awt.Rectangle, int)
+ */
public void loadSubimage(ImageInput ii, Rectangle region, int prescale) throws FileOpException {
logger.debug("loadSubimage");
this.input = ii;
@@ -391,7 +413,10 @@
}
}
- /* write image of type mt to Stream */
+ /*
+ * (non-Javadoc)
+ * @see digilib.image.DocuImageImpl#writeImage(java.lang.String, java.io.OutputStream)
+ */
public void writeImage(String mt, OutputStream ostream) throws ImageOpException, FileOpException {
logger.debug("writeImage");
// setup output
@@ -446,14 +471,43 @@
// TODO: should we: finally { writer.dispose(); }
}
+ /*
+ * (non-Javadoc)
+ * @see digilib.image.DocuImageImpl#scale(double, double)
+ */
public void scale(double scaleX, double scaleY) throws ImageOpException {
logger.debug("scale: " + scaleX);
- /* for downscaling in high quality the image is blurred first */
+ /*
+ * for downscaling in high quality the image is blurred first ...
+ */
if ((scaleX <= 0.5) && (quality > 1)) {
int bl = (int) Math.floor(1 / scaleX);
blur(bl);
}
- /* then scaled */
+ /*
+ * ... then scaled.
+ *
+ * We need to correct the scale factors to round to whole pixels
+ * or else we get a 1px black (or transparent) border.
+ */
+ int imgW = img.getWidth();
+ int imgH = img.getHeight();
+ double targetW = imgW * scaleX;
+ double targetH = imgH * scaleY;
+ double deltaX = targetW - Math.floor(targetW);
+ double deltaY = targetH - Math.floor(targetH);
+ if (deltaX > epsilon) {
+ // round up
+ logger.debug("rounding up x scale factor");
+ scaleX += (1 - deltaX) / imgW;
+ }
+ if (deltaY > epsilon) {
+ // round up
+ logger.debug("rounding up y scale factor");
+ scaleY += (1 - deltaY) / imgH;
+ }
+ // scale with AffineTransformOp
+ logger.debug("scaled from " + imgW + "x" + imgH + " img=" + img);
AffineTransformOp scaleOp = new AffineTransformOp(AffineTransform.getScaleInstance(scaleX, scaleY), renderHint);
img = scaleOp.filter(img, null);
logger.debug("scaled to " + img.getWidth() + "x" + img.getHeight() + " img=" + img);
@@ -461,6 +515,12 @@
imageSize = null;
}
+ /**
+ * Blur the image with a convolution using the given radius.
+ *
+ * @param radius
+ * @throws ImageOpException
+ */
public void blur(int radius) throws ImageOpException {
logger.debug("blur: " + radius);
// minimum radius is 2
@@ -492,6 +552,10 @@
logger.debug("blurred: " + img);
}
+ /*
+ * (non-Javadoc)
+ * @see digilib.image.DocuImageImpl#crop(int, int, int, int)
+ */
public void crop(int x_off, int y_off, int width, int height) throws ImageOpException {
// setup Crop
img = img.getSubimage(x_off, y_off, width, height);
@@ -500,6 +564,10 @@
imageSize = null;
}
+ /*
+ * (non-Javadoc)
+ * @see digilib.image.DocuImageImpl#rotate(double)
+ */
public void rotate(double angle) throws ImageOpException {
logger.debug("rotate: " + angle);
// setup rotation
@@ -528,6 +596,10 @@
imageSize = null;
}
+ /*
+ * (non-Javadoc)
+ * @see digilib.image.DocuImageImpl#mirror(double)
+ */
public void mirror(double angle) throws ImageOpException {
logger.debug("mirror: " + angle);
// setup mirror
@@ -560,6 +632,10 @@
imageSize = null;
}
+ /*
+ * (non-Javadoc)
+ * @see digilib.image.DocuImageImpl#enhance(float, float)
+ */
public void enhance(float mult, float add) throws ImageOpException {
RescaleOp op = null;
logger.debug("enhance: img=" + img);
@@ -591,6 +667,11 @@
op.filter(img, img);
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see digilib.image.DocuImageImpl#enhanceRGB(float[], float[])
+ */
public void enhanceRGB(float[] rgbm, float[] rgba) throws ImageOpException {
logger.debug("enhanceRGB: rgbm=" + rgbm + " rgba=" + rgba);
/*
@@ -702,6 +783,10 @@
}
}
+ /*
+ * (non-Javadoc)
+ * @see digilib.image.DocuImageImpl#dispose()
+ */
public void dispose() {
if (reader != null) {
reader.dispose();
@@ -710,6 +795,10 @@
img = null;
}
+ /*
+ * (non-Javadoc)
+ * @see digilib.image.DocuImageImpl#getAwtImage()
+ */
public Image getAwtImage() {
return (Image) img;
}
diff -r 51587d36fd44 -r 52d0730b1f32 common/src/main/java/digilib/image/ImageWorker.java
--- a/common/src/main/java/digilib/image/ImageWorker.java Mon Oct 19 12:44:26 2015 +0200
+++ b/common/src/main/java/digilib/image/ImageWorker.java Tue Oct 20 19:37:54 2015 +0200
@@ -57,8 +57,7 @@
/**
* render and return the image
*/
- public DocuImage call() throws FileOpException, IOException,
- ImageOpException {
+ public DocuImage call() throws FileOpException, IOException, ImageOpException {
logger.debug("ImageWorker starting");
long startTime = System.currentTimeMillis();
@@ -78,6 +77,7 @@
// set interpolation quality
docuImage.setQuality(jobinfo.getScaleQual());
+ // get area of interest and scale factor
Rectangle loadRect = jobinfo.getOuterUserImgArea().getBounds();
double scaleXY = jobinfo.getScaleXY();
@@ -85,8 +85,11 @@
logger.debug("ImageWorker stopping (after setup)");
return null;
}
- // use subimage loading if possible
+ /*
+ * load, crop and scale the image
+ */
if (docuImage.isSubimageSupported()) {
+ // use subimage loading if possible
logger.debug("Subimage: scale " + scaleXY + " = " + (1 / scaleXY));
double subf = 1d;
double subsamp = 1d;
@@ -126,26 +129,29 @@
}
docuImage.scale(scaleXY, scaleXY);
}
-
if (stopNow) {
logger.debug("ImageWorker stopping (after scaling)");
return null;
}
- // mirror image
- // operation mode: "hmir": mirror horizontally, "vmir": mirror
- // vertically
+
+ /*
+ * mirror image
+ * operation mode: "hmir": mirror horizontally, "vmir": mirror vertically
+ */
if (jobinfo.hasOption("hmir")) {
docuImage.mirror(0);
}
if (jobinfo.hasOption("vmir")) {
docuImage.mirror(90);
}
-
if (stopNow) {
logger.debug("ImageWorker stopping (after mirroring)");
return null;
}
- // rotate image
+
+ /*
+ * rotate image
+ */
if (jobinfo.getAsFloat("rot") != 0d) {
docuImage.rotate(jobinfo.getAsFloat("rot"));
/*
@@ -162,12 +168,14 @@
*/
}
-
if (stopNow) {
logger.debug("ImageWorker stopping (after rotating)");
return null;
}
- // color modification
+
+ /*
+ * color modification
+ */
float[] paramRGBM = jobinfo.getRGBM();
float[] paramRGBA = jobinfo.getRGBA();
if ((paramRGBM != null) || (paramRGBA != null)) {
@@ -185,31 +193,34 @@
}
docuImage.enhanceRGB(mult, paramRGBA);
}
-
if (stopNow) {
logger.debug("ImageWorker stopping (after enhanceRGB)");
return null;
}
- // contrast and brightness enhancement
+
+ /*
+ * contrast and brightness enhancement
+ */
float paramCONT = jobinfo.getAsFloat("cont");
float paramBRGT = jobinfo.getAsFloat("brgt");
if ((paramCONT != 0f) || (paramBRGT != 0f)) {
float mult = (float) Math.pow(2, paramCONT);
docuImage.enhance(mult, paramBRGT);
}
-
if (stopNow) {
logger.debug("ImageWorker stopping (after enhance)");
return null;
}
- // color operation
+
+ /*
+ * color operation
+ */
DocuImage.ColorOp colop = jobinfo.getColOp();
if (colop != null) {
docuImage.colorOp(colop);
}
- logger.debug("rendered in " + (System.currentTimeMillis() - startTime)
- + "ms");
+ logger.debug("rendered in " + (System.currentTimeMillis() - startTime) + "ms");
return docuImage;
}
diff -r 51587d36fd44 -r 52d0730b1f32 servlet3/src/main/java/digilib/conf/DigilibServlet3Configuration.java
--- a/servlet3/src/main/java/digilib/conf/DigilibServlet3Configuration.java Mon Oct 19 12:44:26 2015 +0200
+++ b/servlet3/src/main/java/digilib/conf/DigilibServlet3Configuration.java Tue Oct 20 19:37:54 2015 +0200
@@ -42,7 +42,7 @@
public class DigilibServlet3Configuration extends DigilibServletConfiguration {
public static String getClassVersion() {
- return "2.3.0 async";
+ return "2.3.2 async";
}
/* non-static getVersion for Java inheritance */