Android saving PNG from the device camera (Poor quality)

4k views Asked by At

I am having a problem while trying to save a photo to storage with an Android app. The app uses the devices camera to take a photo and save it as a PNG to the device.

For some reason no matter what I do or where I store the image the quality is very poor. The app is an existing project that is quite large so I was wondering if there are other factors to consider when saving images to a device or maybe another way of overriding the quality.

This is the function that was coded by the previous dev:

public String saveImageToDevice(Bitmap image) {
    saveCanvasImage(image);

    String root = Environment.getExternalStorageDirectory().toString()+"/Android/data/com.app.android";
    File myDir = new File(root + "/saved_images");    
    myDir.mkdirs();

    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    String fname = "Image-"+ timeStamp +".png";
    File file = new File (myDir, fname);
    if (file.exists ()){ 
        file.delete ();
    } 
    try {




       Toast.makeText(getActivity(), "Saving Image...", Toast.LENGTH_SHORT).show();
       Log.i("Image saved", root+"/saved_images/"+fname);
       FileOutputStream out = new FileOutputStream(file);
       image.compress(CompressFormat.PNG, 100, out);
       imageLocations.add(fname);
       out.flush();
       out.close();


       //return myDir.getAbsolutePath() + "/" +fname;
       return fname;

    } catch (Exception e) {
           e.printStackTrace();
           return null;
    }
}

And this is a function I have tried myself from an example online:

public void saveCanvasImage(Bitmap b) {

    File f = new File(Environment.getExternalStorageDirectory().toString() + "/img.png");



    try {

    f.createNewFile();  // your mistake was at here 

    FileOutputStream fos = new FileOutputStream(f);

    b.compress(CompressFormat.PNG, 100, fos);

    fos.flush();

    fos.close();

    }catch (IOException e){

        e.printStackTrace();
    }

  }

Both of these produce the same very poor images. I have posted a before and after segment below. This is what the camera preview looks like.

This is what the camera preview looks like.

This is the resulting image once it has saved. This is the resulting image once it has saved.

After speaking to a few people i am including my camera intent code:

public void startCameraIntent(){
    /*************************** Camera Intent Start ************************/        
    // Define the file-name to save photo taken by Camera activity         
    String fileName = "Camera_Example.png";        
    // Create parameters for Intent with filename
    ContentValues values = new ContentValues();
    values.put(MediaStore.Images.Media.TITLE, fileName);
    values.put(MediaStore.Images.Media.DESCRIPTION,"Image capture by camera");
    // imageUri is the current activity attribute, define and save it for later usage  
    @SuppressWarnings("unused")
    Uri imageUri = getActivity().getContentResolver().insert( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
    /**** EXTERNAL_CONTENT_URI : style URI for the "primary" external storage volume. ****/


    // Standard Intent action that can be sent to have the camera
    // application capture an image and return it.  
    Intent intent = new Intent( MediaStore.ACTION_IMAGE_CAPTURE );
    //intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);   // set the image file name      

    startActivityForResult( intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);


 /*************************** Camera Intent End ************************/
}

As you can see the EXTRA_OUTPUT line is has been commented out due to it causing crashes with the below error:

12-17 13:31:37.339: E/AndroidRuntime(16123): java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=65537, result=-1, data=null} to activity {}: java.lang.NullPointerException

I have also included my onActivityresult code too:

    public void onActivityResult( int requestCode, int resultCode, Intent data)
{
    super.onActivityResult(requestCode, resultCode, data);


    int page = mViewPager.getCurrentItem();
    NotesPagerFragment  note = pages.get(page);
    Log.i("Request Code", ""+requestCode);
        //For the ImageCapture Activity
        if ( requestCode == 1) {

              if ( resultCode != 0) {
                 /*********** Load Captured Image And Data Start ****************/
                 Bitmap bp = (Bitmap) data.getExtras().get("data");
                 //add the image to the note through a function call
                 note.addImage(bp);
                 note.saveImageToDevice(bp);
                  //String imageId = convertImageUriToFile( imageUri,CameraActivity);                       
                 //  Create and excecute AsyncTask to load capture image
                 // new LoadImagesFromSDCard().execute(""+imageId);                      
                /*********** Load Captured Image And Data End ****************/                    
              } else if ( resultCode == 0) {
                  Toast.makeText(this.getActivity(), " Picture was not taken ", Toast.LENGTH_SHORT).show();
              } else {

                  Toast.makeText(this.getActivity(), " Picture was not taken ", Toast.LENGTH_SHORT).show();
              }
          }

        //For the deleting an Image
        if (requestCode == 2) {
            String location = (String) data.getExtras().get("imageLocation");
            if(data.getExtras().get("back") != null){
                //just going back, don't mind me
            }else {
                //Toast.makeText(this.getActivity(), "BOO", Toast.LENGTH_SHORT).show();
                note.removeNoteImageFromView(location);
                database.removeSingleNoteImageFromSystemByLocation(location);

            }
        }
  }
