11

画面に2つのビューを表示したい.1つはカメラのプレビューで、もう1つは画像またはGoogleマップを表示し、画面の下部に表示します。

ただし、それらの間にグラデーションのような遷移が必要なため、それらの間に大まかなエッジはありません。そんな効果がありえるのでしょうか?

編集:達成したい効果は次のようになります(上部はカメラのプレビューから取得され、下部はマップである必要があります...):

カメラの写真にマップをブレンド

iOS では、マップを表示し、レイヤー マスクをグラデーションに設定する CameraOverlay で同様の効果が得られました。

CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = self.map.bounds;
gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor colorWithWhite: 1.0 alpha: 0.0] CGColor], (id)[[UIColor colorWithWhite: 1.0 alpha: 1.0] CGColor], nil];
gradient.startPoint = CGPointMake(0.5f, 0.0f);
gradient.endPoint = CGPointMake(0.5f, 0.5f);
self.map.layer.mask = gradient;
4

2 に答える 2

4

残念ながら、両方のコンポーネントがインタラクティブ/ライブである必要がある場合、カメラ プレビューとマップの間でクロスフェードすることはできません。以前のコメントで述べたように、これは両方のウィジェットの性質と Android 合成の制限に関連しています。

カメラ プレビューがSurfaceView正しく機能するには、 が必要です。公式ドキュメントから:

SurfaceView はウィンドウに穴を開けて、その表面を表示できるようにします。ビュー階層は、通常はその上に表示される SurfaceView の兄弟を Surface と正しく合成する処理を行います。これは、Surface の上にボタンなどのオーバーレイを配置するために使用できますが、Surface が変更されるたびに完全なアルファブレンド合成が実行されるため、パフォーマンスに影響を与える可能性があることに注意してください。

Google マップ v2SurfaceViewも使用されているため (ここを参照)、基本的に 2 つのSurfaceViewインスタンスが重なり合っており、各ウィジェットの描画方法を制御できないため、目的を達成するためにグラデーション マスクを適用することはできません。自体:

  • カメラ プレビューSurfaceViewはカメラ バッファを受け取り、ネイティブにレンダリングします
  • マップSurfaceViewは別のプロセスでレンダリングされます。

さらに、 の 2 つのインスタンスをSurfaceView一緒に使用することは、次のように推奨されませ

サーフェス ビューが実装される方法は、別のサーフェスが作成され、それを含むウィンドウの背後で Z オーダーされ、透明なピクセルが SurfaceView がある四角形に描画されるため、背後のサーフェスを見ることができます。複数の表面ビューを許可するつもりはありませんでした。

あなたが持っている唯一のオプションは、それらの1つだけをライブ/インタラクティブにすることを選択し、もう1つをその上に静的な画像グラデーションとして描画することだと思います.


編集

私の以前の声明をさらに検証するために、カメラの使用に関する公式ドキュメントからの引用を以下に示します。

重要:完全に初期化された SurfaceHolder を setPreviewDisplay(SurfaceHolder) に渡します。サーフェスがないと、カメラはプレビューを開始できません

したがってSurfaceView、 からプレビューを取得するには、を使用する必要がありますCamera。いつも。
繰り返しになりますが、これらのピクセルのレンダリング方法を制御することはできません。プレビューを使用しCamera てフレームバッファに直接書き込むSurfaceHolderためです。

結論として、2 つの完全に不透明な SurfaceViewインスタンスがあり、それらのコンテンツに派手なレンダリングを適用することはできないため、Android ではそのような効果は単に実用的ではないと思います。

于 2013-04-21T18:16:36.363 に答える
0

これは可能ですが、おそらく少し複雑です。簡単にするために、これを実現するためのコアコードを答えに入れました。すでに述べたように、これを行うには 2 つのビューが必要です。「下」のものは、マップ API によって駆動される SurfaceView である必要があります。「高い」ものは、​​カメラ画像がフェードアウトしたことを示しているはずです。

編集: mr_archano が指摘するように、API は (現在) SurfaceView がないとカメラがプレビュー データを送信しないように定義されています。ふむ、それが進歩の性質ですが、これも克服可能です。

コードは以下を示します。

  • 「下」の SurfaceView は、カメラ プレビュー メカニズムによって直接駆動されます。
  • 「中央」の SurfaceView は、MAPS API 用です。
  • 「上部」ビューは、目的の効果を実現するためにカメラ データがレンダリングされる場所です。

