/* * Copyright 2016 MovingBlocks * * 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 org.terasology.rendering.opengl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terasology.config.Config; import org.terasology.config.RenderingConfig; import org.terasology.context.Context; import org.terasology.engine.paths.PathManager; import org.terasology.engine.subsystem.common.ThreadManager; import org.terasology.registry.CoreRegistry; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Path; import java.text.SimpleDateFormat; import java.util.Date; import static org.terasology.rendering.opengl.DefaultDynamicFBOs.FINAL; // TODO: Future work should not only "think" in terms of a DAG-like rendering pipeline // TODO: but actually implement one, see https://github.com/MovingBlocks/Terasology/issues/1741 public class ScreenGrabber { private static final Logger logger = LoggerFactory.getLogger(ScreenGrabber.class); private RenderingConfig renderingConfig; private ThreadManager threadManager; private float currentExposure; private boolean isTakingScreenshot; /** * @param context */ public ScreenGrabber(Context context) { threadManager = CoreRegistry.get(ThreadManager.class); renderingConfig = context.get(Config.class).getRendering(); } /** * Returns the current exposure value (set in downsampleSceneAndUpdateExposure()). * * @return a float representing the current exposure value. */ public float getExposure() { return currentExposure; } // TODO: Remove this method, temporarily here for DownSampleSceneAndUpdateExposure public void setExposure(float exposure) { this.currentExposure = exposure; } /** * Triggers a screenshot. * * Notice that this method just starts the process: screenshot data is captured and written to file * as soon as possible but not necessarily immediately after the trigger. */ public void takeScreenshot() { isTakingScreenshot = true; } /** * Schedules the saving of screenshot data to file. * * Screenshot data from the GPU is obtained as soon as this method executes. However, the data is only scheduled * to be written to file, by submitting a task to the ThreadManager. The task is then executed as soon as possible * but not necessarily immediately. * * The file is then saved in the designated screenshot folder with a filename in the form: * * Terasology-[yyMMddHHmmss]-[width]x[height].[format] * * If no screenshot data is available an error is logged and the method returns doing nothing. */ public void saveScreenshot() { final ByteBuffer buffer = FINAL.getColorBufferRawData(); if (buffer == null) { logger.error("No screenshot data available. No screenshot will be saved."); return; } int width = FINAL.width(); int height = FINAL.height(); Runnable task = () -> { SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss"); final String format = renderingConfig.getScreenshotFormat(); final String fileName = "Terasology-" + sdf.format(new Date()) + "-" + width + "x" + height + "." + format; Path path = PathManager.getInstance().getScreenshotPath().resolve(fileName); BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { int i = (x + width * y) * 4; int r = buffer.get(i) & 0xFF; int g = buffer.get(i + 1) & 0xFF; int b = buffer.get(i + 2) & 0xFF; image.setRGB(x, height - (y + 1), (0xFF << 24) | (r << 16) | (g << 8) | b); } } try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(path))) { ImageIO.write(image, format, out); logger.info("Screenshot '" + fileName + "' saved! "); } catch (IOException e) { logger.warn("Failed to save screenshot!", e); } }; threadManager.submitTask("Write screenshot", task); isTakingScreenshot = false; } /** * Returns true if the rendering engine is not in the process of taking a screenshot. * Returns false if a screenshot is being taken. * * @return true if no screenshot is being taken, false otherwise */ // for code readability it make sense to have this method rather than its opposite. public boolean isNotTakingScreenshot() { return !isTakingScreenshot; } }