2 * Copyright (c) 2014 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 * @file image-scaling-irregular-grid-example.cpp
20 * @brief Demonstrates how to use image scaling modes when loading images.
22 * If an image is going to be drawn on-screen at a lower resolution than it is
23 * stored at on-disk, the scaling feature of the image loader can be used to
24 * reduce the image to save memory, improve performance, and potentially display
25 * a better small version of the image than if the default size were loaded.
27 * The functions CreateImage and CreateImageActor below show how to build an
28 * image using a scaling mode to have %Dali resize it during loading.
30 * This demo defaults to the ScaleToFill mode of ImageAttributes which makes
31 * sure that every pixel in the loaded image is filled with a source colour
32 * from the image's central region while losing the minimum number of pixels
34 * It is the best option for producing thumbnails of input images that have
35 * diverse aspect ratios.
37 * The other four scaling modes of dali can be cycled-through for the whole
38 * grid using the button in the top-right of the toolbar.
39 * A single image can be cycled by clicking the image directly.
41 * @see CreateImage CreateImageActor
47 #include <dali-toolkit/dali-toolkit.h>
51 #include "grid-flags.h"
52 #include "shared/view.h"
55 using namespace Dali::Toolkit;
56 using namespace Dali::Demo;
61 /** Controls the output of application logging. */
62 //#define DEBUG_PRINT_DIAGNOSTICS;
64 const char* BACKGROUND_IMAGE( DALI_IMAGE_DIR "background-gradient.jpg" );
65 const char* TOOLBAR_IMAGE( DALI_IMAGE_DIR "top-bar.png" );
66 const char* APPLICATION_TITLE( "Image Scaling Modes" );
67 const char* TOGGLE_SCALING_IMAGE( DALI_IMAGE_DIR "icon-change.png" );
69 /** The width of the grid in whole grid cells. */
70 const unsigned GRID_WIDTH = 9;
71 /** Limit the grid to be no higher than this in units of a cell. */
72 const unsigned GRID_MAX_HEIGHT = 600;
74 /** The space between the edge of a grid cell and the image embedded within it. */
75 const unsigned GRID_CELL_PADDING = 4;
77 /** The aspect ratio of cells in the image grid. */
78 const float CELL_ASPECT_RATIO = 1.33333333333333333333f;
80 const ImageAttributes::ScalingMode DEFAULT_SCALING_MODE = ImageAttributes::ScaleToFill;
82 /** The number of times to spin an image on touching, each spin taking a second.*/
83 const float SPIN_DURATION = 1.0f;
85 /** The target image sizes in grid cells. */
86 const Vector2 IMAGE_SIZES[] = {
94 // Large, tall configuration:
95 Vector2( GRID_WIDTH / 2, GRID_WIDTH + GRID_WIDTH / 2 ),
96 // Large, square-ish images to show shrink-to-fit well with wide and tall images:
97 Vector2( GRID_WIDTH / 2, GRID_WIDTH / 2.0f * CELL_ASPECT_RATIO + 0.5f ),
98 Vector2( GRID_WIDTH - 2, (GRID_WIDTH - 2) * CELL_ASPECT_RATIO + 0.5f ),
100 const unsigned NUM_IMAGE_SIZES = sizeof(IMAGE_SIZES) / sizeof(IMAGE_SIZES[0]);
102 /** Images to load into the grid. These are mostly large and non-square to
103 * show the scaling. */
104 const char* IMAGE_PATHS[] = {
106 DALI_IMAGE_DIR "dali-logo.png",
107 DALI_IMAGE_DIR "com.samsung.dali-demo.ico",
108 DALI_IMAGE_DIR "square_primitive_shapes.bmp",
109 DALI_IMAGE_DIR "gallery-large-14.wbmp",
111 // Images that show aspect ratio changes clearly in primitive shapes:
113 DALI_IMAGE_DIR "portrait_screen_primitive_shapes.gif",
114 DALI_IMAGE_DIR "landscape_screen_primitive_shapes.gif",
116 // Images from other demos that are tall, wide or just large:
118 DALI_IMAGE_DIR "gallery-large-1.jpg",
119 DALI_IMAGE_DIR "gallery-large-2.jpg",
120 DALI_IMAGE_DIR "gallery-large-3.jpg",
121 DALI_IMAGE_DIR "gallery-large-4.jpg",
122 DALI_IMAGE_DIR "gallery-large-5.jpg",
123 DALI_IMAGE_DIR "gallery-large-6.jpg",
124 DALI_IMAGE_DIR "gallery-large-7.jpg",
125 DALI_IMAGE_DIR "gallery-large-8.jpg",
126 DALI_IMAGE_DIR "gallery-large-9.jpg",
127 DALI_IMAGE_DIR "gallery-large-10.jpg",
128 DALI_IMAGE_DIR "gallery-large-11.jpg",
129 DALI_IMAGE_DIR "gallery-large-12.jpg",
130 DALI_IMAGE_DIR "gallery-large-13.jpg",
131 DALI_IMAGE_DIR "gallery-large-14.jpg",
132 DALI_IMAGE_DIR "gallery-large-15.jpg",
133 DALI_IMAGE_DIR "gallery-large-16.jpg",
134 DALI_IMAGE_DIR "gallery-large-17.jpg",
135 DALI_IMAGE_DIR "gallery-large-18.jpg",
136 DALI_IMAGE_DIR "gallery-large-19.jpg",
137 DALI_IMAGE_DIR "gallery-large-20.jpg",
138 DALI_IMAGE_DIR "gallery-large-21.jpg",
140 DALI_IMAGE_DIR "background-1.jpg",
141 DALI_IMAGE_DIR "background-2.jpg",
142 DALI_IMAGE_DIR "background-3.jpg",
143 DALI_IMAGE_DIR "background-4.jpg",
144 DALI_IMAGE_DIR "background-5.jpg",
145 DALI_IMAGE_DIR "background-blocks.jpg",
146 DALI_IMAGE_DIR "background-magnifier.jpg",
148 DALI_IMAGE_DIR "background-1.jpg",
149 DALI_IMAGE_DIR "background-2.jpg",
150 DALI_IMAGE_DIR "background-3.jpg",
151 DALI_IMAGE_DIR "background-4.jpg",
152 DALI_IMAGE_DIR "background-5.jpg",
153 DALI_IMAGE_DIR "background-blocks.jpg",
154 DALI_IMAGE_DIR "background-magnifier.jpg",
156 DALI_IMAGE_DIR "book-landscape-cover-back.jpg",
157 DALI_IMAGE_DIR "book-landscape-cover.jpg",
158 DALI_IMAGE_DIR "book-landscape-p1.jpg",
159 DALI_IMAGE_DIR "book-landscape-p2.jpg",
161 DALI_IMAGE_DIR "book-portrait-cover.jpg",
162 DALI_IMAGE_DIR "book-portrait-p1.jpg",
163 DALI_IMAGE_DIR "book-portrait-p2.jpg",
166 const unsigned NUM_IMAGE_PATHS = sizeof(IMAGE_PATHS) / sizeof(IMAGE_PATHS[0]) - 1u;
172 * @param[in] filename The path of the image.
173 * @param[in] width The width of the image in pixels.
174 * @param[in] height The height of the image in pixels.
175 * @param[in] scalingMode The mode to use when scaling the image to fit the desired dimensions.
177 Image CreateImage(const std::string& filename, unsigned int width, unsigned int height, ImageAttributes::ScalingMode scalingMode )
179 #ifdef DEBUG_PRINT_DIAGNOSTICS
180 fprintf( stderr, "CreateImage(%s, %u, %u, scalingMode=%u)\n", filename.c_str(), width, height, unsigned( scalingMode ) );
182 ImageAttributes attributes;
184 attributes.SetSize( width, height );
185 attributes.SetScalingMode( scalingMode );
186 attributes.SetFilterMode( ImageAttributes::BoxThenLinear );
187 Image image = ResourceImage::New( filename, attributes );
192 * Creates an ImageActor
194 * @param[in] filename The path of the image.
195 * @param[in] width The width of the image in pixels.
196 * @param[in] height The height of the image in pixels.
197 * @param[in] scalingMode The mode to use when scaling the image to fit the desired dimensions.
199 ImageActor CreateImageActor(const std::string& filename, unsigned int width, unsigned int height, ImageAttributes::ScalingMode scalingMode )
201 Image img = CreateImage( filename, width, height, scalingMode );
202 ImageActor actor = ImageActor::New( img );
203 actor.SetName( filename );
204 actor.SetParentOrigin(ParentOrigin::CENTER);
205 actor.SetAnchorPoint(AnchorPoint::CENTER);
210 /** Cycle the scaling mode options. */
211 ImageAttributes::ScalingMode NextMode( const ImageAttributes::ScalingMode oldMode )
213 ImageAttributes::ScalingMode newMode = ImageAttributes::ShrinkToFit;
216 case ImageAttributes::ShrinkToFit:
217 newMode = ImageAttributes::ScaleToFill;
219 case ImageAttributes::ScaleToFill:
220 newMode = ImageAttributes::FitWidth;
222 case ImageAttributes::FitWidth:
223 newMode = ImageAttributes::FitHeight;
225 case ImageAttributes::FitHeight:
226 newMode = ImageAttributes::ShrinkToFit;
233 * Bundle an image path with the rectangle to pack it into.
235 struct ImageConfiguration
237 ImageConfiguration( const char * const path, const Vector2 dimensions ) :
239 dimensions( dimensions )
246 * Post-layout image data.
248 struct PositionedImage
250 PositionedImage(ImageConfiguration& configuration, unsigned cellX, unsigned cellY, Vector2 imageGridDims) :
251 configuration( configuration ),
254 imageGridDims( imageGridDims )
257 ImageConfiguration configuration;
260 Vector2 imageGridDims;
266 * @brief The main class of the demo.
268 class ImageScalingIrregularGridController : public ConnectionTracker
272 ImageScalingIrregularGridController( Application& application )
273 : mApplication( application ),
276 std::cout << "ImageScalingScaleToFillController::ImageScalingScaleToFillController" << std::endl;
278 // Connect to the Application's Init signal
279 mApplication.InitSignal().Connect( this, &ImageScalingIrregularGridController::Create );
282 ~ImageScalingIrregularGridController()
284 // Nothing to do here.
288 * One-time setup in response to Application InitSignal.
290 void Create( Application& application )
292 std::cout << "ImageScalingScaleToFillController::Create" << std::endl;
294 DemoHelper::RequestThemeChange();
296 // Get a handle to the stage:
297 Stage stage = Stage::GetCurrent();
299 // Connect to input event signals:
300 stage.KeyEventSignal().Connect(this, &ImageScalingIrregularGridController::OnKeyEvent);
302 // Hide the indicator bar
303 mApplication.GetWindow().ShowIndicator(Dali::Window::INVISIBLE);
305 // Create a default view with a default tool bar:
306 mContentLayer = DemoHelper::CreateView( mApplication,
313 // Create an image scaling toggle button. (right of toolbar)
314 Image toggleScalingImage = ResourceImage::New( TOGGLE_SCALING_IMAGE );
315 Toolkit::PushButton toggleScalingButton = Toolkit::PushButton::New();
316 toggleScalingButton.SetBackgroundImage( toggleScalingImage );
317 toggleScalingButton.ClickedSignal().Connect( this, &ImageScalingIrregularGridController::OnToggleScalingTouched );
318 mToolBar.AddControl( toggleScalingButton, DemoHelper::DEFAULT_VIEW_STYLE.mToolBarButtonPercentage, Toolkit::Alignment::HorizontalRight, DemoHelper::DEFAULT_MODE_SWITCH_PADDING );
320 SetTitle( APPLICATION_TITLE );
322 // Build the main content of the widow:
323 PopulateContentLayer( DEFAULT_SCALING_MODE );
327 * Build the main part of the application's view.
329 void PopulateContentLayer( const ImageAttributes::ScalingMode scalingMode )
331 Stage stage = Stage::GetCurrent();
332 Vector2 stageSize = stage.GetSize();
335 Actor imageField = BuildImageField( stageSize.x, GRID_WIDTH, GRID_MAX_HEIGHT, scalingMode, fieldHeight );
337 mScrollView = ScrollView::New();
339 mScrollView.ScrollStartedSignal().Connect( this, &ImageScalingIrregularGridController::OnScrollStarted );
340 mScrollView.ScrollCompletedSignal().Connect( this, &ImageScalingIrregularGridController::OnScrollCompleted );
342 mScrollView.EnableScrollComponent( Scrollable::VerticalScrollBar );
343 mScrollView.EnableScrollComponent( Scrollable::HorizontalScrollBar );
345 mScrollView.SetAnchorPoint(AnchorPoint::CENTER);
346 mScrollView.SetParentOrigin(ParentOrigin::CENTER);
348 mScrollView.SetSize( stageSize );//Vector2( stageSize.width, fieldHeight ) );//stageSize );
349 mScrollView.SetAxisAutoLock( true );
350 mScrollView.SetAxisAutoLockGradient( 1.0f );
352 // Restrict scrolling to mostly vertical only, but with some horizontal wiggle-room:
354 RulerPtr rulerX = new FixedRuler( stageSize.width ); //< Pull the view back to the grid's centre-line when touch is release using a snapping ruler.
355 rulerX->SetDomain( RulerDomain( stageSize.width * -0.125f, stageSize.width * 1.125f ) ); //< Scroll slightly left/right of image field.
356 mScrollView.SetRulerX ( rulerX );
358 RulerPtr rulerY = new DefaultRuler(); //stageSize.height ); //< Snap in multiples of a screen / stage height
359 rulerY->SetDomain( RulerDomain( - fieldHeight * 0.5f + stageSize.height * 0.5f - GRID_CELL_PADDING, fieldHeight * 0.5f + stageSize.height * 0.5f + GRID_CELL_PADDING ) );
360 mScrollView.SetRulerY ( rulerY );
362 mContentLayer.Add( mScrollView );
363 mScrollView.Add( imageField );
364 mGridActor = imageField;
366 // Scroll to top of grid so first images loaded are on-screen:
367 mScrollView.ScrollTo( Vector3( 0, -1000000, 0 ) );
371 * Build a field of images scaled into a variety of shapes from very wide,
372 * through square, to very tall. The images are direct children of the Dali::Actor
375 Actor BuildImageField( const float fieldWidth,
376 const unsigned gridWidth,
377 const unsigned maxGridHeight,
378 ImageAttributes::ScalingMode scalingMode,
379 float & outFieldHeight )
381 // Generate the list of image configurations to be fitted into the field:
383 std::vector<ImageConfiguration> configurations;
384 configurations.reserve( NUM_IMAGE_PATHS * NUM_IMAGE_SIZES );
385 for( unsigned imageIndex = 0; imageIndex < NUM_IMAGE_PATHS; ++imageIndex )
387 for( unsigned dimensionsIndex = 0; dimensionsIndex < NUM_IMAGE_SIZES; ++ dimensionsIndex )
389 configurations.push_back( ImageConfiguration( IMAGE_PATHS[imageIndex], IMAGE_SIZES[dimensionsIndex] ) );
392 // Stir-up the list to get some nice irregularity in the generated field:
393 std::random_shuffle( configurations.begin(), configurations.end() );
394 std::random_shuffle( configurations.begin(), configurations.end() );
396 // Place the images in the grid:
398 std::vector<ImageConfiguration>::iterator config, end;
399 GridFlags grid( gridWidth, maxGridHeight );
400 std::vector<PositionedImage> placedImages;
402 for( config = configurations.begin(), end = configurations.end(); config != end; ++config )
404 unsigned cellX, cellY;
405 Vector2 imageGridDims;
407 // Allocate a region of the grid for the image:
408 bool allocated = grid.AllocateRegion( config->dimensions, cellX, cellY, imageGridDims );
411 #ifdef DEBUG_PRINT_DIAGNOSTICS
412 fprintf( stderr, "Failed to allocate image in grid with dims (%f, %f) and path: %s.\n", config->dimensions.x, config->dimensions.y, config->path );
417 placedImages.push_back( PositionedImage( *config, cellX, cellY, imageGridDims ) );
419 DALI_ASSERT_DEBUG( grid.DebugCheckGridValid() && "Cells were set more than once, indicating erroneous overlap in placing images on the grid." );
420 const unsigned actualGridHeight = grid.GetHighestUsedRow() + 1;
422 // Take the images images in the grid and turn their logical locations into
423 // coordinates in a frame defined by a parent actor:
425 Actor gridActor = Actor::New();
426 gridActor.SetRelayoutEnabled( true );
427 gridActor.SetResizePolicy( FILL_TO_PARENT, ALL_DIMENSIONS );
428 gridActor.SetParentOrigin( ParentOrigin::CENTER );
429 gridActor.SetAnchorPoint( AnchorPoint::CENTER );
431 // Work out the constants of the grid and cell dimensions and positions:
432 const float cellWidth = fieldWidth / gridWidth;
433 const float cellHeight = cellWidth / CELL_ASPECT_RATIO;
434 const Vector2 cellSize = Vector2( cellWidth, cellHeight );
435 outFieldHeight = actualGridHeight * cellHeight;
436 const Vector2 gridOrigin = Vector2( -fieldWidth * 0.5f, -outFieldHeight * 0.5 );
438 // Build the image actors in their right locations in their parent's frame:
439 for( std::vector<PositionedImage>::const_iterator i = placedImages.begin(), end = placedImages.end(); i != end; ++i )
441 const PositionedImage& imageSource = *i;
442 const Vector2 imageSize = imageSource.imageGridDims * cellSize - Vector2( GRID_CELL_PADDING * 2, GRID_CELL_PADDING * 2 );
443 const Vector2 imageRegionCorner = gridOrigin + cellSize * Vector2( imageSource.cellX, imageSource.cellY );
444 const Vector2 imagePosition = imageRegionCorner + Vector2( GRID_CELL_PADDING , GRID_CELL_PADDING ) + imageSize * 0.5f;
446 ImageActor image = CreateImageActor( imageSource.configuration.path, imageSize.x, imageSize.y, scalingMode );
447 image.SetPosition( Vector3( imagePosition.x, imagePosition.y, 0 ) );
448 image.SetSize( imageSize );
449 image.TouchedSignal().Connect( this, &ImageScalingIrregularGridController::OnTouchImage );
450 mScalingModes[image.GetId()] = scalingMode;
451 mSizes[image.GetId()] = imageSize;
453 gridActor.Add( image );
460 * Upon Touching an image (Release), change its scaling mode and make it spin, provided we're not scrolling.
461 * @param[in] actor The actor touched
462 * @param[in] event The TouchEvent.
464 bool OnTouchImage( Actor actor, const TouchEvent& event )
466 if( (event.points.size() > 0) && (!mScrolling) )
468 TouchPoint point = event.points[0];
469 if(point.state == TouchPoint::Up)
471 // Spin the image a few times:
472 Animation animation = Animation::New(SPIN_DURATION);
473 animation.RotateBy( actor, Degree(360.0f * SPIN_DURATION), Vector3::XAXIS, AlphaFunctions::EaseOut);
476 // Change the scaling mode:
477 const unsigned id = actor.GetId();
478 ImageAttributes::ScalingMode newMode = NextMode( mScalingModes[id] );
479 const Vector2 imageSize = mSizes[actor.GetId()];
481 ImageActor imageActor = ImageActor::DownCast( actor );
482 Image oldImage = imageActor.GetImage();
483 Image newImage = CreateImage( ResourceImage::DownCast(oldImage).GetUrl(), imageSize.width + 0.5f, imageSize.height + 0.5f, newMode );
484 imageActor.SetImage( newImage );
485 mScalingModes[id] = newMode;
492 * Main key event handler.
493 * Quit on escape key.
495 void OnKeyEvent(const KeyEvent& event)
497 if( event.state == KeyEvent::Down )
499 if( IsKey( event, Dali::DALI_KEY_ESCAPE )
500 || IsKey( event, Dali::DALI_KEY_BACK ) )
508 * Signal handler, called when the 'Scaling' button has been touched.
510 * @param[in] button The button that was pressed.
512 bool OnToggleScalingTouched( Button button )
514 const unsigned numChildren = mGridActor.GetChildCount();
516 for( unsigned i = 0; i < numChildren; ++i )
518 ImageActor gridImageActor = ImageActor::DownCast( mGridActor.GetChildAt( i ) );
521 // Cycle the scaling mode options:
522 const Vector2 imageSize = mSizes[gridImageActor.GetId()];
523 ImageAttributes::ScalingMode newMode = NextMode( mScalingModes[gridImageActor.GetId()] );
524 Image oldImage = gridImageActor.GetImage();
525 Image newImage = CreateImage(ResourceImage::DownCast(oldImage).GetUrl(), imageSize.width, imageSize.height, newMode );
526 gridImageActor.SetImage( newImage );
528 mScalingModes[gridImageActor.GetId()] = newMode;
530 SetTitle( std::string( newMode == ImageAttributes::ShrinkToFit ? "ShrinkToFit" : newMode == ImageAttributes::ScaleToFill ? "ScaleToFill" : newMode == ImageAttributes::FitWidth ? "FitWidth" : "FitHeight" ) );
537 * Sets/Updates the title of the View
538 * @param[in] title The new title for the view.
540 void SetTitle(const std::string& title)
544 mTitleActor = DemoHelper::CreateToolBarLabel( "" );
545 // Add title to the tool bar.
546 mToolBar.AddControl( mTitleActor, DemoHelper::DEFAULT_VIEW_STYLE.mToolBarTitlePercentage, Alignment::HorizontalCenter );
549 mTitleActor.SetProperty( TextLabel::Property::TEXT, title );
553 * When scroll starts (i.e. user starts to drag scrollview),
554 * note this state (mScrolling = true)
555 * @param[in] position Current Scroll Position
557 void OnScrollStarted( const Vector3& position )
563 * When scroll starts (i.e. user stops dragging scrollview, and scrollview has snapped to destination),
564 * note this state (mScrolling = false).
565 * @param[in] position Current Scroll Position
567 void OnScrollCompleted( const Vector3& position )
573 Application& mApplication;
575 Layer mContentLayer; ///< The content layer (contains non gui chrome actors)
576 Toolkit::View mView; ///< The View instance.
577 Toolkit::ToolBar mToolBar; ///< The View's Toolbar.
578 TextLabel mTitleActor; ///< The Toolbar's Title.
579 Actor mGridActor; ///< The container for the grid of images
580 ScrollView mScrollView; ///< ScrollView UI Component
581 bool mScrolling; ///< ScrollView scrolling state (true = scrolling, false = stationary)
582 std::map<unsigned, ImageAttributes::ScalingMode> mScalingModes; ///< Stores the current scaling mode of each image, keyed by image actor id.
583 std::map<unsigned, Vector2> mSizes; ///< Stores the current size of each image, keyed by image actor id.
586 void RunTest( Application& application )
588 ImageScalingIrregularGridController test( application );
590 application.MainLoop();
593 /** Entry point for Linux & Tizen applications */
594 int main( int argc, char **argv )
596 Application application = Application::New( &argc, &argv );
598 RunTest( application );