7

I'm implementing 3d card flip animation for android (api > 14) and have an issue with big screen tablets (> 2048 dpi). During problem investigation i've come to the following basic block:

Tried to just transform a view (simple ImageView) using matrix and rotateY of camera by some angle and it works ok for angle < 60 and angle > 120 (transformed and displayed) but image disappears (just not displayed) when angle is between 60 and 120. Here is the code I use:

private void applyTransform(float degree) 
{
     float [] values = {1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f};
     float centerX = image1.getMeasuredWidth() / 2.0f;
     float centerY = image1.getMeasuredHeight() / 2.0f;

     Matrix m = new Matrix();
     m.setValues(values);

     Camera camera = new Camera();
     camera.save();

     camera.rotateY(degree);
     camera.getMatrix(m);

     camera.restore();

     m.preTranslate(-centerX, -centerY); // 1 draws fine without these 2 lines
     m.postTranslate(centerX, centerY);  // 2 

    image1.setImageMatrix(m);
}

And here is my layout XML

   <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout     xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
        android:id="@+id/ImageView01"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:src="@drawable/naponer"
        android:clickable="true"
        android:scaleType="matrix">
    </ImageView>
</FrameLayout>  

So I have the following cases:

  • works fine for any angle, any center point if running on small screens 800X480, 1024x720, etc...
  • works ok for angle < 60 and > 120 when running on big screen devices 2048x1536, 2560x1600...
  • works ok for any angle on any device if rotation not centered (matrix pre and post translations commented out )
  • fails (image disappears) when running on big screen device, rotation centered and angle is between 60 and 120 degrees.

Please tell what I'm doing wrong and advise some workaround... thank you!!!

Henrik
  • 73
  • 1
  • 5
  • Does the problem go away if you define android:hardwareAccelerated="false" for the activity in your AndroidManifest.xml? – samgak Jan 06 '15 at 13:00
  • no effect, tested both hardwareAccelerated enabled and disabled cases – Henrik Jan 06 '15 at 16:08
  • 3
    Have you tried playing with the camera distance (e.g. `camera.setLocation(0, 0, -20f)`? – matiash Jan 07 '15 at 17:40
  • YES!!! it worked!!! thank you!!! the default value was 0, 0, -8f... now trying to find some more info about this setting...please explain how it affects drawing so that image disappears when rotated from default camera location? – Henrik Jan 08 '15 at 11:24
  • 2
    @matiash you can answer to this question providing more details on how it affected the problem. – Pablo Jan 08 '15 at 12:52
  • Note that the call to setValues is redundant, as a newly created Matrix is an identity matrix by default. http://developer.android.com/reference/android/graphics/Matrix.html#Matrix() – emidander Oct 15 '15 at 09:10

3 Answers3

8

This problem is caused by the camera distance used to calculate the transformation. While the Camera class itself doesn't say much about the subject, it is better explained in the documentation for the View.setCameraDistance() method (emphasis mine):

Sets the distance along the Z axis (orthogonal to the X/Y plane on which views are drawn) from the camera to this view. The camera's distance affects 3D transformations, for instance rotations around the X and Y axis. (...)

The distance of the camera from the view plane can have an affect on the perspective distortion of the view when it is rotated around the x or y axis. For example, a large distance will result in a large viewing angle, and there will not be much perspective distortion of the view as it rotates. A short distance may cause much more perspective distortion upon rotation, and can also result in some drawing artifacts if the rotated view ends up partially behind the camera (which is why the recommendation is to use a distance at least as far as the size of the view, if the view is to be rotated.)

To be honest, I hadn't seen this particular effect (not drawing at all) before, but I suspected it could be related to this question related to perspective distortion I'd encountered in the past. :)

Therefore, the solution is to use the Camera.setLocation() method to ensure this doesn't happen.

An important distinction with the View.setCameraDistance() method is that the units are not the same, since setLocation() doesn't use pixels. While setCameraDistance() adjusts for density, setLocation() does not. Therefore, if you wanted to calculate an appropriate z-distance based on the view's dimensions, remember to adjust for density. For example:

