diff --git a/app/build.gradle b/app/build.gradle
index 9e5d3e18..a5116986 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -196,10 +196,8 @@ dependencies {
implementation 'com.github.connyduck:sparkbutton:4.1.0'
- implementation 'info.androidhive:imagefilters:1.0.7'
- implementation 'com.github.yalantis:ucrop:2.2.8-native'
+ implementation 'org.pixeldroid.pixeldroid:android-media-editor:1.0'
implementation project(path: ':scrambler')
- implementation project(path: ':mediaEditor')
implementation('com.github.bumptech.glide:glide:4.14.2') {
exclude group: "com.android.support"
diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt
index e6263268..7386599f 100644
--- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt
+++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt
@@ -21,7 +21,6 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.core.net.toFile
import androidx.core.net.toUri
-import androidx.core.os.HandlerCompat
import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt
index b78f9a9c..710ac45a 100644
--- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt
+++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt
@@ -450,6 +450,13 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null
_uiState.update { it.copy(newPostDescriptionText = text.toString()) }
}
+ /**
+ * @param originalUri the Uri of the file you sent to be edited
+ * @param progress percentage of (this pass of) encoding that is done
+ * @param firstPass Whether this is the first pass (currently for analysis of video stabilization) or the second (and last) pass.
+ * @param outputVideoPath when not null, it means the encoding is done and the result is saved in this file
+ * @param error is true when there has been an error during encoding.
+ */
private fun videoEncodeProgress(originalUri: Uri, progress: Int, firstPass: Boolean, outputVideoPath: Uri?, error: Boolean){
photoData.value?.indexOfFirst { it.imageUri == originalUri }?.let { position ->
diff --git a/app/src/main/java/org/pixeldroid/app/utils/Utils.kt b/app/src/main/java/org/pixeldroid/app/utils/Utils.kt
index 621ae44e..2d646bcc 100644
--- a/app/src/main/java/org/pixeldroid/app/utils/Utils.kt
+++ b/app/src/main/java/org/pixeldroid/app/utils/Utils.kt
@@ -25,7 +25,6 @@ import androidx.lifecycle.LifecycleOwner
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
-import com.arthenica.ffmpegkit.FFmpegKitConfig
import com.google.android.material.color.MaterialColors
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
@@ -96,13 +95,6 @@ fun normalizeDomain(domain: String): String {
.trim(Char::isWhitespace)
}
-fun Context.ffmpegCompliantUri(inputUri: Uri?): String =
- if (inputUri?.scheme == "content")
- FFmpegKitConfig.getSafParameterForRead(this, inputUri)
- else inputUri.toString()
-
-
-
fun BaseActivity.openUrl(url: String): Boolean {
val intent = CustomTabsIntent.Builder().build()
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 7e241f04..016627ff 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -168,6 +168,9 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"
- "%d\nFollowing"
- "%d\nFollowing"
+ Edit
+ Unable to save image
+ Image successfully saved
Could not get follow status
Failed to open edit page
Nothing to see here :(
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 30a561c5..854c6060 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -9555,6 +9555,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mediaEditor/.gitignore b/mediaEditor/.gitignore
deleted file mode 100644
index 42afabfd..00000000
--- a/mediaEditor/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
\ No newline at end of file
diff --git a/mediaEditor/build.gradle b/mediaEditor/build.gradle
deleted file mode 100644
index e415469e..00000000
--- a/mediaEditor/build.gradle
+++ /dev/null
@@ -1,62 +0,0 @@
-plugins {
- id 'com.android.library'
- id 'org.jetbrains.kotlin.android'
-}
-
-android {
- namespace 'org.pixeldroid.media_editor'
- compileSdk 33
-
- defaultConfig {
- minSdk 23
- targetSdk 33
- versionCode 1
- versionName "1.0"
-
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- }
-
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- }
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
- kotlinOptions {
- jvmTarget = '1.8'
- }
- buildFeatures {
- viewBinding = true
- }
-}
-
-dependencies {
-
- implementation 'androidx.core:core-ktx:1.9.0'
- implementation 'androidx.appcompat:appcompat:1.5.1'
- implementation 'com.google.android.material:material:1.7.0'
- testImplementation 'junit:junit:4.13.2'
- androidTestImplementation 'androidx.test.ext:junit:1.1.3'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
-
- implementation 'info.androidhive:imagefilters:1.0.7'
- implementation 'com.github.yalantis:ucrop:2.2.8-native'
-
- implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1'
- implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
- implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.5.1'
- implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1"
- implementation "androidx.lifecycle:lifecycle-common-java8:2.5.1"
- implementation 'androidx.media2:media2-widget:1.2.1'
- implementation 'androidx.media2:media2-player:1.2.1'
- implementation "androidx.recyclerview:recyclerview:1.2.1"
- implementation 'com.arthenica:ffmpeg-kit-min-gpl:5.1.LTS'
- implementation('com.github.bumptech.glide:glide:4.14.2') {
- exclude group: "com.android.support"
- }
-
-}
\ No newline at end of file
diff --git a/mediaEditor/proguard-rules.pro b/mediaEditor/proguard-rules.pro
deleted file mode 100644
index 481bb434..00000000
--- a/mediaEditor/proguard-rules.pro
+++ /dev/null
@@ -1,21 +0,0 @@
-# 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
\ No newline at end of file
diff --git a/mediaEditor/src/androidTest/java/org/pixeldroid/media_editor/ExampleInstrumentedTest.kt b/mediaEditor/src/androidTest/java/org/pixeldroid/media_editor/ExampleInstrumentedTest.kt
deleted file mode 100644
index 89737fff..00000000
--- a/mediaEditor/src/androidTest/java/org/pixeldroid/media_editor/ExampleInstrumentedTest.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package org.pixeldroid.media_editor
-
-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("org.pixeldroid.media_editor", appContext.packageName)
- }
-}
\ No newline at end of file
diff --git a/mediaEditor/src/main/AndroidManifest.xml b/mediaEditor/src/main/AndroidManifest.xml
deleted file mode 100644
index 69fc4129..00000000
--- a/mediaEditor/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/EditImageFragment.kt b/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/EditImageFragment.kt
deleted file mode 100644
index 7777c928..00000000
--- a/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/EditImageFragment.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-package org.pixeldroid.media_editor.photoEdit
-
-import android.os.Bundle
-import androidx.fragment.app.Fragment
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.SeekBar
-import org.pixeldroid.media_editor.databinding.FragmentEditImageBinding
-
-class EditImageFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
-
- private var listener: PhotoEditActivity? = null
- private lateinit var binding: FragmentEditImageBinding
-
- private var BRIGHTNESS_MAX = 200
- private var SATURATION_MAX = 20
- private var CONTRAST_MAX= 30
- private var BRIGHTNESS_START = BRIGHTNESS_MAX/2
- private var SATURATION_START = SATURATION_MAX/2
- private var CONTRAST_START = CONTRAST_MAX/2
-
- override fun onCreateView(
- inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
- // Inflate the layout for this fragment
- binding = FragmentEditImageBinding.inflate(inflater, container, false)
-
- binding.seekbarBrightness.max = BRIGHTNESS_MAX
- binding.seekbarBrightness.progress = BRIGHTNESS_START
-
- binding.seekbarContrast.max = CONTRAST_MAX
- binding.seekbarContrast.progress = CONTRAST_START
-
- binding.seekbarSaturation.max = SATURATION_MAX
- binding.seekbarSaturation.progress = SATURATION_START
-
- setOnSeekBarChangeListeners(this)
-
- return binding.root
- }
-
- private fun setOnSeekBarChangeListeners(listener: EditImageFragment?){
- binding.seekbarBrightness.setOnSeekBarChangeListener(listener)
- binding.seekbarContrast.setOnSeekBarChangeListener(listener)
- binding.seekbarSaturation.setOnSeekBarChangeListener(listener)
- }
-
- override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
- var prog = progress
-
- listener?.let {
- when(seekBar) {
- binding.seekbarBrightness -> it.onBrightnessChange(progress - 100)
- binding.seekbarSaturation -> {
- prog += 10
- it.onSaturationChange(.10f * prog)
- }
- binding.seekbarContrast -> {
- it.onContrastChange(.10f * prog)
- }
- }
- }
- }
-
- fun resetControl() {
- // Make sure to ignore seekbar change events, since we don't want to have the reset cause
- // filter applications due to the onProgressChanged calls
- setOnSeekBarChangeListeners(null)
- binding.seekbarBrightness.progress = BRIGHTNESS_START
- binding.seekbarContrast.progress = CONTRAST_START
- binding.seekbarSaturation.progress = SATURATION_START
- setOnSeekBarChangeListeners(this)
- }
-
- override fun onStartTrackingTouch(seekBar: SeekBar?) {
- listener?.onEditStarted()
- }
-
- override fun onStopTrackingTouch(seekBar: SeekBar?) {
- listener?.onEditCompleted()
- }
-
- fun setListener(listener: PhotoEditActivity) {
- this.listener = listener
- }
-}
diff --git a/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/FilterListFragment.kt b/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/FilterListFragment.kt
deleted file mode 100644
index dcb3a589..00000000
--- a/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/FilterListFragment.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-package org.pixeldroid.media_editor.photoEdit
-
-import android.graphics.Bitmap
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.fragment.app.Fragment
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import androidx.recyclerview.widget.LinearLayoutManager
-import com.zomato.photofilters.FilterPack
-import com.zomato.photofilters.imageprocessors.Filter
-import com.zomato.photofilters.utils.ThumbnailItem
-import com.zomato.photofilters.utils.ThumbnailsManager
-import kotlinx.coroutines.launch
-import org.pixeldroid.media_editor.R
-import org.pixeldroid.media_editor.databinding.FragmentFilterListBinding
-
-class FilterListFragment : Fragment() {
-
- private lateinit var binding: FragmentFilterListBinding
-
- private var listener : ((Filter) -> Unit)? = null
- internal lateinit var adapter: ThumbnailAdapter
- private lateinit var tbItemList: MutableList
-
- override fun onCreateView(
- inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
- // Inflate the layout for this fragment
- binding = FragmentFilterListBinding.inflate(inflater, container, false)
-
- tbItemList = ArrayList()
-
- binding.recyclerView.layoutManager = LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false)
-
- adapter = ThumbnailAdapter(requireActivity(), tbItemList, this)
- binding.recyclerView.adapter = adapter
-
- return binding.root
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- displayImage()
- }
-
- private fun displayImage() {
- viewLifecycleOwner.lifecycleScope.launch {
- viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
- val tbImage: Bitmap = bitmapFromUri(requireActivity().contentResolver,
- PhotoEditActivity.imageUri
- )
- setupFilter(tbImage)
-
- tbItemList.addAll(ThumbnailsManager.processThumbs(context))
- adapter.notifyDataSetChanged()
- }
- }
- }
-
- private fun setupFilter(tbImage: Bitmap?) {
- ThumbnailsManager.clearThumbs()
- tbItemList.clear()
-
- val tbItem = ThumbnailItem()
- tbItem.image = tbImage
- tbItem.filter.name = getString(R.string.normal_filter)
- tbItem.filterName = tbItem.filter.name
- ThumbnailsManager.addThumb(tbItem)
-
- val filters = FilterPack.getFilterPack(context)
-
- for (filter in filters) {
- val item = ThumbnailItem()
- item.image = tbImage
- item.filter = filter
- item.filterName = filter.name
- ThumbnailsManager.addThumb(item)
- }
- }
-
- fun resetSelectedFilter(){
- adapter.resetSelected()
- }
-
- fun onFilterSelected(filter: Filter) {
- listener?.invoke(filter)
- }
-
- fun setListener(listFragmentListener: (filter: Filter) -> Unit) {
- this.listener = listFragmentListener
- }
-}
diff --git a/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/PhotoEditActivity.kt b/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/PhotoEditActivity.kt
deleted file mode 100644
index 37e30302..00000000
--- a/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/PhotoEditActivity.kt
+++ /dev/null
@@ -1,462 +0,0 @@
-package org.pixeldroid.media_editor.photoEdit
-
-import android.app.Activity
-import android.app.AlertDialog
-import android.content.Intent
-import android.content.pm.PackageManager
-import android.graphics.Bitmap
-import android.graphics.Point
-import android.graphics.drawable.BitmapDrawable
-import android.net.Uri
-import android.os.Bundle
-import android.view.Menu
-import android.view.MenuItem
-import android.view.View.GONE
-import android.view.View.VISIBLE
-import android.widget.Toast
-import androidx.appcompat.app.AppCompatActivity
-import androidx.core.app.ActivityCompat
-import androidx.core.content.ContextCompat
-import androidx.fragment.app.Fragment
-import androidx.viewpager2.adapter.FragmentStateAdapter
-import androidx.viewpager2.widget.ViewPager2
-import com.bumptech.glide.Glide
-import com.google.android.material.snackbar.Snackbar
-import com.google.android.material.tabs.TabLayoutMediator
-import com.yalantis.ucrop.UCrop
-import com.zomato.photofilters.imageprocessors.Filter
-import com.zomato.photofilters.imageprocessors.subfilters.BrightnessSubFilter
-import com.zomato.photofilters.imageprocessors.subfilters.ContrastSubFilter
-import com.zomato.photofilters.imageprocessors.subfilters.SaturationSubfilter
-import org.pixeldroid.media_editor.databinding.ActivityPhotoEditBinding
-import org.pixeldroid.media_editor.R
-import java.io.File
-import java.io.IOException
-import java.io.OutputStream
-import java.util.concurrent.ExecutorService
-import java.util.concurrent.Executors.newSingleThreadExecutor
-import java.util.concurrent.Future
-
-// This is an arbitrary number we are using to keep track of the permission
-// request. Where an app has multiple context for requesting permission,
-// this can help differentiate the different contexts.
-private const val REQUEST_CODE_PERMISSIONS_SEND_PHOTO = 7
-private val REQUIRED_PERMISSIONS = arrayOf(
- android.Manifest.permission.READ_EXTERNAL_STORAGE,
- android.Manifest.permission.WRITE_EXTERNAL_STORAGE
-)
-
-class PhotoEditActivity : AppCompatActivity() {
-
- var saving: Boolean = false
- private val BITMAP_CONFIG = Bitmap.Config.ARGB_8888
- private val BRIGHTNESS_START = 0
- private val SATURATION_START = 1.0f
- private val CONTRAST_START = 1.0f
-
- private var originalImage: Bitmap? = null
- private var compressedImage: Bitmap? = null
- private var compressedOriginalImage: Bitmap? = null
- private lateinit var filteredImage: Bitmap
-
- private var actualFilter: Filter? = null
-
- private lateinit var filterListFragment: FilterListFragment
- private lateinit var editImageFragment: EditImageFragment
-
- private var picturePosition: Int? = null
-
- private var brightnessFinal = BRIGHTNESS_START
- private var saturationFinal = SATURATION_START
- private var contrastFinal = CONTRAST_START
-
- init {
- System.loadLibrary("NativeImageProcessor")
- }
-
- companion object{
- const val PICTURE_URI = "picture_uri"
- const val PICTURE_POSITION = "picture_position"
-
- private var executor: ExecutorService = newSingleThreadExecutor()
- private var future: Future<*>? = null
-
- private var saveExecutor: ExecutorService = newSingleThreadExecutor()
- private var saveFuture: Future<*>? = null
-
- private var initialUri: Uri? = null
- internal var imageUri: Uri? = null
- }
-
- private lateinit var binding: ActivityPhotoEditBinding
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- binding = ActivityPhotoEditBinding.inflate(layoutInflater)
-
- setContentView(binding.root)
-
-
- supportActionBar?.setTitle(R.string.toolbar_title_edit)
- supportActionBar?.setDisplayHomeAsUpEnabled(true)
- supportActionBar?.setHomeButtonEnabled(true)
-
- initialUri = intent.getParcelableExtra(PICTURE_URI)
- picturePosition = intent.getIntExtra(PICTURE_POSITION, 0)
- imageUri = initialUri
-
- // Crop button on-click listener
- binding.cropImageButton.setOnClickListener {
- startCrop()
- }
-
- loadImage()
-
- setupViewPager(binding.viewPager)
- }
-
- private fun loadImage() {
- originalImage = bitmapFromUri(contentResolver, imageUri)
-
- compressedImage = resizeImage(originalImage!!)
- compressedOriginalImage = compressedImage!!.copy(BITMAP_CONFIG, true)
- filteredImage = compressedImage!!.copy(BITMAP_CONFIG, true)
- Glide.with(this).load(compressedImage).into(binding.imagePreview)
- }
-
- private fun resizeImage(image: Bitmap): Bitmap {
- val display = windowManager.defaultDisplay
- val size = Point()
- display.getSize(size)
-
- val newY = size.y * 0.7
- val scale = newY / image.height
- return Bitmap.createScaledBitmap(image, (image.width * scale).toInt(), newY.toInt(), true)
- }
-
- private fun setupViewPager(viewPager: ViewPager2) {
- filterListFragment = FilterListFragment()
- filterListFragment.setListener(::onFilterSelected)
-
- editImageFragment = EditImageFragment()
- editImageFragment.setListener(this)
-
- val tabs: List<() -> Fragment> = listOf({ filterListFragment }, { editImageFragment })
-
- // Keep both tabs loaded at all times because values are needed there
- viewPager.offscreenPageLimit = 1
-
- //Disable swiping in viewpager
- viewPager.isUserInputEnabled = false
-
- viewPager.adapter = object : FragmentStateAdapter(this) {
- override fun createFragment(position: Int): Fragment {
- return tabs[position]()
- }
-
- override fun getItemCount(): Int {
- return tabs.size
- }
- }
- TabLayoutMediator(binding.tabs, viewPager) { tab, position ->
- tab.setText(when(position) {
- 0 -> R.string.tab_filters
- else -> R.string.edit
- })
- }.attach()
- }
-
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- menuInflater.inflate(R.menu.edit_menu, menu)
- return true
- }
-
- override fun onStop() {
- super.onStop()
- saving = false
- }
-
- @Deprecated("Deprecated in Java")
- override fun onBackPressed() {
- if (noEdits()) super.onBackPressed()
- else {
- val builder = AlertDialog.Builder(this)
- builder.apply {
- setMessage(R.string.save_before_returning)
- setPositiveButton(android.R.string.ok) { _, _ ->
- saveImageToGallery()
- }
- setNegativeButton(R.string.no_cancel_edit) { _, _ ->
- super.onBackPressed()
- }
- }
- // Create the AlertDialog
- builder.show()
- }
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
-
- when(item.itemId) {
- android.R.id.home -> onBackPressed()
- R.id.action_save -> {
- saveImageToGallery()
- }
- R.id.action_reset -> {
- resetControls()
- actualFilter = null
- imageUri = initialUri
- loadImage()
- filterListFragment.resetSelectedFilter()
- }
- }
-
- return super.onOptionsItemSelected(item)
- }
-
- fun onFilterSelected(filter: Filter) {
- filteredImage = compressedOriginalImage!!.copy(BITMAP_CONFIG, true)
- binding.imagePreview.setImageBitmap(filter.processFilter(filteredImage))
- compressedImage = filteredImage.copy(BITMAP_CONFIG, true)
- actualFilter = filter
- resetControls()
- }
-
- private fun resetControls() {
- brightnessFinal = BRIGHTNESS_START
- saturationFinal = SATURATION_START
- contrastFinal = CONTRAST_START
-
- editImageFragment.resetControl()
- }
-
-
- private fun applyFilterAndShowImage(filter: Filter, image: Bitmap?) {
- future?.cancel(true)
- future = executor.submit {
- val bitmap = filter.processFilter(image!!.copy(BITMAP_CONFIG, true))
- binding.imagePreview.post {
- binding.imagePreview.setImageBitmap(bitmap)
- }
- }
- }
-
- fun onBrightnessChange(brightness: Int) {
- brightnessFinal = brightness
- val myFilter = Filter()
- myFilter.addEditFilters(brightness, saturationFinal, contrastFinal)
- applyFilterAndShowImage(myFilter, filteredImage)
- }
-
- fun onSaturationChange(saturation: Float) {
- saturationFinal = saturation
- val myFilter = Filter()
- myFilter.addEditFilters(brightnessFinal, saturation, contrastFinal)
- applyFilterAndShowImage(myFilter, filteredImage)
- }
-
- fun onContrastChange(contrast: Float) {
- contrastFinal = contrast
- val myFilter = Filter()
- myFilter.addEditFilters(brightnessFinal, saturationFinal, contrast)
- applyFilterAndShowImage(myFilter, filteredImage)
- }
-
- private fun Filter.addEditFilters(br: Int, sa: Float, co: Float): Filter {
- addSubFilter(BrightnessSubFilter(br))
- addSubFilter(ContrastSubFilter(co))
- addSubFilter(SaturationSubfilter(sa))
- return this
- }
-
- fun onEditStarted() {
- }
-
- fun onEditCompleted() {
- val myFilter = Filter()
- myFilter.addEditFilters(brightnessFinal, saturationFinal, contrastFinal)
- val bitmap = filteredImage.copy(BITMAP_CONFIG, true)
-
- compressedImage = myFilter.processFilter(bitmap)
- }
-
-
- private fun startCrop() {
- val file = File.createTempFile("temp_crop_img", ".png", cacheDir)
-
- val options: UCrop.Options = UCrop.Options().apply {
- setStatusBarColor(this@PhotoEditActivity.getColorFromAttr(R.attr.colorPrimaryDark))
- setToolbarWidgetColor(this@PhotoEditActivity.getColorFromAttr(R.attr.colorOnSurface))
- setToolbarColor(this@PhotoEditActivity.getColorFromAttr(R.attr.colorSurface))
- setActiveControlsWidgetColor(this@PhotoEditActivity.getColorFromAttr(R.attr.colorPrimary))
- setFreeStyleCropEnabled(true)
- }
- val uCrop: UCrop = UCrop.of(initialUri!!, Uri.fromFile(file)).withOptions(options)
- uCrop.start(this)
- }
-
- @Deprecated("Deprecated in Java")
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- if(resultCode == Activity.RESULT_OK) {
- if (requestCode == UCrop.RESULT_ERROR) {
- handleCropError(data)
- } else {
- handleCropResult(data)
- }
- }
- }
-
- private fun resetFilteredImage(){
- val newBr = if(brightnessFinal != 0) BRIGHTNESS_START/brightnessFinal else 0
- val newSa = if(saturationFinal != 0.0f) SATURATION_START/saturationFinal else 0.0f
- val newCo = if(contrastFinal != 0.0f) CONTRAST_START/contrastFinal else 0.0f
- val myFilter = Filter().addEditFilters(newBr, newSa, newCo)
-
- filteredImage = myFilter.processFilter(filteredImage)
- }
-
- private fun handleCropResult(data: Intent?) {
- val resultCrop: Uri? = UCrop.getOutput(data!!)
- if(resultCrop != null) {
- imageUri = resultCrop
- binding.imagePreview.setImageURI(resultCrop)
- val bitmap = (binding.imagePreview.drawable as BitmapDrawable).bitmap
- originalImage = bitmap.copy(Bitmap.Config.ARGB_8888, true)
- compressedImage = resizeImage(originalImage!!.copy(BITMAP_CONFIG, true))
- compressedOriginalImage = compressedImage!!.copy(BITMAP_CONFIG, true)
- filteredImage = compressedImage!!.copy(BITMAP_CONFIG, true)
- resetFilteredImage()
- } else {
- Toast.makeText(this, R.string.crop_result_error, Toast.LENGTH_SHORT).show()
- }
- }
-
- private fun handleCropError(data: Intent?) {
- val resultError = UCrop.getError(data!!)
- if(resultError != null) {
- Toast.makeText(this, "" + resultError, Toast.LENGTH_SHORT).show()
- } else {
- Toast.makeText(this, R.string.crop_result_error, Toast.LENGTH_SHORT).show()
- }
- }
-
- override fun onRequestPermissionsResult(
- requestCode: Int,
- permissions: Array,
- grantResults: IntArray
- ) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults)
- if(grantResults.size > 1
- && grantResults[0] == PackageManager.PERMISSION_GRANTED
- && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
- // permission was granted
- permissionsGrantedToSave()
- } else {
- Snackbar.make(binding.root, getString(R.string.permission_denied),
- Snackbar.LENGTH_LONG).show()
- }
- }
-
- private fun applyFinalFilters(image: Bitmap?): Bitmap {
- val editFilter = Filter().addEditFilters(brightnessFinal, saturationFinal, contrastFinal)
-
- var finalImage = editFilter.processFilter(image!!.copy(BITMAP_CONFIG, true))
- if (actualFilter!=null) finalImage = actualFilter!!.processFilter(finalImage)
- return finalImage
- }
-
- private fun sendBackImage(file: String) {
- val intent = Intent()
- .apply {
- putExtra(PICTURE_URI, file)
- putExtra(PICTURE_POSITION, picturePosition)
- addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
- }
-
- setResult(Activity.RESULT_OK, intent)
- finish()
- }
-
- private fun saveImageToGallery() {
- // runtime permission and process
- if (!allPermissionsGranted()) {
- ActivityCompat.requestPermissions(
- this,
- REQUIRED_PERMISSIONS,
- REQUEST_CODE_PERMISSIONS_SEND_PHOTO
- )
- } else {
- permissionsGrantedToSave()
- }
- }
-
- /**
- * Check if all permission specified in the manifest have been granted
- */
- private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
- ContextCompat.checkSelfPermission(
- applicationContext, it) == PackageManager.PERMISSION_GRANTED
- }
-
-
- private fun OutputStream.writeBitmap(bitmap: Bitmap) {
- use { out ->
- //(quality is ignored for PNG)
- bitmap.compress(Bitmap.CompressFormat.PNG, 85, out)
- out.flush()
- }
- }
-
- private fun noEdits(): Boolean =
- brightnessFinal == BRIGHTNESS_START
- && contrastFinal == CONTRAST_START
- && saturationFinal == SATURATION_START
- && actualFilter?.let { it.name == getString(R.string.normal_filter)} ?: true
-
- private fun permissionsGrantedToSave() {
- if (saving) {
- val builder = AlertDialog.Builder(this)
- builder.apply {
- setMessage(R.string.busy_dialog_text)
- setNegativeButton(R.string.busy_dialog_ok_button) { _, _ -> }
- }
- // Create the AlertDialog
- builder.show()
- return
- }
- saving = true
- binding.progressBarSaveFile.visibility = VISIBLE
- saveFuture = saveExecutor.submit {
- try {
- val path: String
- if(!noEdits()) {
- // Save modified image in cache
- val tempFile = File.createTempFile("temp_edit_img", ".png", cacheDir)
- path = Uri.fromFile(tempFile).toString()
- tempFile.outputStream().writeBitmap(applyFinalFilters(originalImage))
- }
- else {
- path = imageUri.toString()
- }
-
- if(saving) {
- this.runOnUiThread {
- sendBackImage(path)
- binding.progressBarSaveFile.visibility = GONE
- saving = false
- }
- }
- } catch (e: IOException) {
- this.runOnUiThread {
- Snackbar.make(
- binding.root, getString(R.string.save_image_failed),
- Snackbar.LENGTH_LONG
- ).show()
- binding.progressBarSaveFile.visibility = GONE
- saving = false
- }
- }
- }
- }
-}
diff --git a/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/ThumbnailAdapter.kt b/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/ThumbnailAdapter.kt
deleted file mode 100644
index 95a3e989..00000000
--- a/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/ThumbnailAdapter.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-package org.pixeldroid.media_editor.photoEdit
-
-import android.content.Context
-import android.view.LayoutInflater
-import android.view.ViewGroup
-import android.widget.ImageView
-import android.widget.TextView
-import androidx.recyclerview.widget.RecyclerView
-import com.zomato.photofilters.utils.ThumbnailItem
-import org.pixeldroid.media_editor.R
-import org.pixeldroid.media_editor.databinding.ThumbnailListItemBinding
-
-class ThumbnailAdapter (private val context: Context,
- private val tbItemList: List,
- private val listener: FilterListFragment
-): RecyclerView.Adapter() {
-
- private var selectedIndex = 0
-
- fun resetSelected(){
- selectedIndex = 0
- notifyDataSetChanged()
- }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
- val itemBinding = ThumbnailListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
- return MyViewHolder(itemBinding)
- }
-
- override fun getItemCount(): Int {
- return tbItemList.size
- }
-
- override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
- val tbItem = tbItemList[position]
- holder.thumbnail.setImageBitmap(tbItem.image)
- holder.thumbnail.setOnClickListener {
- listener.onFilterSelected(tbItem.filter)
- selectedIndex = holder.bindingAdapterPosition
- notifyDataSetChanged()
- }
-
- holder.filterName.text = tbItem.filterName
-
- if(selectedIndex == position)
- holder.filterName.setTextColor(context.getColorFromAttr(R.attr.colorPrimary))
- else
- holder.filterName.setTextColor(context.getColorFromAttr(R.attr.colorOnBackground))
- }
-
- class MyViewHolder(itemBinding: ThumbnailListItemBinding): RecyclerView.ViewHolder(itemBinding.root) {
- var thumbnail: ImageView = itemBinding.thumbnail
- var filterName: TextView = itemBinding.filterName
- }
-}
\ No newline at end of file
diff --git a/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/Utils.kt b/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/Utils.kt
deleted file mode 100644
index 151b5e33..00000000
--- a/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/Utils.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-package org.pixeldroid.media_editor.photoEdit
-
-import android.content.ContentResolver
-import android.content.Context
-import android.graphics.Bitmap
-import android.graphics.Color
-import android.graphics.ImageDecoder
-import android.graphics.Matrix
-import android.net.Uri
-import android.os.Build
-import android.provider.MediaStore
-import android.util.TypedValue
-import android.webkit.MimeTypeMap
-import androidx.annotation.AttrRes
-import androidx.annotation.ColorInt
-import androidx.exifinterface.media.ExifInterface
-import com.arthenica.ffmpegkit.FFmpegKitConfig
-import com.google.android.material.color.MaterialColors
-
-
-fun bitmapFromUri(contentResolver: ContentResolver, uri: Uri?): Bitmap =
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- ImageDecoder
- .decodeBitmap(
- ImageDecoder.createSource(contentResolver, uri!!)
- )
- { decoder, _, _ -> decoder.isMutableRequired = true }
- } else {
- @Suppress("DEPRECATION")
- val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, uri)
- modifyOrientation(bitmap!!, contentResolver, uri!!)
- }
-
-fun modifyOrientation(
- bitmap: Bitmap,
- contentResolver: ContentResolver,
- uri: Uri
-): Bitmap {
- val inputStream = contentResolver.openInputStream(uri)!!
- val ei = ExifInterface(inputStream)
- return when (ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)) {
- ExifInterface.ORIENTATION_ROTATE_90 -> bitmap.rotate(90f)
- ExifInterface.ORIENTATION_ROTATE_180 -> bitmap.rotate(180f)
- ExifInterface.ORIENTATION_ROTATE_270 -> bitmap.rotate(270f)
- ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> bitmap.flip(horizontal = true, vertical = false)
- ExifInterface.ORIENTATION_FLIP_VERTICAL -> bitmap.flip(horizontal = false, vertical = true)
- else -> bitmap
- }
-}
-
-fun Bitmap.rotate(degrees: Float): Bitmap {
- val matrix = Matrix()
- matrix.postRotate(degrees)
- return Bitmap.createBitmap(this, 0, 0, width, height, matrix, true)
-}
-
-fun Bitmap.flip(horizontal: Boolean, vertical: Boolean): Bitmap {
- val matrix = Matrix()
- matrix.preScale(if (horizontal) -1f else 1f, if (vertical) -1f else 1f)
- return Bitmap.createBitmap(this, 0, 0, width, height, matrix, true)
-}
-
-@ColorInt
-fun Context.getColorFromAttr(@AttrRes attrColor: Int): Int = MaterialColors.getColor(this, attrColor, Color.BLACK)
-
-fun Context.ffmpegCompliantUri(inputUri: Uri?): String =
- if (inputUri?.scheme == "content")
- FFmpegKitConfig.getSafParameterForRead(this, inputUri)
- else inputUri.toString()
-
-/**
- * This method converts dp unit to equivalent pixels, depending on device density.
- */
-fun Int.dpToPx(context: Context): Int {
- return TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP,
- this.toFloat(),
- context.resources.displayMetrics
- ).toInt()
-}
-
-
-/** Maps a Float from this range to target range */
-fun ClosedRange.convert(number: Float, target: ClosedRange): Float {
- val ratio = number / (endInclusive - start)
- return (ratio * (target.endInclusive - target.start))
-}
-
-fun Uri.fileExtension(contentResolver: ContentResolver): String? {
- return if (scheme == "content") {
- contentResolver.getType(this)?.takeLastWhile { it != '/' }
- } else {
- MimeTypeMap.getFileExtensionFromUrl(toString()).ifEmpty { null }
- }
-}
\ No newline at end of file
diff --git a/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/VideoEditActivity.kt b/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/VideoEditActivity.kt
deleted file mode 100644
index d93fe9de..00000000
--- a/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/VideoEditActivity.kt
+++ /dev/null
@@ -1,647 +0,0 @@
-package org.pixeldroid.media_editor.photoEdit
-
-import android.app.Activity
-import android.app.AlertDialog
-import android.content.ContentResolver
-import android.content.Context
-import android.content.Intent
-import android.graphics.Color
-import android.graphics.Rect
-import android.media.AudioManager
-import android.net.Uri
-import android.os.Bundle
-import android.os.Handler
-import android.os.Looper
-import android.text.format.DateUtils
-import android.util.Log
-import android.util.TypedValue
-import android.view.Menu
-import android.view.MenuItem
-import android.view.View
-import android.widget.FrameLayout
-import android.widget.ImageView
-import androidx.appcompat.app.AppCompatActivity
-import androidx.core.net.toUri
-import androidx.core.os.HandlerCompat
-import androidx.core.view.isVisible
-import androidx.media.AudioAttributesCompat
-import androidx.media2.common.MediaMetadata
-import androidx.media2.common.UriMediaItem
-import androidx.media2.player.MediaPlayer
-import com.arthenica.ffmpegkit.FFmpegKit
-import com.arthenica.ffmpegkit.FFmpegKitConfig
-import com.arthenica.ffmpegkit.FFmpegSession
-import com.arthenica.ffmpegkit.FFprobeKit
-import com.arthenica.ffmpegkit.MediaInformation
-import com.arthenica.ffmpegkit.ReturnCode
-import com.arthenica.ffmpegkit.Statistics
-import com.bumptech.glide.Glide
-import com.google.android.material.slider.RangeSlider
-import com.google.android.material.slider.Slider
-import org.pixeldroid.media_editor.R
-import org.pixeldroid.media_editor.databinding.ActivityVideoEditBinding
-import java.io.File
-import java.io.Serializable
-import kotlin.math.absoluteValue
-import kotlin.math.roundToInt
-
-const val TAG = "VideoEditActivity"
-
-class VideoEditActivity : AppCompatActivity() {
-
- data class RelativeCropPosition(
- // Width of the selected part of the video, relative to the width of the video
- val relativeWidth: Float = 1f,
- // Height of the selected part of the video, relative to the height of the video
- val relativeHeight: Float = 1f,
- // Distance of left corner of selected part, relative to the width of the video
- val relativeX: Float = 0f,
- // Distance of top of selected part, relative to the height of the video
- val relativeY: Float = 0f,
- ): Serializable {
- fun notCropped(): Boolean =
- (relativeWidth - 1f).absoluteValue < 0.001f
- && (relativeHeight - 1f).absoluteValue < 0.001f
- && relativeX.absoluteValue < 0.001f
- && relativeY.absoluteValue < 0.001f
-
- }
-
- data class VideoEditArguments(
- val muted: Boolean,
- val videoStart: Float?,
- val videoEnd: Float? ,
- val speedIndex: Int,
- val videoCrop: RelativeCropPosition,
- val videoStabilize: Float
- ): Serializable
-
- private lateinit var videoUri: Uri
- private lateinit var mediaPlayer: MediaPlayer
- private var videoPosition: Int = -1
-
- private var cropRelativeDimensions: RelativeCropPosition = RelativeCropPosition()
-
- private var stabilization: Float = 0f
- set(value){
- field = value
- if(value > 0.01f && value <= 100f){
- // Stabilization requested, show UI
- binding.stabilisationSaved.isVisible = true
- val typedValue = TypedValue()
- val color: Int = if (binding.stabilizer.context.theme
- .resolveAttribute(R.attr.colorSecondary, typedValue, true)
- ) typedValue.data else Color.TRANSPARENT
-
- binding.stabilizer.drawable.setTint(color)
- }
- else {
- binding.stabilisationSaved.isVisible = false
- binding.stabilizer.drawable.setTintList(null)
- }
- }
-
- private var speed: Int = 1
- set(value) {
- field = value
-
- mediaPlayer.playbackSpeed = speedChoices[value].toFloat()
-
- if(speed != 1) binding.muter.callOnClick()
- }
-
- private lateinit var binding: ActivityVideoEditBinding
- // Map photoData indexes to FFmpeg Session IDs
- private val sessionList: ArrayList = arrayListOf()
- private val tempFiles: ArrayList = ArrayList()
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- binding = ActivityVideoEditBinding.inflate(layoutInflater)
- setContentView(binding.root)
-
- supportActionBar?.setTitle(R.string.toolbar_title_edit)
- supportActionBar?.setDisplayHomeAsUpEnabled(true)
- supportActionBar?.setHomeButtonEnabled(true)
-
-
- binding.videoRangeSeekBar.setCustomThumbDrawablesForValues(R.drawable.thumb_left,R.drawable.double_circle,R.drawable.thumb_right)
- binding.videoRangeSeekBar.thumbRadius = 20.dpToPx(this)
-
-
- val resultHandler: Handler = HandlerCompat.createAsync(Looper.getMainLooper())
-
- videoUri = intent.getParcelableExtra(PhotoEditActivity.PICTURE_URI)!!
-
- videoPosition = intent.getIntExtra(PhotoEditActivity.PICTURE_POSITION, -1)
-
- val inputVideoPath = ffmpegCompliantUri(videoUri)
- val mediaInformation: MediaInformation? = FFprobeKit.getMediaInformation(inputVideoPath).mediaInformation
-
- //Duration in seconds, or null
- val duration: Float? = mediaInformation?.duration?.toFloatOrNull()
-
- binding.videoRangeSeekBar.valueFrom = 0f
- binding.videoRangeSeekBar.valueTo = duration ?: 100f
- binding.videoRangeSeekBar.values = listOf(0f,(duration?: 100f) / 2, duration ?: 100f)
-
-
- val mediaItem: UriMediaItem = UriMediaItem.Builder(videoUri).build()
- mediaItem.metadata = MediaMetadata.Builder()
- .putString(MediaMetadata.METADATA_KEY_TITLE, "")
- .build()
-
- mediaPlayer = MediaPlayer(this)
- mediaPlayer.setMediaItem(mediaItem)
-
- //binding.videoView.mediaControlView?.setMediaController()
-
- // Configure audio
- mediaPlayer.setAudioAttributes(AudioAttributesCompat.Builder()
- .setLegacyStreamType(AudioManager.STREAM_MUSIC)
- .setUsage(AudioAttributesCompat.USAGE_MEDIA)
- .setContentType(AudioAttributesCompat.CONTENT_TYPE_MOVIE)
- .build()
- )
-
- findViewById(R.id.progress_bar)?.visibility = View.GONE
-
- mediaPlayer.prepare()
-
-
- binding.muter.setOnClickListener {
- if(!binding.muter.isSelected) mediaPlayer.playerVolume = 0f
- else {
- mediaPlayer.playerVolume = 1f
- speed = 1
- }
- binding.muter.isSelected = !binding.muter.isSelected
- }
-
- binding.cropper.setOnClickListener {
- showCropInterface(show = true, uri = videoUri)
- }
-
- binding.saveCropButton.setOnClickListener {
- // This is the rectangle selected by the crop
- val cropRect = binding.cropImageView.cropWindowRect
-
- // This is the rectangle of the whole image
- val fullImageRect: Rect = binding.cropImageView.getInitialCropWindowRect()
-
- // x, y are coordinates of top left, in the ImageView
- val x = cropRect.left - fullImageRect.left
- val y = cropRect.top - fullImageRect.top
-
- // width and height selected by the crop
- val width = cropRect.width()
- val height = cropRect.height()
-
- // To avoid having to calculate the dimensions of the video here, we pass
- // relative width, height and x, y back to be treated in FFmpeg
- cropRelativeDimensions = RelativeCropPosition(
- relativeWidth = width/fullImageRect.width(),
- relativeHeight = height/fullImageRect.height(),
- relativeX = x/fullImageRect.width(),
- relativeY = y/fullImageRect.height()
- )
-
- // If a crop was saved, change the color of the crop button to give a visual indication
- if(!cropRelativeDimensions.notCropped()){
- val typedValue = TypedValue()
- val color: Int = if (binding.checkMarkCropped.context.theme
- .resolveAttribute(R.attr.colorSecondary, typedValue, true)
- ) typedValue.data else Color.TRANSPARENT
-
- binding.cropper.drawable.setTint(color)
- } else {
- // Else reset the tint
- binding.cropper.drawable.setTintList(null)
- }
-
- showCropInterface(show = false)
- }
-
- binding.videoView.setPlayer(mediaPlayer)
-
- mediaPlayer.seekTo((binding.videoRangeSeekBar.values[1]*1000).toLong())
-
- object : Runnable {
- override fun run() {
- val getCurrent = mediaPlayer.currentPosition / 1000f
- if(getCurrent >= binding.videoRangeSeekBar.values[0] && getCurrent <= binding.videoRangeSeekBar.values[2] ) {
- binding.videoRangeSeekBar.values = listOf(binding.videoRangeSeekBar.values[0],getCurrent, binding.videoRangeSeekBar.values[2])
- }
- Handler(Looper.getMainLooper()).postDelayed(this, 1000)
- }
- }.run()
-
- binding.videoRangeSeekBar.addOnChangeListener { rangeSlider: RangeSlider, value, fromUser ->
- // Responds to when the middle slider's value is changed
- if(fromUser && value != rangeSlider.values[0] && value != rangeSlider.values[2]) {
- mediaPlayer.seekTo((rangeSlider.values[1]*1000).toLong())
- }
- }
-
- binding.videoRangeSeekBar.setLabelFormatter { value: Float ->
- DateUtils.formatElapsedTime(value.toLong())
- }
-
-
-
- binding.speeder.setOnClickListener {
- AlertDialog.Builder(this).apply {
- setIcon(R.drawable.speed)
- setTitle(R.string.video_speed)
- setSingleChoiceItems(speedChoices.map { it.toString() + "x" }.toTypedArray(), speed) { dialog, which ->
- // update the selected item which is selected by the user so that it should be selected
- // when user opens the dialog next time and pass the instance to setSingleChoiceItems method
- speed = which
-
- // when selected an item the dialog should be closed with the dismiss method
- dialog.dismiss()
- }
- setNegativeButton(android.R.string.cancel) { _, _ -> }
- }.show()
- }
-
- binding.stabilizer.setOnClickListener {
- AlertDialog.Builder(this).apply {
- setIcon(R.drawable.video_stable)
- setTitle(R.string.stabilize_video_intensity)
- val slider = Slider(context).apply {
- valueFrom = 0f
- valueTo = 100f
- value = stabilization
- }
- setView(slider)
- setNegativeButton(android.R.string.cancel) { _, _ -> }
- setPositiveButton(android.R.string.ok) { _, _ -> stabilization = slider.value}
- }.show()
- }
-
-
- val thumbInterval: Float? = duration?.div(7)
-
- thumbInterval?.let {
- thumbnail(videoUri, resultHandler, binding.thumbnail1, it)
- thumbnail(videoUri, resultHandler, binding.thumbnail2, it.times(2))
- thumbnail(videoUri, resultHandler, binding.thumbnail3, it.times(3))
- thumbnail(videoUri, resultHandler, binding.thumbnail4, it.times(4))
- thumbnail(videoUri, resultHandler, binding.thumbnail5, it.times(5))
- thumbnail(videoUri, resultHandler, binding.thumbnail6, it.times(6))
- thumbnail(videoUri, resultHandler, binding.thumbnail7, it.times(7))
- }
-
- resetControls()
- }
-
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- menuInflater.inflate(R.menu.edit_menu, menu)
- return true
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
-
- when(item.itemId) {
- R.id.action_save -> {
- returnWithValues()
- }
- R.id.action_reset -> {
- resetControls()
- }
- }
-
- return super.onOptionsItemSelected(item)
- }
-
- override fun onBackPressed() {
- if(binding.cropImageView.isVisible) {
- showCropInterface(false)
- } else if (noEdits()) super.onBackPressed()
- else {
- val builder = AlertDialog.Builder(this)
- builder.apply {
- setMessage(R.string.save_before_returning)
- setPositiveButton(android.R.string.ok) { _, _ ->
- returnWithValues()
- }
- setNegativeButton(R.string.no_cancel_edit) { _, _ ->
- super.onBackPressed()
- }
- }
- // Create the AlertDialog
- builder.show()
- }
- }
-
- private fun noEdits(): Boolean {
- val videoPositions = binding.videoRangeSeekBar.values.let {
- it[0] == 0f && it[2] == binding.videoRangeSeekBar.valueTo
- }
- val muted = binding.muter.isSelected
- val speedUnchanged = speed == 1
-
- val stabilizationUnchanged = stabilization <= 0.01f || stabilization > 100.5f
-
- return !muted && videoPositions && speedUnchanged && cropRelativeDimensions.notCropped() && stabilizationUnchanged
- }
-
- private fun showCropInterface(show: Boolean, uri: Uri? = null){
- val visibilityOfOthers = if(show) View.GONE else View.VISIBLE
- val visibilityOfCrop = if(show) View.VISIBLE else View.GONE
-
- if(show) mediaPlayer.pause()
-
- if(show) binding.cropSavedCard.visibility = View.GONE
- else if(!cropRelativeDimensions.notCropped()) binding.cropSavedCard.visibility = View.VISIBLE
-
- binding.stabilisationSaved.visibility =
- if(!show && stabilization > 0.01f && stabilization <= 100f) View.VISIBLE
- else View.GONE
-
- binding.muter.visibility = visibilityOfOthers
- binding.speeder.visibility = visibilityOfOthers
- binding.cropper.visibility = visibilityOfOthers
- binding.stabilizer.visibility = visibilityOfOthers
- binding.videoRangeSeekBar.visibility = visibilityOfOthers
- binding.videoView.visibility = visibilityOfOthers
- binding.thumbnail1.visibility = visibilityOfOthers
- binding.thumbnail2.visibility = visibilityOfOthers
- binding.thumbnail3.visibility = visibilityOfOthers
- binding.thumbnail4.visibility = visibilityOfOthers
- binding.thumbnail5.visibility = visibilityOfOthers
- binding.thumbnail6.visibility = visibilityOfOthers
- binding.thumbnail7.visibility = visibilityOfOthers
-
-
- binding.cropImageView.visibility = visibilityOfCrop
- binding.saveCropButton.visibility = visibilityOfCrop
-
- if(show && uri != null) binding.cropImageView.setImageUriAsync(uri, cropRelativeDimensions)
- }
-
- private fun returnWithValues() {
- //TODO Check if some of these should be null to indicate no changes in that category? Ex start/end
- val intent = Intent()
- .apply {
- putExtra(PhotoEditActivity.PICTURE_POSITION, videoPosition)
- putExtra(VIDEO_ARGUMENTS_TAG, VideoEditArguments(
- binding.muter.isSelected, binding.videoRangeSeekBar.values.first(),
- binding.videoRangeSeekBar.values[2],
- speed,
- cropRelativeDimensions,
- stabilization
- )
- )
- putExtra(MODIFIED, !noEdits())
- addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
- }
- setResult(Activity.RESULT_OK, intent)
- finish()
- }
-
- private fun resetControls() {
- binding.videoRangeSeekBar.values = listOf(0f, binding.videoRangeSeekBar.valueTo/2, binding.videoRangeSeekBar.valueTo)
- binding.muter.isSelected = false
-
- binding.cropImageView.resetCropRect()
- cropRelativeDimensions = RelativeCropPosition()
- binding.cropper.drawable.setTintList(null)
- binding.stabilizer.drawable.setTintList(null)
- binding.cropSavedCard.visibility = View.GONE
- stabilization = 0f
- }
-
- override fun onDestroy() {
- super.onDestroy()
- sessionList.forEach {
- FFmpegKit.cancel(it)
- }
- tempFiles.forEach{
- it.delete()
- }
- mediaPlayer.close()
- }
-
- private fun thumbnail(
- inputUri: Uri?,
- resultHandler: Handler,
- thumbnail: ImageView,
- thumbTime: Float,
- ) {
- val file = File.createTempFile("temp_img", ".bmp", cacheDir)
- tempFiles.add(file)
- val fileUri = file.toUri()
- val ffmpegCompliantUri = ffmpegCompliantUri(inputUri)
-
- val outputImagePath =
- if(fileUri.toString().startsWith("content://"))
- FFmpegKitConfig.getSafParameterForWrite(this, fileUri)
- else fileUri.toString()
- val session = FFmpegKit.executeWithArgumentsAsync(arrayOf(
- "-noaccurate_seek", "-ss", "$thumbTime", "-i", ffmpegCompliantUri, "-vf",
- "scale=${thumbnail.width}:${thumbnail.height}",
- "-frames:v", "1", "-f", "image2", "-y", outputImagePath), { session ->
- val state = session.state
- val returnCode = session.returnCode
-
- if (ReturnCode.isSuccess(returnCode)) {
- // SUCCESS
- resultHandler.post {
- if(!this.isFinishing)
- Glide.with(this).load(outputImagePath).centerCrop().into(thumbnail)
- }
- }
- // CALLED WHEN SESSION IS EXECUTED
- Log.d("VideoEditActivity", "FFmpeg process exited with state $state and rc $returnCode.${session.failStackTrace}")
- },
- {/* CALLED WHEN SESSION PRINTS LOGS */ }, { /*CALLED WHEN SESSION GENERATES STATISTICS*/ })
- sessionList.add(session.sessionId)
- }
-
- override fun onPause() {
- super.onPause()
- mediaPlayer.pause()
- }
-
- companion object {
- const val VIDEO_ARGUMENTS_TAG = "org.pixeldroid.media_editor.VideoEditTag"
- // List of choices of speeds
- val speedChoices: List = listOf(0.5, 1, 2, 4, 8)
- const val MODIFIED = "VideoEditModifiedTag"
-
- /**
- * @param muted should audio tracks be removed in the output
- * @param videoStart when we want to start the video, in seconds, or null if we
- * don't want to remove the start
- * @param videoEnd when we want to end the video, in seconds, or null if we
- * don't want to remove the end
- */
- fun startEncoding(
- originalUri: Uri,
- arguments: VideoEditArguments,
- context: Context,
- //TODO make interfaces for these callbacks, or something more explicit
- registerNewFFmpegSession: (Uri, Long) -> Unit,
- trackTempFile: (File) -> Unit,
- videoEncodeProgress: (Uri, Int, Boolean, Uri?, Boolean) -> Unit,
- ) {
-
- // Having a meaningful suffix is necessary so that ffmpeg knows what to put in output
- val suffix = originalUri.fileExtension(context.contentResolver)
- val file = File.createTempFile("temp_video", ".$suffix", context.cacheDir)
- //val file = File.createTempFile("temp_video", ".webm", cacheDir)
- trackTempFile(file)
- val fileUri = file.toUri()
- val outputVideoPath = context.ffmpegCompliantUri(fileUri)
-
- val ffmpegCompliantUri: String = context.ffmpegCompliantUri(originalUri)
-
- val mediaInformation: MediaInformation? = FFprobeKit.getMediaInformation(context.ffmpegCompliantUri(originalUri)).mediaInformation
- val totalVideoDuration = mediaInformation?.duration?.toFloatOrNull()
-
- fun secondPass(stabilizeString: String = ""){
- val speed = speedChoices[arguments.speedIndex]
-
- val mutedString = if(arguments.muted || arguments.speedIndex != 1) "-an" else null
- val startString: List = if(arguments.videoStart != null) listOf("-ss", "${arguments.videoStart/speed.toFloat()}") else listOf(null, null)
-
- val endString: List = if(arguments.videoEnd != null) listOf("-to", "${arguments.videoEnd/speed.toFloat() - (arguments.videoStart ?: 0f)/speed.toFloat()}") else listOf(null, null)
-
- // iw and ih are variables for the original width and height values, FFmpeg will know them
- val cropString = if(arguments.videoCrop.notCropped()) "" else "crop=${arguments.videoCrop.relativeWidth}*iw:${arguments.videoCrop.relativeHeight}*ih:${arguments.videoCrop.relativeX}*iw:${arguments.videoCrop.relativeY}*ih"
- val separator = if(arguments.speedIndex != 1 && !arguments.videoCrop.notCropped()) "," else ""
- val speedString = if(arguments.speedIndex != 1) "setpts=PTS/${speed}" else ""
-
- val separatorStabilize = if(stabilizeString == "" || (speedString == "" && cropString == "")) "" else ","
-
- val speedAndCropString: List = if(arguments.speedIndex!= 1 || !arguments.videoCrop.notCropped() || stabilizeString.isNotEmpty())
- listOf("-filter:v", stabilizeString + separatorStabilize + speedString + separator + cropString)
- // Stream copy is not compatible with filter, but when not filtering we can copy the stream without re-encoding
- else listOf("-c", "copy")
-
- // This should be set when re-encoding is required (otherwise it defaults to mpeg which then doesn't play)
- val encodePreset: List = if(arguments.speedIndex != 1 && !arguments.videoCrop.notCropped()) listOf("-c:v", "libx264", "-preset", "ultrafast") else listOf(null, null, null, null)
-
- val session: FFmpegSession =
- FFmpegKit.executeWithArgumentsAsync(listOfNotNull(
- startString[0], startString[1],
- "-i", ffmpegCompliantUri,
- speedAndCropString[0], speedAndCropString[1],
- endString[0], endString[1],
- mutedString, "-y",
- encodePreset[0], encodePreset[1], encodePreset[2], encodePreset[3],
- outputVideoPath,
- ).toTypedArray(),
- //val session: FFmpegSession = FFmpegKit.executeAsync("$startString -i $inputSafePath $endString -c:v libvpx-vp9 -c:a copy -an -y $outputVideoPath",
- { session ->
- val returnCode = session.returnCode
- if (ReturnCode.isSuccess(returnCode)) {
-
- videoEncodeProgress(originalUri, 100, false, outputVideoPath.toUri(), false)
-
- Log.d(TAG, "Encode completed successfully in ${session.duration} milliseconds")
- } else {
- videoEncodeProgress(originalUri, 0, false, outputVideoPath.toUri(), true)
- Log.e(TAG, "Encode failed with state ${session.state} and rc $returnCode.${session.failStackTrace}")
- }
- },
- { log -> Log.d("PostCreationActivityEncoding", log.message) }
- ) { statistics: Statistics? ->
-
- val timeInMilliseconds: Int? = statistics?.time
- timeInMilliseconds?.let {
- if (timeInMilliseconds > 0) {
- val completePercentage = totalVideoDuration?.let {
- val speedupDurationModifier = speedChoices[arguments.speedIndex].toFloat()
-
- val newTotalDuration = (it - (arguments.videoStart ?: 0f) - (it - (arguments.videoEnd ?: it)))/speedupDurationModifier
- timeInMilliseconds / (10*newTotalDuration)
- }
- completePercentage?.let {
- val rounded: Int = it.roundToInt()
- videoEncodeProgress(originalUri, rounded, false, null, false)
- }
- Log.d(TAG, "Encoding video: %$completePercentage.")
- }
- }
- }
- registerNewFFmpegSession(originalUri, session.sessionId)
- }
-
- fun stabilizationFirstPass(){
-
- val shakeResultsFile = File.createTempFile("temp_shake_results", ".trf", context.cacheDir)
- trackTempFile(shakeResultsFile)
- val shakeResultsFileUri = shakeResultsFile.toUri()
- val shakeResultsFileSafeUri = context.ffmpegCompliantUri(shakeResultsFileUri).removePrefix("file://")
-
- val inputSafeUri: String = context.ffmpegCompliantUri(originalUri)
-
- // Map chosen "stabilization force" to shakiness, from 3 to 10
- val shakiness = (0f..100f).convert(arguments.videoStabilize, 3f..10f).roundToInt()
-
- val analyzeVideoCommandList = listOf(
- "-y", "-i", inputSafeUri,
- "-vf", "vidstabdetect=shakiness=$shakiness:accuracy=15:result=$shakeResultsFileSafeUri",
- "-f", "null", "-"
- ).toTypedArray()
-
- val session: FFmpegSession =
- FFmpegKit.executeWithArgumentsAsync(analyzeVideoCommandList,
- { firstPass ->
- if (ReturnCode.isSuccess(firstPass.returnCode)) {
- // Map chosen "stabilization force" to shakiness, from 8 to 40
- val smoothing = (0f..100f).convert(arguments.videoStabilize, 8f..40f).roundToInt()
-
- val stabilizeVideoCommand =
- "vidstabtransform=smoothing=$smoothing:input=${context.ffmpegCompliantUri(shakeResultsFileUri).removePrefix("file://")}"
- secondPass(stabilizeVideoCommand)
- } else {
- Log.e(
- "PostCreationActivityEncoding",
- "Video stabilization first pass failed!"
- )
- }
- },
- { log -> Log.d("PostCreationActivityEncoding", log.message) },
- { statistics: Statistics? ->
-
- val timeInMilliseconds: Int? = statistics?.time
- timeInMilliseconds?.let {
- if (timeInMilliseconds > 0) {
- val completePercentage = totalVideoDuration?.let {
- // At this stage, we didn't change speed or start/end of the video
- timeInMilliseconds / (10 * it)
- }
- completePercentage?.let {
- val rounded: Int = it.roundToInt()
- videoEncodeProgress(originalUri, rounded, true, null, false)
- }
-
- Log.d(TAG, "Stabilization pass: %$completePercentage.")
- }
- }
- })
- registerNewFFmpegSession(originalUri, session.sessionId)
- }
-
- if(arguments.videoStabilize > 0.01f) {
- // Stabilization was requested: we need an additional first pass to get stabilization data
- stabilizationFirstPass()
- } else {
- // Immediately call the second pass, no stabilization needed
- secondPass()
- }
-
- }
-
- fun cancelEncoding(){
- FFmpegKit.cancel()
- }
- fun cancelEncoding(sessionId: Long){
- FFmpegKit.cancel(sessionId)
- }
- }
-}
\ No newline at end of file
diff --git a/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/cropper/CropImageView.kt b/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/cropper/CropImageView.kt
deleted file mode 100644
index 46567226..00000000
--- a/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/cropper/CropImageView.kt
+++ /dev/null
@@ -1,105 +0,0 @@
-package org.pixeldroid.media_editor.photoEdit.cropper
-
-// Simplified version of https://github.com/ArthurHub/Android-Image-Cropper , which is
-// licensed under the Apache License, Version 2.0. The modifications made to it for PixelDroid
-// are under licensed under the GPLv3 or later, just like the rest of the PixelDroid project
-
-import android.content.Context
-import android.graphics.Rect
-import android.graphics.RectF
-import android.graphics.drawable.Drawable
-import android.net.Uri
-import android.util.AttributeSet
-import android.view.LayoutInflater
-import android.widget.FrameLayout
-import androidx.core.graphics.toRect
-import com.bumptech.glide.Glide
-import com.bumptech.glide.load.DataSource
-import com.bumptech.glide.load.engine.GlideException
-import com.bumptech.glide.request.RequestListener
-import com.bumptech.glide.request.target.Target
-import org.pixeldroid.media_editor.databinding.CropImageViewBinding
-import org.pixeldroid.media_editor.photoEdit.VideoEditActivity
-
-
-/** Custom view that provides cropping capabilities to an image. */
-class CropImageView @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null) :
- FrameLayout(context!!, attrs) {
-
-
- private val binding: CropImageViewBinding =
- CropImageViewBinding.inflate(LayoutInflater.from(context), this, true)
-
- init {
- binding.CropOverlayView.setInitialAttributeValues()
- }
-
- /**
- * Gets the crop window's position relative to the parent's view at screen.
- *
- * @return a Rect instance containing notCropped area boundaries of the source Bitmap
- */
- val cropWindowRect: RectF
- get() = binding.CropOverlayView.cropWindowRect
-
-
- /** Reset crop window to initial rectangle. */
- fun resetCropRect() {
- binding.CropOverlayView.resetCropWindowRect()
- }
-
- fun getInitialCropWindowRect(): Rect = binding.CropOverlayView.initialCropWindowRect
-
- /**
- * Sets the image loaded from the given URI as the content of the CropImageView
- *
- * @param uri the URI to load the image from
- */
- fun setImageUriAsync(uri: Uri, cropRelativeDimensions: VideoEditActivity.RelativeCropPosition) {
- // either no existing task is working or we canceled it, need to load new URI
- binding.CropOverlayView.initialCropWindowRect = Rect()
-
- Glide.with(this).load(uri).fitCenter().listener(object : RequestListener {
- override fun onLoadFailed(
- e: GlideException?,
- m: Any?,
- t: Target?,
- i: Boolean,
- ): Boolean {
- return false
- }
-
- override fun onResourceReady(
- resource: Drawable?,
- model: Any?,
- target: Target?,
- dataSource: DataSource?,
- isFirstResource: Boolean,
- ): Boolean {
- // Get width and height that the image will take on the screen
- val drawnWidth = resource?.intrinsicWidth ?: width
- val drawnHeight = resource?.intrinsicHeight ?: height
-
- binding.CropOverlayView.initialCropWindowRect = RectF(
- (width - drawnWidth) / 2f,
- (height - drawnHeight) / 2f,
- (width + drawnWidth) / 2f,
- (height + drawnHeight) / 2f
- ).toRect()
- binding.CropOverlayView.setCropWindowLimits(
- drawnWidth.toFloat(),
- drawnHeight.toFloat()
- )
- binding.CropOverlayView.invalidate()
- binding.CropOverlayView.setBounds(width, height)
- binding.CropOverlayView.resetCropOverlayView()
- if (!cropRelativeDimensions.notCropped()) binding.CropOverlayView.setRecordedCropWindowRect(cropRelativeDimensions)
- binding.CropOverlayView.visibility = VISIBLE
-
-
- // Indicate to Glide that the image hasn't been set yet
- return false
- }
- }).into(binding.ImageViewImage)
- }
-}
\ No newline at end of file
diff --git a/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/cropper/CropOverlayView.kt b/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/cropper/CropOverlayView.kt
deleted file mode 100644
index 152aa7c0..00000000
--- a/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/cropper/CropOverlayView.kt
+++ /dev/null
@@ -1,490 +0,0 @@
-package org.pixeldroid.media_editor.photoEdit.cropper
-
-// Simplified version of https://github.com/ArthurHub/Android-Image-Cropper , which is
-// licensed under the Apache License, Version 2.0. The modifications made to it for PixelDroid
-// are under licensed under the GPLv3 or later, just like the rest of the PixelDroid project
-
-
-import android.content.Context
-import android.content.res.Resources
-import android.graphics.Canvas
-import android.graphics.Color
-import android.graphics.Paint
-import android.graphics.Rect
-import android.graphics.RectF
-import android.util.AttributeSet
-import android.util.TypedValue
-import android.view.MotionEvent
-import android.view.View
-import org.pixeldroid.media_editor.photoEdit.VideoEditActivity.RelativeCropPosition
-import kotlin.math.max
-import kotlin.math.min
-
-/** A custom View representing the crop window and the shaded background outside the crop window. */
-class CropOverlayView // endregion
-@JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null) : View(context, attrs) {
- // region: Fields and Consts
- /** Handler from crop window stuff, moving and knowing position. */
- private val mCropWindowHandler = CropWindowHandler()
-
- /** The Paint used to draw the white rectangle around the crop area. */
- private var mBorderPaint: Paint? = null
-
- /** The Paint used to draw the corners of the Border */
- private var mBorderCornerPaint: Paint? = null
-
- /** The Paint used to draw the guidelines within the crop area when pressed. */
- private var mGuidelinePaint: Paint? = null
-
- /** The bounding box around the Bitmap that we are cropping. */
- private val mCalcBounds = RectF()
-
- /** The bounding image view width used to know the crop overlay is at view edges. */
- private var mViewWidth = 0
-
- /** The bounding image view height used to know the crop overlay is at view edges. */
- private var mViewHeight = 0
-
- /** The Handle that is currently pressed; null if no Handle is pressed. */
- private var mMoveHandler: CropWindowMoveHandler? = null
-
- /** the initial crop window rectangle to set */
- private val mInitialCropWindowRect = Rect()
-
- /** Whether the Crop View has been initialized for the first time */
- private var initializedCropWindow = false
- /** Get the left/top/right/bottom coordinates of the crop window. */
- /** Set the left/top/right/bottom coordinates of the crop window. */
- var cropWindowRect: RectF
- get() = mCropWindowHandler.rect
- set(rect) {
- mCropWindowHandler.rect = rect
- }
-
- /**
- * Informs the CropOverlayView of the image's position relative to the ImageView. This is
- * necessary to call in order to draw the crop window.
- *
- * @param viewWidth The bounding image view width.
- * @param viewHeight The bounding image view height.
- */
- fun setBounds(viewWidth: Int, viewHeight: Int) {
- mViewWidth = viewWidth
- mViewHeight = viewHeight
- val cropRect = mCropWindowHandler.rect
- if (cropRect.width() == 0f || cropRect.height() == 0f) {
- initCropWindow()
- }
- }
-
- /** Resets the crop overlay view. */
- fun resetCropOverlayView() {
- if (initializedCropWindow) {
- cropWindowRect = RectF()
- initCropWindow()
- invalidate()
- }
- }
-
- /**
- * Set the max width/height and scale factor of the shown image to original image to scale the
- * limits appropriately.
- */
- fun setCropWindowLimits(maxWidth: Float, maxHeight: Float) {
- mCropWindowHandler.setCropWindowLimits(maxWidth, maxHeight)
- }
- /** Get crop window initial rectangle. */
- /** Set crop window initial rectangle to be used instead of default. */
- var initialCropWindowRect: Rect
- get() = mInitialCropWindowRect
- set(rect) {
- mInitialCropWindowRect.set(rect)
- if (initializedCropWindow) {
- initCropWindow()
- invalidate()
- }
- }
-
- fun setRecordedCropWindowRect(relativeCropPosition: RelativeCropPosition) {
- val rect = RectF(
- mInitialCropWindowRect.left + relativeCropPosition.relativeX * mInitialCropWindowRect.width(),
- mInitialCropWindowRect.top + relativeCropPosition.relativeY * mInitialCropWindowRect.height(),
- relativeCropPosition.relativeWidth * mInitialCropWindowRect.width() + mInitialCropWindowRect.left + relativeCropPosition.relativeX * mInitialCropWindowRect.width(),
- relativeCropPosition.relativeHeight * mInitialCropWindowRect.height() + mInitialCropWindowRect.top + relativeCropPosition.relativeY * mInitialCropWindowRect.height()
- )
- mCropWindowHandler.rect = rect
- }
-
- /** Reset crop window to initial rectangle. */
- fun resetCropWindowRect() {
- if (initializedCropWindow) {
- initCropWindow()
- invalidate()
- }
- }
-
- /**
- * Sets all initial values, but does not call initCropWindow to reset the views.
- * Used once at the very start to initialize the attributes.
- */
- fun setInitialAttributeValues() {
- val dm = Resources.getSystem().displayMetrics
- mBorderPaint = getNewPaintOfThickness(
- TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3f, dm),
- Color.argb(170, 255, 255, 255)
- )
- mBorderCornerPaint = getNewPaintOfThickness(
- TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2f, dm),
- Color.WHITE
- )
- mGuidelinePaint = getNewPaintOfThickness(
- TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1f, dm),
- Color.argb(170, 255, 255, 255)
- )
- }
- // region: Private methods
- /**
- * Set the initial crop window size and position. This is dependent on the size and position of
- * the image being cropped.
- */
- private fun initCropWindow() {
- val rect = RectF()
-
- // Tells the attribute functions the crop window has already been initialized
- initializedCropWindow = true
- if (mInitialCropWindowRect.width() > 0 && mInitialCropWindowRect.height() > 0) {
- // Get crop window position relative to the displayed image.
- rect.left = mInitialCropWindowRect.left.toFloat()
- rect.top = mInitialCropWindowRect.top.toFloat()
- rect.right = rect.left + mInitialCropWindowRect.width()
- rect.bottom = rect.top + mInitialCropWindowRect.height()
- }
- fixCropWindowRectByRules(rect)
- mCropWindowHandler.rect = rect
- }
-
- /** Fix the given rect to fit into bitmap rect and follow min, max and aspect ratio rules. */
- private fun fixCropWindowRectByRules(rect: RectF) {
- if (rect.width() < mCropWindowHandler.minCropWidth) {
- val adj = (mCropWindowHandler.minCropWidth - rect.width()) / 2
- rect.left -= adj
- rect.right += adj
- }
- if (rect.height() < mCropWindowHandler.minCropHeight) {
- val adj = (mCropWindowHandler.minCropHeight - rect.height()) / 2
- rect.top -= adj
- rect.bottom += adj
- }
- if (rect.width() > mCropWindowHandler.maxCropWidth) {
- val adj = (rect.width() - mCropWindowHandler.maxCropWidth) / 2
- rect.left += adj
- rect.right -= adj
- }
- if (rect.height() > mCropWindowHandler.maxCropHeight) {
- val adj = (rect.height() - mCropWindowHandler.maxCropHeight) / 2
- rect.top += adj
- rect.bottom -= adj
- }
- setBounds()
- if (mCalcBounds.width() > 0 && mCalcBounds.height() > 0) {
- val leftLimit = max(mCalcBounds.left, 0f)
- val topLimit = max(mCalcBounds.top, 0f)
- val rightLimit = min(mCalcBounds.right, width.toFloat())
- val bottomLimit = min(mCalcBounds.bottom, height.toFloat())
- if (rect.left < leftLimit) {
- rect.left = leftLimit
- }
- if (rect.top < topLimit) {
- rect.top = topLimit
- }
- if (rect.right > rightLimit) {
- rect.right = rightLimit
- }
- if (rect.bottom > bottomLimit) {
- rect.bottom = bottomLimit
- }
- }
- }
-
- /**
- * Draw crop overview by drawing background over image not in the cropping area, then borders and
- * guidelines.
- */
- override fun onDraw(canvas: Canvas) {
- super.onDraw(canvas)
-
- // Draw translucent background for the notCropped area.
- drawBackground(canvas)
- if (mCropWindowHandler.showGuidelines()) {
- // Determines whether guidelines should be drawn or not
- if (mMoveHandler != null) {
- // Draw only when resizing
- drawGuidelines(canvas)
- }
- }
- drawBorders(canvas)
- drawCorners(canvas)
- }
-
- /** Draw shadow background over the image not including the crop area. */
- private fun drawBackground(canvas: Canvas) {
- val rect = mCropWindowHandler.rect
- val background = getNewPaint(Color.argb(119, 0, 0, 0))
- canvas.drawRect(
- mInitialCropWindowRect.left.toFloat(),
- mInitialCropWindowRect.top.toFloat(),
- rect.left,
- mInitialCropWindowRect.bottom.toFloat(),
- background
- )
- canvas.drawRect(
- rect.left,
- rect.bottom,
- mInitialCropWindowRect.right.toFloat(),
- mInitialCropWindowRect.bottom.toFloat(),
- background
- )
- canvas.drawRect(
- rect.right,
- mInitialCropWindowRect.top.toFloat(),
- mInitialCropWindowRect.right.toFloat(),
- rect.bottom,
- background
- )
- canvas.drawRect(
- rect.left,
- mInitialCropWindowRect.top.toFloat(),
- rect.right,
- rect.top,
- background
- )
- }
-
- /**
- * Draw 2 vertical and 2 horizontal guidelines inside the cropping area to split it into 9 equal
- * parts.
- */
- private fun drawGuidelines(canvas: Canvas) {
- if (mGuidelinePaint != null) {
- val sw: Float = if (mBorderPaint != null) mBorderPaint!!.strokeWidth else 0f
- val rect = mCropWindowHandler.rect
- rect.inset(sw, sw)
- val oneThirdCropWidth = rect.width() / 3
- val oneThirdCropHeight = rect.height() / 3
-
- // Draw vertical guidelines.
- val x1 = rect.left + oneThirdCropWidth
- val x2 = rect.right - oneThirdCropWidth
- canvas.drawLine(x1, rect.top, x1, rect.bottom, mGuidelinePaint!!)
- canvas.drawLine(x2, rect.top, x2, rect.bottom, mGuidelinePaint!!)
-
- // Draw horizontal guidelines.
- val y1 = rect.top + oneThirdCropHeight
- val y2 = rect.bottom - oneThirdCropHeight
- canvas.drawLine(rect.left, y1, rect.right, y1, mGuidelinePaint!!)
- canvas.drawLine(rect.left, y2, rect.right, y2, mGuidelinePaint!!)
- }
- }
-
- /** Draw borders of the crop area. */
- private fun drawBorders(canvas: Canvas) {
- if (mBorderPaint != null) {
- val w = mBorderPaint!!.strokeWidth
- val rect = mCropWindowHandler.rect
- // Make the rectangle a bit smaller to accommodate for the border
- rect.inset(w / 2, w / 2)
-
- // Draw rectangle crop window border.
- canvas.drawRect(rect, mBorderPaint!!)
- }
- }
-
- /** Draw the corner of crop overlay. */
- private fun drawCorners(canvas: Canvas) {
- val dm = Resources.getSystem().displayMetrics
- if (mBorderCornerPaint != null) {
- val lineWidth: Float = if (mBorderPaint != null) mBorderPaint!!.strokeWidth else 0f
- val cornerWidth = mBorderCornerPaint!!.strokeWidth
-
- // The corners should be a bit offset from the borders
- val w = (cornerWidth / 2
- + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, dm))
- val rect = mCropWindowHandler.rect
- rect.inset(w, w)
- val cornerOffset = (cornerWidth - lineWidth) / 2
- val cornerExtension = cornerWidth / 2 + cornerOffset
-
- /* the length of the border corner to draw */
- val mBorderCornerLength =
- TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14f, dm)
-
- // Top left
- canvas.drawLine(
- rect.left - cornerOffset,
- rect.top - cornerExtension,
- rect.left - cornerOffset,
- rect.top + mBorderCornerLength,
- mBorderCornerPaint!!
- )
- canvas.drawLine(
- rect.left - cornerExtension,
- rect.top - cornerOffset,
- rect.left + mBorderCornerLength,
- rect.top - cornerOffset,
- mBorderCornerPaint!!
- )
-
- // Top right
- canvas.drawLine(
- rect.right + cornerOffset,
- rect.top - cornerExtension,
- rect.right + cornerOffset,
- rect.top + mBorderCornerLength,
- mBorderCornerPaint!!
- )
- canvas.drawLine(
- rect.right + cornerExtension,
- rect.top - cornerOffset,
- rect.right - mBorderCornerLength,
- rect.top - cornerOffset,
- mBorderCornerPaint!!
- )
-
- // Bottom left
- canvas.drawLine(
- rect.left - cornerOffset,
- rect.bottom + cornerExtension,
- rect.left - cornerOffset,
- rect.bottom - mBorderCornerLength,
- mBorderCornerPaint!!
- )
- canvas.drawLine(
- rect.left - cornerExtension,
- rect.bottom + cornerOffset,
- rect.left + mBorderCornerLength,
- rect.bottom + cornerOffset,
- mBorderCornerPaint!!
- )
-
- // Bottom left
- canvas.drawLine(
- rect.right + cornerOffset,
- rect.bottom + cornerExtension,
- rect.right + cornerOffset,
- rect.bottom - mBorderCornerLength,
- mBorderCornerPaint!!
- )
- canvas.drawLine(
- rect.right + cornerExtension,
- rect.bottom + cornerOffset,
- rect.right - mBorderCornerLength,
- rect.bottom + cornerOffset,
- mBorderCornerPaint!!
- )
- }
- }
-
- override fun onTouchEvent(event: MotionEvent): Boolean {
- // If this View is not enabled, don't allow for touch interactions.
- return if (isEnabled) {
- /* Boolean to see if multi touch is enabled for the crop rectangle */
- when (event.action) {
- MotionEvent.ACTION_DOWN -> {
- onActionDown(event.x, event.y)
- true
- }
-
- MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
- parent.requestDisallowInterceptTouchEvent(false)
- onActionUp()
- true
- }
-
- MotionEvent.ACTION_MOVE -> {
- onActionMove(event.x, event.y)
- parent.requestDisallowInterceptTouchEvent(true)
- true
- }
-
- else -> false
- }
- } else {
- false
- }
- }
-
- /**
- * On press down start crop window movement depending on the location of the press.
- * if press is far from crop window then no move handler is returned (null).
- */
- private fun onActionDown(x: Float, y: Float) {
- val dm = Resources.getSystem().displayMetrics
- mMoveHandler = mCropWindowHandler.getMoveHandler(
- x,
- y,
- TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24f, dm)
- )
- if (mMoveHandler != null) {
- invalidate()
- }
- }
-
- /** Clear move handler starting in [.onActionDown] if exists. */
- private fun onActionUp() {
- if (mMoveHandler != null) {
- mMoveHandler = null
- invalidate()
- }
- }
-
- /**
- * Handle move of crop window using the move handler created in [.onActionDown].
- * The move handler will do the proper move/resize of the crop window.
- */
- private fun onActionMove(x: Float, y: Float) {
- if (mMoveHandler != null) {
- val rect = mCropWindowHandler.rect
- setBounds()
- val dm = Resources.getSystem().displayMetrics
- val snapRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3f, dm)
- mMoveHandler!!.move(
- rect,
- x,
- y,
- mCalcBounds,
- mViewWidth,
- mViewHeight,
- snapRadius
- )
- mCropWindowHandler.rect = rect
- invalidate()
- }
- }
-
- /**
- * Calculate the bounding rectangle for current crop window
- * The bounds rectangle is the bitmap rectangle
- */
- private fun setBounds() {
- mCalcBounds.set(mInitialCropWindowRect)
- }
-
- companion object {
- /** Creates the Paint object for drawing. */
- private fun getNewPaint(color: Int): Paint {
- val paint = Paint()
- paint.color = color
- return paint
- }
-
- /** Creates the Paint object for given thickness and color */
- private fun getNewPaintOfThickness(thickness: Float, color: Int): Paint {
- val borderPaint = Paint()
- borderPaint.color = color
- borderPaint.strokeWidth = thickness
- borderPaint.style = Paint.Style.STROKE
- borderPaint.isAntiAlias = true
- return borderPaint
- }
- }
-}
\ No newline at end of file
diff --git a/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/cropper/CropWindowHandler.kt b/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/cropper/CropWindowHandler.kt
deleted file mode 100644
index fb77634e..00000000
--- a/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/cropper/CropWindowHandler.kt
+++ /dev/null
@@ -1,269 +0,0 @@
-package org.pixeldroid.media_editor.photoEdit.cropper
-
-// Simplified version of https://github.com/ArthurHub/Android-Image-Cropper , which is
-// licensed under the Apache License, Version 2.0. The modifications made to it for PixelDroid
-// are under licensed under the GPLv3 or later, just like the rest of the PixelDroid project
-
-
-import android.content.res.Resources
-import android.graphics.RectF
-import android.util.TypedValue
-import kotlin.math.abs
-import kotlin.math.max
-import kotlin.math.min
-
-/** Handler from crop window stuff, moving and knowing position. */
-internal class CropWindowHandler {
- /** The 4 edges of the crop window defining its coordinates and size */
- private val mEdges = RectF()
-
- /**
- * Rectangle used to return the edges rectangle without ability to change it and without
- * creating new all the time.
- */
- private val mGetEdges = RectF()
-
- /** Maximum width in pixels that the crop window can CURRENTLY get. */
- private var mMaxCropWindowWidth = 0f
-
- /** Maximum height in pixels that the crop window can CURRENTLY get. */
- private var mMaxCropWindowHeight = 0f
-
- /** The left/top/right/bottom coordinates of the crop window. */
- var rect: RectF
- get() {
- mGetEdges.set(mEdges)
- return mGetEdges
- }
- set(rect) {
- mEdges.set(rect)
- }
-
- /** Minimum width in pixels that the crop window can get. */
- val minCropWidth: Float
- get() {
- val dm = Resources.getSystem().displayMetrics
- val mMinCropResultWidth = 40f
- return max(
- TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 42f, dm).toInt().toFloat(),
- mMinCropResultWidth
- )
- }
-
- /** Minimum height in pixels that the crop window can get. */
- val minCropHeight: Float
- get() {
- val dm = Resources.getSystem().displayMetrics
- val mMinCropResultHeight = 40f
- return max(
- TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 42f, dm).toInt().toFloat(),
- mMinCropResultHeight
- )
- }
-
- /** Maximum width in pixels that the crop window can get. */
- val maxCropWidth: Float
- get() {
- val mMaxCropResultWidth = 99999f
- return min(mMaxCropWindowWidth, mMaxCropResultWidth)
- }
-
- /** Maximum height in pixels that the crop window can get. */
- val maxCropHeight: Float
- get() {
- val mMaxCropResultHeight = 99999f
- return min(mMaxCropWindowHeight, mMaxCropResultHeight)
- }
-
- /**
- * Set the max width/height of the shown image to original image to scale the limits appropriately
- */
- fun setCropWindowLimits(maxWidth: Float, maxHeight: Float) {
- mMaxCropWindowWidth = maxWidth
- mMaxCropWindowHeight = maxHeight
- }
-
- /**
- * Indicates whether the crop window is small enough that the guidelines should be shown. Public
- * because this function is also used to determine if the center handle should be focused.
- *
- * @return boolean Whether the guidelines should be shown or not
- */
- fun showGuidelines(): Boolean {
- return !(mEdges.width() < 100 || mEdges.height() < 100)
- }
-
- /**
- * Determines which, if any, of the handles are pressed given the touch coordinates, the bounding
- * box, and the touch radius.
- *
- * @param x the x-coordinate of the touch point
- * @param y the y-coordinate of the touch point
- * @param targetRadius the target radius in pixels
- * @return the Handle that was pressed; null if no Handle was pressed
- */
- fun getMoveHandler(x: Float, y: Float, targetRadius: Float): CropWindowMoveHandler? {
- val type = getRectanglePressedMoveType(x, y, targetRadius)
- return if (type != null) CropWindowMoveHandler(type, this, x, y) else null
- }
- // region: Private methods
- /**
- * Determines which, if any, of the handles are pressed given the touch coordinates, the bounding
- * box, and the touch radius.
- *
- * @param x the x-coordinate of the touch point
- * @param y the y-coordinate of the touch point
- * @param targetRadius the target radius in pixels
- * @return the Handle that was pressed; null if no Handle was pressed
- */
- private fun getRectanglePressedMoveType(
- x: Float, y: Float, targetRadius: Float
- ): CropWindowMoveHandler.Type? {
- var moveType: CropWindowMoveHandler.Type? = null
-
- // Note: corner-handles take precedence, then side-handles, then center.
- if (isInCornerTargetZone(x, y, mEdges.left, mEdges.top, targetRadius)) {
- moveType = CropWindowMoveHandler.Type.TOP_LEFT
- } else if (isInCornerTargetZone(
- x, y, mEdges.right, mEdges.top, targetRadius
- )
- ) {
- moveType = CropWindowMoveHandler.Type.TOP_RIGHT
- } else if (isInCornerTargetZone(
- x, y, mEdges.left, mEdges.bottom, targetRadius
- )
- ) {
- moveType = CropWindowMoveHandler.Type.BOTTOM_LEFT
- } else if (isInCornerTargetZone(
- x, y, mEdges.right, mEdges.bottom, targetRadius
- )
- ) {
- moveType = CropWindowMoveHandler.Type.BOTTOM_RIGHT
- } else if (isInCenterTargetZone(
- x, y, mEdges.left, mEdges.top, mEdges.right, mEdges.bottom
- )
- && focusCenter()
- ) {
- moveType = CropWindowMoveHandler.Type.CENTER
- } else if (isInHorizontalTargetZone(
- x, y, mEdges.left, mEdges.right, mEdges.top, targetRadius
- )
- ) {
- moveType = CropWindowMoveHandler.Type.TOP
- } else if (isInHorizontalTargetZone(
- x, y, mEdges.left, mEdges.right, mEdges.bottom, targetRadius
- )
- ) {
- moveType = CropWindowMoveHandler.Type.BOTTOM
- } else if (isInVerticalTargetZone(
- x, y, mEdges.left, mEdges.top, mEdges.bottom, targetRadius
- )
- ) {
- moveType = CropWindowMoveHandler.Type.LEFT
- } else if (isInVerticalTargetZone(
- x, y, mEdges.right, mEdges.top, mEdges.bottom, targetRadius
- )
- ) {
- moveType = CropWindowMoveHandler.Type.RIGHT
- } else if (isInCenterTargetZone(
- x, y, mEdges.left, mEdges.top, mEdges.right, mEdges.bottom
- )
- && !focusCenter()
- ) {
- moveType = CropWindowMoveHandler.Type.CENTER
- }
- return moveType
- }
-
- /**
- * Determines if the cropper should focus on the center handle or the side handles. If it is a
- * small image, focus on the center handle so the user can move it. If it is a large image, focus
- * on the side handles so user can grab them. Corresponds to the appearance of the
- * RuleOfThirdsGuidelines.
- *
- * @return true if it is small enough such that it should focus on the center; less than
- * show_guidelines limit
- */
- private fun focusCenter(): Boolean = !showGuidelines()
-
- // endregion
-
- companion object {
- /**
- * Determines if the specified coordinate is in the target touch zone for a corner handle.
- *
- * @param x the x-coordinate of the touch point
- * @param y the y-coordinate of the touch point
- * @param handleX the x-coordinate of the corner handle
- * @param handleY the y-coordinate of the corner handle
- * @param targetRadius the target radius in pixels
- * @return true if the touch point is in the target touch zone; false otherwise
- */
- private fun isInCornerTargetZone(
- x: Float, y: Float, handleX: Float, handleY: Float, targetRadius: Float
- ): Boolean {
- return abs(x - handleX) <= targetRadius && abs(y - handleY) <= targetRadius
- }
-
- /**
- * Determines if the specified coordinate is in the target touch zone for a horizontal bar handle.
- *
- * @param x the x-coordinate of the touch point
- * @param y the y-coordinate of the touch point
- * @param handleXStart the left x-coordinate of the horizontal bar handle
- * @param handleXEnd the right x-coordinate of the horizontal bar handle
- * @param handleY the y-coordinate of the horizontal bar handle
- * @param targetRadius the target radius in pixels
- * @return true if the touch point is in the target touch zone; false otherwise
- */
- private fun isInHorizontalTargetZone(
- x: Float,
- y: Float,
- handleXStart: Float,
- handleXEnd: Float,
- handleY: Float,
- targetRadius: Float
- ): Boolean {
- return x > handleXStart && x < handleXEnd && abs(y - handleY) <= targetRadius
- }
-
- /**
- * Determines if the specified coordinate is in the target touch zone for a vertical bar handle.
- *
- * @param x the x-coordinate of the touch point
- * @param y the y-coordinate of the touch point
- * @param handleX the x-coordinate of the vertical bar handle
- * @param handleYStart the top y-coordinate of the vertical bar handle
- * @param handleYEnd the bottom y-coordinate of the vertical bar handle
- * @param targetRadius the target radius in pixels
- * @return true if the touch point is in the target touch zone; false otherwise
- */
- private fun isInVerticalTargetZone(
- x: Float,
- y: Float,
- handleX: Float,
- handleYStart: Float,
- handleYEnd: Float,
- targetRadius: Float
- ): Boolean {
- return abs(x - handleX) <= targetRadius && y > handleYStart && y < handleYEnd
- }
-
- /**
- * Determines if the specified coordinate falls anywhere inside the given bounds.
- *
- * @param x the x-coordinate of the touch point
- * @param y the y-coordinate of the touch point
- * @param left the x-coordinate of the left bound
- * @param top the y-coordinate of the top bound
- * @param right the x-coordinate of the right bound
- * @param bottom the y-coordinate of the bottom bound
- * @return true if the touch point is inside the bounding rectangle; false otherwise
- */
- private fun isInCenterTargetZone(
- x: Float, y: Float, left: Float, top: Float, right: Float, bottom: Float
- ): Boolean {
- return x > left && x < right && y > top && y < bottom
- }
- }
-}
\ No newline at end of file
diff --git a/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/cropper/CropWindowMoveHandler.kt b/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/cropper/CropWindowMoveHandler.kt
deleted file mode 100644
index 920d7774..00000000
--- a/mediaEditor/src/main/java/org/pixeldroid/media_editor/photoEdit/cropper/CropWindowMoveHandler.kt
+++ /dev/null
@@ -1,405 +0,0 @@
-package org.pixeldroid.media_editor.photoEdit.cropper
-
-// Simplified version of https://github.com/ArthurHub/Android-Image-Cropper , which is
-// licensed under the Apache License, Version 2.0. The modifications made to it for PixelDroid
-// are under licensed under the GPLv3 or later, just like the rest of the PixelDroid project
-
-import android.graphics.PointF
-import android.graphics.RectF
-
-/**
- * Handler to update crop window edges by the move type - Horizontal, Vertical, Corner or Center.
- */
-internal class CropWindowMoveHandler(
- /** The type of crop window move that is handled. */
- private val mType: Type,
- cropWindowHandler: CropWindowHandler, touchX: Float, touchY: Float
-) {
- /** Minimum width in pixels that the crop window can get. */
- private val mMinCropWidth: Float
-
- /** Minimum width in pixels that the crop window can get. */
- private val mMinCropHeight: Float
-
- /** Maximum height in pixels that the crop window can get. */
- private val mMaxCropWidth: Float
-
- /** Maximum height in pixels that the crop window can get. */
- private val mMaxCropHeight: Float
-
- /**
- * Holds the x and y offset between the exact touch location and the exact handle location that is
- * activated. There may be an offset because we allow for some leeway (specified by mHandleRadius)
- * in activating a handle. However, we want to maintain these offset values while the handle is
- * being dragged so that the handle doesn't jump.
- */
- private val mTouchOffset = PointF()
-
- init {
- mMinCropWidth = cropWindowHandler.minCropWidth
- mMinCropHeight = cropWindowHandler.minCropHeight
- mMaxCropWidth = cropWindowHandler.maxCropWidth
- mMaxCropHeight = cropWindowHandler.maxCropHeight
- calculateTouchOffset(cropWindowHandler.rect, touchX, touchY)
- }
-
- /**
- * Updates the crop window by change in the touch location.
- * Move type handled by this instance, as initialized in creation, affects how the change in
- * touch location changes the crop window position and size.
- * After the crop window position/size is changed by touch move it may result in values that
- * violate constraints: outside the bounds of the shown bitmap, smaller/larger than min/max size or
- * mismatch in aspect ratio. So a series of fixes is executed on "secondary" edges to adjust it
- * by the "primary" edge movement.
- * Primary is the edge directly affected by move type, secondary is the other edge.
- * The crop window is changed by directly setting the Edge coordinates.
- *
- * @param x the new x-coordinate of this handle
- * @param y the new y-coordinate of this handle
- * @param bounds the bounding rectangle of the image
- * @param viewWidth The bounding image view width used to know the crop overlay is at view edges.
- * @param viewHeight The bounding image view height used to know the crop overlay is at view
- * edges.
- * @param snapMargin the maximum distance (in pixels) at which the crop window should snap to the
- * image
- */
- fun move(
- rect: RectF,
- x: Float,
- y: Float,
- bounds: RectF,
- viewWidth: Int,
- viewHeight: Int,
- snapMargin: Float
- ) {
-
- // Adjust the coordinates for the finger position's offset (i.e. the
- // distance from the initial touch to the precise handle location).
- // We want to maintain the initial touch's distance to the pressed
- // handle so that the crop window size does not "jump".
- val adjX = x + mTouchOffset.x
- val adjY = y + mTouchOffset.y
- if (mType == Type.CENTER) {
- moveCenter(rect, adjX, adjY, bounds, viewWidth, viewHeight, snapMargin)
- } else {
- changeSize(rect, adjX, adjY, bounds, viewWidth, viewHeight, snapMargin)
- }
- }
- // region: Private methods
- /**
- * Calculates the offset of the touch point from the precise location of the specified handle.
- * Save these values in a member variable since we want to maintain this offset as we drag the
- * handle.
- */
- private fun calculateTouchOffset(rect: RectF, touchX: Float, touchY: Float) {
- var touchOffsetX = 0f
- var touchOffsetY = 0f
- when (mType) {
- Type.TOP_LEFT -> {
- touchOffsetX = rect.left - touchX
- touchOffsetY = rect.top - touchY
- }
-
- Type.TOP_RIGHT -> {
- touchOffsetX = rect.right - touchX
- touchOffsetY = rect.top - touchY
- }
-
- Type.BOTTOM_LEFT -> {
- touchOffsetX = rect.left - touchX
- touchOffsetY = rect.bottom - touchY
- }
-
- Type.BOTTOM_RIGHT -> {
- touchOffsetX = rect.right - touchX
- touchOffsetY = rect.bottom - touchY
- }
-
- Type.LEFT -> {
- touchOffsetX = rect.left - touchX
- touchOffsetY = 0f
- }
-
- Type.TOP -> {
- touchOffsetX = 0f
- touchOffsetY = rect.top - touchY
- }
-
- Type.RIGHT -> {
- touchOffsetX = rect.right - touchX
- touchOffsetY = 0f
- }
-
- Type.BOTTOM -> {
- touchOffsetX = 0f
- touchOffsetY = rect.bottom - touchY
- }
-
- Type.CENTER -> {
- touchOffsetX = rect.centerX() - touchX
- touchOffsetY = rect.centerY() - touchY
- }
- }
- mTouchOffset.x = touchOffsetX
- mTouchOffset.y = touchOffsetY
- }
-
- /** Center move only changes the position of the crop window without changing the size. */
- private fun moveCenter(
- rect: RectF,
- x: Float,
- y: Float,
- bounds: RectF,
- viewWidth: Int,
- viewHeight: Int,
- snapRadius: Float
- ) {
- var dx = x - rect.centerX()
- var dy = y - rect.centerY()
- if (rect.left + dx < 0 || rect.right + dx > viewWidth || rect.left + dx < bounds.left || rect.right + dx > bounds.right) {
- dx /= 1.05f
- mTouchOffset.x -= dx / 2
- }
- if (rect.top + dy < 0 || rect.bottom + dy > viewHeight || rect.top + dy < bounds.top || rect.bottom + dy > bounds.bottom) {
- dy /= 1.05f
- mTouchOffset.y -= dy / 2
- }
- rect.offset(dx, dy)
- snapEdgesToBounds(rect, bounds, snapRadius)
- }
-
- /**
- * Change the size of the crop window on the required edge (or edges in the case of a corner)
- */
- private fun changeSize(
- rect: RectF,
- x: Float,
- y: Float,
- bounds: RectF,
- viewWidth: Int,
- viewHeight: Int,
- snapMargin: Float
- ) {
- when (mType) {
- Type.TOP_LEFT -> {
- adjustTop(rect, y, bounds, snapMargin)
- adjustLeft(rect, x, bounds, snapMargin)
- }
-
- Type.TOP_RIGHT -> {
- adjustTop(rect, y, bounds, snapMargin)
- adjustRight(rect, x, bounds, viewWidth, snapMargin)
- }
-
- Type.BOTTOM_LEFT -> {
- adjustBottom(rect, y, bounds, viewHeight, snapMargin)
- adjustLeft(rect, x, bounds, snapMargin)
- }
-
- Type.BOTTOM_RIGHT -> {
- adjustBottom(rect, y, bounds, viewHeight, snapMargin)
- adjustRight(rect, x, bounds, viewWidth, snapMargin)
- }
-
- Type.LEFT -> adjustLeft(rect, x, bounds, snapMargin)
- Type.TOP -> adjustTop(rect, y, bounds, snapMargin)
- Type.RIGHT -> adjustRight(rect, x, bounds, viewWidth, snapMargin)
- Type.BOTTOM -> adjustBottom(rect, y, bounds, viewHeight, snapMargin)
- else -> {}
- }
- }
-
- /** Check if edges have gone out of bounds (including snap margin), and fix if needed. */
- private fun snapEdgesToBounds(edges: RectF, bounds: RectF, margin: Float) {
- if (edges.left < bounds.left + margin) {
- edges.offset(bounds.left - edges.left, 0f)
- }
- if (edges.top < bounds.top + margin) {
- edges.offset(0f, bounds.top - edges.top)
- }
- if (edges.right > bounds.right - margin) {
- edges.offset(bounds.right - edges.right, 0f)
- }
- if (edges.bottom > bounds.bottom - margin) {
- edges.offset(0f, bounds.bottom - edges.bottom)
- }
- }
-
- /**
- * Get the resulting x-position of the left edge of the crop window given the handle's position
- * and the image's bounding box and snap radius.
- *
- * @param left the position that the left edge is dragged to
- * @param bounds the bounding box of the image that is being notCropped
- * @param snapMargin the snap distance to the image edge (in pixels)
- */
- private fun adjustLeft(
- rect: RectF,
- left: Float,
- bounds: RectF,
- snapMargin: Float
- ) {
- var newLeft = left
- if (newLeft < 0) {
- newLeft /= 1.05f
- mTouchOffset.x -= newLeft / 1.1f
- }
- if (newLeft < bounds.left) {
- mTouchOffset.x -= (newLeft - bounds.left) / 2f
- }
- if (newLeft - bounds.left < snapMargin) {
- newLeft = bounds.left
- }
-
- // Checks if the window is too small horizontally
- if (rect.right - newLeft < mMinCropWidth) {
- newLeft = rect.right - mMinCropWidth
- }
-
- // Checks if the window is too large horizontally
- if (rect.right - newLeft > mMaxCropWidth) {
- newLeft = rect.right - mMaxCropWidth
- }
- if (newLeft - bounds.left < snapMargin) {
- newLeft = bounds.left
- }
- rect.left = newLeft
- }
-
- /**
- * Get the resulting x-position of the right edge of the crop window given the handle's position
- * and the image's bounding box and snap radius.
- *
- * @param right the position that the right edge is dragged to
- * @param bounds the bounding box of the image that is being notCropped
- * @param viewWidth
- * @param snapMargin the snap distance to the image edge (in pixels)
- */
- private fun adjustRight(
- rect: RectF,
- right: Float,
- bounds: RectF,
- viewWidth: Int,
- snapMargin: Float
- ) {
- var newRight = right
- if (newRight > viewWidth) {
- newRight = viewWidth + (newRight - viewWidth) / 1.05f
- mTouchOffset.x -= (newRight - viewWidth) / 1.1f
- }
- if (newRight > bounds.right) {
- mTouchOffset.x -= (newRight - bounds.right) / 2f
- }
-
- // If close to the edge
- if (bounds.right - newRight < snapMargin) {
- newRight = bounds.right
- }
-
- // Checks if the window is too small horizontally
- if (newRight - rect.left < mMinCropWidth) {
- newRight = rect.left + mMinCropWidth
- }
-
- // Checks if the window is too large horizontally
- if (newRight - rect.left > mMaxCropWidth) {
- newRight = rect.left + mMaxCropWidth
- }
-
- // If close to the edge
- if (bounds.right - newRight < snapMargin) {
- newRight = bounds.right
- }
- rect.right = newRight
- }
-
- /**
- * Get the resulting y-position of the top edge of the crop window given the handle's position and
- * the image's bounding box and snap radius.
- *
- * @param top the x-position that the top edge is dragged to
- * @param bounds the bounding box of the image that is being notCropped
- * @param snapMargin the snap distance to the image edge (in pixels)
- */
- private fun adjustTop(
- rect: RectF,
- top: Float,
- bounds: RectF,
- snapMargin: Float
- ) {
- var newTop = top
- if (newTop < 0) {
- newTop /= 1.05f
- mTouchOffset.y -= newTop / 1.1f
- }
- if (newTop < bounds.top) {
- mTouchOffset.y -= (newTop - bounds.top) / 2f
- }
- if (newTop - bounds.top < snapMargin) {
- newTop = bounds.top
- }
-
- // Checks if the window is too small vertically
- if (rect.bottom - newTop < mMinCropHeight) {
- newTop = rect.bottom - mMinCropHeight
- }
-
- // Checks if the window is too large vertically
- if (rect.bottom - newTop > mMaxCropHeight) {
- newTop = rect.bottom - mMaxCropHeight
- }
- if (newTop - bounds.top < snapMargin) {
- newTop = bounds.top
- }
- rect.top = newTop
- }
-
- /**
- * Get the resulting y-position of the bottom edge of the crop window given the handle's position
- * and the image's bounding box and snap radius.
- *
- * @param bottom the position that the bottom edge is dragged to
- * @param bounds the bounding box of the image that is being notCropped
- * @param viewHeight
- * @param snapMargin the snap distance to the image edge (in pixels)
- */
- private fun adjustBottom(
- rect: RectF,
- bottom: Float,
- bounds: RectF,
- viewHeight: Int,
- snapMargin: Float
- ) {
- var newBottom = bottom
- if (newBottom > viewHeight) {
- newBottom = viewHeight + (newBottom - viewHeight) / 1.05f
- mTouchOffset.y -= (newBottom - viewHeight) / 1.1f
- }
- if (newBottom > bounds.bottom) {
- mTouchOffset.y -= (newBottom - bounds.bottom) / 2f
- }
- if (bounds.bottom - newBottom < snapMargin) {
- newBottom = bounds.bottom
- }
-
- // Checks if the window is too small vertically
- if (newBottom - rect.top < mMinCropHeight) {
- newBottom = rect.top + mMinCropHeight
- }
-
- // Checks if the window is too small vertically
- if (newBottom - rect.top > mMaxCropHeight) {
- newBottom = rect.top + mMaxCropHeight
- }
- if (bounds.bottom - newBottom < snapMargin) {
- newBottom = bounds.bottom
- }
- rect.bottom = newBottom
- }
- // endregion
-
- /** The type of crop window move that is handled. */
- enum class Type {
- TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, LEFT, TOP, RIGHT, BOTTOM, CENTER
- }
-}
\ No newline at end of file
diff --git a/mediaEditor/src/main/res/drawable/check_circle_24.xml b/mediaEditor/src/main/res/drawable/check_circle_24.xml
deleted file mode 100644
index 86bf1fbe..00000000
--- a/mediaEditor/src/main/res/drawable/check_circle_24.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
diff --git a/mediaEditor/src/main/res/drawable/double_circle.xml b/mediaEditor/src/main/res/drawable/double_circle.xml
deleted file mode 100644
index 907d1235..00000000
--- a/mediaEditor/src/main/res/drawable/double_circle.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
diff --git a/mediaEditor/src/main/res/drawable/ic_crop_black_24dp.xml b/mediaEditor/src/main/res/drawable/ic_crop_black_24dp.xml
deleted file mode 100644
index 6875a20b..00000000
--- a/mediaEditor/src/main/res/drawable/ic_crop_black_24dp.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/mediaEditor/src/main/res/drawable/ic_save_24dp.xml b/mediaEditor/src/main/res/drawable/ic_save_24dp.xml
deleted file mode 100644
index 8cf3d5a7..00000000
--- a/mediaEditor/src/main/res/drawable/ic_save_24dp.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
diff --git a/mediaEditor/src/main/res/drawable/restore_24dp.xml b/mediaEditor/src/main/res/drawable/restore_24dp.xml
deleted file mode 100644
index 0abe38ba..00000000
--- a/mediaEditor/src/main/res/drawable/restore_24dp.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/mediaEditor/src/main/res/drawable/selector_mute.xml b/mediaEditor/src/main/res/drawable/selector_mute.xml
deleted file mode 100644
index 7103cc0c..00000000
--- a/mediaEditor/src/main/res/drawable/selector_mute.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/mediaEditor/src/main/res/drawable/speed.xml b/mediaEditor/src/main/res/drawable/speed.xml
deleted file mode 100644
index 1add8a09..00000000
--- a/mediaEditor/src/main/res/drawable/speed.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
diff --git a/mediaEditor/src/main/res/drawable/thumb_left.xml b/mediaEditor/src/main/res/drawable/thumb_left.xml
deleted file mode 100644
index d6c10419..00000000
--- a/mediaEditor/src/main/res/drawable/thumb_left.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
-
-
diff --git a/mediaEditor/src/main/res/drawable/thumb_right.xml b/mediaEditor/src/main/res/drawable/thumb_right.xml
deleted file mode 100644
index 6b5f6222..00000000
--- a/mediaEditor/src/main/res/drawable/thumb_right.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/mediaEditor/src/main/res/drawable/video_stable.xml b/mediaEditor/src/main/res/drawable/video_stable.xml
deleted file mode 100644
index 7456637a..00000000
--- a/mediaEditor/src/main/res/drawable/video_stable.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
diff --git a/mediaEditor/src/main/res/drawable/volume_off.xml b/mediaEditor/src/main/res/drawable/volume_off.xml
deleted file mode 100644
index b71ede34..00000000
--- a/mediaEditor/src/main/res/drawable/volume_off.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
diff --git a/mediaEditor/src/main/res/drawable/volume_up.xml b/mediaEditor/src/main/res/drawable/volume_up.xml
deleted file mode 100644
index 836cad86..00000000
--- a/mediaEditor/src/main/res/drawable/volume_up.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
diff --git a/mediaEditor/src/main/res/layout/activity_photo_edit.xml b/mediaEditor/src/main/res/layout/activity_photo_edit.xml
deleted file mode 100644
index a2e75050..00000000
--- a/mediaEditor/src/main/res/layout/activity_photo_edit.xml
+++ /dev/null
@@ -1,89 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/mediaEditor/src/main/res/layout/activity_video_edit.xml b/mediaEditor/src/main/res/layout/activity_video_edit.xml
deleted file mode 100644
index 5398b6b5..00000000
--- a/mediaEditor/src/main/res/layout/activity_video_edit.xml
+++ /dev/null
@@ -1,251 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/mediaEditor/src/main/res/layout/crop_image_activity.xml b/mediaEditor/src/main/res/layout/crop_image_activity.xml
deleted file mode 100644
index d78bde52..00000000
--- a/mediaEditor/src/main/res/layout/crop_image_activity.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
\ No newline at end of file
diff --git a/mediaEditor/src/main/res/layout/crop_image_view.xml b/mediaEditor/src/main/res/layout/crop_image_view.xml
deleted file mode 100644
index 080a4aa0..00000000
--- a/mediaEditor/src/main/res/layout/crop_image_view.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/mediaEditor/src/main/res/layout/fragment_edit_image.xml b/mediaEditor/src/main/res/layout/fragment_edit_image.xml
deleted file mode 100644
index 8875d589..00000000
--- a/mediaEditor/src/main/res/layout/fragment_edit_image.xml
+++ /dev/null
@@ -1,79 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/mediaEditor/src/main/res/layout/fragment_filter_list.xml b/mediaEditor/src/main/res/layout/fragment_filter_list.xml
deleted file mode 100644
index 729b401e..00000000
--- a/mediaEditor/src/main/res/layout/fragment_filter_list.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/mediaEditor/src/main/res/layout/thumbnail_list_item.xml b/mediaEditor/src/main/res/layout/thumbnail_list_item.xml
deleted file mode 100644
index 98f5cf9d..00000000
--- a/mediaEditor/src/main/res/layout/thumbnail_list_item.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/mediaEditor/src/main/res/menu/edit_menu.xml b/mediaEditor/src/main/res/menu/edit_menu.xml
deleted file mode 100644
index 4730db3b..00000000
--- a/mediaEditor/src/main/res/menu/edit_menu.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
\ No newline at end of file
diff --git a/mediaEditor/src/main/res/values-night/themes.xml b/mediaEditor/src/main/res/values-night/themes.xml
deleted file mode 100644
index 11414c70..00000000
--- a/mediaEditor/src/main/res/values-night/themes.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/mediaEditor/src/main/res/values/colors.xml b/mediaEditor/src/main/res/values/colors.xml
deleted file mode 100644
index f8c6127d..00000000
--- a/mediaEditor/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
- #FFBB86FC
- #FF6200EE
- #FF3700B3
- #FF03DAC5
- #FF018786
- #FF000000
- #FFFFFFFF
-
\ No newline at end of file
diff --git a/mediaEditor/src/main/res/values/public.xml b/mediaEditor/src/main/res/values/public.xml
deleted file mode 100644
index 7eb22d3b..00000000
--- a/mediaEditor/src/main/res/values/public.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/mediaEditor/src/main/res/values/strings.xml b/mediaEditor/src/main/res/values/strings.xml
deleted file mode 100644
index 9a2d6d5a..00000000
--- a/mediaEditor/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-
- Media Editor
-
- Brightness
- Contrast
- Saturation
- Filters
- Edit
- Thumbnail of filter
- Normal
- Still processing image, wait for that to finish first!
- OK, wait for that.
- "Couldn't retrieve image after crop"
- Preview of the image being edited
- Button to crop or rotate the image
- Save your edits?
- No, cancel edit
- Error while editing
- Edit
- Stabilize video
- Change intensity of stabilization
- Unable to save image
- Image successfully saved
- Mute video
- Save crop
- Crop video
- Select what to keep of the video
- Change video speed
- Crop saved
- Stabilization saved
- Reel showing thumbnails of the video you are editing
- RESET
- SAVE
- Permission denied
-
-
\ No newline at end of file
diff --git a/mediaEditor/src/main/res/values/themes.xml b/mediaEditor/src/main/res/values/themes.xml
deleted file mode 100644
index f394411b..00000000
--- a/mediaEditor/src/main/res/values/themes.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/mediaEditor/src/test/java/org/pixeldroid/media_editor/ExampleUnitTest.kt b/mediaEditor/src/test/java/org/pixeldroid/media_editor/ExampleUnitTest.kt
deleted file mode 100644
index 5842564e..00000000
--- a/mediaEditor/src/test/java/org/pixeldroid/media_editor/ExampleUnitTest.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package org.pixeldroid.media_editor
-
-import org.junit.Test
-
-import org.junit.Assert.*
-
-/**
- * Example local unit test, which will execute on the development machine (host).
- *
- * See [testing documentation](http://d.android.com/tools/testing).
- */
-class ExampleUnitTest {
- @Test
- fun addition_isCorrect() {
- assertEquals(4, 2 + 2)
- }
-}
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index b5f573c9..706f76d9 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,5 +1,4 @@
rootProject.name='PixelDroid'
include ':app'
include ':scrambler'
-project(':scrambler').projectDir = new File(rootDir, 'scrambler/scrambler/')
-include ':mediaEditor'
\ No newline at end of file
+project(':scrambler').projectDir = new File(rootDir, 'scrambler/scrambler/')
\ No newline at end of file