BACKENDS: Compress screenshots using PNG if available
Closes gh-948.
This commit is contained in:
parent
71989715d0
commit
fa0bb7dd5a
6 changed files with 169 additions and 21 deletions
|
@ -24,8 +24,8 @@ MODULES += \
|
||||||
backends \
|
backends \
|
||||||
engines \
|
engines \
|
||||||
video \
|
video \
|
||||||
graphics \
|
|
||||||
image \
|
image \
|
||||||
|
graphics \
|
||||||
audio \
|
audio \
|
||||||
common \
|
common \
|
||||||
po
|
po
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
#include "backends/graphics/opengl/pipelines/shader.h"
|
#include "backends/graphics/opengl/pipelines/shader.h"
|
||||||
#include "backends/graphics/opengl/shader.h"
|
#include "backends/graphics/opengl/shader.h"
|
||||||
|
|
||||||
|
#include "common/array.h"
|
||||||
#include "common/textconsole.h"
|
#include "common/textconsole.h"
|
||||||
#include "common/translation.h"
|
#include "common/translation.h"
|
||||||
#include "common/algorithm.h"
|
#include "common/algorithm.h"
|
||||||
|
@ -43,6 +44,10 @@
|
||||||
#include "graphics/font.h"
|
#include "graphics/font.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_PNG
|
||||||
|
#include "image/png.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace OpenGL {
|
namespace OpenGL {
|
||||||
|
|
||||||
OpenGLGraphicsManager::OpenGLGraphicsManager()
|
OpenGLGraphicsManager::OpenGLGraphicsManager()
|
||||||
|
@ -1311,33 +1316,35 @@ bool OpenGLGraphicsManager::saveScreenshot(const Common::String &filename) const
|
||||||
// Since we use a 3 byte per pixel mode, we can use width % 4 here, since
|
// Since we use a 3 byte per pixel mode, we can use width % 4 here, since
|
||||||
// it is equal to 4 - (width * 3) % 4. (4 - (width * Bpp) % 4, is the
|
// it is equal to 4 - (width * 3) % 4. (4 - (width * Bpp) % 4, is the
|
||||||
// usual way of computing the padding bytes required).
|
// usual way of computing the padding bytes required).
|
||||||
|
// GL_PACK_ALIGNMENT is 4, so this line padding is required for PNG too
|
||||||
const uint linePaddingSize = width % 4;
|
const uint linePaddingSize = width % 4;
|
||||||
const uint lineSize = width * 3 + linePaddingSize;
|
const uint lineSize = width * 3 + linePaddingSize;
|
||||||
|
|
||||||
// Allocate memory for screenshot
|
Common::DumpFile out;
|
||||||
uint8 *pixels = new uint8[lineSize * height];
|
if (!out.open(filename)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Get pixel data from OpenGL buffer
|
Common::Array<uint8> pixels;
|
||||||
GL_CALL(glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels));
|
pixels.resize(lineSize * height);
|
||||||
|
GL_CALL(glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, &pixels.front()));
|
||||||
|
|
||||||
|
#ifdef USE_PNG
|
||||||
|
const Graphics::PixelFormat format(3, 8, 8, 8, 0, 16, 8, 0, 0);
|
||||||
|
Graphics::Surface data;
|
||||||
|
data.init(width, height, lineSize, &pixels.front(), format);
|
||||||
|
return Image::writePNG(out, data, true);
|
||||||
|
#else
|
||||||
// BMP stores as BGR. Since we can't assume that GL_BGR is supported we
|
// BMP stores as BGR. Since we can't assume that GL_BGR is supported we
|
||||||
// will swap the components from the RGB we read to BGR on our own.
|
// will swap the components from the RGB we read to BGR on our own.
|
||||||
for (uint y = height; y-- > 0;) {
|
for (uint y = height; y-- > 0;) {
|
||||||
uint8 *line = pixels + y * lineSize;
|
uint8 *line = &pixels.front() + y * lineSize;
|
||||||
|
|
||||||
for (uint x = width; x > 0; --x, line += 3) {
|
for (uint x = width; x > 0; --x, line += 3) {
|
||||||
SWAP(line[0], line[2]);
|
SWAP(line[0], line[2]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open file
|
|
||||||
Common::DumpFile out;
|
|
||||||
if (!out.open(filename)) {
|
|
||||||
delete[] pixels;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write BMP header
|
|
||||||
out.writeByte('B');
|
out.writeByte('B');
|
||||||
out.writeByte('M');
|
out.writeByte('M');
|
||||||
out.writeUint32LE(height * lineSize + 54);
|
out.writeUint32LE(height * lineSize + 54);
|
||||||
|
@ -1354,13 +1361,10 @@ bool OpenGLGraphicsManager::saveScreenshot(const Common::String &filename) const
|
||||||
out.writeUint32LE(0);
|
out.writeUint32LE(0);
|
||||||
out.writeUint32LE(0);
|
out.writeUint32LE(0);
|
||||||
out.writeUint32LE(0);
|
out.writeUint32LE(0);
|
||||||
|
out.write(&pixels.front(), pixels.size());
|
||||||
|
|
||||||
// Write pixel data to BMP
|
|
||||||
out.write(pixels, lineSize * height);
|
|
||||||
|
|
||||||
// Free allocated memory
|
|
||||||
delete[] pixels;
|
|
||||||
return true;
|
return true;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
} // End of namespace OpenGL
|
} // End of namespace OpenGL
|
||||||
|
|
|
@ -633,7 +633,11 @@ bool OpenGLSdlGraphicsManager::notifyEvent(const Common::Event &event) {
|
||||||
for (int n = 0;; n++) {
|
for (int n = 0;; n++) {
|
||||||
SDL_RWops *file;
|
SDL_RWops *file;
|
||||||
|
|
||||||
|
#ifdef USE_PNG
|
||||||
|
filename = Common::String::format("scummvm%05d.png", n);
|
||||||
|
#else
|
||||||
filename = Common::String::format("scummvm%05d.bmp", n);
|
filename = Common::String::format("scummvm%05d.bmp", n);
|
||||||
|
#endif
|
||||||
|
|
||||||
file = SDL_RWFromFile((screenshotsPath + filename).c_str(), "r");
|
file = SDL_RWFromFile((screenshotsPath + filename).c_str(), "r");
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,10 @@
|
||||||
#include "graphics/scaler/aspect.h"
|
#include "graphics/scaler/aspect.h"
|
||||||
#include "graphics/surface.h"
|
#include "graphics/surface.h"
|
||||||
#include "gui/EventRecorder.h"
|
#include "gui/EventRecorder.h"
|
||||||
|
#ifdef USE_PNG
|
||||||
|
#include "common/file.h"
|
||||||
|
#include "image/png.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
static const OSystem::GraphicsMode s_supportedShaders[] = {
|
static const OSystem::GraphicsMode s_supportedShaders[] = {
|
||||||
{"NONE", "Normal (no shader)", 0},
|
{"NONE", "Normal (no shader)", 0},
|
||||||
|
@ -1321,8 +1325,66 @@ void SurfaceSdlGraphicsManager::internUpdateScreen() {
|
||||||
bool SurfaceSdlGraphicsManager::saveScreenshot(const char *filename) {
|
bool SurfaceSdlGraphicsManager::saveScreenshot(const char *filename) {
|
||||||
assert(_hwscreen != NULL);
|
assert(_hwscreen != NULL);
|
||||||
|
|
||||||
Common::StackLock lock(_graphicsMutex); // Lock the mutex until this function ends
|
Common::StackLock lock(_graphicsMutex);
|
||||||
|
#ifdef USE_PNG
|
||||||
|
Common::DumpFile out;
|
||||||
|
if (!out.open(filename)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||||
|
SDL_Surface *rgbScreen = SDL_ConvertSurfaceFormat(_hwscreen, SDL_PIXELFORMAT_RGB24, 0);
|
||||||
|
#else
|
||||||
|
// This block of code was taken mostly as-is from SDL 1.2's SDL_SaveBMP_RW
|
||||||
|
SDL_Surface *rgbScreen = SDL_CreateRGBSurface(SDL_SWSURFACE,
|
||||||
|
_hwscreen->w,
|
||||||
|
_hwscreen->h,
|
||||||
|
24,
|
||||||
|
#ifdef SCUMM_LITTLE_ENDIAN
|
||||||
|
0x0000FF, 0x00FF00, 0xFF0000,
|
||||||
|
#else
|
||||||
|
0xFF0000, 0x00FF00, 0x0000FF,
|
||||||
|
#endif
|
||||||
|
0);
|
||||||
|
if (rgbScreen == nullptr) {
|
||||||
|
warning("Could not create RGB24 surface");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Rect bounds;
|
||||||
|
bounds.x = bounds.y = 0;
|
||||||
|
bounds.w = _hwscreen->w;
|
||||||
|
bounds.h = _hwscreen->h;
|
||||||
|
if (SDL_LowerBlit(_hwscreen, &bounds, rgbScreen, &bounds) < 0) {
|
||||||
|
SDL_FreeSurface(rgbScreen);
|
||||||
|
rgbScreen = nullptr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (rgbScreen == nullptr) {
|
||||||
|
warning("Could not convert hardware surface to RGB24");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = SDL_LockSurface(rgbScreen);
|
||||||
|
if (result < 0) {
|
||||||
|
warning("Could not lock RGB surface");
|
||||||
|
SDL_FreeSurface(rgbScreen);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Graphics::PixelFormat format(3, 8, 8, 8, 0, 16, 8, 0, 0);
|
||||||
|
Graphics::Surface data;
|
||||||
|
data.init(rgbScreen->w, rgbScreen->h, rgbScreen->pitch, rgbScreen->pixels, format);
|
||||||
|
const bool success = Image::writePNG(out, data);
|
||||||
|
|
||||||
|
SDL_UnlockSurface(rgbScreen);
|
||||||
|
SDL_FreeSurface(rgbScreen);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
#else
|
||||||
return SDL_SaveBMP(_hwscreen, filename) == 0;
|
return SDL_SaveBMP(_hwscreen, filename) == 0;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void SurfaceSdlGraphicsManager::setFullscreenMode(bool enable) {
|
void SurfaceSdlGraphicsManager::setFullscreenMode(bool enable) {
|
||||||
|
@ -2534,7 +2596,11 @@ bool SurfaceSdlGraphicsManager::notifyEvent(const Common::Event &event) {
|
||||||
for (int n = 0;; n++) {
|
for (int n = 0;; n++) {
|
||||||
SDL_RWops *file;
|
SDL_RWops *file;
|
||||||
|
|
||||||
|
#ifdef USE_PNG
|
||||||
|
filename = Common::String::format("scummvm%05d.png", n);
|
||||||
|
#else
|
||||||
filename = Common::String::format("scummvm%05d.bmp", n);
|
filename = Common::String::format("scummvm%05d.bmp", n);
|
||||||
|
#endif
|
||||||
|
|
||||||
file = SDL_RWFromFile((screenshotsPath + filename).c_str(), "r");
|
file = SDL_RWFromFile((screenshotsPath + filename).c_str(), "r");
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
#include "graphics/pixelformat.h"
|
#include "graphics/pixelformat.h"
|
||||||
#include "graphics/surface.h"
|
#include "graphics/surface.h"
|
||||||
|
|
||||||
|
#include "common/array.h"
|
||||||
#include "common/stream.h"
|
#include "common/stream.h"
|
||||||
|
|
||||||
namespace Image {
|
namespace Image {
|
||||||
|
@ -65,12 +66,24 @@ void pngWarning(png_structp pngptr, png_const_charp warningMsg) {
|
||||||
warning("%s", warningMsg);
|
warning("%s", warningMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// libpng-I/O-helper:
|
// libpng-I/O-helpers:
|
||||||
void pngReadFromStream(png_structp pngPtr, png_bytep data, png_size_t length) {
|
void pngReadFromStream(png_structp pngPtr, png_bytep data, png_size_t length) {
|
||||||
void *readIOptr = png_get_io_ptr(pngPtr);
|
void *readIOptr = png_get_io_ptr(pngPtr);
|
||||||
Common::SeekableReadStream *stream = (Common::SeekableReadStream *)readIOptr;
|
Common::SeekableReadStream *stream = (Common::SeekableReadStream *)readIOptr;
|
||||||
stream->read(data, length);
|
stream->read(data, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void pngWriteToStream(png_structp pngPtr, png_bytep data, png_size_t length) {
|
||||||
|
void *writeIOptr = png_get_io_ptr(pngPtr);
|
||||||
|
Common::WriteStream *stream = (Common::WriteStream *)writeIOptr;
|
||||||
|
stream->write(data, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pngFlushStream(png_structp pngPtr) {
|
||||||
|
void *writeIOptr = png_get_io_ptr(pngPtr);
|
||||||
|
Common::WriteStream *stream = (Common::WriteStream *)writeIOptr;
|
||||||
|
stream->flush();
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -232,4 +245,56 @@ bool PNGDecoder::loadStream(Common::SeekableReadStream &stream) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool writePNG(Common::WriteStream &out, const Graphics::Surface &input, const bool bottomUp) {
|
||||||
|
#ifdef USE_PNG
|
||||||
|
const Graphics::PixelFormat requiredFormat(3, 8, 8, 8, 0, 16, 8, 0, 0);
|
||||||
|
|
||||||
|
if (input.format != requiredFormat) {
|
||||||
|
warning("Cannot currently write PNG with pixel format other than %s", requiredFormat.toString().c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
png_structp pngPtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||||||
|
if (!pngPtr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
png_infop infoPtr = png_create_info_struct(pngPtr);
|
||||||
|
if (!infoPtr) {
|
||||||
|
png_destroy_write_struct(&pngPtr, NULL);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
png_infop endInfo = png_create_info_struct(pngPtr);
|
||||||
|
if (!endInfo) {
|
||||||
|
png_destroy_write_struct(&pngPtr, &infoPtr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
png_set_error_fn(pngPtr, NULL, pngError, pngWarning);
|
||||||
|
// TODO: The manual says errors should be handled via setjmp
|
||||||
|
|
||||||
|
png_set_write_fn(pngPtr, &out, pngWriteToStream, pngFlushStream);
|
||||||
|
|
||||||
|
png_set_IHDR(pngPtr, infoPtr, input.w, input.h, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||||||
|
|
||||||
|
Common::Array<const uint8 *> rows;
|
||||||
|
rows.reserve(input.h);
|
||||||
|
if (bottomUp) {
|
||||||
|
for (uint y = input.h; y-- > 0;) {
|
||||||
|
rows.push_back((const uint8 *)input.getBasePtr(0, y));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (uint y = 0; y < input.h; ++y) {
|
||||||
|
rows.push_back((const uint8 *)input.getBasePtr(0, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
png_set_rows(pngPtr, infoPtr, const_cast<uint8 **>(&rows.front()));
|
||||||
|
png_write_png(pngPtr, infoPtr, 0, NULL);
|
||||||
|
png_destroy_write_struct(&pngPtr, &infoPtr);
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
} // End of namespace Image
|
} // End of namespace Image
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
class SeekableReadStream;
|
class SeekableReadStream;
|
||||||
|
class WriteStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Graphics {
|
namespace Graphics {
|
||||||
|
@ -62,6 +63,14 @@ private:
|
||||||
Graphics::Surface *_outputSurface;
|
Graphics::Surface *_outputSurface;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outputs a compressed PNG stream of the given input surface.
|
||||||
|
*
|
||||||
|
* @param bottomUp Flip the vertical axis so pixel data is drawn from the
|
||||||
|
* bottom up, instead of from the top down.
|
||||||
|
*/
|
||||||
|
bool writePNG(Common::WriteStream &out, const Graphics::Surface &input, const bool bottomUp = false);
|
||||||
|
|
||||||
} // End of namespace Image
|
} // End of namespace Image
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue