/**
* Copyright (c) 2015 CommonsWare, LLC
* <p/>
* 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
* <p/>
* 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.commonsware.cwac.cam2;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import com.android.mms.exif.ExifInterface;
import com.android.mms.exif.ExifTag;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
* Represents a picture taken by the camera, to be passed through
* the ImageProcessor chain.
*
* The ImageContext should always hold the byte[] representing
* the JPEG image. If an ImageProcessor needs a Bitmap, it can
* call getBitmap(true) to force creation of a Bitmap for those
* JPEG bytes, but this is memory-intensive and should be avoided
* where possible.
*/
public class ImageContext {
private static final double LOG_2=Math.log(2.0d);
private Context ctxt;
private byte[] jpegOriginal;
private Bitmap bmp;
private Bitmap thumbnail;
private ExifInterface exif;
ImageContext(Context ctxt, byte[] jpeg) {
this.ctxt=ctxt.getApplicationContext();
setJpeg(jpeg);
}
/**
* @return an Android Context suitable for use in cases where
* you need filesystem paths and the like
*/
public Context getContext() {
return(ctxt);
}
/**
* @return the byte[] of JPEG-encoded data for the picture
*/
public byte[] getJpeg() {
return(jpegOriginal);
}
/**
* Updates the JPEG data, invalidating any previous Bitmap.
*
* @param jpeg the new JPEG data
*/
public void setJpeg(byte[] jpeg) {
this.jpegOriginal=jpeg;
this.bmp=null;
this.thumbnail=null;
}
public ExifInterface getExifInterface() throws IOException {
if (exif==null) {
exif=new ExifInterface();
exif.readExif(jpegOriginal);
}
return(exif);
}
public int getOrientation() throws IOException {
ExifTag tag=getExifInterface().getTag(ExifInterface.TAG_ORIENTATION);
return(tag==null ? -1 : tag.getValueAsInt(-1));
}
public byte[] getJpeg(boolean normalizeOrientation) {
if (normalizeOrientation) {
try {
int orientation=getOrientation();
if (needsNormalization(orientation)) {
try {
Bitmap original=
BitmapFactory.decodeByteArray(jpegOriginal, 0,
jpegOriginal.length);
Bitmap rotated=rotateViaMatrix(original, orientation);
exif.setTagValue(ExifInterface.TAG_ORIENTATION, 1);
exif.removeCompressedThumbnail();
ByteArrayOutputStream baos=new ByteArrayOutputStream();
exif.writeExif(rotated, baos, 100);
jpegOriginal=baos.toByteArray();
return(jpegOriginal);
}
catch (OutOfMemoryError e) {
AbstractCameraActivity.BUS
.post(new CameraEngine.DeepImpactEvent(e));
}
}
}
catch (Exception e) {
AbstractCameraActivity.BUS
.post(new CameraEngine.DeepImpactEvent(e));
}
}
return(getJpeg());
}
/**
* Retrieve a Bitmap rendition of the picture. Try to avoid
* this where possible, as it is memory-intensive.
*
* @param force true if you want to force creation of a Bitmap
* if there is none, false if you want the Bitmap
* but can live without it if it is unavailable
* @return the Bitmap rendition of the picture
*/
public Bitmap getBitmap(boolean force, boolean normalizeOrientation) {
if (bmp==null && force) {
updateBitmap(normalizeOrientation);
}
return(bmp);
}
public Bitmap buildPreviewThumbnail(Context ctxt, Float quality,
boolean normalizeOrientation) {
// TODO: move this into PictureTransaction work somewhere, so done
// on a background thread
if (thumbnail==null) {
int limit=2000000;
if (quality!=null && quality>0.0f && quality<1.0f) {
ActivityManager am=(ActivityManager)ctxt.getSystemService(Context.ACTIVITY_SERVICE);
int flags=ctxt.getApplicationInfo().flags;
int memoryClass=am.getMemoryClass();
if ((flags & ApplicationInfo.FLAG_LARGE_HEAP)!=0) {
memoryClass=am.getLargeMemoryClass();
}
limit=(int)(1024*1024*memoryClass*quality);
}
thumbnail=createBitmap(null, limit, normalizeOrientation);
}
return(thumbnail);
}
public Bitmap buildResultThumbnail(boolean normalizeOrientation) {
// TODO: move this onto background thread
return(createBitmap(null, 750000, normalizeOrientation));
}
private Bitmap createBitmap(Bitmap inBitmap, int limit,
boolean normalizeOrientation) {
double ratio=(double)jpegOriginal.length * 10.0d / (double)limit;
int inSampleSize;
if (ratio > 1.0d) {
inSampleSize=1 << (int)(Math.ceil(Math.log(ratio) / LOG_2));
} else {
inSampleSize=1;
}
return(createBitmap(inSampleSize, inBitmap, limit,
normalizeOrientation));
}
private Bitmap createBitmap(int inSampleSize, Bitmap inBitmap,
int limit,
boolean normalizeOrientation) {
BitmapFactory.Options opts=new BitmapFactory.Options();
opts.inSampleSize=inSampleSize;
opts.inBitmap=inBitmap;
Bitmap result;
try {
result=
BitmapFactory.decodeByteArray(jpegOriginal, 0, jpegOriginal.length, opts);
if (limit>0 && result.getByteCount()>limit) {
return(createBitmap(inSampleSize+1, inBitmap,
limit, normalizeOrientation));
}
}
catch (OutOfMemoryError e) {
return(createBitmap(inSampleSize+1, inBitmap,
limit, normalizeOrientation));
}
try {
if (normalizeOrientation) {
int orientation=getOrientation();
if (needsNormalization(orientation)) {
result=rotateViaMatrix(result, orientation);
}
}
}
catch (IOException e) {
AbstractCameraActivity.BUS.post(
new CameraEngine.DeepImpactEvent(e));
}
return(result);
}
private void updateBitmap(boolean normalizeOrientation) {
bmp=createBitmap(1, bmp, -1, normalizeOrientation); // no limit other than OOM
}
private boolean needsNormalization(int orientation) {
return(orientation==8 || orientation==3 || orientation==6);
}
static private Bitmap rotateViaMatrix(Bitmap original, int orientation) {
Matrix matrix=new Matrix();
matrix.setRotate(degreesForRotation(orientation));
return(Bitmap.createBitmap(original, 0, 0, original.getWidth(),
original.getHeight(), matrix, true));
}
static private int degreesForRotation(int orientation) {
int result;
switch (orientation) {
case 8:
result=270;
break;
case 3:
result=180;
break;
default:
result=90;
}
return(result);
}
}