[dali_1.3.28] Merge branch 'devel/master'
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / visuals / npatch-loader.cpp
1  /*
2  * Copyright (c) 2016 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 // CLASS HEADER
19 #include <dali-toolkit/internal/visuals/npatch-loader.h>
20
21 // EXTERNAL HEADER
22 #include <dali/devel-api/adaptor-framework/image-loading.h>
23 #include <dali/devel-api/adaptor-framework/pixel-buffer.h>
24 #include <dali/devel-api/common/hash.h>
25 #include <dali/integration-api/debug.h>
26
27 namespace Dali
28 {
29
30 namespace Toolkit
31 {
32
33 namespace Internal
34 {
35
36 namespace NPatchBuffer
37 {
38
39 void GetRedOffsetAndMask( Dali::Pixel::Format pixelFormat, int& byteOffset, int& bitMask )
40 {
41   switch( pixelFormat )
42   {
43     case Dali::Pixel::A8:
44     case Dali::Pixel::L8:
45     case Dali::Pixel::LA88:
46     {
47       byteOffset = 0;
48       bitMask = 0;
49       break;
50     }
51     case Dali::Pixel::RGB888:
52     case Dali::Pixel::RGB8888:
53     case Dali::Pixel::RGBA8888:
54     {
55       byteOffset = 0;
56       bitMask = 0xFF;
57       break;
58     }
59     case Dali::Pixel::BGR8888:
60     case Dali::Pixel::BGRA8888:
61     {
62       byteOffset = 2;
63       bitMask = 0xff;
64       break;
65     }
66     case Dali::Pixel::RGB565:
67     {
68       byteOffset = 0;
69       bitMask = 0xf8;
70       break;
71     }
72     case Dali::Pixel::BGR565:
73     {
74       byteOffset = 1;
75       bitMask = 0x1f;
76       break;
77     }
78     case Dali::Pixel::RGBA4444:
79     {
80       byteOffset = 0;
81       bitMask = 0xf0;
82       break;
83     }
84     case Dali::Pixel::BGRA4444:
85     {
86       byteOffset = 1;
87       bitMask = 0xf0;
88       break;
89     }
90     case Dali::Pixel::RGBA5551:
91     {
92       byteOffset = 0;
93       bitMask = 0xf8;
94       break;
95     }
96     case Dali::Pixel::BGRA5551:
97     {
98       byteOffset = 1;
99       bitMask = 0x1e;
100       break;
101     }
102     case Dali::Pixel::INVALID:
103     case Dali::Pixel::COMPRESSED_R11_EAC:
104     case Dali::Pixel::COMPRESSED_SIGNED_R11_EAC:
105     case Dali::Pixel::COMPRESSED_RG11_EAC:
106     case Dali::Pixel::COMPRESSED_SIGNED_RG11_EAC:
107     case Dali::Pixel::COMPRESSED_RGB8_ETC2:
108     case Dali::Pixel::COMPRESSED_SRGB8_ETC2:
109     case Dali::Pixel::COMPRESSED_RGB8_ETC1:
110     case Dali::Pixel::COMPRESSED_RGB_PVRTC_4BPPV1:
111     case Dali::Pixel::COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
112     case Dali::Pixel::COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
113     case Dali::Pixel::COMPRESSED_RGBA8_ETC2_EAC:
114     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
115     case Dali::Pixel::COMPRESSED_RGBA_ASTC_4x4_KHR:
116     case Dali::Pixel::COMPRESSED_RGBA_ASTC_5x4_KHR:
117     case Dali::Pixel::COMPRESSED_RGBA_ASTC_5x5_KHR:
118     case Dali::Pixel::COMPRESSED_RGBA_ASTC_6x5_KHR:
119     case Dali::Pixel::COMPRESSED_RGBA_ASTC_6x6_KHR:
120     case Dali::Pixel::COMPRESSED_RGBA_ASTC_8x5_KHR:
121     case Dali::Pixel::COMPRESSED_RGBA_ASTC_8x6_KHR:
122     case Dali::Pixel::COMPRESSED_RGBA_ASTC_8x8_KHR:
123     case Dali::Pixel::COMPRESSED_RGBA_ASTC_10x5_KHR:
124     case Dali::Pixel::COMPRESSED_RGBA_ASTC_10x6_KHR:
125     case Dali::Pixel::COMPRESSED_RGBA_ASTC_10x8_KHR:
126     case Dali::Pixel::COMPRESSED_RGBA_ASTC_10x10_KHR:
127     case Dali::Pixel::COMPRESSED_RGBA_ASTC_12x10_KHR:
128     case Dali::Pixel::COMPRESSED_RGBA_ASTC_12x12_KHR:
129     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR:
130     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR:
131     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR:
132     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR:
133     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR:
134     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR:
135     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR:
136     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR:
137     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR:
138     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR:
139     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR:
140     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR:
141     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR:
142     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR:
143     {
144       DALI_LOG_ERROR("Pixel formats for compressed images are not compatible with simple masking-out of per-pixel alpha.\n");
145       byteOffset=0;
146       bitMask=0;
147       break;
148     }
149     case Dali::Pixel::RGB16F:
150     case Dali::Pixel::RGB32F:
151     {
152       DALI_LOG_ERROR("Pixel format not compatible.\n");
153       byteOffset=0;
154       bitMask=0;
155       break;
156     }
157   }
158 }
159
160 Uint16Pair ParseRange( unsigned int& index, unsigned int width, unsigned char* pixel, unsigned int pixelStride, int testByte, int testBits, int testValue )
161 {
162   unsigned int start = 0xFFFF;
163   for( ; index < width; ++index, pixel += pixelStride )
164   {
165     if( ( pixel[ testByte ] & testBits ) == testValue )
166     {
167         start = index;
168         ++index;
169         pixel += pixelStride;
170         break;
171     }
172   }
173
174   unsigned int end = width;
175   for( ; index < width; ++index, pixel += pixelStride )
176   {
177     if( ( pixel[ testByte ] & testBits ) != testValue )
178     {
179         end = index;
180         ++index;
181         pixel += pixelStride;
182         break;
183     }
184   }
185
186   return Uint16Pair( start, end );
187 }
188
189 void ParseBorders( Devel::PixelBuffer& pixelBuffer, NPatchLoader::Data* data  )
190 {
191   data->stretchPixelsX.Clear();
192   data->stretchPixelsY.Clear();
193
194   Pixel::Format pixelFormat = pixelBuffer.GetPixelFormat();
195
196   int alphaByte = 0;
197   int alphaBits = 0;
198   Pixel::GetAlphaOffsetAndMask( pixelFormat, alphaByte, alphaBits );
199
200   int testByte = alphaByte;
201   int testBits = alphaBits;
202   int testValue = alphaBits; // Opaque == stretch
203   if( !alphaBits )
204   {
205     GetRedOffsetAndMask( pixelFormat, testByte, testBits );
206     testValue = 0;           // Black == stretch
207   }
208
209   unsigned int bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
210   unsigned int width = pixelBuffer.GetWidth();
211   unsigned int height = pixelBuffer.GetHeight();
212   unsigned char* srcPixels = pixelBuffer.GetBuffer();
213   unsigned int srcStride = width * bytesPerPixel;
214
215   // TOP
216   unsigned char* top = srcPixels + bytesPerPixel;
217   unsigned int index = 0;
218
219   for( ; index < width - 2; )
220   {
221     Uint16Pair range = ParseRange( index, width - 2, top, bytesPerPixel, testByte, testBits, testValue );
222     if( range.GetX() != 0xFFFF )
223     {
224       data->stretchPixelsX.PushBack( range );
225     }
226   }
227
228   // LEFT
229   unsigned char* left  = srcPixels + srcStride;
230   index = 0;
231   for( ; index < height - 2; )
232   {
233     Uint16Pair range = ParseRange( index, height - 2, left, srcStride, testByte, testBits, testValue );
234     if( range.GetX() != 0xFFFF )
235     {
236       data->stretchPixelsY.PushBack( range );
237     }
238   }
239
240   // If there are no stretch pixels then make the entire image stretchable
241   if( data->stretchPixelsX.Size() == 0 )
242   {
243     data->stretchPixelsX.PushBack( Uint16Pair( 0, width - 2 ) );
244   }
245   if( data->stretchPixelsY.Size() == 0 )
246   {
247     data->stretchPixelsY.PushBack( Uint16Pair( 0, height - 2 ) );
248   }
249 }
250
251 } // namespace NPatchBuffer
252
253 NPatchLoader::NPatchLoader()
254 {
255 }
256
257 NPatchLoader::~NPatchLoader()
258 {
259 }
260
261 std::size_t NPatchLoader::Load( const std::string& url, const Rect< int >& border, bool& preMultiplyOnLoad )
262 {
263   std::size_t hash = CalculateHash( url );
264   OwnerContainer< Data* >::SizeType index = UNINITIALIZED_ID;
265   const OwnerContainer< Data* >::SizeType count = mCache.Count();
266   int cachedIndex = -1;
267
268   for( ; index < count; ++index )
269   {
270     if( mCache[ index ]->hash == hash )
271     {
272       // hash match, check url as well in case of hash collision
273       if( mCache[ index ]->url == url )
274       {
275         // Use cached data
276         if( mCache[ index ]->border == border )
277         {
278           return index+1u; // valid indices are from 1 onwards
279         }
280         else
281         {
282           cachedIndex = index;
283         }
284       }
285     }
286   }
287
288   if( cachedIndex != -1 )
289   {
290     // Same url but border is different - use the existing texture
291     Data* data = new Data();
292     data->hash = hash;
293     data->url = url;
294     data->croppedWidth = mCache[ cachedIndex ]->croppedWidth;
295     data->croppedHeight = mCache[ cachedIndex ]->croppedHeight;
296
297     data->textureSet = mCache[ cachedIndex ]->textureSet;
298
299     StretchRanges stretchRangesX;
300     stretchRangesX.PushBack( Uint16Pair( border.left, ( (data->croppedWidth >= static_cast< unsigned int >( border.right )) ? data->croppedWidth - border.right : 0 ) ) );
301
302     StretchRanges stretchRangesY;
303     stretchRangesY.PushBack( Uint16Pair( border.top, ( (data->croppedHeight >= static_cast< unsigned int >( border.bottom )) ? data->croppedHeight - border.bottom : 0 ) ) );
304
305     data->stretchPixelsX = stretchRangesX;
306     data->stretchPixelsY = stretchRangesY;
307     data->border = border;
308
309     mCache.PushBack( data );
310
311     return mCache.Count(); // valid ids start from 1u
312   }
313
314   // got to the end so no match, decode N patch and append new item to cache
315   Devel::PixelBuffer pixelBuffer = Dali::LoadImageFromFile( url, ImageDimensions(), FittingMode::DEFAULT, SamplingMode::BOX_THEN_LINEAR, true );
316   if( pixelBuffer )
317   {
318     Data* data = new Data();
319     data->hash = hash;
320     data->url = url;
321
322     if( border == Rect< int >( 0, 0, 0, 0 ) )
323     {
324       NPatchBuffer::ParseBorders( pixelBuffer, data );
325
326       data->border = Rect< int >( 0, 0, 0, 0 );
327
328       // Crop the image
329       pixelBuffer.Crop( 1, 1, pixelBuffer.GetWidth() - 2, pixelBuffer.GetHeight() - 2 );
330     }
331     else
332     {
333       data->stretchPixelsX.PushBack( Uint16Pair( border.left, ( (pixelBuffer.GetWidth() >= static_cast< unsigned int >( border.right )) ? pixelBuffer.GetWidth() - border.right : 0 ) ) );
334       data->stretchPixelsY.PushBack( Uint16Pair( border.top, ( (pixelBuffer.GetHeight() >= static_cast< unsigned int >( border.bottom )) ? pixelBuffer.GetHeight() - border.bottom : 0 ) ) );
335       data->border = border;
336     }
337
338     data->croppedWidth = pixelBuffer.GetWidth();
339     data->croppedHeight = pixelBuffer.GetHeight();
340
341     if( preMultiplyOnLoad && Pixel::HasAlpha( pixelBuffer.GetPixelFormat() ) )
342     {
343       pixelBuffer.MultiplyColorByAlpha();
344     }
345     else
346     {
347       preMultiplyOnLoad = false;
348     }
349
350     PixelData pixels = Devel::PixelBuffer::Convert( pixelBuffer ); // takes ownership of buffer
351
352     Texture texture = Texture::New( TextureType::TEXTURE_2D, pixels.GetPixelFormat(), pixels.GetWidth(), pixels.GetHeight() );
353     texture.Upload( pixels );
354
355     data->textureSet = TextureSet::New();
356     data->textureSet.SetTexture( 0u, texture );
357
358     mCache.PushBack( data );
359
360     return mCache.Count(); // valid ids start from 1u
361   }
362
363   return 0u;
364 }
365
366 bool NPatchLoader::GetNPatchData( std::size_t id, const Data*& data )
367 {
368   if( ( id > UNINITIALIZED_ID )&&( id <= mCache.Count() ) )
369   {
370     data = mCache[ id - 1u ]; // id's start from 1u
371     return true;
372   }
373   data = NULL;
374   return false;
375 }
376
377 } // namespace Internal
378
379 } // namespace Toolkit
380
381 } // namespace Dali