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