Android 手机Camera Orientation问题的深入研究

      在Android 开发过程中经常跟Camera 打交道,对Camera Orientation 的理解由开始的迷糊到精通,着实走了不少弯路。本文将对Android 系统Camera 相关经验做一个总结。

      1. 手机的屏幕画面显示的自然方向:所谓自然方向就是人体手持手机时,视觉的习惯方向。例如,竖屏手机的自然方向一般都是垂直短边框向上,横屏手机的自然方向一般都是垂直长边框向上。这个自然方向是手机出厂时就定义好了的,相当于定义了一个坐标系,所有的旋转都要参照这个坐标系。

      2. 本文提及的所有角度都是顺时针方向旋转,即以参照物为起点,目标物为终点,顺时针走过的角度即为目标物与参照物之间的夹角。

      3. 当手机旋转时,display.getRotation() 返回的值为当前手机的空间位置与手机自然朝向之间的夹角。例如:将竖屛手机向右倾倒横放,display.getRotation() = 90;将竖屛手机向左倾倒横放,display.getRotation() = 270;将竖屛手机上下颠倒放置,display.getRotation() = 180。

      4. 不管是横屏还是竖屛手机,Camera 的物理方向一般都是垂直于长边框向上。也就说对于竖屏手机,其Camera 物理方向与屏幕Display的自然方向的夹角为90度(顺时针)如下图一;对于横屏手机,其Camera 自然方向与屏幕Display的自然方向的夹角为0度,如下图二。

      5. Camera 拍照时,总是按照Camera 的物理方向取景,即相当于将人的脑袋朝着Camera 的物理方向所看到的视角。对于横屏手机,当手横持手机拍照时,拍出来的照片即为用户眼前所见视角。而对于竖屛手机,当手竖持手机拍照时,拍出的照片往往旋转了90度,这是因为在竖屛手机上,Camera 本身物理方向就与屏幕Display的自然方向有90度夹角,用手竖持手机时,Camera的方向是水平向右的,如下图一。因此要想没有90度旋转,需要设置 Camera.setDisplayOrientation(90) 来校正。

 

       

        6. 如何计算当前Camera 物理位置与屏幕Display自然方向间的夹角?

           算法的核心思想是:先假设手机为竖屛手机,然后按照竖屛手机的角度关系计算Camera 物理位置与屏幕Display自然方向间的夹角,最后根据长宽关系判断当前手机是否为竖屛,如果不是,补偿一个270度然后对360取模,非常巧妙。

    public static int getOrientation(Context context) {
        Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
        int rotation = display.getRotation();
        int orientation;
        boolean expectPortrait;
        switch (rotation) {
        case Surface.ROTATION_0:
        default:
            orientation = 90;
            expectPortrait = true;
            break;
        case Surface.ROTATION_90:
            orientation = 0;
            expectPortrait = false;
            break;
        case Surface.ROTATION_180:
            orientation = 270;
            expectPortrait = true;
            break;
        case Surface.ROTATION_270:
            orientation = 180;
            expectPortrait = false;
            break;
        }
        boolean isPortrait = display.getHeight() > display.getWidth();
        if (isPortrait != expectPortrait) {
            orientation = (orientation + 270) % 360; 
        }
        return orientation;
    }


        7.如何为Surface View 设置一个最佳的尺寸?

    private Size calculateBestPreviewSizeAndAdjustLayout(Camera camera, Rect surfaceSize, int orientation, int minHeight, int maxHeight) {
        SizeAndRatio optimalSizeAndRatio = null;  // output
        double targetRatio = (double)surfaceSize.right/surfaceSize.bottom;
        if (orientation % 180 == 90) {
            // We are in portrait
            targetRatio = 1/targetRatio;
            m_updatedCameraFrameLayoutSize = new Point(surfaceSize.bottom,surfaceSize.right);
        } else {
            m_updatedCameraFrameLayoutSize = new Point(surfaceSize.right,surfaceSize.bottom);
        }
        boolean allowedResize = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH && orientation != 270;
        List<Size> supportedPreviewSizes = camera.getParameters().getSupportedPreviewSizes();

        // Collect sub-list that meets height requirements
        List<SizeAndRatio> cameraSizesAndRatios = new ArrayList<SizeAndRatio>();
        for (Size s : supportedPreviewSizes) {
            if (s.height < minHeight || s.height > maxHeight) {
                continue;  // Filter out too small or too large sizes
            }
            double ratio = ((double) s.width) / s.height;
            SizeAndRatio sr = new SizeAndRatio(s, ratio);
            cameraSizesAndRatios.add(sr);
        }

        // Error, no available sizes
        if (cameraSizesAndRatios.isEmpty()) {
            return null;
        }

        // Sort the list by height descending
        Collections.sort(cameraSizesAndRatios, new Comparator<SizeAndRatio>() {
            @Override
            public int compare(SizeAndRatio a_lhs, SizeAndRatio a_rhs) {
                return (a_rhs.size.height) - (a_lhs.size.height);
            }
        });

        // First try to find an exact screen aspect ratio match
        for (SizeAndRatio sr : cameraSizesAndRatios) {
            if (Math.abs(sr.ratio - targetRatio) < EPSILON_TOLERANCE) {
                optimalSizeAndRatio = sr;
                break;
            }
        }
        if (optimalSizeAndRatio != null) {
            return optimalSizeAndRatio.size;
        }

        // If we allow layout resizing, try to find 16:9 or 4:3 ratio
        if (allowedResize) {
            // Try to find a 16:9 ratio
            for (SizeAndRatio sr : cameraSizesAndRatios) {
                if (Math.abs(sr.ratio - RATIO16_9) < EPSILON_TOLERANCE) {
                    optimalSizeAndRatio = sr;
                    break;
                }
            }
            if (optimalSizeAndRatio != null) {<p class="p1">                // Notify that the screen layout should change</p><p class="p2">                <span class="s1">m_updatedCameraFrameLayoutSize</span> = adjustSurfaceSize(<span class="s1">mActivity</span>, optimalSizeAndRatio.<span class="s1">ratio</span>, orientation);</p>                return optimalSizeAndRatio.size;
            }

            // Try to find a 4:3 ratio
            for (SizeAndRatio sr : cameraSizesAndRatios) {
                if (Math.abs(sr.ratio - RATIO4_3) < EPSILON_TOLERANCE) {
                    optimalSizeAndRatio = sr;
                    break;
                }
            }
            if (optimalSizeAndRatio != null) {<p class="p1"><span class="s1">                </span>// Notify that the screen layout should change</p><p class="p2">                <span class="s2">m_updatedCameraFrameLayoutSize</span> = adjustSurfaceSize(<span class="s2">mActivity</span>, optimalSizeAndRatio.<span class="s2">ratio</span>, orientation);</p>                return optimalSizeAndRatio.size;
            }
        }

        // Now just use preview size with smallest ratio difference
        double minRatioDifference = Double.MAX_VALUE;
        for (SizeAndRatio sr : cameraSizesAndRatios) {
            double ratioDifference = Math.abs(sr.ratio - targetRatio);
            if (ratioDifference < minRatioDifference) {
                minRatioDifference = ratioDifference;
                optimalSizeAndRatio = sr;
            }
        }<p class="p1">        </p><p class="p1"><span class="s1">        if</span> (allowedResize) {</p><p class="p1">            <span class="s2">m_updatedCameraFrameLayoutSize</span> = adjustSurfaceSize(<span class="s2">mActivity</span>, optimalSizeAndRatio.<span class="s2">ratio</span>, orientation);</p><p class="p1">        }</p>
        return optimalSizeAndRatio.size;
    }

    public Point adjustSurfaceSize(FSECameraActivity activity, double a_aspectRatio, int a_orientation) {
        boolean isPortrait = a_orientation % 180 == 90;
        // Collect the width, height, and ratio of the screen
        Display display = activity.getWindowManager().getDefaultDisplay();
        int windowWidth, windowHeight;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR2) {
            Point size = new Point();
            display.getSize(size);
            windowWidth = size.x;
            windowHeight = size.y;
        } else {
            // Old APIs
            windowWidth = display.getWidth();
            windowHeight = display.getHeight();   
        }
        int leftMargin = 0;  // This may need to be adjusted for increasing width in portrait
        double currentRatio = (double)windowWidth / (double)windowHeight;

        // Adjust the ratios
        if (currentRatio < 1 && a_aspectRatio > 1 || currentRatio > 1 && a_aspectRatio < 1) {
            a_aspectRatio = 1.0/a_aspectRatio;
        }

        // Adjust the necessary dimension
        if (currentRatio > a_aspectRatio) {
            // If current ratio is larger, we need to increase the smaller dimension (height)
            windowHeight = (int) (windowWidth / a_aspectRatio);
        } else if (currentRatio < a_aspectRatio) {
            // If current ratio is smaller, we need to increase the larger dimension (width)
            int oldWindowWidth = windowWidth;
            windowWidth = (int) (windowHeight * a_aspectRatio);
            if (isPortrait) {
                // We also need to fix the Left Margin if in portrait
                leftMargin = oldWindowWidth - windowWidth;
            }
        } else if (currentRatio == a_aspectRatio) {
            // No adjustment needed
        }

        // Update layout parameters
        final LayoutParams lp = (FrameLayout.LayoutParams) mPreviewLayout.getLayoutParams();
        lp.width = windowWidth;
        lp.height = windowHeight;
        lp.topMargin = 0;
        lp.leftMargin = leftMargin;
        mCameraScreenMarginLeft = leftMargin;
        mPreviewLayout.setLayoutParams(lp);

        // Return a point where X corresponds to Camera Width, and Y corresponds to Camera Height.
        return isPortrait ? new Point(windowHeight, windowWidth) : new Point(windowWidth, windowHeight);
    }

        8. 如何将Camera 捕景框上的点坐标转化为手机屏幕上的坐标?

    /**
     * Translate a point returned by <span style="font-family: Arial, Helvetica, sans-serif;">camera sensor </span>into a point on the camera surface preview
     * @param point input Point returned from <span style="font-family: Arial, Helvetica, sans-serif;">camera sensor</span>
     * @return PointF
     */
    public PointF translatePoint(PointF point) {
        switch (mOrientation) {
        case 0:  // landscape right
            return new PointF(point.x*m_updatedCameraFrameLayoutSize.x/mCameraPreviewSize.width,
                    point.y*m_updatedCameraFrameLayoutSize.y/mCameraPreviewSize.height);
        case 180:  // landscape left 
            return new PointF((mCameraPreviewSize.width-point.x)*m_updatedCameraFrameLayoutSize.x/mCameraPreviewSize.width,
                    (mCameraPreviewSize.height-point.y)*m_updatedCameraFrameLayoutSize.y/mCameraPreviewSize.height);
        case 270:  // upside down portrait
            return new PointF(point.y*m_updatedCameraFrameLayoutSize.y/mCameraPreviewSize.height,
                    (mCameraPreviewSize.width-point.x)*m_updatedCameraFrameLayoutSize.x/mCameraPreviewSize.width);
        default:  // 90, normal portrait
            return new PointF((mCameraPreviewSize.height-point.y)*m_updatedCameraFrameLayoutSize.y/mCameraPreviewSize.height,
                    point.x*m_updatedCameraFrameLayoutSize.x/mCameraPreviewSize.width);
        }
    }


  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值