1 /* Copyright (C) 2018 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:
MemoryStream(u8 * RESTRICT data,size_t size)55 MemoryStream(u8* RESTRICT data, size_t size)
56 : data(data), size(size), pos(0)
57 {
58 }
59
RemainingSize() const60 size_t RemainingSize() const
61 {
62 ASSERT(pos <= size);
63 return size-pos;
64 }
65
CopyTo(u8 * RESTRICT dst,size_t dstSize)66 void CopyTo(u8* RESTRICT dst, size_t dstSize)
67 {
68 memcpy(dst, data+pos, dstSize);
69 pos += dstSize;
70 }
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
io_read(png_struct * png_ptr,u8 * RESTRICT data,png_size_t size)80 static void io_read(png_struct* png_ptr, u8* RESTRICT data, png_size_t size)
81 {
82 MemoryStream* stream = (MemoryStream*)png_get_io_ptr(png_ptr);
83 if(stream->RemainingSize() < size)
84 {
85 png_error(png_ptr, "PNG: not enough input");
86 return;
87 }
88
89 stream->CopyTo(data, size);
90 }
91
92
93 // write libpng output to PNG file
io_write(png_struct * png_ptr,u8 * data,png_size_t length)94 static void io_write(png_struct* png_ptr, u8* data, png_size_t length)
95 {
96 DynArray* da = (DynArray*)png_get_io_ptr(png_ptr);
97 if(da_append(da, data, length) != 0)
98 png_error(png_ptr, "io_write failed");
99 }
100
101
io_flush(png_structp UNUSED (png_ptr))102 static void io_flush(png_structp UNUSED(png_ptr))
103 {
104 }
105
106
107
108 //-----------------------------------------------------------------------------
109
transform(Tex * UNUSED (t),size_t UNUSED (transforms)) const110 Status TexCodecPng::transform(Tex* UNUSED(t), size_t UNUSED(transforms)) const
111 {
112 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.
png_decode_impl(MemoryStream * stream,png_structp png_ptr,png_infop info_ptr,Tex * t)121 static Status png_decode_impl(MemoryStream* stream, png_structp png_ptr, png_infop info_ptr, Tex* t)
122 {
123 png_set_read_fn(png_ptr, stream, io_read);
124
125 // read header and determine format
126 png_read_info(png_ptr, info_ptr);
127 png_uint_32 w, h;
128 int bit_depth, color_type, interlace_type;
129 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 if (color_type == PNG_COLOR_TYPE_PALETTE && bit_depth <= 8)
140 png_set_expand(png_ptr);
141 else if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
142 png_set_expand(png_ptr);
143 else if (bit_depth < 8)
144 png_set_expand(png_ptr);
145
146 if (bit_depth == 16)
147 png_set_strip_16(png_ptr);
148 if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
149 png_set_gray_to_rgb(png_ptr);
150 if (interlace_type != PNG_INTERLACE_NONE)
151 png_set_interlace_handling(png_ptr);
152
153 // Update info after transformations
154 png_read_update_info(png_ptr, info_ptr);
155
156 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 if (w == 0 || h == 0)
164 WARN_RETURN(ERR::TEX_INVALID_SIZE);
165 if (bit_depth != 8)
166 WARN_RETURN(ERR::TEX_NOT_8BIT_PRECISION);
167 if (!(color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_GRAY))
168 WARN_RETURN(ERR::TEX_INVALID_COLOR_TYPE);
169
170 const int channels = png_get_channels(png_ptr, info_ptr);
171 if (!(channels == 3 || channels == 4 || channels == 1))
172 WARN_RETURN(ERR::TEX_FMT_INVALID);
173
174 const size_t pitch = png_get_rowbytes(png_ptr, info_ptr);
175 const u32 bpp = (u32)(pitch / w * 8);
176
177 size_t flags = 0;
178 if (color_type == PNG_COLOR_TYPE_RGB_ALPHA)
179 flags |= TEX_ALPHA;
180 if (color_type == PNG_COLOR_TYPE_GRAY)
181 flags |= TEX_GREY;
182
183 const size_t img_size = pitch * h;
184 shared_ptr<u8> data;
185 AllocateAligned(data, img_size, g_PageSize);
186
187 std::vector<RowPtr> rows = tex_codec_alloc_rows(data.get(), h, pitch, TEX_TOP_DOWN, 0);
188 png_read_image(png_ptr, (png_bytepp)&rows[0]);
189 png_read_end(png_ptr, info_ptr);
190
191 // success; make sure all data was consumed.
192 ENSURE(stream->RemainingSize() == 0);
193
194 // store image info and validate
195 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.
png_encode_impl(Tex * t,png_structp png_ptr,png_infop info_ptr,DynArray * da)201 static Status png_encode_impl(Tex* t, png_structp png_ptr, png_infop info_ptr, DynArray* da)
202 {
203 const png_uint_32 w = (png_uint_32)t->m_Width, h = (png_uint_32)t->m_Height;
204 const size_t pitch = w * t->m_Bpp / 8;
205
206 int color_type;
207 switch(t->m_Flags & (TEX_GREY|TEX_ALPHA))
208 {
209 case TEX_GREY|TEX_ALPHA:
210 color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
211 break;
212 case TEX_GREY:
213 color_type = PNG_COLOR_TYPE_GRAY;
214 break;
215 case TEX_ALPHA:
216 color_type = PNG_COLOR_TYPE_RGB_ALPHA;
217 break;
218 default:
219 color_type = PNG_COLOR_TYPE_RGB;
220 break;
221 }
222
223 png_set_write_fn(png_ptr, da, io_write, io_flush);
224 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 u8* data = t->get_data();
228 std::vector<RowPtr> rows = tex_codec_alloc_rows(data, h, pitch, t->m_Flags, TEX_TOP_DOWN);
229
230 // PNG is native RGB.
231 const int png_transforms = (t->m_Flags & TEX_BGR)? PNG_TRANSFORM_BGR : PNG_TRANSFORM_IDENTITY;
232
233 png_set_rows(png_ptr, info_ptr, (png_bytepp)&rows[0]);
234 png_write_png(png_ptr, info_ptr, png_transforms, 0);
235
236 return INFO::OK;
237 }
238
239
240
is_hdr(const u8 * file) const241 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 return *(u32*)file == FOURCC('\x89','P','N','G');
246 }
247
248
is_ext(const OsPath & extension) const249 bool TexCodecPng::is_ext(const OsPath& extension) const
250 {
251 return extension == L".png";
252 }
253
254
hdr_size(const u8 * UNUSED (file)) const255 size_t TexCodecPng::hdr_size(const u8* UNUSED(file)) const
256 {
257 return 0; // libpng returns decoded image data; no header
258 }
259
user_warning_fn(png_structp UNUSED (png_ptr),png_const_charp warning_msg)260 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 if (strcmp(warning_msg, "iCCP: known incorrect sRGB profile") == 0)
265 return;
266 debug_printf("libpng warning: %s\n", warning_msg);
267 }
268
269 TIMER_ADD_CLIENT(tc_png_decode);
270
271 // limitation: palette images aren't supported
decode(u8 * RESTRICT data,size_t size,Tex * RESTRICT t) const272 Status TexCodecPng::decode(u8* RESTRICT data, size_t size, Tex* RESTRICT t) const
273 {
274 TIMER_ACCRUE(tc_png_decode);
275
276 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 png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, user_warning_fn);
281 if(!png_ptr)
282 WARN_RETURN(ERR::FAIL);
283 info_ptr = png_create_info_struct(png_ptr);
284 if(!info_ptr)
285 {
286 png_destroy_read_struct(&png_ptr, &info_ptr, 0);
287 WARN_RETURN(ERR::NO_MEM);
288 }
289 // setup error handling
290 if(setjmp(png_jmpbuf(png_ptr)))
291 {
292 // libpng longjmps here after an error
293 png_destroy_read_struct(&png_ptr, &info_ptr, 0);
294 WARN_RETURN(ERR::FAIL);
295 }
296
297 MemoryStream stream(data, size);
298 Status ret = png_decode_impl(&stream, png_ptr, info_ptr, t);
299
300 png_destroy_read_struct(&png_ptr, &info_ptr, 0);
301
302 return ret;
303 }
304
305
306 // limitation: palette images aren't supported
encode(Tex * RESTRICT t,DynArray * RESTRICT da) const307 Status TexCodecPng::encode(Tex* RESTRICT t, DynArray* RESTRICT da) const
308 {
309 png_infop info_ptr = 0;
310
311 // allocate PNG structures; use default stderr and longjmp error handlers
312 png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
313 if(!png_ptr)
314 WARN_RETURN(ERR::FAIL);
315 info_ptr = png_create_info_struct(png_ptr);
316 if(!info_ptr)
317 {
318 png_destroy_write_struct(&png_ptr, &info_ptr);
319 WARN_RETURN(ERR::NO_MEM);
320 }
321 // setup error handling
322 if(setjmp(png_jmpbuf(png_ptr)))
323 {
324 // libpng longjmps here after an error
325 png_destroy_write_struct(&png_ptr, &info_ptr);
326 WARN_RETURN(ERR::FAIL);
327 }
328
329 Status ret = png_encode_impl(t, png_ptr, info_ptr, da);
330
331 png_destroy_write_struct(&png_ptr, &info_ptr);
332
333 return ret;
334 }
335