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 Kotlin, Android 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:
- GPU-Accelerated Filters: Uses
GPUImage
for real-time filter rendering (e.g., Sepia, Grayscale). - Adjustments: Slider-based controls for brightness, contrast, etc.
- Undo Functionality: Stack-based history for reverting edits.
- Image Loading: Load images from the gallery via
Intent
.
5. Advanced Enhancements:
- Layers: Use
Canvas
andBitmap
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! 🚀