Open In App

How to Create a Paint Application in Android?

Last Updated : 24 Feb, 2025
Comments
Improve
Suggest changes
Like Article
Like
Report

We all have once used the MS-Paint in our childhood, and when the system was shifted from desks to our palms, we started doodling on Instagram Stories, Hike, WhatsApp, and many more such apps. But have you ever thought about how these functionalities were brought to life? So, In this article, we will be discussing the basic approach used by such apps and will create a basic replica of such apps. A sample video is given below to get an idea about what we are going to do in this article. Note that we are going to implement this project using both Java and Kotlin language. 

Step by Step Implementation

Step 1: Create a New Project

To create a new project in Android Studio please refer to How to Create/Start a New Project in Android Studio. Select Java/Kotlin as the programming language.

Step 2: Add storage permissions in AndroidManifest

Add the following permissions in AndroidManifest.xml

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

Step 3: Adding the dependency in gradle.build.kts (Module :app)

We will wil be adding two libraries, one for the draw canvas and the other for the color palette. Refer to DrawingCanvas and ColorPicker to check out their github repositories.

dependencies {
implementation("com.github.Miihir79:DrawingCanvas:1.1.2")
implementation ("com.github.Dhaval2404:ColorPicker:2.3")
}

Adding Jitpack support in settings.gradle.kts

dependencyResolutionManagement {
..
repositories {
..
maven { url = uri("https://jitpack.io") }
}
}

Step 3: Add icons in drawable

Add the following drawable files in res > drawable folder.

brush_icon.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="40dp"
    android:height="40dp"
    android:viewportWidth="960"
    android:viewportHeight="960"
    android:tint="?attr/colorControlNormal">
  <path
      android:fillColor="@android:color/white"
      android:pathData="M223.33,842Q185.67,842 149,827Q112.33,812 86.67,782.67Q118.67,774.67 137.67,752.5Q156.67,730.33 156.67,691.33Q156.67,645.33 188.67,613.33Q220.67,581.33 266.67,581.33Q312.67,581.33 344.67,613.33Q376.67,645.33 376.67,691.33Q376.67,756 332,799Q287.33,842 223.33,842ZM453.33,604L356.67,504L726.67,134Q739.67,121 756.5,120.5Q773.33,120 787.33,134L824.67,171.33Q838.67,185.33 838.33,202.33Q838,219.33 824.67,232.67L453.33,604Z"/>
</vector>
btn_redo.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="40dp"
    android:height="40dp"
    android:viewportWidth="960"
    android:viewportHeight="960"
    android:tint="?attr/colorControlNormal"
    android:autoMirrored="true">
  <path
      android:fillColor="@android:color/white"
      android:pathData="M393.33,760Q297.67,760 228.83,696.33Q160,632.67 160,538.67Q160,444.67 228.83,381Q297.67,317.33 393.33,317.33L673.33,317.33L562.67,206.67L609.33,160L800,350.67L609.33,541.33L562.67,494.67L673.33,384L392.67,384Q325,384 275.83,428.33Q226.67,472.67 226.67,538.67Q226.67,604.67 275.83,649Q325,693.33 392.67,693.33L694,693.33L694,760L393.33,760Z"/>
</vector>
palette_icon.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="40dp"
    android:height="40dp"
    android:viewportWidth="960"
    android:viewportHeight="960"
    android:tint="?attr/colorControlNormal">
  <path
      android:fillColor="@android:color/white"
      android:pathData="M480,880Q398,880 325,848.5Q252,817 197.5,762.5Q143,708 111.5,635Q80,562 80,480Q80,395.67 112.17,322.67Q144.33,249.67 199.83,195.67Q255.33,141.67 329.67,110.83Q404,80 488.67,80Q568,80 639,106.83Q710,133.67 763.5,181.17Q817,228.67 848.5,293.83Q880,359 880,436Q880,546.33 814.67,608.5Q749.33,670.67 646.67,670.67L572,670.67Q557,670.67 547.17,681.67Q537.33,692.67 537.33,706Q537.33,728 552,748.17Q566.67,768.33 566.67,794.67Q566.67,836.67 543.5,858.33Q520.33,880 480,880ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM251.33,510.67Q273.33,510.67 289,495Q304.67,479.33 304.67,457.33Q304.67,435.33 289,419.67Q273.33,404 251.33,404Q229.33,404 213.67,419.67Q198,435.33 198,457.33Q198,479.33 213.67,495Q229.33,510.67 251.33,510.67ZM375.33,344Q397.33,344 413,328.33Q428.67,312.67 428.67,290.67Q428.67,268.67 413,253Q397.33,237.33 375.33,237.33Q353.33,237.33 337.67,253Q322,268.67 322,290.67Q322,312.67 337.67,328.33Q353.33,344 375.33,344ZM584.67,344Q606.67,344 622.33,328.33Q638,312.67 638,290.67Q638,268.67 622.33,253Q606.67,237.33 584.67,237.33Q562.67,237.33 547,253Q531.33,268.67 531.33,290.67Q531.33,312.67 547,328.33Q562.67,344 584.67,344ZM712,510.67Q734,510.67 749.67,495Q765.33,479.33 765.33,457.33Q765.33,435.33 749.67,419.67Q734,404 712,404Q690,404 674.33,419.67Q658.67,435.33 658.67,457.33Q658.67,479.33 674.33,495Q690,510.67 712,510.67ZM480,813.33Q490.33,813.33 495.17,808.67Q500,804 500,794.67Q500,780.67 485.33,766.33Q470.67,752 470.67,712Q470.67,667.33 500.33,635.67Q530,604 574.67,604L646.67,604Q719.33,604 766.33,561.5Q813.33,519 813.33,436Q813.33,307.67 715.83,227.17Q618.33,146.67 488.67,146.67Q346,146.67 246.33,243.33Q146.67,340 146.67,480Q146.67,618.33 244.17,715.83Q341.67,813.33 480,813.33Z"/>
