add SDL_SavePNG, write files as png if possible, unless ending .bmp

This commit adds a "minimal interface to lib png to support writing
png files from SDL surfaces", to the codebase. It adds a save_image
function to namespace image, and this checks compiler flags to see
if lib png is available. The function will save as a png if png
support is included and save file name doesn't have bmp extension,
and otherwise it will save as a BMP as usual.

We already had lib png as a dependency to build the tools, just not
the main game. This commit changes cmake and scons but makes the
dependency only optional, changing nothing if lib png is not
available.

See here: https://github.com/driedfruit/SDL_SavePNG
This commit is contained in:
Chris Beck 2014-10-22 21:34:38 -04:00
parent b835543392
commit 8dfdc6b185
10 changed files with 353 additions and 3 deletions

View File

@ -85,6 +85,7 @@ option(ENABLE_LOW_MEM "Reduce memory usage by removing extra functionality" OFF)
option(ENABLE_OMP "Enables OpenMP, and has additional dependencies" OFF)
option(ENABLE_PANDORA "Add support for the OpenPandora by deactivating libvorbis support" OFF)
option(ENABLE_SDL_GPU "Enable building with SDL_gpu (experimental" OFF)
option(ENABLE_LIBPNG "Enable support for writing png files (screenshots, images)" ON)
if(NOT DEFINED ENABLE_DISPLAY_REVISION)
# Test whether the code is used in a repository if not autorevision will
@ -586,6 +587,12 @@ if(ENABLE_GAME)
endif(LIBDBUS_FOUND)
endif(ENABLE_NOTIFICATIONS)
find_package( PNG )
if(ENABLE_LIBPNG AND PNG_FOUND)
add_definitions(-DHAVE_LIBPNG)
else
message("Could not find lib PNG. Disabling support for writing PNG images.")
endif()
endif(ENABLE_GAME)
if(ENABLE_POT_UPDATE_TARGET)

View File

