Skip to content

Commit 9bdb30b

Browse files
authored
Merge pull request #3 from elong4321/feature/filters
Feature/filters
2 parents 0031030 + 8910bd7 commit 9bdb30b

File tree

7 files changed

+838
-4
lines changed

7 files changed

+838
-4
lines changed

app/src/main/java/com/example/wangduwei/demos/gles/media/MediaEditFragment.kt

Lines changed: 140 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
11
package com.example.wangduwei.demos.gles.media
22

33
import android.graphics.BitmapFactory
4+
import android.graphics.Color
5+
import android.net.Uri
46
import android.os.Bundle
57
import android.os.Environment
8+
import android.provider.Settings
9+
import android.util.Log
610
import android.view.LayoutInflater
711
import android.view.View
812
import android.view.ViewGroup
913
import android.widget.TextView
1014
import android.widget.Toast
11-
import android.net.Uri
12-
import android.provider.Settings
13-
import android.util.Log
1415
import com.example.lib_gles.video_filter.composer.Mp4Composer
16+
import com.example.lib_gles.video_filter.core.filter.GlFilter
1517
import com.example.lib_gles.video_filter.core.filter.GlFilterGroup
1618
import com.example.lib_gles.video_filter.core.filter.GlFilterList
1719
import com.example.lib_gles.video_filter.core.filter.GlFilterPeriod
20+
import com.example.lib_gles.video_filter.core.filter.TimeScaleFilter
21+
import com.example.lib_gles.video_filter.filter_impl.GlDynamicMosaicFilter
22+
import com.example.lib_gles.video_filter.filter_impl.GlMosaicShiftCascadeFilter
23+
import com.example.lib_gles.video_filter.filter_impl.GlPulseVerticalScaleFilter
24+
import com.example.lib_gles.video_filter.filter_impl.GlPulseZoomFilter
25+
import com.example.lib_gles.video_filter.filter_impl.GlRadialSpreadColorFilter
1826
import com.example.lib_gles.video_filter.filter_impl.GlSoulOutFilter
1927
import com.example.lib_gles.video_filter.filter_impl.GlWatermarkFilter
2028
import com.example.lib_processor.PageInfo
@@ -42,7 +50,7 @@ class MediaEditFragment: BaseSupportFragment() {
4250
).absolutePath
4351

4452

45-
53+
private val duration: Long = 10000;
4654

