/*
* Copyright (C) 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.cellbots.ioioshoot;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.media.FaceDetector;
import android.media.FaceDetector.Face;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.concurrent.ArrayBlockingQueue;
/**
* A worker thread which will detect faces and notify any callbacks if a face is
* detected.
*
* @author raymes@google.com (Raymes Khoury)
*/
public class FaceDetectThread extends Thread implements CameraView.Callback {
interface Callback {
public void faceDetected(
PointF face, float eyesDistance, float confidence, PointF imageSize);
public void faceNotDetected();
}
private class Image {
byte[] mData;
int mHeight;
int mWidth;
int mFormat;
boolean mReversed;
public Image(byte[] data, int width, int height, int format, boolean reversed) {
mData = data;
mWidth = width;
mHeight = height;
mFormat = format;
mReversed = reversed;
}
}
private ArrayBlockingQueue<Image> mImages;
private PointF mImageSize;
private Face[] mFaces;
private ArrayList<Callback> callbacks;
private FaceDetector mFaceDetector;
private boolean mProcessing;
private static final float MIN_QUALITY = 0.4f;
private static final float INCREMENT_QUALITY = 0.1f;
public FaceDetectThread() {
mImages = new ArrayBlockingQueue<Image>(1);
mImageSize = new PointF();
mFaces = new Face[1];
callbacks = new ArrayList<Callback>();
mProcessing = false;
}
public void addCallback(Callback c) {
callbacks.add(c);
}
public void addFace(byte[] data, int width, int height, int format, boolean reversed) {
boolean processing = false;
synchronized (this) {
processing = mProcessing;
}
if (mImages.isEmpty() && !processing) {
Image newImage = new Image(data.clone(), width, height, format, reversed);
mImages.add(newImage);
}
}
@Override
public void run() {
while (true) {
Image current = null;
try {
current = mImages.take();
synchronized (this) {
mProcessing = true;
}
} catch (InterruptedException e) {
continue;
}
Face f = findFace(current.mData, current.mWidth, current.mHeight, current.mFormat);
if (f != null) {
PointF point = new PointF();
f.getMidPoint(point);
if (current.mReversed) {
point.x = mImageSize.x - point.x;
}
for (Callback c : callbacks) {
c.faceDetected(point, f.eyesDistance(), f.confidence(), mImageSize);
}
} else {
for (Callback c : callbacks) {
c.faceNotDetected();
}
}
synchronized (this) {
mProcessing = false;
}
}
}
public Face findFace(byte[] data, int width, int height, int format) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
YuvImage yuvImage = new YuvImage(data, format, width, height, null);
yuvImage.compressToJpeg(new Rect(0, 0, width, height), 50, out);
byte[] imageBytes = out.toByteArray();
Bitmap image = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
float factor = (float) 0.8;
image = Bitmap.createScaledBitmap(
image, (int) (width * factor), (int) (height * factor), true);
mImageSize.set(image.getWidth(), image.getHeight());
if (mFaceDetector == null)
mFaceDetector = new FaceDetector(image.getWidth(), image.getHeight(), 1);
int faceCount = mFaceDetector.findFaces(image, mFaces);
if (faceCount > 0) {
return mFaces[0];
} else {
return null;
}
}
/*
* (non-Javadoc)
* @see com.cellbots.directcontrol.CameraView.Callback#imageReady(byte[],
* int, int, int)
*/
@Override
public void imageReady(byte[] data, int width, int height, int format, boolean reversed) {
addFace(data, width, height, format, reversed);
}
}