Skip to content

Commit d681cc8

Browse files
committed
TLIB.05-Solution-DrawEmojiOverFaces
1 parent 8691c1f commit d681cc8

2 files changed

Lines changed: 100 additions & 22 deletions

File tree

app/src/main/java/com/example/android/emojify/Emojifier.java

Lines changed: 96 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import android.content.Context;
2020
import android.graphics.Bitmap;
21+
import android.graphics.BitmapFactory;
22+
import android.graphics.Canvas;
2123
import android.util.Log;
2224
import android.util.SparseArray;
2325
import android.widget.Toast;
@@ -30,18 +32,18 @@ class Emojifier {
3032

3133
private static final String LOG_TAG = Emojifier.class.getSimpleName();
3234

35+
private static final float EMOJI_SCALE_FACTOR = .9f;
3336
private static final double SMILING_PROB_THRESHOLD = .15;
3437
private static final double EYE_OPEN_PROB_THRESHOLD = .5;
3538

3639
/**
37-
* Method for detecting faces in a bitmap.
40+
* Method for detecting faces in a bitmap, and drawing emoji depending on the facial
41+
* expression.
3842
*
3943
* @param context The application context.
4044
* @param picture The picture in which to detect the faces.
4145
*/
42-
static void detectFaces(Context context, Bitmap picture) {
43-
44-
// TODO (3): Change the name of the detectFaces() method to detectFacesAndOverlayEmoji() and the return type from void to Bitmap
46+
static Bitmap detectFacesandOverlayEmoji(Context context, Bitmap picture) {
4547

4648
// Create the face detector, disable tracking and enable classifications
4749
FaceDetector detector = new FaceDetector.Builder(context)
@@ -58,29 +60,67 @@ static void detectFaces(Context context, Bitmap picture) {
5860
// Log the number of faces
5961
Log.d(LOG_TAG, "detectFaces: number of faces = " + faces.size());
6062

61-
// TODO (7): Create a variable called resultBitmap and initialize it to the original picture bitmap passed into the detectFacesAndOverlayEmoji() method
63+
// Initialize result bitmap to original picture
64+
Bitmap resultBitmap = picture;
65+
6266
// If there are no faces detected, show a Toast message
63-
if(faces.size() == 0) {
67+
if (faces.size() == 0) {
6468
Toast.makeText(context, R.string.no_faces_message, Toast.LENGTH_SHORT).show();
6569
} else {
6670

6771
// Iterate through the faces
6872
for (int i = 0; i < faces.size(); ++i) {
6973
Face face = faces.valueAt(i);
70-
// Get the appropriate emoji for each face
71-
whichEmoji(face);
72-
73-
// TODO (4): Create a variable called emojiBitmap to hold the appropriate Emoji bitmap and remove the call to whichEmoji()
74-
// TODO (5): Create a switch statement on the result of the whichEmoji() call, and assign the proper emoji bitmap to the variable you created
75-
// TODO (8): Call addBitmapToFace(), passing in the resultBitmap, the emojiBitmap and the Face object, and assigning the result to resultBitmap
7674

75+
Bitmap emojiBitmap;
76+
switch (whichEmoji(face)) {
77+
case SMILE:
78+
emojiBitmap = BitmapFactory.decodeResource(context.getResources(),
79+
R.drawable.smile);
80+
break;
81+
case FROWN:
82+
emojiBitmap = BitmapFactory.decodeResource(context.getResources(),
83+
R.drawable.frown);
84+
break;
85+
case LEFT_WINK:
86+
emojiBitmap = BitmapFactory.decodeResource(context.getResources(),
87+
R.drawable.leftwink);
88+
break;
89+
case RIGHT_WINK:
90+
emojiBitmap = BitmapFactory.decodeResource(context.getResources(),
91+
R.drawable.rightwink);
92+
break;
93+
case LEFT_WINK_FROWN:
94+
emojiBitmap = BitmapFactory.decodeResource(context.getResources(),
95+
R.drawable.leftwinkfrown);
96+
break;
97+
case RIGHT_WINK_FROWN:
98+
emojiBitmap = BitmapFactory.decodeResource(context.getResources(),
99+
R.drawable.rightwinkfrown);
100+
break;
101+
case CLOSED_EYE_SMILE:
102+
emojiBitmap = BitmapFactory.decodeResource(context.getResources(),
103+
R.drawable.closed_smile);
104+
break;
105+
case CLOSED_EYE_FROWN:
106+
emojiBitmap = BitmapFactory.decodeResource(context.getResources(),
107+
R.drawable.closed_frown);
108+
break;
109+
default:
110+
emojiBitmap = null;
111+
Toast.makeText(context, R.string.no_emoji, Toast.LENGTH_SHORT).show();
112+
}
113+
114+
// Add the emojiBitmap to the proper position in the original image
115+
resultBitmap = addBitmapToFace(resultBitmap, emojiBitmap, face);
77116
}
78117
}
79118

80119

81120
// Release the detector
82121
detector.release();
83-
// TODO (9): Return the resultBitmap
122+
123+
return resultBitmap;
84124
}
85125

86126

@@ -91,9 +131,7 @@ static void detectFaces(Context context, Bitmap picture) {
91131
* @param face The face for which you pick an emoji.
92132
*/
93133

94-
private static void whichEmoji(Face face) {
95-
96-
// TODO (1): Change the return type of the whichEmoji() method from void to Emoji.
134+
private static Emoji whichEmoji(Face face) {
97135
// Log all the probabilities
98136
Log.d(LOG_TAG, "whichEmoji: smilingProb = " + face.getIsSmilingProbability());
99137
Log.d(LOG_TAG, "whichEmoji: leftEyeOpenProb = "
@@ -136,10 +174,50 @@ private static void whichEmoji(Face face) {
136174
// Log the chosen Emoji
137175
Log.d(LOG_TAG, "whichEmoji: " + emoji.name());
138176

139-
// TODO (2): Have the method return the selected Emoji type.
177+
return emoji;
178+
}
179+
180+
/**
181+
* Combines the original picture with the emoji bitmaps
182+
*
183+
* @param backgroundBitmap The original picture
184+
* @param emojiBitmap The chosen emoji
185+
* @param face The detected face
186+
* @return The final bitmap, including the emojis over the faces
187+
*/
188+
private static Bitmap addBitmapToFace(Bitmap backgroundBitmap, Bitmap emojiBitmap, Face face) {
189+
190+
// Initialize the results bitmap to be a mutable copy of the original image
191+
Bitmap resultBitmap = Bitmap.createBitmap(backgroundBitmap.getWidth(),
192+
backgroundBitmap.getHeight(), backgroundBitmap.getConfig());
193+
194+
// Scale the emoji so it looks better on the face
195+
float scaleFactor = EMOJI_SCALE_FACTOR;
196+
197+
// Determine the size of the emoji to match the width of the face and preserve aspect ratio
198+
int newEmojiWidth = (int) (face.getWidth() * scaleFactor);
199+
int newEmojiHeight = (int) (emojiBitmap.getHeight() *
200+
newEmojiWidth / emojiBitmap.getWidth() * scaleFactor);
201+
202+
203+
// Scale the emoji
204+
emojiBitmap = Bitmap.createScaledBitmap(emojiBitmap, newEmojiWidth, newEmojiHeight, false);
205+
206+
// Determine the emoji position so it best lines up with the face
207+
float emojiPositionX =
208+
(face.getPosition().x + face.getWidth() / 2) - emojiBitmap.getWidth() / 2;
209+
float emojiPositionY =
210+
(face.getPosition().y + face.getHeight() / 2) - emojiBitmap.getHeight() / 3;
211+
212+
// Create the canvas and draw the bitmaps to it
213+
Canvas canvas = new Canvas(resultBitmap);
214+
canvas.drawBitmap(backgroundBitmap, 0, 0, null);
215+
canvas.drawBitmap(emojiBitmap, emojiPositionX, emojiPositionY, null);
216+
217+
return resultBitmap;
140218
}
219+
141220

142-
// TODO (6) Create a method called addBitmapToFace() which takes the background bitmap, the Emoji bitmap, and a Face object as arguments and returns the combined bitmap with the Emoji over the face.
143221
// Enum for all possible Emojis
144222
private enum Emoji {
145223
SMILE,

app/src/main/java/com/example/android/emojify/MainActivity.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,10 @@ private void processAndSetImage() {
181181
// Resample the saved image to fit the ImageView
182182
mResultsBitmap = BitmapUtils.resamplePic(this, mTempPhotoPath);
183183

184-
// Detect the faces
185-
Emojifier.detectFaces(this, mResultsBitmap);
186-
// TODO (10): Change the method call from detectFaces() to detectFacesAndOverlayEmoji() and assign the result to mResultsBitmap.
187-
184+
185+
// Detect the faces and overlay the appropriate emoji
186+
mResultsBitmap = Emojifier.detectFacesandOverlayEmoji(this, mResultsBitmap);
187+
188188
// Set the new bitmap to the ImageView
189189
mImageView.setImageBitmap(mResultsBitmap);
190190
}

0 commit comments

Comments
 (0)