4755
private val audioPath = File(
4856
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
@@ -65,6 +73,10 @@ class MediaEditFragment: BaseSupportFragment() {
6573
val audioOutput = view.findViewById<TextView>(R.id.video_plus_audio)
6674
val filterEffectOutput = view.findViewById<TextView>(R.id.video_plus_filter_effect)
6775

76+
val videoEffect1 = view.findViewById<TextView>(R.id.video_effect1)
77+
val videoEffect3 = view.findViewById<TextView>(R.id.video_effect3)
78+
val videoEffect4 = view.findViewById<TextView>(R.id.video_effect4)
79+
6880

6981
filterOutput.setOnClickListener {
7082
if (hasStoragePermission().not()) {
@@ -247,6 +259,130 @@ class MediaEditFragment: BaseSupportFragment() {
247259
.start()
248260
}
249261

262+
263+
videoEffect1.setOnClickListener { onClickEffect1(videoEffect1) }
264+
videoEffect3.setOnClickListener { onClickEffect3(videoEffect3) }
265+
videoEffect4.setOnClickListener { onClickEffect4(videoEffect4) }
266+
267+
}
268+
269+
private fun onClickEffect1(textView: TextView) {
270+
val dynamicMosaicFilter = GlDynamicMosaicFilter()
271+
.setRange(2f, 40f) // 最小/最大马赛克块大小(px)
272+
.setDurationMs(1000f) // 一个变化周期
273+
.setLoop(true) // 循环
274+
.setPingPong(true)
275+
276+
val shiftMosaicFilter = GlMosaicShiftCascadeFilter()
277+
.setHoldMs(1000f)
278+
.setStepMs(200f)
279+
.setShiftX(0.30f)
280+
.setMosaicLevels(40f, 20f, 10f, 0f)
281+
282+
val filterGroup = GlFilterGroup(
283+
GlFilterPeriod(1000L,Long.MAX_VALUE, dynamicMosaicFilter),
284+
GlFilterPeriod(1000L,Long.MAX_VALUE, shiftMosaicFilter),
285+
GlFilterPeriod(4000L,8000L, TimeScaleFilter(0.5)),
286+
)
287+
288+
val outFile = File(requireContext().externalCacheDir, "特效一_${System.currentTimeMillis()}.mp4")
289+
compose(filterGroup, textView, outFile)
290+
}
291+
292+
private fun onClickEffect3(textView: TextView) {
293+
294+
val radialColorFilter = GlRadialSpreadColorFilter()
295+
.setCycleDurationSec(1.6f)
296+
.setMaxIntensity(0.55f)
297+
.setSpreadSoftness(0.10f)
298+
.setColorList(arrayListOf<Int>(
299+
Color.RED,
300+
Color.YELLOW,
301+
Color.DKGRAY,
302+
Color.LTGRAY,
303+
))
304+
305+
306+
val zoomFilter = GlPulseZoomFilter(2f)
307+
.setZoomInDurationMs(500f)
308+
.setZoomOutDurationMs(500f)
309+
310+
val verticalScalefilter1 = GlPulseVerticalScaleFilter()
311+
.setTargetScaleY(0.7f)
312+
.setShrinkDurationMs(200f)
313+
.setExpandDurationMs(200f)
314+
.setIntervalMs(3000f)
315+
316+
val filterGroup = GlFilterGroup(
317+
GlFilterPeriod(0,Long.MAX_VALUE, radialColorFilter),
318+
GlFilterPeriod(0,Long.MAX_VALUE, zoomFilter),
319+
GlFilterPeriod(0,Long.MAX_VALUE, verticalScalefilter1),
320+
)
321+
322+
val outFile = File(requireContext().externalCacheDir, "特效三_${System.currentTimeMillis()}.mp4")
323+
compose(filterGroup, textView, outFile)
324+
}
325+
326+
private fun onClickEffect4(textView: TextView) {
327+
val shiftMosaicFilter = GlMosaicShiftCascadeFilter()
328+
.setHoldMs(1000f)
329+
.setStepMs(200f)
330+
.setShiftX(0.30f)
331+
.setMosaicLevels(40f, 20f, 10f, 0f)
332+
333+
val filterGroup = GlFilterGroup(
334+
GlFilterPeriod(1000L,Long.MAX_VALUE, shiftMosaicFilter),
335+
GlFilterPeriod(4000L,8000L, TimeScaleFilter(0.5)),
336+
)
337+
338+
val outFile = File(requireContext().externalCacheDir, "特效四_${System.currentTimeMillis()}.mp4")
339+
compose(filterGroup, textView, outFile)
340+
}
341+
342+
343+
private fun compose(filter: GlFilter, textView: TextView, outFile: File) {
344+
val glFilterList = GlFilterList()
345+
glFilterList.putGlFilter(GlFilterPeriod(0, Long.MAX_VALUE, filter))
346+
val defaultText = textView.text;
347+
Mp4Composer(videoPath, outFile.absolutePath)
348+
.size(720, 1280)
349+
.clip(0, duration)
350+
.filterList(glFilterList)
351+
.listener(object : Mp4Composer.Listener {
352+
override fun onProgress(progress: Double) {
353+
val percent = (progress * 100).toInt().coerceIn(0, 100)
354+
activity?.runOnUiThread {
355+
textView.text = "处理中 ${percent}%"
356+
}
357+
}
358+
359+
override fun onCompleted() {
360+
activity?.runOnUiThread {
361+
textView.isEnabled = true
362+
textView.text = defaultText
363+
Toast.makeText(requireContext(), "输出完成: ${outFile.absolutePath}", Toast.LENGTH_SHORT).show()
364+
365+
Log.d("wdw-gl","path = ${outFile.absolutePath}")
366+
}
367+
}
368+
369+
override fun onCanceled() {
370+
activity?.runOnUiThread {
371+
textView.isEnabled = true
372+
textView.text = defaultText
373+
Toast.makeText(requireContext(), "已取消", Toast.LENGTH_SHORT).show()
374+
}
375+
}
376+
377+
override fun onFailed(exception: Exception) {
378+
activity?.runOnUiThread {
379+
textView.isEnabled = true
380+
textView.text = defaultText
381+
Toast.makeText(requireContext(), "输出失败: ${exception.message}", Toast.LENGTH_SHORT).show()
382+
}
383+
}
384+
})
385+
.start()
250386
}
251387

252388

app/src/main/res/layout/fragment_gl_export.xml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,35 @@
3535
app:layout_constraintTop_toBottomOf="@id/video_plus_audio" />
3636

3737

38+
<TextView
39+
android:id="@+id/video_effect1"
40+
android:layout_width="match_parent"
41+
android:layout_height="wrap_content"
42+
android:gravity="center"
43+
android:text="特效一"
44+
android:textSize="20dp"
45+
android:layout_marginTop="60dp"
46+
app:layout_constraintTop_toBottomOf="@id/video_plus_filter_effect"/>
47+
48+
<TextView
49+
android:id="@+id/video_effect3"
50+
android:layout_width="match_parent"
51+
android:layout_height="wrap_content"
52+
android:gravity="center"
53+
android:text="特效三"
54+
android:textSize="20dp"
55+
android:layout_marginTop="20dp"
56+
app:layout_constraintTop_toBottomOf="@id/video_effect1"/>
57+
58+
<TextView
59+
android:id="@+id/video_effect4"
60+
android:layout_width="match_parent"
61+
android:layout_height="wrap_content"
62+
android:gravity="center"
63+
android:text="特效四"
64+
android:textSize="20dp"
65+
android:layout_marginTop="20dp"
66+
app:layout_constraintTop_toBottomOf="@id/video_effect3"/>
67+
68+
3869
</androidx.constraintlayout.widget.ConstraintLayout>
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package com.example.lib_gles.video_filter.filter_impl;
2+
3+
import android.opengl.GLES20;
4+
import android.os.SystemClock;
5+
6+
import com.example.lib_gles.video_filter.core.filter.GlFilter;
7+
import com.example.lib_gles.video_filter.core.filter.GlFilterList;
8+
9+
import java.util.Map;
10+
11+
/**
12+
* Dynamic mosaic effect driven by timeline time.
13+
* Supports loop and one-shot modes.
14+
*/
15+
public class GlDynamicMosaicFilter extends GlFilter {
16+
17+
private static final String FRAGMENT_SHADER = ""
18+
+ "precision mediump float;\n"
19+
+ "varying highp vec2 textureCoordinate;\n"
20+
+ "uniform lowp sampler2D sTexture;\n"
21+
+ "uniform vec2 uResolution;\n"
22+
+ "uniform float uBlockSize;\n"
23+
+ "void main() {\n"
24+
+ " vec2 pixel = textureCoordinate * uResolution;\n"
25+
+ " vec2 block = floor(pixel / uBlockSize) * uBlockSize + vec2(uBlockSize * 0.5);\n"
26+
+ " vec2 uv = block / uResolution;\n"
27+
+ " gl_FragColor = texture2D(sTexture, uv);\n"
28+
+ "}\n";
29+
30+
private int resolutionHandle = -1;
31+
private int blockSizeHandle = -1;
32+
33+
private float minBlockSizePx = 2f;
34+
private float maxBlockSizePx = 36f;
35+
private float durationMs = 1000f;
36+
private boolean loop = true;
37+
private boolean pingPong = true; // 0->1->0 in one cycle
38+
39+
public GlDynamicMosaicFilter() {
40+
super(VERTEX_SHADER, FRAGMENT_SHADER);
41+
}
42+
43+
@Override
44+
public void initProgramHandle() {
45+
super.initProgramHandle();
46+
resolutionHandle = GLES20.glGetUniformLocation(mProgramHandle, "uResolution");
47+
blockSizeHandle = GLES20.glGetUniformLocation(mProgramHandle, "uBlockSize");
48+
}
49+
50+
@Override
51+
protected void onDraw(long presentationTimeUs, Map<String, Integer> extraTextureIds) {
52+
if (mWidth <= 0 || mHeight <= 0) {
53+
return;
54+
}
55+
56+
float timelineMs = readTimelineMs(extraTextureIds);
57+
float progress = computeProgress(timelineMs);
58+
float block = minBlockSizePx + (maxBlockSizePx - minBlockSizePx) * progress;
59+
60+
GLES20.glUniform2f(resolutionHandle, mWidth, mHeight);
61+
GLES20.glUniform1f(blockSizeHandle, Math.max(1f, block));
62+
}
63+
64+
private float readTimelineMs(Map<String, Integer> extraTextureIds) {
65+
// if (extraTextureIds != null && extraTextureIds.containsKey(GlFilterList.EXTRA_PRESENTATION_TIME_MS)) {
66+
// return extraTextureIds.get(GlFilterList.EXTRA_PRESENTATION_TIME_MS);
67+
// }
68+
return SystemClock.uptimeMillis() % 600000L;
69+
}
70+
71+
private float computeProgress(float timelineMs) {
72+
float d = Math.max(1f, durationMs);
73+
float p;
74+
if (loop) {
75+
p = (timelineMs % d) / d; // [0,1)
76+
} else {
77+
p = clamp(timelineMs / d, 0f, 1f);
78+
}
79+
if (pingPong) {
80+
p = 1f - Math.abs(2f * p - 1f);
81+
}
82+
return clamp(p, 0f, 1f);
83+
}
84+
85+
public GlDynamicMosaicFilter setRange(float minBlockSizePx, float maxBlockSizePx) {
86+
this.minBlockSizePx = Math.max(1f, Math.min(minBlockSizePx, maxBlockSizePx));
87+
this.maxBlockSizePx = Math.max(this.minBlockSizePx, maxBlockSizePx);
88+
return this;
89+
}
90+
91+
public GlDynamicMosaicFilter setDurationMs(float durationMs) {
92+
this.durationMs = Math.max(1f, durationMs);
93+
return this;
94+
}
95+
96+
public GlDynamicMosaicFilter setLoop(boolean loop) {
97+
this.loop = loop;
98+
return this;
99+
}
100+
101+
public GlDynamicMosaicFilter setPingPong(boolean pingPong) {
102+
this.pingPong = pingPong;
103+
return this;
104+
}
105+
106+
private static float clamp(float v, float min, float max) {
107+
return Math.max(min, Math.min(max, v));
108+
}
109+
}

0 commit comments

Comments
 (0)