144 votes

Aperçu de la caméra Android étiré

J'ai travaillé sur la création de mon activité de caméra personnalisée sur Android, mais lorsque je tourne la caméra, le rapport hauteur/largeur de la vue surface est perturbé.

Dans mon onCreate pour l'activité, j'ai défini le framelayout qui contient la vue surface qui affiche les paramètres de la caméra.

//FrameLayout qui contiendra l'aperçu de la caméra
        FrameLayout previewHolder = (FrameLayout) findViewById(R.id.camerapreview);

        //Définition de la taille de l'aperçu de la caméra pour la meilleure taille d'aperçu
        Size optimalSize = null;
        camera = getCameraInstance();
        double aspectRatio = 0;
        if(camera != null){
            //Définition du rapport hauteur/largeur de la caméra
            Camera.Parameters parameters = camera.getParameters();
            List sizes = parameters.getSupportedPreviewSizes();
            optimalSize = CameraPreview.getOptimalPreviewSize(sizes, getResources().getDisplayMetrics().widthPixels, getResources().getDisplayMetrics().heightPixels);
            aspectRatio = (float)optimalSize.width/optimalSize.height;
        }

        if(optimalSize!= null){
            RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, (int)(getResources().getDisplayMetrics().widthPixels*aspectRatio));
            previewHolder.setLayoutParams(params);
            LayoutParams surfaceParams = new LayoutParams(LayoutParams.MATCH_PARENT, (int)(getResources().getDisplayMetrics().widthPixels*aspectRatio));
            cameraPreview.setLayoutParams(surfaceParams);

        }

        cameraPreview.setCamera(camera);

        //Ajout de l'aperçu au conteneur
        previewHolder.addView(cameraPreview);

Ensuite, dans la vue de surface, je définis les paramètres de la caméra à afficher

public void setCamera(Camera camera) {
        if (mCamera == camera) { return; }

        mCamera = camera;

        if (mCamera != null) {
            requestLayout();

            try {
                mCamera.setPreviewDisplay(mHolder);
            } catch (IOException e) {
                e.printStackTrace();
            }

            if(mCamera != null){
                //Définition du rapport hauteur/largeur de la caméra
                Camera.Parameters parameters = mCamera.getParameters();
                List sizes = parameters.getSupportedPreviewSizes();
                Size optimalSize = getOptimalPreviewSize(sizes, getResources().getDisplayMetrics().widthPixels, getResources().getDisplayMetrics().heightPixels);

                parameters.setPreviewSize(optimalSize.width, optimalSize.height);
                mCamera.setParameters(parameters);
            }

            /*
              Important : Appelez startPreview() pour commencer à mettre à jour la surface de prévisualisation. La prévisualisation doit 
              être démarrée avant de pouvoir prendre une photo.
              */
            mCamera.startPreview();
        }

    }

entrez la description de l'image ici

Vous pouvez voir que l'homme LEGO devient plus grand et plus mince lorsque le téléphone est tourné :

Comment puis-je m'assurer que le rapport hauteur/largeur de ma vue de caméra est correct ?

2voto

Priyankagb Points 739

