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