mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-09 18:16:28 +00:00
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:
parent
b835543392
commit
8dfdc6b185
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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
101
src/SDL_SavePNG/README.md
Normal 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
157
src/SDL_SavePNG/savepng.cpp
Normal 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
36
src/SDL_SavePNG/savepng.h
Normal 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
|
@ -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;
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user