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 219d4fab..4a9c3a9d 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt @@ -47,7 +47,6 @@ import java.io.OutputStream import java.text.SimpleDateFormat import java.util.* import kotlin.math.roundToInt -import kotlin.collections.flatten const val TAG = "Post Creation Activity" @@ -131,8 +130,13 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() { uiState.newEncodingJobVideoStart?.let { videoStart -> uiState.newEncodingJobVideoEnd?.let { videoEnd -> uiState.newEncodingJobSpeedIndex?.let { speedIndex -> - startEncoding(position, muted, videoStart, videoEnd, speedIndex) - model.encodingStarted() + uiState.newEncodingJobVideoCrop?.let { crop -> + startEncoding(position, muted, + videoStart, videoEnd, + speedIndex, crop + ) + model.encodingStarted() + } } } } @@ -331,7 +335,8 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() { muted: Boolean, videoStart: Float?, videoEnd: Float?, - speedIndex: Int + speedIndex: Int, + crop: VideoEditActivity.RelativeCropPosition ) { val originalUri = model.getPhotoData().value!![position].imageUri @@ -352,29 +357,33 @@ class PostCreationActivity : BaseThemedWithoutBarActivity() { val speed = VideoEditActivity.speedChoices[speedIndex] - //TODO also have audio when speed is changed? val mutedString = if(muted || speedIndex != 1) "-an" else null val startString: List = if(videoStart != null) listOf("-ss", "${videoStart/speed.toFloat()}") else listOf(null, null) val endString: List = if(videoEnd != null) listOf("-to", "${videoEnd/speed.toFloat() - (videoStart ?: 0f)/speed.toFloat()}") else listOf(null, null) - val speedString: List = if(speedIndex!= 1) - listOf("-filter:v", "setpts=PTS/${speed}") + // iw and ih are variables for the original width and height values, FFmpeg will know them + val cropString = if(crop.notCropped()) "" else "crop=${crop.relativeWidth}*iw:${crop.relativeHeight}*ih:${crop.relativeX}*iw:${crop.relativeY}*ih" + val separator = if(speedIndex != 1 && !crop.notCropped()) "," else "" + val speedString = if(speedIndex != 1) "setpts=PTS/${speed}" else "" + + val speedAndCropString: List = if(speedIndex!= 1 || !crop.notCropped()) + listOf("-filter:v", 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(speedIndex != 1) listOf("-c:v", "libx264", "-preset", "ultrafast") else listOf(null, null, null, null) + val encodePreset: List = if(speedIndex != 1 && !crop.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, - speedString[0], speedString[1], + speedAndCropString[0], speedAndCropString[1], endString[0], endString[1], mutedString, "-y", encodePreset[0], encodePreset[1], encodePreset[2], encodePreset[3], - outputVideoPath + outputVideoPath, ).toTypedArray(), //val session: FFmpegSession = FFmpegKit.executeAsync("$startString -i $inputSafePath $endString -c:v libvpx-vp9 -c:a copy -an -y $outputVideoPath", { session -> 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 75e9b35a..02850774 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationViewModel.kt @@ -24,6 +24,7 @@ import org.pixeldroid.app.MainActivity import org.pixeldroid.app.R import org.pixeldroid.app.postCreation.photoEdit.PhotoEditActivity import org.pixeldroid.app.postCreation.photoEdit.VideoEditActivity +import org.pixeldroid.app.postCreation.photoEdit.VideoEditActivity.RelativeCropPosition import org.pixeldroid.app.utils.PixelDroidApplication import org.pixeldroid.app.utils.api.objects.Attachment import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity @@ -61,6 +62,8 @@ data class PostCreationActivityUiState( val newEncodingJobSpeedIndex: Int? = null, val newEncodingJobVideoStart: Float? = null, val newEncodingJobVideoEnd: Float? = null, + val newEncodingJobVideoCrop: RelativeCropPosition? = null, + ) class PostCreationViewModel(application: Application, clipdata: ClipData? = null, val instance: InstanceDatabaseEntity? = null) : AndroidViewModel(application) { @@ -372,6 +375,8 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null if(it == -1f) null else it } + val videoCrop: RelativeCropPosition = data.getSerializableExtra(VideoEditActivity.VIDEO_CROP) as RelativeCropPosition + videoEncodeProgress = 0 sessionMap[position]?.let { FFmpegKit.cancel(it) } _uiState.update { currentUiState -> @@ -380,7 +385,8 @@ class PostCreationViewModel(application: Application, clipdata: ClipData? = null newEncodingJobMuted = muted, newEncodingJobSpeedIndex = speedIndex, newEncodingJobVideoStart = videoStart, - newEncodingJobVideoEnd = videoEnd + newEncodingJobVideoEnd = videoEnd, + newEncodingJobVideoCrop = videoCrop ) } } diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/VideoEditActivity.kt b/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/VideoEditActivity.kt index f2653d93..e69bfc08 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/VideoEditActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/VideoEditActivity.kt @@ -2,15 +2,13 @@ package org.pixeldroid.app.postCreation.photoEdit import android.app.Activity import android.app.AlertDialog -import android.content.ContentUris import android.content.Intent +import android.graphics.Rect import android.media.AudioManager import android.net.Uri -import android.os.Build import android.os.Bundle import android.os.Handler import android.os.Looper -import android.provider.MediaStore import android.text.format.DateUtils import android.util.Log import android.view.Menu @@ -24,8 +22,11 @@ import androidx.media.AudioAttributesCompat import androidx.media2.common.MediaMetadata import androidx.media2.common.UriMediaItem import androidx.media2.player.MediaPlayer -import androidx.media2.player.MediaPlayer.PlayerCallback -import com.arthenica.ffmpegkit.* +import com.arthenica.ffmpegkit.FFmpegKit +import com.arthenica.ffmpegkit.FFmpegKitConfig +import com.arthenica.ffmpegkit.FFprobeKit +import com.arthenica.ffmpegkit.MediaInformation +import com.arthenica.ffmpegkit.ReturnCode import com.bumptech.glide.Glide import com.google.android.material.slider.RangeSlider import org.pixeldroid.app.R @@ -35,14 +36,31 @@ import org.pixeldroid.app.postCreation.carousel.dpToPx import org.pixeldroid.app.utils.BaseThemedWithBarActivity import org.pixeldroid.app.utils.ffmpegCompliantUri import java.io.File +import java.io.Serializable +import kotlin.math.absoluteValue class VideoEditActivity : BaseThemedWithBarActivity() { + data class RelativeCropPosition( + val relativeWidth: Float, + val relativeHeight: Float, + val relativeX: Float, + val relativeY: Float, + ): Serializable { + fun notCropped(): Boolean = + (relativeX - 1f).absoluteValue < 0.001f + && (relativeY - 1f).absoluteValue < 0.001f + && relativeX.absoluteValue < 0.001f + && relativeWidth.absoluteValue < 0.001f + + } + private lateinit var mediaPlayer: MediaPlayer private var videoPosition: Int = -1 - //TODO react to change of playbackSpeed (when changed in the player itself) + private var cropRelativeDimensions: RelativeCropPosition = RelativeCropPosition(1f,1f,0f,0f) + private var speed: Int = 1 set(value) { field = value @@ -75,17 +93,11 @@ class VideoEditActivity : BaseThemedWithBarActivity() { val uri = intent.getParcelableExtra(PhotoEditActivity.PICTURE_URI)!! - binding.cropImageView.setImageUriAsync(uri) - videoPosition = intent.getIntExtra(PhotoEditActivity.PICTURE_POSITION, -1) val inputVideoPath = ffmpegCompliantUri(uri) val mediaInformation: MediaInformation? = FFprobeKit.getMediaInformation(inputVideoPath).mediaInformation - binding.muter.setOnClickListener { - binding.muter.isSelected = !binding.muter.isSelected - } - //Duration in seconds, or null val duration: Float? = mediaInformation?.duration?.toFloatOrNull() @@ -126,6 +138,38 @@ class VideoEditActivity : BaseThemedWithBarActivity() { binding.muter.isSelected = !binding.muter.isSelected } + binding.cropper.setOnClickListener { + //TODO set crop from saved value + showCropInterface(show = true, uri = uri) + } + + binding.saveCropButton.setOnClickListener { + // This is the rectangle selected by the crop + val cropRect = binding.cropImageView.cropWindowRect ?: return@setOnClickListener + + // 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() + ) + + showCropInterface(show = false) + } + binding.videoView.setPlayer(mediaPlayer) mediaPlayer.seekTo((binding.videoRangeSeekBar.values[1]*1000).toLong()) @@ -230,9 +274,33 @@ class VideoEditActivity : BaseThemedWithBarActivity() { val muted = binding.muter.isSelected val speedUnchanged = speed == 1 - return !muted && videoPositions && speedUnchanged + return !muted && videoPositions && speedUnchanged && cropRelativeDimensions.notCropped() } + 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() + + binding.muter.visibility = visibilityOfOthers + binding.speeder.visibility = visibilityOfOthers + binding.cropper.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() { val intent = Intent(this, PostCreationActivity::class.java) @@ -243,6 +311,7 @@ class VideoEditActivity : BaseThemedWithBarActivity() { putExtra(MODIFIED, !noEdits()) putExtra(VIDEO_START, binding.videoRangeSeekBar.values.first()) putExtra(VIDEO_END, binding.videoRangeSeekBar.values[2]) + putExtra(VIDEO_CROP, cropRelativeDimensions) addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) } @@ -253,6 +322,7 @@ class VideoEditActivity : BaseThemedWithBarActivity() { private fun resetControls() { binding.videoRangeSeekBar.values = listOf(0f, binding.videoRangeSeekBar.valueTo/2, binding.videoRangeSeekBar.valueTo) binding.muter.isSelected = false + binding.cropImageView.resetCropRect() } override fun onDestroy() { @@ -315,6 +385,7 @@ class VideoEditActivity : BaseThemedWithBarActivity() { val speedChoices: List = listOf(0.5, 1, 2, 4, 8) const val VIDEO_START = "VideoEditVideoStartTag" const val VIDEO_END = "VideoEditVideoEndTag" + const val VIDEO_CROP = "VideoEditVideoCropTag" const val MODIFIED = "VideoEditModifiedTag" } } \ No newline at end of file diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/cropper/CropImageView.kt b/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/cropper/CropImageView.kt index 21616b97..4da256ad 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/cropper/CropImageView.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/cropper/CropImageView.kt @@ -8,108 +8,94 @@ import android.net.Uri import android.util.AttributeSet import android.view.LayoutInflater import android.widget.FrameLayout -import android.widget.ImageView 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.app.R +import org.pixeldroid.app.databinding.CropImageViewBinding +import org.pixeldroid.app.postCreation.photoEdit.VideoEditActivity /** Custom view that provides cropping capabilities to an image. */ class CropImageView @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null) : FrameLayout(context!!, attrs) { - /** Image view widget used to show the image for cropping. */ - private val mImageView: ImageView - /** Overlay over the image view to show cropping UI. */ - private val mCropOverlayView: CropOverlayView? - /** The sample size the image was loaded by if was loaded by URI */ - private var mLoadedSampleSize = 1 + private val binding: CropImageViewBinding = + CropImageViewBinding.inflate(LayoutInflater.from(context), this, true) init { - val inflater = LayoutInflater.from(context) - val v = inflater.inflate(R.layout.crop_image_view, this, true) - mImageView = v.findViewById(R.id.ImageView_image) - mCropOverlayView = v.findViewById(R.id.CropOverlayView) - mCropOverlayView.setInitialAttributeValues() + binding.CropOverlayView.setInitialAttributeValues() } /** * Gets the crop window's position relative to the parent's view at screen. * - * @return a Rect instance containing cropped area boundaries of the source Bitmap + * @return a Rect instance containing notCropped area boundaries of the source Bitmap */ val cropWindowRect: RectF? - get() = mCropOverlayView?.cropWindowRect// Get crop window position relative to the displayed image. + get() = binding.CropOverlayView.cropWindowRect - /** - * Set the crop window position and size to the given rectangle. - * Image to crop must be first set before invoking this, for async - after complete callback. - * - * @param rect window rectangle (position and size) relative to source bitmap - */ - fun setCropRect(rect: Rect?) { - mCropOverlayView!!.initialCropWindowRect = rect - } /** Reset crop window to initial rectangle. */ fun resetCropRect() { - mCropOverlayView!!.resetCropWindowRect() + binding.CropOverlayView.resetCropWindowRect() } + fun getInitialCropWindowRect(): Rect = binding.CropOverlayView.initialCropWindowRect + /** - * Sets a bitmap loaded from the given Android URI as the content of the CropImageView.

- * Can be used with URI from gallery or camera source.

- * Will rotate the image by exif data.

+ * 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) { - // either no existing task is working or we canceled it, need to load new URI - mCropOverlayView!!.initialCropWindowRect = null + 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 = null Glide.with(this).load(uri).fitCenter().listener(object : RequestListener { - override fun onLoadFailed(e: GlideException?, model: Any?, target: Target?, isFirstResource: Boolean): Boolean {return false } + 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 + 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 - mCropOverlayView.cropWindowRect = - RectF((width - drawnWidth)/2f, (height - drawnHeight)/2f, (width + drawnWidth)/2f, (height + drawnHeight)/2f) - mCropOverlayView.initialCropWindowRect = mCropOverlayView.cropWindowRect.toRect() - mCropOverlayView.setCropWindowLimits(drawnWidth.toFloat(), drawnHeight.toFloat(), 1f, 1f) - setBitmap() + 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(mImageView) - } - - /** - * Set the given bitmap to be used in for cropping

- * Optionally clear full if the bitmap is new, or partial clear if the bitmap has been - * manipulated. - */ - private fun setBitmap() { - mLoadedSampleSize = 1 - if (mCropOverlayView != null) { - mCropOverlayView.invalidate() - mCropOverlayView.setBounds(width, height) - mCropOverlayView.resetCropOverlayView() - mCropOverlayView.visibility = VISIBLE - } + }).into(binding.ImageViewImage) } } \ No newline at end of file diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/cropper/CropOverlayView.java b/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/cropper/CropOverlayView.java index 82205c8d..d66f58d1 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/cropper/CropOverlayView.java +++ b/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/cropper/CropOverlayView.java @@ -13,6 +13,10 @@ import android.util.TypedValue; import android.view.MotionEvent; import android.view.View; +import androidx.annotation.NonNull; + +import org.pixeldroid.app.postCreation.photoEdit.VideoEditActivity; + /** A custom View representing the crop window and the shaded background outside the crop window. */ public class CropOverlayView extends View { @@ -30,9 +34,6 @@ public class CropOverlayView extends View { /** The Paint used to draw the guidelines within the crop area when pressed. */ private Paint mGuidelinePaint; - /** The Paint used to darken the surrounding areas outside the crop area. */ - private final Paint mBackgroundPaint = getNewPaint(Color.argb(119, 0, 0, 0)); - /** The bounding box around the Bitmap that we are cropping. */ private final RectF mCalcBounds = new RectF(); @@ -42,18 +43,9 @@ public class CropOverlayView extends View { /** The bounding image view height used to know the crop overlay is at view edges. */ private int mViewHeight; - /** The initial crop window padding from image borders */ - private float mInitialCropWindowPaddingRatio; - /** The Handle that is currently pressed; null if no Handle is pressed. */ private CropWindowMoveHandler mMoveHandler; - /** save the current aspect ratio of the image */ - private int mAspectRatioX; - - /** save the current aspect ratio of the image */ - private int mAspectRatioY; - /** the initial crop window rectangle to set */ private final Rect mInitialCropWindowRect = new Rect(); @@ -113,50 +105,12 @@ public class CropOverlayView extends View { } } - /** the X value of the aspect ratio; */ - public int getAspectRatioX() { - return mAspectRatioX; - } - - /** Sets the X value of the aspect ratio to 1. */ - public void setAspectRatioX() { - if (mAspectRatioX != 1) { - mAspectRatioX = 1; - - if (initializedCropWindow) { - initCropWindow(); - invalidate(); - } - } - } - - /** the Y value of the aspect ratio; */ - public int getAspectRatioY() { - return mAspectRatioY; - } - /** - * Sets the Y value of the aspect ratio to 1. - * - */ - public void setAspectRatioY() { - if (mAspectRatioY != 1) { - mAspectRatioY = 1; - - if (initializedCropWindow) { - initCropWindow(); - invalidate(); - } - } - } - /** - * set the max width/height and scale factor of the shown image to original image to scale the + * Set the max width/height and scale factor of the shown image to original image to scale the * limits appropriately. */ - public void setCropWindowLimits( - float maxWidth, float maxHeight, float scaleFactorWidth, float scaleFactorHeight) { - mCropWindowHandler.setCropWindowLimits( - maxWidth, maxHeight, scaleFactorWidth, scaleFactorHeight); + public void setCropWindowLimits(float maxWidth, float maxHeight) { + mCropWindowHandler.setCropWindowLimits(maxWidth, maxHeight); } /** Get crop window initial rectangle. */ @@ -164,6 +118,19 @@ public class CropOverlayView extends View { return mInitialCropWindowRect; } + public void setRecordedCropWindowRect(@NonNull VideoEditActivity.RelativeCropPosition relativeCropPosition) { + Rect rect = new Rect( + (int) (mInitialCropWindowRect.left + relativeCropPosition.getRelativeX() * mInitialCropWindowRect.width()), + (int) (mInitialCropWindowRect.top + relativeCropPosition.getRelativeY() * mInitialCropWindowRect.height()), + (int) (relativeCropPosition.getRelativeWidth() * mInitialCropWindowRect.width() + + mInitialCropWindowRect.left + relativeCropPosition.getRelativeX() * mInitialCropWindowRect.width()), + (int) (relativeCropPosition.getRelativeHeight() * mInitialCropWindowRect.height() + + mInitialCropWindowRect.top + relativeCropPosition.getRelativeY() * mInitialCropWindowRect.width()) + ); + //TODO call correct thing instead of initial (which sets the limits...) + setInitialCropWindowRect(rect); + } + /** Set crop window initial rectangle to be used instead of default. */ public void setInitialCropWindowRect(Rect rect) { mInitialCropWindowRect.set(rect != null ? rect : new Rect()); @@ -188,25 +155,21 @@ public class CropOverlayView extends View { public void setInitialAttributeValues() { DisplayMetrics dm = Resources.getSystem().getDisplayMetrics(); - setAspectRatioX(); - - setAspectRatioY(); - - mInitialCropWindowPaddingRatio = 0.1f; - - mBorderPaint = getNewPaintOrNull(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, dm), Color.argb(170, 255, 255, 255)); + mBorderPaint = + getNewPaintOfThickness(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, dm), Color.argb(170, 255, 255, 255)); mBorderCornerPaint = - getNewPaintOrNull(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, dm), Color.WHITE); + getNewPaintOfThickness(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, dm), Color.WHITE); - mGuidelinePaint = getNewPaintOrNull(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, dm), Color.argb(170, 255, 255, 255)); + mGuidelinePaint = + getNewPaintOfThickness(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, 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. + * the image being notCropped. */ private void initCropWindow() { @@ -281,7 +244,7 @@ public class CropOverlayView extends View { super.onDraw(canvas); - // Draw translucent background for the cropped area. + // Draw translucent background for the notCropped area. drawBackground(canvas); if (mCropWindowHandler.showGuidelines()) { @@ -302,7 +265,7 @@ public class CropOverlayView extends View { RectF rect = mCropWindowHandler.getRect(); - canvas.drawRect(rect.left, rect.top, rect.right, rect.bottom, mBackgroundPaint); + canvas.drawRect(rect.left, rect.top, rect.right, rect.bottom, getNewPaint(Color.argb(119, 0, 0, 0))); } /** @@ -433,18 +396,14 @@ public class CropOverlayView extends View { return paint; } - /** Creates the Paint object for given thickness and color, if thickness < 0 return null. */ - private static Paint getNewPaintOrNull(float thickness, int color) { - if (thickness > 0) { - Paint borderPaint = new Paint(); - borderPaint.setColor(color); - borderPaint.setStrokeWidth(thickness); - borderPaint.setStyle(Paint.Style.STROKE); - borderPaint.setAntiAlias(true); - return borderPaint; - } else { - return null; - } + /** Creates the Paint object for given thickness and color */ + private static Paint getNewPaintOfThickness(float thickness, int color) { + Paint borderPaint = new Paint(); + borderPaint.setColor(color); + borderPaint.setStrokeWidth(thickness); + borderPaint.setStyle(Paint.Style.STROKE); + borderPaint.setAntiAlias(true); + return borderPaint; } @Override @@ -523,8 +482,6 @@ public class CropOverlayView extends View { /** * Calculate the bounding rectangle for current crop window * The bounds rectangle is the bitmap rectangle - * - * @param rect the crop window rectangle to start finding bounded rectangle from */ private void calculateBounds(RectF rect) { mCalcBounds.set(mInitialCropWindowRect); diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/cropper/CropWindowHandler.java b/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/cropper/CropWindowHandler.java index 6226811b..8fd93d5f 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/cropper/CropWindowHandler.java +++ b/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/cropper/CropWindowHandler.java @@ -25,11 +25,6 @@ final class CropWindowHandler { /** Maximum height in pixels that the crop window can CURRENTLY get. */ private float mMaxCropWindowHeight; - /** The width scale factor of shown image and actual image */ - private float mScaleFactorWidth = 1; - - /** The height scale factor of shown image and actual image */ - private float mScaleFactorHeight = 1; // endregion /** Get the left/top/right/bottom coordinates of the crop window. */ @@ -46,7 +41,7 @@ final class CropWindowHandler { */ DisplayMetrics dm = Resources.getSystem().getDisplayMetrics(); float mMinCropResultWidth = 40; - return Math.max((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 42, dm), mMinCropResultWidth / mScaleFactorWidth); + return Math.max((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 42, dm), mMinCropResultWidth); } /** Minimum height in pixels that the crop window can get. */ @@ -57,49 +52,27 @@ final class CropWindowHandler { */ DisplayMetrics dm = Resources.getSystem().getDisplayMetrics(); float mMinCropResultHeight = 40; - return Math.max((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 42, dm), mMinCropResultHeight / mScaleFactorHeight); + return Math.max((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 42, dm), mMinCropResultHeight); } /** Maximum width in pixels that the crop window can get. */ public float getMaxCropWidth() { - /* - * Maximum width in pixels that the result of cropping an image can get, affects crop window width - * adjusted by width scale factor. - */ float mMaxCropResultWidth = 99999; - return Math.min(mMaxCropWindowWidth, mMaxCropResultWidth / mScaleFactorWidth); + return Math.min(mMaxCropWindowWidth, mMaxCropResultWidth); } /** Maximum height in pixels that the crop window can get. */ public float getMaxCropHeight() { - /* - * Maximum height in pixels that the result of cropping an image can get, affects crop window - * height adjusted by height scale factor. - */ float mMaxCropResultHeight = 99999; - return Math.min(mMaxCropWindowHeight, mMaxCropResultHeight / mScaleFactorHeight); - } - - /** get the scale factor (on width) of the shown image to original image. */ - public float getScaleFactorWidth() { - return mScaleFactorWidth; - } - - /** get the scale factor (on height) of the shown image to original image. */ - public float getScaleFactorHeight() { - return mScaleFactorHeight; + return Math.min(mMaxCropWindowHeight, mMaxCropResultHeight); } /** - * set the max width/height and scale factor of the shown image to original image to scale the - * limits appropriately. + * Set the max width/height of the shown image to original image to scale the limits appropriately */ - public void setCropWindowLimits( - float maxWidth, float maxHeight, float scaleFactorWidth, float scaleFactorHeight) { + public void setCropWindowLimits(float maxWidth, float maxHeight) { mMaxCropWindowWidth = maxWidth; mMaxCropWindowHeight = maxHeight; - mScaleFactorWidth = scaleFactorWidth; - mScaleFactorHeight = scaleFactorHeight; } /** Set the left/top/right/bottom coordinates of the crop window. */ @@ -126,8 +99,7 @@ final class CropWindowHandler { * @param targetRadius the target radius in pixels * @return the Handle that was pressed; null if no Handle was pressed */ - public CropWindowMoveHandler getMoveHandler( - float x, float y, float targetRadius) { + public CropWindowMoveHandler getMoveHandler(float x, float y, float targetRadius) { CropWindowMoveHandler.Type type = getRectanglePressedMoveType(x, y, targetRadius); return type != null ? new CropWindowMoveHandler(type, this, x, y) : null; } diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/cropper/CropWindowMoveHandler.java b/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/cropper/CropWindowMoveHandler.java index 41f5409b..d67411ad 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/cropper/CropWindowMoveHandler.java +++ b/app/src/main/java/org/pixeldroid/app/postCreation/photoEdit/cropper/CropWindowMoveHandler.java @@ -236,7 +236,7 @@ final class CropWindowMoveHandler { * 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 cropped + * @param bounds the bounding box of the image that is being notCropped * @param snapMargin the snap distance to the image edge (in pixels) */ private void adjustLeft( @@ -282,7 +282,7 @@ final class CropWindowMoveHandler { * 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 cropped + * @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) */ @@ -332,7 +332,7 @@ final class CropWindowMoveHandler { * 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 cropped + * @param bounds the bounding box of the image that is being notCropped * @param snapMargin the snap distance to the image edge (in pixels) */ private void adjustTop( @@ -378,7 +378,7 @@ final class CropWindowMoveHandler { * 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 cropped + * @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) */ diff --git a/app/src/main/res/layout/activity_video_edit.xml b/app/src/main/res/layout/activity_video_edit.xml index d46a8994..f8777276 100644 --- a/app/src/main/res/layout/activity_video_edit.xml +++ b/app/src/main/res/layout/activity_video_edit.xml @@ -22,12 +22,26 @@ + android:visibility="gone"/> + + + - + + Select what to keep of the video Mute video Change video speed + Crop video One or more videos are still encoding. Wait for them to finish before uploading Create new post New post @@ -293,6 +294,7 @@ For more info about Pixelfed, you can check here: https://pixelfed.org" Error while adding images "Thumbnail of image in this notification's post" Preview of a post + Save crop %d reply %d replies