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