Skip to content
This repository has been archived by the owner on Dec 29, 2022. It is now read-only.

Add image orientation option #45

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.google.android.cameraview.demo;

/**
*
* Compress image async task callback
*/
public interface CompressImageCallback {
void onImageCompressed(byte[] image);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@

package com.google.android.cameraview.demo;

import com.google.android.cameraview.CameraView;

import android.Manifest;
import android.app.Dialog;
import android.content.DialogInterface;
Expand All @@ -43,6 +41,8 @@
import android.view.View;
import android.widget.Toast;

import com.google.android.cameraview.CameraView;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
Expand Down Expand Up @@ -195,6 +195,12 @@ public boolean onOptionsItemSelected(MenuItem item) {
CameraView.FACING_BACK : CameraView.FACING_FRONT);
}
break;
case R.id.switch_with_angle:
if (mCameraView != null) {
mCameraView.setComputeImageAngle(!mCameraView.isComputeImageAngle());
Toast.makeText(this, String.format(getString(R.string.rotation_option_click), mCameraView.isComputeImageAngle()), Toast.LENGTH_SHORT).show();
}
break;
}
return false;
}
Expand Down Expand Up @@ -253,6 +259,43 @@ public void run() {
});
}

@Override
public void onPictureTaken(CameraView cameraView, final byte[] data, int angle) {
Log.d(TAG, "onPictureTaken " + data.length + " angle " + angle);
Toast.makeText(cameraView.getContext(), String.format(getString(R.string.picture_taken_rotating), String.valueOf(angle)), Toast.LENGTH_SHORT)
.show();

new RotateImageTask(new CompressImageCallback() {
@Override
public void onImageCompressed(final byte[] image) {
getBackgroundHandler().post(new Runnable() {
@Override
public void run() {
// This demo app saves the taken picture to a constant file.
// $ adb pull /sdcard/Android/data/com.google.android.cameraview.demo/files/Pictures/picture.jpg
File file = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES),
"picture.jpg");
OutputStream os = null;
try {
os = new FileOutputStream(file);
os.write(image);
os.close();
} catch (IOException e) {
Log.w(TAG, "Cannot write to " + file, e);
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
// Ignore
}
}
}
}
});
}
}, angle).execute(data);
}
};

public static class ConfirmationDialogFragment extends DialogFragment {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.google.android.cameraview.demo;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.os.AsyncTask;

import java.io.ByteArrayOutputStream;

/**
* Created by damian on 09/11/2016.
*
* Compressing and rotating byte array
*/
class RotateImageTask extends AsyncTask<byte[], Void, byte[]> {

private CompressImageCallback mListener;
private int angle;

public RotateImageTask(CompressImageCallback mListener, int angle) {
this.mListener = mListener;
this.angle = angle;
}

@Override
protected byte[] doInBackground(byte[]... params) {
byte[] data = params[0];
Bitmap image = BitmapFactory.decodeByteArray(data, 0, data.length);
Bitmap bmp = rotateBitmap(image, angle);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bmp.compress(Bitmap.CompressFormat.PNG, 100, stream);
return stream.toByteArray();
}

@Override
protected void onPostExecute(byte[] bitmap) {
super.onPostExecute(bitmap);
mListener.onImageCompressed(bitmap);
}

private Bitmap rotateBitmap(Bitmap source, int angle) {
Matrix matrix = new Matrix();
matrix.postRotate(angle);
return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);
}

}
9 changes: 9 additions & 0 deletions demo/src/main/res/drawable/ic_rotate.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFF"
android:pathData="M7.11,8.53L5.7,7.11C4.8,8.27 4.24,9.61 4.07,11h2.02c0.14,-0.87 0.49,-1.72 1.02,-2.47zM6.09,13L4.07,13c0.17,1.39 0.72,2.73 1.62,3.89l1.41,-1.42c-0.52,-0.75 -0.87,-1.59 -1.01,-2.47zM7.1,18.32c1.16,0.9 2.51,1.44 3.9,1.61L11,17.9c-0.87,-0.15 -1.71,-0.49 -2.46,-1.03L7.1,18.32zM13,4.07L13,1L8.45,5.55 13,10L13,6.09c2.84,0.48 5,2.94 5,5.91s-2.16,5.43 -5,5.91v2.02c3.95,-0.49 7,-3.85 7,-7.93s-3.05,-7.44 -7,-7.93z"/>
</vector>
5 changes: 5 additions & 0 deletions demo/src/main/res/menu/main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,9 @@
android:title="@string/switch_camera"
app:showAsAction="ifRoom"/>