1

There are 1 answers

0
jampez77 On BEST ANSWER

OK so after a lot of help from MelquiadesI have eventually solved this issue. The problem I had was that my intent and onActivityResult were retrieving the thumbnail of the image and scaling it up (hence the poor quality).

The line below is responsible for getting the thumbnail preview (120px x 160px):

Bitmap bp = (Bitmap) data.getExtras().get("data");

In order to access the full image I need to add EXTRA_OUTPUT to the intent which looks as follows:

public void startCameraIntent(){
    /*************************** Camera Intent Start ************************/        
    File imageFile = new File(imageFilePath); 
    Uri imageFileUri = Uri.fromFile(imageFile); // convert path to Uri
    
    // Standard Intent action that can be sent to have the camera
    // application capture an image and return it.  
    Intent intent = new Intent( MediaStore.ACTION_IMAGE_CAPTURE );
    intent.putExtra(MediaStore.EXTRA_OUTPUT, imageFileUri);   // set the image file name      
    
    startActivityForResult( intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);
    
    
 /*************************** Camera Intent End ************************/
}

I also declared my imageFilePath as a string at the top of my activity:

String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFilePath = Environment.getExternalStorageDirectory().toString()+"/Android/data/com.my.app/Image-"+timeStamp+".png";

I then had to change onActivityResult so it could access the full image to use:

public void onActivityResult( int requestCode, int resultCode, Intent data){
    super.onActivityResult(requestCode, resultCode, data);
    
    int page = mViewPager.getCurrentItem();
    NotesPagerFragment  note = pages.get(page);
    Log.i("Request Code", ""+requestCode);
        //For the ImageCapture Activity
        if ( requestCode == 1) {
               
              if ( resultCode != 0) {
                 /*********** Load Captured Image And Data Start ****************/
                  
                  // Decode it for real 
                 BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();
                 bmpFactoryOptions.inJustDecodeBounds = false; 

                //imageFilePath image path which you pass with intent 
                 Bitmap bp = BitmapFactory.decodeFile(imageFilePath, bmpFactoryOptions); 
                 
                 //rotate image by 90 degrees
                 Matrix rotateMatrix = new Matrix();
                 rotateMatrix.postRotate(90);
                 Bitmap rotatedBitmap = Bitmap.createBitmap(bp, 0, 0, bp.getWidth(), bp.getHeight(), rotateMatrix, false);
                
                //add the image to the note through a function call
                 note.addImage(rotatedBitmap);
                 note.saveImageToDevice(rotatedBitmap);
                  //String imageId = convertImageUriToFile( imageUri,CameraActivity);                       
                 //  Create and excecute AsyncTask to load capture image
                 // new LoadImagesFromSDCard().execute(""+imageId);                      
                /*********** Load Captured Image And Data End ****************/   
                 
              } else if ( resultCode == 0) {
                  Toast.makeText(this.getActivity(), " Picture was not taken ", Toast.LENGTH_SHORT).show();
              } else {
                   
                  Toast.makeText(this.getActivity(), " Picture was not taken ", Toast.LENGTH_SHORT).show();
              }
          }
        
        //For the deleting an Image
        if (requestCode == 2) {
            String location = (String) data.getExtras().get("imageLocation");
            if(data.getExtras().get("back") != null){
                //just going back, don't mind me
            }else {
                //Toast.makeText(this.getActivity(), "BOO", Toast.LENGTH_SHORT).show();
                note.removeNoteImageFromView(location);
                database.removeSingleNoteImageFromSystemByLocation(location);
                
            }
        }
  }

The key part here is:

 // Decode it for real 
 BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();
 bmpFactoryOptions.inJustDecodeBounds = false; 

 //imageFilePath image path which you pass with intent 
 Bitmap bp = BitmapFactory.decodeFile(imageFilePath, bmpFactoryOptions); 

This code decodes the image you saved at imageFilePath into a usable bitmap. From here you can use it as normal.

Sometimes (apparently this is quite common) the image comes in rotated by 90°, the next bit of code will rotate that back if you need it to:

//rotate image by 90 degrees
Matrix rotateMatrix = new Matrix();
rotateMatrix.postRotate(90);
Bitmap rotatedBitmap = Bitmap.createBitmap(bp, 0, 0, bp.getWidth(), bp.getHeight(), rotateMatrix, false);