Add Pixel format for depth and stencil
[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     case Dali::Pixel::DEPTH_UNSIGNED_INT:
151     case Dali::Pixel::DEPTH_FLOAT:
152     case Dali::Pixel::DEPTH_STENCIL:
153     {
154       DALI_LOG_ERROR("Pixel format not compatible.\n");
155       byteOffset=0;
156       bitMask=0;
157       break;
158     }
159   }
160 }
161
162 Uint16Pair ParseRange( unsigned int& index, unsigned int width, unsigned char*& pixel, unsigned int pixelStride, int testByte, int testBits, int testValue )
163 {
164   unsigned int start = 0xFFFF;
165   for( ; index < width; ++index, pixel += pixelStride )
166   {
167     if( ( pixel[ testByte ] & testBits ) == testValue )
168     {
169         start = index;
170         ++index;
171         pixel += pixelStride;
172         break;
173     }
174   }
175
176   unsigned int end = width;
177   for( ; index < width; ++index, pixel += pixelStride )
178   {
179     if( ( pixel[ testByte ] & testBits ) != testValue )
180     {
181         end = index;
182         ++index;
183         pixel += pixelStride;
184         break;
185     }
186   }
187
188   return Uint16Pair( start, end );
189 }
190
191 void ParseBorders( Devel::PixelBuffer& pixelBuffer, NPatchLoader::Data* data  )
192 {
193   data->stretchPixelsX.Clear();
194   data->stretchPixelsY.Clear();
195
196   Pixel::Format pixelFormat = pixelBuffer.GetPixelFormat();
197
198   int alphaByte = 0;
199   int alphaBits = 0;
200   Pixel::GetAlphaOffsetAndMask( pixelFormat, alphaByte, alphaBits );
201
202   int testByte = alphaByte;
203   int testBits = alphaBits;
204   int testValue = alphaBits; // Opaque == stretch
205   if( !alphaBits )
206   {
207     GetRedOffsetAndMask( pixelFormat, testByte, testBits );
208     testValue = 0;           // Black == stretch
209   }
210
211   unsigned int bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
212   unsigned int width = pixelBuffer.GetWidth();
213   unsigned int height = pixelBuffer.GetHeight();
214   unsigned char* srcPixels = pixelBuffer.GetBuffer();
215   unsigned int srcStride = width * bytesPerPixel;
216
217   // TOP
218   unsigned char* top = srcPixels + bytesPerPixel;
219   unsigned int index = 0;
220
221   for( ; index < width - 2; )
222   {
223     Uint16Pair range = ParseRange( index, width - 2, top, bytesPerPixel, testByte, testBits, testValue );
224     if( range.GetX() != 0xFFFF )
225     {
226       data->stretchPixelsX.PushBack( range );
227     }
228   }
229
230   // LEFT
231   unsigned char* left  = srcPixels + srcStride;
232   index = 0;
233   for( ; index < height - 2; )
234   {
235     Uint16Pair range = ParseRange( index, height - 2, left, srcStride, testByte, testBits, testValue );
236     if( range.GetX() != 0xFFFF )
237     {
238       data->stretchPixelsY.PushBack( range );
239     }
240   }
241
242   // If there are no stretch pixels then make the entire image stretchable
243   if( data->stretchPixelsX.Size() == 0 )
244   {
245     data->stretchPixelsX.PushBack( Uint16Pair( 0, width - 2 ) );
246   }
247   if( data->stretchPixelsY.Size() == 0 )
248   {
249     data->stretchPixelsY.PushBack( Uint16Pair( 0, height - 2 ) );
250   }
251 }
252
253 void SetLoadedNPatchData( NPatchLoader::Data* data, Devel::PixelBuffer& pixelBuffer )
254 {
255   if( data->border == Rect< int >( 0, 0, 0, 0 ) )
256   {
257     NPatchBuffer::ParseBorders( pixelBuffer, data );
258
259     // Crop the image
260     pixelBuffer.Crop( 1, 1, pixelBuffer.GetWidth() - 2, pixelBuffer.GetHeight() - 2 );
261   }
262   else
263   {
264     data->stretchPixelsX.PushBack( Uint16Pair( data->border.left, ( (pixelBuffer.GetWidth() >= static_cast< unsigned int >( data->border.right )) ? pixelBuffer.GetWidth() - data->border.right : 0 ) ) );
265     data->stretchPixelsY.PushBack( Uint16Pair( data->border.top, ( (pixelBuffer.GetHeight() >= static_cast< unsigned int >( data->border.bottom )) ? pixelBuffer.GetHeight() - data->border.bottom : 0 ) ) );
266   }
267
268   data->croppedWidth = pixelBuffer.GetWidth();
269   data->croppedHeight = pixelBuffer.GetHeight();
270
271   PixelData pixels = Devel::PixelBuffer::Convert( pixelBuffer ); // takes ownership of buffer
272
273   Texture texture = Texture::New( TextureType::TEXTURE_2D, pixels.GetPixelFormat(), pixels.GetWidth(), pixels.GetHeight() );
274   texture.Upload( pixels );
275
276   data->textureSet = TextureSet::New();
277   data->textureSet.SetTexture( 0u, texture );
278
279   data->loadCompleted = true;
280 }
281
282 } // namespace NPatchBuffer
283
284 NPatchLoader::NPatchLoader()
285 {
286 }
287
288 NPatchLoader::~NPatchLoader()
289 {
290 }
291
292 std::size_t NPatchLoader::Load( TextureManager& textureManager, TextureUploadObserver* textureObserver, const std::string& url, const Rect< int >& border, bool& preMultiplyOnLoad, bool synchronousLoading )
293 {
294   std::size_t hash = CalculateHash( url );
295   OwnerContainer< Data* >::SizeType index = UNINITIALIZED_ID;
296   const OwnerContainer< Data* >::SizeType count = mCache.Count();
297   int cachedIndex = -1;
298   Data* data;
299
300   for( ; index < count; ++index )
301   {
302     if( mCache[ index ]->hash == hash )
303     {
304       // hash match, check url as well in case of hash collision
305       if( mCache[ index ]->url == url )
306       {
307         // Use cached data
308         if( mCache[ index ]->border == border )
309         {
310           if( mCache[ index ]->loadCompleted )
311           {
312             return index + 1u; // valid indices are from 1 onwards
313           }
314           data = mCache[ index ];
315           cachedIndex = index + 1u; // valid indices are from 1 onwards
316           break;
317         }
318         else
319         {
320           if( mCache[ index ]->loadCompleted )
321           {
322             // Same url but border is different - use the existing texture
323             Data* data = new Data();
324             data->hash = hash;
325             data->url = url;
326             data->croppedWidth = mCache[ index ]->croppedWidth;
327             data->croppedHeight = mCache[ index ]->croppedHeight;
328
329             data->textureSet = mCache[ index ]->textureSet;
330
331             StretchRanges stretchRangesX;
332             stretchRangesX.PushBack( Uint16Pair( border.left, ( (data->croppedWidth >= static_cast< unsigned int >( border.right )) ? data->croppedWidth - border.right : 0 ) ) );
333
334             StretchRanges stretchRangesY;
335             stretchRangesY.PushBack( Uint16Pair( border.top, ( (data->croppedHeight >= static_cast< unsigned int >( border.bottom )) ? data->croppedHeight - border.bottom : 0 ) ) );
336
337             data->stretchPixelsX = stretchRangesX;
338             data->stretchPixelsY = stretchRangesY;
339             data->border = border;
340
341             data->loadCompleted = mCache[ index ]->loadCompleted;
342
343             mCache.PushBack( data );
344
345             return mCache.Count(); // valid ids start from 1u
346           }
347         }
348       }
349     }
350   }
351
352   if( cachedIndex == -1 )
353   {
354     data = new Data();
355     data->loadCompleted = false;
356     data->hash = hash;
357     data->url = url;
358     data->border = border;
359
360     mCache.PushBack( data );
361
362     cachedIndex = mCache.Count();
363   }
364
365   auto preMultiplyOnLoading = preMultiplyOnLoad ? TextureManager::MultiplyOnLoad::MULTIPLY_ON_LOAD
366                                                 : TextureManager::MultiplyOnLoad::LOAD_WITHOUT_MULTIPLY;
367   Devel::PixelBuffer pixelBuffer = textureManager.LoadPixelBuffer( url, Dali::ImageDimensions(), FittingMode::DEFAULT,
368                                                                    SamplingMode::BOX_THEN_LINEAR, synchronousLoading,
369                                                                    textureObserver, true, preMultiplyOnLoading );
370
371   if( pixelBuffer )
372   {
373     NPatchBuffer::SetLoadedNPatchData( data, pixelBuffer );
374     preMultiplyOnLoad = ( preMultiplyOnLoading == TextureManager::MultiplyOnLoad::MULTIPLY_ON_LOAD ) ? true : false;
375   }
376
377   return cachedIndex;
378 }
379
380 void NPatchLoader::SetNPatchData( std::size_t id, Devel::PixelBuffer& pixelBuffer )
381 {
382   Data* data;
383   data = mCache[ id - 1u ];
384
385   if( !data->loadCompleted )
386   {
387     NPatchBuffer::SetLoadedNPatchData( data, pixelBuffer );
388   }
389 }
390
391 bool NPatchLoader::GetNPatchData( std::size_t id, const Data*& data )
392 {
393   if( ( id > UNINITIALIZED_ID )&&( id <= mCache.Count() ) )
394   {
395     data = mCache[ id - 1u ]; // id's start from 1u
396     return true;
397   }
398   data = NULL;
399   return false;
400 }
401
402 } // namespace Internal
403
404 } // namespace Toolkit
405
406 } // namespace Dali