Using Views: Warp Image
The Mosaic class Views::WarpImage implements a graphical object specialized to display a perspective correct projection of a bitmap area (the texture) onto a quad (four corners polygon). This so-called Warp Image view can be used to compose the appearance of a GUI component, in particular to add fancy 2D and 3D scale and rotate effects to it. The following screenshot demonstrates few examples of how Warp Image 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 Image view. For the complete reference please see the documentation of the Views::WarpImage class.
Please note, if your target device doesn't provide any dedicated graphics hardware for texture mapping, the Warp Image 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 Image view
To add a new Warp Image 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 Image.
★Drag & Drop the template into the canvas area of the Composer window:
★Eventually name the new added Warp Image view.
Inspect the Warp Image view
This is in so far worth mentioning as all following sections describe diverse features of the Warp Image 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 Image 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 Image 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 Image view to appear behind other views you can reorder it explicitly.
Please note the restriction of the Warp Image 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:
Specify the bitmap for the Warp Image view
The content to display within a Warp Image view is determined by its property Bitmap. Usually, you initialize this property with the name of an existing bitmap resource member, which is typical for all immutable contents like button icons, screen backgrounds and any other decorations well known already at the design time. With the Inspector Assistant you can conveniently select the right resource when you edit the initialization expression for the property Bitmap.
From technical point of view, you can initialize the property Bitmap with any instance of a class descended from the Mosaic class Resources::Bitmap. In particular, you can use an instance of the Resources::ExternBitmap class if you want to display bitmap contents, you have e.g. received just at the runtime. Or you render the bitmap dynamically into a Graphics::Canvas object and assign it to the Warp Image view.
Please note, every new added Warp Image view uses the Resources::DefaultBitmap bitmap resource as place holder to display something on the screen. In addition to it, Embedded Wizard Studio provides a set of default bitmap resources with icons for typical application cases. They are intended to help you when you start to create a prototype of your new product.
Determine the source area
Per default the Warp Image view projects the entire content of the specified bitmap. With the property SourceArea you can specify a smaller rectangular area of the bitmap to use. In other words, you can instruct the Warp Image view to display a section of the original bitmap instead of the entire bitmap.
The property SourceArea expects a valid rect operand determining the area relative to the top-left corner of the bitmap. By initializing this property with an empty rect <0,0,0,0>, the Warp Image restores its default behavior and projects the entire content of the bitmap. For example (the gray borders indicate the source areas of the bitmap 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:
Despite the advantages, you should consider, that with the bilinear filter the bitmap 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.
Colorize alpha-only bitmaps
Alpha-only bitmaps don't provide any colors. Instead they contain pure opacity information. Thus if you assign an alpha-only bitmap to the Warp Image view, you have to specify which color you want the bitmap to be colorized with when it is shown on the screen otherwise the bitmap appears with white color.
To specify the color you use the property Color. Alternatively you can configure the bitmap to be colorized with a color gradient. For this purpose you specify the colors for the four corners of the Warp Image view (Point1, Point2, Point3, Point4) individually by using the corresponding properties Color1, Color2, Color3 and Color4. The gradient colors are interpolated linearly between the specified values. Using colors with alpha < 255 causes the bitmap to appear semi- or even transparent. For example:
Please note, before the version 11.00 the common property Color and the individual properties Color1.. Color4 were intended to be used exclusively. You could specify either the common color for the entire image or individual colors for its four corners. Using both approaches simultaneously produced unexpected results depending on the order in which the properties were initialized. This confused the users.
Starting with the version 11.00 the Warp Image view calculates the resulting colors by modulating the values from the individual properties Color1..Color4 by the value from the common property Color. In this manner, you are able to specify colors in all properties simultaneously avoiding the unpredictable behavior found in the preceding version. From technical point of view, the modulation is performed by multiplying the color components (red, green, blue, alpha) from the individual property (e.g. Color1) with the corresponding components from the common property Color.
Modulate the opacity of the displayed bitmap
Using the property Opacity you can modulate the original opacity of the displayed bitmap, 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:
Additionally you can specify for every corner (Point1, Point2, Point3, Point4) an individual opacity value resulting in the bitmap being modulated by an opacity gradient. For this purpose modify the alpha values of the corresponding properties Color1, Color2, Color3 and Color4. For example:
Select the frame number of a multi-frame bitmap resource
If you assign a multi-frame bitmap resource to the Warp Image view, the view displays always one (per default the first available) frame. To display another frame you have to specify its number by using the property FrameNumber. In the following example you see three Warp Image views, each configured to display another frame from one and the same multi-frame bitmap resource:
Multi-frame bitmaps are ideal wherever several versions of one and the same image are necessary. For example, a button can display different versions of its background depending on whether the user actually presses the button or not. Instead of creating individual bitmap resources for every possible button state you store the different versions of the background as frames within one multi-frame bitmap resource.
Control the playback of an animated bitmap resource
To control the playback you use the property Animated or you send signals to the slot methods StartAnimation and StopAnimation. Per default the Warp Image view plays the sequence endless as long as the property Animated is true. You can disable this behavior by additionally setting the property Endless to false.
Once finished the animation, the Warp Image view sends a signal to a slot method assigned to its property OnFinished. In this manner you can trigger other animation effects or simply perform operations you want to complete just after the playback is done.
Perform 2D rotate and scale operations
When you want the bitmap 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 Image 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 Image 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 bitmap. 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 bitmap around which you intend to rotate and scale it. You can imagine it as the fixed origin of a bitmap 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 bitmap to rotate around its center, you initialize the property SourceAnchor with the position of the corresponding pixel in the center of the bitmap:
Besides the source anchor position, you should also understand the concept of the corresponding destination position. It determines the position around which the bitmap should be scaled or rotated within the component, the Warp Image view belongs to. In other words, this is the position on the screen where you want the source anchor of the bitmap to appear. The following figure demonstrates the relation between the both positions:
The following Chora code demonstrates an example of how the bitmap associated to a Warp Image 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 Image view to calculate its corners, so that // the original bitmap appears scaled by 120% and rotated 30° around // the position destPos within the current component. SomeWarpImageView.RotateAndScale( destPos, angle, scale, scale );
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 bitmap. 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 Image view implements a method called Warp3D(). This method expects two parameters: the destination position where to map the bitmap's 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 bitmap 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:
Applies to the matrix a rotation operation around the X-, Y- and/or Z-axes. The rotation angles are specified in degrees.
Applies to the matrix a scale operation along the X-, Y- and/or Z-axes.
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 bitmap 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 Image view to calculate its corners from the // prepared matrix object relative to the position destPos within // the current component. SomeWarpImageView.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 Image view to calculate its corners from the // prepared matrix object relative to the position destPos within // the current component. SomeWarpImageView.Warp3D( destPos, matrix );
Please note, the example presents eventually features available as of version 8.10
Please note the restriction of the Warp Image 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 Image view is not able to perform the projection and remains empty. The following figure demonstrates in a side view the described rotation situation:
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.
Control the visibility of the Warp Image view
The visibility of the Warp Image view is controlled primarily by the property Visible and the content of the assigned bitmap. Also the specified opacity and eventually the color values may affect the resulting appearance of the Warp Image 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 image will overwrite the contents in the background. For example:
If the Warp Image view remains empty and you are performing a 3D transformation, review the operations applied on the transformation matrix as well as the initialization of its EyeDistance property.
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.