Line data Source code
1 : /* Copyright (C) 2022 Wildfire Games.
2 : *
3 : * Permission is hereby granted, free of charge, to any person obtaining
4 : * a copy of this software and associated documentation files (the
5 : * "Software"), to deal in the Software without restriction, including
6 : * without limitation the rights to use, copy, modify, merge, publish,
7 : * distribute, sublicense, and/or sell copies of the Software, and to
8 : * permit persons to whom the Software is furnished to do so, subject to
9 : * the following conditions:
10 : *
11 : * The above copyright notice and this permission notice shall be included
12 : * in all copies or substantial portions of the Software.
13 : *
14 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 : * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 : * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 : * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18 : * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 : * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 : * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 : */
22 :
23 : /*
24 : * PNG codec using libpng.
25 : */
26 :
27 : #include "precompiled.h"
28 :
29 : #include "lib/external_libraries/png.h"
30 :
31 : #include "lib/byte_order.h"
32 : #include "tex_codec.h"
33 : #include "lib/allocators/shared_ptr.h"
34 : #include "lib/timer.h"
35 :
36 : #if MSC_VERSION
37 :
38 : // squelch "dtor / setjmp interaction" warnings.
39 : // all attempts to resolve the underlying problem failed; apparently
40 : // the warning is generated if setjmp is used at all in C++ mode.
41 : // (png_*_impl have no code that would trigger ctors/dtors, nor are any
42 : // called in their prolog/epilog code).
43 : # pragma warning(disable: 4611)
44 :
45 : #endif // MSC_VERSION
46 :
47 :
48 : //-----------------------------------------------------------------------------
49 : //
50 : //-----------------------------------------------------------------------------
51 :
52 : class MemoryStream
53 : {
54 : public:
55 3 : MemoryStream(u8* RESTRICT data, size_t size)
56 3 : : data(data), size(size), pos(0)
57 : {
58 3 : }
59 :
60 54 : size_t RemainingSize() const
61 : {
62 54 : ASSERT(pos <= size);
63 54 : return size-pos;
64 : }
65 :
66 51 : void CopyTo(u8* RESTRICT dst, size_t dstSize)
67 : {
68 51 : memcpy(dst, data+pos, dstSize);
69 51 : pos += dstSize;
70 51 : }
71 :
72 : private:
73 : u8* RESTRICT data;
74 : size_t size;
75 : size_t pos;
76 : };
77 :
78 :
79 : // pass data from PNG file in memory to libpng
80 51 : static void io_read(png_struct* png_ptr, u8* RESTRICT data, png_size_t size)
81 : {
82 51 : MemoryStream* stream = (MemoryStream*)png_get_io_ptr(png_ptr);
83 51 : if(stream->RemainingSize() < size)
84 : {
85 0 : png_error(png_ptr, "PNG: not enough input");
86 : return;
87 : }
88 :
89 51 : stream->CopyTo(data, size);
90 51 : }
91 :
92 :
93 : // write libpng output to PNG file
94 0 : static void io_write(png_struct* png_ptr, u8* data, png_size_t length)
95 : {
96 0 : DynArray* da = (DynArray*)png_get_io_ptr(png_ptr);
97 0 : if(da_append(da, data, length) != 0)
98 0 : png_error(png_ptr, "io_write failed");
99 0 : }
100 :
101 :
102 0 : static void io_flush(png_structp UNUSED(png_ptr))
103 : {
104 0 : }
105 :
106 :
107 :
108 : //-----------------------------------------------------------------------------
109 :
110 13 : Status TexCodecPng::transform(Tex* UNUSED(t), size_t UNUSED(transforms)) const
111 : {
112 13 : return INFO::TEX_CODEC_CANNOT_HANDLE;
113 : }
114 :
115 :
116 : // note: it's not worth combining png_encode and png_decode, due to
117 : // libpng read/write interface differences (grr).
118 :
119 : // split out of png_decode to simplify resource cleanup and avoid
120 : // "dtor / setjmp interaction" warning.
121 3 : static Status png_decode_impl(MemoryStream* stream, png_structp png_ptr, png_infop info_ptr, Tex* t)
122 : {
123 3 : png_set_read_fn(png_ptr, stream, io_read);
124 :
125 : // read header and determine format
126 3 : png_read_info(png_ptr, info_ptr);
127 : png_uint_32 w, h;
128 : int bit_depth, color_type, interlace_type;
129 3 : png_get_IHDR(png_ptr, info_ptr, &w, &h, &bit_depth, &color_type, &interlace_type, 0, 0);
130 :
131 : // (The following is based on GdkPixbuf's PNG image loader)
132 :
133 : // Convert the following images to 8-bit RGB/RGBA:
134 : // * indexed colors
135 : // * grayscale with alpha
136 : // * transparency header
137 : // * bit depth of 16 or less than 8
138 : // * interlaced
139 3 : if (color_type == PNG_COLOR_TYPE_PALETTE && bit_depth <= 8)
140 0 : png_set_expand(png_ptr);
141 3 : else if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
142 0 : png_set_expand(png_ptr);
143 3 : else if (bit_depth < 8)
144 0 : png_set_expand(png_ptr);
145 :
146 3 : if (bit_depth == 16)
147 0 : png_set_strip_16(png_ptr);
148 3 : if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
149 0 : png_set_gray_to_rgb(png_ptr);
150 3 : if (interlace_type != PNG_INTERLACE_NONE)
151 0 : png_set_interlace_handling(png_ptr);
152 :
153 : // Update info after transformations
154 3 : png_read_update_info(png_ptr, info_ptr);
155 :
156 3 : png_get_IHDR(png_ptr, info_ptr, &w, &h, &bit_depth, &color_type, &interlace_type, 0, 0);
157 :
158 : // make sure format is acceptable:
159 : // * non-zero dimensions
160 : // * 8-bit depth
161 : // * RGB, RGBA, or grayscale
162 : // * 1, 3 or 4 channels
163 3 : if (w == 0 || h == 0)
164 0 : WARN_RETURN(ERR::TEX_INVALID_SIZE);
165 3 : if (bit_depth != 8)
166 0 : WARN_RETURN(ERR::TEX_NOT_8BIT_PRECISION);
167 3 : if (!(color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_GRAY))
168 0 : WARN_RETURN(ERR::TEX_INVALID_COLOR_TYPE);
169 :
170 3 : const int channels = png_get_channels(png_ptr, info_ptr);
171 3 : if (!(channels == 3 || channels == 4 || channels == 1))
172 0 : WARN_RETURN(ERR::TEX_FMT_INVALID);
173 :
174 3 : const size_t pitch = png_get_rowbytes(png_ptr, info_ptr);
175 3 : const u32 bpp = (u32)(pitch / w * 8);
176 :
177 3 : size_t flags = 0;
178 3 : if (color_type == PNG_COLOR_TYPE_RGB_ALPHA)
179 2 : flags |= TEX_ALPHA;
180 3 : if (color_type == PNG_COLOR_TYPE_GRAY)
181 0 : flags |= TEX_GREY;
182 :
183 3 : const size_t img_size = pitch * h;
184 6 : std::shared_ptr<u8> data;
185 3 : AllocateAligned(data, img_size, g_PageSize);
186 :
187 6 : std::vector<RowPtr> rows = tex_codec_alloc_rows(data.get(), h, pitch, TEX_TOP_DOWN, 0);
188 3 : png_read_image(png_ptr, (png_bytepp)&rows[0]);
189 3 : png_read_end(png_ptr, info_ptr);
190 :
191 : // success; make sure all data was consumed.
192 3 : ENSURE(stream->RemainingSize() == 0);
193 :
194 : // store image info and validate
195 3 : return t->wrap(w,h,bpp,flags,data,0);
196 : }
197 :
198 :
199 : // split out of png_encode to simplify resource cleanup and avoid
200 : // "dtor / setjmp interaction" warning.
201 0 : static Status png_encode_impl(Tex* t, png_structp png_ptr, png_infop info_ptr, DynArray* da)
202 : {
203 0 : const png_uint_32 w = (png_uint_32)t->m_Width, h = (png_uint_32)t->m_Height;
204 0 : const size_t pitch = w * t->m_Bpp / 8;
205 :
206 : int color_type;
207 0 : switch(t->m_Flags & (TEX_GREY|TEX_ALPHA))
208 : {
209 0 : case TEX_GREY|TEX_ALPHA:
210 0 : color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
211 0 : break;
212 0 : case TEX_GREY:
213 0 : color_type = PNG_COLOR_TYPE_GRAY;
214 0 : break;
215 0 : case TEX_ALPHA:
216 0 : color_type = PNG_COLOR_TYPE_RGB_ALPHA;
217 0 : break;
218 0 : default:
219 0 : color_type = PNG_COLOR_TYPE_RGB;
220 0 : break;
221 : }
222 :
223 0 : png_set_write_fn(png_ptr, da, io_write, io_flush);
224 0 : png_set_IHDR(png_ptr, info_ptr, w, h, 8, color_type,
225 : PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
226 :
227 0 : u8* data = t->get_data();
228 0 : std::vector<RowPtr> rows = tex_codec_alloc_rows(data, h, pitch, t->m_Flags, TEX_TOP_DOWN);
229 :
230 : // PNG is native RGB.
231 0 : const int png_transforms = (t->m_Flags & TEX_BGR)? PNG_TRANSFORM_BGR : PNG_TRANSFORM_IDENTITY;
232 :
233 0 : png_set_rows(png_ptr, info_ptr, (png_bytepp)&rows[0]);
234 0 : png_write_png(png_ptr, info_ptr, png_transforms, 0);
235 :
236 0 : return INFO::OK;
237 : }
238 :
239 :
240 :
241 4 : bool TexCodecPng::is_hdr(const u8* file) const
242 : {
243 : // don't use png_sig_cmp, so we don't pull in libpng for
244 : // this check alone (it might not actually be used).
245 4 : return *(u32*)file == FOURCC('\x89','P','N','G');
246 : }
247 :
248 :
249 0 : bool TexCodecPng::is_ext(const OsPath& extension) const
250 : {
251 0 : return extension == L".png";
252 : }
253 :
254 :
255 6 : size_t TexCodecPng::hdr_size(const u8* UNUSED(file)) const
256 : {
257 6 : return 0; // libpng returns decoded image data; no header
258 : }
259 :
260 0 : static void user_warning_fn(png_structp UNUSED(png_ptr), png_const_charp warning_msg)
261 : {
262 : // Suppress this warning because it's useless and occurs on a large number of files
263 : // see http://trac.wildfiregames.com/ticket/2184
264 0 : if (strcmp(warning_msg, "iCCP: known incorrect sRGB profile") == 0)
265 0 : return;
266 0 : debug_printf("libpng warning: %s\n", warning_msg);
267 : }
268 :
269 1 : TIMER_ADD_CLIENT(tc_png_decode);
270 :
271 : // limitation: palette images aren't supported
272 3 : Status TexCodecPng::decode(u8* RESTRICT data, size_t size, Tex* RESTRICT t) const
273 : {
274 6 : TIMER_ACCRUE(tc_png_decode);
275 :
276 3 : png_infop info_ptr = 0;
277 :
278 : // allocate PNG structures; use default stderr and longjmp error handler, use custom
279 : // warning handler to filter out useless messages
280 3 : png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, user_warning_fn);
281 3 : if(!png_ptr)
282 0 : return ERR::FAIL;
283 3 : info_ptr = png_create_info_struct(png_ptr);
284 3 : if(!info_ptr)
285 : {
286 0 : png_destroy_read_struct(&png_ptr, &info_ptr, 0);
287 0 : return ERR::NO_MEM;
288 : }
289 : // setup error handling
290 3 : if(setjmp(png_jmpbuf(png_ptr)))
291 : {
292 : // libpng longjmps here after an error
293 0 : png_destroy_read_struct(&png_ptr, &info_ptr, 0);
294 0 : return ERR::FAIL;
295 : }
296 :
297 3 : MemoryStream stream(data, size);
298 3 : Status ret = png_decode_impl(&stream, png_ptr, info_ptr, t);
299 :
300 3 : png_destroy_read_struct(&png_ptr, &info_ptr, 0);
301 :
302 3 : return ret;
303 : }
304 :
305 :
306 : // limitation: palette images aren't supported
307 0 : Status TexCodecPng::encode(Tex* RESTRICT t, DynArray* RESTRICT da) const
308 : {
309 0 : png_infop info_ptr = 0;
310 :
311 : // allocate PNG structures; use default stderr and longjmp error handlers
312 0 : png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
313 0 : if(!png_ptr)
314 0 : WARN_RETURN(ERR::FAIL);
315 0 : info_ptr = png_create_info_struct(png_ptr);
316 0 : if(!info_ptr)
317 : {
318 0 : png_destroy_write_struct(&png_ptr, &info_ptr);
319 0 : WARN_RETURN(ERR::NO_MEM);
320 : }
321 : // setup error handling
322 0 : if(setjmp(png_jmpbuf(png_ptr)))
323 : {
324 : // libpng longjmps here after an error
325 0 : png_destroy_write_struct(&png_ptr, &info_ptr);
326 0 : WARN_RETURN(ERR::FAIL);
327 : }
328 :
329 0 : Status ret = png_encode_impl(t, png_ptr, info_ptr, da);
330 :
331 0 : png_destroy_write_struct(&png_ptr, &info_ptr);
332 :
333 0 : return ret;
334 3 : }
|