4806fcb650fef8b68f8f83d375b38281e9b9d3f3
[platform/core/uifw/dali-adaptor.git] / dali / internal / imaging / common / loader-png.cpp
1 /*
2  * Copyright (c) 2017 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 #include <dali/internal/imaging/common/loader-png.h>
19
20 #include <cstring>
21
22 #include <zlib.h>
23 #include <png.h>
24
25 #include <dali/integration-api/debug.h>
26 #include <dali/public-api/images/image.h>
27 #include <dali/internal/legacy/tizen/platform-capabilities.h>
28 #include <dali/devel-api/adaptor-framework/pixel-buffer.h>
29
30 #include <dali/internal/system/common/file-closer.h>
31
32 using namespace Dali::Internal::Platform;
33
34 namespace Dali
35 {
36 namespace TizenPlatform
37 {
38
39 namespace
40 {
41
42 // simple class to enforce clean-up of PNG structures
43 struct auto_png
44 {
45   auto_png(png_structp& _png, png_infop& _info)
46   : png(_png),
47     info(_info)
48   {
49   }
50
51   ~auto_png()
52   {
53     if(NULL != png)
54     {
55       png_destroy_read_struct(&png, &info, NULL);
56     }
57   }
58
59   png_structp& png;
60   png_infop& info;
61 }; // struct auto_png;
62
63 bool LoadPngHeader(FILE *fp, unsigned int &width, unsigned int &height, png_structp &png, png_infop &info)
64 {
65   png_byte header[8] = { 0 };
66
67   // Check header to see if it is a PNG file
68   size_t size = InternalFile::fread(header, 1, 8, fp);
69   if(size != 8)
70   {
71     return false;
72   }
73
74   if(png_sig_cmp(header, 0, 8))
75   {
76     return false;
77   }
78
79   png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
80
81   if(!png)
82   {
83     DALI_LOG_WARNING("Can't create PNG read structure\n");
84     return false;
85   }
86
87   info = png_create_info_struct(png);
88   if(!info)
89   {
90     DALI_LOG_WARNING("png_create_info_struct failed\n");
91     return false;
92   }
93
94   png_set_expand(png);
95
96   if(setjmp(png_jmpbuf(png)))
97   {
98     DALI_LOG_WARNING("error during png_init_io\n");
99     return false;
100   }
101
102   png_init_io(png, fp);
103   png_set_sig_bytes(png, 8);
104
105   // read image info
106   png_read_info(png, info);
107
108   // dimensions
109   width = png_get_image_width(png, info);
110   height = png_get_image_height(png, info);
111
112   return true;
113 }
114
115 } // namespace - anonymous
116
117 bool LoadPngHeader( const Dali::ImageLoader::Input& input, unsigned int& width, unsigned int& height )
118 {
119   png_structp png = NULL;
120   png_infop info = NULL;
121   auto_png autoPng(png, info);
122
123   bool success = LoadPngHeader( input.file, width, height, png, info );
124
125   return success;
126 }
127
128 bool LoadBitmapFromPng( const Dali::ImageLoader::Input& input, Dali::Devel::PixelBuffer& bitmap )
129 {
130   png_structp png = NULL;
131   png_infop info = NULL;
132   auto_png autoPng(png, info);
133
134   /// @todo: consider parameters
135   unsigned int y;
136   unsigned int width, height;
137   png_bytep *rows;
138   unsigned int bpp = 0; // bytes per pixel
139   bool valid = false;
140
141   // Load info from the header
142   if( !LoadPngHeader( input.file, width, height, png, info ) )
143   {
144     return false;
145   }
146
147   Pixel::Format pixelFormat = Pixel::RGBA8888;
148
149   // decide pixel format
150   unsigned int colordepth = png_get_bit_depth(png, info);
151
152   // Ask PNGLib to convert high precision images into something we can use:
153   if (colordepth == 16)
154   {
155     png_set_strip_16(png);
156     colordepth = 8;
157   }
158
159   png_byte colortype = png_get_color_type(png, info);
160
161   if( colortype == PNG_COLOR_TYPE_GRAY ||
162       colortype == PNG_COLOR_TYPE_GRAY_ALPHA )
163   {
164     if( colortype == PNG_COLOR_TYPE_GRAY )
165     {
166       pixelFormat = Pixel::L8;
167       if( png_get_valid(png, info, PNG_INFO_tRNS) )
168       {
169         colortype = PNG_COLOR_TYPE_GRAY_ALPHA;
170         /* expand transparency entry -> alpha channel if present */
171         png_set_tRNS_to_alpha(png);
172         pixelFormat = Pixel::LA88;
173       }
174     }
175     else
176     {
177       pixelFormat = Pixel::LA88;
178     }
179
180     if( colordepth < 8 )
181     {
182       /* expand gray (w/reduced bits) -> 8-bit RGB if necessary */
183       png_set_expand_gray_1_2_4_to_8(png);
184       /* pack all pixels to byte boundaries */
185       png_set_packing(png);
186     }
187     valid = true;
188   }
189   else if(colortype == PNG_COLOR_TYPE_RGB )
190   {
191     switch(colordepth)
192     {
193       case 8:
194       {
195         pixelFormat = Pixel::RGB888;
196         valid = true;
197         break;
198       }
199       case 5:      /// @todo is this correct for RGB16 5-6-5 ?
200       {
201         pixelFormat = Pixel::RGB565;
202         valid = true;
203         break;
204       }
205       default:
206       {
207         break;
208       }
209     }
210   }
211   else if(colortype == PNG_COLOR_TYPE_RGBA)
212   {
213     switch(colordepth)
214     {
215       case 8:
216       {
217         pixelFormat = Pixel::RGBA8888;
218         valid = true;
219         break;
220       }
221       default:
222       {
223         break;
224       }
225     }
226   }
227   else if(colortype == PNG_COLOR_TYPE_PALETTE)
228   {
229     switch(colordepth)
230     {
231       case 1:
232       {
233         pixelFormat = Pixel::LA88;
234         valid = true;
235         break;
236       }
237
238       case 2:
239       case 4:
240       case 8:
241       {
242         /* Expand paletted or RGB images with transparency to full alpha channels
243          * so the data will be available as RGBA quartets. PNG_INFO_tRNS = 0x10
244          */
245         if(png_get_valid(png, info, PNG_INFO_tRNS) == 0x10)
246         {
247           pixelFormat = Pixel::RGBA8888;
248           valid = true;
249         }
250         else
251         {
252           pixelFormat = Pixel::RGB888;
253           png_set_packing(png);
254           png_set_packswap(png);
255           png_set_palette_to_rgb(png);
256           valid = true;
257         }
258         break;
259       }
260       default:
261       {
262         break;
263       }
264     }
265   }
266
267   if( !valid )
268   {
269     DALI_LOG_WARNING( "Unsupported png format\n" );
270     return false;
271   }
272
273   // bytes per pixel
274   bpp = Pixel::GetBytesPerPixel(pixelFormat);
275
276   png_read_update_info(png, info);
277
278   if(setjmp(png_jmpbuf(png)))
279   {
280     DALI_LOG_WARNING("error during png_read_image\n");
281     return false;
282   }
283
284   unsigned int rowBytes = png_get_rowbytes(png, info);
285
286   unsigned int bufferWidth   = GetTextureDimension(width);
287   unsigned int bufferHeight  = GetTextureDimension(height);
288   unsigned int stride        = bufferWidth*bpp;
289
290   // not sure if this ever happens
291   if( rowBytes > stride )
292   {
293     stride = GetTextureDimension(rowBytes);
294
295     bpp = stride / bufferWidth;
296     switch(bpp)
297     {
298       case 3:
299         pixelFormat = Pixel::RGB888;
300         break;
301       case 4:
302         pixelFormat = Pixel::RGBA8888;
303         break;
304       default:
305         break;
306     }
307
308   }
309
310   // decode the whole image into bitmap buffer
311   auto pixels = (bitmap = Dali::Devel::PixelBuffer::New(bufferWidth, bufferHeight, pixelFormat)).GetBuffer();
312
313   DALI_ASSERT_DEBUG(pixels);
314   rows = reinterpret_cast< png_bytep* >( malloc(sizeof(png_bytep) * height) );
315   for(y=0; y<height; y++)
316   {
317     rows[y] = pixels + y * stride;
318   }
319
320   // decode image
321   png_read_image(png, rows);
322
323   free(rows);
324
325   return true;
326 }
327
328 // simple class to enforce clean-up of PNG structures
329 struct AutoPngWrite
330 {
331   AutoPngWrite(png_structp& _png, png_infop& _info)
332   : png(_png),
333     info(_info)
334   {
335   }
336
337   ~AutoPngWrite()
338   {
339     if(NULL != png)
340     {
341       png_destroy_write_struct(&png, &info);
342     }
343   }
344
345   png_structp& png;
346   png_infop& info;
347 }; // struct AutoPngWrite;
348
349 namespace
350 {
351   // Custom libpng write callbacks that buffer to a vector instead of a file:
352
353   /**
354    * extern "C" linkage is used because this is a callback that we pass to a C
355    * library which is part of the underlying platform and so potentially compiled
356    * as C rather than C++.
357    * @see http://stackoverflow.com/a/2594222
358    * */
359   extern "C" void WriteData(png_structp png_ptr, png_bytep data, png_size_t length)
360   {
361     DALI_ASSERT_DEBUG(png_ptr && data);
362     if(!png_ptr || !data)
363     {
364       return;
365     }
366     // Make sure we don't try to propagate a C++ exception up the call stack of a pure C library:
367     try
368     {
369       // Recover our buffer for writing into:
370       Vector<unsigned char>* const encoded_img = static_cast< Vector<unsigned char>* >( png_get_io_ptr(png_ptr) );
371       if(encoded_img)
372       {
373         const Vector<unsigned char>::SizeType bufferSize = encoded_img->Count();
374         encoded_img->Resize( bufferSize + length ); //< Can throw OOM.
375         unsigned char* const bufferBack = encoded_img->Begin() + bufferSize;
376         memcpy(bufferBack, data, length);
377       }
378       else
379       {
380         DALI_LOG_ERROR("PNG buffer for write to memory was passed from libpng as null.\n");
381       }
382     }
383     catch(...)
384     {
385       DALI_LOG_ERROR("C++ Exception caught\n");
386     }
387   }
388
389   /** Override the flush with a NOP to prevent libpng trying cstdlib file io. */
390   extern "C" void FlushData(png_structp png_ptr)
391   {
392 #ifdef DEBUG_ENABLED
393     Debug::LogMessage(Debug::DebugInfo, "PNG Flush");
394 #endif // DEBUG_ENABLED
395   }
396 }
397
398 /**
399  * Potential improvements:
400  * 1. Detect <= 256 colours and write in palette mode.
401  * 2. Detect grayscale (will early-out quickly for colour images).
402  * 3. Store colour space / gamma correction info related to the device screen?
403  *    http://www.libpng.org/pub/png/book/chapter10.html
404  * 4. Refactor with callers to write straight through to disk and save keeping a big buffer around.
405  * 5. Prealloc buffer (reserve) to input size / <A number greater than 2 (expexcted few realloc but without using lots of memory) | 1 (expected zero reallocs but using a lot of memory)>.
406  * 6. Set the modification time with png_set_tIME(png_ptr, info_ptr, mod_time);
407  * 7. If caller asks for no compression, bypass libpng and blat raw data to
408  *    disk, topped and tailed with header/tail blocks.
409  */
410 bool EncodeToPng( const unsigned char* const pixelBuffer, Vector<unsigned char>& encodedPixels, std::size_t width, std::size_t height, Pixel::Format pixelFormat )
411 {
412   // Translate pixel format enum:
413   int pngPixelFormat = -1;
414   unsigned pixelBytes = 0;
415   bool rgbaOrder = true;
416
417   // Account for RGB versus BGR and presence of alpha in input pixels:
418   switch( pixelFormat )
419   {
420     case Pixel::RGB888:
421     {
422       pngPixelFormat = PNG_COLOR_TYPE_RGB;
423       pixelBytes = 3;
424       break;
425     }
426     case Pixel::BGRA8888:
427     {
428       rgbaOrder = false;
429       ///! No break: fall through:
430     }
431     case Pixel::RGBA8888:
432     {
433       pngPixelFormat = PNG_COLOR_TYPE_RGB_ALPHA;
434       pixelBytes = 4;
435       break;
436     }
437     default:
438     {
439       DALI_LOG_ERROR( "Unsupported pixel format for encoding to PNG.\n" );
440       return false;
441     }
442   }
443
444   const int interlace = PNG_INTERLACE_NONE;
445
446   png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
447   if(!png_ptr)
448   {
449     return false;
450   }
451   /* Allocate/initialize the image information data.  REQUIRED */
452   png_infop info_ptr = png_create_info_struct( png_ptr );
453   if(!info_ptr)
454   {
455     png_destroy_write_struct(&png_ptr, NULL);
456     return false;
457   }
458
459   /* Set error handling.  REQUIRED if you aren't supplying your own
460    * error handling functions in the png_create_write_struct() call.
461    */
462   if(setjmp(png_jmpbuf(png_ptr)))
463   {
464     png_destroy_write_struct(&png_ptr, &info_ptr);
465     return false;
466   }
467
468   // Since we are going to write to memory instead of a file, lets provide
469   // libpng with a custom write function and ask it to pass back our
470   // Vector buffer each time it calls back to flush data to "file":
471   png_set_write_fn(png_ptr, &encodedPixels, WriteData, FlushData);
472
473   // png_set_compression_level( png_ptr, Z_BEST_COMPRESSION);
474   png_set_compression_level(png_ptr, Z_BEST_SPEED);
475   // png_set_compression_level( png_ptr, Z_NO_COMPRESSION); //! We could just generate png directly without libpng in this case.
476
477   // Explicitly limit the number of filters used per scanline to speed us up:
478   // png_set_filter(png_ptr, 0, PNG_FILTER_NONE); ///!ToDo: Try this once baseline profile is in place.
479        // PNG_FILTER_SUB   |
480        // PNG_FILTER_UP    |
481        // PNG_FILTER_AVE   |
482        // PNG_FILTER_PAETH |
483        // PNG_ALL_FILTERS);
484   // Play with Zlib parameters in optimisation phase:
485     // png_set_compression_mem_level(png_ptr, 8);
486     // png_set_compression_strategy(png_ptr,
487         // Z_DEFAULT_STRATEGY);
488     // png_set_compression_window_bits(png_ptr, 15);
489     // png_set_compression_method(png_ptr, 8);
490     // png_set_compression_buffer_size(png_ptr, 8192)
491
492   // Let lib_png know if the pixel bytes are in BGR(A) order:
493   if(!rgbaOrder)
494   {
495     png_set_bgr( png_ptr );
496   }
497
498   // Set the image information:
499   png_set_IHDR(png_ptr, info_ptr, width, height, 8,
500      pngPixelFormat, interlace,
501      PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
502
503   // Start to output the PNG data to our buffer:
504   png_write_info(png_ptr, info_ptr);
505
506   // Walk the rows:
507   const unsigned row_step = width * pixelBytes;
508   png_bytep row_ptr = const_cast<png_bytep>(pixelBuffer);
509   const png_bytep row_end = row_ptr + height * row_step;
510   for(; row_ptr < row_end; row_ptr += row_step)
511   {
512     png_write_row(png_ptr, row_ptr);
513   }
514
515   /* It is REQUIRED to call this to finish writing the rest of the file */
516   png_write_end(png_ptr, info_ptr);
517   /* Clean up after the write, and free any memory allocated */
518   png_destroy_write_struct(&png_ptr, &info_ptr);
519   return true;
520 }
521
522 } // namespace TizenPlatform
523
524 } // namespace Dali