</vector>
undo_icon.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="40dp"
    android:height="40dp"
    android:viewportWidth="960"
    android:viewportHeight="960"
    android:tint="?attr/colorControlNormal"
    android:autoMirrored="true">
  <path
      android:fillColor="@android:color/white"
      android:pathData="M266,760L266,693.33L567.33,693.33Q635,693.33 684.17,649Q733.33,604.67 733.33,538.67Q733.33,472.67 684.17,428.33Q635,384 567.33,384L286.67,384L397.33,494.67L350.67,541.33L160,350.67L350.67,160L397.33,206.67L286.67,317.33L566.67,317.33Q662.33,317.33 731.17,381Q800,444.67 800,538.67Q800,632.67 731.17,696.33Q662.33,760 566.67,760L266,760Z"/>
</vector>


Step 4: Working with the activity_main.xml file

Navigate to the app > res > layout > activity_main.xml and add the below code to that file. Below is the code for the activity_main.xml file. 

activity_main.xml:

XML
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <LinearLayout
        android:id="@+id/linear"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:orientation="horizontal"
            android:weightSum="4">

            <ImageButton
                android:id="@+id/btn_undo"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:backgroundTint="@color/primary_color"
                android:src="@drawable/undo_icon" />

            <ImageButton
                android:id="@+id/btn_redo"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:backgroundTint="@color/primary_color"
                android:src="@drawable/btn_redo" />

            <ImageButton
                android:id="@+id/btn_color"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:backgroundTint="@color/primary_color"
                android:src="@drawable/palette_icon" />

            <ImageButton
                android:id="@+id/btn_stroke"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:backgroundTint="@color/primary_color"
                android:src="@drawable/brush_icon" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <com.google.android.material.slider.RangeSlider
                android:id="@+id/rangebar"
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:visibility="visible" />

        </LinearLayout>

    </LinearLayout>

    <com.mihir.drawingcanvas.drawingView
        android:id="@+id/draw_view"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/linearLayout"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/linear" />

    <LinearLayout
        android:id="@+id/linearLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginBottom="8dp"
        android:orientation="horizontal"
        android:weightSum="2"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent">

        <Button
            android:id="@+id/btn_clean"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_weight="1"
            android:background="@color/black"
            android:backgroundTint="@color/primary_color"
            android:text="Clean"
            android:textColor="@color/black" />

        <Button
            android:id="@+id/btn_save"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginEnd="8dp"
            android:layout_weight="1"
            android:background="@color/black"
            android:backgroundTint="@color/primary_color"
            android:text="Save"
            android:textColor="@color/black" />
    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

Step 5: Working with the MainActivity file

Go to the MainActivity file and refer to the following code. Below is the code for the MainActivity file. Comments are added inside the code to understand the code in more detail. 

MainActivity.java
package org.geeksforgeeks.paint;

import android.Manifest;
import android.content.ContentValues;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.github.dhaval2404.colorpicker.ColorPickerDialog;
import com.github.dhaval2404.colorpicker.model.ColorShape;
import com.google.android.material.slider.RangeSlider;
import com.mihir.drawingcanvas.drawingView;

import java.io.File;
import java.io.FileOutputStream;

public class MainActivity extends AppCompatActivity {

