In this tutorial I will show you how you can make a simple photo app. First the app allow you to take a picture with the embedded photo camera app. Second, the app will place this picture in an ImageView and allows you to apply a Colorfilter. Similar, but not completely, like Instagram.
When the user has applied a color filter the user can either save the picture to the SD card or share the photo via an Intent to for example Twitter or Facebook. In case of sharing, the picture will not be saved to the internal SD card (unless the user clicks the save button as well).
The following concepts will be discussed:
- Use MediaStore to take and grab a photo.
- Use onActivityOnResult to set the photo to your ImageView
- Save the picture either in the cache or save it to a new folder on your SD card
- Use SeekBar and Colorfilter to apply a filter to the Photo.
Step 1: Create your Layout
The layout consist out of an ImageView, Seekbar and three buttons: take picture, save and share. The XML layout should look like this:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <Button android:id="@+id/btn_takephoto" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_marginBottom="15dp" android:text="Take Photo" /> <TextView android:id="@+id/tv_titleapp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/btn_takephoto" android:layout_alignParentTop="true" android:text="InstaPicture" android:textAppearance="?android:attr/textAppearanceMedium" /> <ImageView android:id="@+id/iv_displayphoto" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@+id/skbarChangeColor" android:layout_alignLeft="@+id/tv_titleapp" android:layout_alignParentRight="true" android:layout_below="@+id/tv_titleapp" android:layout_marginTop="18dp" /> <SeekBar android:id="@+id/skbarChangeColor" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_above="@+id/btn_takephoto" android:layout_alignParentLeft="true" android:progress="50" /> <Button android:id="@+id/btn_save" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/btn_takephoto" android:layout_alignBottom="@+id/btn_takephoto" android:layout_toRightOf="@+id/btn_takephoto" android:text="Save" /> <Button android:id="@+id/btn_share" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/btn_save" android:layout_alignBottom="@+id/btn_save" android:layout_toRightOf="@+id/btn_save" android:text="share" /> </RelativeLayout>
Step 2: Create your Java class
Next open your MainActivity.java, and lets have fun :). On the bottom of this post you find the complete class. For now I will walk you through the most important parts.
First create your references from the Take Picture button and your Imageview
btntakephoto = (Button)findViewById(R.id.btn_takephoto); ivdisplayphoto = (ImageView)findViewById(R.id.iv_displayphoto);
We need to create OnClickListener on the btntakephoto button to launch the camera. You can use an Intent (MediaStore.ACTION_IMAGE_CAPTURE) to launch your camera. Store your photo in File, but first call the getExternalStoragePublicDirectory.
btntakephoto.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { File photostorage = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); photofile = new File(photostorage, (System.currentTimeMillis()) + ".jpg"); Intent i = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); //intent to start camera i.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photofile)); startActivityForResult(i, TAKENPHOTO); } });
Now that you have launched the camera and stored the picture in photofile, we need to retrieve the photo and set it in your ImageView. Note that we did not yet save the picture to the internal storage.
Use onActivityResult and requestcode to check if a picture has been taken. A default int variable has been made initialized with 0 to check against this. You can see in the code below that I try two things, first to get the photo via data.getExtras().get(“data”), and than via BitmapFactory.decodeFile(photofile.getAbsolutePath(). The reason is that I noticed during testing that HTC devices wont work with the BitmapFactory. Why? I’m not sure, but I guess it has something to do with the HTC Sense UI as it treats the Gallery a bit different (Damnn you HTC!). Another small note is that you can see I set the Seekbar as visible, this is as the Seekbar is only useful to the user when an image is actually available. If your not fully following this tutorial, the Seekbar has not much to do with setting the image to the ImageView. Finally, when we dont succeed in retrieving the photo we show the user a toast message.
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // TODO Auto-generated method stub super.onActivityResult(requestCode, resultCode, data); if (requestCode == TAKENPHOTO){ try{ photo = (Bitmap) data.getExtras().get("data"); } catch(NullPointerException ex){ photo = BitmapFactory.decodeFile(photofile.getAbsolutePath()); } if(photo != null){ ivdisplayphoto.setImageBitmap(photo); sbSeekBar.setVisibility(View.VISIBLE); } else{ Toast.makeText(this, "Oops,can't get the photo from your gallery", Toast.LENGTH_LONG).show(); } } }
So now you have the photo in the ImageView. The next step is to use the SeekBar to apply a colorfilter to your photo. Below are the settings for the SeekBar. As you can see the Seekbar has been set as View.Gone when it was initialized in the onCreate. The setProgress(50) means that we have set the Seekbar in the middle when the Seekbar was created. You can adjust these parameter to your own preferences.
sbSeekBar = (SeekBar) findViewById(R.id.skbarChangeColor); sbSeekBar.setMax(100); sbSeekBar.setKeyProgressIncrement(1); sbSeekBar.setProgress(50); sbSeekBar.setVisibility(View.GONE);
Lets first create the setOnSeekBarChangeListener, to tell the Seekbar what to do when the user drags the Seekbar.
sbSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener(){ @Override public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) { applyColorFilter(progress); } @Override public void onStartTrackingTouch(SeekBar arg0) { // TODO Auto-generated method stub } @Override public void onStopTrackingTouch(SeekBar arg0) { // TODO Auto-generated method stub } });
As you can see I call a different function when the user change the progress of the SeekBar: applyColorFilter(progress); Before we get into this make sure you call the right Android SDK APIs for the ColorFilter.
colorMatrix = new ColorMatrix(); filter = new ColorMatrixColorFilter(this.colorMatrix); paint = new Paint(); paint.setColorFilter(filter);
public void applyColorFilter(int progress){ colorMatrix.setSaturation(progress/(float)40); filter = new ColorMatrixColorFilter(colorMatrix); paint.setColorFilter(filter); canvasBitmap = Bitmap.createBitmap(photo.getWidth(), photo.getHeight(), Bitmap.Config.ARGB_8888); cv = new Canvas(canvasBitmap); cv.drawBitmap(photo, 0, 0, paint); ivdisplayphoto.setImageBitmap(canvasBitmap); }
Ok, now we have the filter right, and the filter is creating a new canvas of your photo which was taken with the camera (we also replace the ImageView with this new canvasBitmap). The next step is that we want to save it to the gallery of the phone.
First we retrieve the image in the ImageView via ivdisplayphoto.getDrawingCache(). Than we create a new Directory on your SD card (newDir), as you might use the app multiple times we need to create an unique title for your photo. The Java Random is used to get a number and make it part of your title. Create a new file and use fileOutputStream to save the photo.
btnsave.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ivdisplayphoto.setDrawingCacheEnabled(true); Bitmap bitmap = ivdisplayphoto.getDrawingCache(); String root = Environment.getExternalStorageDirectory().toString(); File newDir = new File(root + "/your folder name"); newDir.mkdirs(); Random gen = new Random(); int n = 10000; n = gen.nextInt(n); String fotoname = "Photo-"+ n +".jpg"; File file = new File (newDir, fotoname); if (file.exists ()) file.delete (); try { FileOutputStream out = new FileOutputStream(file); bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out); out.flush(); out.close(); Toast.makeText(getApplicationContext(), "Saved to your folder", Toast.LENGTH_SHORT ).show(); } catch (Exception e) { } } });
We are almost done, the last piece is the possibility for the user to share the photo via an Intent to e.g. Facebook or Twitter.
Again, alike with the save option, we retrieve the Bitmap from the ImageView. We create again a File (sharefile) and compress the Bitmap. Next we call an Intent with Action_Send (so the apps are provided which can be used to share the photo with).
btnshare.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { BitmapDrawable bitmapDrawable = (BitmapDrawable)ivdisplayphoto.getDrawable(); Bitmap bitmap = bitmapDrawable.getBitmap(); // Save this bitmap to a file. File cache = getApplicationContext().getExternalCacheDir(); File sharefile = new File(cache, "toshare.png"); try { FileOutputStream out = new FileOutputStream(sharefile); bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); out.flush(); out.close(); } catch (IOException e) { } // Now send it out to share Intent share = new Intent(android.content.Intent.ACTION_SEND); share.setType("image/*"); share.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + sharefile)); try { startActivity(Intent.createChooser(share, "Share photo")); } catch (Exception e) { } } });
Step 3: The complete Java Class
In the code below you find the complete MainActivity.java class.
package com.christianpeeters.photoapp; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.Random; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.provider.MediaStore; import android.app.Activity; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.Paint; import android.graphics.drawable.BitmapDrawable; import android.view.View; import android.widget.Button; import android.widget.ImageView; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.Toast; public class MainActivity extends Activity { Button btntakephoto, btnsave, btnshare; ImageView ivdisplayphoto; SeekBar sbSeekBar; private ColorMatrix colorMatrix; private ColorMatrixColorFilter filter; private Paint paint; private Canvas cv; String fotoname; int progress; private File photofile, file; private int TAKENPHOTO = 0; Bitmap photo, canvasBitmap; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btntakephoto = (Button)findViewById(R.id.btn_takephoto); btnsave = (Button)findViewById(R.id.btn_save); btnshare = (Button)findViewById(R.id.btn_share); ivdisplayphoto = (ImageView)findViewById(R.id.iv_displayphoto); sbSeekBar = (SeekBar) findViewById(R.id.skbarChangeColor); sbSeekBar.setMax(100); sbSeekBar.setKeyProgressIncrement(1); sbSeekBar.setProgress(50); sbSeekBar.setVisibility(View.GONE); colorMatrix = new ColorMatrix(); filter = new ColorMatrixColorFilter(this.colorMatrix); paint = new Paint(); paint.setColorFilter(filter); sbSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener(){ @Override public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) { applyColorFilter(progress); } @Override public void onStartTrackingTouch(SeekBar arg0) { // TODO Auto-generated method stub } @Override public void onStopTrackingTouch(SeekBar arg0) { // TODO Auto-generated method stub } }); btntakephoto.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { File photostorage = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); photofile = new File(photostorage, (System.currentTimeMillis()) + ".jpg"); Intent i = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); //intent to start camera i.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photofile)); startActivityForResult(i, TAKENPHOTO); } }); btnsave.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ivdisplayphoto.setDrawingCacheEnabled(true); Bitmap bitmap = ivdisplayphoto.getDrawingCache(); String root = Environment.getExternalStorageDirectory().toString(); File newDir = new File(root + "/saved_images"); newDir.mkdirs(); Random gen = new Random(); int n = 10000; n = gen.nextInt(n); String fotoname = "photo-"+ n +".jpg"; File file = new File (newDir, fotoname); if (file.exists ()) file.delete (); try { FileOutputStream out = new FileOutputStream(file); bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out); out.flush(); out.close(); Toast.makeText(getApplicationContext(), "safed to your folder", Toast.LENGTH_SHORT ).show(); } catch (Exception e) { } } }); btnshare.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { BitmapDrawable bitmapDrawable = (BitmapDrawable)ivdisplayphoto.getDrawable(); Bitmap bitmap = bitmapDrawable.getBitmap(); // Save this bitmap to a file. File cache = getApplicationContext().getExternalCacheDir(); File sharefile = new File(cache, "toshare.png"); try { FileOutputStream out = new FileOutputStream(sharefile); bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); out.flush(); out.close(); } catch (IOException e) { } // Now send it out to share Intent share = new Intent(android.content.Intent.ACTION_SEND); share.setType("image/*"); share.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + sharefile)); try { startActivity(Intent.createChooser(share, "Share photo")); } catch (Exception e) { } } }); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // TODO Auto-generated method stub super.onActivityResult(requestCode, resultCode, data); if (requestCode == TAKENPHOTO){ try{ photo = (Bitmap) data.getExtras().get("data"); } catch(NullPointerException ex){ photo = BitmapFactory.decodeFile(photofile.getAbsolutePath()); } if(photo != null){ ivdisplayphoto.setImageBitmap(photo); sbSeekBar.setVisibility(View.VISIBLE); } else{ Toast.makeText(this, "Oops,can't get the photo from your gallery", Toast.LENGTH_LONG).show(); } } } public void applyColorFilter(int progress){ colorMatrix.setSaturation(progress/(float)40); filter = new ColorMatrixColorFilter(colorMatrix); paint.setColorFilter(filter); canvasBitmap = Bitmap.createBitmap(photo.getWidth(), photo.getHeight(), Bitmap.Config.ARGB_8888); cv = new Canvas(canvasBitmap); cv.drawBitmap(photo, 0, 0, paint); ivdisplayphoto.setImageBitmap(canvasBitmap); } }
Excellent information, thanks for sharing great information. We deal with Android App Development this information is quite informative and interesting.
Hey, Nice work. Just wondering do you have the Manifest file? Thanks for sharing!
for the saving in manifest