Torque3D/Engine/source/gfx/screenshot.cpp

284 lines
10 KiB
C++

//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "gfx/screenshot.h"
#include "math/util/frustum.h"
#include "core/stream/fileStream.h"
#include "gui/core/guiCanvas.h"
#include "gfx/bitmap/pngUtils.h"
#include "console/engineAPI.h"
// Note: This will be initialized by the device.
ScreenShot *gScreenShot = NULL;
inline void sBlendPixelRGB888( U8* src, U8* dst, F32 factor )
{
U32 inFactor = factor * BIT(8);
U32 outFactor = BIT(8) - inFactor;
dst[0] = ((U32)src[0]*inFactor + (U32)dst[0]*outFactor) >> 8;
dst[1] = ((U32)src[1]*inFactor + (U32)dst[1]*outFactor) >> 8;
dst[2] = ((U32)src[2]*inFactor + (U32)dst[2]*outFactor) >> 8;
}
ScreenShot::ScreenShot()
: mPending( false ),
mWriteJPG( false ),
mTiles( 1 ),
mCurrTile( 0, 0 )
{
mFilename[0] = 0;
}
void ScreenShot::setPending( const char *filename, bool writeJPG, S32 tiles, F32 overlap )
{
dStrcpy( mFilename, filename, 256 );
mWriteJPG = writeJPG;
mTiles = getMax( tiles, 1 );
mPixelOverlap.set(getMin(overlap, 0.25f), getMin(overlap, 0.25f));
mPending = true;
}
void ScreenShot::tileFrustum( Frustum& frustum )
{
AssertFatal( mPending, "ScreenShot::tileFrustum() - This should only be called during screenshots!" );
// We do not need to make changes on a single tile.
if ( mTiles == 1 )
return;
frustum.tileFrustum(mTiles, mCurrTile, mFrustumOverlap);
}
void ScreenShot::tileGui( const Point2I &screenSize )
{
AssertFatal( mPending, "ScreenShot::tileGui() - This should only be called during screenshots!" );
// We do not need to make changes on a single tile.
if ( mTiles == 1 )
return;
GFX->setWorldMatrix( MatrixF::Identity );
S32 currTileX = mCurrTile.x;
S32 currTileY = (S32)mTiles - mCurrTile.y - 1; //Vertically flipped tile index
MatrixF tileMat( true );
Point3F tilePos(0,0,0);
tilePos.x = currTileX * (-screenSize.x) + mPixelOverlap.x * screenSize.x * (currTileX * 2 + 1);
tilePos.y = currTileY * (-screenSize.y) + mPixelOverlap.y * screenSize.y * (currTileY * 2 + 1);
tileMat.setPosition( tilePos + Point3F(0.5f, 0.5f, 0) );
tileMat.scale( Point3F( (F32)mTiles * (1-mPixelOverlap.x*2), (F32)mTiles * (1-mPixelOverlap.y*2), 0 ) );
GFX->setViewMatrix( tileMat );
}
void ScreenShot::capture( GuiCanvas *canvas )
{
AssertFatal( mPending, "ScreenShot::capture() - The capture wasn't pending!" );
if ( mTiles == 1 )
{
_singleCapture( canvas );
return;
}
char filename[256];
Point2I canvasSize = canvas->getPlatformWindow()->getVideoMode().resolution;
// Calculate the real final size taking overlap in account
Point2I overlapPixels( canvasSize.x * mPixelOverlap.x, canvasSize.y * mPixelOverlap.y );
// Calculate the overlap to be used by the frustum tiling,
// so it properly expands the frustum to overlap the amount of pixels we want
mFrustumOverlap.x = ((F32)canvasSize.x/(canvasSize.x - overlapPixels.x*2)) * ((F32)overlapPixels.x/(F32)canvasSize.x);
mFrustumOverlap.y = ((F32)canvasSize.y/(canvasSize.y - overlapPixels.y*2)) * ((F32)overlapPixels.y/(F32)canvasSize.y);
//overlapPixels.set(0,0);
// Get a buffer to write a row of tiles into.
GBitmap *outBuffer = new GBitmap( canvasSize.x * mTiles - overlapPixels.x * mTiles * 2 , canvasSize.y - overlapPixels.y );
// Open up the file on disk.
dSprintf( filename, 256, "%s.%s", mFilename, "png" );
FileStream fs;
if ( !fs.open( filename, Torque::FS::File::Write ) )
Con::errorf( "ScreenShot::capture() - Failed to open output file '%s'!", filename );
// Open a PNG stream for the final image
DeferredPNGWriter pngWriter;
pngWriter.begin(outBuffer->getFormat(), outBuffer->getWidth(), canvasSize.y * mTiles - overlapPixels.y * mTiles * 2, fs, 0);
// Render each tile to generate a huge screenshot.
for( U32 ty=0; ty < mTiles; ty++ )
{
for( S32 tx=0; tx < mTiles; tx++ )
{
// Set the current tile offset for tileFrustum().
mCurrTile.set( tx, mTiles - ty - 1 );
// Let the canvas render the scene.
canvas->renderFrame( false );
// Now grab the current back buffer.
GBitmap *gb = _captureBackBuffer();
// The current GFX device couldn't capture the backbuffer or it's unable of doing so.
if (gb == NULL)
return;
// Copy the captured bitmap into its tile
// within the output bitmap.
const U32 inStride = gb->getWidth() * gb->getBytesPerPixel();
const U8 *inColor = gb->getBits() + inStride * overlapPixels.y;
const U32 outStride = outBuffer->getWidth() * outBuffer->getBytesPerPixel();
const U32 inOverlapOffset = overlapPixels.x * gb->getBytesPerPixel();
const U32 inOverlapStride = overlapPixels.x * gb->getBytesPerPixel()*2;
const U32 outOffset = (tx * (gb->getWidth() - overlapPixels.x*2 )) * gb->getBytesPerPixel();
U8 *outColor = outBuffer->getWritableBits() + outOffset;
for( U32 row=0; row < gb->getHeight() - overlapPixels.y; row++ )
{
dMemcpy( outColor, inColor + inOverlapOffset, inStride - inOverlapStride );
//Grandient blend the left overlap area of this tile over the previous tile left border
if (tx && !(ty && row < overlapPixels.y))
{
U8 *blendOverlapSrc = (U8*)inColor;
U8 *blendOverlapDst = outColor - inOverlapOffset;
for ( U32 px=0; px < overlapPixels.x; px++)
{
F32 blendFactor = (F32)px / (F32)overlapPixels.x;
sBlendPixelRGB888(blendOverlapSrc, blendOverlapDst, blendFactor);
blendOverlapSrc += gb->getBytesPerPixel();
blendOverlapDst += outBuffer->getBytesPerPixel();
}
}
//Gradient blend against the rows the excess overlap rows already in the buffer
if (ty && row < overlapPixels.y)
{
F32 rowBlendFactor = (F32)row / (F32)overlapPixels.y;
U8 *blendSrc = outColor + outStride * (outBuffer->getHeight() - overlapPixels.y);
U8 *blendDst = outColor;
for ( U32 px=0; px < gb->getWidth() - overlapPixels.x*2; px++)
{
sBlendPixelRGB888(blendSrc, blendDst, 1.0-rowBlendFactor);
blendSrc += gb->getBytesPerPixel();
blendDst += outBuffer->getBytesPerPixel();
}
}
inColor += inStride;
outColor += outStride;
}
delete gb;
}
// Write the captured tile row into the PNG stream
pngWriter.append(outBuffer, outBuffer->getHeight()-overlapPixels.y);
}
//Close the PNG stream
pngWriter.end();
// We captured... clear the flag.
mPending = false;
}
void ScreenShot::_singleCapture( GuiCanvas *canvas )
{
// Let the canvas render the scene.
canvas->renderFrame( false );
// Now grab the current back buffer.
GBitmap *bitmap = _captureBackBuffer();
// The current GFX device couldn't capture the backbuffer or it's unable of doing so.
if (bitmap == NULL)
return;
// We captured... clear the flag.
mPending = false;
// We gotta attach the extension ourselves.
char filename[256];
dSprintf( filename, 256, "%s.%s", mFilename, mWriteJPG ? "jpg" : "png" );
// Open up the file on disk.
FileStream fs;
if ( !fs.open( filename, Torque::FS::File::Write ) )
Con::errorf( "ScreenShot::_singleCapture() - Failed to open output file '%s'!", filename );
else
{
// Write it and close.
if ( mWriteJPG )
bitmap->writeBitmap( "jpg", fs );
else
bitmap->writeBitmap( "png", fs );
fs.close();
}
// Cleanup.
delete bitmap;
}
DefineEngineFunction( screenShot, void,
( const char *file, const char *format, U32 tileCount, F32 tileOverlap ),
( 1, 0 ),
"Takes a screenshot with optional tiling to produce huge screenshots.\n"
"@param file The output image file path.\n"
"@param format Either JPEG or PNG.\n"
"@param tileCount If greater than 1 will tile the current screen size to take a large format screenshot.\n"
"@param tileOverlap The amount of horizontal and vertical overlap between the tiles used to remove tile edge artifacts from post effects.\n"
"@ingroup GFX\n" )
{
if ( !gScreenShot )
{
Con::errorf( "Screenshot module not initialized by device" );
return;
}
Torque::Path ssPath( file );
Torque::FS::CreatePath( ssPath );
Torque::FS::FileSystemRef fs = Torque::FS::GetFileSystem(ssPath);
Torque::Path newPath = fs->mapTo(ssPath);
gScreenShot->setPending( newPath.getFullPath(),
dStricmp( format, "JPEG" ) == 0,
tileCount,
tileOverlap );
}