screenshot.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "platform/platform.h"
  23. #include "gfx/screenshot.h"
  24. #include "math/util/frustum.h"
  25. #include "core/stream/fileStream.h"
  26. #include "gui/core/guiCanvas.h"
  27. #include "gfx/bitmap/pngUtils.h"
  28. #include "console/engineAPI.h"
  29. // Note: This will be initialized by the device.
  30. ScreenShot *gScreenShot = NULL;
  31. inline void sBlendPixelRGB888( U8* src, U8* dst, F32 factor )
  32. {
  33. U32 inFactor = factor * BIT(8);
  34. U32 outFactor = BIT(8) - inFactor;
  35. dst[0] = ((U32)src[0]*inFactor + (U32)dst[0]*outFactor) >> 8;
  36. dst[1] = ((U32)src[1]*inFactor + (U32)dst[1]*outFactor) >> 8;
  37. dst[2] = ((U32)src[2]*inFactor + (U32)dst[2]*outFactor) >> 8;
  38. }
  39. ScreenShot::ScreenShot()
  40. : mPending( false ),
  41. mWriteJPG( false ),
  42. mTiles( 1 ),
  43. mCurrTile( 0, 0 )
  44. {
  45. mFilename[0] = 0;
  46. }
  47. void ScreenShot::setPending( const char *filename, bool writeJPG, S32 tiles, F32 overlap )
  48. {
  49. dStrcpy( mFilename, filename, 256 );
  50. mWriteJPG = writeJPG;
  51. mTiles = getMax( tiles, 1 );
  52. mPixelOverlap.set(getMin(overlap, 0.25f), getMin(overlap, 0.25f));
  53. mPending = true;
  54. }
  55. void ScreenShot::tileFrustum( Frustum& frustum )
  56. {
  57. AssertFatal( mPending, "ScreenShot::tileFrustum() - This should only be called during screenshots!" );
  58. // We do not need to make changes on a single tile.
  59. if ( mTiles == 1 )
  60. return;
  61. frustum.tileFrustum(mTiles, mCurrTile, mFrustumOverlap);
  62. }
  63. void ScreenShot::tileGui( const Point2I &screenSize )
  64. {
  65. AssertFatal( mPending, "ScreenShot::tileGui() - This should only be called during screenshots!" );
  66. // We do not need to make changes on a single tile.
  67. if ( mTiles == 1 )
  68. return;
  69. GFX->setWorldMatrix( MatrixF::Identity );
  70. S32 currTileX = mCurrTile.x;
  71. S32 currTileY = (S32)mTiles - mCurrTile.y - 1; //Vertically flipped tile index
  72. MatrixF tileMat( true );
  73. Point3F tilePos(0,0,0);
  74. tilePos.x = currTileX * (-screenSize.x) + mPixelOverlap.x * screenSize.x * (currTileX * 2 + 1);
  75. tilePos.y = currTileY * (-screenSize.y) + mPixelOverlap.y * screenSize.y * (currTileY * 2 + 1);
  76. tileMat.setPosition( tilePos + Point3F(0.5f, 0.5f, 0) );
  77. tileMat.scale( Point3F( (F32)mTiles * (1-mPixelOverlap.x*2), (F32)mTiles * (1-mPixelOverlap.y*2), 0 ) );
  78. GFX->setViewMatrix( tileMat );
  79. }
  80. void ScreenShot::capture( GuiCanvas *canvas )
  81. {
  82. AssertFatal( mPending, "ScreenShot::capture() - The capture wasn't pending!" );
  83. if ( mTiles == 1 )
  84. {
  85. _singleCapture( canvas );
  86. return;
  87. }
  88. char filename[256];
  89. Point2I canvasSize = canvas->getPlatformWindow()->getVideoMode().resolution;
  90. // Calculate the real final size taking overlap in account
  91. Point2I overlapPixels( canvasSize.x * mPixelOverlap.x, canvasSize.y * mPixelOverlap.y );
  92. // Calculate the overlap to be used by the frustum tiling,
  93. // so it properly expands the frustum to overlap the amount of pixels we want
  94. mFrustumOverlap.x = ((F32)canvasSize.x/(canvasSize.x - overlapPixels.x*2)) * ((F32)overlapPixels.x/(F32)canvasSize.x);
  95. mFrustumOverlap.y = ((F32)canvasSize.y/(canvasSize.y - overlapPixels.y*2)) * ((F32)overlapPixels.y/(F32)canvasSize.y);
  96. //overlapPixels.set(0,0);
  97. // Get a buffer to write a row of tiles into.
  98. GBitmap *outBuffer = new GBitmap( canvasSize.x * mTiles - overlapPixels.x * mTiles * 2 , canvasSize.y - overlapPixels.y );
  99. // Open up the file on disk.
  100. dSprintf( filename, 256, "%s.%s", mFilename, "png" );
  101. FileStream fs;
  102. if ( !fs.open( filename, Torque::FS::File::Write ) )
  103. Con::errorf( "ScreenShot::capture() - Failed to open output file '%s'!", filename );
  104. // Open a PNG stream for the final image
  105. DeferredPNGWriter pngWriter;
  106. pngWriter.begin(outBuffer->getFormat(), outBuffer->getWidth(), canvasSize.y * mTiles - overlapPixels.y * mTiles * 2, fs, 0);
  107. // Render each tile to generate a huge screenshot.
  108. for( U32 ty=0; ty < mTiles; ty++ )
  109. {
  110. for( S32 tx=0; tx < mTiles; tx++ )
  111. {
  112. // Set the current tile offset for tileFrustum().
  113. mCurrTile.set( tx, mTiles - ty - 1 );
  114. // Let the canvas render the scene.
  115. canvas->renderFrame( false );
  116. // Now grab the current back buffer.
  117. GBitmap *gb = _captureBackBuffer();
  118. // The current GFX device couldn't capture the backbuffer or it's unable of doing so.
  119. if (gb == NULL)
  120. return;
  121. // Copy the captured bitmap into its tile
  122. // within the output bitmap.
  123. const U32 inStride = gb->getWidth() * gb->getBytesPerPixel();
  124. const U8 *inColor = gb->getBits() + inStride * overlapPixels.y;
  125. const U32 outStride = outBuffer->getWidth() * outBuffer->getBytesPerPixel();
  126. const U32 inOverlapOffset = overlapPixels.x * gb->getBytesPerPixel();
  127. const U32 inOverlapStride = overlapPixels.x * gb->getBytesPerPixel()*2;
  128. const U32 outOffset = (tx * (gb->getWidth() - overlapPixels.x*2 )) * gb->getBytesPerPixel();
  129. U8 *outColor = outBuffer->getWritableBits() + outOffset;
  130. for( U32 row=0; row < gb->getHeight() - overlapPixels.y; row++ )
  131. {
  132. dMemcpy( outColor, inColor + inOverlapOffset, inStride - inOverlapStride );
  133. //Grandient blend the left overlap area of this tile over the previous tile left border
  134. if (tx && !(ty && row < overlapPixels.y))
  135. {
  136. U8 *blendOverlapSrc = (U8*)inColor;
  137. U8 *blendOverlapDst = outColor - inOverlapOffset;
  138. for ( U32 px=0; px < overlapPixels.x; px++)
  139. {
  140. F32 blendFactor = (F32)px / (F32)overlapPixels.x;
  141. sBlendPixelRGB888(blendOverlapSrc, blendOverlapDst, blendFactor);
  142. blendOverlapSrc += gb->getBytesPerPixel();
  143. blendOverlapDst += outBuffer->getBytesPerPixel();
  144. }
  145. }
  146. //Gradient blend against the rows the excess overlap rows already in the buffer
  147. if (ty && row < overlapPixels.y)
  148. {
  149. F32 rowBlendFactor = (F32)row / (F32)overlapPixels.y;
  150. U8 *blendSrc = outColor + outStride * (outBuffer->getHeight() - overlapPixels.y);
  151. U8 *blendDst = outColor;
  152. for ( U32 px=0; px < gb->getWidth() - overlapPixels.x*2; px++)
  153. {
  154. sBlendPixelRGB888(blendSrc, blendDst, 1.0-rowBlendFactor);
  155. blendSrc += gb->getBytesPerPixel();
  156. blendDst += outBuffer->getBytesPerPixel();
  157. }
  158. }
  159. inColor += inStride;
  160. outColor += outStride;
  161. }
  162. delete gb;
  163. }
  164. // Write the captured tile row into the PNG stream
  165. pngWriter.append(outBuffer, outBuffer->getHeight()-overlapPixels.y);
  166. }
  167. //Close the PNG stream
  168. pngWriter.end();
  169. // We captured... clear the flag.
  170. mPending = false;
  171. }
  172. void ScreenShot::_singleCapture( GuiCanvas *canvas )
  173. {
  174. // Let the canvas render the scene.
  175. canvas->renderFrame( false );
  176. // Now grab the current back buffer.
  177. GBitmap *bitmap = _captureBackBuffer();
  178. // The current GFX device couldn't capture the backbuffer or it's unable of doing so.
  179. if (bitmap == NULL)
  180. return;
  181. // We captured... clear the flag.
  182. mPending = false;
  183. // We gotta attach the extension ourselves.
  184. char filename[256];
  185. dSprintf( filename, 256, "%s.%s", mFilename, mWriteJPG ? "jpg" : "png" );
  186. // Open up the file on disk.
  187. FileStream fs;
  188. if ( !fs.open( filename, Torque::FS::File::Write ) )
  189. Con::errorf( "ScreenShot::_singleCapture() - Failed to open output file '%s'!", filename );
  190. else
  191. {
  192. // Write it and close.
  193. if ( mWriteJPG )
  194. bitmap->writeBitmap( "jpg", fs );
  195. else
  196. bitmap->writeBitmap( "png", fs );
  197. fs.close();
  198. }
  199. // Cleanup.
  200. delete bitmap;
  201. }
  202. DefineEngineFunction( screenShot, void,
  203. ( const char *file, const char *format, U32 tileCount, F32 tileOverlap ),
  204. ( 1, 0 ),
  205. "Takes a screenshot with optional tiling to produce huge screenshots.\n"
  206. "@param file The output image file path.\n"
  207. "@param format Either JPEG or PNG.\n"
  208. "@param tileCount If greater than 1 will tile the current screen size to take a large format screenshot.\n"
  209. "@param tileOverlap The amount of horizontal and vertical overlap between the tiles used to remove tile edge artifacts from post effects.\n"
  210. "@ingroup GFX\n" )
  211. {
  212. if ( !gScreenShot )
  213. {
  214. Con::errorf( "Screenshot module not initialized by device" );
  215. return;
  216. }
  217. Torque::Path ssPath( file );
  218. Torque::FS::CreatePath( ssPath );
  219. Torque::FS::FileSystemRef fs = Torque::FS::GetFileSystem(ssPath);
  220. Torque::Path newPath = fs->mapTo(ssPath);
  221. gScreenShot->setPending( newPath.getFullPath(),
  222. dStricmp( format, "JPEG" ) == 0,
  223. tileCount,
  224. tileOverlap );
  225. }