Merge branch 'devel/master' into tizen
[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/integration-api/platform-abstraction.h>
31 #include <dali/integration-api/resource-types.h>
32 #include <dali/integration-api/resource-cache.h>
33
34
35 namespace
36 {
37 void GetRedOffsetAndMask(Dali::Pixel::Format pixelFormat, int& byteOffset, int& bitMask)
38 {
39   switch (pixelFormat)
40   {
41     case Dali::Pixel::A8:
42     case Dali::Pixel::L8:
43     case Dali::Pixel::LA88:
44     {
45       byteOffset=0;
46       bitMask=0;
47       break;
48     }
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
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
91     case Dali::Pixel::RGBA5551:
92     {
93       byteOffset=0;
94       bitMask=0xf8;
95       break;
96     }
97
98     case Dali::Pixel::BGRA5551:
99     {
100       byteOffset=1;
101       bitMask=0x1e;
102       break;
103     }
104
105     case Dali::Pixel::INVALID:
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     case Dali::Pixel::COMPRESSED_RGBA_ASTC_4x4_KHR:
119     case Dali::Pixel::COMPRESSED_RGBA_ASTC_5x4_KHR:
120     case Dali::Pixel::COMPRESSED_RGBA_ASTC_5x5_KHR:
121     case Dali::Pixel::COMPRESSED_RGBA_ASTC_6x5_KHR:
122     case Dali::Pixel::COMPRESSED_RGBA_ASTC_6x6_KHR:
123     case Dali::Pixel::COMPRESSED_RGBA_ASTC_8x5_KHR:
124     case Dali::Pixel::COMPRESSED_RGBA_ASTC_8x6_KHR:
125     case Dali::Pixel::COMPRESSED_RGBA_ASTC_8x8_KHR:
126     case Dali::Pixel::COMPRESSED_RGBA_ASTC_10x5_KHR:
127     case Dali::Pixel::COMPRESSED_RGBA_ASTC_10x6_KHR:
128     case Dali::Pixel::COMPRESSED_RGBA_ASTC_10x8_KHR:
129     case Dali::Pixel::COMPRESSED_RGBA_ASTC_10x10_KHR:
130     case Dali::Pixel::COMPRESSED_RGBA_ASTC_12x10_KHR:
131     case Dali::Pixel::COMPRESSED_RGBA_ASTC_12x12_KHR:
132     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR:
133     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR:
134     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR:
135     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR:
136     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR:
137     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR:
138     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR:
139     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR:
140     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR:
141     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR:
142     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR:
143     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR:
144     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR:
145     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR:
146     {
147       DALI_LOG_ERROR("Pixel formats for compressed images are not compatible with simple masking-out of per-pixel alpha.\n");
148       byteOffset=0;
149       bitMask=0;
150       break;
151     }
152   }
153 }
154
155 } // anonymous namespace
156
157
158 namespace Dali
159 {
160 namespace Internal
161 {
162
163 namespace
164 {
165 TypeRegistration mType( typeid( Dali::NinePatchImage ), typeid( Dali::Image ), NULL );
166 } // unnamed namespace
167
168 NinePatchImagePtr NinePatchImage::New( const std::string& filename )
169 {
170   Internal::NinePatchImagePtr internal( new NinePatchImage( filename ) );
171   internal->Initialize();
172   return internal;
173 }
174
175 NinePatchImage::NinePatchImage( const std::string& filename )
176 : ResourceImage(),
177   mParsedBorder(false)
178 {
179   mUrl = filename;
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
192     mWidth = mBitmap->GetImageWidth();
193     mHeight = mBitmap->GetImageHeight();
194     mTexture = NewTexture::New( Dali::TextureType::TEXTURE_2D, mBitmap->GetPixelFormat(), mWidth, mHeight );
195
196     size_t bufferSize = mBitmap->GetBufferSize();
197     unsigned char* buffer = new unsigned char[bufferSize];
198     memcpy( buffer, mBitmap->GetBuffer(), bufferSize );
199     PixelDataPtr pixelData = PixelData::New( buffer, bufferSize, mWidth, mHeight, mBitmap->GetPixelFormat(), Dali::PixelData::DELETE_ARRAY );
200     mTexture->Upload( pixelData );
201   }
202   else
203   {
204     mBitmap.Reset();
205     mWidth = 0;
206     mHeight = 0;
207   }
208 }
209
210 NinePatchImage* NinePatchImage::DownCast( Image* image)
211 {
212   return dynamic_cast<NinePatchImage*>(image);
213 }
214
215 NinePatchImage::~NinePatchImage()
216 {
217 }
218
219 const NinePatchImage::StretchRanges& NinePatchImage::GetStretchPixelsX()
220 {
221   if( ! mParsedBorder )
222   {
223     ParseBorders();
224   }
225   return mStretchPixelsX;
226 }
227
228 const NinePatchImage::StretchRanges& NinePatchImage::GetStretchPixelsY()
229 {
230   if( ! mParsedBorder )
231   {
232     ParseBorders();
233   }
234   return mStretchPixelsY;
235 }
236
237 Rect<int> NinePatchImage::GetChildRectangle()
238 {
239   if( ! mParsedBorder )
240   {
241     ParseBorders();
242   }
243   return mChildRectangle;
244 }
245
246 Internal::BufferImagePtr NinePatchImage::CreateCroppedBufferImage()
247 {
248   BufferImagePtr cropped;
249
250   if( ! mBitmap )
251   {
252     DALI_LOG_ERROR( "NinePatchImage: Bitmap not loaded, cannot perform operation\n");
253   }
254   else
255   {
256     Pixel::Format pixelFormat = mBitmap->GetPixelFormat();
257
258     cropped = BufferImage::New( mWidth-2, mHeight-2, pixelFormat );
259
260     Integration::Bitmap::PackedPixelsProfile* srcProfile = mBitmap->GetPackedPixelsProfile();
261     DALI_ASSERT_DEBUG( srcProfile && "Wrong profile for source bitmap");
262
263     if( srcProfile )
264     {
265       PixelBuffer* destPixels = cropped->GetBuffer();
266       unsigned int destStride = cropped->GetBufferStride();
267       unsigned int pixelWidth = GetBytesPerPixel(pixelFormat);
268
269       PixelBuffer* srcPixels = mBitmap->GetBuffer();
270       unsigned int srcStride = srcProfile->GetBufferStride();
271
272       for( unsigned int row=1; row < mHeight-1; ++row )
273       {
274         PixelBuffer* src  = srcPixels + row*srcStride + pixelWidth;
275         PixelBuffer* dest = destPixels + (row-1)*destStride;
276         memcpy(dest, src, destStride );
277       }
278     }
279
280     RectArea area;
281     cropped->Update(area); // default area has no width or height
282   }
283   return cropped;
284 }
285
286 const std::string& NinePatchImage::GetUrl() const
287 {
288   return mUrl;
289 }
290
291 void NinePatchImage::ParseBorders()
292 {
293   if( !mBitmap )
294   {
295     DALI_LOG_ERROR( "NinePatchImage: Bitmap not loaded, cannot perform operation\n");
296     return;
297   }
298
299   mStretchPixelsX.Clear();
300   mStretchPixelsY.Clear();
301
302   Pixel::Format pixelFormat = mBitmap->GetPixelFormat();
303
304   const Integration::Bitmap::PackedPixelsProfile* srcProfile = mBitmap->GetPackedPixelsProfile();
305   DALI_ASSERT_DEBUG( srcProfile && "Wrong profile for source bitmap" );
306
307   if( srcProfile )
308   {
309     int alphaByte = 0;
310     int alphaBits = 0;
311     Pixel::GetAlphaOffsetAndMask( pixelFormat, alphaByte, alphaBits );
312
313     int testByte = alphaByte;
314     int testBits = alphaBits;
315     int testValue = alphaBits; // Opaque == stretch
316     if( ! alphaBits )
317     {
318       GetRedOffsetAndMask( pixelFormat, testByte, testBits );
319       testValue = 0;           // Black == stretch
320     }
321
322     unsigned int pixelWidth = GetBytesPerPixel( pixelFormat );
323     const PixelBuffer* srcPixels = mBitmap->GetBuffer();
324     unsigned int srcStride = srcProfile->GetBufferStride();
325
326     //TOP
327     const PixelBuffer* top = srcPixels + pixelWidth;
328     unsigned int index = 0;
329     unsigned int width = mBitmap->GetImageWidth();
330     unsigned int height = mBitmap->GetImageHeight();
331
332     for(; index < width - 2; )
333     {
334       Uint16Pair range = ParseRange( index, width - 2, top, pixelWidth, testByte, testBits, testValue );
335       if( range.GetX() != 0xFFFF )
336       {
337         mStretchPixelsX.PushBack( range );
338       }
339     }
340
341     //LEFT
342     const PixelBuffer* left  = srcPixels + srcStride;
343     index = 0;
344     for(; index < height - 2; )
345     {
346       Uint16Pair range = ParseRange( index, height - 2, left, srcStride, testByte, testBits, testValue );
347       if( range.GetX() != 0xFFFF )
348       {
349         mStretchPixelsY.PushBack( range );
350       }
351     }
352
353     //If there are no stretch pixels then make the entire image stretchable
354     if( mStretchPixelsX.Size() == 0 )
355     {
356       mStretchPixelsX.PushBack( Uint16Pair( 0, width - 2 ) );
357     }
358     if( mStretchPixelsY.Size() == 0 )
359     {
360       mStretchPixelsY.PushBack( Uint16Pair( 0, height - 2 ) );
361     }
362
363     //Child Rectangle
364     //BOTTOM
365     const PixelBuffer* bottom = srcPixels + ( height - 1 ) * srcStride + pixelWidth;
366     index = 0;
367     Uint16Pair contentRangeX = ParseRange( index, width - 2, bottom, pixelWidth, testByte, testBits, testValue );
368     if( contentRangeX.GetX() == 0xFFFF )
369     {
370       contentRangeX = Uint16Pair();
371     }
372
373     //RIGHT
374     const PixelBuffer* right = srcPixels + srcStride + ( width - 1 ) * pixelWidth;
375     index = 0;
376     Uint16Pair contentRangeY = ParseRange( index, height - 2, right, srcStride, testByte, testBits, testValue );
377     if( contentRangeY.GetX() == 0xFFFF )
378     {
379       contentRangeY = Uint16Pair();
380     }
381
382     mChildRectangle.x = contentRangeX.GetX() + 1;
383     mChildRectangle.y = contentRangeY.GetX() + 1;
384     mChildRectangle.width = contentRangeX.GetY() - contentRangeX.GetX();
385     mChildRectangle.height = contentRangeY.GetY() - contentRangeY.GetX();
386
387     mParsedBorder = true;
388   }
389 }
390
391 Uint16Pair NinePatchImage::ParseRange( unsigned int& index, unsigned int width, const PixelBuffer* & pixel, unsigned int pixelStride, int testByte, int testBits, int testValue )
392 {
393   unsigned int start = 0xFFFF;
394   for( ; index < width; ++index, pixel += pixelStride )
395   {
396     if( ( pixel[ testByte ] & testBits ) == testValue )
397     {
398         start = index;
399         ++index;
400         pixel += pixelStride;
401         break;
402     }
403   }
404
405   unsigned int end = width;
406   for( ; index < width; ++index, pixel += pixelStride )
407   {
408     if( ( pixel[ testByte ] & testBits ) != testValue )
409     {
410         end = index;
411         ++index;
412         pixel += pixelStride;
413         break;
414     }
415   }
416
417   return Uint16Pair( start, end );
418 }
419
420 bool NinePatchImage::IsNinePatchUrl( const std::string& url )
421 {
422   bool match = false;
423
424   std::string::const_reverse_iterator iter = url.rbegin();
425   enum { SUFFIX, HASH, HASH_DOT, DONE } state = SUFFIX;
426   while(iter < url.rend())
427   {
428     switch(state)
429     {
430       case SUFFIX:
431       {
432         if(*iter == '.')
433         {
434           state = HASH;
435         }
436         else if(!isalnum(*iter))
437         {
438           state = DONE;
439         }
440       }
441       break;
442       case HASH:
443       {
444         if( *iter == '#' || *iter == '9' )
445         {
446           state = HASH_DOT;
447         }
448         else
449         {
450           state = DONE;
451         }
452       }
453       break;
454       case HASH_DOT:
455       {
456         if(*iter == '.')
457         {
458           match = true;
459         }
460         state = DONE; // Stop testing characters
461       }
462       break;
463       case DONE:
464       {
465       }
466       break;
467     }
468
469     // Satisfy prevent
470     if( state == DONE )
471     {
472       break;
473     }
474
475     ++iter;
476   }
477   return match;
478 }
479
480 } // namespace Internal
481
482 } // namespace Dali