Add exporter to swing and awt native canvas. Let drawable text have a point on the value and smart layout according to camera

This commit is contained in:
martin 2022-10-02 00:02:58 +02:00
parent 4ff9276764
commit 787e9c27be
11 changed files with 194 additions and 39 deletions

View File

@ -5,11 +5,25 @@ package org.jzy3d.io;
import java.awt.image.BufferedImage;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.jzy3d.maths.TicToc;
/**
* The {@link AbstractImageExporter} can receive {@link BufferedImage} image from a renderer to
* export them somewhere.
*
* The exporter as a target frame rate. If it receive images at a higher rate, exceeding images will
* be dropped. If it receive images as a lower rate, it will repeat the last known image until the
* new one arrives.
*
* The exporter uses its own thread to handle export image queries without slowing down the caller
* thread.
*
*
* @author Martin Pernollet
*
*/
public abstract class AbstractImageExporter implements AWTImageExporter {
protected static int DEFAULT_FRAME_RATE_MS = 1000;
@ -137,35 +151,36 @@ public abstract class AbstractImageExporter implements AWTImageExporter {
}
protected void scheduleImageExport(BufferedImage image, boolean isLastImage) {
//try {
executor.execute(new Runnable() {
@Override
public void run() {
// try {
executor.execute(new Runnable() {
@Override
public void run() {
numberSubmittedImages.incrementAndGet();
numberOfPendingImages.incrementAndGet();
numberSubmittedImages.incrementAndGet();
numberOfPendingImages.incrementAndGet();
// System.out.println("Adding image to GIF (pending tasks " + pendingTasks.get() + ")");
// pendingTasks.incrementAndGet();
// System.out.println("Adding image to GIF (pending tasks " + pendingTasks.get() + ")");
// pendingTasks.incrementAndGet();
/*
* Graphics2D graphics = (Graphics2D)image.getGraphics();
* AWTGraphicsUtils.configureRenderingHints(graphics);
* graphics.setColor(java.awt.Color.GRAY);
* graphics.drawString("Powered by Jzy3D (martin@jzy3d.org)", image.getWidth()/2+40,
* image.getHeight()-20); graphics.dispose();
*/
/*
* Graphics2D graphics = (Graphics2D)image.getGraphics();
* AWTGraphicsUtils.configureRenderingHints(graphics);
* graphics.setColor(java.awt.Color.GRAY);
* graphics.drawString("Powered by Jzy3D (martin@jzy3d.org)", image.getWidth()/2+40,
* image.getHeight()-20); graphics.dispose();
*/
doAddFrameByRunnable(image, isLastImage);
doAddFrameByRunnable(image, isLastImage);
numberOfPendingImages.decrementAndGet();
numberOfPendingImages.decrementAndGet();
}
}
});
/*} catch (RejectedExecutionException e) {
System.err.println("AbstractImageExporter : should stop appending image (unregister me from canvas!");
}*/
});
/*
* } catch (RejectedExecutionException e) { System.err.
* println("AbstractImageExporter : should stop appending image (unregister me from canvas!"); }
*/
}
protected abstract void doAddFrameByRunnable(BufferedImage image, boolean isLastImage);
@ -191,7 +206,7 @@ public abstract class AbstractImageExporter implements AWTImageExporter {
public AtomicInteger getNumberOfSkippedImages() {
return numberOfSkippedImages;
}
public AtomicInteger getNumberOfSavedImages() {
return numberOfSavedImages;
}

View File

@ -10,6 +10,8 @@ import org.jzy3d.maths.TicToc;
public class GifExporter extends AbstractImageExporter implements AWTImageExporter {
protected File outputFile;
protected AnimatedGifEncoder encoder;
protected TicToc timer = new TicToc();
public GifExporter(File outputFile) {
this(outputFile, DEFAULT_FRAME_RATE_MS); // 1 frame per sec
@ -25,15 +27,25 @@ public class GifExporter extends AbstractImageExporter implements AWTImageExport
this.encoder.setDelay(gifFrameRateMs);
this.encoder.setRepeat(1000);
this.encoder.setQuality(8);
this.timer.tic();
}
@Override
protected void doAddFrameByRunnable(BufferedImage image, boolean isLastImage) {
if (debug)
System.out.println("GifExporter : Adding image to GIF " + numberSubmittedImages.get());
if (debug) {
timer.toc();
System.out.println("GifExporter : Adding image to GIF " + numberSubmittedImages.get() + " at " + timer.elapsedSecond());
}
encoder.addFrame(image);
if (debug) {
timer.toc();
System.out.println("GifExporter : Adding image to GIF " + numberSubmittedImages.get() + " at " + timer.elapsedSecond());
}
if (isLastImage) {
closeOutput();
}
@ -41,6 +53,7 @@ public class GifExporter extends AbstractImageExporter implements AWTImageExport
numberOfSavedImages.incrementAndGet();
}
@Override
protected void closeOutput() {
encoder.finish();

View File

@ -332,7 +332,7 @@ public class Chart {
canvas.screenshot(file);
}
public Object screenshot() throws IOException {
public Object screenshot() {
return canvas.screenshot();
}

View File

@ -142,5 +142,7 @@ public interface ICanvas {
*/
@Deprecated
public static boolean ALLOW_WATCH_PIXEL_SCALE = true;
}

View File

@ -7,11 +7,11 @@ import org.jzy3d.maths.Coord3d;
import org.jzy3d.painters.Font;
import org.jzy3d.painters.IPainter;
import org.jzy3d.plot3d.primitives.Drawable;
import org.jzy3d.plot3d.primitives.Point;
import org.jzy3d.plot3d.primitives.axis.layout.AxisLayout;
import org.jzy3d.plot3d.text.ITextRenderer;
import org.jzy3d.plot3d.text.align.Horizontal;
import org.jzy3d.plot3d.text.align.Vertical;
import org.jzy3d.plot3d.text.renderers.TextRenderer;
import org.jzy3d.plot3d.transform.Transform;
/**
@ -34,6 +34,12 @@ public class DrawableTextWrapper extends Drawable {
protected AxisLayout axisLayout;
protected Font defaultFont = Font.Helvetica_12;
protected boolean pointDisplayed = false;
protected Point point;
protected boolean flipHorizontalAlignWithViewpoint = false;
public DrawableTextWrapper(ITextRenderer renderer) {
this("", new Coord3d(), Color.BLACK, renderer);
@ -42,8 +48,14 @@ public class DrawableTextWrapper extends Drawable {
public DrawableTextWrapper(String txt, Coord3d position, Color color, ITextRenderer renderer) {
super();
this.renderer = renderer;
this.point = new Point();
configure(txt, position, color, Horizontal.CENTER, Vertical.CENTER);
}
/*******************************************************************************************/
@ -56,12 +68,34 @@ public class DrawableTextWrapper extends Drawable {
font = axisLayout.getFont();
}
BoundingBox3d box = renderer.drawText(painter, font, txt, position, rotation, halign, valign,
Horizontal fixedHAlign = flipHorizontalAlignIfEnabled(painter);
//Coord3d fixedSceneOffset = flipHorizontalAlignWithViewpoint?sceneOffset/*.mul(-1)*/:sceneOffset;
BoundingBox3d box = renderer.drawText(painter, font, txt, position, rotation, fixedHAlign, valign,
color, screenOffset, sceneOffset);
if (box != null)
bbox = box.scale(new Coord3d(1 / 10, 1 / 10, 1 / 10));
else
bbox = null;
point.draw(painter);
}
public Horizontal flipHorizontalAlignIfEnabled(IPainter painter) {
boolean left = painter.getCamera().side(position);
Horizontal fixedHAlign = halign;
if(left && flipHorizontalAlignWithViewpoint) {
if(halign==Horizontal.LEFT) {
fixedHAlign = Horizontal.RIGHT;
}
else if(halign==Horizontal.RIGHT) {
fixedHAlign = Horizontal.LEFT;
}
}
return fixedHAlign;
}
@Override
@ -87,6 +121,7 @@ public class DrawableTextWrapper extends Drawable {
public void setPosition(Coord3d position) {
this.position = position;
this.point.setCoord(position);
}
public Coord3d getPosition() {
@ -96,6 +131,14 @@ public class DrawableTextWrapper extends Drawable {
public void setColor(Color color) {
this.color = color;
}
public boolean isFlipHorizontalAlignWithViewpoint() {
return flipHorizontalAlignWithViewpoint;
}
public void setFlipHorizontalAlignWithViewpoint(boolean flipHorizontalAlignWithViewpoint) {
this.flipHorizontalAlignWithViewpoint = flipHorizontalAlignWithViewpoint;
}
public Horizontal getHalign() {
return halign;
@ -161,6 +204,18 @@ public class DrawableTextWrapper extends Drawable {
public void setDefaultFont(Font defaultFont) {
this.defaultFont = defaultFont;
}
public Point getPoint() {
return point;
}
public boolean isPointDisplayed() {
return pointDisplayed;
}
public void setPointDisplayed(boolean pointDisplayed) {
this.pointDisplayed = pointDisplayed;
}
@Override
public String toString() {

View File

@ -9,6 +9,10 @@ public class OffscreenChartFactory extends AWTChartFactory {
int width;
int height;
public OffscreenChartFactory() {
this(800,600);
}
public OffscreenChartFactory(int width, int height) {
super(new OffscreenWindowFactory());
this.width = width;

View File

@ -13,11 +13,13 @@ import org.jzy3d.awt.AWTHelper;
import org.jzy3d.chart.IAnimator;
import org.jzy3d.chart.factories.IChartFactory;
import org.jzy3d.chart.factories.NativePainterFactory;
import org.jzy3d.io.AWTImageExporter;
import org.jzy3d.maths.Coord2d;
import org.jzy3d.maths.Dimension;
import org.jzy3d.painters.IPainter;
import org.jzy3d.plot3d.GPUInfo;
import org.jzy3d.plot3d.rendering.scene.Scene;
import org.jzy3d.plot3d.rendering.view.AWTRenderer3d;
import org.jzy3d.plot3d.rendering.view.Renderer3d;
import org.jzy3d.plot3d.rendering.view.View;
import com.jogamp.nativewindow.ScalableSurface;
@ -343,5 +345,20 @@ public class CanvasAWT extends GLCanvas implements IScreenCanvas, INativeCanvas
return true;
}
public AWTImageExporter getExporter() {
if(renderer!=null && renderer instanceof AWTRenderer3d) {
AWTRenderer3d r = (AWTRenderer3d)renderer;
return r.getExporter();
}
else {
return null;
}
}
public void setExporter(AWTImageExporter exporter) {
if(renderer!=null && renderer instanceof AWTRenderer3d) {
AWTRenderer3d r = (AWTRenderer3d)renderer;
r.setExporter(exporter);
}
}
}

View File

@ -1,6 +1,7 @@
package org.jzy3d.plot3d.rendering.view;
import java.awt.image.BufferedImage;
import org.jzy3d.io.AWTImageExporter;
import com.jogamp.opengl.GL;
import com.jogamp.opengl.GLEventListener;
import com.jogamp.opengl.GLProfile;
@ -14,6 +15,8 @@ import com.jogamp.opengl.util.awt.AWTGLReadBufferUtil;
public class AWTRenderer3d extends Renderer3d {
protected BufferedImage bufferedImage;
protected AWTImageExporter exporter;
protected AWTGLReadBufferUtil screenshotMaker;
public AWTRenderer3d() {
super();
@ -25,6 +28,9 @@ public class AWTRenderer3d extends Renderer3d {
public AWTRenderer3d(View view, boolean traceGL, boolean debugGL) {
super(view, traceGL, debugGL);
screenshotMaker = new AWTGLReadBufferUtil(GLProfile.getGL2GL3(), true);
}
/********************* SCREENSHOTS ***********************/
@ -36,13 +42,33 @@ public class AWTRenderer3d extends Renderer3d {
@Override
protected void renderScreenshotIfRequired(GL gl) {
if (doScreenshotAtNextDisplay) {
AWTGLReadBufferUtil screenshot = new AWTGLReadBufferUtil(GLProfile.getGL2GL3(), true);
screenshot.readPixels(gl, true);
image = screenshot.getTextureData();
bufferedImage = screenshot.readPixelsToBufferedImage(gl, true);
screenshotMaker.readPixels(gl, true);
image = screenshotMaker.getTextureData();
bufferedImage = screenshotMaker.readPixelsToBufferedImage(gl, true);
doScreenshotAtNextDisplay = false;
}
}
@Override
protected void exportImageIfRequired(GL gl) {
if(exporter!=null) {
screenshotMaker.readPixels(gl, true);
exporter.export(screenshotMaker.readPixelsToBufferedImage(gl, true));
}
}
public AWTImageExporter getExporter() {
return exporter;
}
public void setExporter(AWTImageExporter exporter) {
this.exporter = exporter;
}
}

View File

@ -97,9 +97,10 @@ public class Renderer3d implements GLEventListener {
view.clear();
view.render();
exportImageIfRequired(canvas.getGL());
renderScreenshotIfRequired(canvas.getGL());
}
}
}
@ -107,6 +108,7 @@ public class Renderer3d implements GLEventListener {
lastRenderingTimeMs = profileDisplayTimer.elapsedMilisecond();
}
/** Called when the {@link GLAutoDrawable} is resized. */
@Override
public void reshape(GLAutoDrawable canvas, int x, int y, int width, int height) {
@ -163,6 +165,11 @@ public class Renderer3d implements GLEventListener {
doScreenshotAtNextDisplay = false;
}
}
protected void exportImageIfRequired(GL gl) {
}

View File

@ -15,12 +15,14 @@ import org.jzy3d.awt.AWTHelper;
import org.jzy3d.chart.IAnimator;
import org.jzy3d.chart.factories.IChartFactory;
import org.jzy3d.chart.factories.NativePainterFactory;
import org.jzy3d.io.AWTImageExporter;
import org.jzy3d.maths.Coord2d;
import org.jzy3d.maths.Dimension;
import org.jzy3d.painters.IPainter;
import org.jzy3d.painters.NativeDesktopPainter;
import org.jzy3d.plot3d.GPUInfo;
import org.jzy3d.plot3d.rendering.scene.Scene;
import org.jzy3d.plot3d.rendering.view.AWTRenderer3d;
import org.jzy3d.plot3d.rendering.view.Renderer3d;
import org.jzy3d.plot3d.rendering.view.View;
import com.jogamp.nativewindow.ScalableSurface;
@ -329,4 +331,22 @@ public class CanvasSwing extends GLJPanel implements IScreenCanvas, INativeCanva
public boolean isNative() {
return true;
}
public AWTImageExporter getExporter() {
if(renderer!=null && renderer instanceof AWTRenderer3d) {
AWTRenderer3d r = (AWTRenderer3d)renderer;
return r.getExporter();
}
else {
return null;
}
}
public void setExporter(AWTImageExporter exporter) {
if(renderer!=null && renderer instanceof AWTRenderer3d) {
AWTRenderer3d r = (AWTRenderer3d)renderer;
r.setExporter(exporter);
}
}
}

View File

@ -191,11 +191,7 @@ public class MultiChartPanel extends JPanel {
}
public JPanel getChartScreenshotAsComponent(Chart chart) {
try {
chart.screenshot();
} catch (IOException e) {
throw new RuntimeException(e);
}
AWTRenderer3d renderer = (AWTRenderer3d) ((INativeCanvas) chart.getCanvas()).getRenderer();
BufferedImage i = renderer.getLastScreenshotImage();
JPanel component = new ImagePanel(i);