Use SDL_Image to handle saving PNG screenshots instead of SDL_SavePNG

When iceiceice (@cbeck88) added "save screenshots as PNGs" support back in 2014
(8dfdc6b185) we had yet to switch to SDL 2. At the
time, SDL_Image didn't provide any method of saving PNG files on its own. However,
it does as of SDL_Image 2.0.0.

A small caveat is that this built-in PNG save functionality is a bit less efficient
at compression than SDL_SavePNG. A test in DiD S1 with SDL_SavePNG_RW vs IMG_SavePNG_RW
yielded a file ~ 1 MB larger in the latter case.

This removed SDL_SavePNG and it's optional build-time dependency of libpng.
This commit is contained in:
Charles Dang 2018-03-15 02:43:44 +11:00
parent 9b128d7ec8
commit dfc42e8a8d
11 changed files with 2 additions and 356 deletions

View file

@ -42,9 +42,6 @@ order to build Wesnoth:
The following libraries are optional dependencies that enable additional The following libraries are optional dependencies that enable additional
features: features:
* libpng:
PNG screenshots, otherwise only BMP is supported.
* D-Bus (libdbus-1): * D-Bus (libdbus-1):
Desktop notifications on Linux, *BSD, etc. Desktop notifications on Linux, *BSD, etc.

View file

@ -2804,13 +2804,6 @@
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Debug|Win32'">$(IntDir)SDL\</ObjectFileName> <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Debug|Win32'">$(IntDir)SDL\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Release|Win32'">$(IntDir)SDL\</ObjectFileName> <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Release|Win32'">$(IntDir)SDL\</ObjectFileName>
</ClCompile> </ClCompile>
<ClCompile Include="..\..\src\SDL_SavePNG\savepng.c">
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)SDL\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='ReleaseDEBUG|Win32'">$(IntDir)SDL\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)SDL\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Debug|Win32'">$(IntDir)SDL\</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Release|Win32'">$(IntDir)SDL\</ObjectFileName>
</ClCompile>
<ClCompile Include="..\..\src\seed_rng.cpp" /> <ClCompile Include="..\..\src\seed_rng.cpp" />
<ClCompile Include="..\..\src\serialization\tag.cpp"> <ClCompile Include="..\..\src\serialization\tag.cpp">
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)Serialization\</ObjectFileName> <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)Serialization\</ObjectFileName>
@ -3974,7 +3967,6 @@
<ClInclude Include="..\..\src\sdl\userevent.hpp" /> <ClInclude Include="..\..\src\sdl\userevent.hpp" />
<ClInclude Include="..\..\src\sdl\utils.hpp" /> <ClInclude Include="..\..\src\sdl\utils.hpp" />
<ClInclude Include="..\..\src\sdl\window.hpp" /> <ClInclude Include="..\..\src\sdl\window.hpp" />
<ClInclude Include="..\..\src\SDL_SavePNG\savepng.h" />
<ClInclude Include="..\..\src\seed_rng.hpp" /> <ClInclude Include="..\..\src\seed_rng.hpp" />
<ClInclude Include="..\..\src\settings.hpp" /> <ClInclude Include="..\..\src\settings.hpp" />
<ClInclude Include="..\..\src\show_dialog.hpp" /> <ClInclude Include="..\..\src\show_dialog.hpp" />

View file

@ -1220,9 +1220,6 @@
<ClCompile Include="..\..\src\sdl\surface.cpp"> <ClCompile Include="..\..\src\sdl\surface.cpp">
<Filter>SDL</Filter> <Filter>SDL</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="..\..\src\SDL_SavePNG\savepng.c">
<Filter>SDL</Filter>
</ClCompile>
<ClCompile Include="..\..\src\sdl\utils.cpp"> <ClCompile Include="..\..\src\sdl\utils.cpp">
<Filter>SDL</Filter> <Filter>SDL</Filter>
</ClCompile> </ClCompile>
@ -2620,9 +2617,6 @@
<ClInclude Include="..\..\src\sdl\surface.hpp"> <ClInclude Include="..\..\src\sdl\surface.hpp">
<Filter>SDL</Filter> <Filter>SDL</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\..\src\SDL_SavePNG\savepng.h">
<Filter>SDL</Filter>
</ClInclude>
<ClInclude Include="..\..\src\sdl\userevent.hpp"> <ClInclude Include="..\..\src\sdl\userevent.hpp">
<Filter>SDL</Filter> <Filter>SDL</Filter>
</ClInclude> </ClInclude>

