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