[dali_2.3.20] Merge branch 'devel/master'
[platform/core/uifw/dali-toolkit.git] / automated-tests / src / dali-toolkit / utc-Dali-ImageAtlas.cpp
1 /*
2  * Copyright (c) 2022 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 #include <stdlib.h>
19 #include <unistd.h>
20
21 #include <dali-toolkit-test-suite-utils.h>
22 #include <toolkit-event-thread-callback.h>
23
24 #include <dali-toolkit/dali-toolkit.h>
25 #include <dali-toolkit/devel-api/image-loader/image-atlas.h>
26 #include <dali/dali.h>
27
28 using namespace Dali;
29 using namespace Dali::Toolkit;
30
31 namespace
32 {
33 // resolution: 34*34, pixel format: RGBA8888
34 static const char* gImage_34_RGBA = TEST_RESOURCE_DIR "/icon-edit.png";
35 // resolution: 50*50, pixel format: RGBA8888
36 static const char* gImage_50_RGBA = TEST_RESOURCE_DIR "/icon-delete.png";
37 // resolution: 128*128, pixel format: RGB888
38 static const char* gImage_128_RGB = TEST_RESOURCE_DIR "/gallery-small-1.jpg";
39
40 // Empty image, for testing broken image loading
41 static const char* gEmptyImage = TEST_RESOURCE_DIR "/empty.bmp";
42
43 const int RENDER_FRAME_INTERVAL = 16; ///< Duration of each frame in ms. (at approx 60FPS)
44
45 PixelData CreatePixelData(unsigned int width, unsigned int height)
46 {
47   unsigned int bufferSize = width * height * Pixel::GetBytesPerPixel(Pixel::RGBA8888);
48
49   unsigned char* buffer    = reinterpret_cast<unsigned char*>(malloc(bufferSize));
50   PixelData      pixelData = PixelData::New(buffer, bufferSize, width, height, Pixel::RGBA8888, PixelData::FREE);
51
52   return pixelData;
53 }
54
55 Rect<int> TextureCoordinateToPixelArea(const Vector4& textureCoordinate, float size)
56 {
57   Vector4   temp = textureCoordinate * size;
58   Rect<int> pixelArea;
59   pixelArea.x      = static_cast<int>(temp.x);
60   pixelArea.y      = static_cast<int>(temp.y);
61   pixelArea.width  = static_cast<int>(temp.z - temp.x + 1.01f);
62   pixelArea.height = static_cast<int>(temp.w - temp.y + 1.01f);
63
64   return pixelArea;
65 }
66
67 Rect<int> TextureCoordinateToPixelArea(const Vector4& textureCoordinate, float width, float height)
68 {
69   Rect<int> pixelArea;
70   pixelArea.x      = static_cast<int>(textureCoordinate.x * width);
71   pixelArea.y      = static_cast<int>(textureCoordinate.y * height);
72   pixelArea.width  = static_cast<int>((textureCoordinate.z - textureCoordinate.x) * width + 1.01f);
73   pixelArea.height = static_cast<int>((textureCoordinate.w - textureCoordinate.y) * height + 1.01f);
74
75   return pixelArea;
76 }
77
78 bool IsOverlap(Rect<int> rect1, Rect<int> rect2)
79 {
80   return rect1.x < rect2.x + rect2.width && rect2.x < rect1.x + rect1.width && rect1.y < rect2.y + rect2.height && rect2.y < rect1.y + rect1.height;
81 }
82
83 static unsigned int gCountOfTestFuncCall;
84 class TestUploadObserver : public AtlasUploadObserver
85 {
86 public:
87   TestUploadObserver()
88   {
89   }
90
91   virtual ~TestUploadObserver()
92   {
93   }
94
95   void UploadCompleted()
96   {
97     gCountOfTestFuncCall++;
98   }
99 };
100
101 } // anonymous namespace
102
103 void dali_image_atlas_startup(void)
104 {
105   test_return_value = TET_UNDEF;
106 }
107
108 void dali_image_atlas_cleanup(void)
109 {
110   test_return_value = TET_PASS;
111 }
112
113 int UtcDaliImageAtlasNew(void)
114 {
115   ToolkitTestApplication application;
116
117   // invoke default handle constructor
118   ImageAtlas atlas;
119
120   DALI_TEST_CHECK(!atlas);
121
122   // initialise handle
123   atlas = ImageAtlas::New(32, 32);
124
125   DALI_TEST_CHECK(atlas);
126   END_TEST;
127 }
128
129 int UtcDaliImageAtlasCopyConstructor(void)
130 {
131   ToolkitTestApplication application;
132
133   ImageAtlas atlas = ImageAtlas::New(32, 32);
134   ImageAtlas atlasCopy(atlas);
135
136   DALI_TEST_EQUALS((bool)atlasCopy, true, TEST_LOCATION);
137   END_TEST;
138 }
139
140 int UtcDaliImageAtlasAssignmentOperator(void)
141 {
142   ToolkitTestApplication application;
143
144   ImageAtlas atlas = ImageAtlas::New(32, 32);
145
146   ImageAtlas atlas2;
147   DALI_TEST_EQUALS((bool)atlas2, false, TEST_LOCATION);
148
149   atlas2 = atlas;
150   DALI_TEST_EQUALS((bool)atlas2, true, TEST_LOCATION);
151
152   END_TEST;
153 }
154
155 int UtcDaliImageAtlasGetAtlas(void)
156 {
157   ToolkitTestApplication application;
158
159   ImageAtlas atlas = ImageAtlas::New(32, 32);
160   Texture    image = atlas.GetAtlas();
161
162   // test the atlas created
163   DALI_TEST_EQUALS((bool)image, true, TEST_LOCATION);
164   DALI_TEST_CHECK(image.GetHeight() == 32u);
165   DALI_TEST_CHECK(image.GetWidth() == 32u);
166
167   END_TEST;
168 }
169
170 int UtcDaliImageAtlasGetOccupancyRate(void)
171 {
172   ToolkitTestApplication application;
173
174   ImageAtlas atlas = ImageAtlas::New(100, 100);
175
176   DALI_TEST_EQUALS(atlas.GetOccupancyRate(), 0.f, TEST_LOCATION);
177
178   Vector4 textureRect1;
179   atlas.Upload(textureRect1, gImage_34_RGBA, ImageDimensions(34, 34));
180   DALI_TEST_EQUALS(atlas.GetOccupancyRate(), 34.f * 34.f / 10000.f, 0.001f, TEST_LOCATION);
181
182   Vector4 textureRect2;
183   atlas.Upload(textureRect2, gImage_50_RGBA, ImageDimensions(50, 50));
184   DALI_TEST_EQUALS(atlas.GetOccupancyRate(), (34.f * 34.f + 50.f * 50.f) / 10000.f, 0.001f, TEST_LOCATION);
185
186   END_TEST;
187 }
188
189 int UtcDaliImageAtlasSetBrokenImage(void)
190 {
191   ToolkitTestApplication application;
192   unsigned int           size  = 200;
193   ImageAtlas             atlas = ImageAtlas::New(size, size);
194
195   // Set broken image
196   TestPlatformAbstraction& platform = application.GetPlatform();
197   platform.SetClosestImageSize(Vector2(34, 34));
198   atlas.SetBrokenImage(gImage_34_RGBA);
199
200   Vector4 textureRect;
201
202   // the empty image will be replaced with the broken image
203   platform.SetClosestImageSize(Vector2(20, 20));
204   atlas.Upload(textureRect, gEmptyImage);
205   DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
206
207   Rect<int> pixelArea = TextureCoordinateToPixelArea(textureRect, size);
208   DALI_TEST_EQUALS(pixelArea.width, 20, TEST_LOCATION);
209   DALI_TEST_EQUALS(pixelArea.height, 20, TEST_LOCATION);
210
211   END_TEST;
212 }
213
214 int UtcDaliImageAtlasUploadP(void)
215 {
216   ToolkitTestApplication application;
217   unsigned int           size  = 200;
218   ImageAtlas             atlas = ImageAtlas::New(size, size);
219
220   TraceCallStack& callStack = application.GetGlAbstraction().GetTextureTrace();
221   callStack.Reset();
222   callStack.Enable(true);
223
224   Vector4 textureRect1;
225   atlas.Upload(textureRect1, gImage_34_RGBA, ImageDimensions(34, 34));
226   Vector4 textureRect2;
227   atlas.Upload(textureRect2, gImage_50_RGBA, ImageDimensions(50, 50));
228   Vector4 textureRect3;
229   atlas.Upload(textureRect3, gImage_128_RGB, ImageDimensions(128, 128));
230
231   DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(3), true, TEST_LOCATION);
232
233   application.SendNotification();
234   application.Render(RENDER_FRAME_INTERVAL);
235
236   callStack.Enable(false);
237
238   Rect<int> pixelArea1 = TextureCoordinateToPixelArea(textureRect1, size);
239   DALI_TEST_EQUALS(pixelArea1.width, 34, TEST_LOCATION);
240   DALI_TEST_EQUALS(pixelArea1.height, 34, TEST_LOCATION);
241
242   TraceCallStack::NamedParams params;
243   params["width"] << pixelArea1.width;
244   params["height"] << pixelArea1.height;
245   params["xoffset"] << pixelArea1.x;
246   params["yoffset"] << pixelArea1.y;
247   DALI_TEST_CHECK(callStack.FindMethodAndParams("TexSubImage2D", params));
248
249   Rect<int> pixelArea2 = TextureCoordinateToPixelArea(textureRect2, size);
250   DALI_TEST_EQUALS(pixelArea2.width, 50, TEST_LOCATION);
251   DALI_TEST_EQUALS(pixelArea2.height, 50, TEST_LOCATION);
252
253   params.mParams.clear();
254   params["width"] << pixelArea2.width;
255   params["height"] << pixelArea2.height;
256   params["xoffset"] << pixelArea2.x;
257   params["yoffset"] << pixelArea2.y;
258   DALI_TEST_CHECK(callStack.FindMethodAndParams("TexSubImage2D", params));
259
260   Rect<int> pixelArea3 = TextureCoordinateToPixelArea(textureRect3, size);
261   DALI_TEST_EQUALS(pixelArea3.width, 128, TEST_LOCATION);
262   DALI_TEST_EQUALS(pixelArea3.height, 128, TEST_LOCATION);
263
264   params.mParams.clear();
265   params["width"] << pixelArea3.width;
266   params["height"] << pixelArea3.height;
267   params["xoffset"] << pixelArea3.x;
268   params["yoffset"] << pixelArea3.y;
269   DALI_TEST_CHECK(callStack.FindMethodAndParams("TexSubImage2D", params));
270
271   DALI_TEST_CHECK(!IsOverlap(pixelArea1, pixelArea2));
272   DALI_TEST_CHECK(!IsOverlap(pixelArea1, pixelArea3));
273   DALI_TEST_CHECK(!IsOverlap(pixelArea2, pixelArea3));
274
275   END_TEST;
276 }
277
278 int UtcDaliImageAtlasUploadWithObserver01(void)
279 {
280   ToolkitTestApplication application;
281   ImageAtlas             atlas = ImageAtlas::New(200, 200);
282
283   gCountOfTestFuncCall = 0;
284   TestUploadObserver uploadObserver;
285
286   Vector4 textureRect1;
287   atlas.Upload(textureRect1, gImage_34_RGBA, ImageDimensions(34, 34), FittingMode::DEFAULT, true, &uploadObserver);
288   Vector4 textureRect2;
289   atlas.Upload(textureRect2, gImage_50_RGBA, ImageDimensions(50, 50), FittingMode::DEFAULT, true, NULL);
290   Vector4 textureRect3;
291   atlas.Upload(textureRect3, gImage_128_RGB, ImageDimensions(128, 128), FittingMode::DEFAULT, true, &uploadObserver);
292
293   // waiting until all three images are loaded and uploaded to atlas
294   DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(3), true, TEST_LOCATION);
295   application.SendNotification();
296   application.Render(RENDER_FRAME_INTERVAL);
297
298   // Check that TestFunc is called twice
299   DALI_TEST_EQUALS(gCountOfTestFuncCall, 2, TEST_LOCATION);
300
301   END_TEST;
302 }
303
304 int UtcDaliImageAtlasUploadWithObserver02(void)
305 {
306   ToolkitTestApplication application;
307   ImageAtlas             atlas = ImageAtlas::New(200, 200);
308
309   gCountOfTestFuncCall               = 0;
310   TestUploadObserver* uploadObserver = new TestUploadObserver;
311
312   Vector4 textureRect1;
313   atlas.Upload(textureRect1, gImage_34_RGBA, ImageDimensions(34, 34), FittingMode::DEFAULT, true, uploadObserver);
314   Vector4 textureRect2;
315   atlas.Upload(textureRect2, gImage_50_RGBA, ImageDimensions(50, 50), FittingMode::DEFAULT, true, uploadObserver);
316   Vector4 textureRect3;
317   atlas.Upload(textureRect3, gImage_128_RGB, ImageDimensions(128, 128), FittingMode::DEFAULT, true, uploadObserver);
318
319   // destroy the object.
320   delete uploadObserver;
321
322   // waiting until all three images are loaded and uploaded to atlas
323   DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(3), true, TEST_LOCATION);
324
325   application.Render(RENDER_FRAME_INTERVAL);
326   application.SendNotification();
327
328   // Check that TestFunc is called twice
329   DALI_TEST_EQUALS(gCountOfTestFuncCall, 0, TEST_LOCATION);
330
331   END_TEST;
332 }
333
334 int UtcDaliImageAtlasUploadWithObserver03(void)
335 {
336   ToolkitTestApplication application;
337
338   gCountOfTestFuncCall               = 0;
339   TestUploadObserver* uploadObserver = new TestUploadObserver;
340
341   {
342     ImageAtlas atlas = ImageAtlas::New(200, 200);
343
344     Vector4 textureRect1;
345     atlas.Upload(textureRect1, gImage_34_RGBA, ImageDimensions(34, 34), FittingMode::DEFAULT, true, uploadObserver);
346     Vector4 textureRect2;
347     atlas.Upload(textureRect2, gImage_50_RGBA, ImageDimensions(50, 50), FittingMode::DEFAULT, true, uploadObserver);
348     Vector4 textureRect3;
349     atlas.Upload(textureRect3, gImage_128_RGB, ImageDimensions(128, 128), FittingMode::DEFAULT, true, uploadObserver);
350   }
351
352   //ImageAtlas is out of scope, so it will get destroyed
353
354   application.Render(RENDER_FRAME_INTERVAL);
355   application.SendNotification();
356   application.SendNotification();
357   application.Render(RENDER_FRAME_INTERVAL);
358
359   // Check that TestFunc is called twice
360   DALI_TEST_EQUALS(gCountOfTestFuncCall, 0, TEST_LOCATION);
361
362   END_TEST;
363 }
364
365 int UtcDaliImageAtlasRemove(void)
366 {
367   ToolkitTestApplication application;
368   unsigned int           size  = 100;
369   ImageAtlas             atlas = ImageAtlas::New(size, size);
370   Vector4                textureRect1;
371   atlas.Upload(textureRect1, gImage_34_RGBA, ImageDimensions(34, 34));
372
373   atlas.Remove(textureRect1);
374
375   Vector4 textureRect2;
376   atlas.Upload(textureRect2, gImage_50_RGBA, ImageDimensions(50, 50));
377
378   // one pixel gap
379   Rect<int> pixelArea = TextureCoordinateToPixelArea(textureRect2, size);
380   DALI_TEST_EQUALS(pixelArea.x, 0, TEST_LOCATION);
381   DALI_TEST_EQUALS(pixelArea.y, 0, TEST_LOCATION);
382
383   END_TEST;
384 }
385
386 int UtcDaliImageAtlasImageView(void)
387 {
388   ToolkitTestApplication application;
389
390   TraceCallStack& callStack = application.GetGlAbstraction().GetTextureTrace();
391   callStack.Reset();
392   callStack.Enable(true);
393
394   Property::Map imageMap1;
395
396   imageMap1[ImageVisual::Property::URL]            = gImage_34_RGBA;
397   imageMap1[ImageVisual::Property::DESIRED_HEIGHT] = 34;
398   imageMap1[ImageVisual::Property::DESIRED_WIDTH]  = 34;
399   imageMap1[ImageVisual::Property::ATLASING]       = true;
400
401   Property::Map imageMap2;
402
403   imageMap2[ImageVisual::Property::URL]            = gImage_50_RGBA;
404   imageMap2[ImageVisual::Property::DESIRED_HEIGHT] = 50;
405   imageMap2[ImageVisual::Property::DESIRED_WIDTH]  = 50;
406   imageMap2[ImageVisual::Property::ATLASING]       = true;
407
408   ImageView imageView1 = ImageView::New();
409   imageView1.SetProperty(ImageView::Property::IMAGE, imageMap1);
410
411   ImageView imageView2 = ImageView::New();
412   imageView2.SetProperty(ImageView::Property::IMAGE, imageMap2);
413
414   // ImageView doesn't do size negotiation properly: it only listens to OnSizeSet:
415   imageView1.SetProperty(Actor::Property::SIZE, Vector2(100, 100));
416   imageView2.SetProperty(Actor::Property::SIZE, Vector2(100, 100));
417   imageView1.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);
418   imageView2.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);
419
420   application.GetPlatform().SetClosestImageSize(Vector2(34, 34));
421   application.GetScene().Add(imageView1);
422   application.GetPlatform().SetClosestImageSize(Vector2(50, 50));
423   application.GetScene().Add(imageView2);
424
425   DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(2), true, TEST_LOCATION);
426
427   application.SendNotification();
428   application.Render(RENDER_FRAME_INTERVAL);
429
430   callStack.Enable(false);
431
432   TraceCallStack::NamedParams params1;
433   params1["width"] << 34;
434   params1["height"] << 34;
435   params1["xoffset"] << 0;
436   params1["yoffset"] << 0;
437
438   TraceCallStack::NamedParams params2;
439   params2["width"] << 50;
440   params2["height"] << 50;
441   params2["xoffset"] << 0;
442   params2["yoffset"] << 34;
443
444   DALI_TEST_EQUALS(callStack.FindMethodAndParams("TexSubImage2D", params1), true, TEST_LOCATION);
445   DALI_TEST_EQUALS(callStack.FindMethodAndParams("TexSubImage2D", params2), true, TEST_LOCATION);
446
447   callStack.Reset();
448   callStack.Enable(true);
449
450   // remove the imageView2 from stage, the second image will also be removed from atlas
451   // then the space on the atlas will be used by the third image added.
452   application.GetScene().Remove(imageView2);
453   application.SendNotification();
454   application.Render(RENDER_FRAME_INTERVAL);
455
456   Property::Map imageMap3;
457   imageMap3[ImageVisual::Property::URL]            = gImage_128_RGB;
458   imageMap3[ImageVisual::Property::DESIRED_HEIGHT] = 100;
459   imageMap3[ImageVisual::Property::DESIRED_WIDTH]  = 100;
460   imageMap3[ImageVisual::Property::ATLASING]       = true;
461
462   ImageView imageView3 = ImageView::New();
463   imageView3.SetProperty(ImageView::Property::IMAGE, imageMap3);
464
465   application.GetPlatform().SetClosestImageSize(Vector2(100, 100));
466   application.GetScene().Add(imageView3);
467
468   DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
469
470   application.SendNotification();
471   application.Render(RENDER_FRAME_INTERVAL);
472
473   callStack.Enable(false);
474
475   TraceCallStack::NamedParams params3;
476   params3["width"] << 100;
477   params3["height"] << 100;
478   params3["xoffset"] << 0;
479   params3["yoffset"] << 34;
480
481   DALI_TEST_EQUALS(callStack.FindMethodAndParams("TexSubImage2D", params3), true, TEST_LOCATION);
482
483   END_TEST;
484 }
485
486 int UtcDaliImageAtlasPackToAtlas(void)
487 {
488   ToolkitTestApplication application;
489
490   std::vector<PixelData> pixelDataContainer;
491   pixelDataContainer.push_back(CreatePixelData(20, 30));
492   pixelDataContainer.push_back(CreatePixelData(10, 10));
493   pixelDataContainer.push_back(CreatePixelData(45, 30));
494   pixelDataContainer.push_back(CreatePixelData(20, 20));
495
496   Dali::Vector<Vector4> textureRects;
497   Texture               texture = ImageAtlas::PackToAtlas(pixelDataContainer, textureRects);
498
499   //  --------------
500   //  |            |
501   //  |    45*30   |
502   //  |            |
503   //  --------------
504   //  | 20 |    | 20*20
505   //  |  * |____|
506   //  | 30 |  |  10*10
507   //  --------
508
509   DALI_TEST_EQUALS(texture.GetWidth(), 45, TEST_LOCATION);
510   DALI_TEST_EQUALS(texture.GetHeight(), 60, TEST_LOCATION);
511
512   Rect<int> pixelArea = TextureCoordinateToPixelArea(textureRects[0], texture.GetWidth(), texture.GetHeight());
513   DALI_TEST_EQUALS(pixelArea.x, 0, TEST_LOCATION);
514   DALI_TEST_EQUALS(pixelArea.y, 30, TEST_LOCATION);
515   DALI_TEST_EQUALS(pixelArea.width, 20, TEST_LOCATION);
516   DALI_TEST_EQUALS(pixelArea.height, 30, TEST_LOCATION);
517
518   pixelArea = TextureCoordinateToPixelArea(textureRects[1], texture.GetWidth(), texture.GetHeight());
519   DALI_TEST_EQUALS(pixelArea.x, 20, TEST_LOCATION);
520   DALI_TEST_EQUALS(pixelArea.y, 50, TEST_LOCATION);
521   DALI_TEST_EQUALS(pixelArea.width, 10, TEST_LOCATION);
522   DALI_TEST_EQUALS(pixelArea.height, 10, TEST_LOCATION);
523
524   pixelArea = TextureCoordinateToPixelArea(textureRects[2], texture.GetWidth(), texture.GetHeight());
525   DALI_TEST_EQUALS(pixelArea.x, 0, TEST_LOCATION);
526   DALI_TEST_EQUALS(pixelArea.y, 0, TEST_LOCATION);
527   DALI_TEST_EQUALS(pixelArea.width, 45, TEST_LOCATION);
528   DALI_TEST_EQUALS(pixelArea.height, 30, TEST_LOCATION);
529
530   pixelArea = TextureCoordinateToPixelArea(textureRects[3], texture.GetWidth(), texture.GetHeight());
531   DALI_TEST_EQUALS(pixelArea.x, 20, TEST_LOCATION);
532   DALI_TEST_EQUALS(pixelArea.y, 30, TEST_LOCATION);
533   DALI_TEST_EQUALS(pixelArea.width, 20, TEST_LOCATION);
534   DALI_TEST_EQUALS(pixelArea.height, 20, TEST_LOCATION);
535
536   END_TEST;
537 }