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