/* * JaamSim Discrete Event Simulation * Copyright (C) 2012 Ausenco Engineering Canada Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.jaamsim.controllers; import java.awt.Color; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import javax.imageio.ImageIO; import com.jaamsim.datatypes.IntegerVector; import com.jaamsim.math.Color4d; import com.jaamsim.render.Future; import com.jaamsim.render.OffscreenTarget; import com.jaamsim.ui.LogBox; import com.jaamsim.ui.View; import com.jaamsim.video.AviWriter; import com.jaamsim.video.vp8.Encoder; /** * The VideoRecorder class is used to generate a series of saved images (PNG only for the first implementation) from the renderer. * This allows the user to composite several views together. Once the recorder is created, calling sample() will * cause the renderer to draw the image and save it to disk. sample() blocks until the image has been written to disk. * @author matt.chudleigh * */ public class VideoRecorder { private static class ViewInfo { public int x; public int y; public int width; public int height; OffscreenTarget renderTarget; View view; } private ArrayList<ViewInfo> _views; private String _filenamePrefix; private int _width; private int _height; private int _sampleNumber = 0; private AviWriter _aviWriter; private Encoder _encoder; private boolean _isLoaded; private boolean _saveImages; private boolean _saveVideo; private Color4d _bgColor; public VideoRecorder(ArrayList<View> views, String filenamePrefix, int width, int height, int numFrames, boolean saveImages, boolean saveVideo, Color4d bgColor) { _filenamePrefix = filenamePrefix; _width = width; _height = height; _saveImages = saveImages; _saveVideo = saveVideo; _bgColor = bgColor; _views = new ArrayList<>(views.size()); // Cache the view position information and build the offscreen render targets for (View v : views) { ViewInfo vi = new ViewInfo(); IntegerVector windSize = v.getWindowSize(); IntegerVector windPos = v.getWindowPos(); vi.x = windPos.get(0); vi.y = windPos.get(1); vi.width = windSize.get(0); vi.height = windSize.get(1); vi.renderTarget = RenderManager.inst().createOffscreenTarget(vi.width, vi.height); vi.view = v; _views.add(vi); } if (_saveVideo) { String videoName = String.format("%s.avi", _filenamePrefix); _aviWriter = new AviWriter(videoName, width, height, numFrames); _encoder = new Encoder(); } _isLoaded = true; } public void sample() { assert(_isLoaded); if (!_saveVideo && !_saveImages) { return; // Don't waste the time } // long start = System.nanoTime(); ArrayList<Future<BufferedImage>> images = new ArrayList<>(); for (ViewInfo vi : _views) { images.add(RenderManager.inst().renderScreenShot(vi.view, vi.width, vi.height, vi.renderTarget)); } // Make sure all the renders are queued up before waiting for any of them. for (Future<BufferedImage> fi : images) { fi.blockUntilDone(); } // long renders = System.nanoTime(); // Now composite the images based on the views BufferedImage img = new BufferedImage(_width, _height, BufferedImage.TYPE_INT_RGB); Graphics2D g2 = img.createGraphics(); g2.setColor(new Color((float)_bgColor.r, (float)_bgColor.g, (float)_bgColor.b)); g2.fillRect(0, 0, _width, _height); for (int i = 0; i < images.size(); ++i) { ViewInfo vi = _views.get(i); boolean drawResult = g2.drawImage(images.get(i).get(), vi.x, vi.y, vi.width, vi.height, null); assert(drawResult == true); } // long composite = System.nanoTime(); if (_saveVideo) { boolean keyFrame = (_sampleNumber % 100) == 0; ByteBuffer frame = _encoder.encodeFrame(img, keyFrame); _aviWriter.addFrame(frame, keyFrame); } if (_saveImages) { try { FileOutputStream out = new FileOutputStream(String.format("%s%04d.png", _filenamePrefix, _sampleNumber)); // Finally write the image to disk ImageIO.write(img, "PNG", out); out.close(); } catch (FileNotFoundException ex) { LogBox.renderLogException(ex); } catch (IOException ex) { LogBox.renderLogException(ex); } } _sampleNumber++; // long writeout = System.nanoTime(); // // double renderTimeMS = (renders - start) * 0.000001; // double compositeTimeMS = (composite - renders) * 0.000001; // double writeoutTimeMS = (writeout - composite) * 0.000001; // // LogBox.formatRenderLog("Render: %f Composite: %f Writeout %f\n", renderTimeMS, compositeTimeMS, writeoutTimeMS); } public void freeResources() { if (_saveVideo) { _aviWriter.close(); } if (!_isLoaded) { return; } for (ViewInfo vi : _views) { RenderManager.inst().freeOffscreenTarget(vi.renderTarget); } _isLoaded = false; } }