2020-05-08 10:53:56 +02:00
|
|
|
package com.h.pixeldroid
|
|
|
|
|
2020-05-15 11:46:12 +02:00
|
|
|
import android.app.Activity
|
|
|
|
import android.content.Context
|
2020-05-08 10:53:56 +02:00
|
|
|
import android.content.Intent
|
|
|
|
import android.content.pm.PackageManager
|
|
|
|
import android.graphics.Bitmap
|
2020-05-15 11:46:12 +02:00
|
|
|
import android.graphics.Bitmap.CompressFormat
|
|
|
|
import android.graphics.BitmapFactory
|
|
|
|
import android.graphics.Point
|
|
|
|
import android.graphics.drawable.BitmapDrawable
|
2020-05-08 10:53:56 +02:00
|
|
|
import android.net.Uri
|
|
|
|
import android.os.Bundle
|
|
|
|
import android.provider.MediaStore
|
2020-05-15 11:46:12 +02:00
|
|
|
import android.util.Log
|
2020-05-08 10:53:56 +02:00
|
|
|
import android.view.Menu
|
|
|
|
import android.view.MenuItem
|
2020-05-15 11:46:12 +02:00
|
|
|
import android.widget.Toast
|
2020-05-08 10:53:56 +02:00
|
|
|
import androidx.appcompat.app.AppCompatActivity
|
|
|
|
import androidx.core.app.ActivityCompat
|
|
|
|
import androidx.core.content.ContextCompat
|
2020-05-15 11:46:12 +02:00
|
|
|
import androidx.fragment.app.Fragment
|
|
|
|
import com.bumptech.glide.Glide
|
|
|
|
import com.bumptech.glide.request.RequestOptions
|
|
|
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
2020-05-08 10:53:56 +02:00
|
|
|
import com.google.android.material.snackbar.Snackbar
|
|
|
|
import com.google.android.material.tabs.TabLayout
|
|
|
|
import com.h.pixeldroid.adapters.EditPhotoViewPagerAdapter
|
|
|
|
import com.h.pixeldroid.fragments.EditImageFragment
|
|
|
|
import com.h.pixeldroid.fragments.FilterListFragment
|
|
|
|
import com.h.pixeldroid.interfaces.EditImageFragmentListener
|
|
|
|
import com.h.pixeldroid.interfaces.FilterListFragmentListener
|
|
|
|
import com.h.pixeldroid.utils.NonSwipeableViewPager
|
2020-05-15 11:46:12 +02:00
|
|
|
import com.yalantis.ucrop.UCrop
|
2020-05-08 10:53:56 +02:00
|
|
|
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 kotlinx.android.synthetic.main.activity_photo_edit.*
|
|
|
|
import kotlinx.android.synthetic.main.content_photo_edit.*
|
2020-05-15 11:46:12 +02:00
|
|
|
import java.io.ByteArrayOutputStream
|
2020-05-08 10:53:56 +02:00
|
|
|
import java.io.File
|
|
|
|
import java.io.IOException
|
|
|
|
import java.text.SimpleDateFormat
|
|
|
|
import java.util.*
|
|
|
|
|
|
|
|
// 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_SAVE_PHOTO = 8
|
|
|
|
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(), FilterListFragmentListener, EditImageFragmentListener {
|
|
|
|
|
2020-05-15 11:46:12 +02:00
|
|
|
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
|
2020-05-08 10:53:56 +02:00
|
|
|
|
|
|
|
private var originalImage: Bitmap? = null
|
2020-05-15 11:46:12 +02:00
|
|
|
private var compressedImage: Bitmap? = null
|
|
|
|
private var compressedOriginalImage: Bitmap? = null
|
2020-05-08 10:53:56 +02:00
|
|
|
private lateinit var filteredImage: Bitmap
|
|
|
|
private lateinit var finalImage: Bitmap
|
|
|
|
|
2020-05-15 11:46:12 +02:00
|
|
|
private var actualFilter: Filter? = null
|
|
|
|
|
2020-05-08 10:53:56 +02:00
|
|
|
private lateinit var filterListFragment: FilterListFragment
|
|
|
|
private lateinit var editImageFragment: EditImageFragment
|
|
|
|
|
|
|
|
private lateinit var outputDirectory: File
|
|
|
|
|
|
|
|
lateinit var viewPager: NonSwipeableViewPager
|
|
|
|
lateinit var tabLayout: TabLayout
|
|
|
|
|
2020-05-15 11:46:12 +02:00
|
|
|
private var brightnessFinal = BRIGHTNESS_START
|
|
|
|
private var saturationFinal = SATURATION_START
|
|
|
|
private var contrastFinal = CONTRAST_START
|
2020-05-08 10:53:56 +02:00
|
|
|
|
2020-05-15 11:46:12 +02:00
|
|
|
private var imageUri: Uri? = null
|
|
|
|
private var cropUri: Uri? = null
|
2020-05-08 10:53:56 +02:00
|
|
|
|
|
|
|
object URI {var picture_uri: Uri? = null}
|
|
|
|
|
|
|
|
init {
|
|
|
|
System.loadLibrary("NativeImageProcessor")
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
|
super.onCreate(savedInstanceState)
|
|
|
|
setContentView(R.layout.activity_photo_edit)
|
|
|
|
|
|
|
|
setSupportActionBar(toolbar)
|
|
|
|
supportActionBar!!.title = "Edit"
|
|
|
|
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
|
|
|
supportActionBar!!.setHomeButtonEnabled(true)
|
|
|
|
|
2020-05-15 12:10:28 +02:00
|
|
|
cropUri = intent.getParcelableExtra("picture_uri")
|
2020-05-15 11:46:12 +02:00
|
|
|
|
2020-05-08 10:53:56 +02:00
|
|
|
loadImage()
|
2020-05-15 11:46:12 +02:00
|
|
|
val file = File.createTempFile("temp_compressed_img", ".png", cacheDir)
|
|
|
|
file.writeBitmap(compressedImage!!)
|
|
|
|
URI.picture_uri = Uri.fromFile(file)
|
2020-05-08 10:53:56 +02:00
|
|
|
|
|
|
|
viewPager = findViewById(R.id.viewPager)
|
|
|
|
tabLayout = findViewById(R.id.tabs)
|
|
|
|
setupViewPager(viewPager)
|
|
|
|
tabLayout.setupWithViewPager(viewPager)
|
|
|
|
outputDirectory = getOutputDirectory()
|
|
|
|
|
2020-05-15 11:46:12 +02:00
|
|
|
val cropButton: FloatingActionButton = findViewById(R.id.cropImageButton)
|
|
|
|
// set on-click listener
|
|
|
|
cropButton.setOnClickListener {
|
|
|
|
startCrop()
|
|
|
|
}
|
2020-05-08 10:53:56 +02:00
|
|
|
}
|
|
|
|
|
2020-05-15 11:46:12 +02:00
|
|
|
//<editor-fold desc="ON LAUNCH">
|
2020-05-08 10:53:56 +02:00
|
|
|
private fun loadImage() {
|
2020-05-15 11:46:12 +02:00
|
|
|
originalImage = MediaStore.Images.Media.getBitmap(contentResolver, cropUri)
|
|
|
|
compressedImage = resizeImage(originalImage!!.copy(BITMAP_CONFIG, true))
|
|
|
|
compressedOriginalImage = compressedImage!!.copy(BITMAP_CONFIG, true)
|
|
|
|
filteredImage = compressedImage!!.copy(BITMAP_CONFIG, true)
|
|
|
|
image_preview.setImageBitmap(compressedImage)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun resizeImage(image: Bitmap): Bitmap {
|
|
|
|
val display = windowManager.defaultDisplay
|
|
|
|
val size = Point()
|
|
|
|
display.getSize(size)
|
2020-05-08 10:53:56 +02:00
|
|
|
|
2020-05-15 11:46:12 +02:00
|
|
|
val newY = size.y * 0.7
|
|
|
|
val scale = newY / image.height
|
|
|
|
return Bitmap.createScaledBitmap(image, (image.width * scale).toInt(), newY.toInt(), true)
|
2020-05-08 10:53:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private fun setupViewPager(viewPager: NonSwipeableViewPager?) {
|
|
|
|
val adapter = EditPhotoViewPagerAdapter(supportFragmentManager)
|
|
|
|
|
|
|
|
filterListFragment = FilterListFragment()
|
|
|
|
filterListFragment.setListener(this)
|
|
|
|
|
|
|
|
editImageFragment = EditImageFragment()
|
|
|
|
editImageFragment.setListener(this)
|
|
|
|
adapter.addFragment(filterListFragment, "FILTERS")
|
|
|
|
adapter.addFragment(editImageFragment, "EDIT")
|
|
|
|
|
|
|
|
viewPager!!.adapter = adapter
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
|
|
|
menuInflater.inflate(R.menu.edit_photo_menu, menu)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
|
|
|
|
|
|
|
when(item.itemId) {
|
|
|
|
android.R.id.home -> {
|
|
|
|
super.onBackPressed()
|
|
|
|
}
|
|
|
|
R.id.action_upload -> {
|
|
|
|
saveImageToGallery(false)
|
|
|
|
}
|
|
|
|
R.id.action_save -> {
|
|
|
|
saveImageToGallery(true)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return super.onOptionsItemSelected(item)
|
|
|
|
}
|
|
|
|
|
2020-05-15 11:46:12 +02:00
|
|
|
//</editor-fold>
|
|
|
|
//<editor-fold desc="FILTERS">
|
|
|
|
|
|
|
|
override fun onFilterSelected(filter: Filter) {
|
|
|
|
resetControls()
|
|
|
|
filteredImage = compressedOriginalImage!!.copy(BITMAP_CONFIG, true)
|
|
|
|
image_preview.setImageBitmap(filter.processFilter(filteredImage))
|
|
|
|
compressedImage = filteredImage.copy(BITMAP_CONFIG, true)
|
|
|
|
actualFilter = filter
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun resetControls() {
|
|
|
|
editImageFragment.resetControl()
|
|
|
|
|
|
|
|
brightnessFinal = BRIGHTNESS_START
|
|
|
|
saturationFinal = SATURATION_START
|
|
|
|
contrastFinal = CONTRAST_START
|
|
|
|
}
|
|
|
|
|
|
|
|
//</editor-fold>
|
|
|
|
//<editor-fold desc="EDITS">
|
|
|
|
|
|
|
|
private fun applyFilterAndShowImage(filter: Filter, image: Bitmap?) {
|
|
|
|
image_preview.setImageBitmap(filter.processFilter(image!!.copy(BITMAP_CONFIG, true)))
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onBrightnessChange(brightness: Int) {
|
|
|
|
brightnessFinal = brightness
|
|
|
|
val myFilter = Filter()
|
|
|
|
myFilter.addSubFilter(BrightnessSubFilter(brightness))
|
|
|
|
applyFilterAndShowImage(myFilter, filteredImage)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onSaturationChange(saturation: Float) {
|
|
|
|
saturationFinal = saturation
|
|
|
|
val myFilter = Filter()
|
|
|
|
myFilter.addSubFilter(SaturationSubfilter(saturation))
|
|
|
|
applyFilterAndShowImage(myFilter, filteredImage)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onContrastChange(contrast: Float) {
|
|
|
|
contrastFinal = contrast
|
|
|
|
val myFilter = Filter()
|
|
|
|
myFilter.addSubFilter(ContrastSubFilter(contrast))
|
|
|
|
applyFilterAndShowImage(myFilter, filteredImage)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun addEditFilters(filter: Filter, br: Int, sa: Float, co: Float): Filter {
|
|
|
|
filter.addSubFilter(BrightnessSubFilter(br))
|
|
|
|
filter.addSubFilter(ContrastSubFilter(co))
|
|
|
|
filter.addSubFilter(SaturationSubfilter(sa))
|
|
|
|
|
|
|
|
return filter
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onEditStarted() {
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onEditCompleted() {
|
|
|
|
val bitmap = filteredImage.copy(BITMAP_CONFIG, true)
|
|
|
|
val myFilter = Filter()
|
|
|
|
addEditFilters(myFilter, brightnessFinal, saturationFinal, contrastFinal)
|
|
|
|
|
|
|
|
compressedImage = myFilter.processFilter(bitmap)
|
|
|
|
}
|
|
|
|
|
|
|
|
//</editor-fold>
|
|
|
|
//<editor-fold desc="CROPPING">
|
|
|
|
|
|
|
|
private fun startCrop() {
|
|
|
|
applyFinalFilters(MediaStore.Images.Media.getBitmap(contentResolver, cropUri))
|
|
|
|
val file = File.createTempFile("temp_crop_img", ".png", cacheDir)
|
|
|
|
file.writeBitmap(finalImage)
|
|
|
|
|
|
|
|
val uCrop: UCrop = UCrop.of(Uri.fromFile(file), URI.picture_uri!!)
|
|
|
|
uCrop.start(this)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
|
|
super.onActivityResult(requestCode, resultCode, data)
|
|
|
|
if(resultCode == Activity.RESULT_OK) {
|
|
|
|
imageUri = data!!.data
|
|
|
|
|
|
|
|
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 = addEditFilters(Filter(), newBr, newSa, newCo)
|
|
|
|
|
|
|
|
filteredImage = myFilter.processFilter(filteredImage)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun handleCropResult(data: Intent?) {
|
|
|
|
val resultCrop: Uri? = UCrop.getOutput(data!!)
|
|
|
|
if(resultCrop != null) {
|
|
|
|
image_preview.setImageURI(resultCrop)
|
|
|
|
|
|
|
|
val bitmap = (image_preview.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, "Cannot retrieve image", 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, "Unexpected Error", Toast.LENGTH_SHORT).show()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//</editor-fold>
|
|
|
|
//<editor-fold desc="FLOW">
|
2020-05-08 10:53:56 +02:00
|
|
|
override fun onRequestPermissionsResult(
|
|
|
|
requestCode: Int,
|
|
|
|
permissions: Array<out String>,
|
|
|
|
grantResults: IntArray
|
|
|
|
) {
|
|
|
|
if(grantResults.size > 1
|
|
|
|
&& grantResults[0] == PackageManager.PERMISSION_GRANTED
|
|
|
|
&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
|
|
|
|
// permission was granted
|
|
|
|
when (requestCode) {
|
|
|
|
REQUEST_CODE_PERMISSIONS_SAVE_PHOTO -> permissionsGrantedToSave(true)
|
|
|
|
REQUEST_CODE_PERMISSIONS_SEND_PHOTO -> permissionsGrantedToSave(false)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Snackbar.make(coordinator_edit, "Permission denied", Snackbar.LENGTH_LONG).show()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-15 11:46:12 +02:00
|
|
|
private fun applyFinalFilters(image: Bitmap?) {
|
|
|
|
var editFilter = Filter()
|
|
|
|
editFilter = addEditFilters(editFilter, brightnessFinal, saturationFinal, contrastFinal)
|
|
|
|
|
|
|
|
finalImage = editFilter.processFilter(image!!.copy(BITMAP_CONFIG, true))
|
|
|
|
if (actualFilter!=null) finalImage = actualFilter!!.processFilter(finalImage)
|
|
|
|
}
|
|
|
|
|
2020-05-08 10:53:56 +02:00
|
|
|
private fun uploadImage(file: File) {
|
|
|
|
val intent = Intent (applicationContext, PostCreationActivity::class.java)
|
|
|
|
intent.putExtra("picture_uri", Uri.fromFile(file))
|
|
|
|
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
|
|
applicationContext!!.startActivity(intent)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun saveImageToGallery(save: Boolean) {
|
|
|
|
// runtime permission and process
|
|
|
|
if (!allPermissionsGranted()) {
|
|
|
|
ActivityCompat.requestPermissions(
|
|
|
|
this,
|
|
|
|
REQUIRED_PERMISSIONS,
|
|
|
|
if(save) REQUEST_CODE_PERMISSIONS_SAVE_PHOTO else REQUEST_CODE_PERMISSIONS_SEND_PHOTO
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
permissionsGrantedToSave(save)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if all permission specified in the manifest have been granted
|
|
|
|
*/
|
|
|
|
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
|
|
|
|
ContextCompat.checkSelfPermission(
|
|
|
|
applicationContext, it) == PackageManager.PERMISSION_GRANTED
|
|
|
|
}
|
|
|
|
|
2020-05-15 11:46:12 +02:00
|
|
|
/** Use external media if it is available, our app's file directory otherwise */
|
|
|
|
private fun getOutputDirectory(): File {
|
|
|
|
val appContext = applicationContext
|
|
|
|
val mediaDir = externalMediaDirs.firstOrNull()?.let {
|
|
|
|
File(it, appContext.resources.getString(R.string.app_name)).apply { mkdirs() } }
|
|
|
|
return if (mediaDir != null && mediaDir.exists())
|
|
|
|
mediaDir else appContext.filesDir
|
|
|
|
}
|
|
|
|
|
2020-05-08 10:53:56 +02:00
|
|
|
private fun File.writeBitmap(bitmap: Bitmap) {
|
|
|
|
outputStream().use { out ->
|
|
|
|
bitmap.compress(Bitmap.CompressFormat.PNG, 85, out)
|
|
|
|
out.flush()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun permissionsGrantedToSave(save: Boolean) {
|
2020-05-15 11:46:12 +02:00
|
|
|
val file =
|
|
|
|
if(!save){
|
|
|
|
//put picture in cache
|
|
|
|
File.createTempFile("temp_edit_img", ".png", cacheDir)
|
|
|
|
} else{
|
|
|
|
// Save the picture (quality is ignored for PNG)
|
|
|
|
File(outputDirectory, SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.US)
|
2020-05-08 10:53:56 +02:00
|
|
|
.format(System.currentTimeMillis()) + ".png")
|
2020-05-15 11:46:12 +02:00
|
|
|
}
|
2020-05-08 10:53:56 +02:00
|
|
|
try {
|
2020-05-15 11:46:12 +02:00
|
|
|
applyFinalFilters(originalImage)
|
2020-05-08 10:53:56 +02:00
|
|
|
file.writeBitmap(finalImage)
|
|
|
|
} catch (e: IOException) {
|
|
|
|
Snackbar.make(coordinator_edit, "Unable to save image", Snackbar.LENGTH_LONG).show()
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!save) {
|
|
|
|
uploadImage(file)
|
|
|
|
} else {
|
|
|
|
Snackbar.make(coordinator_edit, "Image succesfully saved", Snackbar.LENGTH_LONG).show()
|
|
|
|
}
|
|
|
|
}
|
2020-05-15 11:46:12 +02:00
|
|
|
//</editor-fold>
|
2020-05-08 10:53:56 +02:00
|
|
|
}
|