View file

@ -219,15 +219,6 @@ set_target_properties(lua
GetSources("libwesnoth_sdl" wesnoth-sdl_SRC) GetSources("libwesnoth_sdl" wesnoth-sdl_SRC)
# If requested, compile and link this file also to be able to save png files
if(ENABLE_LIBPNG AND PNG_FOUND)
set(wesnoth-sdl_SRC
${wesnoth-sdl_SRC}
SDL_SavePNG/savepng.c
)
endif(ENABLE_LIBPNG AND PNG_FOUND)
add_library(wesnoth-sdl add_library(wesnoth-sdl
${LIBRARY_TYPE} ${LIBRARY_TYPE}
EXCLUDE_FROM_ALL EXCLUDE_FROM_ALL

View file

@ -74,9 +74,6 @@ libwesnoth_game = client_env.Library("wesnoth-game", libwesnoth_game_sources)
#---libwesnoth_sdl--- #---libwesnoth_sdl---
libwesnoth_sdl_sources = GetSources("libwesnoth_sdl") libwesnoth_sdl_sources = GetSources("libwesnoth_sdl")
if env["png"]:
libwesnoth_sdl_sources.append("SDL_SavePNG/savepng.c")
libwesnoth_sdl = client_env.Library("wesnoth-sdl", libwesnoth_sdl_sources) libwesnoth_sdl = client_env.Library("wesnoth-sdl", libwesnoth_sdl_sources)
#---libwesnoth_widgets--- #---libwesnoth_widgets---

View file

@ -1,101 +0,0 @@
# SDL_SavePNG
Minimal libpng interface to save SDL_Surfaces as PNG files.
You might want to take a look in "savepng.h" - it is much shorter and simpler
than this README.
## Install
Add "savepng.c" and "savepng.h" to your project.
Link the libpng library, i.e. add the `-lpng` LDFLAG (even if you already have
`-lSDL_image`).
## Use
```
#include "savepng.h"
SDL_Surface *bmp = ... //your surface
if (SDL_SavePNG(bmp, "image.png")) { //boring way with error checking
printf("Unable to save png -- %s\n", SDL_GetError());
}
```
As you can see, `SDL_SavePNG` accepts an SDL_Surface and a filename for it's
input. Similar to SDL_SaveBMP, it is a wrapper around the actual RWops-based
`SDL_SavePNG_RW` function, so you could use that, if needed.
Lastly, there is `SDL_PNGFormatAlpha`, modeled after SDL_DisplayFormatAlpha,
that would convert *any SDL_Surface* to an *SDL_Surface suitable for PNG
output*. Each call to `SDL_PNGFormatAlpha` produces a **new** SDL_Surface that
**must** be freed using `SDL_FreeSurface`.
```
//safest way, useful for 'screen' surface
SDL_Surface *tmp = SDL_PNGFormatAlpha(screen);
SDL_SavePNG(tmp, "screenshot.png");
SDL_FreeSurface(tmp)
```
Such conversion is actually only required for *one* surface format (see below),
and would do **nothing** for all other formats, making it **very fast**. The
format in question is:
### 32-bpp surfaces without alpha
There is a interesting caveat of combining naive libpng and cunning SDL in a
32-bpp video mode.
The *screen* surface (obtained by `SDL_SetVideoMode` or similarly) might (and
will!) ignore it's alpha-component even in the 32bpp mode. Meaning that an
0xAARRGGBB color would be blitted as 0xFFrrggbb irregardless, as if it was a
24bpp color.
Since screen itself is never blitted onto anything else, ignoring the alpha
makes perfect sense. However, unlike 24bpp images, the alpha component *does*
exist. Thus, when such surface is saved, it appears to be completely
transparent, as the alpha values for each pixel are set to 0.
Depending on your video mode, you might or might not need to first convert your
surface using `SDL_PNGFormatAlpha`. If you have absolute control over the video
surface, you can force it to 24bpp (or less) mode, which would avoid the
problem.
If the surface passed to `SDL_PNGFormatAlpha` is already suitable, a no-op is
performed. It is very fast, so you should probably always convert your surfaces
before saving.
### No text chunks
Unfortunately, a simplistic interface such as SDL_SavePNG provides no means to
write PNG meta-data. If you need to add iTXT chunks to your PNGs, you would
have to modify this code or write your own version.
If you have some kind of simple API, that would be thematically consistent with
SDL, in mind -- please share.
## Demo
See `main.c` and `Makefile` for an example program. It too is shorter than this
README.
# About
The problem in question is very simple, and this little piece of functionality
was implemented and re-implemented multiple times by multiple authors (notably,
Angelo "Encelo" Theodorou and Darren Grant, among others). I decided to write
my own version to ensure it's correctness, learn more about libpng, and to
provide a copy-pastable, maintained, libpng15-aware, palette-supporting
variation that I could link to. You can view it as a continuation of their
efforts.
SDL_Image would've been perfect place for this, but that library has different
purposes.
*Next up: code to load SDL_Surfaces as OpenGL 1.1 textures. J/K ;)*
# Copying
SDL_SavePNG is available under the zlib/libpng license.

View file

@ -1,159 +0,0 @@
/*
* SDL_SavePNG -- libpng-based SDL_Surface writer.
*
* This code is free software, available under zlib/libpng license.
* http://www.libpng.org/pub/png/src/libpng-LICENSE.txt
*/
#include <SDL.h>
#include <png.h>
#include "savepng.h"
#define SAVEPNG_SUCCESS 0
#define SAVEPNG_ERROR -1
#define USE_ROW_POINTERS
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
#define rmask 0xFF000000
#define gmask 0x00FF0000
#define bmask 0x0000FF00
#define amask 0x000000FF
#else
#define rmask 0x000000FF
#define gmask 0x0000FF00
#define bmask 0x00FF0000
#define amask 0xFF000000
#endif
#include <stdlib.h>
/* libpng callbacks */
static void png_error_SDL(png_structp ctx, png_const_charp str)
{
(void) ctx; // Unused
SDL_SetError("libpng: %s\n", str);
}
static void png_write_SDL(png_structp png_ptr, png_bytep data, png_size_t length)
{
SDL_RWops *rw = (SDL_RWops*)(png_get_io_ptr(png_ptr));
SDL_RWwrite(rw, data, sizeof(png_byte), length);
}
SDL_Surface *SDL_PNGFormatAlpha(SDL_Surface *src)
{
SDL_Surface *surf;
SDL_Rect rect = { 0 , 0 , 0 , 0 };
/* NO-OP for images < 32bpp and 32bpp images that already have Alpha channel */
if (src->format->BitsPerPixel <= 24 || src->format->Amask) {
src->refcount++;
return src;
}
/* Convert 32bpp alpha-less image to 24bpp alpha-less image */
rect.w = src->w;
rect.h = src->h;
surf = SDL_CreateRGBSurface(src->flags, src->w, src->h, 24,
src->format->Rmask, src->format->Gmask, src->format->Bmask, 0);
SDL_LowerBlit(src, &rect, surf, &rect);
return surf;
}
int SDL_SavePNG_RW(SDL_Surface *surface, SDL_RWops *dst, int freedst)
{
png_structp png_ptr;
png_infop info_ptr;
png_colorp pal_ptr;
SDL_Palette *pal;
int i, colortype;
#ifdef USE_ROW_POINTERS
png_bytep *row_pointers;
#endif
/* Initialize and do basic error checking */
if (!dst)
{
SDL_SetError("Argument 2 to SDL_SavePNG_RW can't be NULL, expecting SDL_RWops*\n");
return (SAVEPNG_ERROR);
}
if (!surface)
{
SDL_SetError("Argument 1 to SDL_SavePNG_RW can't be NULL, expecting SDL_Surface*\n");
if (freedst) SDL_RWclose(dst);
return (SAVEPNG_ERROR);
}
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, png_error_SDL, NULL); /* err_ptr, err_fn, warn_fn */
if (!png_ptr)
{
SDL_SetError("Unable to png_create_write_struct on %s\n", PNG_LIBPNG_VER_STRING);
if (freedst) SDL_RWclose(dst);
return (SAVEPNG_ERROR);
}
info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr)
{
SDL_SetError("Unable to png_create_info_struct\n");
png_destroy_write_struct(&png_ptr, NULL);
if (freedst) SDL_RWclose(dst);
return (SAVEPNG_ERROR);
}
if (setjmp(png_jmpbuf(png_ptr))) /* All other errors, see also "png_error_SDL" */
{
png_destroy_write_struct(&png_ptr, &info_ptr);
if (freedst) SDL_RWclose(dst);
return (SAVEPNG_ERROR);
}
/* Setup our RWops writer */
png_set_write_fn(png_ptr, dst, png_write_SDL, NULL); /* w_ptr, write_fn, flush_fn */
/* Prepare chunks */
colortype = PNG_COLOR_MASK_COLOR;
if (surface->format->BytesPerPixel > 0
&& surface->format->BytesPerPixel <= 8
&& ((pal = surface->format->palette) != NULL))
{
colortype |= PNG_COLOR_MASK_PALETTE;
pal_ptr = (png_colorp)(malloc(pal->ncolors * sizeof(png_color)));
for (i = 0; i < pal->ncolors; i++) {
pal_ptr[i].red = pal->colors[i].r;
pal_ptr[i].green = pal->colors[i].g;
pal_ptr[i].blue = pal->colors[i].b;
}
png_set_PLTE(png_ptr, info_ptr, pal_ptr, pal->ncolors);
free(pal_ptr);
}
else if (surface->format->BytesPerPixel > 3 || surface->format->Amask)
colortype |= PNG_COLOR_MASK_ALPHA;
png_set_IHDR(png_ptr, info_ptr, surface->w, surface->h, 8, colortype,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
// png_set_packing(png_ptr);
/* Allow BGR surfaces */
if (surface->format->Rmask == bmask
&& surface->format->Gmask == gmask
&& surface->format->Bmask == rmask)
png_set_bgr(png_ptr);
/* Write everything */
png_write_info(png_ptr, info_ptr);
#ifdef USE_ROW_POINTERS
row_pointers = (png_bytep*) (malloc(sizeof(png_bytep)*surface->h));
for (i = 0; i < surface->h; i++)
row_pointers[i] = ((png_bytep)surface->pixels) + i * surface->pitch;
png_write_image(png_ptr, row_pointers);
free(row_pointers);
#else
for (i = 0; i < surface->h; i++)
png_write_row(png_ptr, (png_bytep)(Uint8*)surface->pixels + i * surface->pitch);
#endif
png_write_end(png_ptr, info_ptr);
/* Done */
png_destroy_write_struct(&png_ptr, &info_ptr);
if (freedst) SDL_RWclose(dst);
return (SAVEPNG_SUCCESS);
}

View file

@ -1,36 +0,0 @@
#ifndef _SDL_SAVEPNG
#define _SDL_SAVEPNG
/*
* SDL_SavePNG -- libpng-based SDL_Surface writer.
*
* This code is free software, available under zlib/libpng license.
* http://www.libpng.org/pub/png/src/libpng-LICENSE.txt
*/
#include <SDL_video.h>
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/*
* Save an SDL_Surface as a PNG file, using writable RWops.
*
* surface - the SDL_Surface structure containing the image to be saved
* dst - a data stream to save to
* freedst - non-zero to close the stream after being written
*
* Returns 0 success or -1 on failure, the error message is then retrievable
* via SDL_GetError().
*/
extern int SDL_SavePNG_RW(SDL_Surface *surface, SDL_RWops *rw, int freedst);
/*
* Return new SDL_Surface with a format suitable for PNG output.
*/
extern SDL_Surface *SDL_PNGFormatAlpha(SDL_Surface *src);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -36,10 +36,6 @@
#include <pango/pangocairo.h> #include <pango/pangocairo.h>
#ifdef HAVE_LIBPNG
#include <png.h>
#endif
#ifdef __APPLE__ #ifdef __APPLE__
// apple_notification.mm uses Foundation.h, which is an Objective-C header; // apple_notification.mm uses Foundation.h, which is an Objective-C header;
// but CoreFoundation.h is a C header which also defines these. // but CoreFoundation.h is a C header which also defines these.
@ -260,16 +256,6 @@ version_table_manager::version_table_manager()
linked[LIB_PANGO] = pango_version_string(); linked[LIB_PANGO] = pango_version_string();
names[LIB_PANGO] = "Pango"; names[LIB_PANGO] = "Pango";
//
// libpng
//
#ifdef HAVE_LIBPNG
compiled[LIB_PNG] = PNG_LIBPNG_VER_STRING;
linked[LIB_PNG] = png_get_libpng_ver(nullptr);
names[LIB_PNG] = "libpng";
#endif
// //
// Features table. // Features table.
// //
@ -279,11 +265,6 @@ version_table_manager::version_table_manager()
features.back().enabled = true; features.back().enabled = true;
#endif #endif
features.emplace_back(N_("feature^PNG screenshots"));
#ifdef HAVE_LIBPNG
features.back().enabled = true;
#endif
features.emplace_back(N_("feature^Lua console completion")); features.emplace_back(N_("feature^Lua console completion"));
#ifdef HAVE_HISTORY #ifdef HAVE_HISTORY
features.back().enabled = true; features.back().enabled = true;

View file

@ -57,11 +57,8 @@ template<typename TFunc>
void make_screenshot(const std::string& name, const TFunc& func) void make_screenshot(const std::string& name, const TFunc& func)
{ {
std::string filename = filesystem::get_screenshot_dir() + "/" + name + "_"; std::string filename = filesystem::get_screenshot_dir() + "/" + name + "_";
#ifdef HAVE_LIBPNG
const std::string ext = ".png"; const std::string ext = ".png";
#else
const std::string ext = ".bmp";
#endif
filename = filesystem::get_next_filename(filename, ext); filename = filesystem::get_next_filename(filename, ext);
const bool res = func(filename); const bool res = func(filename);
if (res) { if (res) {

View file

@ -33,10 +33,6 @@
#include "sdl/rect.hpp" #include "sdl/rect.hpp"
#include "utils/general.hpp" #include "utils/general.hpp"
#ifdef HAVE_LIBPNG
#include "SDL_SavePNG/savepng.h"
#endif
#include <SDL_image.h> #include <SDL_image.h>
#include "utils/functional.hpp" #include "utils/functional.hpp"
@ -1335,15 +1331,12 @@ bool save_image(const surface& surf, const std::string& filename)
return false; return false;
} }
#ifdef HAVE_LIBPNG
if(!filesystem::ends_with(filename, ".bmp")) { if(!filesystem::ends_with(filename, ".bmp")) {
LOG_DP << "Writing a png image to " << filename << std::endl; LOG_DP << "Writing a png image to " << filename << std::endl;
surface tmp = SDL_PNGFormatAlpha(surf.get()); const int err = IMG_SavePNG_RW(surf, filesystem::make_write_RWops(filename).release(), true); // SDL takes ownership of the RWops
const int err = SDL_SavePNG_RW(tmp, filesystem::make_write_RWops(filename).release(), 1); //1 means SDL takes ownership of the RWops
return err == 0; return err == 0;
} }
#endif
LOG_DP << "Writing a bmp image to " << filename << std::endl; LOG_DP << "Writing a bmp image to " << filename << std::endl;
return SDL_SaveBMP(surf, filename.c_str()) == 0; return SDL_SaveBMP(surf, filename.c_str()) == 0;