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