فهرست منبع

Merge pull request #31047 from Zylann/save_exr

Add Image.save_exr()
Rémi Verschelde 6 سال پیش
والد
کامیت
1d5ae6da5b
6فایلهای تغییر یافته به همراه347 افزوده شده و 0 حذف شده
  1. 10 0
      core/image.cpp
  2. 4 0
      core/image.h
  3. 11 0
      doc/classes/Image.xml
  4. 279 0
      modules/tinyexr/image_saver_tinyexr.cpp
  5. 38 0
      modules/tinyexr/image_saver_tinyexr.h
  6. 5 0
      modules/tinyexr/register_types.cpp

+ 10 - 0
core/image.cpp

@@ -83,6 +83,7 @@ const char *Image::format_names[Image::FORMAT_MAX] = {
 };
 
 SavePNGFunc Image::save_png_func = NULL;
+SaveEXRFunc Image::save_exr_func = NULL;
 
 void Image::_put_pixelb(int p_x, int p_y, uint32_t p_pixelsize, uint8_t *p_data, const uint8_t *p_pixel) {
 
@@ -1917,6 +1918,14 @@ Error Image::save_png(const String &p_path) const {
 	return save_png_func(p_path, Ref<Image>((Image *)this));
 }
 
+Error Image::save_exr(const String &p_path, bool p_grayscale) const {
+
+	if (save_exr_func == NULL)
+		return ERR_UNAVAILABLE;
+
+	return save_exr_func(p_path, Ref<Image>((Image *)this), p_grayscale);
+}
+
 int Image::get_image_data_size(int p_width, int p_height, Format p_format, bool p_mipmaps) {
 
 	int mm;
@@ -2746,6 +2755,7 @@ void Image::_bind_methods() {
 
 	ClassDB::bind_method(D_METHOD("load", "path"), &Image::load);
 	ClassDB::bind_method(D_METHOD("save_png", "path"), &Image::save_png);
+	ClassDB::bind_method(D_METHOD("save_exr", "path", "grayscale"), &Image::save_exr, DEFVAL(false));
 
 	ClassDB::bind_method(D_METHOD("detect_alpha"), &Image::detect_alpha);
 	ClassDB::bind_method(D_METHOD("is_invisible"), &Image::is_invisible);

+ 4 - 0
core/image.h

@@ -49,11 +49,14 @@ class Image;
 typedef Error (*SavePNGFunc)(const String &p_path, const Ref<Image> &p_img);
 typedef Ref<Image> (*ImageMemLoadFunc)(const uint8_t *p_png, int p_size);
 
+typedef Error (*SaveEXRFunc)(const String &p_path, const Ref<Image> &p_img, bool p_grayscale);
+
 class Image : public Resource {
 	GDCLASS(Image, Resource);
 
 public:
 	static SavePNGFunc save_png_func;
+	static SaveEXRFunc save_exr_func;
 
 	enum {
 		MAX_WIDTH = 16384, // force a limit somehow
@@ -258,6 +261,7 @@ public:
 
 	Error load(const String &p_path);
 	Error save_png(const String &p_path) const;
+	Error save_exr(const String &p_path, bool p_grayscale) const;
 
 	/**
 	 * create an empty image

+ 11 - 0
doc/classes/Image.xml

@@ -415,6 +415,17 @@
 				Saves the image as a PNG file to [code]path[/code].
 			</description>
 		</method>
+		<method name="save_exr" qualifiers="const">
+			<return type="int" enum="Error">
+			</return>
+			<argument index="0" name="path" type="String">
+			</argument>
+			<argument index="1" name="grayscale" type="bool" default="false">
+			</argument>
+			<description>
+				Saves the image as an EXR file to [code]path[/code]. If grayscale is true and the image has only one channel, it will be saved explicitely as monochrome rather than one red channel. This function will return [constant ERR_UNAVAILABLE]  if Godot was compiled without the TinyEXR module.
+			</description>
+		</method>
 		<method name="set_pixel">
 			<return type="void">
 			</return>

+ 279 - 0
modules/tinyexr/image_saver_tinyexr.cpp

@@ -0,0 +1,279 @@
+/*************************************************************************/
+/*  image_saver_tinyexr.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 "image_saver_tinyexr.h"
+#include "core/math/math_funcs.h"
+
+#include "thirdparty/tinyexr/tinyexr.h"
+
+static bool is_supported_format(Image::Format p_format) {
+	// This is checked before anything else.
+	// Mostly uncompressed formats are considered.
+	switch (p_format) {
+		case Image::FORMAT_RF:
+		case Image::FORMAT_RGF:
+		case Image::FORMAT_RGBF:
+		case Image::FORMAT_RGBAF:
+		case Image::FORMAT_RH:
+		case Image::FORMAT_RGH:
+		case Image::FORMAT_RGBH:
+		case Image::FORMAT_RGBAH:
+		case Image::FORMAT_R8:
+		case Image::FORMAT_RG8:
+		case Image::FORMAT_RGB8:
+		case Image::FORMAT_RGBA8:
+			return true;
+		default:
+			return false;
+	}
+}
+
+enum SrcPixelType {
+	SRC_FLOAT,
+	SRC_HALF,
+	SRC_BYTE
+};
+
+static SrcPixelType get_source_pixel_type(Image::Format p_format) {
+	switch (p_format) {
+		case Image::FORMAT_RF:
+		case Image::FORMAT_RGF:
+		case Image::FORMAT_RGBF:
+		case Image::FORMAT_RGBAF:
+			return SRC_FLOAT;
+		case Image::FORMAT_RH:
+		case Image::FORMAT_RGH:
+		case Image::FORMAT_RGBH:
+		case Image::FORMAT_RGBAH:
+			return SRC_HALF;
+		case Image::FORMAT_R8:
+		case Image::FORMAT_RG8:
+		case Image::FORMAT_RGB8:
+		case Image::FORMAT_RGBA8:
+			return SRC_BYTE;
+		default:
+			CRASH_NOW();
+	}
+}
+
+static int get_target_pixel_type(Image::Format p_format) {
+	switch (p_format) {
+		case Image::FORMAT_RF:
+		case Image::FORMAT_RGF:
+		case Image::FORMAT_RGBF:
+		case Image::FORMAT_RGBAF:
+			return TINYEXR_PIXELTYPE_FLOAT;
+		case Image::FORMAT_RH:
+		case Image::FORMAT_RGH:
+		case Image::FORMAT_RGBH:
+		case Image::FORMAT_RGBAH:
+		// EXR doesn't support 8-bit channels so in that case we'll convert
+		case Image::FORMAT_R8:
+		case Image::FORMAT_RG8:
+		case Image::FORMAT_RGB8:
+		case Image::FORMAT_RGBA8:
+			return TINYEXR_PIXELTYPE_HALF;
+		default:
+			CRASH_NOW();
+	}
+}
+
+static int get_pixel_type_size(int p_pixel_type) {
+	switch (p_pixel_type) {
+		case TINYEXR_PIXELTYPE_HALF:
+			return 2;
+		case TINYEXR_PIXELTYPE_FLOAT:
+			return 4;
+	}
+	CRASH_NOW();
+}
+
+static int get_channel_count(Image::Format p_format) {
+	switch (p_format) {
+		case Image::FORMAT_RF:
+		case Image::FORMAT_RH:
+		case Image::FORMAT_R8:
+			return 1;
+		case Image::FORMAT_RGF:
+		case Image::FORMAT_RGH:
+		case Image::FORMAT_RG8:
+			return 2;
+		case Image::FORMAT_RGBF:
+		case Image::FORMAT_RGBH:
+		case Image::FORMAT_RGB8:
+			return 3;
+		case Image::FORMAT_RGBAF:
+		case Image::FORMAT_RGBAH:
+		case Image::FORMAT_RGBA8:
+			return 4;
+		default:
+			CRASH_NOW();
+	}
+}
+
+Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale) {
+
+	Image::Format format = p_img->get_format();
+
+	if (!is_supported_format(format)) {
+		// Format not supported
+		print_error("Image format not supported for saving as EXR. Consider saving as PNG.");
+		return ERR_UNAVAILABLE;
+	}
+
+	EXRHeader header;
+	InitEXRHeader(&header);
+
+	EXRImage image;
+	InitEXRImage(&image);
+
+	const int max_channels = 4;
+
+	// Godot does not support more than 4 channels,
+	// so we can preallocate header infos on the stack and use only the subset we need
+	PoolByteArray channels[max_channels];
+	unsigned char *channels_ptrs[max_channels];
+	EXRChannelInfo channel_infos[max_channels];
+	int pixel_types[max_channels];
+	int requested_pixel_types[max_channels] = { -1 };
+
+	// Gimp and Blender are a bit annoying so order of channels isn't straightforward.
+	const int channel_mappings[4][4] = {
+		{ 0 }, // R
+		{ 1, 0 }, // GR
+		{ 2, 1, 0 }, // BGR
+		{ 2, 1, 0, 3 } // BGRA
+	};
+
+	int channel_count = get_channel_count(format);
+	ERR_FAIL_COND_V(p_grayscale && channel_count != 1, ERR_INVALID_PARAMETER);
+
+	int target_pixel_type = get_target_pixel_type(format);
+	int target_pixel_type_size = get_pixel_type_size(target_pixel_type);
+	SrcPixelType src_pixel_type = get_source_pixel_type(format);
+	const int pixel_count = p_img->get_width() * p_img->get_height();
+
+	const int *channel_mapping = channel_mappings[channel_count - 1];
+
+	{
+		PoolByteArray src_data = p_img->get_data();
+		PoolByteArray::Read src_r = src_data.read();
+
+		for (int channel_index = 0; channel_index < channel_count; ++channel_index) {
+
+			// De-interleave channels
+
+			PoolByteArray &dst = channels[channel_index];
+			dst.resize(pixel_count * target_pixel_type_size);
+
+			PoolByteArray::Write dst_w = dst.write();
+
+			if (src_pixel_type == SRC_FLOAT && target_pixel_type == TINYEXR_PIXELTYPE_FLOAT) {
+
+				// Note: we don't save mipmaps
+				CRASH_COND(src_data.size() < pixel_count * channel_count * target_pixel_type_size);
+
+				const float *src_rp = (float *)src_r.ptr();
+				float *dst_wp = (float *)dst_w.ptr();
+
+				for (int i = 0; i < pixel_count; ++i) {
+					dst_wp[i] = src_rp[channel_index + i * channel_count];
+				}
+
+			} else if (src_pixel_type == SRC_HALF && target_pixel_type == TINYEXR_PIXELTYPE_HALF) {
+
+				CRASH_COND(src_data.size() < pixel_count * channel_count * target_pixel_type_size);
+
+				const uint16_t *src_rp = (uint16_t *)src_r.ptr();
+				uint16_t *dst_wp = (uint16_t *)dst_w.ptr();
+
+				for (int i = 0; i < pixel_count; ++i) {
+					dst_wp[i] = src_rp[channel_index + i * channel_count];
+				}
+
+			} else if (src_pixel_type == SRC_BYTE && target_pixel_type == TINYEXR_PIXELTYPE_HALF) {
+
+				CRASH_COND(src_data.size() < pixel_count * channel_count);
+
+				const uint8_t *src_rp = (uint8_t *)src_r.ptr();
+				uint16_t *dst_wp = (uint16_t *)dst_w.ptr();
+
+				for (int i = 0; i < pixel_count; ++i) {
+					dst_wp[i] = Math::make_half_float(src_rp[channel_index + i * channel_count] / 255.f);
+				}
+
+			} else {
+				CRASH_NOW();
+			}
+
+			int remapped_index = channel_mapping[channel_index];
+
+			channels_ptrs[remapped_index] = dst_w.ptr();
+
+			// No conversion
+			pixel_types[remapped_index] = target_pixel_type;
+			requested_pixel_types[remapped_index] = target_pixel_type;
+
+			// Write channel name
+			if (p_grayscale) {
+				channel_infos[remapped_index].name[0] = 'Y';
+				channel_infos[remapped_index].name[1] = '\0';
+			} else {
+				const char *rgba = "RGBA";
+				channel_infos[remapped_index].name[0] = rgba[channel_index];
+				channel_infos[remapped_index].name[1] = '\0';
+			}
+		}
+	}
+
+	image.images = channels_ptrs;
+	image.num_channels = channel_count;
+	image.width = p_img->get_width();
+	image.height = p_img->get_height();
+
+	header.num_channels = image.num_channels;
+	header.channels = channel_infos;
+	header.pixel_types = pixel_types;
+	header.requested_pixel_types = requested_pixel_types;
+	// TODO DEBUG REMOVE
+	for (int i = 0; i < 4; ++i) {
+		print_line(String("requested_pixel_types{0}: {1}").format(varray(i, requested_pixel_types[i])));
+	}
+
+	CharString utf8_filename = p_path.utf8();
+	const char *err;
+	int ret = SaveEXRImageToFile(&image, &header, utf8_filename.ptr(), &err);
+	if (ret != TINYEXR_SUCCESS) {
+		print_error(String("Saving EXR failed. Error: {0}").format(varray(err)));
+		return ERR_FILE_CANT_WRITE;
+	}
+
+	return OK;
+}

+ 38 - 0
modules/tinyexr/image_saver_tinyexr.h

@@ -0,0 +1,38 @@
+/*************************************************************************/
+/*  image_saver_tinyexr.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 IMAGE_SAVER_TINYEXR_H
+#define IMAGE_SAVER_TINYEXR_H
+
+#include "core/os/os.h"
+
+Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale);
+
+#endif // IMAGE_SAVER_TINYEXR_H

+ 5 - 0
modules/tinyexr/register_types.cpp

@@ -31,6 +31,7 @@
 #include "register_types.h"
 
 #include "image_loader_tinyexr.h"
+#include "image_saver_tinyexr.h"
 
 static ImageLoaderTinyEXR *image_loader_tinyexr = NULL;
 
@@ -38,9 +39,13 @@ void register_tinyexr_types() {
 
 	image_loader_tinyexr = memnew(ImageLoaderTinyEXR);
 	ImageLoader::add_image_format_loader(image_loader_tinyexr);
+
+	Image::save_exr_func = save_exr;
 }
 
 void unregister_tinyexr_types() {
 
 	memdelete(image_loader_tinyexr);
+
+	Image::save_exr_func = NULL;
 }