Path: blob/master/modules/tinyexr/image_saver_tinyexr.cpp
21005 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.36#include <cstdlib>3738#include "thirdparty/tinyexr/tinyexr.h"3940static bool is_supported_format(Image::Format p_format) {41// This is checked before anything else.42// Mostly uncompressed formats are considered.43switch (p_format) {44case Image::FORMAT_RF:45case Image::FORMAT_RGF:46case Image::FORMAT_RGBF:47case Image::FORMAT_RGBAF:48case Image::FORMAT_RH:49case Image::FORMAT_RGH:50case Image::FORMAT_RGBH:51case Image::FORMAT_RGBAH:52case Image::FORMAT_R8:53case Image::FORMAT_RG8:54case Image::FORMAT_RGB8:55case Image::FORMAT_RGBA8:56return true;57default:58return false;59}60}6162enum SrcPixelType {63SRC_FLOAT,64SRC_HALF,65SRC_BYTE,66SRC_UNSUPPORTED67};6869static SrcPixelType get_source_pixel_type(Image::Format p_format) {70switch (p_format) {71case Image::FORMAT_RF:72case Image::FORMAT_RGF:73case Image::FORMAT_RGBF:74case Image::FORMAT_RGBAF:75return SRC_FLOAT;76case Image::FORMAT_RH:77case Image::FORMAT_RGH:78case Image::FORMAT_RGBH:79case Image::FORMAT_RGBAH:80return SRC_HALF;81case Image::FORMAT_R8:82case Image::FORMAT_RG8:83case Image::FORMAT_RGB8:84case Image::FORMAT_RGBA8:85return SRC_BYTE;86default:87return SRC_UNSUPPORTED;88}89}9091static int get_target_pixel_type(Image::Format p_format) {92switch (p_format) {93case Image::FORMAT_RF:94case Image::FORMAT_RGF:95case Image::FORMAT_RGBF:96case Image::FORMAT_RGBAF:97return TINYEXR_PIXELTYPE_FLOAT;98case Image::FORMAT_RH:99case Image::FORMAT_RGH:100case Image::FORMAT_RGBH:101case Image::FORMAT_RGBAH:102// EXR doesn't support 8-bit channels so in that case we'll convert103case Image::FORMAT_R8:104case Image::FORMAT_RG8:105case Image::FORMAT_RGB8:106case Image::FORMAT_RGBA8:107return TINYEXR_PIXELTYPE_HALF;108default:109return -1;110}111}112113static int get_pixel_type_size(int p_pixel_type) {114switch (p_pixel_type) {115case TINYEXR_PIXELTYPE_HALF:116return 2;117case TINYEXR_PIXELTYPE_FLOAT:118return 4;119}120return -1;121}122123static int get_channel_count(Image::Format p_format) {124switch (p_format) {125case Image::FORMAT_RF:126case Image::FORMAT_RH:127case Image::FORMAT_R8:128return 1;129case Image::FORMAT_RGF:130case Image::FORMAT_RGH:131case Image::FORMAT_RG8:132return 2;133case Image::FORMAT_RGBF:134case Image::FORMAT_RGBH:135case Image::FORMAT_RGB8:136return 3;137case Image::FORMAT_RGBAF:138case Image::FORMAT_RGBAH:139case Image::FORMAT_RGBA8:140return 4;141default:142return -1;143}144}145146Vector<uint8_t> save_exr_buffer(const Ref<Image> &p_img, bool p_grayscale) {147Image::Format format = p_img->get_format();148149if (!is_supported_format(format)) {150// Format not supported151print_error("Image format not supported for saving as EXR. Consider saving as PNG.");152153return Vector<uint8_t>();154}155156EXRHeader header;157InitEXRHeader(&header);158159EXRImage image;160InitEXRImage(&image);161162const int max_channels = 4;163164// Godot does not support more than 4 channels,165// so we can preallocate header infos on the stack and use only the subset we need166PackedByteArray channels[max_channels];167unsigned char *channels_ptrs[max_channels];168EXRChannelInfo channel_infos[max_channels];169int pixel_types[max_channels];170int requested_pixel_types[max_channels] = { -1 };171172// Gimp and Blender are a bit annoying so order of channels isn't straightforward.173const int channel_mappings[4][4] = {174{ 0 }, // R175{ 1, 0 }, // GR176{ 2, 1, 0 }, // BGR177{ 3, 2, 1, 0 } // ABGR178};179180int channel_count = get_channel_count(format);181ERR_FAIL_COND_V(channel_count < 0, Vector<uint8_t>());182ERR_FAIL_COND_V(p_grayscale && channel_count != 1, Vector<uint8_t>());183184int target_pixel_type = get_target_pixel_type(format);185ERR_FAIL_COND_V(target_pixel_type < 0, Vector<uint8_t>());186int target_pixel_type_size = get_pixel_type_size(target_pixel_type);187ERR_FAIL_COND_V(target_pixel_type_size < 0, Vector<uint8_t>());188SrcPixelType src_pixel_type = get_source_pixel_type(format);189ERR_FAIL_COND_V(src_pixel_type == SRC_UNSUPPORTED, Vector<uint8_t>());190const int pixel_count = p_img->get_width() * p_img->get_height();191192const int *channel_mapping = channel_mappings[channel_count - 1];193194{195PackedByteArray src_data = p_img->get_data();196const uint8_t *src_r = src_data.ptr();197198for (int channel_index = 0; channel_index < channel_count; ++channel_index) {199// De-interleave channels200201PackedByteArray &dst = channels[channel_index];202dst.resize(pixel_count * target_pixel_type_size);203204uint8_t *dst_w = dst.ptrw();205206if (src_pixel_type == SRC_FLOAT && target_pixel_type == TINYEXR_PIXELTYPE_FLOAT) {207// Note: we don't save mipmaps208CRASH_COND(src_data.size() < pixel_count * channel_count * target_pixel_type_size);209210const float *src_rp = (float *)src_r;211float *dst_wp = (float *)dst_w;212213for (int i = 0; i < pixel_count; ++i) {214dst_wp[i] = src_rp[channel_index + i * channel_count];215}216217} else if (src_pixel_type == SRC_HALF && target_pixel_type == TINYEXR_PIXELTYPE_HALF) {218CRASH_COND(src_data.size() < pixel_count * channel_count * target_pixel_type_size);219220const uint16_t *src_rp = (uint16_t *)src_r;221uint16_t *dst_wp = (uint16_t *)dst_w;222223for (int i = 0; i < pixel_count; ++i) {224dst_wp[i] = src_rp[channel_index + i * channel_count];225}226227} else if (src_pixel_type == SRC_BYTE && target_pixel_type == TINYEXR_PIXELTYPE_HALF) {228CRASH_COND(src_data.size() < pixel_count * channel_count);229230const uint8_t *src_rp = (uint8_t *)src_r;231uint16_t *dst_wp = (uint16_t *)dst_w;232233for (int i = 0; i < pixel_count; ++i) {234dst_wp[i] = Math::make_half_float(src_rp[channel_index + i * channel_count] / 255.f);235}236237} else {238CRASH_NOW();239}240241int remapped_index = channel_mapping[channel_index];242243channels_ptrs[remapped_index] = dst_w;244245// No conversion246pixel_types[remapped_index] = target_pixel_type;247requested_pixel_types[remapped_index] = target_pixel_type;248249// Write channel name250if (p_grayscale) {251channel_infos[remapped_index].name[0] = 'Y';252channel_infos[remapped_index].name[1] = '\0';253} else {254const char *rgba = "RGBA";255channel_infos[remapped_index].name[0] = rgba[channel_index];256channel_infos[remapped_index].name[1] = '\0';257}258}259}260261image.images = channels_ptrs;262image.num_channels = channel_count;263image.width = p_img->get_width();264image.height = p_img->get_height();265266header.num_channels = image.num_channels;267header.channels = channel_infos;268header.pixel_types = pixel_types;269header.requested_pixel_types = requested_pixel_types;270header.compression_type = TINYEXR_COMPRESSIONTYPE_PIZ;271272unsigned char *mem = nullptr;273const char *err = nullptr;274275size_t bytes = SaveEXRImageToMemory(&image, &header, &mem, &err);276if (err && *err != OK) {277return Vector<uint8_t>();278}279Vector<uint8_t> buffer;280buffer.resize(bytes);281memcpy(buffer.ptrw(), mem, bytes);282free(mem);283return buffer;284}285286Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale) {287const Vector<uint8_t> buffer = save_exr_buffer(p_img, p_grayscale);288if (buffer.is_empty()) {289print_error(String("Saving EXR failed."));290return ERR_FILE_CANT_WRITE;291} else {292Ref<FileAccess> ref = FileAccess::open(p_path, FileAccess::WRITE);293ERR_FAIL_COND_V(ref.is_null(), ERR_FILE_CANT_WRITE);294ref->store_buffer(buffer.ptr(), buffer.size());295}296297return OK;298}299300301