1/**
2 * Copyright (c) 2006-2023 LOVE Development Team
3 *
4 * This software is provided 'as-is', without any express or implied
5 * warranty. In no event will the authors be held liable for any damages
6 * arising from the use of this software.
7 *
8 * Permission is granted to anyone to use this software for any purpose,
9 * including commercial applications, and to alter it and redistribute it
10 * freely, subject to the following restrictions:
11 *
12 * 1. The origin of this software must not be misrepresented; you must not
13 * claim that you wrote the original software. If you use this software
14 * in a product, an acknowledgment in the product documentation would be
15 * appreciated but is not required.
16 * 2. Altered source versions must be plainly marked as such, and must not be
17 * misrepresented as being the original software.
18 * 3. This notice may not be removed or altered from any source distribution.
19 **/
20
21// LOVE
22#include "EXRHandler.h"
23#include "common/floattypes.h"
24#include "common/Exception.h"
25
26// zlib (for tinyexr)
27#include <zlib.h>
28
29// tinyexr
30#define TINYEXR_IMPLEMENTATION
31#define TINYEXR_USE_MINIZ 0
32#include "libraries/tinyexr/tinyexr.h"
33
34// C
35#include <cstdlib>
36
37namespace love
38{
39namespace image
40{
41namespace magpie
42{
43
44bool EXRHandler::canDecode(Data *data)
45{
46 EXRVersion version;
47 return ParseEXRVersionFromMemory(&version, (const unsigned char *) data->getData(), data->getSize()) == TINYEXR_SUCCESS;
48}
49
50bool EXRHandler::canEncode(PixelFormat /*rawFormat*/, EncodedFormat /*encodedFormat*/)
51{
52 return false;
53}
54
55template <typename T>
56static void getEXRChannels(const EXRHeader &header, const EXRImage &image, T *rgba[4])
57{
58 for (int i = 0; i < header.num_channels; i++)
59 {
60 switch (*header.channels[i].name)
61 {
62 case 'R':
63 rgba[0] = (T *) image.images[i];
64 break;
65 case 'G':
66 rgba[1] = (T *) image.images[i];
67 break;
68 case 'B':
69 rgba[2] = (T *) image.images[i];
70 break;
71 case 'A':
72 rgba[3] = (T *) image.images[i];
73 break;
74 }
75 }
76}
77
78template <typename T>
79static T *loadEXRChannels(int width, int height, T *rgba[4], T one)
80{
81 T *data = nullptr;
82
83 try
84 {
85 data = new T[width * height * 4];
86 }
87 catch (std::exception &)
88 {
89 throw love::Exception("Out of memory.");
90 }
91
92 for (int y = 0; y < height; y++)
93 {
94 for (int x = 0; x < width; x++)
95 {
96 size_t offset = y * width + x;
97
98 data[offset * 4 + 0] = rgba[0] != nullptr ? rgba[0][offset] : 0;
99 data[offset * 4 + 1] = rgba[1] != nullptr ? rgba[1][offset] : 0;
100 data[offset * 4 + 2] = rgba[2] != nullptr ? rgba[2][offset] : 0;
101 data[offset * 4 + 3] = rgba[3] != nullptr ? rgba[3][offset] : one;
102 }
103 }
104
105 return data;
106}
107
108FormatHandler::DecodedImage EXRHandler::decode(Data *data)
109{
110 const char *err = "unknown error";
111 auto mem = (const unsigned char *) data->getData();
112 size_t memsize = data->getSize();
113 DecodedImage img;
114
115 EXRHeader exrHeader;
116 InitEXRHeader(&exrHeader);
117
118 EXRImage exrImage;
119 InitEXRImage(&exrImage);
120
121 try
122 {
123 EXRVersion exrVersion;
124 if (ParseEXRVersionFromMemory(&exrVersion, mem, memsize) != TINYEXR_SUCCESS)
125 throw love::Exception("Could not parse EXR image header.");
126
127 if (exrVersion.multipart || exrVersion.non_image || exrVersion.tiled)
128 throw love::Exception("Multi-part, tiled, and non-image EXR files are not supported.");
129
130 if (ParseEXRHeaderFromMemory(&exrHeader, &exrVersion, mem, memsize, &err) != TINYEXR_SUCCESS)
131 throw love::Exception("Could not parse EXR image header: %s", err);
132
133 if (LoadEXRImageFromMemory(&exrImage, &exrHeader, mem, memsize, &err) != TINYEXR_SUCCESS)
134 throw love::Exception("Could not decode EXR image: %s", err);
135 }
136 catch (love::Exception &)
137 {
138 FreeEXRErrorMessage(err);
139 throw;
140 }
141
142 int pixelType = exrHeader.pixel_types[0];
143
144 for (int i = 1; i < exrHeader.num_channels; i++)
145 {
146 if (pixelType != exrHeader.pixel_types[i])
147 {
148 FreeEXRImage(&exrImage);
149 throw love::Exception("Could not decode EXR image: all channels must have the same data type.");
150 }
151 }
152
153 img.width = exrImage.width;
154 img.height = exrImage.height;
155
156 if (pixelType == TINYEXR_PIXELTYPE_HALF)
157 {
158 img.format = PIXELFORMAT_RGBA16F;
159
160 float16 *rgba[4] = {nullptr};
161 getEXRChannels(exrHeader, exrImage, rgba);
162
163 try
164 {
165 img.data = (unsigned char *) loadEXRChannels(img.width, img.height, rgba, float32to16(1.0f));
166 }
167 catch (love::Exception &)
168 {
169 FreeEXRImage(&exrImage);
170 throw;
171 }
172 }
173 else if (pixelType == TINYEXR_PIXELTYPE_FLOAT)
174 {
175 img.format = PIXELFORMAT_RGBA32F;
176
177 float *rgba[4] = {nullptr};
178 getEXRChannels(exrHeader, exrImage, rgba);
179
180 try
181 {
182 img.data = (unsigned char *) loadEXRChannels(img.width, img.height, rgba, 1.0f);
183 }
184 catch (love::Exception &)
185 {
186 FreeEXRImage(&exrImage);
187 throw;
188 }
189 }
190 else
191 {
192 FreeEXRImage(&exrImage);
193 throw love::Exception("Could not decode EXR image: unknown pixel format.");
194 }
195
196 img.size = img.width * img.height * getPixelFormatSize(img.format);
197
198 FreeEXRImage(&exrImage);
199
200 return img;
201}
202
203FormatHandler::EncodedImage EXRHandler::encode(const DecodedImage & /*img*/, EncodedFormat /*encodedFormat*/)
204{
205 throw love::Exception("Invalid format.");
206}
207
208void EXRHandler::freeRawPixels(unsigned char *mem)
209{
210 delete[] mem;
211}
212
213} // magpie
214} // image
215} // love
216