How to Make an Android Camera App with Photo Filters? Full Code and Step by Step Guide
Dhruv Joshi
Posted on January 6, 2023
Creating a camera app that has photo filters is a great way to learn about image processing in Android. In this tutorial, we will create a simple camera app that allows you to apply different filters to your photos.
We will start by setting up a basic layout that includes a preview of the camera and a button to take a picture. Then, we will add the ability to select different filters and apply them to the image. Finally, we will save the modified image to the device's storage.
Clear here to get in touch with me if you got similar app ideas for your projects.
Here is a step by step guide to creating your own camera app with photo filters:
Create a new Android Studio project and select "Empty Activity" as the template.
Add the following permissions to your AndroidManifest.xml file:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- Create a layout file for the main activity, activity_main.xml. Add a TextureView to display the camera preview and a Button to take a picture. Your layout should look something 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"
tools:context=".MainActivity">
<TextureView
android:id="@+id/textureView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button
android:id="@+id/button_capture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:text="Capture" />
</RelativeLayout>
- In the MainActivity.java file, add the following instance variables:
private TextureView textureView;
private Button buttonCapture;
private String cameraId;
private CameraDevice cameraDevice;
private CameraCaptureSession cameraCaptureSessions;
private CaptureRequest.Builder captureRequestBuilder;
private Size imageDimension;
private ImageReader imageReader;
- Set up the camera in the onCreate() method of the MainActivity. First, initialize the textureView and buttonCapture variables:
textureView = (TextureView) findViewById(R.id.textureView);
buttonCapture = (Button) findViewById(R.id.button_capture);
Next, set up the TextureView to display the camera preview. Add the following code:
textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
openCamera();
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
// Transform you image captured size according to the surface width and height
}
@Override
public boolean onSurfaceTexture
- In the openCamera() method, we will set up the camera using the CameraManager class. Add the following code:
private void openCamera() {
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
cameraId = manager.getCameraIdList()[0];
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
assert map != null;
imageDimension = map.getOutputSizes(SurfaceTexture.class)[0];
// Add permission for camera and let user grant the permission
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CAMERA_PERMISSION);
return;
}
manager.openCamera(cameraId, stateCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
- Now we will set up the cameraDevice and create a cameraCaptureSession to take pictures. Add the following code:
private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice camera) {
//This is called when the camera is open
cameraDevice = camera;
createCameraPreview();
}
@Override
public void onDisconnected(CameraDevice camera) {
cameraDevice.close();
}
@Override
public void onError(CameraDevice camera, int error) {
cameraDevice.close();
cameraDevice = null;
}
};
protected void createCameraPreview() {
try {
SurfaceTexture texture = textureView.getSurfaceTexture();
assert texture != null;
texture.setDefaultBufferSize(imageDimension.getWidth(), imageDimension.getHeight());
Surface surface = new Surface(texture);
captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureRequestBuilder.addTarget(surface);
cameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
//The camera is already closed
if (null == cameraDevice) {
return;
}
// When the session is ready, we start displaying the preview.
cameraCaptureSessions = cameraCaptureSession;
updatePreview();
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
Toast.makeText(MainActivity.this, "Configuration change", Toast.LENGTH_SHORT).show();
- In the updatePreview() method, we will start displaying the camera preview. Add the following code:
protected void updatePreview() {
if (null == cameraDevice) {
Log.e(TAG, "updatePreview error, return");
}
captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
try {
cameraCaptureSessions.setRepeatingRequest(captureRequestBuilder.build(), null, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
- Now we will add the ability to take a picture. In the onClick() method of the Button, add the following code:
buttonCapture.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
takePicture();
}
});
private void takePicture() {
if (null == cameraDevice) {
Log.e(TAG, "cameraDevice is null");
return;
}
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraDevice.getId());
Size[] jpegSizes = null;
if (characteristics != null) {
jpegSizes = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputSizes(ImageFormat.JPEG);
}
int width = 640;
int height = 480;
if (jpegSizes != null && 0 < jpegSizes.length) {
width = jpegSizes[0].getWidth();
height = jpegSizes[0].getHeight();
}
ImageReader reader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 1);
List<Surface> outputSurfaces = new ArrayList<Surface>(2);
outputSurfaces.add(reader.getSurface());
outputSurfaces.add(new Surface(textureView.getSurfaceTexture()));
final CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(reader.getSurface());
captureBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
// Orientation
int rotation = getWindowManager().getDefaultDisplay().getRotation();
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));
final File file = new File(Environment.getExternalStorageDirectory() + "/pic.jpg");
ImageReader.OnImageAvailableListener readerListener = new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Image image = null;
try {
image = reader.acquireLatestImage();
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.capacity()];
buffer.get(bytes);
save
- Now we will add the ability to apply filters to the image. First, create a layout file for a FilterSelectionDialogFragment. This will be a simple layout with a RecyclerView to display the available filters and a Button to apply the selected filter.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/button_apply"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:text="Apply" />
</LinearLayout>
- Next, create a FilterSelectionAdapter to display the filters in the RecyclerView. This adapter will need to extend the RecyclerView.Adapter class and implement the ViewHolder pattern.
public class FilterSelectionAdapter extends RecyclerView.Adapter<FilterSelectionAdapter.ViewHolder> {
private List<Filter> filters;
private int selectedPosition = -1;
public FilterSelectionAdapter(List<Filter> filters) {
this.filters = filters;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_filter, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Filter filter = filters.get(position);
holder.textView.setText(filter.getName());
holder.imageView.setImageBitmap(filter.getThumbnail());
holder.itemView.setSelected(selectedPosition == position);
}
@Override
public int getItemCount() {
return filters.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
ImageView imageView;
TextView textView;
public ViewHolder(View itemView) {
super(itemView);
imageView = (ImageView) itemView.findViewById(R.id.imageView);
textView = (TextView) itemView.findViewById(R.id.textView);
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
selectedPosition = getAdapterPosition();
notifyDataSetChanged();
}
});
}
}
}
- Create a FilterSelectionDialogFragment to display the filters and allow the user to select one. This fragment will need to implement the DialogFragment class and include a RecyclerView and Button in its layout.
public class FilterSelectionDialogFrag
- In the applyFilter() method of the FilterSelectionDialogFragment, we will apply the selected filter to the image. First, we will create a method to apply the filter using the RenderScript framework:
private void applyFilter(Bitmap bitmap, Filter filter) {
RenderScript rs = RenderScript.create(getActivity());
Allocation input = Allocation.createFromBitmap(rs, bitmap);
Allocation output = Allocation.createTyped(rs, input.getType());
ScriptIntrinsicColorMatrix script = ScriptIntrinsicColorMatrix.create(rs);
script.setColorMatrix(filter.getColorMatrix());
script.forEach(output);
output.copyTo(bitmap);
input.destroy();
output.destroy();
script.destroy();
rs.destroy();
}
- Next, we will retrieve the selected filter and apply it to the image:
Filter filter = filters.get(selectedPosition);
applyFilter(bitmap, filter);
- Finally, we will save the modified image to the device's storage:
private void saveBitmap(Bitmap bitmap) {
FileOutputStream out = null;
try {
out = new FileOutputStream(imageFile);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
- Finally, add a button to the main layout to open the FilterSelectionDialogFragment and display the available filters:
<Button
android:id="@+id/button_filters"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:text="Filters" />
In the onClick() method of the Button, open the FilterSelectionDialogFragment:
buttonFilters.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FilterSelectionDialogFragment fragment = new FilterSelectionDialogFragment();
fragment.setImageFile(imageFile);
fragment.show(getSupportFragmentManager(), "filters");
}
});
And that's it! You now have a fully functional camera app with the ability to apply different filters to your photos. I hope you found this tutorial helpful. If you need any help, reach me here!
Happy coding!
Blog banner by wang_selina.
Posted on January 6, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
January 6, 2023