Kreisförmige Kameravorschau

Hallo zusammen,

ich versuche momentan eine kreisförmige Kameravorschau zu realisieren. Eine viereckige Kameravorschau habe ich schon mit Hilfe einer SurfaceView hinbekommen, das schaut momentan so aus:

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback
{
    [...]

    public CameraPreview(Context context, Camera camera, PreviewCallback previewCb, AutoFocusCallback autoFocusCb)
    {
        super(context);

        [...]

        bolder = getHolder();
        bolder.addCallback(this);
    }

    public void surfaceCreated(SurfaceHolder holder)
    {
        // The Surface has been created, now tell the camera where to draw the preview.
        try {
            camera.setPreviewDisplay(holder);
        } catch (IOException ioe) {
            Log.d(Constants.LOG_TAG, "Error setting camera preview", ioe);
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder)
    {
        holder.removeCallback(this);
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
    {
        [...]
    }
}

Ich habe jetzt allerdings keinen Anhaltspunkt wie ich die Vorschau kreisförmig hinbekomme.

Nach etwas googlen bin ich zwar auf ein paar Lösungen gestoßen die mit shapes arbeiten, allerdings funktioniert das bei mir nicht, da ich ein Hintergrundbild verwende das sichtbar sein soll. Ungefähr so:

nur dass der rote Hintergrund ein richtiges Bild ist. Hat sowas schonmal jemand gemacht und kann mir da ein paar Tips geben?

(Null Ahnung von Android, sorry, aber … Wissen aus Swing übertragend die Frage: Sollte das mit einem Shape und einem passenden BitmapShader | Android Developers nicht gehen?..)

EDIT: Ich hätte vermutet, dass das geht, indem man

  • entweder eine “Region” erstellt, die das gesamte Screen-Rechteck beschreibt, wo aber der Kreis mit Region.Op.DIFFERENCE “ausgestanzt” ist, und die man dann mit einem Bitmap-Shader-Paint malt
  • oder die gesamte Kameravorschau mit den entsprechenden Screen-Rechteck und dem Bitmap-Shader-Paint überpinselt, aber vorher den “clip path” des Canvas entsprechend um den Kreis erweitert…

(Komplett geratener Code…

Path circlePath = new Path();
circlePath.addOval(left, top, right, bottom, Path.Direction.CW);
canvas.clipPath(circlePath, Region.Op.INTERSECT)

Paint paint = new Paint();
// Does something as descibed in 
// http://stackoverflow.com/questions/13307282
initializeShader(paint);

canvas.drawRect(fullScreenRect, paint);

…)

aber das ist alles ziemlich spekulativ. Mal schauen, ob sich jemand meldet, der sich auskennt.

Hm, da bin ich leider überfragt. Ich wüsste nicht wie mir da nen Shape weiterhelfen kann.
Vielleicht hat ja noch jemand ne Idee.

Die sicherste Variante ist ein Overlay, dass du über dein SurfaceView zeichnen lässt. Wenn die Mitte transparent ist (PNG), haut das sofort hin. Dies kann einfach über ein RelativeLayout drüber gelegt werden. Die aufwendigere Variante geht in die selbe Richtung wie sie @Marco13 vorschlägt. Dazu musst du eine eigene View implementieren, die nur in einem bestimmten Bereich zeichnet. Hier findest du Beispiel-Code.

Worauf du da allerdings aufpassen musst, ist ob deine Zeichenoperation auch ohne 3D-Beschleunigung funktioniert. Tut sie das nicht, stürzt deine App beinhart ab, auf Geräten die 3D-Beschleunigung deaktiviert haben oder sie gar nicht anbieten können.

Hallo Schlingel,

danke für die Ideen. Die Variante mit dem Bild klingt sehr einleuchtend, das werte ich mal als erstes testen.
@Marco13
Dein Code aus dem EDIT geht in eine Richtung, die ich schon ausprobiert hatte. Ich hatte die daw(Canvas) Methode aus SurfaceView überschrieben um dort statt einem Viereck einen Kreis “auszustanzen”, das ganze hat aber leider überhaupt nicht funktioniert, vermutlich hatte ich da noch irgendwas vergessen.
Falls die Variante mit dem transparanten Loch nicht funktioniert, werde ich mal versuchen dafür nen kleinen Testcase zu bauen.

Die Variante von @Marco13 funktioniert. Ich hab’ sehr ähnlichen Code in einer App verwendet um runde Profilbilder zu zeichnen.

Ich bin nun schon etwas weitergekommen.
Die Variante mit dem Bild und den transparenten “Loch” kann ich leider nicht nutzen, da sich dort noch ein paar Dinge animieren müssen. Ich habe allerdings den zweiten Weg den ihr vorgeschlagen habt weiterverfolgt. Dabei ist folgendes rausgekommen:

Dazu habe ich in meiner CameraPreview Klasse die Methoden draw und dispatchDraw wie folgt überschrieben:

    @Override
    public void draw(Canvas canvas)
    {
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));

        Path circ = new Path();
        circ.addCircle(canvas.getWidth() / 2, canvas.getWidth() / 2, canvas.getWidth() / 2, Path.Direction.CW);
        canvas.drawPath(circ, paint);
    }

    @Override
    protected void dispatchDraw(Canvas canvas)
    {
        draw(canvas);
    }

Die Kamera wird nun also schonmal rund angezeigt, einzig der schwarze Hintergrund dort müsste noch weg. Habt ihr eine Idee wo der herkommen könnte? Ich habe schon versucht einen transparenten Hintergrund zu setzen, das hat aber nicht geholfen.

EDIT:
Blöder Fehler, meine CameraPreview lag noch in einem LinearLayout, das diesen schwarzen Hintergrund hatte. Nun schauts aus wie gewünscht! Danke euch.

Meine Klasse schaut nun so aus:

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
	
    [...]

    public CameraPreview(Context context, Camera camera, PreviewCallback previewCb, AutoFocusCallback autoFocusCb) {
        super(context);
	
	[...]

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        holder = getHolder();
        holder.addCallback(this);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        // The Surface has been created, now tell the camera where to draw the preview.
        try {
            camera.setPreviewDisplay(holder);
        } catch (IOException ioe) {
            Log.d(Constants.LOG_TAG, "Error setting camera preview", ioe);
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        [...]
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        [...]
    }

    @Override
    public void draw(Canvas canvas)
    {
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));

        Path circ = new Path();
        circ.addCircle(canvas.getWidth() / 2, canvas.getWidth() / 2, canvas.getWidth() / 2, Path.Direction.CW);
        canvas.drawPath(circ, paint);
    }

    @Override
    protected void dispatchDraw(Canvas canvas)
    {
        draw(canvas);
    }
}