1/*-------------------------------------------------------------------------
2 * drawElements Quality Program Tester Core
3 * ----------------------------------------
4 *
5 * Copyright 2014 The Android Open Source Project
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 *      http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 *//*!
20 * \file
21 * \brief Image IO.
22 *//*--------------------------------------------------------------------*/
23
24#include "tcuImageIO.hpp"
25#include "tcuResource.hpp"
26#include "tcuSurface.hpp"
27#include "tcuCompressedTexture.hpp"
28#include "deFilePath.hpp"
29#include "deUniquePtr.hpp"
30
31#include <string>
32#include <vector>
33#include <cstdio>
34
35#include "png.h"
36
37namespace tcu
38{
39namespace ImageIO
40{
41
42using std::string;
43using std::vector;
44
45/*--------------------------------------------------------------------*//*!
46 * \brief Load image from resource
47 *
48 * TextureLevel storage is set to match image data. Only PNG format is
49 * currently supported.
50 *
51 * \param dst		Destination pixel container
52 * \param archive	Resource archive
53 * \param fileName	Resource file name
54 *//*--------------------------------------------------------------------*/
55void loadImage (TextureLevel& dst, const tcu::Archive& archive, const char* fileName)
56{
57	string ext = de::FilePath(fileName).getFileExtension();
58
59	if (ext == "png" || ext == "PNG")
60		loadPNG(dst, archive, fileName);
61	else
62		throw InternalError("Unrecognized image file extension", fileName, __FILE__, __LINE__);
63}
64
65DE_BEGIN_EXTERN_C
66static void pngReadResource (png_structp png_ptr, png_bytep data, png_size_t length)
67{
68	tcu::Resource* resource = (tcu::Resource*)png_get_io_ptr(png_ptr);
69	resource->read(data, (int)length);
70}
71DE_END_EXTERN_C
72
73/*--------------------------------------------------------------------*//*!
74 * \brief Load PNG image from resource
75 *
76 * TextureLevel storage is set to match image data.
77 *
78 * \param dst		Destination pixel container
79 * \param archive	Resource archive
80 * \param fileName	Resource file name
81 *//*--------------------------------------------------------------------*/
82void loadPNG (TextureLevel& dst, const tcu::Archive& archive, const char* fileName)
83{
84	de::UniquePtr<Resource> resource(archive.getResource(fileName));
85
86	// Verify header.
87	deUint8 header[8];
88	resource->read(header, sizeof(header));
89	TCU_CHECK(png_sig_cmp((png_bytep)&header[0], 0, DE_LENGTH_OF_ARRAY(header)) == 0);
90
91	png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, DE_NULL, DE_NULL, DE_NULL);
92	TCU_CHECK(png_ptr);
93
94	png_infop info_ptr = png_create_info_struct(png_ptr);
95	TCU_CHECK(info_ptr);
96
97	if (setjmp(png_jmpbuf(png_ptr)))
98		throw InternalError("An error occured when loading PNG", fileName, __FILE__, __LINE__);
99
100	png_set_read_fn(png_ptr, resource.get(), pngReadResource);
101	png_set_sig_bytes(png_ptr, 8);
102
103	png_read_info(png_ptr, info_ptr);
104
105	const int		width			= png_get_image_width(png_ptr, info_ptr);
106	const int		height			= png_get_image_height(png_ptr, info_ptr);
107	TextureFormat	textureFormat;
108
109	{
110		const png_byte	colorType	= png_get_color_type(png_ptr, info_ptr);
111		const png_byte	bitDepth	= png_get_bit_depth(png_ptr, info_ptr);
112
113		if (colorType == PNG_COLOR_TYPE_RGB && bitDepth == 8)
114			textureFormat = TextureFormat(TextureFormat::RGB, TextureFormat::UNORM_INT8);
115		else if (colorType == PNG_COLOR_TYPE_RGBA && bitDepth == 8)
116			textureFormat = TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8);
117		else
118			throw InternalError("Unsupported PNG depth or color type", fileName, __FILE__, __LINE__);
119	}
120
121	// Resize destination texture.
122	dst.setStorage(textureFormat, width, height);
123
124	std::vector<png_bytep> row_pointers;
125	row_pointers.resize(height);
126	for (int y = 0; y < height; y++)
127		row_pointers[y] = (deUint8*)dst.getAccess().getDataPtr() + y*dst.getAccess().getRowPitch();
128
129	png_read_image(png_ptr, &row_pointers[0]);
130
131	png_destroy_info_struct(png_ptr, &info_ptr);
132	png_destroy_read_struct(&png_ptr, DE_NULL, DE_NULL);
133}
134
135static int textureFormatToPNGFormat (const TextureFormat& format)
136{
137	if (format == TextureFormat(TextureFormat::RGB, TextureFormat::UNORM_INT8))
138		return PNG_COLOR_TYPE_RGB;
139	else if (format == TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8))
140		return PNG_COLOR_TYPE_RGBA;
141	else
142		throw InternalError("Unsupported texture format", DE_NULL, __FILE__, __LINE__);
143}
144
145/*--------------------------------------------------------------------*//*!
146 * \brief Write image to file in PNG format
147 *
148 * This is provided for debugging and development purposes. Test code must
149 * not write to any files except the test log by default.
150 *
151 * \note Only RGB/RGBA, UNORM_INT8 formats are supported
152 * \param src		Source pixel data
153 * \param fileName	File name
154 *//*--------------------------------------------------------------------*/
155void savePNG (const ConstPixelBufferAccess& src, const char* fileName)
156{
157	FILE*	fp			= fopen(fileName, "wb");
158	TCU_CHECK(fp);
159
160	png_structp pngPtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
161
162	if (!pngPtr)
163	{
164		fclose(fp);
165		TCU_CHECK(pngPtr);
166	}
167
168	png_infop infoPtr = png_create_info_struct(pngPtr);
169	if (!infoPtr)
170	{
171		png_destroy_write_struct(&pngPtr, NULL);
172		TCU_CHECK(infoPtr);
173	}
174
175	if (setjmp(png_jmpbuf(pngPtr)))
176	{
177		png_destroy_write_struct(&pngPtr, &infoPtr);
178		fclose(fp);
179		throw tcu::InternalError("PNG compression failed");
180	}
181	else
182	{
183		int pngFormat = textureFormatToPNGFormat(src.getFormat());
184
185		png_init_io(pngPtr, fp);
186
187		// Header
188		png_set_IHDR(pngPtr, infoPtr, src.getWidth(), src.getHeight(), 8,
189					 pngFormat, PNG_INTERLACE_NONE,
190					 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
191		png_write_info(pngPtr, infoPtr);
192
193		std::vector<png_bytep> rowPointers(src.getHeight());
194		for (int y = 0; y < src.getHeight(); y++)
195			rowPointers[y] = (deUint8*)src.getDataPtr() + y*src.getRowPitch();
196
197		png_write_image(pngPtr, &rowPointers[0]);
198		png_write_end(pngPtr, NULL);
199
200		png_destroy_write_struct(&pngPtr, &infoPtr);
201		fclose(fp);
202	}
203}
204
205enum PkmImageFormat
206{
207	ETC1_RGB_NO_MIPMAPS		= 0,
208	ETC1_RGBA_NO_MIPMAPS	= 1,
209	ETC1_RGB_MIPMAPS		= 2,
210	ETC1_RGBA_MIPMAPS		= 3
211};
212
213static inline deUint16 readBigEndianShort (tcu::Resource* resource)
214{
215	deUint16 val;
216	resource->read((deUint8*)&val, sizeof(val));
217	return ((val >> 8) & 0xFF) | ((val << 8) & 0xFF00);
218}
219
220/*--------------------------------------------------------------------*//*!
221 * \brief Load compressed image data from PKM file
222 *
223 * \note			Only ETC1_RGB8_NO_MIPMAPS format is supported
224 * \param dst		Destination pixel container
225 * \param archive	Resource archive
226 * \param fileName	Resource file name
227 *//*--------------------------------------------------------------------*/
228void loadPKM (CompressedTexture& dst, const tcu::Archive& archive, const char* fileName)
229{
230	de::UniquePtr<Resource> resource(archive.getResource(fileName));
231
232	// Check magic and version.
233	deUint8 refMagic[] = {'P', 'K', 'M', ' ', '1', '0'};
234	deUint8 magic[6];
235	resource->read(magic, DE_LENGTH_OF_ARRAY(magic));
236
237	if (memcmp(refMagic, magic, sizeof(magic)) != 0)
238		throw InternalError("Signature doesn't match PKM signature", resource->getName().c_str(), __FILE__, __LINE__);
239
240	deUint16 type = readBigEndianShort(resource.get());
241	if (type != ETC1_RGB_NO_MIPMAPS)
242		throw InternalError("Unsupported PKM type", resource->getName().c_str(), __FILE__, __LINE__);
243
244	deUint16	width			= readBigEndianShort(resource.get());
245	deUint16	height			= readBigEndianShort(resource.get());
246	deUint16	activeWidth		= readBigEndianShort(resource.get());
247	deUint16	activeHeight	= readBigEndianShort(resource.get());
248
249    DE_UNREF(width && height);
250
251	dst.setStorage(CompressedTexture::ETC1_RGB8, (int)activeWidth, (int)activeHeight);
252	resource->read((deUint8*)dst.getData(), dst.getDataSize());
253}
254
255} // ImageIO
256} // tcu
257