Take picture with drawable/paint on face using vision api - android

What I am trying?
I am trying to take picture with drawable/paint on face but, i am not able to get both on same picture.
What I have tried?
I have tried using CameraSource.takePicture but i am just getting face without any drawable/paint on it.
mCameraSource.takePicture(shutterCallback, new CameraSource.PictureCallback() {
#Override
public void onPictureTaken(byte[] bytes) {
try {
String mainpath = getExternalStorageDirectory() + separator + "TestXyz" + separator + "images" + separator;
File basePath = new File(mainpath);
if (!basePath.exists())
Log.d("CAPTURE_BASE_PATH", basePath.mkdirs() ? "Success": "Failed");
String path = mainpath + "photo_" + getPhotoTime() + ".jpg";
File captureFile = new File(path);
captureFile.createNewFile();
if (!captureFile.exists())
Log.d("CAPTURE_FILE_PATH", captureFile.createNewFile() ? "Success": "Failed");
FileOutputStream stream = new FileOutputStream(captureFile);
stream.write(bytes);
stream.flush();
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
I also tried using :
mPreview.setDrawingCacheEnabled(true);
Bitmap drawingCache = mPreview.getDrawingCache();
try {
String mainpath = getExternalStorageDirectory() + separator + "TestXyz" + separator + "images" + separator;
File basePath = new File(mainpath);
if (!basePath.exists())
Log.d("CAPTURE_BASE_PATH", basePath.mkdirs() ? "Success": "Failed");
String path = mainpath + "photo_" + getPhotoTime() + ".jpg";
File captureFile = new File(path);
captureFile.createNewFile();
if (!captureFile.exists())
Log.d("CAPTURE_FILE_PATH", captureFile.createNewFile() ? "Success": "Failed");
FileOutputStream stream = new FileOutputStream(captureFile);
drawingCache.compress(Bitmap.CompressFormat.PNG, 100, stream);
stream.flush();
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
in this case i am only getting what i draw on face. Here, mPreview is the CameraSourcePreview.
Just added capture button and added above code in this google example.

You are very close to achieve what you need :)
You have:
An image from the Camera of the face (First code snippet)
An image from the Canvas of the eyes overlay (Second code snippet)
What you need:
An image that has the face with the eyes overlay on top - A merged image.
How to merge?
To merge 2 images simply use a canvas, like so:
public Bitmap mergeBitmaps(Bitmap face, Bitmap overlay) {
// Create a new image with target size
int width = face.getWidth();
int height = face.getHeight();
Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Rect faceRect = new Rect(0,0,width,height);
Rect overlayRect = new Rect(0,0,overlay.getWidth(),overlay.getHeight());
// Draw face and then overlay (Make sure rects are as needed)
Canvas canvas = new Canvas(newBitmap);
canvas.drawBitmap(face, faceRect, faceRect, null);
canvas.drawBitmap(overlay, overlayRect, faceRect, null);
return newBitmap
}
Then you can save the new image, as you are doing now.
Full code would look like:
mCameraSource.takePicture(shutterCallback, new
CameraSource.PictureCallback() {
#Override
public void onPictureTaken(byte[] bytes) {
// Generate the Face Bitmap
BitmapFactory.Options options = new BitmapFactory.Options();
Bitmap face = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
// Generate the Eyes Overlay Bitmap
mPreview.setDrawingCacheEnabled(true);
Bitmap overlay = mPreview.getDrawingCache();
// Generate the final merged image
Bitmap result = mergeBitmaps(face, overlay);
// Save result image to file
try {
String mainpath = getExternalStorageDirectory() + separator + "TestXyz" + separator + "images" + separator;
File basePath = new File(mainpath);
if (!basePath.exists())
Log.d("CAPTURE_BASE_PATH", basePath.mkdirs() ? "Success": "Failed");
String path = mainpath + "photo_" + getPhotoTime() + ".jpg";
File captureFile = new File(path);
captureFile.createNewFile();
if (!captureFile.exists())
Log.d("CAPTURE_FILE_PATH", captureFile.createNewFile() ? "Success": "Failed");
FileOutputStream stream = new FileOutputStream(captureFile);
result.compress(Bitmap.CompressFormat.PNG, 100, stream);
stream.flush();
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
Note that the above is just an example code.
You should probably move the merging and saving to a file to a background thread.

I am able to capture image with drawable/paint on it by below solution :
private void captureImage() {
mPreview.setDrawingCacheEnabled(true);
Bitmap drawingCache = mPreview.getDrawingCache();
mCameraSource.takePicture(shutterCallback, new CameraSource.PictureCallback() {
#Override
public void onPictureTaken(byte[] bytes) {
int orientation = Exif.getOrientation(bytes);
Bitmap temp = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
Bitmap picture = rotateImage(temp,orientation);
Bitmap overlay = Bitmap.createBitmap(mGraphicOverlay.getWidth(),mGraphicOverlay.getHeight(),picture.getConfig());
Canvas canvas = new Canvas(overlay);
Matrix matrix = new Matrix();
matrix.setScale((float)overlay.getWidth()/(float)picture.getWidth(),(float)overlay.getHeight()/(float)picture.getHeight());
// mirror by inverting scale and translating
matrix.preScale(-1, 1);
matrix.postTranslate(canvas.getWidth(), 0);
Paint paint = new Paint();
canvas.drawBitmap(picture,matrix,paint);
canvas.drawBitmap(drawingCache,0,0,paint);
try {
String mainpath = getExternalStorageDirectory() + separator + "MaskIt" + separator + "images" + separator;
File basePath = new File(mainpath);
if (!basePath.exists())
Log.d("CAPTURE_BASE_PATH", basePath.mkdirs() ? "Success": "Failed");
String path = mainpath + "photo_" + getPhotoTime() + ".jpg";
File captureFile = new File(path);
captureFile.createNewFile();
if (!captureFile.exists())
Log.d("CAPTURE_FILE_PATH", captureFile.createNewFile() ? "Success": "Failed");
FileOutputStream stream = new FileOutputStream(captureFile);
overlay.compress(Bitmap.CompressFormat.PNG, 100, stream);
stream.flush();
stream.close();
picture.recycle();
drawingCache.recycle();
mPreview.setDrawingCacheEnabled(false);
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
Sometimes orientation issue also occurs on some devices. For that i used Exif class and rotateImage() function.
Exif Class (reference from here) :
public class Exif {
private static final String TAG = "CameraExif";
// Returns the degrees in clockwise. Values are 0, 90, 180, or 270.
public static int getOrientation(byte[] jpeg) {
if (jpeg == null) {
return 0;
}
int offset = 0;
int length = 0;
// ISO/IEC 10918-1:1993(E)
while (offset + 3 < jpeg.length && (jpeg[offset++] & 0xFF) == 0xFF) {
int marker = jpeg[offset] & 0xFF;
// Check if the marker is a padding.
if (marker == 0xFF) {
continue;
}
offset++;
// Check if the marker is SOI or TEM.
if (marker == 0xD8 || marker == 0x01) {
continue;
}
// Check if the marker is EOI or SOS.
if (marker == 0xD9 || marker == 0xDA) {
break;
}
// Get the length and check if it is reasonable.
length = pack(jpeg, offset, 2, false);
if (length < 2 || offset + length > jpeg.length) {
Log.e(TAG, "Invalid length");
return 0;
}
// Break if the marker is EXIF in APP1.
if (marker == 0xE1 && length >= 8 &&
pack(jpeg, offset + 2, 4, false) == 0x45786966 &&
pack(jpeg, offset + 6, 2, false) == 0) {
offset += 8;
length -= 8;
break;
}
// Skip other markers.
offset += length;
length = 0;
}
// JEITA CP-3451 Exif Version 2.2
if (length > 8) {
// Identify the byte order.
int tag = pack(jpeg, offset, 4, false);
if (tag != 0x49492A00 && tag != 0x4D4D002A) {
Log.e(TAG, "Invalid byte order");
return 0;
}
boolean littleEndian = (tag == 0x49492A00);
// Get the offset and check if it is reasonable.
int count = pack(jpeg, offset + 4, 4, littleEndian) + 2;
if (count < 10 || count > length) {
Log.e(TAG, "Invalid offset");
return 0;
}
offset += count;
length -= count;
// Get the count and go through all the elements.
count = pack(jpeg, offset - 2, 2, littleEndian);
while (count-- > 0 && length >= 12) {
// Get the tag and check if it is orientation.
tag = pack(jpeg, offset, 2, littleEndian);
if (tag == 0x0112) {
// We do not really care about type and count, do we?
int orientation = pack(jpeg, offset + 8, 2, littleEndian);
switch (orientation) {
case 1:
return 0;
case 3:
return 3;
case 6:
return 6;
case 8:
return 8;
}
Log.i(TAG, "Unsupported orientation");
return 0;
}
offset += 12;
length -= 12;
}
}
Log.i(TAG, "Orientation not found");
return 0;
}
private static int pack(byte[] bytes, int offset, int length,
boolean littleEndian) {
int step = 1;
if (littleEndian) {
offset += length - 1;
step = -1;
}
int value = 0;
while (length-- > 0) {
value = (value << 8) | (bytes[offset] & 0xFF);
offset += step;
}
return value;
}
}
rotateImage function :
private Bitmap rotateImage(Bitmap bm, int i) {
Matrix matrix = new Matrix();
switch (i) {
case ExifInterface.ORIENTATION_NORMAL:
return bm;
case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
matrix.setScale(-1, 1);
break;
case ExifInterface.ORIENTATION_ROTATE_180:
matrix.setRotate(180);
break;
case ExifInterface.ORIENTATION_FLIP_VERTICAL:
matrix.setRotate(180);
matrix.postScale(-1, 1);
break;
case ExifInterface.ORIENTATION_TRANSPOSE:
matrix.setRotate(90);
matrix.postScale(-1, 1);
break;
case ExifInterface.ORIENTATION_ROTATE_90:
matrix.setRotate(90);
break;
case ExifInterface.ORIENTATION_TRANSVERSE:
matrix.setRotate(-90);
matrix.postScale(-1, 1);
break;
case ExifInterface.ORIENTATION_ROTATE_270:
matrix.setRotate(-90);
break;
default:
return bm;
}
try {
Bitmap bmRotated = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true);
bm.recycle();
return bmRotated;
} catch (OutOfMemoryError e) {
e.printStackTrace();
return null;
}
}

You can achieve the effect that you want by breaking it into smaller steps.
Take the picture
Send the bitmap to Google Mobile Vision to detect the "landmarks" in the face and the probability that each eye is open
Paint the appropriate "eyes" onto your image
When using Google Mobile Vision's FaceDetector, you'll get back a SparseArray of Face objects (which may contain more than one face, or which may be empty). So you'll need to handle these cases. But you can loop through the SparseArray and find the Face object that you want to play with.
static Bitmap processFaces(Context context, Bitmap picture) {
// Create a "face detector" object, using the builder pattern
FaceDetector detector = new FaceDetector.Builder(context)
.setTrackingEnabled(false) // disable tracking to improve performance
.setClassificationType(FaceDetector.ALL_CLASSIFICATIONS)
.build();
// create a "Frame" object, again using a builder pattern (and passing in our picture)
Frame frame = new Frame.Builder().setBitmap(picture).build(); // build frame
// get a sparse array of face objects
SparseArray<Face> faces = detector.detect(frame); // detect the faces
// This example just deals with a single face for the sake of simplicity,
// but you can change this to deal with multiple faces.
if (faces.size() != 1) return picture;
// make a mutable copy of the background image that we can modify
Bitmap bmOverlay = Bitmap.createBitmap(picture.getWidth(), picture.getHeight(), picture.getConfig());
Canvas canvas = new Canvas(bmOverlay);
canvas.drawBitmap(picture, 0, 0, null);
// get the Face object that we want to manipulate, and process it
Face face = faces.valueAt(0);
processFace(face, canvas);
detector.release();
return bmOverlay;
}
Once you've got a Face object, you can find the features that interest you like this
private static void processFace(Face face, Canvas canvas) {
// The Face object can tell you the probability that each eye is open.
// I'm comparing this probability to an arbitrary threshold of 0.6 here,
// but you can vary it between 0 and 1 as you please.
boolean leftEyeClosed = face.getIsLeftEyeOpenProbability() < .6;
boolean rightEyeClosed = face.getIsRightEyeOpenProbability() < .6;
// Loop through the face's "landmarks" (eyes, nose, etc) to find the eyes.
// landmark.getPosition() gives you the (x,y) coordinates of each feature.
for (Landmark landmark : face.getLandmarks()) {
if (landmark.getType() == Landmark.LEFT_EYE)
overlayEyeBitmap(canvas, leftEyeClosed, landmark.getPosition().x, landmark.getPosition().y);
if (landmark.getType() == Landmark.RIGHT_EYE)
overlayEyeBitmap(canvas, rightEyeClosed, landmark.getPosition().x, landmark.getPosition().y);
}
}
Then you can add your paint!
private static void overlayEyeBitmap(Canvas canvas, boolean eyeClosed, float cx, float cy) {
float radius = 40;
// draw the eye's background circle with appropriate color
Paint paintFill = new Paint();
paintFill.setStyle(Paint.Style.FILL);
if (eyeClosed)
paintFill.setColor(Color.YELLOW);
else
paintFill.setColor(Color.WHITE);
canvas.drawCircle(cx, cy, radius, paintFill);
// draw a black border around the eye
Paint paintStroke = new Paint();
paintStroke.setColor(Color.BLACK);
paintStroke.setStyle(Paint.Style.STROKE);
paintStroke.setStrokeWidth(5);
canvas.drawCircle(cx, cy, radius, paintStroke);
if (eyeClosed)
// draw horizontal line across closed eye
canvas.drawLine(cx - radius, cy, cx + radius, cy, paintStroke);
else {
// draw big off-center pupil on open eye
paintFill.setColor(Color.BLACK);
float cxPupil = cx - 10;
float cyPupil = cy + 10;
canvas.drawCircle(cxPupil, cyPupil, 25, paintFill);
}
}
In the snippet above, I just hardcoded the eye radii, to show proof of concept. You'll probably want to do some more flexible scaling, using some percentage of face.getWidth() to determine the appropriate values. But here's what this image processing can do:
Some more details about the Mobile Vision API are here, and Udacity's current Advanced Android course has a nice walkthrough of this stuff (taking a picture, sending it to Mobile Vision, and adding a bitmap onto it). The course is free, or you can just look at what they did on Github.

Related

I am capturing an image using CameraX in android on button click, but in some phones the captured image is rotated. Why so and how to correct it?

I am capturing the image to transform it into a square of a certain size. When I capture an image using different phones, in some of them the image captured is the same as the image shown in the preview, but in some phones, the image captured is rotated.
Below is the code:
On button click:
bGetImage.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
final Bitmap[] bitmap = {null};
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
imageCapture.takePicture(getMainExecutor(),new ImageCapture.OnImageCapturedCallback() {
#Override
public void onCaptureSuccess(#NonNull ImageProxy image) {
super.onCaptureSuccess(image);
Log.d(TAG, "onCaptureSuccess: testview.innerRectangle " + testview.innerRectangle.width());
Log.d(TAG, "onCaptureSuccess: testview.innerRectangle " + testview.innerRectangle.height());
OutputTransform source = previewView.getOutputTransform();
// imageProxy is the output of the ImageCapture.
OutputTransform target = new ImageProxyTransformFactory().getOutputTransform(image);
//print size of target
Log.d(TAG, "onCaptureSuccess: target " + target.getMatrix().toString());
// Build the transform from ImageAnalysis to PreviewView
CoordinateTransform coordinateTransform = new CoordinateTransform(source, target);
// The variable box here is your bounding box in PreviewView
coordinateTransform.mapRect(testview.innerRectangle);
Log.d(TAG, "onCaptureSuccess: testview.innerRectangle " + testview.innerRectangle.width());
Log.d(TAG, "onCaptureSuccess: testview.innerRectangle " + testview.innerRectangle.height());
bitmap[0] = imageProxytoBitmap(image);
Log.d(TAG, "onCaptureSuccess: bitmap[0] " + bitmap[0].getWidth());
Log.d(TAG, "onCaptureSuccess: bitmap[0] " + bitmap[0].getHeight());
// bitmap[0] = Bitmap.createBitmap(bitmap[0],(int)testview.innerRectangle.left,(int)testview.innerRectangle.top,(int)testview.innerRectangle.width(),(int)testview.innerRectangle.height());
try {
Uri uri = getUri(bitmap[0], getContentResolver());
Log.d(TAG, "onCaptureSuccess: bitmap[0] " + bitmap[0].getWidth());
Log.d(TAG, "onCaptureSuccess: bitmap[0] " + bitmap[0].getHeight());
String path = uri.toString();
moveToScanActivity(path);
} catch (IOException e) {
Log.e(TAG, "onCaptureSuccess: ",e);
}
}
});
}
}
});
Camera Provider initialization:
cameraProviderFuture.addListener(() -> {
try {
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
CameraSelector selector = CameraSelector.DEFAULT_BACK_CAMERA;
cameraProvider.unbindAll();
// Camera camera = cameraProvider.bindToLifecycle(this, selector);
bindPreview(cameraProvider);
} catch (ExecutionException | InterruptedException e) {
// No errors need to be handled for this Future.
// This should never be reached.
}
}, ContextCompat.getMainExecutor(this));
Other functions I am using:
void bindPreview(#NonNull ProcessCameraProvider cameraProvider) {
Log.d(TAG, "bindPreview: width " + width);
Log.d(TAG, "bindPreview: height " + height);
Preview preview = new Preview.Builder()
.setTargetResolution(new Size(width, height))
.setTargetRotation(ROTATION_0)
.build();
previewView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(
View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
Size newViewFinderDimens = new Size(right - left, bottom - top);
updateTransform(0, MainActivity.bufferDimens, newViewFinderDimens);
}
});
imageCapture =
new ImageCapture.Builder()
.setTargetResolution(new Size(width, height))
.setTargetRotation(ROTATION_0)
.build();
CameraSelector cameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build();
preview.setSurfaceProvider(previewView.getSurfaceProvider());
UseCaseGroup useCaseGroup = new UseCaseGroup.Builder()
.addUseCase(preview)
.addUseCase(imageCapture)
.build();
cameraProvider.bindToLifecycle((LifecycleOwner)this, cameraSelector, useCaseGroup);
previewHeight = previewView.getHeight();
previewWidth = previewView.getWidth();
}
private Uri getUri(Bitmap bitmap, ContentResolver contentResolver) throws FileNotFoundException {
File file = new File(getApplicationContext().getExternalMediaDirs()[0].getAbsolutePath(),"Mytittle_" + System.currentTimeMillis() + ".png");
bitmap.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(file));
Log.d(TAG, "getUri: path " + file.getAbsolutePath());
return Uri.parse(file.getAbsolutePath());
}
private Bitmap rotateImage(Bitmap img, int degree)
{
Matrix matrix = new Matrix();
matrix.postRotate(degree);
return Bitmap.createBitmap(img, 0, 0, img.getWidth(), img.getHeight(), matrix, false);
}
public void updateTransform(int rotation, Size newBufferDimens, Size newViewFinderDimens) {
if (rotation == viewFinderRotation &&
newBufferDimens == bufferDimens &&
newViewFinderDimens == viewFinderDimens) {
// Nothing has changed, no need to transform output again
//Log.d("AK", "nothing has changed, no need to transform output again");
return;
}
if (newBufferDimens.getWidth() == 0 || newBufferDimens.getHeight() == 0) {
// Invalid buffer dimens - wait for valid inputs before setting matrix
//Log.d("AK", "returning from here newbufferdimens");
return;
} else {
// Update internal field with new inputs
bufferDimens = newBufferDimens;
}
if (newViewFinderDimens.getWidth() == 0 || newViewFinderDimens.getHeight() == 0) {
// Invalid view finder dimens - wait for valid inputs before setting matrix
//Log.d("AK", "returning from here newViewfinder");
return;
} else {
// Update internal field with new inputs
viewFinderDimens = newViewFinderDimens;
}
Matrix matrix = new Matrix();
Log.d(TAG, "Applying output transformation.\n" +
"View finder size: " + viewFinderDimens + "\n" +
"Preview output size: " + bufferDimens + "\n" +
"View finder rotation: " + viewFinderRotation + "\n" +
"Preview output rotation: " + 0);
float centerX = previewView.getWidth() / 2f;
float centerY = previewView.getHeight() / 2f;
// Correct preview output to account for display rotation
float rotationDegrees;
switch (previewView.getDisplay().getRotation()) {
case Surface.ROTATION_0:
rotationDegrees = 0f;
break;
case Surface.ROTATION_90:
rotationDegrees = 90f;
break;
case Surface.ROTATION_180:
rotationDegrees = 180f;
break;
case Surface.ROTATION_270:
rotationDegrees = 270f;
break;
default:
return;
}
matrix.postRotate(-rotationDegrees, centerX, centerY);
float bufferRatio = bufferDimens.getHeight() / (float) bufferDimens.getWidth();
int scaledWidth;
int scaledHeight;
if (previewView.getWidth() > previewView.getHeight()) {
scaledHeight = viewFinderDimens.getWidth();
scaledWidth = round(viewFinderDimens.getWidth() * bufferRatio);
} else {
scaledHeight = viewFinderDimens.getHeight();
scaledWidth = round(viewFinderDimens.getHeight() * bufferRatio);
}
Canvas canvas = new Canvas();
float xScale = scaledWidth / (float) viewFinderDimens.getWidth();
float yScale = scaledHeight / (float) viewFinderDimens.getHeight();
//Log.d("AK", "scale x y" + xScale + " " + yScale);
matrix.preScale(xScale, yScale, centerX, centerY);
}
private Bitmap imageProxytoBitmap(ImageProxy image) {
ByteBuffer buffer =image.getPlanes()[0].getBuffer();
buffer.rewind();
byte[] bytes = new byte[buffer.capacity()];
buffer.get(bytes);
byte[] cloneBytes = bytes.clone();
return BitmapFactory.decodeByteArray(cloneBytes, 0, cloneBytes.length);
}
I want the captured image to exactly same as the preview (unrotated image)
If you open the rotated image in the Google Photo app, are they still rotated? If not, then it means that you are not using the EXIF info.
Basically when a photo is captured, it may or may not be rotated depending on the OEM. When it's not rotated, CameraX saves the rotation info in the TAG_ORIENTATION tag. You will need to read that info and apply it on the Bitmap yourself. Something like:
// Read the exif rotation value from the JPEG byte buffer
ExifInterface exifInterface = new ExifInterface(jpegFilePath);
int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
// Decode the JPEG byte buffer into a bitmap
Bitmap bitmap = BitmapFactory.decodeFile(jpegFilePath);
// Rotate the bitmap based on the exif rotation value
Matrix matrix = new Matrix();
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
matrix.postRotate(90);
break;
case ExifInterface.ORIENTATION_ROTATE_180:
matrix.postRotate(180);
break;
case ExifInterface.ORIENTATION_ROTATE_270:
matrix.postRotate(270);
break;
}
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
Code not tested. You might need to swap width/height during rotation.

Android: Samsung devices rotates the image automatically

I have created a custom camera. When I click on the capture button in the application, image has been taken. Moreover, I am getting the data in the form of byte array in the function named as onPictureTaken.
I am converting the byte array into the bitmap using the library known as Glide.
My problem is that in Samsung device the images rotates itself. I have been researching on it for quite a while. I found the library called as metadata extraction library to get the Exif information from byte[] and rotate the image on it but it is not working on the Samsung devices. The metadata extraction library every time returns a value of 1 for portrait image which shows that image does not need rotation however, the image taken in portrait mode is always 90 degree rotated.
Whenever, the photo is taken in portrait mode it is rotated at an angle of 90 degrees for both front and back camera and meta extraction library shows a value of 1.
Is there something other then metadata extraction extraction library which extract Exif information stream data?
Note: I cannot use ExifInterface because it requires the minimum Api level of 24 whereas, I am testing on API level 22
I have tried many solution but nothing is working. Is there any solution for this?
The code is given below:
public void onPictureTaken(byte[] data, Camera camera) {
mCamera.stopPreview();
Glide.with(this).load(data)
.asBitmap().centerCrop().animate(R.anim.abc_fade_in)
.into(new SimpleTarget<Bitmap>(width, height) {
#Override
public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
camera_view.setVisibility(View.INVISIBLE);
int w = resource.getWidth();
int h = resource.getHeight();
// Setting post rotate to 90
Matrix mtx = new Matrix();
try {
InputStream is = new ByteArrayInputStream(data);
Metadata metadata = ImageMetadataReader.readMetadata(is);
final ExifIFD0Directory exifIFD0Directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
if (exifIFD0Directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) {
final int exifOrientation = exifIFD0Directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
switch (exifOrientation) {
case 6:
mtx.postRotate(90);
break; // top left
case 3:
mtx.postRotate(180);;
break; // top right
case 8:
mtx.postRotate(270);
break; // bottom right
}
photo = Bitmap.createBitmap(resource, 0, 0, w, h, mtx, true);
/* Work on exifOrientation */
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
I am using Samsung J5 for the testing.
You don't need a library for this. Here is a couple methods that I wrote that should do the trick for you.
public static int getCapturedImageOrientation(Context context, Uri imageUri){
int rotate = 0;
try {
context.getContentResolver().notifyChange(imageUri, null);
File imageFile = new File(imageUri.getPath());
ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_270:
rotate = 270;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
rotate = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_90:
rotate = 90;
break;
}
Log.i("RotateImage", "Exif orientation: " + orientation);
Log.i("RotateImage", "Rotate value: " + rotate);
} catch (Exception e) {
Log.e(TAG, "Error getting rotation of image");
}
return rotate;
}
public static int GetRotateAngle(Context context, Uri imageUri) {
String[] columns = { MediaStore.Images.Media.DATA, MediaStore.Images.Media.ORIENTATION };
Cursor cursor = context.getContentResolver().query(imageUri, columns, null, null, null);
if (cursor == null) {
//If null, it is not in the gallery, so may be temporary image
return getCapturedImageOrientation(context, imageUri);
}
cursor.moveToFirst();
int orientationColumnIndex = cursor.getColumnIndex(columns[1]);
int orientation = cursor.getInt(orientationColumnIndex);
cursor.close();
return orientation;
}
I wrap these in a class called ImageHelper. You can use it like this:
rotateImage(ImageHelper.GetRotateAngle(Context, mCropImageUri));
Then of course the rotateImage code would be:
private void rotateImage(int degrees) {
Matrix mat = new Matrix();
mat.postRotate(degrees);
mCropImage = Bitmap.createBitmap(mCropImage, 0, 0, mCropImage.getWidth(), mCropImage.getHeight(), mat, true);
setImageForCropping(mCropImage);
}
Of course i was doing all this for a photo editing, cropping and scaling app, so you can ignore some of the extras, but this should take care of ya. Goodluck.
Almost in all Samsung Devices the image rotation Issue is common ,in my case i am using Samsung Note 3 and this same issue occurs but i am using below code to solve this issue
public static Bitmap decodeFile(String path) { // this method is for avoiding the image rotation
int orientation;
try {
if (path == null) {
return null;
}
// decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
// Find the correct scale value. It should be the power of 2.
final int REQUIRED_SIZE = 70;
int width_tmp = o.outWidth, height_tmp = o.outHeight;
int scale = 4;
while (true) {
if (width_tmp / 2 < REQUIRED_SIZE || height_tmp / 2 < REQUIRED_SIZE)
break;
width_tmp /= 2;
height_tmp /= 2;
scale++;
}
// decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
Bitmap bm = BitmapFactory.decodeFile(path, o2);
Bitmap bitmap = bm;
ExifInterface exif = new ExifInterface(path);
orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
Log.e("orientation", "" + orientation);
Matrix m = new Matrix();
if ((orientation == 3)) {
m.postRotate(180);
m.postScale((float) bm.getWidth(), (float) bm.getHeight());
// if(m.preRotate(90)){
Log.e("in orientation", "" + orientation);
bitmap = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), m, true);
return bitmap;
} else if (orientation == 6) {
m.postRotate(90);
Log.e("in orientation", "" + orientation);
bitmap = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), m, true);
return bitmap;
} else if (orientation == 8) {
m.postRotate(270);
Log.e("in orientation", "" + orientation);
bitmap = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), m, true);
return bitmap;
}
return bitmap;
} catch (Exception e) {
}
return null;
}
This code is work for me so i hope this will helpful for you
This can be easily fixed by using ExifInterface provided by Google.
You can add it to your project as follows:
implementation "androidx.exifinterface:exifinterface:1.1.0"
After this, get the rotation from your image and apply it to your ImageView:
// uri of the image
val inputStream = contentResolver.openInputStream(Uri.parse(uri))
val exifInterface = ExifInterface(requireNotNull(inputStream))
var rotation = 0
when (exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)) {
ExifInterface.ORIENTATION_ROTATE_90 -> rotation = 90
ExifInterface.ORIENTATION_ROTATE_180 -> rotation = 180
ExifInterface.ORIENTATION_ROTATE_270 -> rotation = 270
}

camerasource.takePicture() save rotated images in some device

I am using vision api for tracking face. I applied a mask on the basis of face position.When i take a picture from front camera i call camerasource.takePicture() to save images.I am facing issue of image rotation in some device like samsung and capture image shows mask and face in different different position.I use Exif class to get orientation of images but it always return 0 so i am unable to rotate the image.
I am using following class to getOrientation and rotate image.
public class ExifUtils {
public Bitmap rotateBitmap(String src, Bitmap bitmap) {
try {
int orientation = getExifOrientation(src);
if (orientation == 1) {
return bitmap;
}
Matrix matrix = new Matrix();
switch (orientation) {
case 2:
matrix.setScale(-1, 1);
break;
case 3:
matrix.setRotate(180);
break;
case 4:
matrix.setRotate(180);
matrix.postScale(-1, 1);
break;
case 5:
matrix.setRotate(90);
matrix.postScale(-1, 1);
break;
case 6:
matrix.setRotate(90);
break;
case 7:
matrix.setRotate(-90);
matrix.postScale(-1, 1);
break;
case 8:
matrix.setRotate(-90);
break;
default:
return bitmap;
}
try {
Bitmap oriented = Bitmap.createBitmap(bitmap, 0, 0,
bitmap.getWidth(), bitmap.getHeight(), matrix, true);
bitmap.recycle();
return oriented;
} catch (OutOfMemoryError e) {
e.printStackTrace();
return bitmap;
}
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
private int getExifOrientation(String src) throws IOException {
int orientation = 1;
try {
if (Build.VERSION.SDK_INT >= 5) {
Class<?> exifClass = Class
.forName("android.media.ExifInterface");
Constructor<?> exifConstructor = exifClass
.getConstructor(new Class[]{String.class});
Object exifInstance = exifConstructor
.newInstance(new Object[]{src});
Method getAttributeInt = exifClass.getMethod("getAttributeInt",
new Class[]{String.class, int.class});
Field tagOrientationField = exifClass
.getField("TAG_ORIENTATION");
String tagOrientation = (String) tagOrientationField.get(null);
orientation = (Integer) getAttributeInt.invoke(exifInstance,
new Object[]{tagOrientation, 1});
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (Fragment.InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (java.lang.InstantiationException e) {
e.printStackTrace();
}
return orientation;
}
}
I found this issue in vision api is there any solution.
I solve my problem myself. I get orientation from byte data then rotate my images according to orientation.
private CameraSource.PictureCallback mPicture = new CameraSource.PictureCallback() {
#Override
public void onPictureTaken(byte[] bytes) {
int orientation = Exif.getOrientation(bytes);
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
switch(orientation) {
case 90:
bitmapPicture= rotateImage(bitmap, 90);
break;
case 180:
bitmapPicture= rotateImage(bitmap, 180);
break;
case 270:
bitmapPicture= rotateImage(bitmap, 270);
break;
case 0:
// if orientation is zero we don't need to rotate this
default:
break;
}
//write your code here to save bitmap
}
}
};
public static Bitmap rotateImage(Bitmap source, float angle) {
Matrix matrix = new Matrix();
matrix.postRotate(angle);
return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix,
true);
}
Below class is used to get orientation from byte[] data.
public class Exif {
private static final String TAG = "CameraExif";
// Returns the degrees in clockwise. Values are 0, 90, 180, or 270.
public static int getOrientation(byte[] jpeg) {
if (jpeg == null) {
return 0;
}
int offset = 0;
int length = 0;
// ISO/IEC 10918-1:1993(E)
while (offset + 3 < jpeg.length && (jpeg[offset++] & 0xFF) == 0xFF) {
int marker = jpeg[offset] & 0xFF;
// Check if the marker is a padding.
if (marker == 0xFF) {
continue;
}
offset++;
// Check if the marker is SOI or TEM.
if (marker == 0xD8 || marker == 0x01) {
continue;
}
// Check if the marker is EOI or SOS.
if (marker == 0xD9 || marker == 0xDA) {
break;
}
// Get the length and check if it is reasonable.
length = pack(jpeg, offset, 2, false);
if (length < 2 || offset + length > jpeg.length) {
Log.e(TAG, "Invalid length");
return 0;
}
// Break if the marker is EXIF in APP1.
if (marker == 0xE1 && length >= 8 &&
pack(jpeg, offset + 2, 4, false) == 0x45786966 &&
pack(jpeg, offset + 6, 2, false) == 0) {
offset += 8;
length -= 8;
break;
}
// Skip other markers.
offset += length;
length = 0;
}
// JEITA CP-3451 Exif Version 2.2
if (length > 8) {
// Identify the byte order.
int tag = pack(jpeg, offset, 4, false);
if (tag != 0x49492A00 && tag != 0x4D4D002A) {
Log.e(TAG, "Invalid byte order");
return 0;
}
boolean littleEndian = (tag == 0x49492A00);
// Get the offset and check if it is reasonable.
int count = pack(jpeg, offset + 4, 4, littleEndian) + 2;
if (count < 10 || count > length) {
Log.e(TAG, "Invalid offset");
return 0;
}
offset += count;
length -= count;
// Get the count and go through all the elements.
count = pack(jpeg, offset - 2, 2, littleEndian);
while (count-- > 0 && length >= 12) {
// Get the tag and check if it is orientation.
tag = pack(jpeg, offset, 2, littleEndian);
if (tag == 0x0112) {
// We do not really care about type and count, do we?
int orientation = pack(jpeg, offset + 8, 2, littleEndian);
switch (orientation) {
case 1:
return 0;
case 3:
return 180;
case 6:
return 90;
case 8:
return 270;
}
Log.i(TAG, "Unsupported orientation");
return 0;
}
offset += 12;
length -= 12;
}
}
Log.i(TAG, "Orientation not found");
return 0;
}
private static int pack(byte[] bytes, int offset, int length,
boolean littleEndian) {
int step = 1;
if (littleEndian) {
offset += length - 1;
step = -1;
}
int value = 0;
while (length-- > 0) {
value = (value << 8) | (bytes[offset] & 0xFF);
offset += step;
}
return value;
}
}
I have encountered similar problem with Samsung devices, ExifInterface doesn't seem to work correctly with images saved by them. In order to solve the problem I used code from Glide image library, it seems to handle checking original image rotation correctly.
Check out this link: Glide source
getOrientation method from there seems to do the job most of the time.
Sounds like an issue with Exif tags to me. Basically, modern cameras save images in the same orientation, but also save a tag that tells you, what the original orientation was.
You could use Exif Interface, which comes bundled with the java api. I prefer Alessandro Crugnola's Android-Exif-Interface library, which doesn't require you to keep filepaths around
How I used Android-Exif-Interface in my project:
ExifInterface exif = new ExifInterface();
Matrix matrix = new Matrix();
try {
exif.readExif(context.getContentResolver().openInputStream(fileUri), ExifInterface.Options.OPTION_ALL);
ExifTag tag = exif.getTag(ExifInterface.TAG_ORIENTATION);
int orientation = tag.getValueAsInt(1);
switch (orientation) {
case 3: /* 180° */
matrix.postRotate(180);
break;
case 6: /* 90° */
matrix.postRotate(90);
break;
case 8: /* 270° */
matrix.postRotate(-90);
break;
}
} catch (IOException e) {
Log.i("INFO","expected behaviour: IOException");
//not every picture comes from the phone, should that be the case,
// we can't get exif tags anyway, since those aren't being transmitted
// via http (atleast I think so. I'd need to save the picture on the SD card to
// confirm that and I don't want to do that)
} catch(NullPointerException e){
Log.i("INFO","expected behaviour: NullPointerException");
//same as above, not every picture comes from the phone
}
In many cases, the pictureCallback() receives a Jpeg with undefined orientation tag. But you can calculate the actual device orientation either by looking at the display rotation, or running an orientation listener, as for Camera.takePicture returns a rotated byteArray.

Android - Template Matching

I want to create an android application. Program steps are below
Open camera
Get frames
Select a frame by touch screen
Load template image under drawable folder
Apply template matching
Show result
The mat object of template image is not empty. I check it. When I run this code, I get below error message.
Code :
public void onCameraViewStarted(int width, int height) {
mRgba = new Mat(height, width, CvType.CV_8UC4);
temp = new Mat(height, width, CvType.CV_8UC4);
}
public boolean onTouch(View v, MotionEvent event) {
int cols = mRgba.cols();
int rows = mRgba.rows();
int xOffset = (mOpenCvCameraView.getWidth() - cols) / 2;
int yOffset = (mOpenCvCameraView.getHeight() - rows) / 2;
int x = (int)event.getX() - xOffset;
int y = (int)event.getY() - yOffset;
Log.i(TAG, "Touch image coordinates: (" + x + ", " + y + ")");
if ((x < 0) || (y < 0) || (x > cols) || (y > rows)) return false;
mIsColorSelected = true;
return true; // don't need subsequent touch events
}
private static Mat readInputStreamIntoMat(InputStream inputStream) throws IOException {
// Read into byte-array
byte[] temporaryImageInMemory = readStream(inputStream);
// Decode into mat. Use any IMREAD_ option that describes your image appropriately
Mat outputImage = Highgui.imdecode(new MatOfByte(temporaryImageInMemory), Highgui.IMREAD_GRAYSCALE);
return outputImage;
}
private static byte[] readStream(InputStream stream) throws IOException {
// Copy content of the image to byte-array
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[16384];
while ((nRead = stream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
byte[] temporaryImageInMemory = buffer.toByteArray();
buffer.close();
stream.close();
return temporaryImageInMemory;
}
public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
if(mIsColorSelected) {
InputStream inpT = getResources().openRawResource(R.drawable.imgt);
Mat mTemp;
try {
mRgba.copyTo(temp);
mTemp = readInputStreamIntoMat(inpT);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// / Create the result matrix
int result_cols = temp.cols() - mTemp.cols() + 1;
int result_rows = temp.rows() - mTemp.rows() + 1;
Mat result = new Mat(result_rows, result_cols, CvType.CV_32FC1);
int match_method = 4;
// / Do the Matching and Normalize
Imgproc.matchTemplate(temp, mTemp, result, match_method);
Core.normalize(result, result, 0, 1, Core.NORM_MINMAX, -1, new Mat());
/*
Localizing the best match with minMaxLoc
MinMaxLocResult mmr = Core.minMaxLoc(result);
Point matchLoc;
if (match_method == Imgproc.TM_SQDIFF || match_method == Imgproc.TM_SQDIFF_NORMED) {
matchLoc = mmr.minLoc;
} else {
matchLoc = mmr.maxLoc;
}/*
// / Show me what you got
Core.rectangle(temp, matchLoc, new Point(matchLoc.x + mTemp.cols(),
matchLoc.y + mTemp.rows()), new Scalar(0, 255, 0));*/
return temp;
}
else {
mRgba = inputFrame.rgba();
}
return mRgba;
}
For template matching , both source image and template image must be of same data type(1). Here your template image(mTemp) is a gray scale image and source image( mRgba / temp ) is a color image with alpha channel.
So, lets change both source and template images to be gray scale images
temp = new Mat(height, width, CvType.CV_8UC1);
and replace mRgba.copyTo(temp) with
Imgproc.cvtColor(mRgba, temp, Imgproc.COLOR_RGBA2GRAY);

Android Picasso auto rotates image

I am using Picasso to load images from the web in my application. I have noticed that some images are shown rotated by 90degrees although when I open the image in my browser I see it correctly positioned. I assume that these images have EXIF data. Is there any way to instruct Picasso to ignore EXIF?
As we know, Picasso supports EXIF from local storage, this is done via Android inner Utils. Providing the same functionality can't be done easy due to ability to use custom Http loading libraries.
My solution is simple: we must override caching and apply Exif rotation before item is cached.
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(chain -> {
Response originalResponse = chain.proceed(chain.request());
byte[] body = originalResponse.body().bytes();
ResponseBody newBody = ResponseBody
.create(originalResponse.body().contentType(), ImageUtils.processImage(body));
return originalResponse.newBuilder().body(newBody).build();
})
.cache(cache)
.build();
Here we add NetworkInterceptor that can transform request and response before it gets cached.
public class ImageUtils {
public static byte[] processImage(byte[] originalImg) {
int orientation = Exif.getOrientation(originalImg);
if (orientation != 0) {
Bitmap bmp = BitmapFactory.decodeByteArray(originalImg, 0, originalImg.length);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
rotateImage(orientation, bmp).compress(Bitmap.CompressFormat.PNG, 100, stream);
return stream.toByteArray();
}
return originalImg;
}
private static Bitmap rotateImage(int angle, Bitmap bitmapSrc) {
Matrix matrix = new Matrix();
matrix.postRotate(angle);
return Bitmap.createBitmap(bitmapSrc, 0, 0,
bitmapSrc.getWidth(), bitmapSrc.getHeight(), matrix, true);
}
}
Exif transformation:
public class Exif {
private static final String TAG = "Exif";
// Returns the degrees in clockwise. Values are 0, 90, 180, or 270.
public static int getOrientation(byte[] jpeg) {
if (jpeg == null) {
return 0;
}
int offset = 0;
int length = 0;
// ISO/IEC 10918-1:1993(E)
while (offset + 3 < jpeg.length && (jpeg[offset++] & 0xFF) == 0xFF) {
int marker = jpeg[offset] & 0xFF;
// Check if the marker is a padding.
if (marker == 0xFF) {
continue;
}
offset++;
// Check if the marker is SOI or TEM.
if (marker == 0xD8 || marker == 0x01) {
continue;
}
// Check if the marker is EOI or SOS.
if (marker == 0xD9 || marker == 0xDA) {
break;
}
// Get the length and check if it is reasonable.
length = pack(jpeg, offset, 2, false);
if (length < 2 || offset + length > jpeg.length) {
Log.e(TAG, "Invalid length");
return 0;
}
// Break if the marker is EXIF in APP1.
if (marker == 0xE1 && length >= 8 &&
pack(jpeg, offset + 2, 4, false) == 0x45786966 &&
pack(jpeg, offset + 6, 2, false) == 0) {
offset += 8;
length -= 8;
break;
}
// Skip other markers.
offset += length;
length = 0;
}
// JEITA CP-3451 Exif Version 2.2
if (length > 8) {
// Identify the byte order.
int tag = pack(jpeg, offset, 4, false);
if (tag != 0x49492A00 && tag != 0x4D4D002A) {
Log.e(TAG, "Invalid byte order");
return 0;
}
boolean littleEndian = (tag == 0x49492A00);
// Get the offset and check if it is reasonable.
int count = pack(jpeg, offset + 4, 4, littleEndian) + 2;
if (count < 10 || count > length) {
Log.e(TAG, "Invalid offset");
return 0;
}
offset += count;
length -= count;
// Get the count and go through all the elements.
count = pack(jpeg, offset - 2, 2, littleEndian);
while (count-- > 0 && length >= 12) {
// Get the tag and check if it is orientation.
tag = pack(jpeg, offset, 2, littleEndian);
if (tag == 0x0112) {
// We do not really care about type and count, do we?
int orientation = pack(jpeg, offset + 8, 2, littleEndian);
switch (orientation) {
case 1:
return 0;
case 3:
return 180;
case 6:
return 90;
case 8:
return 270;
}
Log.i(TAG, "Unsupported orientation");
return 0;
}
offset += 12;
length -= 12;
}
}
Log.i(TAG, "Orientation not found");
return 0;
}
private static int pack(byte[] bytes, int offset, int length,
boolean littleEndian) {
int step = 1;
if (littleEndian) {
offset += length - 1;
step = -1;
}
int value = 0;
while (length-- > 0) {
value = (value << 8) | (bytes[offset] & 0xFF);
offset += step;
}
return value;
}
}
This solution is experimental and must be tested for leaks and probably improved. In most cases Samsung and iOs devices return 90 DEG rotation and this solution works. Other cases also must be tested.
Can you post the image you're using?
because as this thread said, exif orientation for images loaded from web is ignored(only content provider and local files).
I also try to display this image in picasso 2.5.2, the real orientation of the image is facing rightside(the bottom code in image is facing right). The exif orientation, is 90deg clockwise. Try open it in chrome(chrome is honoring exif rotation), the image will be faced down(bottom code in image is facing down).
based on #ph0en1x response this version use google exif library and kotlin: add this interceptor to okhttpclient used by picasso
addNetworkInterceptor {
val response = it.proceed(it.request())
val body = response.body
if (body?.contentType()?.type == "image") {
val bytes = body.bytes()
val degrees = bytes.inputStream().use { input ->
when (ExifInterface(input).getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)) {
ExifInterface.ORIENTATION_ROTATE_270 -> 270
ExifInterface.ORIENTATION_ROTATE_180 -> 180
ExifInterface.ORIENTATION_ROTATE_90 -> 90
else -> 0
}
}
if (degrees != 0) {
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
ByteArrayOutputStream().use { output ->
Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, Matrix().apply { postRotate(degrees.toFloat()) }, true)
.compress(Bitmap.CompressFormat.PNG, 100, output)
response.newBuilder().body(output.toByteArray().toResponseBody(body.contentType())).build()
}
} else
response.newBuilder().body(bytes.toResponseBody(body.contentType())).build()
} else
response
}

Categories

Resources