@ -74,6 +74,7 @@ opts.AddVariables(
BoolVariable('notifications', 'Enable support for desktop notifications', True),
BoolVariable('nls','enable compile/install of gettext message catalogs',True),
BoolVariable('boostfilesystem', 'Use boost filesystem', True),
BoolVariable('png', 'Clear to disable writing png files for screenshots, images', True),
PathVariable('prefix', 'autotools-style installation prefix', "/usr/local", PathVariable.PathAccept),
PathVariable('prefsdir', 'user preferences directory', "", PathVariable.PathAccept),
PathVariable('default_prefs_file', 'default preferences file name', "", PathVariable.PathAccept),
@ -398,6 +399,10 @@ if env["prereqs"]:
if client_env['fribidi']:
client_env['fribidi'] = conf.CheckPKG('fribidi >= 0.10.9') or Warning("Can't find libfribidi, disabling freebidi support.")
env["png"] = env["png"] and conf.CheckLib("png")
if env["png"]:
client_env.Append(CPPDEFINES = ["HAVE_LIBPNG"])
if env["forum_user_handler"]:
flags = env.ParseFlags("!mysql_config --libs --cflags")
try: # Some versions of mysql_config add -DNDEBUG but we don't want it

View File

@ -365,6 +365,14 @@ if(LIBDBUS_FOUND)
)
endif()
if(PNG_FOUND)
set_sources_files_properties(
SDL_SavePNG/savepng.cpp
PROPERTIES
COMPILE_FLAGS "${CXX_FLAG_NO_OLD_STYLE_CAST}"
)
endif()
########### Helper libraries ###############
set(wesnoth-sdl_SRC
@ -1026,6 +1034,14 @@ if(LIBDBUS_FOUND)
)
endif(LIBDBUS_FOUND)
# If requested, compile and link this file also to be able to save png files
if(ENABLE_LIBPNG AND PNG_FOUND)
set(libwesnoth-game_STAT_SRC
${libwesnoth-game_STAT_SRC}
SDL_SavePNG/savepng.cpp
)
endif(ENABLE_LIBPNG AND PNG_FOUND)
if(ENABLE_GAME AND ENABLE_TESTS)
set(libwesnoth-game_STAT_SRC
${libwesnoth-game_STAT_SRC}

View File

@ -571,6 +571,9 @@ if env["PLATFORM"] == "win32":
if env["notifications"]:
wesnoth_sources.append("desktop/dbus_notification.cpp")
if env["png"]:
wesnoth_sources.append("SDL_SavePNG/savepng.cpp")
wesnoth_sources.extend(client_env.Object("game_preferences_display.cpp", EXTRA_DEFINE = env["PLATFORM"] != "win32" and "WESNOTH_PREFIX='\"$prefix\"'" or None))
libwesnoth_extras = client_env.Library("wesnoth_extras", wesnoth_sources)

101
src/SDL_SavePNG/README.md Normal file
View File

@ -0,0 +1,101 @@
# 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, usefull 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.

157
src/SDL_SavePNG/savepng.cpp Normal file
View File

@ -0,0 +1,157 @@
/*
* 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 SUCCESS 0
#define 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
/* libpng callbacks */
static void png_error_SDL(png_structp /*ctx*/, png_const_charp str)
{
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");
if (freedst) SDL_RWclose(dst);
return (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 (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 (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 (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 (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))
{
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)(Uint8*)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 (SUCCESS);
}

36
src/SDL_SavePNG/savepng.h Normal file
View File

@ -0,0 +1,36 @@
#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>
/*
* Save an SDL_Surface as a PNG file.
*
* Returns 0 success or -1 on failure, the error message is then retrievable
* via SDL_GetError().
*/
#define SDL_SavePNG(surface, file) \
SDL_SavePNG_RW(surface, SDL_RWFromFile(file, "wb"), 1)
/*
* 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);
#endif

View File

@ -34,6 +34,7 @@
#include "play_controller.hpp" //note: this can probably be refactored out
#include "reports.hpp"
#include "resources.hpp"
#include "SDL_SavePNG/savepng.h"
#include "synced_context.hpp"
#include "team.hpp"
#include "terrain_builder.hpp"
@ -770,7 +771,9 @@ int display::screenshot(std::string filename, bool map_screenshot)
int size = 0;
if (!map_screenshot) {
surface screenshot_surf = screen_.getSurface();
SDL_SaveBMP(screenshot_surf, filename.c_str());
image::save_image(screenshot_surf, filename);
size = screenshot_surf->w * screenshot_surf->h;
} else {
if (get_map().empty()) {
@ -802,7 +805,7 @@ int display::screenshot(std::string filename, bool map_screenshot)
draw(true,true);
// finally save the image on disk
SDL_SaveBMP(map_screenshot_surf_, filename.c_str());
image::save_image(map_screenshot_surf_,filename);
//NOTE: need to be sure that we free this huge surface (is it enough?)
map_screenshot_surf_ = NULL;

View File

@ -30,6 +30,11 @@
#include "log.hpp"
#include "gettext.hpp"
#include "sdl/rect.hpp"
#ifdef HAVE_LIBPNG
#include "SDL_SavePNG/savepng.h"
#endif
#include "serialization/string_utils.hpp"
#include "video.hpp"
@ -1192,7 +1197,23 @@ bool precached_file_exists(const std::string& file)
void save_image(const locator & i_locator, const std::string & filename)
{
surface surf = get_image(i_locator);
save_image(get_image(i_locator), filename);
}
void save_image(const surface & surf, const std::string & filename)
{
#ifdef HAVE_LIBPNG
if (! ((filename.length() > 3) && (filename.substr(filename.length()-3, 3) == "bmp"))) {
LOG_DP << "Writing a png image to " << filename << std::endl;
//safest way,
SDL_Surface *tmp = SDL_PNGFormatAlpha(surf.get());
SDL_SavePNG(tmp, filename.c_str());
SDL_FreeSurface(tmp);
return ;
}
#endif
LOG_DP << "Writing a bmp image to " << filename << std::endl;
SDL_SaveBMP(surf, filename.c_str());
}

View File

@ -251,6 +251,7 @@ namespace image {
std::string describe_versions();
void save_image(const locator& i_locator, const std::string& outfile);
void save_image(const surface& surf, const std::string& outfile);
}
#endif