(Image) Add support for 9-patch images
[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 Flora License, Version 1.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://floralicense.org/license/
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 // CLASS HEADER
18 #include <dali/internal/event/images/nine-patch-image-impl.h>
19
20 // INTERNAL INCLUDES
21 #include <dali/integration-api/bitmap.h>
22 #include <dali/internal/event/images/bitmap-external.h>
23 #include <dali/internal/event/common/thread-local-storage.h>
24 #include <dali/internal/event/resources/resource-client.h>
25 #include <dali/internal/update/manager/update-manager.h>
26 #include <dali/internal/event/images/image-factory.h>
27 #include <dali/integration-api/platform-abstraction.h>
28 #include <dali/integration-api/resource-types.h>
29 #include <dali/integration-api/resource-cache.h>
30
31
32 namespace
33 {
34 void GetRedOffsetAndMask(Dali::Pixel::Format pixelFormat, int& byteOffset, int& bitMask)
35 {
36   switch (pixelFormat)
37   {
38     case Dali::Pixel::A8:
39     case Dali::Pixel::L8:
40     case Dali::Pixel::LA88:
41     {
42       byteOffset=0;
43       bitMask=0;
44       break;
45     }
46
47     case Dali::Pixel::RGB888:
48     case Dali::Pixel::RGB8888:
49     case Dali::Pixel::RGBA8888:
50     {
51       byteOffset=0;
52       bitMask=0xFF;
53       break;
54     }
55     case Dali::Pixel::BGR8888:
56     case Dali::Pixel::BGRA8888:
57     {
58       byteOffset=2;
59       bitMask=0xff;
60       break;
61     }
62     case Dali::Pixel::RGB565:
63     {
64       byteOffset=0;
65       bitMask=0xf8;
66       break;
67     }
68     case Dali::Pixel::BGR565:
69     {
70       byteOffset=1;
71       bitMask=0x1f;
72       break;
73     }
74
75     case Dali::Pixel::RGBA4444:
76     {
77       byteOffset=0;
78       bitMask=0xf0;
79       break;
80     }
81     case Dali::Pixel::BGRA4444:
82     {
83       byteOffset=1;
84       bitMask=0xf0;
85       break;
86     }
87
88     case Dali::Pixel::RGBA5551:
89     {
90       byteOffset=0;
91       bitMask=0xf8;
92       break;
93     }
94
95     case Dali::Pixel::BGRA5551:
96     {
97       byteOffset=1;
98       bitMask=0x1e;
99       break;
100     }
101
102     case Dali::Pixel::COMPRESSED_R11_EAC:
103     case Dali::Pixel::COMPRESSED_SIGNED_R11_EAC:
104     case Dali::Pixel::COMPRESSED_RG11_EAC:
105     case Dali::Pixel::COMPRESSED_SIGNED_RG11_EAC:
106     case Dali::Pixel::COMPRESSED_RGB8_ETC2:
107     case Dali::Pixel::COMPRESSED_SRGB8_ETC2:
108     case Dali::Pixel::COMPRESSED_RGB8_ETC1:
109     case Dali::Pixel::COMPRESSED_RGB_PVRTC_4BPPV1:
110     case Dali::Pixel::COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
111     case Dali::Pixel::COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
112     case Dali::Pixel::COMPRESSED_RGBA8_ETC2_EAC:
113     case Dali::Pixel::COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
114     {
115       DALI_LOG_ERROR("Pixel formats for compressed images are not compatible with simple masking-out of per-pixel alpha.\n");
116       byteOffset=0;
117       bitMask=0;
118       break;
119     }
120   }
121 }
122
123 } // anonymous namespace
124
125
126 namespace Dali
127 {
128 namespace Internal
129 {
130
131
132 NinePatchImagePtr NinePatchImage::New( const std::string& filename, const Dali::ImageAttributes& attributes, LoadPolicy loadPol, ReleasePolicy releasePol )
133 {
134   Internal::NinePatchImagePtr internal( new NinePatchImage( filename, attributes, loadPol, releasePol ) );
135   internal->Initialize();
136   return internal;
137 }
138
139 NinePatchImage::NinePatchImage( const std::string& filename, const Dali::ImageAttributes& attributes, LoadPolicy loadPol, ReleasePolicy releasePol)
140 : Image(Dali::Image::Immediate, Dali::Image::Never),
141   mParsedBorder(false)
142 {
143   Initialize();
144
145   Integration::PlatformAbstraction& platformAbstraction = Internal::ThreadLocalStorage::Get().GetPlatformAbstraction();
146
147   Vector2 closestSize;
148   platformAbstraction.GetClosestImageSize( filename, attributes, closestSize );
149   ImageAttributes loadedAttrs;
150   loadedAttrs.SetSize( closestSize );
151   mWidth = closestSize.width;
152   mHeight = closestSize.height;
153   Integration::BitmapResourceType resourceType( loadedAttrs );
154
155   // Note, bitmap is only destroyed when the image is destroyed.
156   Integration::ResourcePointer resource = platformAbstraction.LoadResourceSynchronously(resourceType, filename);
157   mBitmap = static_cast<Integration::Bitmap*>( resource.Get());
158 }
159
160 NinePatchImage* NinePatchImage::GetNinePatchImage( Image* image)
161 {
162   return dynamic_cast<NinePatchImage*>(image);
163 }
164
165 NinePatchImage::~NinePatchImage()
166 {
167 }
168
169 Vector4 NinePatchImage::GetStretchBorders()
170 {
171   if( ! mParsedBorder )
172   {
173     ParseBorders();
174   }
175   return mStretchBorders;
176 }
177
178 Rect<int> NinePatchImage::GetChildRectangle()
179 {
180   if( ! mParsedBorder )
181   {
182     ParseBorders();
183   }
184   return mChildRectangle;
185 }
186
187 Internal::BitmapImagePtr NinePatchImage::CreateCroppedBitmapImage()
188 {
189   Pixel::Format pixelFormat = mBitmap->GetPixelFormat();
190
191   BitmapImagePtr cropped = BitmapImage::New( mWidth-2, mHeight-2, pixelFormat,
192                                              Dali::Image::Immediate, Dali::Image::Never );
193
194   Integration::Bitmap::PackedPixelsProfile* srcProfile = mBitmap->GetPackedPixelsProfile();
195   DALI_ASSERT_DEBUG( srcProfile && "Wrong profile for source bitmap");
196
197   if( srcProfile )
198   {
199     PixelBuffer* destPixels = cropped->GetBuffer();
200     unsigned int destStride = cropped->GetBufferStride();
201     unsigned int pixelWidth = GetBytesPerPixel(pixelFormat);
202
203     PixelBuffer* srcPixels = mBitmap->GetBuffer();
204     unsigned int srcStride = srcProfile->GetBufferStride();
205
206     for( unsigned int row=1; row < mHeight-1; ++row )
207     {
208       PixelBuffer* src  = srcPixels + row*srcStride + pixelWidth;
209       PixelBuffer* dest = destPixels + (row-1)*destStride;
210       memcpy(dest, src, destStride );
211     }
212   }
213
214   RectArea area;
215   cropped->Update(area); // default area has no width or height
216   return cropped;
217 }
218
219 void NinePatchImage::Initialize()
220 {
221   ThreadLocalStorage& tls = ThreadLocalStorage::Get();
222   mResourceClient = &tls.GetResourceClient();
223 }
224
225 void NinePatchImage::Connect()
226 {
227   if( mConnectionCount == 0 && !mTicket )
228   {
229     const ImageTicketPtr& t = mResourceClient->AddBitmapImage(mBitmap.Get());
230     mTicket = t.Get();
231     mTicket->AddObserver(*this);
232   }
233
234   ++mConnectionCount;
235 }
236
237 void NinePatchImage::Disconnect()
238 {
239   if( mConnectionCount > 0 )
240   {
241     --mConnectionCount;
242   }
243 }
244
245
246 void NinePatchImage::ParseBorders()
247 {
248   Pixel::Format pixelFormat = mBitmap->GetPixelFormat();
249
250   Integration::Bitmap::PackedPixelsProfile* srcProfile = mBitmap->GetPackedPixelsProfile();
251   DALI_ASSERT_DEBUG( srcProfile && "Wrong profile for source bitmap");
252
253   if( srcProfile )
254   {
255     unsigned int pixelWidth = GetBytesPerPixel(pixelFormat);
256     PixelBuffer* srcPixels = mBitmap->GetBuffer();
257     unsigned int srcStride = srcProfile->GetBufferStride();
258
259     int alphaByte=0;
260     int alphaBits=0;
261     Pixel::GetAlphaOffsetAndMask(pixelFormat, alphaByte, alphaBits);
262     int redByte=0;
263     int redBits=0;
264     GetRedOffsetAndMask(pixelFormat, redByte, redBits);
265
266     int testByte = alphaByte;
267     int testBits = alphaBits;
268     int testValue = alphaBits; // Opaque == stretch
269     if( ! alphaBits )
270     {
271       testByte = redByte;
272       testBits = redBits;
273       testValue = 0;           // Black == stretch
274     }
275
276     int startX1=-1;
277     int endX1=-1;
278     int startY1=-1;
279     int endY1=-1;
280     int startX2=-1;
281     int endX2=-1;
282     int startY2=-1;
283     int endY2=-1;
284
285     PixelBuffer* top = srcPixels + pixelWidth;
286     PixelBuffer* bottom = srcPixels + (mHeight-1)*srcStride + pixelWidth;
287
288     // Read the top and bottom rows:
289     // (Also read the last column to ensure end value gets set)
290     for( unsigned int col=1; col < mWidth; ++col )
291     {
292       if( (top[testByte] & testBits) == testValue )
293       {
294         if(startX1 < 0)
295         {
296           startX1 = col;
297         }
298       }
299       else if(startX1 >= 0 && endX1 < 0)
300       {
301         endX1 = col-1;
302         break;
303       }
304
305       if( (bottom[testByte] & testBits) == testValue )
306       {
307         if(startX2 < 0)
308         {
309           startX2 = col;
310         }
311       }
312       else if(startX2 >= 0 && endX2 < 0)
313       {
314         endX2 = col-1;
315         break;
316       }
317       top+=pixelWidth;
318       bottom+=pixelWidth;
319     }
320
321     // Read the left and right columns:
322     PixelBuffer* left  = srcPixels + srcStride;
323     PixelBuffer* right = left + (srcStride - pixelWidth);
324
325     // (Also read the last row to ensure end value gets set)
326     for( unsigned int row=1; row < mHeight; ++row )
327     {
328       if((left[testByte] & testBits) == testValue)
329       {
330         if(startY1 < 0)
331         {
332           startY1 = row;
333         }
334       }
335       else if(startY1 >= 0 && endY1 < 0)
336       {
337         endY1 = row-1;
338         break;
339       }
340
341       if((right[testByte] & testBits) == testValue)
342       {
343         if(startY2 < 0)
344         {
345           startY2 = row;
346         }
347       }
348       else if(startY2 >= 0 && endY2 < 0)
349       {
350         endY2 = row-1;
351         break;
352       }
353       left += srcStride;
354       right += srcStride;
355     }
356
357     mStretchBorders.x = startX1-1;
358     mStretchBorders.y = startY1-1;
359     mStretchBorders.z = mWidth-endX1-1;
360     mStretchBorders.w = mHeight-endY1-1;
361
362     mChildRectangle.x = startX2-1;
363     mChildRectangle.y = startY2-1;
364     mChildRectangle.width = endX2-startX2;
365     mChildRectangle.height = endY2-startY2;
366
367     mParsedBorder = true;
368   }
369 }
370
371 } // namespace Internal
372
373 } // namespace Dali