    private drawingView draw;
    private ImageButton undo, redo, color, stroke;
    private Button clear, save;
    private RangeSlider rangeSlider;
    private static final int STORAGE_PERMISSION_CODE = 100;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Request storage permissions if not granted
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, STORAGE_PERMISSION_CODE);
        }

        // Initialize views
        draw = findViewById(R.id.draw_view);
        undo = findViewById(R.id.btn_undo);
        redo = findViewById(R.id.btn_redo);
        color = findViewById(R.id.btn_color);
        clear = findViewById(R.id.btn_clean);
        stroke = findViewById(R.id.btn_stroke);
        save = findViewById(R.id.btn_save);
        rangeSlider = findViewById(R.id.rangebar);

        // Initially hide the range slider
        rangeSlider.setVisibility(View.GONE);

        undo.setOnClickListener(v -> draw.undo());
        redo.setOnClickListener(v -> draw.redo());
        clear.setOnClickListener(v -> draw.clearDrawingBoard());

        // Toggle visibility of the brush size slider
        stroke.setOnClickListener(v -> {
            if (rangeSlider.getVisibility() == View.VISIBLE) {
                rangeSlider.setVisibility(View.GONE);
            } else {
                rangeSlider.setVisibility(View.VISIBLE);
            }
        });

        // Adjust brush size based on slider value
        rangeSlider.addOnChangeListener((slider, value, fromUser) -> 
            draw.setSizeForBrush((int) (20 * value))
        );

        // Show color picker
        color.setOnClickListener(v -> 
            ColorPickerDialog
                .Builder(this)
                .setTitle("Choose color")
                .setColorShape(ColorShape.SQAURE)
                .setDefaultColor(getResources().getColor(R.color.black))
                .setColorListener((selectedColor, colorHex) -> draw.setBrushColor(selectedColor))
                .show()
        );

        // Save drawing to storage
        save.setOnClickListener(v -> draw.post(() -> {
            Bitmap bitmap = getBitmapFromView(draw);
            Uri imageUri = saveBitmapToStorage(bitmap);

            if (imageUri != null) {
                Log.e("draw", "✅ Image saved at: " + imageUri);
                Toast.makeText(this, "Image saved!", Toast.LENGTH_SHORT).show();
                shareImage(imageUri);
            } else {
                Log.e("draw", "❌ Saving failed!");
                Toast.makeText(this, "Failed to save image", Toast.LENGTH_SHORT).show();
            }
        }));
    }

    private Bitmap getBitmapFromView(View view) {
        Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        if (view.getBackground() != null) {
            view.getBackground().draw(canvas);
        } else {
            canvas.drawColor(Color.WHITE);
        }
        view.draw(canvas);
        return bitmap;
    }

    private Uri saveBitmapToStorage(Bitmap bitmap) {
        String filename = "Drawing_" + System.currentTimeMillis() + ".png";
        Uri uri = null;

        try {
            File imagesDir = new File(getExternalFilesDir(null) + "/Pictures/DrawingApp");

            if (!imagesDir.exists()) {
                imagesDir.mkdirs(); // Create folder if not exists
            }

            File imageFile = new File(imagesDir, filename);
            FileOutputStream outputStream = new FileOutputStream(imageFile);
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
            outputStream.flush();
            outputStream.close();

            // Update gallery
            MediaStore.Images.Media.insertImage(getContentResolver(), imageFile.getAbsolutePath(), filename, null);
            uri = Uri.fromFile(imageFile);

        } catch (Exception e) {
            Log.e("draw", "❌ Error saving image: " + e.getMessage());
            e.printStackTrace();
        }

        return uri;
    }

    private void shareImage(Uri uri) {
        Intent shareIntent = new Intent(Intent.ACTION_SEND);
        shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
        shareIntent.setType("image/png");
        startActivity(Intent.createChooser(shareIntent, "Share via"));
    }
}
MainActivity.kt
package org.geeksforgeeks.paint

import android.content.ContentValues
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.ImageButton
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.github.dhaval2404.colorpicker.ColorPickerDialog
import com.github.dhaval2404.colorpicker.model.ColorShape
import com.google.android.material.slider.RangeSlider
import com.mihir.drawingcanvas.drawingView
import android.Manifest
import android.widget.Toast


