mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 04:34:48 +00:00
Generating image previews of image assets was failing DDS remove redundant check for stream status. STB requires the file to be free before being written to, move check to make sure we can open the path into gBitmap and remove FileStream checks from everywhere else.
275 lines
10 KiB
C++
275 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" );
|
|
|
|
// Write it and close.
|
|
if ( mWriteJPG )
|
|
bitmap->writeBitmap( "jpg", filename);
|
|
else
|
|
bitmap->writeBitmap( "png", filename);
|
|
|
|
// 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 );
|
|
}
|
|
|