Il existe plusieurs tutoriels qui expliquent comment obtenir un simple aperçu de la caméra et le faire fonctionner sur un appareil Android. Mais je n'ai trouvé aucun exemple qui explique comment manipuler l'image avant qu'elle ne soit rendu.
Ce que je veux faire est d'implémenter des filtres de couleur personnalisés pour simuler par exemple carence en rouge et/ou vert.
J'ai fait quelques recherches à ce sujet et mis sur pied un exemple de travail (ish). Voici ce que j'ai trouvé. Il est assez facile d'extraire les données brutes de l'appareil photo. Il est renvoyé sous forme de tableau d'octets YUV. Vous devez le dessiner manuellement sur une surface pour pouvoir le modifier. Pour ce faire, vous devez disposer d'une SurfaceView avec laquelle vous pouvez exécuter manuellement des appels de tirage. Il y a quelques indicateurs que vous pouvez définir pour accomplir cela.
Pour effectuer l'appel de dessin manuellement, vous devez convertir le tableau d'octets en une sorte de bitmap. Les bitmaps et le BitmapDecoder ne semblent pas très bien gérer le tableau d'octets YUV à ce stade. Un bogue a été déposé pour cela, mais ne savez pas quel est le statut à ce sujet. Les gens ont donc essayé de décoder eux-mêmes le tableau d'octets au format RVB.
On dirait que faire le décodage manuellement a été un peu lent et les gens ont eu différents degrés de succès avec. Quelque chose comme ça devrait probablement être vraiment fait avec du code natif au niveau NDK.
Pourtant, il est possible de le faire fonctionner. De plus, ma petite démo est juste que je passe quelques heures à pirater des trucs ensemble (je suppose que cela a attiré mon imagination un peu trop;)). Donc, il y a des chances que quelques ajustements vous permettent d'améliorer considérablement ce que j'ai réussi à faire fonctionner.
Ce petit extrait de code contient également quelques autres joyaux que j'ai trouvés. Si tout ce que vous voulez est de pouvoir dessiner sur la surface, vous pouvez remplacer la fonction onDraw de la surface - vous pouvez potentiellement analyser l'image de la caméra retournée et dessiner une superposition - ce serait beaucoup plus rapide que d'essayer de traiter chaque image. De plus, j'ai changé le SurfaceHolder.SURFACE_TYPE_NORMAL de ce qui serait nécessaire si vous vouliez qu'un aperçu de la caméra apparaisse. Donc, quelques modifications au code - le code commenté:
//try { mCamera.setPreviewDisplay(holder); } catch (IOException e)
// { Log.e("Camera", "mCamera.setPreviewDisplay(holder);"); }
Et le:
SurfaceHolder.SURFACE_TYPE_NORMAL //SurfaceHolder.SURFACE_TYPE_Push_BUFFERS - for preview to work
Devrait vous permettre de superposer des images en fonction de l'aperçu de la caméra au-dessus de l'aperçu réel.
Quoi qu'il en soit, voici un morceau de code de travail - devrait vous donner quelque chose pour commencer.
Mettez simplement une ligne de code dans l'une de vos vues comme celle-ci:
<pathtocustomview.MySurfaceView Android:id="@+id/surface_camera"
Android:layout_width="fill_parent" Android:layout_height="10dip"
Android:layout_weight="1">
</pathtocustomview.MySurfaceView>
Et incluez cette classe dans votre source quelque part:
package pathtocustomview;
import Java.io.IOException;
import Java.nio.Buffer;
import Android.content.Context;
import Android.graphics.Bitmap;
import Android.graphics.BitmapFactory;
import Android.graphics.Canvas;
import Android.graphics.Paint;
import Android.graphics.Rect;
import Android.hardware.Camera;
import Android.util.AttributeSet;
import Android.util.Log;
import Android.view.SurfaceHolder;
import Android.view.SurfaceHolder.Callback;
import Android.view.SurfaceView;
public class MySurfaceView extends SurfaceView implements Callback,
Camera.PreviewCallback {
private SurfaceHolder mHolder;
private Camera mCamera;
private boolean isPreviewRunning = false;
private byte [] rgbbuffer = new byte[256 * 256];
private int [] rgbints = new int[256 * 256];
protected final Paint rectanglePaint = new Paint();
public MySurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
rectanglePaint.setARGB(100, 200, 0, 0);
rectanglePaint.setStyle(Paint.Style.FILL);
rectanglePaint.setStrokeWidth(2);
mHolder = getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_NORMAL);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawRect(new Rect((int) Math.random() * 100,
(int) Math.random() * 100, 200, 200), rectanglePaint);
Log.w(this.getClass().getName(), "On Draw Called");
}
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
public void surfaceCreated(SurfaceHolder holder) {
synchronized (this) {
this.setWillNotDraw(false); // This allows us to make our own draw
// calls to this canvas
mCamera = Camera.open();
Camera.Parameters p = mCamera.getParameters();
p.setPreviewSize(240, 160);
mCamera.setParameters(p);
//try { mCamera.setPreviewDisplay(holder); } catch (IOException e)
// { Log.e("Camera", "mCamera.setPreviewDisplay(holder);"); }
mCamera.startPreview();
mCamera.setPreviewCallback(this);
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
synchronized (this) {
try {
if (mCamera != null) {
mCamera.stopPreview();
isPreviewRunning = false;
mCamera.release();
}
} catch (Exception e) {
Log.e("Camera", e.getMessage());
}
}
}
public void onPreviewFrame(byte[] data, Camera camera) {
Log.d("Camera", "Got a camera frame");
Canvas c = null;
if(mHolder == null){
return;
}
try {
synchronized (mHolder) {
c = mHolder.lockCanvas(null);
// Do your drawing here
// So this data value you're getting back is formatted in YUV format and you can't do much
// with it until you convert it to rgb
int bwCounter=0;
int yuvsCounter=0;
for (int y=0;y<160;y++) {
System.arraycopy(data, yuvsCounter, rgbbuffer, bwCounter, 240);
yuvsCounter=yuvsCounter+240;
bwCounter=bwCounter+256;
}
for(int i = 0; i < rgbints.length; i++){
rgbints[i] = (int)rgbbuffer[i];
}
//decodeYUV(rgbbuffer, data, 100, 100);
c.drawBitmap(rgbints, 0, 256, 0, 0, 256, 256, false, new Paint());
Log.d("SOMETHING", "Got Bitmap");
}
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
mHolder.unlockCanvasAndPost(c);
}
}
}
}
J'ai utilisé la solution de walta mais j'ai eu quelques problèmes avec la conversion YUV, les tailles de sortie des cadres de caméra et le plantage à la sortie de la caméra.
Enfin, le code suivant a fonctionné pour moi:
public class MySurfaceView extends SurfaceView implements Callback, Camera.PreviewCallback {
private static final String TAG = "MySurfaceView";
private int width;
private int height;
private SurfaceHolder mHolder;
private Camera mCamera;
private int[] rgbints;
private boolean isPreviewRunning = false;
private int mMultiplyColor;
public MySurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
mHolder = getHolder();
mHolder.addCallback(this);
mMultiplyColor = getResources().getColor(R.color.multiply_color);
}
// @Override
// protected void onDraw(Canvas canvas) {
// Log.w(this.getClass().getName(), "On Draw Called");
// }
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
synchronized (this) {
if (isPreviewRunning)
return;
this.setWillNotDraw(false); // This allows us to make our own draw calls to this canvas
mCamera = Camera.open();
isPreviewRunning = true;
Camera.Parameters p = mCamera.getParameters();
Size size = p.getPreviewSize();
width = size.width;
height = size.height;
p.setPreviewFormat(ImageFormat.NV21);
showSupportedCameraFormats(p);
mCamera.setParameters(p);
rgbints = new int[width * height];
// try { mCamera.setPreviewDisplay(holder); } catch (IOException e)
// { Log.e("Camera", "mCamera.setPreviewDisplay(holder);"); }
mCamera.startPreview();
mCamera.setPreviewCallback(this);
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
synchronized (this) {
try {
if (mCamera != null) {
//mHolder.removeCallback(this);
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
isPreviewRunning = false;
mCamera.release();
}
} catch (Exception e) {
Log.e("Camera", e.getMessage());
}
}
}
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
// Log.d("Camera", "Got a camera frame");
if (!isPreviewRunning)
return;
Canvas canvas = null;
if (mHolder == null) {
return;
}
try {
synchronized (mHolder) {
canvas = mHolder.lockCanvas(null);
int canvasWidth = canvas.getWidth();
int canvasHeight = canvas.getHeight();
decodeYUV(rgbints, data, width, height);
// draw the decoded image, centered on canvas
canvas.drawBitmap(rgbints, 0, width, canvasWidth-((width+canvasWidth)>>1), canvasHeight-((height+canvasHeight)>>1), width, height, false, null);
// use some color filter
canvas.drawColor(mMultiplyColor, Mode.MULTIPLY);
}
} catch (Exception e){
e.printStackTrace();
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (canvas != null) {
mHolder.unlockCanvasAndPost(canvas);
}
}
}
/**
* Decodes YUV frame to a buffer which can be use to create a bitmap. use
* this for OS < FROYO which has a native YUV decoder decode Y, U, and V
* values on the YUV 420 buffer described as YCbCr_422_SP by Android
*
* @param rgb
* the outgoing array of RGB bytes
* @param fg
* the incoming frame bytes
* @param width
* of source frame
* @param height
* of source frame
* @throws NullPointerException
* @throws IllegalArgumentException
*/
public void decodeYUV(int[] out, byte[] fg, int width, int height) throws NullPointerException, IllegalArgumentException {
int sz = width * height;
if (out == null)
throw new NullPointerException("buffer out is null");
if (out.length < sz)
throw new IllegalArgumentException("buffer out size " + out.length + " < minimum " + sz);
if (fg == null)
throw new NullPointerException("buffer 'fg' is null");
if (fg.length < sz)
throw new IllegalArgumentException("buffer fg size " + fg.length + " < minimum " + sz * 3 / 2);
int i, j;
int Y, Cr = 0, Cb = 0;
for (j = 0; j < height; j++) {
int pixPtr = j * width;
final int jDiv2 = j >> 1;
for (i = 0; i < width; i++) {
Y = fg[pixPtr];
if (Y < 0)
Y += 255;
if ((i & 0x1) != 1) {
final int cOff = sz + jDiv2 * width + (i >> 1) * 2;
Cb = fg[cOff];
if (Cb < 0)
Cb += 127;
else
Cb -= 128;
Cr = fg[cOff + 1];
if (Cr < 0)
Cr += 127;
else
Cr -= 128;
}
int R = Y + Cr + (Cr >> 2) + (Cr >> 3) + (Cr >> 5);
if (R < 0)
R = 0;
else if (R > 255)
R = 255;
int G = Y - (Cb >> 2) + (Cb >> 4) + (Cb >> 5) - (Cr >> 1) + (Cr >> 3) + (Cr >> 4) + (Cr >> 5);
if (G < 0)
G = 0;
else if (G > 255)
G = 255;
int B = Y + Cb + (Cb >> 1) + (Cb >> 2) + (Cb >> 6);
if (B < 0)
B = 0;
else if (B > 255)
B = 255;
out[pixPtr++] = 0xff000000 + (B << 16) + (G << 8) + R;
}
}
}
private void showSupportedCameraFormats(Parameters p) {
List<Integer> supportedPictureFormats = p.getSupportedPreviewFormats();
Log.d(TAG, "preview format:" + cameraFormatIntToString(p.getPreviewFormat()));
for (Integer x : supportedPictureFormats) {
Log.d(TAG, "suppoterd format: " + cameraFormatIntToString(x.intValue()));
}
}
private String cameraFormatIntToString(int format) {
switch (format) {
case PixelFormat.JPEG:
return "JPEG";
case PixelFormat.YCbCr_420_SP:
return "NV21";
case PixelFormat.YCbCr_422_I:
return "YUY2";
case PixelFormat.YCbCr_422_SP:
return "NV16";
case PixelFormat.RGB_565:
return "RGB_565";
default:
return "Unknown:" + format;
}
}
}
Pour l'utiliser, exécutez à partir de votre activité onCréez le code suivant:
SurfaceView surfaceView = new MySurfaceView(this, null);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
surfaceView.setLayoutParams(layoutParams);
mRelativeLayout.addView(surfaceView);
Avez-vous regardé GPUImage?
Il s'agissait à l'origine d'une bibliothèque OSX/iOS créée par Brad Larson, qui existe en tant que wrapper Objective-C autour d'OpenGL/ES.
https://github.com/BradLarson/GPUImage
Les gens de CyberAgent ont créé un Android (qui n'a pas de parité complète des fonctionnalités), qui est un ensemble de Java wrappers au-dessus d'OpenGLES C'est un niveau relativement élevé, et assez facile à implémenter, avec beaucoup des mêmes fonctionnalités mentionnées ci-dessus ...