7ff68592d7499434f78c954b75fc41bce805fdc8
[platform/core/uifw/dali-core.git] / dali / internal / event / images / nine-patch-image-impl.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/internal/event/images/nine-patch-image-impl.h>
20
21 // EXTERNAL INCLUDES
22 #include <cstring> // for memcmp
23
24 // INTERNAL INCLUDES
25 #include <dali/public-api/object/type-registry.h>
26 #include <dali/integration-api/bitmap.h>
27 #include <dali/internal/event/common/thread-local-storage.h>
28 #include <dali/internal/event/resources/resource-client.h>
29 #include <dali/internal/update/manager/update-manager.h>
30 #include <dali/internal/event/images/image-factory.h>
31 #include <dali/integration-api/platform-abstraction.h>
32 #include <dali/integration-api/resource-types.h>
33 #include <dali/integration-api/resource-cache.h>
34
35
36 namespace
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
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
79     case Dali::Pixel::RGBA4444:
80     {
81       byteOffset=0;
82       bitMask=0xf0;
83       break;
84     }
85     case Dali::Pixel::BGRA4444:
86     {
87       byteOffset=1;
88       bitMask=0xf0;
89       break;
90     }
91
92     case Dali::Pixel::RGBA5551:
93     {
94       byteOffset=0;
95       bitMask=0xf8;
96       break;
97     }
98
99     case Dali::Pixel::BGRA5551:
100     {
101       byteOffset=1;
102       bitMask=0x1e;
103       break;
104     }
105
106     case Dali::Pixel::INVALID:
107     case Dali::Pixel::COMPRESSED_R11_EAC:
108     case Dali::Pixel::COMPRESSED_SIGNED_R11_EAC:
109     case Dali::Pixel::COMPRESSED_RG11_EAC:
110     case Dali::Pixel::COMPRESSED_SIGNED_RG11_EAC:
111     case Dali::Pixel::COMPRESSED_RGB8_ETC2:
112     case Dali::Pixel::COMPRESSED_SRGB8_ETC2:
113     case Dali::Pixel::COMPRESSED_RGB8_ETC1:
114     case Dali::Pixel::COMPRESSED_RGB_PVRTC_4BPPV1:
115     case Dali::Pixel::COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
116     case Dali::Pixel::COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
117     case Dali::Pixel::COMPRESSED_RGBA8_ETC2_EAC:
118     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
119     case Dali::Pixel::COMPRESSED_RGBA_ASTC_4x4_KHR:
120     case Dali::Pixel::COMPRESSED_RGBA_ASTC_5x4_KHR:
121     case Dali::Pixel::COMPRESSED_RGBA_ASTC_5x5_KHR:
122     case Dali::Pixel::COMPRESSED_RGBA_ASTC_6x5_KHR:
123     case Dali::Pixel::COMPRESSED_RGBA_ASTC_6x6_KHR:
124     case Dali::Pixel::COMPRESSED_RGBA_ASTC_8x5_KHR:
125     case Dali::Pixel::COMPRESSED_RGBA_ASTC_8x6_KHR:
126     case Dali::Pixel::COMPRESSED_RGBA_ASTC_8x8_KHR:
127     case Dali::Pixel::COMPRESSED_RGBA_ASTC_10x5_KHR:
128     case Dali::Pixel::COMPRESSED_RGBA_ASTC_10x6_KHR:
129     case Dali::Pixel::COMPRESSED_RGBA_ASTC_10x8_KHR:
130     case Dali::Pixel::COMPRESSED_RGBA_ASTC_10x10_KHR:
131     case Dali::Pixel::COMPRESSED_RGBA_ASTC_12x10_KHR:
132     case Dali::Pixel::COMPRESSED_RGBA_ASTC_12x12_KHR:
133     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR:
134     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR:
135     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR:
136     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR:
137     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR:
138     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR:
139     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR:
140     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR:
141     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR:
142     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR:
143     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR:
144     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR:
145     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR:
146     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR:
147     {
148       DALI_LOG_ERROR("Pixel formats for compressed images are not compatible with simple masking-out of per-pixel alpha.\n");
149       byteOffset=0;
150       bitMask=0;
151       break;
152     }
153   }
154 }
155
156 } // anonymous namespace
157
158
159 namespace Dali
160 {
161 namespace Internal
162 {
163
164 namespace
165 {
166 TypeRegistration mType( typeid( Dali::NinePatchImage ), typeid( Dali::Image ), NULL );
167 } // unnamed namespace
168
169 NinePatchImagePtr NinePatchImage::New( const std::string& filename, ReleasePolicy releasePol )
170 {
171   Internal::NinePatchImagePtr internal( new NinePatchImage( filename, releasePol ) );
172   internal->Initialize();
173   return internal;
174 }
175
176 NinePatchImage::NinePatchImage( const std::string& filename, ReleasePolicy releasePol )
177 : ResourceImage( IMAGE_LOAD_POLICY_DEFAULT, releasePol ),
178   mParsedBorder(false)
179 {
180   ThreadLocalStorage& tls = ThreadLocalStorage::Get();
181   mResourceClient = &tls.GetResourceClient();
182
183   Integration::PlatformAbstraction& platformAbstraction = tls.GetPlatformAbstraction();
184   Integration::BitmapResourceType resourceType;
185
186   // Note, bitmap is only destroyed when the image is destroyed.
187   Integration::ResourcePointer resource = platformAbstraction.LoadResourceSynchronously( resourceType, filename );
188   if( resource )
189   {
190     mBitmap = static_cast<Integration::Bitmap*>( resource.Get());
191     mWidth = mBitmap->GetImageWidth();
192     mHeight = mBitmap->GetImageHeight();
193   }
194   else
195   {
196     mBitmap.Reset();
197     mWidth = 0;
198     mHeight = 0;
199   }
200 }
201
202 NinePatchImage* NinePatchImage::DownCast( Image* image)
203 {
204   return dynamic_cast<NinePatchImage*>(image);
205 }
206
207 NinePatchImage::~NinePatchImage()
208 {
209 }
210
211 const NinePatchImage::StretchRanges& NinePatchImage::GetStretchPixelsX()
212 {
213   if( ! mParsedBorder )
214   {
215     ParseBorders();
216   }
217   return mStretchPixelsX;
218 }
219
220 const NinePatchImage::StretchRanges& NinePatchImage::GetStretchPixelsY()
221 {
222   if( ! mParsedBorder )
223   {
224     ParseBorders();
225   }
226   return mStretchPixelsY;
227 }
228
229 Rect<int> NinePatchImage::GetChildRectangle()
230 {
231   if( ! mParsedBorder )
232   {
233     ParseBorders();
234   }
235   return mChildRectangle;
236 }
237
238 Internal::BufferImagePtr NinePatchImage::CreateCroppedBufferImage()
239 {
240   BufferImagePtr cropped;
241
242   if( ! mBitmap )
243   {
244     DALI_LOG_ERROR( "NinePatchImage: Bitmap not loaded, cannot perform operation\n");
245   }
246   else
247   {
248     Pixel::Format pixelFormat = mBitmap->GetPixelFormat();
249
250     cropped = BufferImage::New( mWidth-2, mHeight-2, pixelFormat, Dali::Image::NEVER );
251
252     Integration::Bitmap::PackedPixelsProfile* srcProfile = mBitmap->GetPackedPixelsProfile();
253     DALI_ASSERT_DEBUG( srcProfile && "Wrong profile for source bitmap");
254
255     if( srcProfile )
256     {
257       PixelBuffer* destPixels = cropped->GetBuffer();
258       unsigned int destStride = cropped->GetBufferStride();
259       unsigned int pixelWidth = GetBytesPerPixel(pixelFormat);
260
261       PixelBuffer* srcPixels = mBitmap->GetBuffer();
262       unsigned int srcStride = srcProfile->GetBufferStride();
263
264       for( unsigned int row=1; row < mHeight-1; ++row )
265       {
266         PixelBuffer* src  = srcPixels + row*srcStride + pixelWidth;
267         PixelBuffer* dest = destPixels + (row-1)*destStride;
268         memcpy(dest, src, destStride );
269       }
270     }
271
272     RectArea area;
273     cropped->Update(area); // default area has no width or height
274   }
275   return cropped;
276 }
277
278 void NinePatchImage::Connect()
279 {
280   if( !mTicket )
281   {
282     if( mBitmap )
283     {
284       const ImageTicketPtr& t = mResourceClient->AddBitmapImage(mBitmap.Get());
285       mTicket = t.Get();
286       mTicket->AddObserver(*this);
287     }
288   }
289
290   ++mConnectionCount;
291 }
292
293 void NinePatchImage::Disconnect()
294 {
295   if( mConnectionCount > 0 )
296   {
297     --mConnectionCount;
298   }
299 }
300
301
302 void NinePatchImage::ParseBorders()
303 {
304   if( !mBitmap )
305   {
306     DALI_LOG_ERROR( "NinePatchImage: Bitmap not loaded, cannot perform operation\n");
307     return;
308   }
309
310   mStretchPixelsX.Clear();
311   mStretchPixelsY.Clear();
312
313   Pixel::Format pixelFormat = mBitmap->GetPixelFormat();
314
315   const Integration::Bitmap::PackedPixelsProfile* srcProfile = mBitmap->GetPackedPixelsProfile();
316   DALI_ASSERT_DEBUG( srcProfile && "Wrong profile for source bitmap" );
317
318   if( srcProfile )
319   {
320     int alphaByte = 0;
321     int alphaBits = 0;
322     Pixel::GetAlphaOffsetAndMask( pixelFormat, alphaByte, alphaBits );
323
324     int testByte = alphaByte;
325     int testBits = alphaBits;
326     int testValue = alphaBits; // Opaque == stretch
327     if( ! alphaBits )
328     {
329       GetRedOffsetAndMask( pixelFormat, testByte, testBits );
330       testValue = 0;           // Black == stretch
331     }
332
333     unsigned int pixelWidth = GetBytesPerPixel( pixelFormat );
334     const PixelBuffer* srcPixels = mBitmap->GetBuffer();
335     unsigned int srcStride = srcProfile->GetBufferStride();
336
337     //TOP
338     const PixelBuffer* top = srcPixels + pixelWidth;
339     unsigned int index = 0;
340     unsigned int width = mBitmap->GetImageWidth();
341     unsigned int height = mBitmap->GetImageHeight();
342
343     for(; index < width - 2; )
344     {
345       Uint16Pair range = ParseRange( index, width - 2, top, pixelWidth, testByte, testBits, testValue );
346       if( range.GetX() != 0xFFFF )
347       {
348         mStretchPixelsX.PushBack( range );
349       }
350     }
351
352     //LEFT
353     const PixelBuffer* left  = srcPixels + srcStride;
354     index = 0;
355     for(; index < height - 2; )
356     {
357       Uint16Pair range = ParseRange( index, height - 2, left, srcStride, testByte, testBits, testValue );
358       if( range.GetX() != 0xFFFF )
359       {
360         mStretchPixelsY.PushBack( range );
361       }
362     }
363
364     //If there are no stretch pixels then make the entire image stretchable
365     if( mStretchPixelsX.Size() == 0 )
366     {
367       mStretchPixelsX.PushBack( Uint16Pair( 0, width - 2 ) );
368     }
369     if( mStretchPixelsY.Size() == 0 )
370     {
371       mStretchPixelsY.PushBack( Uint16Pair( 0, height - 2 ) );
372     }
373
374     //Child Rectangle
375     //BOTTOM
376     const PixelBuffer* bottom = srcPixels + ( height - 1 ) * srcStride + pixelWidth;
377     index = 0;
378     Uint16Pair contentRangeX = ParseRange( index, width - 2, bottom, pixelWidth, testByte, testBits, testValue );
379     if( contentRangeX.GetX() == 0xFFFF )
380     {
381       contentRangeX = Uint16Pair();
382     }
383
384     //RIGHT
385     const PixelBuffer* right = srcPixels + srcStride + ( width - 1 ) * pixelWidth;
386     index = 0;
387     Uint16Pair contentRangeY = ParseRange( index, height - 2, right, srcStride, testByte, testBits, testValue );
388     if( contentRangeY.GetX() == 0xFFFF )
389     {
390       contentRangeY = Uint16Pair();
391     }
392
393     mChildRectangle.x = contentRangeX.GetX() + 1;
394     mChildRectangle.y = contentRangeY.GetX() + 1;
395     mChildRectangle.width = contentRangeX.GetY() - contentRangeX.GetX();
396     mChildRectangle.height = contentRangeY.GetY() - contentRangeY.GetX();
397
398     mParsedBorder = true;
399   }
400 }
401
402 Uint16Pair NinePatchImage::ParseRange( unsigned int& index, unsigned int width, const PixelBuffer* & pixel, unsigned int pixelStride, int testByte, int testBits, int testValue )
403 {
404   unsigned int start = 0xFFFF;
405   for( ; index < width; ++index, pixel += pixelStride )
406   {
407     if( ( pixel[ testByte ] & testBits ) == testValue )
408     {
409         start = index;
410         ++index;
411         pixel += pixelStride;
412         break;
413     }
414   }
415
416   unsigned int end = width;
417   for( ; index < width; ++index, pixel += pixelStride )
418   {
419     if( ( pixel[ testByte ] & testBits ) != testValue )
420     {
421         end = index;
422         ++index;
423         pixel += pixelStride;
424         break;
425     }
426   }
427
428   return Uint16Pair( start, end );
429 }
430
431 bool NinePatchImage::IsNinePatchUrl( const std::string& url )
432 {
433   bool match = false;
434
435   std::string::const_reverse_iterator iter = url.rbegin();
436   enum { SUFFIX, HASH, HASH_DOT, DONE } state = SUFFIX;
437   while(iter < url.rend())
438   {
439     switch(state)
440     {
441       case SUFFIX:
442       {
443         if(*iter == '.')
444         {
445           state = HASH;
446         }
447         else if(!isalnum(*iter))
448         {
449           state = DONE;
450         }
451       }
452       break;
453       case HASH:
454       {
455         if( *iter == '#' || *iter == '9' )
456         {
457           state = HASH_DOT;
458         }
459         else
460         {
461           state = DONE;
462         }
463       }
464       break;
465       case HASH_DOT:
466       {
467         if(*iter == '.')
468         {
469           match = true;
470         }
471         state = DONE; // Stop testing characters
472       }
473       break;
474       case DONE:
475       {
476       }
477       break;
478     }
479
480     // Satisfy prevent
481     if( state == DONE )
482     {
483       break;
484     }
485
486     ++iter;
487   }
488   return match;
489 }
490
491 } // namespace Internal
492
493 } // namespace Dali