123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- //-----------------------------------------------------------------------------
- // 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 );
- }
|