-
+
diff --git a/apps/jrobin/java/src/org/rrd4j/core/RrdBackendFactory.java b/apps/jrobin/java/src/org/rrd4j/core/RrdBackendFactory.java
index 3be1036ead..75491729b9 100644
--- a/apps/jrobin/java/src/org/rrd4j/core/RrdBackendFactory.java
+++ b/apps/jrobin/java/src/org/rrd4j/core/RrdBackendFactory.java
@@ -310,10 +310,7 @@ public abstract class RrdBackendFactory implements Closeable {
}
@Override
public void clear() {
- try {
- backend.close();
- } catch (IOException e) {
- }
+ // backend doesn't need to be closed here as it already happens in RrdBackend.rrdClose()
backend = null;
super.clear();
}
diff --git a/apps/jrobin/java/src/org/rrd4j/core/RrdDbPool.java b/apps/jrobin/java/src/org/rrd4j/core/RrdDbPool.java
index b95e8ae34a..ffedca8972 100644
--- a/apps/jrobin/java/src/org/rrd4j/core/RrdDbPool.java
+++ b/apps/jrobin/java/src/org/rrd4j/core/RrdDbPool.java
@@ -300,6 +300,9 @@ public class RrdDbPool {
RrdEntry ref;
try {
ref = getEntry(dburi, false);
+ } catch (IllegalStateException e) {
+ // This means that corresponding database has been already closed before, so returning immediately.
+ return;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Release interrupted for " + rrdDb.getPath(), e);
diff --git a/apps/jrobin/java/src/org/rrd4j/graph/BufferedImageWorker.java b/apps/jrobin/java/src/org/rrd4j/graph/BufferedImageWorker.java
new file mode 100644
index 0000000000..6a778dbc8a
--- /dev/null
+++ b/apps/jrobin/java/src/org/rrd4j/graph/BufferedImageWorker.java
@@ -0,0 +1,161 @@
+package org.rrd4j.graph;
+
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.imageio.spi.ImageWriterSpi;
+import javax.imageio.stream.ImageOutputStream;
+
+class BufferedImageWorker extends ImageWorker {
+
+ static class Builder {
+ private int width = 1;
+ private int height = 1;
+ private RrdGraphDef gdef;
+ private ImageWriter writer;
+ private ImageWriteParam imageWriteParam;
+ BufferedImageWorker build() {
+ return new BufferedImageWorker(this);
+ }
+ private ImageWriteParam getImageParams() {
+ ImageWriteParam iwp = writer.getDefaultWriteParam();
+ ImageWriterSpi imgProvider = writer.getOriginatingProvider();
+ //If lossy compression, use the quality
+ if (! imgProvider.isFormatLossless()) {
+ iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+ iwp.setCompressionQuality(gdef.imageQuality);
+ }
+
+ if (iwp.canWriteProgressive()) {
+ iwp.setProgressiveMode(gdef.interlaced ? ImageWriteParam.MODE_DEFAULT:ImageWriteParam.MODE_DISABLED);
+ }
+ return iwp;
+ }
+
+ public Builder setWidth(int width) {
+ this.width = width;
+ return this;
+ }
+
+ public Builder setHeight(int height) {
+ this.height = height;
+ return this;
+ }
+
+ public Builder setGdef(RrdGraphDef gdef) {
+ this.gdef = gdef;
+ if (this.writer == null) {
+ this.writer = ImageIO.getImageWritersByFormatName(gdef.imageFormat).next();
+ }
+ if (this.imageWriteParam == null) {
+ this.imageWriteParam = getImageParams();
+ }
+ return this;
+ }
+
+ public Builder setWriter(ImageWriter writer) {
+ this.writer = writer;
+ if (this.imageWriteParam == null) {
+ this.imageWriteParam = getImageParams();
+ }
+ return this;
+ }
+
+ public Builder setImageWriteParam(ImageWriteParam imageWriteParam) {
+ this.imageWriteParam = imageWriteParam;
+ return this;
+ }
+ }
+ public static Builder getBuilder() {
+ return new Builder();
+ }
+
+ private BufferedImage img;
+ private int imgWidth;
+ private int imgHeight;
+ private AffineTransform initialAffineTransform;
+ private final ImageWriter writer;
+ private final ImageWriteParam iwp;
+
+ private BufferedImageWorker(Builder builder) {
+ this.imgHeight = builder.height;
+ this.imgWidth = builder.width;
+ this.writer = builder.writer;
+ this.iwp = builder.imageWriteParam;
+ resize(imgWidth, imgHeight);
+ }
+
+ /*
+ * Not for use, only for tests
+ */
+ BufferedImageWorker(int width, int height) {
+ this.imgHeight = height;
+ this.imgWidth = width;
+ this.writer = ImageIO.getImageWritersByFormatName("png").next();
+ this.iwp = writer.getDefaultWriteParam();
+ resize(imgWidth, imgHeight);
+ }
+
+ protected void resize(int width, int height) {
+ imgWidth = width;
+ imgHeight = height;
+ img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+
+ Graphics2D g2d = img.createGraphics();
+ setG2d(g2d);
+ initialAffineTransform = g2d.getTransform();
+
+ setAntiAliasing(false);
+ setTextAntiAliasing(false);
+
+ g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ }
+
+ protected void reset(Graphics2D g2d) {
+ g2d.setTransform(initialAffineTransform);
+ g2d.setClip(0, 0, imgWidth, imgHeight);
+ }
+
+ protected void makeImage(OutputStream stream) throws IOException {
+ BufferedImage outputImage = img;
+
+ ImageWriterSpi imgProvider = writer.getOriginatingProvider();
+
+ img.coerceData(false);
+
+ // Some format can't manage 16M colors images
+ // JPEG don't like transparency
+ if (! imgProvider.canEncodeImage(outputImage) || "image/jpeg".equalsIgnoreCase(imgProvider.getMIMETypes()[0])) {
+ int w = img.getWidth();
+ int h = img.getHeight();
+ outputImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
+ outputImage.getGraphics().drawImage(img, 0, 0, w, h, null);
+ if (! imgProvider.canEncodeImage(outputImage)) {
+ throw new IllegalArgumentException("Invalid image type");
+ }
+ }
+
+ if (! imgProvider.canEncodeImage(outputImage)) {
+ throw new IllegalArgumentException("Invalid image type");
+ }
+
+ try (ImageOutputStream imageStream = ImageIO.createImageOutputStream(stream)) {
+ writer.setOutput(imageStream);
+ writer.write(null, new IIOImage(outputImage, null, null), iwp);
+ imageStream.flush();
+ } catch (IOException e) {
+ writer.abort();
+ throw e;
+ } finally {
+ writer.dispose();
+ }
+ }
+
+}
diff --git a/apps/jrobin/java/src/org/rrd4j/graph/ImageWorker.java b/apps/jrobin/java/src/org/rrd4j/graph/ImageWorker.java
index e278a4a17e..f5a9892529 100644
--- a/apps/jrobin/java/src/org/rrd4j/graph/ImageWorker.java
+++ b/apps/jrobin/java/src/org/rrd4j/graph/ImageWorker.java
@@ -1,223 +1,211 @@
package org.rrd4j.graph;
-import java.awt.Font;
-import java.awt.Graphics2D;
-import java.awt.Paint;
-import java.awt.RenderingHints;
-import java.awt.Stroke;
+import java.awt.*;
import java.awt.font.LineMetrics;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.nio.file.Paths;
-import javax.imageio.IIOImage;
-import javax.imageio.ImageIO;
-import javax.imageio.ImageWriteParam;
-import javax.imageio.ImageWriter;
-import javax.imageio.spi.ImageWriterSpi;
-import javax.imageio.stream.ImageOutputStream;
+/**
+ * An abstract class, that allows to use custom {@link Graphics2D}. To use it the construct should build it and call
+ * {@link #setG2d(Graphics2D)} when finished.
+ */
+public abstract class ImageWorker {
-class ImageWorker {
private static final String DUMMY_TEXT = "Dummy";
private static final int IMG_BUFFER_CAPACITY = 10000; // bytes
-
- private BufferedImage img;
private Graphics2D g2d;
- private int imgWidth, imgHeight;
- private AffineTransform initialAffineTransform;
- ImageWorker(int width, int height) {
- resize(width, height);
- }
-
- void resize(int width, int height) {
+ protected void setG2d(Graphics2D g2d) {
if (g2d != null) {
dispose();
}
-
- imgWidth = width;
- imgHeight = height;
- img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
-
- g2d = img.createGraphics();
- initialAffineTransform = g2d.getTransform();
-
- setAntiAliasing(false);
- setTextAntiAliasing(false);
-
- g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ this.g2d = g2d;
}
- void clip(int x, int y, int width, int height) {
+ protected abstract void resize(int width, int height);
+
+ protected void clip(int x, int y, int width, int height) {
g2d.setClip(x, y, width, height);
}
- void transform(int x, int y, double angle) {
+ protected void transform(int x, int y, double angle) {
g2d.translate(x, y);
g2d.rotate(angle);
}
- void reset() {
- g2d.setTransform(initialAffineTransform);
- g2d.setClip(0, 0, imgWidth, imgHeight);
+ protected void reset() {
+ reset(g2d);
}
- void fillRect(int x, int y, int width, int height, Paint paint) {
+ /**
+ * reset the dimensions of the {@link Graphics2D}
+ */
+ protected abstract void reset(Graphics2D g2d);
+
+ protected void fillRect(int x, int y, int width, int height, Paint paint) {
g2d.setPaint(paint);
g2d.fillRect(x, y, width, height);
}
- void fillPolygon(int[] x, int[] y, Paint paint) {
- g2d.setPaint(paint);
- g2d.fillPolygon(x, y, x.length);
- }
-
- void fillPolygon(double[] x, double yBottom, double[] yTop, Paint paint) {
+ protected void fillPolygon(double[] x, double yBottom, double[] yTop, Paint paint) {
g2d.setPaint(paint);
PathIterator path = new PathIterator(yTop);
for (int[] pos = path.getNextPath(); pos != null; pos = path.getNextPath()) {
int start = pos[0], end = pos[1], n = end - start;
int[] xDev = new int[n + 2], yDev = new int[n + 2];
+ int c = 0;
for (int i = start; i < end; i++) {
- xDev[i - start] = (int) x[i];
- yDev[i - start] = (int) yTop[i];
+ int cx = (int) x[i];
+ int cy = (int) yTop[i];
+ if (c == 0 || cx != xDev[c - 1] || cy != yDev[c - 1]) {
+ if (c >= 2 && cy == yDev[c - 1] && cy == yDev[c - 2]) {
+ // collapse horizontal lines
+ xDev[c - 1] = cx;
+ } else {
+ xDev[c] = cx;
+ yDev[c++] = cy;
+ }
+ }
}
- xDev[n] = xDev[n - 1];
- xDev[n + 1] = xDev[0];
- yDev[n] = yDev[n + 1] = (int) yBottom;
- g2d.fillPolygon(xDev, yDev, xDev.length);
- g2d.drawPolygon(xDev, yDev, xDev.length);
+ xDev[c] = xDev[c - 1];
+ xDev[c + 1] = xDev[0];
+ yDev[c] = yDev[c + 1] = (int) yBottom;
+ g2d.fillPolygon(xDev, yDev, c + 2);
}
}
- void fillPolygon(double[] x, double[] yBottom, double[] yTop, Paint paint) {
+ protected void fillPolygon(double[] x, double[] yBottom, double[] yTop, Paint paint) {
g2d.setPaint(paint);
PathIterator path = new PathIterator(yTop);
for (int[] pos = path.getNextPath(); pos != null; pos = path.getNextPath()) {
int start = pos[0], end = pos[1], n = end - start;
int[] xDev = new int[n * 2], yDev = new int[n * 2];
+ int c = 0;
for (int i = start; i < end; i++) {
- int ix1 = i - start, ix2 = n * 2 - 1 - i + start;
- xDev[ix1] = xDev[ix2] = (int) x[i];
- yDev[ix1] = (int) yTop[i];
- yDev[ix2] = (int) yBottom[i];
+ int cx = (int) x[i];
+ int cy = (int) yTop[i];
+ if (c == 0 || cx != xDev[c - 1] || cy != yDev[c - 1]) {
+ if (c >= 2 && cy == yDev[c - 1] && cy == yDev[c - 2]) {
+ // collapse horizontal lines
+ xDev[c - 1] = cx;
+ } else {
+ xDev[c] = cx;
+ yDev[c++] = cy;
+ }
+ }
}
- g2d.fillPolygon(xDev, yDev, xDev.length);
+ for (int i = end - 1; i >= start; i--) {
+ int cx = (int) x[i];
+ int cy = (int) yBottom[i];
+ if (c == 0 || cx != xDev[c - 1] || cy != yDev[c - 1]) {
+ if (c >= 2 && cy == yDev[c - 1] && cy == yDev[c - 2]) {
+ // collapse horizontal lines
+ xDev[c - 1] = cx;
+ } else {
+ xDev[c] = cx;
+ yDev[c++] = cy;
+ }
+ }
+ }
+ g2d.fillPolygon(xDev, yDev, c);
}
}
- void drawLine(int x1, int y1, int x2, int y2, Paint paint, Stroke stroke) {
+ protected void drawLine(int x1, int y1, int x2, int y2, Paint paint, Stroke stroke) {
g2d.setStroke(stroke);
g2d.setPaint(paint);
g2d.drawLine(x1, y1, x2, y2);
}
- void drawPolyline(int[] x, int[] y, Paint paint, Stroke stroke) {
- g2d.setStroke(stroke);
- g2d.setPaint(paint);
- g2d.drawPolyline(x, y, x.length);
- }
-
- void drawPolyline(double[] x, double[] y, Paint paint, Stroke stroke) {
+ protected void drawPolyline(double[] x, double[] y, Paint paint, Stroke stroke) {
g2d.setPaint(paint);
g2d.setStroke(stroke);
PathIterator path = new PathIterator(y);
for (int[] pos = path.getNextPath(); pos != null; pos = path.getNextPath()) {
int start = pos[0], end = pos[1];
int[] xDev = new int[end - start], yDev = new int[end - start];
+ int c = 0;
for (int i = start; i < end; i++) {
- xDev[i - start] = (int) x[i];
- yDev[i - start] = (int) y[i];
+ int cx = (int) x[i];
+ int cy = (int) y[i];
+ if (c == 0 || cx != xDev[c - 1] || cy != yDev[c - 1]) {
+ if (c >= 2 && cy == yDev[c - 1] && cy == yDev[c - 2]) {
+ // collapse horizontal lines
+ xDev[c - 1] = cx;
+ } else {
+ xDev[c] = cx;
+ yDev[c++] = cy;
+ }
+ }
}
- g2d.drawPolyline(xDev, yDev, xDev.length);
+ g2d.drawPolyline(xDev, yDev, c);
}
}
- void drawString(String text, int x, int y, Font font, Paint paint) {
+ protected void drawString(String text, int x, int y, Font font, Paint paint) {
g2d.setFont(font);
g2d.setPaint(paint);
g2d.drawString(text, x, y);
}
- double getFontAscent(Font font) {
+ protected double getFontAscent(Font font) {
LineMetrics lm = font.getLineMetrics(DUMMY_TEXT, g2d.getFontRenderContext());
return lm.getAscent();
}
- double getFontHeight(Font font) {
+ protected double getFontHeight(Font font) {
LineMetrics lm = font.getLineMetrics(DUMMY_TEXT, g2d.getFontRenderContext());
return lm.getAscent() + lm.getDescent();
}
- double getStringWidth(String text, Font font) {
+ protected double getStringWidth(String text, Font font) {
return font.getStringBounds(text, 0, text.length(), g2d.getFontRenderContext()).getBounds().getWidth();
}
- void setAntiAliasing(boolean enable) {
+ protected void setAntiAliasing(boolean enable) {
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
enable ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
}
- void setTextAntiAliasing(boolean enable) {
+ protected void setTextAntiAliasing(boolean enable) {
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
enable ? RenderingHints.VALUE_TEXT_ANTIALIAS_ON : RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
}
- void dispose() {
- g2d.dispose();
- }
-
- void makeImage(Object stream, ImageWriter writer, ImageWriteParam iwp) throws IOException {
- BufferedImage outputImage = img;
-
- ImageWriterSpi imgProvider = writer.getOriginatingProvider();
-
- img.coerceData(false);
-
- // Some format can't manage 16M colors images
- // JPEG don't like transparency
- if (! imgProvider.canEncodeImage(outputImage) || "image/jpeg".equalsIgnoreCase(imgProvider.getMIMETypes()[0])) {
- int w = img.getWidth();
- int h = img.getHeight();
- outputImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
- outputImage.getGraphics().drawImage(img, 0, 0, w, h, null);
- if (! imgProvider.canEncodeImage(outputImage)) {
- throw new RuntimeException("Invalid image type");
- }
- }
-
- if (! imgProvider.canEncodeImage(outputImage)) {
- throw new RuntimeException("Invalid image type");
- }
-
- try (ImageOutputStream imageStream = ImageIO.createImageOutputStream(stream)) {
- writer.setOutput(imageStream);
- writer.write(null, new IIOImage(outputImage, null, null), iwp);
- imageStream.flush();
- } catch (IOException e) {
- writer.abort();
- throw e;
- } finally {
- writer.dispose();
- }
- }
-
- void saveImage(String path, ImageWriter writer, ImageWriteParam iwp) throws IOException {
- makeImage(Paths.get(path).toFile(), writer, iwp);
- }
-
- byte[] getImageBytes(ImageWriter writer, ImageWriteParam iwp) throws IOException {
- try (ByteArrayOutputStream stream = new ByteArrayOutputStream(IMG_BUFFER_CAPACITY)){
- makeImage(stream, writer, iwp);
- return stream.toByteArray();
- }
- }
-
- void loadImage(RrdGraphDef.ImageSource imageSource, int x, int y, int w, int h) throws IOException {
+ protected void loadImage(RrdGraphDef.ImageSource imageSource, int x, int y, int w, int h) throws IOException {
BufferedImage wpImage = imageSource.apply(w, h).getSubimage(0, 0, w, h);
g2d.drawImage(wpImage, new AffineTransform(1f, 0f, 0f, 1f, x, y), null);
}
+ protected void dispose() {
+ if (g2d != null) {
+ g2d.dispose();
+ }
+ }
+
+ protected void makeImage(Path path) throws IOException {
+ try (OutputStream os = Files.newOutputStream(path)) {
+ makeImage(os);
+ }
+ }
+
+ protected abstract void makeImage(OutputStream os) throws IOException ;
+
+ protected void saveImage(String path) throws IOException {
+ makeImage(Paths.get(path));
+ }
+
+ protected byte[] getImageBytes() throws IOException {
+ try (ByteArrayOutputStream stream = new ByteArrayOutputStream(IMG_BUFFER_CAPACITY)){
+ makeImage(stream);
+ return stream.toByteArray();
+ }
+ }
+
}
diff --git a/apps/jrobin/java/src/org/rrd4j/graph/RrdGraph.java b/apps/jrobin/java/src/org/rrd4j/graph/RrdGraph.java
index 404ed556f0..8b029cb6af 100644
--- a/apps/jrobin/java/src/org/rrd4j/graph/RrdGraph.java
+++ b/apps/jrobin/java/src/org/rrd4j/graph/RrdGraph.java
@@ -12,10 +12,8 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
-import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
-import javax.imageio.spi.ImageWriterSpi;
import javax.swing.ImageIcon;
import org.rrd4j.core.Util;
@@ -47,8 +45,6 @@ public class RrdGraph implements RrdGraphConstants {
Mapper mapper;
private final RrdGraphInfo info = new RrdGraphInfo();
private final String signature;
- private final ImageWriter writer;
- private final ImageWriteParam param;
/**
* Creates graph from the corresponding {@link org.rrd4j.graph.RrdGraphDef} object.
@@ -60,9 +56,8 @@ public class RrdGraph implements RrdGraphConstants {
this.gdef = gdef;
signature = gdef.getSignature();
im = new ImageParameters();
- worker = new ImageWorker(1, 1); // Dummy worker, just to start with something
- writer = ImageIO.getImageWritersByFormatName(gdef.imageFormat).next();
- param = getImageParams();
+
+ worker = BufferedImageWorker.getBuilder().setGdef(gdef).build();
try {
createGraph();
}
@@ -73,6 +68,28 @@ public class RrdGraph implements RrdGraphConstants {
}
}
+ /**
+ * Create graph from a custom image worker
+ * @param gdef
+ * @param worker
+ * @throws IOException
+ */
+ public RrdGraph(RrdGraphDef gdef, ImageWorker worker) throws IOException {
+ this.gdef = gdef;
+ signature = gdef.getSignature();
+ im = new ImageParameters();
+ this.worker = worker;
+ try {
+ createGraph();
+ }
+ finally {
+ worker.dispose();
+ this.worker = null;
+ dproc = null;
+ }
+ }
+
+
/**
* Creates graph from the corresponding {@link org.rrd4j.graph.RrdGraphDef} object.
* The graph will be created using customs {@link javax.imageio.ImageWriter} and {@link javax.imageio.ImageWriteParam} given.
@@ -88,9 +105,7 @@ public class RrdGraph implements RrdGraphConstants {
this.gdef = gdef;
signature = gdef.getSignature();
im = new ImageParameters();
- worker = new ImageWorker(1, 1); // Dummy worker, just to start with something
- this.writer = writer;
- this.param = param;
+ worker = BufferedImageWorker.getBuilder().setGdef(gdef).setWriter(writer).setImageWriteParam(param).build();
try {
createGraph();
}
@@ -110,21 +125,6 @@ public class RrdGraph implements RrdGraphConstants {
return info;
}
- private ImageWriteParam getImageParams() {
- ImageWriteParam iwp = writer.getDefaultWriteParam();
- ImageWriterSpi imgProvider = writer.getOriginatingProvider();
- //If lossy compression, use the quality
- if (! imgProvider.isFormatLossless()) {
- iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
- iwp.setCompressionQuality(gdef.imageQuality);
- }
-
- if (iwp.canWriteProgressive()) {
- iwp.setProgressiveMode(gdef.interlaced ? ImageWriteParam.MODE_DEFAULT:ImageWriteParam.MODE_DISABLED);
- }
- return iwp;
- }
-
private void createGraph() throws IOException {
boolean lazy = lazyCheck();
if (!lazy || gdef.printStatementCount() != 0) {
@@ -177,7 +177,7 @@ public class RrdGraph implements RrdGraphConstants {
private void saveImage() throws IOException {
if (! RrdGraphConstants.IN_MEMORY_IMAGE.equals(gdef.filename)) {
Path imgpath = Paths.get(gdef.filename);
- worker.saveImage(gdef.filename, writer, param);
+ worker.saveImage(gdef.filename);
info.bytesSource = () -> {
try {
return Files.readAllBytes(imgpath);
@@ -194,7 +194,7 @@ public class RrdGraph implements RrdGraphConstants {
};
}
else {
- byte[] content = worker.getImageBytes(writer, param);
+ byte[] content = worker.getImageBytes();
info.bytesSource = () -> Arrays.copyOf(content, content.length);
info.bytesCount = () -> content.length;
}