diff --git a/.gitignore b/.gitignore index da44912..0422061 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ local.properties # Signing signing/ +build/ diff --git a/FaceCropper-library/build.gradle b/FaceCropper-library/build.gradle index 0b1954b..1c3159b 100644 --- a/FaceCropper-library/build.gradle +++ b/FaceCropper-library/build.gradle @@ -14,17 +14,17 @@ * limitations under the License. */ -apply plugin: 'android-library' +apply plugin: 'com.android.library' android { compileSdkVersion 19 - buildToolsVersion "19.0.3" + buildToolsVersion "20.0.0" defaultConfig { minSdkVersion 8 targetSdkVersion 16 - versionCode 1 - versionName "1.0" + versionCode 2 + versionName "1.1" } buildTypes { release { @@ -38,9 +38,11 @@ dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) } +import com.android.builder.core.BuilderConstants + android.libraryVariants.all { variant -> def name = variant.buildType.name - if (name.equals(com.android.builder.BuilderConstants.DEBUG)) { + if (name.equals(BuilderConstants.DEBUG)) { return; // Skip debug builds. } def task = project.tasks.create "jar${name.capitalize()}", Jar diff --git a/FaceCropper-library/src/main/java/cat/lafosca/facecropper/FaceCropper.java b/FaceCropper-library/src/main/java/cat/lafosca/facecropper/FaceCropper.java index 652808c..5eb6d48 100644 --- a/FaceCropper-library/src/main/java/cat/lafosca/facecropper/FaceCropper.java +++ b/FaceCropper-library/src/main/java/cat/lafosca/facecropper/FaceCropper.java @@ -19,7 +19,13 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Point; import android.graphics.PointF; +import android.graphics.Rect; import android.media.FaceDetector; import android.util.Log; @@ -41,15 +47,32 @@ public enum SizeMode { FaceMarginPx, EyeDistanceFactorMargin }; private float mEyeDistanceFactorMargin = 2f; private int mMaxFaces = MAX_FACES; private SizeMode mSizeMode = SizeMode.EyeDistanceFactorMargin; + private boolean mDebug; + private Paint mDebugPainter; + private Paint mDebugAreaPainter; - public FaceCropper() { } + public FaceCropper() { + initPaints(); + } public FaceCropper(int faceMarginPx) { setFaceMarginPx(faceMarginPx); + initPaints(); } public FaceCropper(float eyesDistanceFactorMargin) { setEyeDistanceFactorMargin(eyesDistanceFactorMargin); + initPaints(); + } + + private void initPaints() { + mDebugPainter = new Paint(); + mDebugPainter.setColor(Color.RED); + mDebugPainter.setAlpha(80); + + mDebugAreaPainter = new Paint(); + mDebugAreaPainter.setColor(Color.GREEN); + mDebugAreaPainter.setAlpha(80); } public int getMaxFaces() { @@ -90,48 +113,57 @@ public void setEyeDistanceFactorMargin(float eyeDistanceFactorMargin) { mSizeMode = SizeMode.EyeDistanceFactorMargin; } - public Bitmap cropFace(Context ctx, int resDrawable) { - // Set internal configuration to RGB_565 - BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); - bitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565; + public boolean isDebug() { + return mDebug; + } - return cropFace(BitmapFactory.decodeResource(ctx.getResources(), resDrawable, bitmapOptions)); + public void setDebug(boolean debug) { + mDebug = debug; } - public Bitmap cropFace(Bitmap original) { + protected CropResult cropFace(Bitmap original, boolean debug) { Bitmap fixedBitmap = BitmapUtils.forceEvenBitmapSize(original); fixedBitmap = BitmapUtils.forceConfig565(fixedBitmap); + Bitmap mutableBitmap = fixedBitmap.copy(Bitmap.Config.RGB_565, true); + + if (fixedBitmap != mutableBitmap) { + fixedBitmap.recycle(); + } FaceDetector faceDetector = new FaceDetector( - fixedBitmap.getWidth(), fixedBitmap.getHeight(), + mutableBitmap.getWidth(), mutableBitmap.getHeight(), mMaxFaces); FaceDetector.Face[] faces = new FaceDetector.Face[mMaxFaces]; // The bitmap must be in 565 format (for now). - int faceCount = faceDetector.findFaces(fixedBitmap, faces); + int faceCount = faceDetector.findFaces(mutableBitmap, faces); if (BuildConfig.DEBUG) { Log.d(LOG_TAG, faceCount + " faces found"); } if (faceCount == 0) { - return fixedBitmap; + return new CropResult(mutableBitmap); } - int initX = fixedBitmap.getWidth(); - int initY = fixedBitmap.getHeight(); + int initX = mutableBitmap.getWidth(); + int initY = mutableBitmap.getHeight(); int endX = 0; int endY = 0; PointF centerFace = new PointF(); + Canvas canvas = new Canvas(mutableBitmap); + canvas.drawBitmap(mutableBitmap, new Matrix(), null); + // Calculates minimum box to fit all detected faces for (int i = 0; i < faceCount; i++) { FaceDetector.Face face = faces[i]; // Eyes distance * 3 usually fits an entire face int faceSize = (int) (face.eyesDistance() * 3); + if (SizeMode.FaceMarginPx.equals(mSizeMode)) { faceSize += mFaceMarginPx * 2; // *2 for top and down/right and left effect } @@ -143,6 +175,11 @@ else if (SizeMode.EyeDistanceFactorMargin.equals(mSizeMode)) { face.getMidPoint(centerFace); + if (debug) { + canvas.drawPoint(centerFace.x, centerFace.y, mDebugPainter); + canvas.drawCircle(centerFace.x, centerFace.y, face.eyesDistance() * 1.5f, mDebugPainter); + } + int tInitX = (int) (centerFace.x - faceSize / 2); int tInitY = (int) (centerFace.y - faceSize / 2); tInitX = Math.max(0, tInitX); @@ -150,8 +187,8 @@ else if (SizeMode.EyeDistanceFactorMargin.equals(mSizeMode)) { int tEndX = tInitX + faceSize; int tEndY = tInitY + faceSize; - tEndX = Math.min(tEndX, fixedBitmap.getWidth()); - tEndY = Math.min(tEndY, fixedBitmap.getHeight()); + tEndX = Math.min(tEndX, mutableBitmap.getWidth()); + tEndY = Math.min(tEndY, mutableBitmap.getHeight()); initX = Math.min(initX, tInitX); initY = Math.min(initY, tInitY); @@ -162,18 +199,101 @@ else if (SizeMode.EyeDistanceFactorMargin.equals(mSizeMode)) { int sizeX = endX - initX; int sizeY = endY - initY; - if (sizeX + initX > fixedBitmap.getWidth()) { - sizeX = fixedBitmap.getWidth() - initX; + if (sizeX + initX > mutableBitmap.getWidth()) { + sizeX = mutableBitmap.getWidth() - initX; } - if (sizeY + initY > fixedBitmap.getHeight()) { - sizeY = fixedBitmap.getHeight() - initY; + if (sizeY + initY > mutableBitmap.getHeight()) { + sizeY = mutableBitmap.getHeight() - initY; } - Bitmap croppedBitmap = Bitmap.createBitmap(fixedBitmap, initX, initY, sizeX, sizeY); - if (fixedBitmap != croppedBitmap) { - fixedBitmap.recycle(); + Point init = new Point(initX, initY); + Point end = new Point(initX + sizeX, initY + sizeY); + + return new CropResult(mutableBitmap, init, end); + } + + @Deprecated + public Bitmap cropFace(Context ctx, int resDrawable) { + return getCroppedImage(ctx, resDrawable); + } + + @Deprecated + public Bitmap cropFace(Bitmap bitmap) { + return getCroppedImage(bitmap); + } + + public Bitmap getFullDebugImage(Context ctx, int resDrawable) { + // Set internal configuration to RGB_565 + BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); + bitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565; + + return getFullDebugImage(BitmapFactory.decodeResource(ctx.getResources(), resDrawable, bitmapOptions)); + } + + public Bitmap getFullDebugImage(Bitmap bitmap) { + CropResult result = cropFace(bitmap, true); + Canvas canvas = new Canvas(result.getBitmap()); + + canvas.drawBitmap(result.getBitmap(), new Matrix(), null); + canvas.drawRect(result.getInit().x, + result.getInit().y, + result.getEnd().x, + result.getEnd().y, + mDebugAreaPainter); + + return result.getBitmap(); + } + + public Bitmap getCroppedImage(Context ctx, int resDrawable) { + // Set internal configuration to RGB_565 + BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); + bitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565; + + return getCroppedImage(BitmapFactory.decodeResource(ctx.getResources(), resDrawable, bitmapOptions)); + } + + public Bitmap getCroppedImage(Bitmap bitmap) { + CropResult result = cropFace(bitmap, mDebug); + Bitmap croppedBitmap = Bitmap.createBitmap(result.getBitmap(), + result.getInit().x, + result.getInit().y, + result.getEnd().x - result.getInit().x, + result.getEnd().y - result.getInit().y); + + if (result.getBitmap() != croppedBitmap) { + result.getBitmap().recycle(); } return croppedBitmap; } + + protected class CropResult { + Bitmap mBitmap; + Point mInit; + Point mEnd; + + public CropResult(Bitmap bitmap, Point init, Point end) { + mBitmap = bitmap; + mInit = init; + mEnd = end; + } + + public CropResult(Bitmap bitmap) { + mBitmap = bitmap; + mInit = new Point(0, 0); + mEnd = new Point(bitmap.getWidth(), bitmap.getHeight()); + } + + public Bitmap getBitmap() { + return mBitmap; + } + + public Point getInit() { + return mInit; + } + + public Point getEnd() { + return mEnd; + } + } } diff --git a/FaceCropper-sample/build.gradle b/FaceCropper-sample/build.gradle index fe0e172..dc7a835 100644 --- a/FaceCropper-sample/build.gradle +++ b/FaceCropper-sample/build.gradle @@ -1,8 +1,8 @@ -apply plugin: 'android' +apply plugin: 'com.android.application' android { compileSdkVersion 19 - buildToolsVersion "19.0.3" + buildToolsVersion "20.0.0" defaultConfig { minSdkVersion 8 @@ -19,8 +19,8 @@ android { } dependencies { - compile 'com.android.support:appcompat-v7:+' compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.squareup.picasso:picasso:2.2.0' compile project(':FaceCropper-library') + compile 'com.android.support:appcompat-v7:20.+' + compile 'com.squareup.picasso:picasso:2.2.0' } diff --git a/FaceCropper-sample/src/main/AndroidManifest.xml b/FaceCropper-sample/src/main/AndroidManifest.xml index ff0623a..b4c0b70 100644 --- a/FaceCropper-sample/src/main/AndroidManifest.xml +++ b/FaceCropper-sample/src/main/AndroidManifest.xml @@ -3,11 +3,10 @@ package="cat.lafosca.facecropper.sample" > - + - diff --git a/FaceCropper-sample/src/main/java/cat/lafosca/facecropper/sample/MainActivity.java b/FaceCropper-sample/src/main/java/cat/lafosca/facecropper/sample/MainActivity.java index db7d258..446c727 100644 --- a/FaceCropper-sample/src/main/java/cat/lafosca/facecropper/sample/MainActivity.java +++ b/FaceCropper-sample/src/main/java/cat/lafosca/facecropper/sample/MainActivity.java @@ -25,12 +25,12 @@ public class MainActivity extends ActionBarActivity { @Override public Bitmap transform(Bitmap source) { - return mFaceCropper.cropFace(source); + return mFaceCropper.getCroppedImage(source); } @Override public String key() { - StringBuilder builder = new StringBuilder(48); + StringBuilder builder = new StringBuilder(); builder.append("faceCrop("); builder.append("minSize=").append(mFaceCropper.getFaceMinSize()); @@ -47,6 +47,32 @@ public String key() { } }; + private Transformation mDebugCropTransformation = new Transformation() { + + @Override + public Bitmap transform(Bitmap source) { + return mFaceCropper.getFullDebugImage(source); + } + + @Override + public String key() { + StringBuilder builder = new StringBuilder(); + + builder.append("faceDebugCrop("); + builder.append("minSize=").append(mFaceCropper.getFaceMinSize()); + builder.append(",maxFaces=").append(mFaceCropper.getMaxFaces()); + + FaceCropper.SizeMode mode = mFaceCropper.getSizeMode(); + if (FaceCropper.SizeMode.EyeDistanceFactorMargin.equals(mode)) { + builder.append(",distFactor=").append(mFaceCropper.getEyeDistanceFactorMargin()); + } else if (FaceCropper.SizeMode.FaceMarginPx.equals(mode)) { + builder.append(",margin=").append(mFaceCropper.getFaceMarginPx()); + } + + return builder.append(")").toString(); + } + }; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -54,6 +80,7 @@ protected void onCreate(Bundle savedInstanceState) { mFaceCropper = new FaceCropper(1f); mFaceCropper.setFaceMinSize(0); + mFaceCropper.setDebug(true); mPicasso = Picasso.with(this); final ImageAdapter adapter = new ImageAdapter(); @@ -135,7 +162,7 @@ public void setupView(View v, int position) { ImageView image = (ImageView) v.findViewById(R.id.imageView); ImageView imageCropped = (ImageView) v.findViewById(R.id.imageViewCropped); - mPicasso.load(urls[position]).into(image); + mPicasso.load(urls[position]).transform(mDebugCropTransformation).into(image); mPicasso.load(urls[position]) .config(Bitmap.Config.RGB_565) diff --git a/FaceCropper-sample/src/main/res/values/strings.xml b/FaceCropper-sample/src/main/res/values/strings.xml index 8ce5e79..234f1a9 100644 --- a/FaceCropper-sample/src/main/res/values/strings.xml +++ b/FaceCropper-sample/src/main/res/values/strings.xml @@ -4,5 +4,6 @@ FaceCropper Hello world! Settings + DebugActivity diff --git a/build.gradle b/build.gradle index 80eec1a..e33f142 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:0.9.+' + classpath 'com.android.tools.build:gradle:0.12.+' } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5de946b..1e61d1f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-all.zip +distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip