Png driver reworked to use libpng 1.6 simplified API

Wrapped libpng usage in a pair of functions under PNGDriverCommon,
which convert between Godot Image and png data.
Switched to libpng 1.6 simplified API for ease of maintenance.
Implemented ImageLoaderPNG and ResourceSaverPNG in terms of
PNGDriverCommon functions.
Travis, switched to builtin libpng (thus builtin freetype and zlib also)
so we can build on Xenial.
This commit is contained in:
Ibrahn Sahir 2019-06-16 20:08:52 +01:00
parent 22afebcad7
commit 5e24611241
9 changed files with 312 additions and 454 deletions

View file

@ -8,7 +8,7 @@ env:
global:
- SCONS_CACHE=$HOME/.scons_cache
- SCONS_CACHE_LIMIT=1024
- OPTIONS="debug_symbols=no verbose=yes progress=no"
- OPTIONS="debug_symbols=no verbose=yes progress=no builtin_libpng=yes"
- secure: "uch9QszCgsl1qVbuzY41P7S2hWL2IiNFV4SbAYRCdi0oJ9MIu+pVyrQdpf3+jG4rH6j4Rffl+sN17Zz4dIDDioFL1JwqyCqyCyswR8uACC0Rr8gr4Mi3+HIRbv+2s2P4cIQq41JM8FJe84k9jLEMGCGh69w+ibCWoWs74CokYVA="
cache:
@ -39,7 +39,7 @@ matrix:
- ubuntu-toolchain-r-test
packages:
- &gcc8_deps [gcc-8, g++-8]
- &linux_deps [libasound2-dev, libfreetype6-dev, libgl1-mesa-dev, libglu1-mesa-dev, libx11-dev, libxcursor-dev, libxi-dev, libxinerama-dev, libxrandr-dev]
- &linux_deps [libasound2-dev, libgl1-mesa-dev, libglu1-mesa-dev, libx11-dev, libxcursor-dev, libxi-dev, libxinerama-dev, libxrandr-dev]
- &linux_mono_deps [mono-devel, msbuild, nuget]
coverity_scan:

View file

