1 /**********************************************\
2 *
3 * Andrey A. Ugolnik
4 * http://www.ugolnik.info
5 * andrey@ugolnik.info
6 *
7 \**********************************************/
8
9 #include "PngReader.h"
10 #include "common/bitmap_description.h"
11 #include "common/file.h"
12 #include "common/helpers.h"
13
14 #include <cstring>
15 #include <png.h>
16
17 namespace
18 {
19 class cPngMemoryReader
20 {
21 public:
cPngMemoryReader(const uint8_t * data,uint32_t size)22 cPngMemoryReader(const uint8_t* data, uint32_t size)
23 : m_data(data)
24 , m_remain(size)
25 , m_offset(0)
26 {
27 }
28
read(uint8_t * out,uint32_t size)29 uint32_t read(uint8_t* out, uint32_t size)
30 {
31 size = size <= m_remain ? size : m_remain;
32
33 ::memcpy(out, &m_data[m_offset], size);
34 m_offset += size;
35 m_remain -= size;
36
37 return size;
38 }
39
memoryReader(png_structp png,png_bytep outBytes,png_size_t byteCountToRead)40 static void memoryReader(png_structp png, png_bytep outBytes, png_size_t byteCountToRead)
41 {
42 auto reader = static_cast<cPngMemoryReader*>(png_get_io_ptr(png));
43 if (reader != nullptr)
44 {
45 reader->read(outBytes, byteCountToRead);
46 }
47 }
48
49 private:
50 const uint8_t* m_data;
51 uint32_t m_remain;
52 uint32_t m_offset;
53 };
54
locateICCProfile(const png_structp png,const png_infop info,uint32_t & iccProfileSize)55 void* locateICCProfile(const png_structp png, const png_infop info, uint32_t& iccProfileSize)
56 {
57 png_charp name;
58 int comp_type;
59 #if ((PNG_LIBPNG_VER_MAJOR << 8) | PNG_LIBPNG_VER_MINOR << 0) < ((1 << 8) | (5 << 0))
60 png_charp icc;
61 #else // >= libpng 1.5.0
62 png_bytep icc;
63 #endif
64 png_uint_32 size;
65 if (png_get_iCCP(png, info, &name, &comp_type, &icc, &size) == PNG_INFO_iCCP)
66 {
67 // ::printf("-- name: %s\n", name);
68 // ::printf("-- comp_type: %d\n", comp_type);
69 // ::printf("-- size: %u\n", size);
70
71 iccProfileSize = size;
72 return icc;
73 }
74
75 return nullptr;
76 }
77
78 } // namespace
79
cPngReader()80 cPngReader::cPngReader()
81 {
82 }
83
~cPngReader()84 cPngReader::~cPngReader()
85 {
86 }
87
isValid(const uint8_t * data,uint32_t size)88 bool cPngReader::isValid(const uint8_t* data, uint32_t size)
89 {
90 uint8_t header[MinBytesToTest];
91 ::memcpy(header, data, sizeof(header));
92 return size >= MinBytesToTest && png_sig_cmp(header, 0, sizeof(header)) == 0;
93 }
94
loadPng(sBitmapDescription & desc,const uint8_t * data,uint32_t size)95 bool cPngReader::loadPng(sBitmapDescription& desc, const uint8_t* data, uint32_t size)
96 {
97 if (isValid(data, size) == false)
98 {
99 ::printf("(EE) Frame is not recognized as a PNG format.\n");
100 return false;
101 }
102
103 // initialize stuff
104 auto png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
105 if (png == nullptr)
106 {
107 ::printf("(EE) png_create_read_struct failed.\n");
108 return false;
109 }
110
111 auto info = png_create_info_struct(png);
112 if (info == nullptr)
113 {
114 ::printf("(EE) png_create_info_struct failed.\n");
115 png_destroy_read_struct(&png, nullptr, nullptr);
116 return false;
117 }
118
119 cPngMemoryReader pngReader{ data, size };
120 png_set_read_fn(png, &pngReader, cPngMemoryReader::memoryReader);
121
122 png_read_info(png, info);
123
124 auto colorType = png_get_color_type(png, info);
125 if (colorType == PNG_COLOR_TYPE_PALETTE)
126 {
127 png_set_palette_to_rgb(png);
128 }
129
130 #if defined(PNG_1_0_X) || defined(PNG_1_2_X)
131 if (colorType == PNG_COLOR_TYPE_GRAY && info->bit_depth < 8)
132 {
133 // depreceted in libPNG-1.4.2
134 png_set_gray_1_2_4_to_8(png);
135 }
136 #endif
137
138 if (png_get_valid(png, info, PNG_INFO_tRNS))
139 {
140 png_set_tRNS_to_alpha(png);
141 }
142 if (png_get_bit_depth(png, info) == 16)
143 {
144 png_set_strip_16(png);
145 }
146 if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA)
147 {
148 png_set_gray_to_rgb(png);
149 }
150
151 // int number_of_passes = png_set_interlace_handling(png);
152 png_read_update_info(png, info);
153
154 desc.width = png_get_image_width(png, info);
155 desc.height = png_get_image_height(png, info);
156 desc.bpp = png_get_bit_depth(png, info) * png_get_channels(png, info);
157 desc.pitch = helpers::calculatePitch(desc.width, desc.bpp); //png_get_rowbytes(png, info);
158 if (desc.pitch < png_get_rowbytes(png, info))
159 {
160 ::printf("(EE) Invalid pitch: %u instead %u.\n", desc.pitch, (uint32_t)png_get_rowbytes(png, info));
161 }
162
163 colorType = png_get_color_type(png, info);
164
165 if (colorType != PNG_COLOR_TYPE_RGB_ALPHA && colorType != PNG_COLOR_TYPE_RGB)
166 {
167 ::printf("(EE) Should't be happened.\n");
168 }
169
170 desc.format = colorType == PNG_COLOR_TYPE_RGB_ALPHA ? GL_RGBA : GL_RGB;
171
172 desc.bitmap.resize(desc.pitch * desc.height);
173 auto out = desc.bitmap.data();
174 std::vector<png_bytep> scanlines(desc.height);
175 for (uint32_t y = 0; y < desc.height; y++)
176 {
177 scanlines[y] = out + desc.pitch * y;
178 }
179 png_read_image(png, scanlines.data());
180
181 uint32_t iccProfileSize = 0;
182 auto iccProfile = locateICCProfile(png, info, iccProfileSize);
183 m_iccProfile.resize(iccProfileSize);
184 if (iccProfile != nullptr && iccProfileSize != 0)
185 {
186 ::memcpy(m_iccProfile.data(), iccProfile, iccProfileSize);
187 }
188
189 png_destroy_read_struct(&png, &info, nullptr);
190
191 return true;
192 }
193
loadPng(sBitmapDescription & desc,cFile & file)194 bool cPngReader::loadPng(sBitmapDescription& desc, cFile& file)
195 {
196 desc.size = file.getSize();
197
198 uint8_t header[MinBytesToTest];
199 if (file.read(&header, MinBytesToTest) != MinBytesToTest
200 && isValid(header, file.getSize()) == false)
201 {
202 ::printf("(EE) Is not recognized as a PNG file.\n");
203 return false;
204 }
205
206 // initialize stuff
207 auto png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
208 if (png == nullptr)
209 {
210 ::printf("(EE) png_create_read_struct failed.\n");
211 return false;
212 }
213
214 auto info = png_create_info_struct(png);
215 if (info == nullptr)
216 {
217 ::printf("(EE) png_create_info_struct failed.\n");
218 return false;
219 }
220
221 if (setjmp(png_jmpbuf(png)) != 0)
222 {
223 ::printf("(EE) Error during init_io.\n");
224 return false;
225 }
226
227 png_init_io(png, (FILE*)file.getHandle());
228 png_set_sig_bytes(png, 8);
229
230 png_read_info(png, info);
231
232 // get real bits per pixel
233 desc.bppImage = png_get_bit_depth(png, info) * png_get_channels(png, info);
234
235 auto colorType = png_get_color_type(png, info);
236 if (colorType == PNG_COLOR_TYPE_PALETTE)
237 {
238 png_set_palette_to_rgb(png);
239 }
240
241 if (png_get_valid(png, info, PNG_INFO_tRNS))
242 {
243 png_set_tRNS_to_alpha(png);
244 }
245 if (png_get_bit_depth(png, info) == 16)
246 {
247 png_set_strip_16(png);
248 }
249 if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA)
250 {
251 png_set_gray_to_rgb(png);
252 }
253
254 // int number_of_passes = png_set_interlace_handling(png);
255 png_read_update_info(png, info);
256
257 desc.width = png_get_image_width(png, info);
258 desc.height = png_get_image_height(png, info);
259 desc.bpp = png_get_bit_depth(png, info) * png_get_channels(png, info);
260 desc.pitch = helpers::calculatePitch(desc.width, desc.bpp); //png_get_rowbytes(png, info);
261 if (desc.pitch < png_get_rowbytes(png, info))
262 {
263 ::printf("(EE) Invalid pitch: %u instead %u.\n", desc.pitch, (uint32_t)png_get_rowbytes(png, info));
264 }
265
266 colorType = png_get_color_type(png, info);
267
268 if (colorType != PNG_COLOR_TYPE_RGB_ALPHA && colorType != PNG_COLOR_TYPE_RGB)
269 {
270 ::printf("(EE) Should't be happened.\n");
271 }
272
273 desc.format = colorType == PNG_COLOR_TYPE_RGB_ALPHA ? GL_RGBA : GL_RGB;
274
275 // read file
276 if (setjmp(png_jmpbuf(png)) != 0)
277 {
278 ::printf("(EE) Error during read_image.\n");
279 return false;
280 }
281
282 desc.bitmap.resize(desc.pitch * desc.height);
283 auto out = desc.bitmap.data();
284 std::vector<png_bytep> scanlines(desc.height);
285 for (uint32_t y = 0; y < desc.height; y++)
286 {
287 scanlines[y] = out + desc.pitch * y;
288 }
289 png_read_image(png, scanlines.data());
290
291 uint32_t iccProfileSize = 0;
292 auto iccProfile = locateICCProfile(png, info, iccProfileSize);
293 m_iccProfile.resize(iccProfileSize);
294 if (iccProfile != nullptr && iccProfileSize != 0)
295 {
296 ::memcpy(m_iccProfile.data(), iccProfile, iccProfileSize);
297 }
298
299 png_destroy_read_struct(&png, &info, nullptr);
300
301 return true;
302 }
303