I have an application that uses the Camera API to capture images. The implementation is contained within a CameraActivity class that is modeled after a number of examples that I have seen within this site. The application is currently running on thousands of phones correctly; however, today, I received a report from a HTC Desire Z user stating that my images are being captured as a series of vertical lines (unfortunately, I don't have enough points to post a picture, but have plenty to send if someone would like to see one). The image preview within my application and the image that I save are both distorted. The user has also confirmed that he is able to use his phone camera outside of my application without problem. I've attached the source for review.
Any help would be greatly appreciated.
public class CameraActivity extends Activity {
public static final int FOTO_MODE = 0;
private SurfaceView preview = null;
private SurfaceHolder previewHolder = null;
private Camera camera = null;
private boolean inPreview = false;
pr开发者_StackOverflowivate String receiptFileName = null;
SurfaceHolder.Callback surfaceCallback = new SurfaceHolder.Callback() {
public void surfaceCreated(SurfaceHolder holder) {
try {
camera.setPreviewDisplay(previewHolder);
} catch (Throwable t) {
Log.e("PreviewDemo-surfaceCallback", "Exception in setPreviewDisplay()", t);
Toast.makeText(CameraActivity.this, t.getMessage(), Toast.LENGTH_LONG).show();
}
}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Camera.Parameters parameters = camera.getParameters();
Camera.Size size = getBestPreviewSize(width, height, parameters);
if (size != null) {
parameters.setPreviewSize(size.width, size.height);
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
camera.setParameters(parameters);
camera.startPreview();
inPreview = true;
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
// no-op
}
};
Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() {
public void onPictureTaken(byte[] imageData, Camera c) {
if (imageData != null) {
StoreByteImage(CameraActivity.this, imageData, 50, "ImageName");
camera.startPreview();
Intent resultIntent = new Intent();
resultIntent.putExtra(getString(R.string.receiptFileName), receiptFileName);
setResult(FOTO_MODE, resultIntent);
finish();
}
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.camera_preview);
receiptFileName = getIntent().getStringExtra(getString(R.string.receiptFileName));
ImageView shutterButton = (ImageView) findViewById(R.id.shutterButton);
shutterButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
takePicture();
}
});
shutterButton.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
focusOnly();
return false;
}
});
preview = (SurfaceView) findViewById(R.id.surface_camera);
previewHolder = preview.getHolder();
previewHolder.addCallback(surfaceCallback);
previewHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
@Override
public void onResume() {
super.onResume();
camera = Camera.open();
}
@Override
public void onPause() {
if (inPreview) {
camera.stopPreview();
}
camera.release();
camera = null;
inPreview = false;
super.onPause();
}
private Camera.Size getBestPreviewSize(int width, int height, Camera.Parameters parameters) {
Camera.Size result = null;
for (Camera.Size size : parameters.getSupportedPreviewSizes()) {
if (size.width <= width && size.height <= height) {
if (result == null) {
result = size;
} else {
int resultArea = result.width * result.height;
int newArea = size.width * size.height;
if (newArea > resultArea) {
result = size;
}
}
}
}
return result;
}
private void focusOnly() {
String focusMode = camera.getParameters().getFocusMode();
if (focusMode != null
&& (focusMode.equals(Camera.Parameters.FOCUS_MODE_AUTO) || focusMode
.equals(Camera.Parameters.FOCUS_MODE_MACRO))) {
Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() {
public void onAutoFocus(boolean success, Camera camera) {
}
};
camera.autoFocus(autoFocusCallback);
}
}
private void takePicture() {
camera.takePicture(null, mPictureCallback, mPictureCallback);
}
private boolean StoreByteImage(Context mContext, byte[] imageData, int quality, String expName) {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
FileOutputStream fileOutputStream = null;
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
Bitmap bMap = BitmapFactory.decodeByteArray(imageData, 0, imageData.length, options);
Matrix mat = new Matrix();
mat.postRotate(90);
Bitmap myImage = Bitmap.createBitmap(bMap, 0, 0, bMap.getWidth(), bMap.getHeight(), mat, true);
File path = new File(Environment.getExternalStorageDirectory() + ExpenseIt.SD_CARD_PATH_EXTENSION);
path.mkdirs();
fileOutputStream = new FileOutputStream(path + "/" + determineReceiptName());
BufferedOutputStream bos = new BufferedOutputStream(fileOutputStream);
myImage.compress(CompressFormat.JPEG, quality, bos);
bos.flush();
bos.close();
} catch (Exception e) {
FailureDialogFactory.getInstance().handleFailure(this, "Failure storing camera image.", e);
}
} else {
FailureDialogFactory.getInstance().handleFailure(this,
"Unable capture receipts while SD card is not available or phone is connected to computer.", null);
}
return true;
}
private String determineReceiptName() {
if (receiptFileName == null) {
receiptFileName = Calendar.getInstance().getTimeInMillis() + ".jpg";
}
return receiptFileName;
}
I struggled with the same issue in my own camera project on my HTC Desire S. Even though the getSupportedPictureSizes method returned a huge list of supported sizes, I found that the pictures that I was able to capture without the "vertical bar" issue were the ones that had an aspect ratio most closely matching the aspect ratio of the device screen. Example:
My HTC Desire S returns the following list when getSupportedPictureSizes method is called:
2592x1952,2592x1456,2592x1520,2592x1936,2592x1728,2592x1552,2048x1536,2048x1360,2048x1216,2048x1152,2048x1200,1600x1200,1584x1056,1280x960,1280x848,1280x768,1280x720,1280x752,1024x768,640x480,640x416,640x384,640x368,512x384,400x400,272x272
The screen resolution of the phone is:
800x480
Aspect Ratio: 1.67
So if I use the greatest size returned by getSupportedPictureSizes method to capture the image
2592x1952
Aspect Ratio: 1.32
The result: The captured image is a series of vertical bars. :(
So I modified the getOptimalPreviewSize method as given here in the CameraPreview example to return the list of resolutions that are closest to the handet screen resolution. Not the most efficient way to do it but works for me:
private List getOptimalPictureSizes(List<Size> sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.1;
double targetRatio = (double) w / h;
Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
List<Size> finalSizes = new ArrayList();
// First filter out all sizes that have an aspect
// tolerance > 0.1
for (Size size : sizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) < ASPECT_TOLERANCE){
finalSizes.add(size);
}
}
// Remove sizes with same width but more deviation from the
// handset's aspect ratio (1.67 in my HTC Desire S)
List<Size> absoulteSizes = new ArrayList<Size>();
for(int i=0; i<finalSizes.size()-1; i++){
for(int j=i+1; j<finalSizes.size(); j++){
Size iSize = finalSizes.get(i);
Size jSize = finalSizes.get(j);
if(iSize.width != jSize.width)
continue;
double iRatio = (double)iSize.width/iSize.height;
double jRatio = (double)jSize.width/jSize.height;
if(Math.abs(targetRatio - iRatio) < Math.abs(targetRatio - jRatio)){
absoulteSizes.add(iSize);
}else if(Math.abs(targetRatio - iRatio) > Math.abs(targetRatio - jRatio)){
absoulteSizes.add(jSize);
}
else{
absoulteSizes.add(iSize);
absoulteSizes.add(jSize);
}
}
}
for(Size s:absoulteSizes){
Log.e("Preview", "COMPUTED::: "+s.width+"x"+s.height);
}
return absoulteSizes;
}
The above method gives me the following filtered list:
2592x1552 Aspect Ratio: 1.67
2048x1216 Aspect Ratio: 1.684
1280x768 Aspect Ratio: 1.67
640x384 Aspect Ratio: 1.67
All the 4 sizes work fine for my camera without the vertical bar problem. It's also worthwhile to note that the same 4 sizes are suggested in the stock camera app of the phone as well.
Hope this helps.
精彩评论