したがって、コア コードは「カメラ プレビュー」よりも「カメ​​ラ プレビュー」を提供し、上部の画像は意図的に歪められているため、上部が完全にはっきりと見え、中央がフェードし、下部が見えなくなります。

このコードを使用する最善の方法は、これらの最初の 4 つのステップを単独で実装して動作することを確認し、次に最後の 2 つのステップを追加して動作することを確認してから、重要な概念を別のものに挿入することです。複雑なコード。

最初の 4 つのステップ:

  1. トップ、カメラ、ビューに表示するためのカスタム ビューを作成します。このクラスは、その下にあるものすべての上にビットマップをレンダリングします。ビットマップ内の各ピクセルのアルファ値によって、下側のビューがどれだけ通過するかが決まります。

    public class CameraOverlayView extends View {
        private Paint  paint;
        private Size   incomingSize;
        private Bitmap bitmap = null;
    
        public CameraOverlayView(Context context) {
            super(context);
            init();
        }
    
        public CameraOverlayView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        private void init() {
            paint = new Paint();
            paint.setStyle(Style.FILL_AND_STROKE);
            paint.setColor(0xffffffff);
            paint.setTextSize((float) 20.0);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            int width  = canvas.getWidth();
            int height = canvas.getHeight();
    
            canvas.drawBitmap(bitmap, 0.0f, 0.0f, paint);
        }
    }
    
  2. 3 つのビューを 1 つのフレームに配置し、それらをすべてfill_parent両方向に設定します。最初のものは「下」になります (SurfaceView でカメラのプレビューが機能します)。2 つ目は「真ん中」 (マップなどのサーフェス ビュー) です。3 番目の「上」(色あせたカメラ画像のビュー)。

    <SurfaceView
        android:id="@+id/beneathSurfaceView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />
    
    <SurfaceView
        android:id="@+id/middleSurfaceView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />
    
    <com.blah.blah.blah.CameraOverlayView
        android:id="@+id/aboveCameraView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />
    

  3. カメラをセットアップし、自動プレビュー画像を (下部) SurfaceView に送信し、プレビュー画像データを処理ルーチンに送信する、簡素化されたメイン アクティビティ。プレビュー データをキャッチするコールバックを設定します。この 2 つが並行して実行されます。

    public class CameraOverlay extends Activity implements SurfaceHolder.Callback2 {
    
        private SurfaceView       backSV;
        private CameraOverlayView cameraV;
        private SurfaceHolder cameraH;
        private Camera        camera=null;
    
        private Camera.PreviewCallback cameraCPCB;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.camera_overlay);
    
            // Get the two views        
            backSV  = (SurfaceView) findViewById(R.id.beneathSurfaceView);
            cameraV = (CameraOverlayView) findViewById(R.id.aboveCameraView);
    
            // BACK: Putting the camera on the back SV (replace with whatever is driving that SV)
            cameraH  = backSV.getHolder();
            cameraH.addCallback(this);
    
            // FRONT: For getting the data from the camera (for the front view)
            cameraCPCB = new Camera.PreviewCallback () {
                @Override
                public void onPreviewFrame(byte[] data, Camera camera) {
                    cameraV.acceptCameraData(data, camera);
                }
            };
        }
    
        // Making the camera run and stop with state changes
        @Override
        public void onResume() {
            super.onResume();
            camera = Camera.open();
            camera.startPreview();
        }
    
        @Override
        public void onPause() {
            super.onPause();
            camera.setPreviewCallback(null);
            camera.stopPreview();
            camera.release();
            camera=null;
        }
    
        private void cameraImageToViewOn() {
            // FRONT
            cameraV.setIncomingSize(camera.getParameters().getPreviewSize());
            camera.setPreviewCallback(cameraCPCB);
        }
    
        private void cameraImageToViewOff() {
            // FRONT
            camera.setPreviewCallback(null);
        }
    
        // The callbacks which mean that the Camera does stuff ...
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            // If your preview can change or rotate, take care of those events here.
            // Make sure to stop the preview before resizing or reformatting it.
    
            if (holder == null) return;
    
            // stop preview before making changes
            try {
                cameraImageToViewOff(); // FRONT
                camera.stopPreview();
                } catch (Exception e){
                // ignore: tried to stop a non-existent preview
            }
    
            // set preview size and make any resize, rotate or reformatting changes here
    
            // start preview with new settings
            try {
                camera.setPreviewDisplay(holder); //BACK
                camera.startPreview();
                cameraImageToViewOn(); // FRONT
            } catch (Exception e){
            }
        }
    
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            try {
                camera.setPreviewDisplay(holder); //BACK
                camera.startPreview();
                cameraImageToViewOn(); // FRONT
            } catch (IOException e) {
            }       
        }
    
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
    }
    
        @Override
        public void surfaceRedrawNeeded(SurfaceHolder holder) {
        }
    }
    

    いくつかのものが欠けています:

    • カメラ画像が正しい向きであることを確認する
    • カメラのプレビュー画像が最適なサイズであることを確認する

  4. ここで、ステップ 1 で作成したビューに 2 つの関数を追加します。1つ目は、ビューが受信画像データのサイズを認識していることを確認します。2 つ目は、プレビュー イメージ データを受け取り、それをビットマップに変換し、可視性とアルファ フェードの両方を示すために途中で歪めます。

    public void setIncomingSize(Size size) {
        incomingSize = size;
        if (bitmap != null) bitmap.recycle();
        bitmap = Bitmap.createBitmap(size.width, size.height, Bitmap.Config.ARGB_8888);
    }
    
    public void acceptCameraData(byte[] data, Camera camera) {
        int width  = incomingSize.width;
        int height = incomingSize.height;
    
        // the bitmap we want to fill with the image
        int numPixels = width*height;
    
        // the buffer we fill up which we then fill the bitmap with
        IntBuffer intBuffer = IntBuffer.allocate(width*height);
        // If you're reusing a buffer, next line imperative to refill from the start, - if not good practice
        intBuffer.position(0);
    
        // Get each pixel, one at a time
        int Y;
        int xby2, yby2;
        int R, G, B, alpha;
        float U, V, Yf;
        for (int y = 0; y < height; y++) {
            // Set the transparency based on how far down the image we are:
            if (y<200) alpha = 255;          // This image only at the top
            else if (y<455) alpha = 455-y;   // Fade over the next 255 lines
            else alpha = 0;                  // nothing after that
            // For speed's sake, you should probably break out of this loop once alpha is zero ...
    
            for (int x = 0; x < width; x++) {
                // Get the Y value, stored in the first block of data
                // The logical "AND 0xff" is needed to deal with the signed issue
                Y = data[y*width + x] & 0xff;
    
                // Get U and V values, stored after Y values, one per 2x2 block
                // of pixels, interleaved. Prepare them as floats with correct range
                // ready for calculation later.
                xby2 = x/2;
                yby2 = y/2;
                U = (float)(data[numPixels + 2*xby2 + yby2*width] & 0xff) - 128.0f;
                V = (float)(data[numPixels + 2*xby2 + 1 + yby2*width] & 0xff) - 128.0f;
    
                // Do the YUV -> RGB conversion
                Yf = 1.164f*((float)Y) - 16.0f;
                R = (int)(Yf + 1.596f*V);
                G = 2*(int)(Yf - 0.813f*V - 0.391f*U); // Distorted to show effect
                B = (int)(Yf + 2.018f*U);
    
                // Clip rgb values to 0-255
                R = R < 0 ? 0 : R > 255 ? 255 : R;
                G = G < 0 ? 0 : G > 255 ? 255 : G;
                B = B < 0 ? 0 : B > 255 ? 255 : B;
    
                // Put that pixel in the buffer
                intBuffer.put(Color.argb(alpha, R, G, B));
            }
        }
    
        // Get buffer ready to be read
        intBuffer.flip();
    
        // Push the pixel information from the buffer onto the bitmap.
        bitmap.copyPixelsFromBuffer(intBuffer);
    
        this.invalidate();
    }
    

    2 番目のルーチンに関する注意事項:

    • 入力カメラのフォーマット NV21 を想定しています。他のものも利用できるかもしれませんが、これは苦痛であってもそこにあることが保証されています. Android で onPreviewFrame 中に YUV->RGB(画像処理)->YUVを変換するを参照してください。.
    • Androidの最新バージョンとコードの最適化により、おそらくより速く、またはより良く実行できる可能性があります.

そのコードは基本的な考え方を示しています。次に、次のフェーズに進みます。

  1. カメラの Surface ビューを十分に小さく設定して、トップ ビューのフェードされていないセクションの後ろに隠れるようにします。つまり、次のように変更android:layout_height60dpます。

  2. 中央の SurfaceView をマップ情報を受け取るように設定します。

于 2013-04-21T14:31:03.997 に答える