La réponse de @Hesam sur Stack Overflow est correcte, CameraPreview fonctionnera sur tous les appareils en mode portrait, mais si l'appareil est en mode paysage ou en mode multi-fenêtres, ce code fonctionne parfaitement, il suffit de remplacer onMeasure().

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
    int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
    setMeasuredDimension(width, height);

    int rotation = ((Activity) mContext).getWindowManager().getDefaultDisplay().getRotation();
    if ((Surface.ROTATION_0 == rotation || Surface.ROTATION_180 == rotation)) {//portrait
        mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, height, width);
    } else
        mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);//landscape

    if (mPreviewSize == null) return;
    float ratio;
    if (mPreviewSize.height >= mPreviewSize.width) {
        ratio = (float) mPreviewSize.height / (float) mPreviewSize.width;
    } else ratio = (float) mPreviewSize.width / (float) mPreviewSize.height;

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && ((Activity) mContext).isInMultiWindowMode()) {
        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ||
                !(Surface.ROTATION_0 == rotation || Surface.ROTATION_180 == rotation)) {
            setMeasuredDimension(width, (int) (width / ratio));
        } else {
            setMeasuredDimension((int) (height / ratio), height);
        }
    } else {
        if ((Surface.ROTATION_0 == rotation || Surface.ROTATION_180 == rotation)) {
            Log.e("---", "onMeasure: " + height + " - " + width * ratio);
            //2264 - 2400.0 pix c -- yes
            //2240 - 2560.0 samsung -- yes
            //1582 - 1440.0 pix 2 -- no
            //1864 - 2048.0 sam tab -- yes
            //848 - 789.4737 iball -- no
            //1640 - 1600.0 nexus 7 -- no
            //1093 - 1066.6667 lenovo -- no
            //if width * ratio is > height, need to minus toolbar height
           if ((width * ratio) < height)
                setMeasuredDimension(width, (int) (width * ratio));
            else
                setMeasuredDimension(width, (int) (width * ratio) - toolbarHeight);
        } else {
            setMeasuredDimension((int) (height * ratio), height);
        }
    }
    requestLayout();
}

1voto

Alexander Danilov Points 1332

J'ai découvert quel est le problème - il concerne les changements d'orientation. Si vous changez l'orientation de la caméra à 90 ou 270 degrés, alors vous devez échanger la largeur et la hauteur des tailles supportées et tout ira bien.

Aussi, la vue de surface devrait être contenue dans un frame layout et avoir une gravité centrale.

Voici un exemple en C# (Xamarin):

public void SurfaceChanged(ISurfaceHolder holder, Android.Graphics.Format format, int width, int height)
{
    _camera.StopPreview();

    // trouver la meilleure taille de prévisualisation supportée

    var parameters = _camera.GetParameters();
    var supportedSizes = parameters.SupportedPreviewSizes;
    var bestPreviewSize = supportedSizes
        .Select(x => new { Width = x.Height, Height = x.Width, Original = x }) // HACK permuter hauteur et largeur en raison de l'orientation modifiée à 90 degrés
        .OrderBy(x => Math.Pow(Math.Abs(x.Width - width), 3) + Math.Pow(Math.Abs(x.Height - height), 2))
        .First();

    if (height == bestPreviewSize.Height && width == bestPreviewSize.Width)
    {
        // démarrer la prévisualisation si la meilleure taille de prévisualisation supportée correspond à la taille actuelle de la vue de surface

        parameters.SetPreviewSize(bestPreviewSize.Original.Width, bestPreviewSize.Original.Height);
        _camera.SetParameters(parameters);
        _camera.StartPreview();
    }
    else
    {
        // si ce n'est pas le cas, alors changer la taille de la vue de surface pour la meilleure taille supportée (SurfaceChanged sera appelé une fois de plus)

        var layoutParameters = _surfaceView.LayoutParameters;
        layoutParameters.Width = bestPreviewSize.Width;
        layoutParameters.Height = bestPreviewSize.Height;
        _surfaceView.LayoutParameters = layoutParameters;
    }
}

Attention, les paramètres de la caméra doivent être définis à la taille d'origine (non permutée), et la taille de la vue de surface doit être permutée.

1voto

SalutonMondo Points 88

J'ai essayé toutes les solutions ci-dessus mais aucune d'entre elles ne fonctionne pour moi. Finalement, je l'ai résolu moi-même et j'ai en fait trouvé que c'était assez facile. Il y a deux points sur lesquels vous devez être prudent.

parameters.setPreviewSize(cameraResolution.x, cameraResolution.y);

cette previewSize doit être l'une des résolutions prises en charge par l'appareil photo, qui peut être obtenue comme suit :

List rawSupportedSizes = parameters.getSupportedPreviewSizes();

habituellement l'une des rawSupportedSize est égale à la résolution de l'appareil.