class MainActivity : AppCompatActivity() {
    private lateinit var draw: drawingView
    private lateinit var undo: ImageButton
    private lateinit var redo: ImageButton
    private lateinit var color: ImageButton
    private lateinit var stroke: ImageButton
    private lateinit var clear: Button
    private lateinit var save: Button
    private lateinit var rangeSlider: RangeSlider

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Check & request storage permission
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
            != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,
                arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 100)
        }

        draw = findViewById(R.id.draw_view)
        undo = findViewById(R.id.btn_undo)
        redo = findViewById(R.id.btn_redo)
        color = findViewById(R.id.btn_color)
        clear = findViewById(R.id.btn_clean)
        stroke = findViewById(R.id.btn_stroke)
        save = findViewById(R.id.btn_save)
        rangeSlider = findViewById(R.id.rangebar)

        // Set initial visibility of the rangeSlider to GONE
        rangeSlider.visibility = View.GONE

        undo.setOnClickListener { draw.undo() }

        clear.setOnClickListener { draw.clearDrawingBoard() }

        redo.setOnClickListener { draw.redo() }

        stroke.setOnClickListener {
            // Toggle visibility of the rangeSlider
            if (rangeSlider.visibility == View.VISIBLE) {
                rangeSlider.visibility = View.GONE
            } else {
                rangeSlider.visibility = View.VISIBLE
            }
        }

        // Set up the RangeSlider listener to change brush size
        rangeSlider.addOnChangeListener(RangeSlider.OnChangeListener { slider: RangeSlider?, value: Float, fromUser: Boolean ->
            // Set brush size based on slider value
            draw.setSizeForBrush((20 * value).toInt())
        })

        color.setOnClickListener {
            ColorPickerDialog
                .Builder(this)
                .setTitle("Choose color")
                .setColorShape(ColorShape.SQAURE)
                .setDefaultColor(R.color.black)
                .setColorListener { color, colorHex ->
                    draw.setBrushColor(color)
                }
                .show()
        }

        save.setOnClickListener {
            draw.post {
                val bitmap = getBitmapFromView(draw)
                val imageUri = saveBitmapToStorage(bitmap)

                if (imageUri != null) {
                    println("Image saved at: $imageUri")
                } else {
                    println("Saving failed!")
                }
            }
        }
    }

    private fun getBitmapFromView(view: View): Bitmap {
        val returnedBitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
        val canvas = Canvas(returnedBitmap)
        val bgDrawable = view.background
        if (bgDrawable != null) {
            bgDrawable.draw(canvas)
        } else {
            canvas.drawColor(Color.WHITE)
        }
        view.draw(canvas)
        return returnedBitmap
    }

    private fun saveBitmapToStorage(bitmap: Bitmap): Uri? {
        val filename = "Drawing_${System.currentTimeMillis()}.png"
        var uri: Uri? = null

        try {
            val imagesDir = getExternalFilesDir(null)?.absolutePath + "/Pictures/DrawingApp"
            val file = java.io.File(imagesDir)

            if (!file.exists()) {
                file.mkdirs() // Create folder if it doesn't exist
            }

            val imageFile = java.io.File(file, filename)
            val outputStream = java.io.FileOutputStream(imageFile)
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
            outputStream.flush()
            outputStream.close()

            // Tell the gallery to update
            MediaStore.Images.Media.insertImage(contentResolver, imageFile.absolutePath, filename, null)
            uri = Uri.fromFile(imageFile)

            Log.e("draw", "✅ Image saved successfully at: $uri")
            Toast.makeText(this, "Image saved in Gallery", Toast.LENGTH_SHORT).show()

        } catch (e: Exception) {
            Log.e("draw", "❌ Failed to save image: ${e.message}")
            Toast.makeText(this, "Failed to save image", Toast.LENGTH_SHORT).show()
            e.printStackTrace()
        }

        return uri
    }


    private fun shareImage(uri: Uri) {
        Toast.makeText(this, "Sharing image URI: $uri", Toast.LENGTH_SHORT).show()
        val shareIntent = Intent().apply {
            action = Intent.ACTION_SEND
            putExtra(Intent.EXTRA_STREAM, uri)
            type = "image/png"
        }
        startActivity(Intent.createChooser(shareIntent, "Share via"))
    }
}

Output:

Refer to this github repository to get the entire code.

Future Scope

There are plenty of things you can add to this project like:- 

  1. Adding a mask to the painted object, i.e. creating a blur or emboss effect on the stroke.
  2. Adding animations to the app.
  3. Adding a color selector for canvas, i.e. changing the color of canvas from the default White color as per the user requirement.
  4. Adding a sharing button, to directly share the drawing on various apps.
  5. Adding an eraser functionality that clears the specific path/stroke on which the eraser is dragged.
  6. Adding a shape picker, by which a user can directly select any particular shape from the list and can drag on the screen to create that shape.
  7. Enhancing UI, by adding a BottomSheet, vectors, etc.

“Anyone can put paint on a canvas, but only a true master can bring the painting to life.”, we finish building our app, now draw some awesome paintings on this canvas and become a “true master”. 



Next Article

Similar Reads