Using Views: Warp Group

The Mosaic class Views::WarpGroup implements a graphical object specialized to display a perspective correct projection of the image of an existing GUI component (the texture) onto a quad (four corners polygon). In this manner multiple images of the original component can appear simultaneously at various screen locations, every with a different 2D or 3D transformation. This so-called Warp Group view can be used to compose the appearance of a GUI component, in particular to add fancy 2D and 3D effects to it. The following screenshot demonstrates few examples of how Warp Group views appear in the canvas area of Composer (and accordingly on the screen in your target device):

The following sections are intended to provide you an introduction and useful tips of how to work with the Warp Group view. For the complete reference please see the documentation of the Views::WarpGroup class.

IMPORTANT

Please note, if your target device doesn't provide any dedicated graphics hardware for texture mapping, the Warp Group view performs all operations by the CPU, which may impact the performance of your application significantly if you use this view extensively.

Add new Warp Group view

To add a new Warp Group view just at the design time of a GUI component do following:

First ensure that the Templates window is visible.

In Templates window switch to the folder Views.

In the folder locate the template Warp Group.

Drag & Drop the template into the canvas area of the Composer window:

Eventually name the new added Warp Group view.

Inspect the Warp Group view

As long as the Warp Group view is selected you can inspect and modify its properties conveniently in the Inspector window as demonstrated with the property Point1 in the screenshot below:

This is in so far worth mentioning as all following sections describe diverse features of the Warp Group view by explicitly referring to its corresponding properties. If you are not familiar with the concept of a property and the usage of Inspector window, please read first the preceding chapter Compositing component appearance.

Arrange the Warp Group view

The Warp view maps the four corners of the original image (top-left, top-right, bottom-right and bottom-left) on its own four corners numbered 1 to 4. Being a quad (four corners polygon) view, the position of each of its corners can be controlled individually. You can imagine that depending on the resulting quad shape, its orientation and size, the projected image will appear scaled, stretched, rotated, mirrored or even perspectively distorted. The following figure demonstrates the idea and the relation between the corners:

Once added, you can freely move the Warp Group view, or you simply grab one of its corners and resize it in this way. You can control the position of the corners also by directly modifying the corresponding properties Point1, Point2, Point3 and Point4. If you want the Warp Group view to appear behind other views you can reorder it explicitly.

Please note the restriction of the Warp Group view expecting its shape to be convex. If you arrange the corners so they span a concave polygon, the view is not able to project the image and remains empty. Similarly, arranging three of the corners on a common straight line or crossing over the view edges will result in an empty view:

Connect the Warp Group view with a GUI component

The content to display within a Warp Group view is determined by its property Group. Usually, you initialize this property with a GUI component existing already as an embedded object within the component you are currently editing in Composer (in other words, with a sibling member of the Warp Group view). With the Inspector Assistant you can conveniently select the right object when you edit the initialization expression for the property Group.

Once established the relation between the Warp Group view and a component, you have also to activate the buffered mode for the original component. Without this, the Warp Group view will remain empty. To control the buffered mode, the GUI component provides a property Buffered:

Select the component you have connected to the Warp Group view.

In Inspector locate its property Buffered.

Change this property to true.

From technical point of view, you can initialize the property Group with any instance of a class descended from the Mosaic class Core::Group, except this as representing the currently edited component, since a component can't display a projection of itself recursively. It is even not necessary for the affected component to be visible or be a part of the current screen composition. You can, for example, create a new instance of a GUI component and assign it exclusively to a Warp Group view:

// Create a new instance of a component
var Core::Group component = new SomeUnit::SomeGuiComponentClass;

// Activate the buffered mode for the just created component instance
component.Buffered = true;

// Assign it to a Warp Group view
SomeWarpGroupView.Group = component;

In the above example, the new created component appears within the area of the SomeWarpGroupView view as if it were a part of the screen composition. In reality, the Warp Group view displays only an image of the original component. Using the above example code as basis, you can implement fancy fading animation effects to perform with the projected component image.

CAUTION

Please consider, that with activating the buffered mode, the component reserves memory for an internal bitmap to store its image inside. The size of the bitmap corresponds to the actual size of the component. In other words, a 200x100 pixel large GUI component will be buffered with a 200x100 pixel large bitmap. Assuming you are working with the RGBA4444 (16-bit per pixel) Platform Package, then the bitmap would occupy ~40 KB of the RAM. Thus, if RAM is a scarce resource in your target device, you should use the buffering (and thus the Warp Group view) with prudence.