@ -32,186 +32,26 @@
#include "core/os/os.h"
#include "core/print_string.h"
#include "drivers/png/png_driver_common.h"
#include <string.h>
void ImageLoaderPNG::_read_png_data(png_structp png_ptr, png_bytep data, png_size_t p_length) {
FileAccess *f = (FileAccess *)png_get_io_ptr(png_ptr);
f->get_buffer((uint8_t *)data, p_length);
}
/*
png_structp png_ptr = png_create_read_struct_2
(PNG_LIBPNG_VER_STRING, (png_voidp)user_error_ptr,
user_error_fn, user_warning_fn, (png_voidp)
user_mem_ptr, user_malloc_fn, user_free_fn);
*/
static png_voidp _png_malloc_fn(png_structp png_ptr, png_size_t size) {
return memalloc(size);
}
static void _png_free_fn(png_structp png_ptr, png_voidp ptr) {
memfree(ptr);
}
static void _png_error_function(png_structp, png_const_charp text) {
ERR_PRINT(text);
}
static void _png_warn_function(png_structp, png_const_charp text) {
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
if (String(text).begins_with("iCCP")) return; // silences annoying spam emitted to output every time the user opened assetlib
}
#endif
WARN_PRINT(text);
}
typedef void(PNGAPI *png_error_ptr) PNGARG((png_structp, png_const_charp));
Error ImageLoaderPNG::_load_image(void *rf_up, png_rw_ptr p_func, Ref<Image> p_image) {
png_structp png;
png_infop info;
//png = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)NULL, NULL, NULL);
png = png_create_read_struct_2(PNG_LIBPNG_VER_STRING, (png_voidp)NULL, _png_error_function, _png_warn_function, (png_voidp)NULL,
_png_malloc_fn, _png_free_fn);
ERR_FAIL_COND_V(!png, ERR_OUT_OF_MEMORY);
info = png_create_info_struct(png);
if (!info) {
png_destroy_read_struct(&png, NULL, NULL);
ERR_PRINT("Out of Memory");
return ERR_OUT_OF_MEMORY;
}
if (setjmp(png_jmpbuf(png))) {
png_destroy_read_struct(&png, NULL, NULL);
ERR_PRINT("PNG Corrupted");
return ERR_FILE_CORRUPT;
}
png_set_read_fn(png, (void *)rf_up, p_func);
png_uint_32 width, height;
int depth, color;
png_read_info(png, info);
png_get_IHDR(png, info, &width, &height, &depth, &color, NULL, NULL, NULL);
//https://svn.gov.pt/projects/ccidadao/repository/middleware-offline/trunk/_src/eidmw/FreeImagePTEiD/Source/FreeImage/PluginPNG.cpp
//png_get_text(png,info,)
/*
printf("Image width:%i\n", width);
printf("Image Height:%i\n", height);
printf("Bit depth:%i\n", depth);
printf("Color type:%i\n", color);
*/
bool update_info = false;
if (depth < 8) { //only bit dept 8 per channel is handled
png_set_packing(png);
update_info = true;
};
if (png_get_color_type(png, info) == PNG_COLOR_TYPE_PALETTE) {
png_set_palette_to_rgb(png);
update_info = true;
}
if (depth > 8) {
png_set_strip_16(png);
update_info = true;
}
if (png_get_valid(png, info, PNG_INFO_tRNS)) {
//png_set_expand_gray_1_2_4_to_8(png);
png_set_tRNS_to_alpha(png);
update_info = true;
}
if (update_info) {
png_read_update_info(png, info);
png_get_IHDR(png, info, &width, &height, &depth, &color, NULL, NULL, NULL);
}
int components = 0;
Image::Format fmt;
switch (color) {
case PNG_COLOR_TYPE_GRAY: {
fmt = Image::FORMAT_L8;
components = 1;
} break;
case PNG_COLOR_TYPE_GRAY_ALPHA: {
fmt = Image::FORMAT_LA8;
components = 2;
} break;
case PNG_COLOR_TYPE_RGB: {
fmt = Image::FORMAT_RGB8;
components = 3;
} break;
case PNG_COLOR_TYPE_RGB_ALPHA: {
fmt = Image::FORMAT_RGBA8;
components = 4;
} break;
default: {
ERR_PRINT("INVALID PNG TYPE");
png_destroy_read_struct(&png, &info, NULL);
return ERR_UNAVAILABLE;
} break;
}
//int rowsize = png_get_rowbytes(png, info);
int rowsize = components * width;
PoolVector<uint8_t> dstbuff;
dstbuff.resize(rowsize * height);
PoolVector<uint8_t>::Write dstbuff_write = dstbuff.write();
uint8_t *data = dstbuff_write.ptr();
uint8_t **row_p = memnew_arr(uint8_t *, height);
for (unsigned int i = 0; i < height; i++) {
row_p[i] = &data[components * width * i];
}
png_read_image(png, (png_bytep *)row_p);
memdelete_arr(row_p);
p_image->create(width, height, 0, fmt, dstbuff);
png_destroy_read_struct(&png, &info, NULL);
return OK;
}
Error ImageLoaderPNG::load_image(Ref<Image> p_image, FileAccess *f, bool p_force_linear, float p_scale) {
Error err = _load_image(f, _read_png_data, p_image);
f->close();
return err;
const size_t buffer_size = f->get_len();
PoolVector<uint8_t> file_buffer;
Error err = file_buffer.resize(buffer_size);
if (err) {
f->close();
return err;
}
{
PoolVector<uint8_t>::Write writer = file_buffer.write();
f->get_buffer(writer.ptr(), buffer_size);
f->close();
}
PoolVector<uint8_t>::Read reader = file_buffer.read();
return PNGDriverCommon::png_to_image(reader.ptr(), buffer_size, p_image);
}
void ImageLoaderPNG::get_recognized_extensions(List<String> *p_extensions) const {
@ -219,178 +59,53 @@ void ImageLoaderPNG::get_recognized_extensions(List<String> *p_extensions) const
p_extensions->push_back("png");
}
struct PNGReadStatus {
uint32_t offset;
uint32_t size;
const unsigned char *image;
};
static void user_read_data(png_structp png_ptr, png_bytep data, png_size_t p_length) {
PNGReadStatus *rstatus;
rstatus = (PNGReadStatus *)png_get_io_ptr(png_ptr);
png_size_t to_read = MIN(p_length, rstatus->size - rstatus->offset);
memcpy(data, &rstatus->image[rstatus->offset], to_read);
rstatus->offset += to_read;
if (to_read < p_length) {
memset(&data[to_read], 0, p_length - to_read);
}
}
static Ref<Image> _load_mem_png(const uint8_t *p_png, int p_size) {
PNGReadStatus prs;
prs.image = p_png;
prs.offset = 0;
prs.size = p_size;
Ref<Image> ImageLoaderPNG::load_mem_png(const uint8_t *p_png, int p_size) {
Ref<Image> img;
img.instance();
Error err = ImageLoaderPNG::_load_image(&prs, user_read_data, img);
Error err = PNGDriverCommon::png_to_image(p_png, p_size, img);
ERR_FAIL_COND_V(err, Ref<Image>());
return img;
}
static Ref<Image> _lossless_unpack_png(const PoolVector<uint8_t> &p_data) {
Ref<Image> ImageLoaderPNG::lossless_unpack_png(const PoolVector<uint8_t> &p_data) {
int len = p_data.size();
const int len = p_data.size();
ERR_FAIL_COND_V(len < 4, Ref<Image>());
PoolVector<uint8_t>::Read r = p_data.read();
ERR_FAIL_COND_V(r[0] != 'P' || r[1] != 'N' || r[2] != 'G' || r[3] != ' ', Ref<Image>());
return _load_mem_png(&r[4], len - 4);
return load_mem_png(&r[4], len - 4);
}
static void _write_png_data(png_structp png_ptr, png_bytep data, png_size_t p_length) {
PoolVector<uint8_t> ImageLoaderPNG::lossless_pack_png(const Ref<Image> &p_image) {
PoolVector<uint8_t> &v = *(PoolVector<uint8_t> *)png_get_io_ptr(png_ptr);
int vs = v.size();
PoolVector<uint8_t> out_buffer;
v.resize(vs + p_length);
PoolVector<uint8_t>::Write w = v.write();
copymem(&w[vs], data, p_length);
}
static PoolVector<uint8_t> _lossless_pack_png(const Ref<Image> &p_image) {
Ref<Image> img = p_image->duplicate();
if (img->is_compressed())
img->decompress();
ERR_FAIL_COND_V(img->is_compressed(), PoolVector<uint8_t>());
png_structp png_ptr;
png_infop info_ptr;
png_bytep *row_pointers;
/* initialize stuff */
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
ERR_FAIL_COND_V(!png_ptr, PoolVector<uint8_t>());
info_ptr = png_create_info_struct(png_ptr);
ERR_FAIL_COND_V(!info_ptr, PoolVector<uint8_t>());
if (setjmp(png_jmpbuf(png_ptr))) {
ERR_FAIL_V(PoolVector<uint8_t>());
}
PoolVector<uint8_t> ret;
ret.push_back('P');
ret.push_back('N');
ret.push_back('G');
ret.push_back(' ');
png_set_write_fn(png_ptr, &ret, _write_png_data, NULL);
/* write header */
if (setjmp(png_jmpbuf(png_ptr))) {
// add Godot's own "PNG " prefix
if (out_buffer.resize(4) != OK) {
ERR_FAIL_V(PoolVector<uint8_t>());
}
int pngf = 0;
int cs = 0;
switch (img->get_format()) {
case Image::FORMAT_L8: {
pngf = PNG_COLOR_TYPE_GRAY;
cs = 1;
} break;
case Image::FORMAT_LA8: {
pngf = PNG_COLOR_TYPE_GRAY_ALPHA;
cs = 2;
} break;
case Image::FORMAT_RGB8: {
pngf = PNG_COLOR_TYPE_RGB;
cs = 3;
} break;
case Image::FORMAT_RGBA8: {
pngf = PNG_COLOR_TYPE_RGB_ALPHA;
cs = 4;
} break;
default: {
if (img->detect_alpha()) {
img->convert(Image::FORMAT_RGBA8);
pngf = PNG_COLOR_TYPE_RGB_ALPHA;
cs = 4;
} else {
img->convert(Image::FORMAT_RGB8);
pngf = PNG_COLOR_TYPE_RGB;
cs = 3;
}
}
// scope for writer lifetime
{
// must be closed before call to image_to_png
PoolVector<uint8_t>::Write writer = out_buffer.write();
copymem(writer.ptr(), "PNG ", 4);
}
int w = img->get_width();
int h = img->get_height();
png_set_IHDR(png_ptr, info_ptr, w, h,
8, pngf, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
png_write_info(png_ptr, info_ptr);
/* write bytes */
if (setjmp(png_jmpbuf(png_ptr))) {
Error err = PNGDriverCommon::image_to_png(p_image, out_buffer);
if (err) {
ERR_FAIL_V(PoolVector<uint8_t>());
}
PoolVector<uint8_t>::Read r = img->get_data().read();
row_pointers = (png_bytep *)memalloc(sizeof(png_bytep) * h);
for (int i = 0; i < h; i++) {
row_pointers[i] = (png_bytep)&r[i * w * cs];
}
png_write_image(png_ptr, row_pointers);
memfree(row_pointers);
/* end write */
if (setjmp(png_jmpbuf(png_ptr))) {
ERR_FAIL_V(PoolVector<uint8_t>());
}
png_write_end(png_ptr, NULL);
return ret;
return out_buffer;
}
ImageLoaderPNG::ImageLoaderPNG() {
Image::_png_mem_loader_func = _load_mem_png;
Image::lossless_unpacker = _lossless_unpack_png;
Image::lossless_packer = _lossless_pack_png;
Image::_png_mem_loader_func = load_mem_png;
Image::lossless_unpacker = lossless_unpack_png;
Image::lossless_packer = lossless_pack_png;
}

View file

@ -33,17 +33,16 @@
#include "core/io/image_loader.h"
#include <png.h>
/**
@author Juan Linietsky <reduzio@gmail.com>
*/
class ImageLoaderPNG : public ImageFormatLoader {
static void _read_png_data(png_structp png_ptr, png_bytep data, png_size_t p_length);
private:
static PoolVector<uint8_t> lossless_pack_png(const Ref<Image> &p_image);
static Ref<Image> lossless_unpack_png(const PoolVector<uint8_t> &p_data);
static Ref<Image> load_mem_png(const uint8_t *p_png, int p_size);
public:
static Error _load_image(void *rf_up, png_rw_ptr p_func, Ref<Image> p_image);
virtual Error load_image(Ref<Image> p_image, FileAccess *f, bool p_force_linear, float p_scale);
virtual void get_recognized_extensions(List<String> *p_extensions) const;
ImageLoaderPNG();

View file

@ -0,0 +1,205 @@
/*************************************************************************/
/* png_driver_common.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "png_driver_common.h"
#include "core/os/os.h"
#include <png.h>
#include <string.h>
namespace PNGDriverCommon {
// Print any warnings.
// On error, set explain and return true.
// Call should be wrapped in ERR_FAIL_COND
static bool check_error(const png_image &image) {
const png_uint_32 failed = PNG_IMAGE_FAILED(image);
if (failed & PNG_IMAGE_ERROR) {
ERR_EXPLAINC(image.message);
return true;
} else if (failed) {
#ifdef TOOLS_ENABLED
// suppress this warning, to avoid log spam when opening assetlib
const static char *const noisy = "iCCP: known incorrect sRGB profile";
const Engine *const eng = Engine::get_singleton();
if (eng && eng->is_editor_hint() && !strcmp(image.message, noisy)) {
return false;
}
#endif
WARN_PRINT(image.message);
}
return false;
}
Error png_to_image(const uint8_t *p_source, size_t p_size, Ref<Image> p_image) {
png_image png_img;
zeromem(&png_img, sizeof(png_img));
png_img.version = PNG_IMAGE_VERSION;
// fetch image properties
int success = png_image_begin_read_from_memory(&png_img, p_source, p_size);
ERR_FAIL_COND_V(check_error(png_img), ERR_FILE_CORRUPT);
ERR_FAIL_COND_V(!success, ERR_FILE_CORRUPT);
// flags to be masked out of input format to give target format
const png_uint_32 format_mask = ~(
// convert component order to RGBA
PNG_FORMAT_FLAG_BGR | PNG_FORMAT_FLAG_AFIRST
// convert 16 bit components to 8 bit
| PNG_FORMAT_FLAG_LINEAR
// convert indexed image to direct color
| PNG_FORMAT_FLAG_COLORMAP);
png_img.format &= format_mask;
Image::Format dest_format;
switch (png_img.format) {
case PNG_FORMAT_GRAY:
dest_format = Image::FORMAT_L8;
break;
case PNG_FORMAT_GA:
dest_format = Image::FORMAT_LA8;
break;
case PNG_FORMAT_RGB:
dest_format = Image::FORMAT_RGB8;
break;
case PNG_FORMAT_RGBA:
dest_format = Image::FORMAT_RGBA8;
break;
default:
png_image_free(&png_img); // only required when we return before finish_read
ERR_PRINT("Unsupported png format");
return ERR_UNAVAILABLE;
}
const png_uint_32 stride = PNG_IMAGE_ROW_STRIDE(png_img);
PoolVector<uint8_t> buffer;
Error err = buffer.resize(PNG_IMAGE_BUFFER_SIZE(png_img, stride));
if (err) {
png_image_free(&png_img); // only required when we return before finish_read
return err;
}
PoolVector<uint8_t>::Write writer = buffer.write();
// read image data to buffer and release libpng resources
success = png_image_finish_read(&png_img, NULL, writer.ptr(), stride, NULL);
ERR_FAIL_COND_V(check_error(png_img), ERR_FILE_CORRUPT);
ERR_FAIL_COND_V(!success, ERR_FILE_CORRUPT);
p_image->create(png_img.width, png_img.height, 0, dest_format, buffer);
return OK;
}
Error image_to_png(const Ref<Image> &p_image, PoolVector<uint8_t> &p_buffer) {
Ref<Image> source_image = p_image->duplicate();
if (source_image->is_compressed())
source_image->decompress();
ERR_FAIL_COND_V(source_image->is_compressed(), FAILED);
png_image png_img;
zeromem(&png_img, sizeof(png_img));
png_img.version = PNG_IMAGE_VERSION;
png_img.width = source_image->get_width();
png_img.height = source_image->get_height();
switch (source_image->get_format()) {
case Image::FORMAT_L8:
png_img.format = PNG_FORMAT_GRAY;
break;
case Image::FORMAT_LA8:
png_img.format = PNG_FORMAT_GA;
break;
case Image::FORMAT_RGB8:
png_img.format = PNG_FORMAT_RGB;
break;
case Image::FORMAT_RGBA8:
png_img.format = PNG_FORMAT_RGBA;
break;
default:
if (source_image->detect_alpha()) {
source_image->convert(Image::FORMAT_RGBA8);
png_img.format = PNG_FORMAT_RGBA;
} else {
source_image->convert(Image::FORMAT_RGB8);
png_img.format = PNG_FORMAT_RGB;
}
}
const PoolVector<uint8_t> image_data = source_image->get_data();
const PoolVector<uint8_t>::Read reader = image_data.read();
// we may be passed a buffer with existing content we're expected to append to
const int buffer_offset = p_buffer.size();
const size_t png_size_estimate = PNG_IMAGE_PNG_SIZE_MAX(png_img);
// try with estimated size
size_t compressed_size = png_size_estimate;
int success = 0;
{ // scope writer lifetime
Error err = p_buffer.resize(buffer_offset + png_size_estimate);
ERR_FAIL_COND_V(err, err);
PoolVector<uint8_t>::Write writer = p_buffer.write();
success = png_image_write_to_memory(&png_img, &writer[buffer_offset],
&compressed_size, 0, reader.ptr(), 0, NULL);
ERR_FAIL_COND_V(check_error(png_img), FAILED);
}
if (!success) {
if (compressed_size <= png_size_estimate) {
// buffer was big enough, must be some other error
ERR_FAIL_V(FAILED);
}
// write failed due to buffer size, resize and retry
Error err = p_buffer.resize(buffer_offset + compressed_size);
ERR_FAIL_COND_V(err, err);
PoolVector<uint8_t>::Write writer = p_buffer.write();
success = png_image_write_to_memory(&png_img, &writer[buffer_offset],
&compressed_size, 0, reader.ptr(), 0, NULL);
ERR_FAIL_COND_V(check_error(png_img), FAILED);
ERR_FAIL_COND_V(!success, FAILED);
}
// trim buffer size to content
Error err = p_buffer.resize(buffer_offset + compressed_size);
ERR_FAIL_COND_V(err, err);
return OK;
}
} // namespace PNGDriverCommon

View file

@ -0,0 +1,48 @@
/*************************************************************************/
/* png_driver_common.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef PNG_DRIVER_COMMON_H
#define PNG_DRIVER_COMMON_H
#include "core/image.h"
#include "core/pool_vector.h"
namespace PNGDriverCommon {
// Attempt to load png from buffer (p_source, p_size) into p_image
Error png_to_image(const uint8_t *p_source, size_t p_size, Ref<Image> p_image);
// Append p_image, as a png, to p_buffer.
// Contents of p_buffer is unspecified if error returned.
Error image_to_png(const Ref<Image> &p_image, PoolVector<uint8_t> &p_buffer);
} // namespace PNGDriverCommon
#endif

View file

@ -32,17 +32,9 @@
#include "core/image.h"
#include "core/os/file_access.h"
#include "core/project_settings.h"
#include "drivers/png/png_driver_common.h"
#include "scene/resources/texture.h"
#include <png.h>
static void _write_png_data(png_structp png_ptr, png_bytep data, png_size_t p_length) {
FileAccess *f = (FileAccess *)png_get_io_ptr(png_ptr);
f->store_buffer((const uint8_t *)data, p_length);
}
Error ResourceSaverPNG::save(const String &p_path, const RES &p_resource, uint32_t p_flags) {
Ref<ImageTexture> texture = p_resource;
@ -55,129 +47,27 @@ Error ResourceSaverPNG::save(const String &p_path, const RES &p_resource, uint32
Error err = save_image(p_path, img);
if (err == OK) {
}
return err;
};
Error ResourceSaverPNG::save_image(const String &p_path, const Ref<Image> &p_img) {
Ref<Image> img = p_img->duplicate();
PoolVector<uint8_t> buffer;
Error err = PNGDriverCommon::image_to_png(p_img, buffer);
ERR_FAIL_COND_V(err, err);
FileAccess *file = FileAccess::open(p_path, FileAccess::WRITE, &err);
ERR_FAIL_COND_V(err, err);
if (img->is_compressed())
img->decompress();
PoolVector<uint8_t>::Read reader = buffer.read();
ERR_FAIL_COND_V(img->is_compressed(), ERR_INVALID_PARAMETER);
png_structp png_ptr;
png_infop info_ptr;
png_bytep *row_pointers;
/* initialize stuff */
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
ERR_FAIL_COND_V(!png_ptr, ERR_CANT_CREATE);
info_ptr = png_create_info_struct(png_ptr);
ERR_FAIL_COND_V(!info_ptr, ERR_CANT_CREATE);
if (setjmp(png_jmpbuf(png_ptr))) {
ERR_FAIL_V(ERR_CANT_OPEN);
}
//change this
Error err;
FileAccess *f = FileAccess::open(p_path, FileAccess::WRITE, &err);
if (err) {
ERR_FAIL_V(err);
file->store_buffer(reader.ptr(), buffer.size());
if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) {
memdelete(file);
return ERR_CANT_CREATE;
}
png_set_write_fn(png_ptr, f, _write_png_data, NULL);
/* write header */
if (setjmp(png_jmpbuf(png_ptr))) {
ERR_FAIL_V(ERR_CANT_OPEN);
}
int pngf = 0;
int cs = 0;
switch (img->get_format()) {
case Image::FORMAT_L8: {
pngf = PNG_COLOR_TYPE_GRAY;
cs = 1;
} break;
case Image::FORMAT_LA8: {
pngf = PNG_COLOR_TYPE_GRAY_ALPHA;
cs = 2;
} break;
case Image::FORMAT_RGB8: {
pngf = PNG_COLOR_TYPE_RGB;
cs = 3;
} break;
case Image::FORMAT_RGBA8: {
pngf = PNG_COLOR_TYPE_RGB_ALPHA;
cs = 4;
} break;
default: {
if (img->detect_alpha()) {
img->convert(Image::FORMAT_RGBA8);
pngf = PNG_COLOR_TYPE_RGB_ALPHA;
cs = 4;
} else {
img->convert(Image::FORMAT_RGB8);
pngf = PNG_COLOR_TYPE_RGB;
cs = 3;
}
}
}
int w = img->get_width();
int h = img->get_height();
png_set_IHDR(png_ptr, info_ptr, w, h,
8, pngf, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
png_write_info(png_ptr, info_ptr);
/* write bytes */
if (setjmp(png_jmpbuf(png_ptr))) {
memdelete(f);
ERR_FAIL_V(ERR_CANT_OPEN);
}
PoolVector<uint8_t>::Read r = img->get_data().read();
row_pointers = (png_bytep *)memalloc(sizeof(png_bytep) * h);
for (int i = 0; i < h; i++) {
row_pointers[i] = (png_bytep)&r[i * w * cs];
}
png_write_image(png_ptr, row_pointers);
memfree(row_pointers);
/* end write */
if (setjmp(png_jmpbuf(png_ptr))) {
memdelete(f);
ERR_FAIL_V(ERR_CANT_OPEN);
}
png_write_end(png_ptr, NULL);
memdelete(f);
/* cleanup heap allocation */
png_destroy_write_struct(&png_ptr, &info_ptr);
file->close();
memdelete(file);
return OK;
}
@ -186,6 +76,7 @@ bool ResourceSaverPNG::recognize(const RES &p_resource) const {
return (p_resource.is_valid() && p_resource->is_class("ImageTexture"));
}
void ResourceSaverPNG::get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const {
if (Object::cast_to<Texture>(*p_resource)) {

View file

@ -80,7 +80,7 @@ def configure(env):
env.ParseConfig('pkg-config freetype2 --cflags --libs')
if not env['builtin_libpng']:
env.ParseConfig('pkg-config libpng --cflags --libs')
env.ParseConfig('pkg-config libpng16 --cflags --libs')
if not env['builtin_bullet']:
# We need at least version 2.88

View file

@ -142,7 +142,7 @@ def configure(env):
env.ParseConfig('pkg-config freetype2 --cflags --libs')
if not env['builtin_libpng']:
env.ParseConfig('pkg-config libpng --cflags --libs')
env.ParseConfig('pkg-config libpng16 --cflags --libs')
if not env['builtin_bullet']:
# We need at least version 2.89

View file

@ -216,7 +216,7 @@ def configure(env):
env.ParseConfig('pkg-config freetype2 --cflags --libs')
if not env['builtin_libpng']:
env.ParseConfig('pkg-config libpng --cflags --libs')
env.ParseConfig('pkg-config libpng16 --cflags --libs')
if not env['builtin_bullet']:
# We need at least version 2.89