Deuxièmement, placez votre SurfaceView dans un FrameLayout et définissez la hauteur et la largeur de la mise en page de la surface dans la méthode surfaceChanged comme ci-dessus

FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) surfaceView.getLayoutParams();
layoutParams.height = cameraResolution.x;
layoutParams.width = cameraResolution.y;

OK, les choses sont faites, j'espère que cela pourra vous aider.

1voto

Stav Bodik Points 538

Point très important à comprendre ici, la taille de SurfaceView doit être la même que la taille des paramètres de la caméra, cela signifie qu'ils ont le même rapport hauteur/largeur, puis l'effet d'étirement disparaîtra.

Vous devez obtenir la taille de prévisualisation de la caméra prise en charge correcte en utilisant params.getSupportedPreviewSizes(), en choisir une, puis changer votre SurfaceView et ses supports à cette taille.

0voto

Howard Lin Points 51

Mes exigences sont que l'aperçu de la caméra doit être en plein écran et conserver le rapport d'aspect. La solution de Hesam et de Yoosuf était géniale mais je vois un problème de zoom élevé pour une raison quelconque.

L'idée est la même, avoir le conteneur de l'aperçu centré dans le parent et augmenter la largeur ou la hauteur en fonction des rapports d'aspect jusqu'à ce qu'il puisse couvrir tout l'écran.

Une chose à noter est que la taille de l'aperçu est en mode paysage car nous avons défini l'orientation de l'affichage.

camera.setDisplayOrientation(90);

Le conteneur auquel nous ajouterons la vue SurfaceView :

Ajoutez l'aperçu à son conteneur avec un centrage dans le parent dans votre activité.

this.cameraPreview = new CameraPreview(this, camera);
cameraPreviewContainer.removeAllViews();
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
params.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
cameraPreviewContainer.addView(cameraPreview, 0, params);

À l'intérieur de la classe CameraPreview :

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    // Si votre aperçu peut changer ou pivoter, gérez ces événements ici.
    // Assurez-vous d'arrêter l'aperçu avant de redimensionner ou reformater.

    if (holder.getSurface() == null) {
        // la surface de prévisualisation n'existe pas
        return;
    }

    stopPreview();

    // définir la taille des aperçus et effectuer tout changement de redimensionnement ou de rotation ici
    try {
        Camera.Size nativePictureSize = CameraUtils.getNativeCameraPictureSize(camera);
        Camera.Parameters parameters = camera.getParameters();
        parameters.setPreviewSize(optimalSize.width, optimalSize.height);
        parameters.setPictureSize(nativePictureSize.width, nativePictureSize.height);
        camera.setParameters(parameters);
        camera.setDisplayOrientation(90);
        camera.setPreviewDisplay(holder);
        camera.startPreview();

    } catch (Exception e){
        Log.d(TAG, "Erreur lors du démarrage de l'aperçu de la caméra : " + e.getMessage());
    }
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
    final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);

    if (supportedPreviewSizes != null && optimalSize == null) {
        optimalSize = CameraUtils.getOptimalSize(supportedPreviewSizes, width, height);
        Log.i(TAG, "taille optimale : " + optimalSize.width + "w, " + optimalSize.height + "h");
    }

    float previewRatio =  (float) optimalSize.height / (float) optimalSize.width;
    // le ratio d'aperçu est hauteur/largeur car les tailles d'aperçu de la caméra sont en mode paysage
    float measuredSizeRatio = (float) width / (float) height;

    if (previewRatio >= measuredSizeRatio) {
        measuredHeight = height;
        measuredWidth = (int) ((float)height * previewRatio);
    } else {
        measuredWidth = width;
        measuredHeight = (int) ((float)width / previewRatio);
    }
    Log.i(TAG, "Taille de l'aperçu : " + width + "w, " + height + "h");
    Log.i(TAG, "Taille de l'aperçu calculée : " + measuredWidth + "w, " + measuredHeight + "h");

    setMeasuredDimension(measuredWidth, measuredHeight);
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X