Determine the source area

Per default the Warp Group view projects the entire image of the assigned component. With the property SourceArea you can specify a smaller rectangular area of the image to use. In other words, you can instruct the Warp Group view to display a section of the associated component instead of the entire component.

The property SourceArea expects a valid rect operand determining the area relative to the top-left corner of the image. By initializing this property with an empty rect <0,0,0,0>, the Warp Group restores its default behavior and projects the entire content of the image. For example (the gray borders indicate the source areas of the image as well as the polygons onto them they are projected):

Control the bilinear filtering

Bilinear filtering is a technique to smooth an image when it is scaled, stretched or even rotated. The problem: through the distortion, the center of a pixel in the resulting image does not necessarily correspond to the center of a pixel in the original content. The simply copying of the pixel color value results then in an ugly image with rough pixel steps. The bilinear filter helps to improve the results by interpolating the color values of the pixels lying around the mapped position.

The usage of the bilinear filter is controlled by the property Quality. Per default the filter is enabled. By initializing Quality with the value false you can deactivate it again. The following figure demonstrates the results depending on the current setting of the property:

TIP

Despite the advantages, you should consider, that with the bilinear filter the image operations are more computing intensive. Thus, if your target device is not powerful nor doesn't provide any dedicated graphics hardware, deactivating the filter may improve the performance.

Modulate the opacity of the displayed image

Using the property Opacity you can modulate the original opacity of the displayed component image, so that it appears semi-transparent even if the original content was opaque. The valid values for the property Opacity are 0 .. 255, whereby the smaller the value the more transparent the resulting image. For example:

Alternatively you can specify for every corner (Point1, Point2, Point3, Point4) an individual opacity value resulting in the component image being modulated by an opacity gradient. For this purpose modify the corresponding properties Opacity1, Opacity2, Opacity3 and Opacity4. For example:

Perform 2D rotate and scale operations

When you want the image of the associated component to appear scaled and/or rotated in 2D space, the manually arranging of the view corners can quickly become inaccurate and inconvenient. More sophisticated is to instruct the Warp Group view to calculate automatically the position of the corners from a given scale factor and rotation angle. This is in particular ideal for all kinds of analog gauge and clock applications.

The Warp Group view implements for this purpose a method called RotateAndScale(). This method expects in its parameters the rotation angle, the scaling factors and the position within the destination component where to map the anchor (the pivot point) of the original image. Knowing this, the method calculates the coordinates of the four corners and arranges the view accordingly.

Before you use this method, you should understand the concept of the anchor position. It determines the pixel within the image around which you intend to rotate and scale it. You can imagine it as the fixed origin of an image local coordinate system. The anchor position is specified in the property SourceAnchor as a distance relative to the top-left corner of the source area. For example, if you want the entire image to rotate around its center, you initialize the property SourceAnchor with the position of the corresponding pixel in the center of the image:

Besides the source anchor position, you should also understand the concept of the corresponding destination position. It determines the position around which the image should be scaled or rotated within the component, the Warp Group view belongs to. In other words, this is the position on the screen where you want the source anchor of the image to appear. The following figure demonstrates the relation between the both positions:

The following Chora code demonstrates an example of how the image associated to a Warp Group view is scaled and rotated around its anchor position and displayed in the center of the component by using the above explained method RotateAndScale():

// First query the coordinates of the position in the center of
// the current component. This is the destination position where
// to map the resulting image.
var point destPos = Bounds.orect.center;

// The desired rotation angle
var float angle  = 30.0;

// The desired scaling factor (120 %)
var float scale  = 1.2;

// Instruct the Warp Group view to calculate its corners, so that
// the original image appears scaled by 120% and rotated 30° around
// the position destPos within the current component.
SomeWarpGroupView.RotateAndScale( destPos, angle, scale, scale );

DOWNLOAD EXAMPLE

Please note, the example presents eventually features available as of version 8.10

Perform 3D rotate, scale and translate operations

Similarly to the technique described in the section above of how to 2D rotate and scale, you can also perform 3D transformations on the component image. In particular you can rotate, scale and translate it along the X-, Y-, Z-axes. Even various transformation steps can be combined together resulting in real looking 3D effects.

If you are not yet familiar with the concepts of anchor and destination position, please read first the section above.

The Warp Group view implements a method called Warp3D(). This method expects two parameters: the destination position where to map the image source anchor and a matrix object. With the matrix object you provide precise mathematical description about the desired transformation. In other words, the matrix determines how individual pixel from the image are projected on the screen.

