001/* 002 * ImageCaptureCanvas3D -- A Canvas3D that allows you to capture the contents to an image. 003 * 004 * Copyright (C) 2009-2023, by Joseph A. Huwaldt. 005 * All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public License 018 * along with this program; if not, write to the Free Software 019 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 020 * Or visit: http://www.gnu.org/licenses/lgpl.html 021 * 022 * This is based on ImageCaptureCanvas3D from the org.j3d package which included 023 * the following note: 024 * 025 * J3D.org Copyright (c) 2000 026 * Java Source 027 * 028 * This source is licensed under the GNU LGPL v2.1 029 */ 030package jahuwaldt.j3d; 031 032import jahuwaldt.j3d.image.CapturedImageObserver; 033import java.awt.GraphicsConfiguration; 034import java.awt.image.BufferedImage; 035import java.util.ArrayList; 036import static java.util.Objects.requireNonNull; 037import java.util.logging.Level; 038import java.util.logging.Logger; 039import org.jogamp.java3d.*; 040import org.jogamp.vecmath.Point3f; 041 042/** 043 * A version of the standard Java3D Canvas3D class that allows you to capture the contents 044 * and write out the image information. 045 * 046 * <p> 047 * The canvas uses a callback mechanism to capture an image and notify the observer of the 048 * image data.</p> 049 * <p> 050 * The original code for this was written by Peter Z. Kunszt of John Hopkins University 051 * and posted to the Java 3D interest list. This version has been modified to make it more 052 * reusable and flexible. The image can be used to pass to a printer or written to a file 053 * for example.</p> 054 * <p> 055 * When the observer is notified, this class does not provide any separation of the 056 * notification from the rendering thread. A call to the observer will prevent the 057 * renderer from starting the next frame. If you are intending to save a lot of images, 058 * you should implement some form of buffering system to take the conversion process into 059 * a separate thread otherwise on-screen performance will be <I>severely</I> impacted.</p> 060 * 061 * <p> Modified by: Joseph A.Huwaldt </p> 062 * 063 * @author Justin Couch 064 * @version June 4, 2023 065 */ 066public class ImageCaptureCanvas3D extends Canvas3D { 067 068 private static final long serialVersionUID = 1L; 069 070 /** 071 * Flag to indicate that the canvas should notify observers of an image. Faster than 072 * making a call to observers.size() each frame. 073 */ 074 private boolean captureImage = false; 075 076 /** 077 * The list of registered observers 078 */ 079 private final transient ArrayList<CapturedImageObserver> observers = new ArrayList(); 080 081 /** 082 * Create a new canvas given the graphics configuration that runs as an on screen 083 * canvas. 084 * 085 * @param gc The graphics configuration for this canvas. May not be null. 086 */ 087 public ImageCaptureCanvas3D(GraphicsConfiguration gc) { 088 super(requireNonNull(gc, "gc == null")); 089 } 090 091 /** 092 * Create a new canvas that allows capture and may operate either on screen or 093 * off-screen. 094 * 095 * @param gc The graphics configuration to use for the canvas. May not be null. 096 * @param offScreen True if this is to operate in an offscreen mode 097 */ 098 public ImageCaptureCanvas3D(GraphicsConfiguration gc, boolean offScreen) { 099 super(requireNonNull(gc, "gc == null"), offScreen); 100 } 101 102 /** 103 * Process code after we have swapped the image to the foreground. Overrides the 104 * standard implementation to fetch the image to call to the observers if needed. 105 */ 106 @Override 107 public void postSwap() { 108 super.postSwap(); 109 110 if (!captureImage) 111 return; 112 113 // Do any of the observers want an image? 114 synchronized (observers) { 115 boolean captureNextFrame = false; 116 int size = observers.size(); 117 int i = 0; 118 while (!captureNextFrame && i < size) { 119 captureNextFrame = observers.get(i++).captureNextFrame(); 120 } 121 if (!captureNextFrame) 122 return; 123 124 // Get the image from the frame buffer. 125 BufferedImage img = getCurrentImage(); 126 127 notifyObservers(img); 128 } 129 } 130 131 /** 132 * Method that extracts an image from the current frame buffer of this canvas. If this 133 * method is called anywhere but from "postSwap", it could have incomplete information 134 * in it. 135 * 136 * @return The image from the current frame buffer. 137 */ 138 protected BufferedImage getCurrentImage() { 139 GraphicsContext3D ctx = getGraphicsContext3D(); 140 int width = this.getWidth(); 141 int height = this.getHeight(); 142 143 BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 144 145 ImageComponent2D comp = new ImageComponent2D(ImageComponent.FORMAT_RGB, img, false, false); 146 147 Raster ras = new Raster(new Point3f(-1.0f, -1.0f, -1.0f), 148 Raster.RASTER_COLOR, 149 0, 150 0, 151 width, 152 height, 153 comp, 154 null); 155 156 ctx.readRaster(ras); 157 158 return ras.getImage().getImage(); 159 } 160 161 /** 162 * Add an observer to this canvas to listen for images. Each instance can only be 163 * registered once. 164 * 165 * @param obs The observer to be registered. 166 */ 167 public void addCaptureObserver(CapturedImageObserver obs) { 168 if (obs != null) { 169 synchronized (observers) { 170 if (!observers.contains(obs)) { 171 observers.add(obs); 172 captureImage = true; 173 } 174 } 175 } 176 } 177 178 /** 179 * Remove a registered observer from this canvas. If the reference is null or cannot 180 * be found registered here it will silently ignore the request. 181 * 182 * @param obs The observer to be removed. 183 */ 184 public void removeCaptureObserver(CapturedImageObserver obs) { 185 if (obs != null) { 186 synchronized (observers) { 187 observers.remove(obs); 188 189 if (observers.isEmpty()) 190 captureImage = false; 191 } 192 } 193 } 194 195 /** 196 * Notify all the observers that we have an image to send them. 197 * 198 * @param img The image to be sent to the observers. 199 */ 200 private void notifyObservers(BufferedImage img) { 201 int num_obs = observers.size(); 202 for (int i = 0; i < num_obs; i++) { 203 CapturedImageObserver obs = observers.get(i); 204 if (obs.captureNextFrame()) { 205 try { 206 207 obs.canvasImageCaptured(img); 208 209 } catch (Exception ex) { 210 Logger.getLogger(ImageCaptureCanvas3D.class.getName()).log(Level.SEVERE, 211 "Error sending image to observer", ex); 212 } 213 } 214 } 215 } 216}