float cameraDistance = Math.max(image1.getMeasuredHeight(), image1.getMeasuredWidth()) * 5;
float densityDpi = getResources().getDisplayMetrics().densityDpi;
camera.setLocation(0, 0, -cameraDistance / densityDpi);
Community
  • 1
  • 1
matiash
  • 54,791
  • 16
  • 125
  • 154
  • great solution...worked for me...but i with smaller device this method change my resolution and camera angle...any suggestion? – H Raval Oct 04 '16 at 07:33
0

Instead of using 12 lines to create rotation matrix, you could just implement this one in first line http://en.wikipedia.org/wiki/Rotation_matrix

Depending of effect you want, you might want to center image to axis you want to rotate around. http://en.wikipedia.org/wiki/Transformation_matrix

Hmm for image disappearing, I would guess it has something to do with either memory (out of memory - although this would bring exception) or rounding problems. Maybe you could try increasing precision to double precision?

One thing that comes to mind is that cos(alpha) goes toward 0 when alpha goes toward PI/2. Other than that I don's see any correlation between angles and why it doesn't work for big images.

Jani
  • 190
  • 2
  • 12
  • sure the matrix calculation may be optimized to 1-2 lines, but that's not crucial for me now...the problem is the image disappearing after applying the final matrix in the range i have described above and precision does not play role here... i'm ok if it disappears at PI/2 degrees (as it does on other devices). Seems the problem is related to range where part of image goes out of screen bounds, but i can't figure out why it must disappear???? – Henrik Jan 06 '15 at 10:05
  • Does it disappear every time at exact 60 and 120 degrees? Or is it a bit random, sometimes at 62, sometimes at 58? – Jani Jan 06 '15 at 10:58
  • Another thing the pre/post Translate. You say it works without those lines? The problem might be that you don't center image before rotation. Maybe try translate ( center) -> rotate -> translate (back). Try using Camera.translate(x,y,z) http://developer.android.com/reference/android/graphics/Camera.html – Jani Jan 06 '15 at 11:15
  • 1. not exact 60 and 120, i was testing by 5 degree steps and it disappears at 60 and appears back at 120... – Henrik Jan 06 '15 at 11:22
  • 2. without pre/post translation image rotation axis is not centered, and it never goes out of screen bounds, that's why it works... problem comes when rotation axis is centered somehow (pre/post translate or pre-calculated matrix) – Henrik Jan 06 '15 at 11:24
0

You need to adjust your Translate coordinates. When calculating the translation for your image you need to take image size into account too. When you perform matrix calculations you set android:scaleType="matrix" for your ImageView. This aligns your image at the top left corner by default. Then, when you apply your pre/post translation, your image may get off the bounds of your ImageView (especially if the ImageView is relatively large and your image is relatively small, like in case of beeg screen tablets).

The following translation results in the image being rotated around its center Y axis and keeps the image aligned to the top left corner:

m.preTranslate(-imageWidth/2, 0);
m.postTranslate(imageWidth/2, 0);

The following alternative results in the image being rotated around its center Y/X axises and aligns the image to the center of the ImageView:

m.preTranslate(-imageWidth/2, -imageHeight/2);
m.postTranslate(centerX, centerY);

If your image is a bitmap you can use intrinsic width/height:

Drawable drawable = image1.getDrawable();
imageHeight = drawable.getIntrinsicHeight();
imageWidth = drawable.getIntrinsicWidth();
stealth
  • 371
  • 2
  • 8
  • actually i can't see any difference between those 2 center points... however tested your suggestion and result is exactly the same - image disappears in the same range... – Henrik Jan 08 '15 at 11:26
  • Can you provide your activity layout XML? I am using simple layout containing single `ImageView` stretched to fill the whole screen and it works ok :) – stealth Jan 08 '15 at 11:49
  • sure, added it to original post... also tested with ImageView stretched to fill whole screen - didn't work...i suspect you're not testing on high resolution device > 2048x1536 – Henrik Jan 08 '15 at 12:19