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