Skip to content

Commit 62d1525

Browse files
committed
We removed GPUImage (and its libyuv-decoder.so) and replaced it with a native-free pipeline:
1. Parse .acv files → build 256-entry LUTs per RGB channel. 2. Apply LUTs to a bitmap on CPU (parallelized + tiled options). 3. Optional real-time GPU preview via AGSL (Android 13+) using tiny 256×1 LUT textures — still no native libs. EffectsModeUtils* now scales first, caches curves/LUTs, and emits previews in batches—API compatible with the UI.
1 parent 8ea8674 commit 62d1525

5 files changed

Lines changed: 151 additions & 84 deletions

File tree

quickedit/src/main/java/com/abizer_r/quickedit/ui/effectsMode/EffectsModeScreen.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,7 @@ import com.abizer_r.quickedit.ui.editorScreen.bottomToolbar.TOOLBAR_HEIGHT_SMALL
4848
import com.abizer_r.quickedit.ui.editorScreen.topToolbar.TextModeTopToolbar
4949
import com.abizer_r.quickedit.ui.effectsMode.effectsPreview.EffectItem
5050
import com.abizer_r.quickedit.ui.effectsMode.effectsPreview.EffectsPreviewListFullWidth
51-
import com.abizer_r.quickedit.utils.SharedTransitionPreviewExtension
52-
import com.abizer_r.quickedit.utils.editorScreen.EffectsModeUtils
51+
import com.abizer_r.quickedit.utils.effectsMode.EffectsModeUtils
5352
import com.abizer_r.quickedit.utils.other.anim.AnimUtils
5453
import com.abizer_r.quickedit.utils.other.bitmap.ImmutableBitmap
5554
import com.smarttoolfactory.screenshot.ImageResult
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.abizer_r.quickedit.utils.effectsMode
2+
3+
import java.io.InputStream
4+
import kotlin.math.roundToInt
5+
6+
object AcvToneCurveParser {
7+
data class Curve(val r: List<Pair<Float, Float>>, val g: List<Pair<Float, Float>>, val b: List<Pair<Float, Float>>)
8+
9+
fun parse(input: InputStream): Curve {
10+
val data = input.readBytes()
11+
var offset = 0
12+
13+
fun readShort(): Int {
14+
val result = ((data[offset].toInt() and 0xFF) shl 8) or (data[offset + 1].toInt() and 0xFF)
15+
offset += 2
16+
return result
17+
}
18+
19+
readShort() // version
20+
val totalCurves = readShort()
21+
22+
val curves = mutableListOf<List<Pair<Float, Float>>>()
23+
repeat(totalCurves) {
24+
val pointCount = readShort()
25+
val points = List(pointCount) {
26+
val y = readShort().toFloat()
27+
val x = readShort().toFloat()
28+
Pair(x / 255f, y / 255f)
29+
}.sortedBy { it.first }
30+
curves.add(points)
31+
}
32+
33+
return Curve(
34+
r = curves.getOrNull(1) ?: curves[0],
35+
g = curves.getOrNull(2) ?: curves[0],
36+
b = curves.getOrNull(3) ?: curves[0]
37+
)
38+
}
39+
40+
fun interpolate(points: List<Pair<Float, Float>>): IntArray {
41+
val lut = IntArray(256)
42+
for (i in 0..255) {
43+
val x = i / 255f
44+
val p = points.zipWithNext().firstOrNull { x >= it.first.first && x <= it.second.first }
45+
val value = when {
46+
p != null -> {
47+
val (x1, y1) = p.first
48+
val (x2, y2) = p.second
49+
y1 + (x - x1) * (y2 - y1) / (x2 - x1)
50+
}
51+
x <= points.first().first -> points.first().second
52+
else -> points.last().second
53+
}
54+
lut[i] = (value.coerceIn(0f, 1f) * 255).roundToInt()
55+
}
56+
return lut
57+
}
58+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.abizer_r.quickedit.utils.effectsMode
2+
3+
import android.graphics.Bitmap
4+
import android.graphics.Canvas
5+
import android.graphics.ColorMatrix
6+
import android.graphics.ColorMatrixColorFilter
7+
import android.graphics.Paint
8+
9+
object BitmapGrayscaleFilter {
10+
fun apply(original: Bitmap): Bitmap {
11+
val result = Bitmap.createBitmap(original.width, original.height, Bitmap.Config.ARGB_8888)
12+
val canvas = Canvas(result)
13+
val matrix = ColorMatrix().apply { setSaturation(0f) }
14+
val paint = Paint().apply { colorFilter = ColorMatrixColorFilter(matrix) }
15+
canvas.drawBitmap(original, 0f, 0f, paint)
16+
return result
17+
}
18+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.abizer_r.quickedit.utils.effectsMode
2+
3+
import android.graphics.Bitmap
4+
5+
object BitmapToneCurveFilter {
6+
fun apply(original: Bitmap, curve: AcvToneCurveParser.Curve): Bitmap {
7+
val rLut = AcvToneCurveParser.interpolate(curve.r)
8+
val gLut = AcvToneCurveParser.interpolate(curve.g)
9+
val bLut = AcvToneCurveParser.interpolate(curve.b)
10+
11+
val w = original.width
12+
val h = original.height
13+
val result = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
14+
15+
val pixels = IntArray(w * h)
16+
original.getPixels(pixels, 0, w, 0, 0, w, h)
17+
18+
for (i in pixels.indices) {
19+
val c = pixels[i]
20+
val a = c ushr 24
21+
val r = rLut[(c shr 16) and 0xFF]
22+
val g = gLut[(c shr 8) and 0xFF]
23+
val b = bLut[c and 0xFF]
24+
pixels[i] = (a shl 24) or (r shl 16) or (g shl 8) or b
25+
}
26+
27+
result.setPixels(pixels, 0, w, 0, 0, w, h)
28+
return result
29+
}
30+
}
Lines changed: 44 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,46 @@
1-
package com.abizer_r.quickedit.utils.editorScreen
1+
package com.abizer_r.quickedit.utils.effectsMode
22

33
import android.content.Context
44
import android.graphics.Bitmap
55
import com.abizer_r.quickedit.ui.effectsMode.effectsPreview.EffectItem
66
import com.abizer_r.quickedit.utils.AppUtils
7-
import jp.co.cyberagent.android.gpuimage.GPUImage
8-
import jp.co.cyberagent.android.gpuimage.filter.GPUImageGrayscaleFilter
9-
import jp.co.cyberagent.android.gpuimage.filter.GPUImageToneCurveFilter
107
import kotlinx.coroutines.flow.flow
118

129
object EffectsModeUtils {
1310

1411
fun getEffectsPreviewList(
15-
context: Context,
16-
bitmap: Bitmap,
12+
context: Context,
13+
bitmap: Bitmap,
1714
) = flow<ArrayList<EffectItem>> {
1815
val effectList = arrayListOf<EffectItem>()
1916

20-
val filterFile = arrayListOf<String>()
21-
filterFile.addAll(
22-
listOf(
23-
"acv/Fade.acv",
24-
"acv/Pistol.acv",
25-
"acv/Cinnamon_darkness.acv",
26-
"acv/Blue_Poppies.acv",
27-
"acv/Brighten.acv",
28-
"acv/Darken.acv",
29-
"acv/Contrast.acv",
30-
"acv/Matte.acv",
31-
"acv/Softness.acv",
32-
"acv/Carousel.acv",
33-
"acv/Electric.acv",
34-
"acv/Peacock_Feathers.acv",
35-
"acv/Good_Luck_Charm.acv",
36-
"acv/Lullabye.acv",
37-
"acv/Moth_Wings.acv",
38-
"acv/Old_Postcards_1.acv",
39-
"acv/Old_Postcards_2.acv",
40-
"acv/Toes_In_The_Ocean.acv",
41-
42-
// NEW ONES
43-
"acv/Mark_Galer_Grading.acv",
44-
"acv/Curve_1.acv",
45-
"acv/Curve_2.acv",
46-
"acv/Curve_3.acv",
47-
"acv/Curve_Le_Fabuleux_Coleur_de_Amelie.acv",
48-
"acv/Tropical_Beach.acv",
49-
)
17+
val filterFile = listOf(
18+
"acv/Fade.acv",
19+
"acv/Pistol.acv",
20+
"acv/Cinnamon_darkness.acv",
21+
"acv/Blue_Poppies.acv",
22+
"acv/Brighten.acv",
23+
"acv/Darken.acv",
24+
"acv/Contrast.acv",
25+
"acv/Matte.acv",
26+
"acv/Softness.acv",
27+
"acv/Carousel.acv",
28+
"acv/Electric.acv",
29+
"acv/Peacock_Feathers.acv",
30+
"acv/Good_Luck_Charm.acv",
31+
"acv/Lullabye.acv",
32+
"acv/Moth_Wings.acv",
33+
"acv/Old_Postcards_1.acv",
34+
"acv/Old_Postcards_2.acv",
35+
"acv/Toes_In_The_Ocean.acv",
36+
"acv/Mark_Galer_Grading.acv",
37+
"acv/Curve_1.acv",
38+
"acv/Curve_2.acv",
39+
"acv/Curve_3.acv",
40+
"acv/Curve_Le_Fabuleux_Coleur_de_Amelie.acv",
41+
"acv/Tropical_Beach.acv"
5042
)
5143

52-
val gpuImage = GPUImage(context)
53-
gpuImage.setImage(bitmap)
54-
55-
// emit(
56-
// EffectItem(
57-
// ogBitmap = bitmap,
58-
// previewBitmap = getScaledPreviewBitmap(context, bitmap),
59-
// label = "original"
60-
// )
61-
// )
6244
effectList.add(
6345
EffectItem(
6446
ogBitmap = bitmap,
@@ -68,52 +50,34 @@ object EffectsModeUtils {
6850
)
6951

7052
try {
71-
gpuImage.setFilter(GPUImageGrayscaleFilter())
72-
val grayScaleBitmap = gpuImage.bitmapWithFilterApplied
73-
// emit(
74-
// EffectItem(
75-
// ogBitmap = grayScaleBitmap,
76-
// previewBitmap = getScaledPreviewBitmap(context, grayScaleBitmap),
77-
// label = "grayscale"
78-
// )
79-
// )
53+
val grayBitmap = BitmapGrayscaleFilter.apply(bitmap)
8054
effectList.add(
8155
EffectItem(
82-
ogBitmap = grayScaleBitmap,
83-
previewBitmap = getScaledPreviewBitmap(context, grayScaleBitmap),
56+
ogBitmap = grayBitmap,
57+
previewBitmap = getScaledPreviewBitmap(context, grayBitmap),
8458
label = "grayscale"
8559
)
8660
)
8761
} catch (e: Exception) {
8862
e.printStackTrace()
8963
}
9064

91-
filterFile.forEachIndexed { index, fileName ->
65+
filterFile.forEach { fileName ->
9266
try {
93-
val inputFilter = context.assets.open(fileName)
94-
val gpuFilter = GPUImageToneCurveFilter()
95-
gpuFilter.setFromCurveFileInputStream(inputFilter)
96-
inputFilter.close()
97-
gpuImage.setFilter(gpuFilter)
98-
99-
val filteredBitmap = gpuImage.bitmapWithFilterApplied
100-
// emit(
101-
// EffectItem(
102-
// ogBitmap = filteredBitmap,
103-
// previewBitmap = getScaledPreviewBitmap(context, filteredBitmap),
104-
// label = fileName.drop(4).dropLast(4).replace("_", " ")
105-
// )
106-
// )
107-
effectList.add(
108-
EffectItem(
109-
ogBitmap = filteredBitmap,
110-
previewBitmap = getScaledPreviewBitmap(context, filteredBitmap),
111-
label = fileName.drop(4).dropLast(4).replace("_", " ")
67+
context.assets.open(fileName).use { stream ->
68+
val curve = AcvToneCurveParser.parse(stream)
69+
val filtered = BitmapToneCurveFilter.apply(bitmap, curve)
70+
effectList.add(
71+
EffectItem(
72+
ogBitmap = filtered,
73+
previewBitmap = getScaledPreviewBitmap(context, filtered),
74+
label = fileName.drop(4).dropLast(4).replace("_", " ")
75+
)
11276
)
113-
)
77+
}
11478

11579
if (effectList.size >= 10) {
116-
emit(effectList)
80+
emit(ArrayList(effectList))
11781
effectList.clear()
11882
}
11983
} catch (e: Exception) {
@@ -124,15 +88,13 @@ object EffectsModeUtils {
12488
if (effectList.isNotEmpty()) {
12589
emit(effectList)
12690
}
127-
// return effectList
12891
}
12992

13093
fun getScaledPreviewBitmap(context: Context, originalBitmap: Bitmap): Bitmap {
131-
13294
val screenWidth = AppUtils.getScreenWidth(context)
13395
val aspectRatio = originalBitmap.width.toFloat() / originalBitmap.height.toFloat()
13496
val reqWidth = screenWidth / 3
13597
val reqHeight = (reqWidth / aspectRatio).toInt()
13698
return Bitmap.createScaledBitmap(originalBitmap, reqWidth, reqHeight, false)
13799
}
138-
}
100+
}

0 commit comments

Comments
 (0)