Before you get to use the Warp3D() method, you should understand how to initialize and manage the matrix objects. The corresponding functionality is implemented in the Mosaic class Graphics::WarpMatrix. The first step is thus to create a new instance of the matrix class. Then you call one of the below described methods to specify the desired transformation:

Method

Description

Rotate()

Applies to the matrix a rotation operation around the X-, Y- and/or Z-axes. The rotation angles are specified in degrees.

Scale()

Applies to the matrix a scale operation along the X-, Y- and/or Z-axes.

Translate()

Applies to the matrix a translate operation along the X-, Y- and/or Z-axes.

Calling several of these methods in succession results in a composite transformation. For example, if you want the image to rotate around the Z-axis and be scaled by 120% (identical to the example in the section above), implement following code:

// First query the coordinates of the position in the center of
// the current component. This is the destination position where
// to map the resulting image.
var point destPos = Bounds.orect.center;

// The desired rotation angle
var float angle  = 30.0;

// The desired scaling factor (120 %)
var float scale  = 1.2;

// Create a new matrix object
var Graphics::WarpMatrix matrix = new Graphics::WarpMatrix;

// Apply the desired rotation to it. Here we rotate around the
// Z-axis only. The angles for X and Y are 0 degree.
matrix.Rotate( 0.0, 0.0, -angle );

// Apply the desired scaling to it. Here we scale along the
// X- and Y-axis. The scaling along the Z-axis remains unchanged: 1:1
matrix.Scale( scale, scale, 1.0 );

// Instruct the Warp Group view to calculate its corners from the
// prepared matrix object relative to the position destPos within
// the current component.
SomeWarpGroupView.Warp3D( destPos, matrix );

Please note, the orientation of the axes in Embedded Wizard coordinate systems differs from the you know from the math lessons. In particular, the Y-axis points down and the Z-axis points into the background of the screen. You can consider the coordinate system being rotated by 180° around the X-axis. Accordingly, to apply a counterclockwise rotation operation around the Z-axis, you have to calculate with a negative angle as done in the above code example.

Mathematically, with the matrix you describe a transformation to apply on a coordinate system. Scaling along X-axis stretches the coordinate system accordingly. Rotating around Z-axis rotates the coordinate system. With every invocation of the above described matrix methods, a new transformation is applied to the current version of the coordinate system resulting in a new version of it. Thus you should consider all the executed transformations as cumulative and not individual. Being such it is essential in which order you perform them.

Imagine, you intend to combine the translation along the X-axis with the rotation around the Z-axis. You have thus two possibilities: you can rotate first and then translate, or you translate first and then rotate. Depending on this order, you will get completely different results:

In the real world, when you look at an object, you perceive it differently depending on the distance between it and your eyes. A flat picture, for example, appears perspectively distorted, when you rotate it around its X- or Y- axis. The closer the picture, the stronger the resulting perspective distortion.

Similarly, the implementation of the Graphics::WarpMatrix class provides a property called EyeDistance. Here you specify the position of the potential viewer relative to the origin of the coordinate system just before performing any transformation on it. The distance is expressed in pixel. For example, the following figure demonstrates two versions of the same image rotated by 45° around the X-axis. In the first case, the matrix was initialized with larger value for the EyeDistance property, in the second case with a smaller value:

The corresponding Chora code for this example:

// First query the coordinates of the position in the center of
// the current component. This is the destination position where
// to map the resulting image.
var point destPos = Bounds.orect.center;

// Create a new matrix object
var Graphics::WarpMatrix matrix = new Graphics::WarpMatrix;

// Apply the desired rotation to it. Here we rotate around the
// X-axis only. The angles for Y and Z are 0 degree.
matrix.Rotate( -45.0, 0.0, 0.0 );

// Determine the eye distance.
matrix.EyeDistance = 300.0;

// Instruct the Warp Group view to calculate its corners from the
// prepared matrix object relative to the position destPos within
// the current component.
SomeWarpGroupView.Warp3D( destPos, matrix );

DOWNLOAD EXAMPLE

Please note, the example presents eventually features available as of version 8.10

Please note the restriction of the Warp Group view expecting the image, after being 3D transformed, to lie completely in front of the specified viewer. For example, when you rotate a large image around its X- or Y-axis, the image edges may reach or even pass over the position of the viewer. If parts of the projected image lie behind the viewer, the Warp Group view is not able to perform the projection and remains empty. The following figure demonstrates in a side view the described rotation situation:

