Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
9c51a50
renamed "app" to "app-sample"
Abizer-R Sep 13, 2025
b4cd9d2
Introduced empty module "quickedit-core-engine"
Abizer-R Sep 13, 2025
fd1d9c0
Introduced empty module "quickedit-ui"
Abizer-R Sep 13, 2025
eb436f4
Introduced empty module "quickedit-tool-draw"
Abizer-R Sep 13, 2025
9e57682
Introduced empty module "quickedit-tool-text"
Abizer-R Sep 13, 2025
3f6f8fc
Introduced empty module "quickedit-tool-crop"
Abizer-R Sep 13, 2025
dfe7dfb
Introduced empty module "quickedit-tool-effects"
Abizer-R Sep 13, 2025
f9f12f8
replaced java-11 with java-17 in all new modules
Abizer-R Sep 13, 2025
56cd350
Added skeleton for quickedit-core-engine module
Abizer-R Sep 13, 2025
7b6c840
Added UI API shell and placeholders for quickedit-ui module
Abizer-R Sep 13, 2025
46f3ffd
Added skeleton placeholder for tools modules (crop, draw, effects and…
Abizer-R Sep 13, 2025
799a1d8
Replace the body of QuickEditApp() with a facade to the new editor
Abizer-R Sep 13, 2025
ec19753
Add Engine APIs & minimal Impl
Abizer-R Sep 13, 2025
818beb2
Added comments for possible future additions in current engine APIs
Abizer-R Sep 13, 2025
67713f4
Fix indentation
Abizer-R Sep 13, 2025
f35523f
Replace UI placeholders with engine types
Abizer-R Sep 13, 2025
4ad16a9
Re-export "quickedit-core-engine" from "quickedit-ui" module so that …
Abizer-R Sep 13, 2025
c8ad10f
Adapt facade to map Uri? to EditImage? in order to support latest cha…
Abizer-R Sep 13, 2025
3132ad7
Added coroutines (core & android) for core-engine and ui modules
Abizer-R Sep 13, 2025
1f87887
Extend RenderResult to carry preview image
Abizer-R Sep 13, 2025
f71c1be
Implement decode, render, save in DefaultEditEngine
Abizer-R Sep 14, 2025
5f03a83
fix dependency resolution issue for test classes in quickedit-ui module
Abizer-R Sep 14, 2025
694378f
UI shell: live preview + simple Save + job cancellation using keys in…
Abizer-R Sep 14, 2025
0196b77
Add BottomBar + animated Panel switching to QuickEditEditor. Also, pa…
Abizer-R Sep 14, 2025
1834190
Give Proper Icon to tools (Crop, Draw, Text, Effects)
Abizer-R Sep 14, 2025
254d4f2
Moved AnimUtils.kt and AnimatedToolbarContainer.kt from "quickedit" m…
Abizer-R Sep 14, 2025
ab875c4
Added a dummy initial Bitmap for temporary development purposes
Abizer-R Sep 14, 2025
0bd6203
Modify ToolContribution.kt to support FullScreenTool.
Abizer-R Sep 14, 2025
19e53c5
Modify QuickEditEditor.kt (shell) to support smooth toolbar slide-in …
Abizer-R Sep 14, 2025
a8d3f05
Add Crop Tool UI in the tool-crop module
Abizer-R Sep 14, 2025
fae145e
Handle actual cropping logic using canhub's CropImageView
Abizer-R Sep 14, 2025
172e9da
Skip preview rendering in QuickEditEditor.kt when a tool is active to…
Abizer-R Sep 14, 2025
407ce05
Moved CommonExtensions.kt into "quickedit-ui" module
Abizer-R Sep 14, 2025
a43c9d4
Added strings in newer modules
Abizer-R Sep 14, 2025
095d988
Moved CropperOption.kt, CropperOptionsFullWidth.kt and AspectRatioDia…
Abizer-R Sep 14, 2025
205e4a8
Move theme files from "quickedit" to the new "quickedit-ui" module. A…
Abizer-R Sep 14, 2025
01b1d1a
Bring UI of crop tool in new module (CropContribution.kt) similar to …
Abizer-R Sep 14, 2025
88a610e
Default only to DarkScheme
Abizer-R Sep 15, 2025
a412e2f
Extracted CropToolScreen.kt from CropContribution.kt
Abizer-R Sep 15, 2025
52ec657
Introduced PreviewUtils.kt for easing preview generations by centrali…
Abizer-R Sep 15, 2025
eb660b8
Added default text color in QuickEditTheme{}
Abizer-R Sep 15, 2025
94e0e0c
Added Previews in newer screens
Abizer-R Sep 15, 2025
ac2c25e
Replicate UI of EditorScreen toolbar in QuickEditEditor.kt
Abizer-R Sep 15, 2025
41483a8
Use IconButton in CropToolScreen's TopToolbar
Abizer-R Sep 15, 2025
10f0d3b
Changes & new flow:
Abizer-R Sep 20, 2025
56eaf3b
Remove redundant module import (":quickedit-ui" already re-exports "c…
Abizer-R Sep 20, 2025
d6aef39
Add a minimal DrawToolContribution.kt extending ToolContributionWithF…
Abizer-R Sep 20, 2025
4ec09ba
Move DrawToolSession.kt in proper package
Abizer-R Sep 20, 2025
1988278
Moved models into draw-tool module
Abizer-R Sep 20, 2025
1891def
Use DrawMode specific classes for legacy DrawModeScreen.kt
Abizer-R Sep 20, 2025
31d5803
emit crop-rectangle and crop the current image in the DefaultEditEngi…
Abizer-R Sep 20, 2025
d7ae909
Introduced draw tool specific Edit operations
Abizer-R Sep 20, 2025
29a6401
Introduced DrawShape operation in EditOp. Also a ShapeSpec.kt (shared…
Abizer-R Sep 21, 2025
915355e
Reuse drawing logic from "engine-core" module in "tool-draw"
Abizer-R Sep 21, 2025
06aad24
fix import for ShapeType.kt (as it was moved)
Abizer-R Sep 21, 2025
b7a2670
Use ShapeSpec in shape implementations of "tool-draw" (BrushShape, Li…
Abizer-R Sep 21, 2025
8efaa32
Update README.md
Abizer-R Sep 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
.DS_Store
/build
/captures
/app/release
/app-sample/release
/keystoreDetails
.externalNativeBuild
.cxx
Expand Down
74 changes: 59 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
# <img src="https://github.com/user-attachments/assets/8ed6acde-46c9-43e0-a68c-905bec182234" align="center" width="60" height="60"> QuickEdit - Photo Editor
QuickEdit is a user-friendly photo editor for Android, built using **Jetpack Compose**. It offers essential photo editing tools with a clean and smooth interface.

## Latest Release

[![Release v1.1.0](https://img.shields.io/github/v/release/Abizer-R/QuickEdit-Photo-Editor)](https://github.com/Abizer-R/QuickEdit-Photo-Editor/releases/tag/v1.1.0-4)

- [Download from the Google Play Store](https://play.google.com/store/apps/details?id=com.abizer_r.quickedit)
- [Download Apk (v1.1.0)](https://github.com/Abizer-R/QuickEdit-Photo-Editor/releases/download/v1.1.0-4/app-release.apk)


## [Click Here To Watch Demo Video](https://drive.google.com/file/d/18IipYR_jbUQVFL8U1jNEJd_KTm_Y9ije/view?usp=sharing)
## ⚠️ Partial Readme (Will be updated after draw tool is completed)
---
# <img src="https://github.com/user-attachments/assets/8ed6acde-46c9-43e0-a68c-905bec182234" align="center" width="60" height="60"> QuickEdit - Photo Editor (Compose, Modular, Pluggable)

[![Release](https://img.shields.io/github/v/release/Abizer-R/QuickEdit-Photo-Editor)](https://github.com/Abizer-R/QuickEdit-Photo-Editor/releases)
[![Min SDK](https://img.shields.io/badge/minSdk-24-blue)](#)
[![Target SDK](https://img.shields.io/badge/targetSdk-35-blue)](#)
[![License](https://img.shields.io/badge/license-Apache--2.0-green)](#license)

**QuickEdit** is a modular, pluggable photo editor for Android with a **stable core engine**, a **Compose UI shell**, and **tool plugins** (Crop ✅, Draw/Text/Effects scaffolds).
It preserves legacy UX (slide-out editor bars → full-screen tools) while cleaning layering, performance, and testability.

- 📱 **Play Store:** [Install](https://play.google.com/store/apps/details?id=com.abizer_r.quickedit)
- 📦 **Latest APK:** [Releases](https://github.com/Abizer-R/QuickEdit-Photo-Editor/releases)

<div style="display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-start;">
<img src="https://github.com/user-attachments/assets/78217ce9-4771-4db0-9dae-345214eb28f1" alt="1" width="200" />
Expand All @@ -35,16 +34,61 @@ QuickEdit is a user-friendly photo editor for Android, built using **Jetpack Com
- **Add Text**: Add text with option to customize fonts and format (bold, italic and more).
- **Smooth Animations**: Enjoy a seamless experience thanks to Jetpack Compose.

---

## Architecture (v0.x)

**Goal:** a reusable editor published as Maven artifacts with a stable engine API and pluggable tools.
**Status:** engine + shell done; Crop tool delivered; Draw in progress; Effects & Text planned.

## Modules & Dependency Rules

```
app-sample/ # Host app; exercises the library
quickedit/ # Legacy façade (keeps old entrypoints stable)
quickedit-core-engine/ # Engine API + default impl (no Compose/UI deps)
quickedit-compose-ui/ # Shell + tool contracts (Compose UI, no tool logic)
quickedit-tool-crop/ # Crop tool (full-screen)
quickedit-tool-draw/ # (ongoing)
quickedit-tool-text/ # (planned)
quickedit-tool-effects/ # (planned)
```

### **Dependency graph (enforced):**
```
app-sample ──► quickedit (legacy)
├──► quickedit-compose-ui ──api──► quickedit-core-engine
└──► quickedit-tool-*
quickedit-tool-* ──► quickedit-compose-ui
quickedit-core-engine (Android framework only; no UI)
```

**Why `api(..)` from UI → Engine?** The UI public API mentions engine types (`EditImage`, `SaveFormat`). Re-exporting via `api(project(":quickedit-core-engine"))` makes those types visible to consumers cleanly.


## Tech Stack & Versions (from `libs.versions.toml`)

- **Android:** minSdk 24 · target/compile 35 · JDK 17
- **Gradle/AGP:** 8.6.1
- **Kotlin:** 2.1.10 (+ Compose plugin)
- **Compose BOM:** 2025.05.00
- **Compose libs:** Material3, Animation `1.7.0-beta04`, UI/Test/Tooling
- **AndroidX:** Activity Compose 1.9.0 · Lifecycle 2.8.2 · Navigation 2.7.7 · AppCompat 1.7.0 · Core-ktx 1.13.1
- **DI:** Hilt 2.57.1 (with hilt-navigation-compose 1.2.0)
- **Coroutines:** 1.10.1
- **Imaging:** GPUImage 2.1.0 · CanHub Cropper 4.5.0 · Compose-Screenshot 1.0.3
- **UX Utils:** Cloudy 0.2.7 · ColorPicker 1.0.0
- **Testing:** JUnit 4.13.2 · AndroidX JUnit 1.2.0 · Espresso 3.6.0

---

## Libraries Used

QuickEdit makes use of the following libraries to provide its features:

- **Jetpack Compose**: A modern toolkit for building native Android UI.
- **Compose Animations**: For smooth and customizable UI animations.
- **[GPUImage](https://github.com/CyberAgent/android-gpuimage)**: A library for GPU-based image processing by CyberAgent.
- **[Cloudy](https://github.com/skydoves/cloudy)**: A library by Skydoves for blurring a composable.
- **[Image Cropper](https://github.com/CanHub/Android-Image-Cropper)**: A cropping library by Canhub that allows users to crop images seamlessly.
- **[Compose-Screenshot](https://github.com/SmartToolFactory/Compose-Screenshot)**: A library by SmartToolFactory for capturing screenshots of composables in Jetpack Compose.


# License
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
3 changes: 3 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ lifecycleRuntimeKtx = "2.8.2"
material = "1.12.0"
navigationCompose = "2.7.7"
composeAnimation = "1.7.0-beta04"
coroutines = "1.10.1"


[libraries]
Expand Down Expand Up @@ -53,6 +54,8 @@ hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltA
hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hiltAndroid" }
junit = { module = "junit:junit", version.ref = "junit" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
material = { module = "com.google.android.material:material", version.ref = "material" }

[plugins]
Expand Down
1 change: 1 addition & 0 deletions quickedit-core-engine/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
48 changes: 48 additions & 0 deletions quickedit-core-engine/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
}

android {
namespace = "io.github.abizerr.quickedit.engine"
compileSdk = 35

defaultConfig {
minSdk = 24

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
}

dependencies {

implementation(libs.androidx.core.ktx)
implementation(libs.kotlinx.coroutines.core)
// implementation(libs.androidx.appcompat)
// implementation(libs.material)
// testImplementation(libs.junit)
// androidTestImplementation(libs.androidx.junit)
// androidTestImplementation(libs.androidx.espresso.core)

testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
}
Empty file.
21 changes: 21 additions & 0 deletions quickedit-core-engine/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.github.abizerr.quickedit.engine

import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4

import org.junit.Test
import org.junit.runner.RunWith

import org.junit.Assert.*

/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("io.github.abizerr.quickedit.engine.test", appContext.packageName)
}
}
5 changes: 5 additions & 0 deletions quickedit-core-engine/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.github.abizerr.quickedit.engine">

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.github.abizerr.quickedit.engine.api

interface EditEngine {
suspend fun newSession(image: EditImage): EditSnapshot
suspend fun apply(op: EditOp): EditSnapshot
suspend fun render(snapshot: EditSnapshot, size: Size): RenderResult
suspend fun save(snapshot: EditSnapshot, format: SaveFormat): Result<EditedImage>
val history: HistoryView
/**
* FUTURE-1: Capabilities & queries (non-breaking):
* fun supports(op: kotlin.reflect.KClass<out EditOp>): Boolean = true
* val maxCanvasSize: Size get() = Size(8192, 8192)
* FUTURE-2: Observability hooks:
* var events: ((EngineEvent) -> Unit)?
*/
}

interface HistoryView {
val canUndo: Boolean
val canRedo: Boolean
val undoCount: Int
val redoCount: Int
/**
* FUTURE: Expose memory footprint if required
* val approxMemoryBytes: Long get() = 0
*/
}


/**
* FUTURE: Emit non-fatal diagnostics without throwing:
* sealed interface EngineEvent {
* data class OpLatency(val op: EditOp, val ms: Long): EngineEvent
* data class RenderDownscaled(val requested: Size, val actual: Size): EngineEvent
* data class OOMPrevented(val reducedHistory: Int): EngineEvent
* }
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package io.github.abizerr.quickedit.engine.api

import android.graphics.Bitmap


/** A lightweight reference to the image the editor works on. */
sealed interface EditImage {
data class FromUri(val uri: android.net.Uri) : EditImage
data class FromBitmap(val bitmap: android.graphics.Bitmap) : EditImage
/**
* FUTURE: Possible additions without breaking callers:
* example: FromBytes, FromFile, FromHardwareBuffer, ContentProvider, etc.
*/
}

/** Immutable snapshot of the editing graph at a point in time. */
data class EditSnapshot(
val image: EditImage,
val rev: Long = 0L,
val operations: List<EditOp> = emptyList()
/**
* FUTURE: Add graph states without breaking callers:
* example: Layers, Selection, Metadata
*/
)

/** Output from save(). You can extend later with Uri/bytes/metadata. */
data class EditedImage(
val mimeType: String,
val bytes: ByteArray? = null
/**
* FUTURE: Add destination/EXIF without breaking callers:
* example: savedFile: android.net.Uri, exif: Map<String, String>
*/
)

/** Sizes used by render() */
data class Size(val width: Int, val height: Int)

/** Render output placeholder (Phase 5 will hold a Bitmap or ImageBitmap). */
data class RenderResult(
val ok: Boolean,
val preview: Bitmap?
)
/**
* FUTURE: Add preview payloads without chaning the signature of render() in EditEngine interface:
* example: bitmap, downscaleFactor, etc.
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.github.abizerr.quickedit.engine.api

import android.graphics.Rect
import android.graphics.RectF
import io.github.abizerr.quickedit.engine.drawspec.ShapeSpec

/** Minimum set; expand in Phase 6 with tool packs. */
sealed interface EditOp {
// data class ApplyCurve(val presetId: String) : EditOp
data class CropImage(val rect: Rect) : EditOp
// data class InsertText(val text: String, val x: Float, val y: Float) : EditOp
data object Undo : EditOp
data object Redo : EditOp

data class DrawShape(val spec: ShapeSpec)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.github.abizerr.quickedit.engine.api

sealed interface SaveFormat {
data class Png(val lossless: Boolean = true) : SaveFormat
data class Jpeg(val quality: Int = 90) : SaveFormat
data class WebP(val lossless: Boolean = false, val quality: Int = 90) : SaveFormat
/**
* FUTURE: Add new Codecs additively
* example: Heif, Avif
*/
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.github.abizerr.quickedit.engine.drawspec

data class PaintSpec(
val argb: Int,
val alpha: Float,
val widthPx: Float,
val isEraser: Boolean
)

fun ShapeSpec.toPaintSpec(): PaintSpec = PaintSpec(
argb = this.argb,
alpha = this.alpha.coerceIn(0f,1f),
widthPx = this.widthPx.coerceAtLeast(0.5f),
isEraser = this.isEraser
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.github.abizerr.quickedit.engine.drawspec

import androidx.annotation.FloatRange

sealed interface ShapeSpec {

companion object {
val defaultOffset = Pair(Float.NaN, Float.NaN)
}
val argb: Int
val alpha: Float // 0f..1f
val widthPx: Float
val isEraser: Boolean get() = false

data class Brush(
val points: MutableList<Pair<Float, Float>> = mutableListOf(),
override val argb: Int,
@FloatRange(from = 0.0, to = 1.0) override val alpha: Float,
override val widthPx: Float,
override val isEraser: Boolean = false
) : ShapeSpec

data class Shape(
val shapeType: ShapeType,
var startOffset: Pair<Float, Float> = defaultOffset,
var endOffset: Pair<Float, Float> = defaultOffset,
override val argb: Int,
@FloatRange(from = 0.0, to = 1.0) override val alpha: Float,
override val widthPx: Float,
) : ShapeSpec
}
Loading