Creating a photo editing Android app requires the integration of complex features like filters, adjustments, layers, and advanced image processing. Below is a simplified example using KotlinAndroid Jetpack Components, and popular libraries like GPUImage for real-time filters. This example demonstrates core functionalities.

1. Setup Dependencies (build.gradle)

dependencies {
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.github.bumptech.glide:glide:4.12.0'
    implementation 'jp.co.cyberagent.android:gpuimage:2.1.0' // For GPU-accelerated filters
    implementation 'com.google.android.material:material:1.9.0' // For sliders and UI components
}

2. XML Layout (activity_photo_editor.xml)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- Image Preview -->
    <ImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerInside" />

    <!-- Editing Tools Panel -->
    <HorizontalScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true">

        <LinearLayout
            android:id="@+id/toolsLayout"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:padding="8dp">

            <Button
                android:id="@+id/btnFilters"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Filters" />

            <Button
                android:id="@+id/btnAdjust"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Adjust" />

            <Button
                android:id="@+id/btnCrop"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Crop" />

            <Button
                android:id="@+id/btnUndo"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Undo" />
        </LinearLayout>
    </HorizontalScrollView>

    <!-- Slider for Adjustments -->
    <com.google.android.material.slider.Slider
        android:id="@+id/adjustmentSlider"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@id/toolsLayout"
        android:visibility="gone"
        app:thumbRadius="8dp"
        app:trackHeight="4dp" />
</RelativeLayout>

3. Kotlin Code (PhotoEditorActivity.kt)

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.view.View
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.slider.Slider
import jp.co.cyberagent.android.gpuimage.GPUImage
import jp.co.cyberagent.android.gpuimage.filter.GPUImageBrightnessFilter
import jp.co.cyberagent.android.gpuimage.filter.GPUImageContrastFilter
import java.util.Stack

class PhotoEditorActivity : AppCompatActivity() {

    private lateinit var imageView: ImageView
    private lateinit var adjustmentSlider: Slider
    private lateinit var gpuImage: GPUImage
    private lateinit var originalBitmap: Bitmap
    private val editHistory = Stack<Bitmap>()

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

        imageView = findViewById(R.id.imageView)
        adjustmentSlider = findViewById(R.id.adjustmentSlider)
        gpuImage = GPUImage(this)

        // Load image from intent (e.g., gallery)
        val imageUri = intent.getParcelableExtra<Uri>("imageUri")
        originalBitmap = MediaStore.Images.Media.getBitmap(contentResolver, imageUri)
        imageView.setImageBitmap(originalBitmap)
        editHistory.push(originalBitmap.copy(originalBitmap.config, true))

        setupButtons()
    }

    private fun setupButtons() {
        // Filter Button
        findViewById<View>(R.id.btnFilters).setOnClickListener {
            showFilterOptions()
        }

        // Adjust Button
        findViewById<View>(R.id.btnAdjust).setOnClickListener {
            showAdjustmentOptions()
        }

        // Undo Button
        findViewById<View>(R.id.btnUndo).setOnClickListener {
            if (editHistory.size > 1) {
                editHistory.pop() // Remove current state
                imageView.setImageBitmap(editHistory.peek())
            }
        }

        // Slider Listener
        adjustmentSlider.addOnChangeListener { _, value, _ ->
            applyAdjustment(value)
        }
    }

    // Show filter options (simplified)
    private fun showFilterOptions() {
        val filters = listOf("Sepia", "Grayscale", "Vignette")
        // Open a dialog or bottom sheet to select filters
        // Example: Apply Sepia filter
        gpuImage.setFilter(GPUImageSepiaToneFilter())
        applyGPUFilter()
    }

    // Show adjustment options (brightness, contrast, etc.)
    private fun showAdjustmentOptions() {
        adjustmentSlider.visibility = View.VISIBLE
        adjustmentSlider.value = 0.5f // Reset slider
        // For demo, adjust brightness
        gpuImage.setFilter(GPUImageBrightnessFilter())
    }

    // Apply adjustment using slider value
    private fun applyAdjustment(value: Float) {
        when (gpuImage.filter) {
            is GPUImageBrightnessFilter -> {
                (gpuImage.filter as GPUImageBrightnessFilter).setBrightness(value * 2 - 1) // Range [-1, 1]
            }
            is GPUImageContrastFilter -> {
                (gpuImage.filter as GPUImageContrastFilter).setContrast(value * 2) // Range [1-4]
            }
        }
        applyGPUFilter()
    }

    // Apply GPUImage filter and update ImageView
    private fun applyGPUFilter() {
        val filteredBitmap = gpuImage.getBitmapWithFilterApplied(originalBitmap)
        imageView.setImageBitmap(filteredBitmap)
        editHistory.push(filteredBitmap.copy(filteredBitmap.config, true))
    }

    // Save edited image
    private fun saveImage() {
        val editedBitmap = (imageView.drawable as BitmapDrawable).bitmap
        MediaStore.Images.Media.insertImage(
            contentResolver,
            editedBitmap,
            "Edited_Image",
            "Edited with Photo Editor"
        )
    }
}

4. Key Features:

  1. GPU-Accelerated Filters: Uses GPUImage for real-time filter rendering (e.g., Sepia, Grayscale).
  2. Adjustments: Slider-based controls for brightness, contrast, etc.
  3. Undo Functionality: Stack-based history for reverting edits.
  4. Image Loading: Load images from the gallery via Intent.

5. Advanced Enhancements:

  • Layers: Use Canvas and Bitmap to implement layer-based editing.
  • Face Detection: Integrate ML Kit for face detection and beautification.
  • Custom Filters: Create custom OpenGL shaders.
  • Export Options: Save in JPEG/PNG, share to social media.
  • Performance: Optimize with background threads and caching.

6. Sample Code for Face Detection (using ML Kit):

Add to build.gradle:

implementation 'com.google.mlkit:face-detection:16.1.5'

In PhotoEditorActivity:

private fun detectFaces(bitmap: Bitmap) {
    val faceDetector = FaceDetection.getClient()
    val image = InputImage.fromBitmap(bitmap, 0)
    faceDetector.process(image)
        .addOnSuccessListener { faces ->
            // Highlight detected faces on the image
        }
        .addOnFailureListener { e ->
            // Handle error
        }
}

This is a foundational example. For a production app, consider adding error handling, performance optimizations, and more advanced features like curves, HSL adjustments, or AI-based enhancements. Let me know if you’d like to dive deeper into any component! 🚀

Rate This Post: