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