Path: blob/master/modules/tinyexr/image_saver_tinyexr.cpp
10277 views
/**************************************************************************/1/* image_saver_tinyexr.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "image_saver_tinyexr.h"3132#include "core/io/file_access.h"33#include "core/math/math_funcs.h"3435#include <zlib.h> // Should come before including tinyexr.3637#include "thirdparty/tinyexr/tinyexr.h"3839static bool is_supported_format(Image::Format p_format) {40// This is checked before anything else.41// Mostly uncompressed formats are considered.42switch (p_format) {43case Image::FORMAT_RF:44case Image::FORMAT_RGF:45case Image::FORMAT_RGBF:46case Image::FORMAT_RGBAF:47case Image::FORMAT_RH:48case Image::FORMAT_RGH:49case Image::FORMAT_RGBH:50case Image::FORMAT_RGBAH:51case Image::FORMAT_R8:52case Image::FORMAT_RG8:53case Image::FORMAT_RGB8:54case Image::FORMAT_RGBA8:55return true;56default:57return false;58}59}6061enum SrcPixelType {62SRC_FLOAT,63SRC_HALF,64SRC_BYTE,65SRC_UNSUPPORTED66};6768static SrcPixelType get_source_pixel_type(Image::Format p_format) {69switch (p_format) {70case Image::FORMAT_RF:71case Image::FORMAT_RGF:72case Image::FORMAT_RGBF:73case Image::FORMAT_RGBAF:74return SRC_FLOAT;75case Image::FORMAT_RH:76case Image::FORMAT_RGH:77case Image::FORMAT_RGBH:78case Image::FORMAT_RGBAH:79return SRC_HALF;80case Image::FORMAT_R8:81case Image::FORMAT_RG8:82case Image::FORMAT_RGB8:83case Image::FORMAT_RGBA8:84return SRC_BYTE;85default:86return SRC_UNSUPPORTED;87}88}8990static int get_target_pixel_type(Image::Format p_format) {91switch (p_format) {92case Image::FORMAT_RF:93case Image::FORMAT_RGF:94case Image::FORMAT_RGBF:95case Image::FORMAT_RGBAF:96return TINYEXR_PIXELTYPE_FLOAT;97case Image::FORMAT_RH:98case Image::FORMAT_RGH:99case Image::FORMAT_RGBH:100case Image::FORMAT_RGBAH:101// EXR doesn't support 8-bit channels so in that case we'll convert102case Image::FORMAT_R8:103case Image::FORMAT_RG8:104case Image::FORMAT_RGB8:105case Image::FORMAT_RGBA8:106return TINYEXR_PIXELTYPE_HALF;107default:108return -1;109}110}111112static int get_pixel_type_size(int p_pixel_type) {113switch (p_pixel_type) {114case TINYEXR_PIXELTYPE_HALF:115return 2;116case TINYEXR_PIXELTYPE_FLOAT:117return 4;118}119return -1;120}121122static int get_channel_count(Image::Format p_format) {123switch (p_format) {124case Image::FORMAT_RF:125case Image::FORMAT_RH:126case Image::FORMAT_R8:127return 1;128case Image::FORMAT_RGF:129case Image::FORMAT_RGH:130case Image::FORMAT_RG8:131return 2;132case Image::FORMAT_RGBF:133case Image::FORMAT_RGBH:134case Image::FORMAT_RGB8:135return 3;136case Image::FORMAT_RGBAF:137case Image::FORMAT_RGBAH:138case Image::FORMAT_RGBA8:139return 4;140default:141return -1;142}143}144145Vector<uint8_t> save_exr_buffer(const Ref<Image> &p_img, bool p_grayscale) {146Image::Format format = p_img->get_format();147148if (!is_supported_format(format)) {149// Format not supported150print_error("Image format not supported for saving as EXR. Consider saving as PNG.");151152return Vector<uint8_t>();153}154155EXRHeader header;156InitEXRHeader(&header);157158EXRImage image;159InitEXRImage(&image);160161const int max_channels = 4;162163// Godot does not support more than 4 channels,164// so we can preallocate header infos on the stack and use only the subset we need165PackedByteArray channels[max_channels];166unsigned char *channels_ptrs[max_channels];167EXRChannelInfo channel_infos[max_channels];168int pixel_types[max_channels];169int requested_pixel_types[max_channels] = { -1 };170171// Gimp and Blender are a bit annoying so order of channels isn't straightforward.172const int channel_mappings[4][4] = {173{ 0 }, // R174{ 1, 0 }, // GR175{ 2, 1, 0 }, // BGR176{ 3, 2, 1, 0 } // ABGR177};178179int channel_count = get_channel_count(format);180ERR_FAIL_COND_V(channel_count < 0, Vector<uint8_t>());181ERR_FAIL_COND_V(p_grayscale && channel_count != 1, Vector<uint8_t>());182183int target_pixel_type = get_target_pixel_type(format);184ERR_FAIL_COND_V(target_pixel_type < 0, Vector<uint8_t>());185int target_pixel_type_size = get_pixel_type_size(target_pixel_type);186ERR_FAIL_COND_V(target_pixel_type_size < 0, Vector<uint8_t>());187SrcPixelType src_pixel_type = get_source_pixel_type(format);188ERR_FAIL_COND_V(src_pixel_type == SRC_UNSUPPORTED, Vector<uint8_t>());189const int pixel_count = p_img->get_width() * p_img->get_height();190191const int *channel_mapping = channel_mappings[channel_count - 1];192193{194PackedByteArray src_data = p_img->get_data();195const uint8_t *src_r = src_data.ptr();196197for (int channel_index = 0; channel_index < channel_count; ++channel_index) {198// De-interleave channels199200PackedByteArray &dst = channels[channel_index];201dst.resize(pixel_count * target_pixel_type_size);202203uint8_t *dst_w = dst.ptrw();204205if (src_pixel_type == SRC_FLOAT && target_pixel_type == TINYEXR_PIXELTYPE_FLOAT) {206// Note: we don't save mipmaps207CRASH_COND(src_data.size() < pixel_count * channel_count * target_pixel_type_size);208209const float *src_rp = (float *)src_r;210float *dst_wp = (float *)dst_w;211212for (int i = 0; i < pixel_count; ++i) {213dst_wp[i] = src_rp[channel_index + i * channel_count];214}215216} else if (src_pixel_type == SRC_HALF && target_pixel_type == TINYEXR_PIXELTYPE_HALF) {217CRASH_COND(src_data.size() < pixel_count * channel_count * target_pixel_type_size);218219const uint16_t *src_rp = (uint16_t *)src_r;220uint16_t *dst_wp = (uint16_t *)dst_w;221222for (int i = 0; i < pixel_count; ++i) {223dst_wp[i] = src_rp[channel_index + i * channel_count];224}225226} else if (src_pixel_type == SRC_BYTE && target_pixel_type == TINYEXR_PIXELTYPE_HALF) {227CRASH_COND(src_data.size() < pixel_count * channel_count);228229const uint8_t *src_rp = (uint8_t *)src_r;230uint16_t *dst_wp = (uint16_t *)dst_w;231232for (int i = 0; i < pixel_count; ++i) {233dst_wp[i] = Math::make_half_float(src_rp[channel_index + i * channel_count] / 255.f);234}235236} else {237CRASH_NOW();238}239240int remapped_index = channel_mapping[channel_index];241242channels_ptrs[remapped_index] = dst_w;243244// No conversion245pixel_types[remapped_index] = target_pixel_type;246requested_pixel_types[remapped_index] = target_pixel_type;247248// Write channel name249if (p_grayscale) {250channel_infos[remapped_index].name[0] = 'Y';251channel_infos[remapped_index].name[1] = '\0';252} else {253const char *rgba = "RGBA";254channel_infos[remapped_index].name[0] = rgba[channel_index];255channel_infos[remapped_index].name[1] = '\0';256}257}258}259260image.images = channels_ptrs;261image.num_channels = channel_count;262image.width = p_img->get_width();263image.height = p_img->get_height();264265header.num_channels = image.num_channels;266header.channels = channel_infos;267header.pixel_types = pixel_types;268header.requested_pixel_types = requested_pixel_types;269header.compression_type = TINYEXR_COMPRESSIONTYPE_PIZ;270271unsigned char *mem = nullptr;272const char *err = nullptr;273274size_t bytes = SaveEXRImageToMemory(&image, &header, &mem, &err);275if (err && *err != OK) {276return Vector<uint8_t>();277}278Vector<uint8_t> buffer;279buffer.resize(bytes);280memcpy(buffer.ptrw(), mem, bytes);281free(mem);282return buffer;283}284285Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale) {286const Vector<uint8_t> buffer = save_exr_buffer(p_img, p_grayscale);287if (buffer.is_empty()) {288print_error(String("Saving EXR failed."));289return ERR_FILE_CANT_WRITE;290} else {291Ref<FileAccess> ref = FileAccess::open(p_path, FileAccess::WRITE);292ERR_FAIL_COND_V(ref.is_null(), ERR_FILE_CANT_WRITE);293ref->store_buffer(buffer.ptr(), buffer.size());294}295296return OK;297}298299300