IMPORTANT

Please note, the matrix property EyeDistance is per default initialized with the value 0. You should therefore always initialize it with a correct value when you intend to perform a 3D transformation. If you are not sure which value is appropriate, you can try several values starting with a large one, at least greater than the width and height of the original image to display in the view.

Relay touch screen events to the original GUI component

Per default the Warp Group view limits to display the current image of the associated GUI component, so it doesn't react to any user inputs. This behavior can be changed by setting the property RelayCursorEvents to the value true. By enabling this mode the Warp Group view reacts to user touch events and relays them to the original component. In other words, the user can tap within the projection of a component and the original component will process the event. This works even if the component image appears perspectively distorted.

Use Warp Group view for fade-in and fade-out animations

The Warp Group view is ideal to perform fancy animations and transitions with the image of the original component. For example, you can implement an application which fades-in the image of an embedded component by scaling and rotating it dynamically from the center of the screen. Once the image has reached the size and the angle of the original component, the Warp Group view disappears and the original component becomes visible. From the users perspective, the component was visible all the time. The following steps describe how such application case can be done:

First add the desired component to the Composer canvas area and arrange it, so that it appears already at its intended destination position.

While the component is still selected change in Inspector its property Visible to the value false so the component is originally not visible when the application starts.

Add a new Warp Group view to the canvas area.

Connect the Warp Group view with the previously added component.

Add a new 'Change float' animation effect.

Configure the duration of the effect to e.g. 500 and the number of cycles to 1. Thus the fade-in animation will take 500 milliseconds.

Add a new slot method. Name it e.g. onAnimate.

Implement the slot method as follows. The method should calculate from the current animation progress the corresponding rotation and scaling.

// First query the coordinates of the position in the center of
// the current component. This is the destination position where
// to map the resulting image.
var point destPos = Bounds.orect.center;

// The 'Change float effect' starts per default by 0.0 and ends
// with 1.0. Depending on its current value calculate the rotation
// in range 0 .. 360 degree and the scaling factor.
var float angle = 360.0 * FloatEffect.Value;
var float scale = FloatEffect.Value;

// Get a new matrix instance
var Graphics::WarpMatrix matrix = new Graphics::WarpMatrix;

// Apply the rotation around the X-axis
matrix.Rotate( angle, 0.0, 0.0 );

// ... and scale the image
matrix.Scale( scale, scale, 1.0 );

// Determine the position of the viewer. It should not be to close
// in order to not intersect the edges of the rotated image.
matrix.EyeDistance = 300.0;

// Instruct the view to calculate its 4-corners from the matrix
// relative to the given destination position.
WarpGroupView.Warp3D( destPos, matrix );

Connect the slot method to the animation effect in order to be called periodically during the animation is active.

Add another slot method. Name it e.g. onFinished.

Implement the slot method as follows. This method handles the situation when the animation is finished:

// Once finished the animation, the buffer is not needed anymore.
// Release the associated memory.
Component.Buffered = false;

// The original component should appear now.
Component.Visible = true;

// The Group view, in turn, is not necessary anymore and disappears.
WarpGroupView.Visible = false;

Connect the slot method to the animation effect in order to handle the effect termination.

Configure the animation effect to start immediately.

Now, every time you start the Prototyper, the component fades-in with the implemented rotation/scale animation. The animation steps are calculated in the slot method onAnimate. At the end of the animation, the onFinished slot method is invoked and makes the original component visible. The Warp Group view used during the animation disappears then.

DOWNLOAD EXAMPLE

Please note, the example presents eventually features available as of version 8.10

Control the visibility of the Warp Group view

The visibility of the Warp Group view is controlled primarily by the property Visible and the content of the associated component. Also the specified opacity may affect the resulting appearance of the Warp Group view.

Per default the views appear alpha-blended over the contents lying behind them unless you explicitly disable this mode by setting the property AlphaBlended to the value false. In such case, the component image will overwrite the contents in the background. For example:

TIP

If the Warp Group view remains empty although it is connected with a GUI component, you have probably forgotten to activate the buffered mode for the component. See the section Connect the Warp Group view with a GUI component. If you are performing a 3D transformation, review also the operations applied on the transformation matrix as well as the initialization of its EyeDistance property.

TIP

In the complete GUI application an individual view is visible on the screen only when all of its superior Owner components are visible too, the view itself does lie within the visible area of the superior components and the view is not covered by other sibling views nor components.