<item
android:id="@+id/switch_with_angle"
android:icon="@drawable/ic_rotate"
android:title="@string/rotation_on"
app:showAsAction="ifRoom"/>
</menu>
3 changes: 3 additions & 0 deletions demo/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@
<string name="camera_permission_confirmation">This app demonstrates the usage of CameraView. In order to do that, it needs permission to access camera.</string>
<string name="camera_permission_not_granted">Camera app cannot do anything without camera permission.</string>
<string name="picture_taken">Picture taken</string>
<string name="picture_taken_rotating">Picture taken, angle %1$s, rotating</string>
<string name="switch_flash">Switch flash</string>
<string name="switch_camera">Switch camera</string>
<string name="flash_auto">Flash auto</string>
<string name="flash_off">Flash off</string>
<string name="flash_on">Flash on</string>
<string name="rotation_on">Take photo with rotation</string>
<string name="rotation_option_click">"Take with rotation %1$b"</string>
</resources>
127 changes: 127 additions & 0 deletions library/src/main/base/com/google/android/cameraview/ExifUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package com.google.android.cameraview;

import android.util.Log;

/**
* byte array orientation util
*/
public abstract class ExifUtil {
private static final String TAG = "CameraExif";

/**
* Computing image orientation
*
* @param jpeg byte array from camera
* @return the degrees in clockwise. Values are 0, 90, 180, or 270. Returns 0 if undefined or rotation not needed
*/
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;
}
return 0;
}
offset += 12;
length -= 12;
}
}

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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ public class CameraView extends FrameLayout {

private final DisplayOrientationDetector mDisplayOrientationDetector;

private boolean mComputeImageAngle;

public CameraView(Context context) {
this(context, null);
}
Expand Down Expand Up @@ -201,6 +203,7 @@ protected Parcelable onSaveInstanceState() {
state.ratio = getAspectRatio();
state.autoFocus = getAutoFocus();
state.flash = getFlash();
state.computeAngle = isComputeImageAngle();
return state;
}

Expand All @@ -216,6 +219,7 @@ protected void onRestoreInstanceState(Parcelable state) {
setAspectRatio(ss.ratio);
setAutoFocus(ss.autoFocus);
setFlash(ss.flash);
setComputeImageAngle(ss.computeAngle);
}

/**
Expand Down Expand Up @@ -370,6 +374,24 @@ public int getFlash() {
return mImpl.getFlash();
}

/**
* Gets the current angle computing option
*
* @return current angle computing state
*/
public boolean isComputeImageAngle() {
return mComputeImageAngle;
}

/**
* Sets the current angle computing option
*
* @param computeImageAngle angle computing options
*/
public void setComputeImageAngle(boolean computeImageAngle) {
this.mComputeImageAngle = computeImageAngle;
}

/**
* Take a picture. The result will be returned to
* {@link Callback#onPictureTaken(CameraView, byte[])}.
Expand Down Expand Up @@ -416,7 +438,11 @@ public void onCameraClosed() {
@Override
public void onPictureTaken(byte[] data) {
for (Callback callback : mCallbacks) {
callback.onPictureTaken(CameraView.this, data);
if (isComputeImageAngle()) {
callback.onPictureTaken(CameraView.this, data, ExifUtil.getOrientation(data));
} else {
callback.onPictureTaken(CameraView.this, data);
}
}
}

Expand All @@ -437,13 +463,16 @@ protected static class SavedState extends BaseSavedState {
@Flash
int flash;

boolean computeAngle;

@SuppressWarnings("WrongConstant")
public SavedState(Parcel source, ClassLoader loader) {
super(source);
facing = source.readInt();
ratio = source.readParcelable(loader);
autoFocus = source.readByte() != 0;
flash = source.readInt();
computeAngle = source.readByte() != 0;
}

public SavedState(Parcelable superState) {
Expand All @@ -457,6 +486,7 @@ public void writeToParcel(Parcel out, int flags) {
out.writeParcelable(ratio, 0);
out.writeByte((byte) (autoFocus ? 1 : 0));
out.writeInt(flash);
out.writeByte((byte) (autoFocus ? 1 : 0));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be
out.writeByte((byte) (computeAngle ? 1 : 0));
?

}

public static final Parcelable.Creator<SavedState> CREATOR
Expand Down Expand Up @@ -506,6 +536,15 @@ public void onCameraClosed(CameraView cameraView) {
*/
public void onPictureTaken(CameraView cameraView, byte[] data) {
}

/**
* Called when a picture is taken and image angle is needed
*
* @param cameraView The associated {@link CameraView}.
* @param data JPEG data.
*/
public void onPictureTaken(CameraView cameraView, byte[] data, int angle) {
}
}

}