From 74997dd62a574be0bc6132749e93b65e536b082a Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 23 Feb 2021 14:02:03 -0300 Subject: [PATCH 01/48] using Chris Banes' PhotoView for the BackgroundImageView, to provide panning / pinch to zoom --- build.gradle | 2 ++ photoeditor/build.gradle | 2 ++ .../photoeditor/views/background/fixed/BackgroundImageView.kt | 4 ++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 33629e1b3..92c92e372 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,7 @@ buildscript { google() jcenter() maven { url "https://dl.bintray.com/automattic/maven/" } + maven { url "https://jitpack.io" } } dependencies { classpath 'com.android.tools.build:gradle:3.5.1' @@ -30,6 +31,7 @@ allprojects { repositories { google() jcenter() + maven { url "https://jitpack.io" } } if (tasks.findByPath('checkstyle') == null) { diff --git a/photoeditor/build.gradle b/photoeditor/build.gradle index f9f107303..a5f9ac119 100644 --- a/photoeditor/build.gradle +++ b/photoeditor/build.gradle @@ -54,6 +54,8 @@ dependencies { implementation 'com.github.bumptech.glide:glide:4.10.0' kapt 'com.github.bumptech.glide:compiler:4.10.0' + implementation 'com.github.chrisbanes:PhotoView:2.3.0' + implementation project(path: ':mp4compose') lintChecks 'org.wordpress:lint:1.0.1' diff --git a/photoeditor/src/main/java/com/automattic/photoeditor/views/background/fixed/BackgroundImageView.kt b/photoeditor/src/main/java/com/automattic/photoeditor/views/background/fixed/BackgroundImageView.kt index ada546983..090d72972 100644 --- a/photoeditor/src/main/java/com/automattic/photoeditor/views/background/fixed/BackgroundImageView.kt +++ b/photoeditor/src/main/java/com/automattic/photoeditor/views/background/fixed/BackgroundImageView.kt @@ -10,14 +10,14 @@ import android.graphics.drawable.Drawable import android.graphics.drawable.Icon import android.net.Uri import android.util.AttributeSet -import androidx.appcompat.widget.AppCompatImageView +import com.github.chrisbanes.photoview.PhotoView /** * @author [Burhanuddin Rashid](https://github.com/burhanrashid52) * @version 0.1.2 * @since 5/21/2018 */ -internal class BackgroundImageView : AppCompatImageView { +internal class BackgroundImageView : PhotoView { private var mOnImageChangedListener: OnImageChangedListener? = null val bitmap: Bitmap? From ca897d726cc75d6a51b911dfa0461fbc06ba4a88 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 23 Feb 2021 20:16:09 -0300 Subject: [PATCH 02/48] save the original ImageMatrix and set it on the ghostPhotoView after loading the image drawable on it in preparePhotoEditorViewForSnapshot when saving the image frame to disk --- .../stories/compose/frame/FrameSaveManager.kt | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt b/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt index cbb3f2538..78418e493 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt @@ -3,6 +3,7 @@ package com.wordpress.stories.compose.frame import android.content.Context import android.graphics.Bitmap import android.graphics.Color +import android.graphics.Matrix import android.net.Uri import android.view.View import android.view.ViewGroup.LayoutParams @@ -142,8 +143,9 @@ class FrameSaveManager( } else { try { // create ghost PhotoEditorView to be used for saving off-screen + val originalMatrix = photoEditor.composedCanvas.source.imageMatrix val ghostPhotoEditorView = createGhostPhotoEditor(context, photoEditor.composedCanvas) - frameFile = saveImageFrame(context, frame, ghostPhotoEditorView, frameIndex) + frameFile = saveImageFrame(context, frame, ghostPhotoEditorView, originalMatrix, frameIndex) frame.composedFrameFile = frameFile saveProgressListener?.onFrameSaveCompleted(frameIndex, frame) } catch (ex: Exception) { @@ -167,10 +169,11 @@ class FrameSaveManager( context: Context, frame: StoryFrameItem, ghostPhotoEditorView: PhotoEditorView, + originalMatrix: Matrix, frameIndex: FrameIndex ): File { // prepare the ghostview with its background image and the AddedViews on top of it - val futureTarget = preparePhotoEditorViewForSnapshot(context, frame, ghostPhotoEditorView) + val futureTarget = preparePhotoEditorViewForSnapshot(context, frame, originalMatrix, ghostPhotoEditorView) val file = withContext(Dispatchers.IO) { if (normalizeTo916 && !isSizeRatio916(ghostPhotoEditorView.width, ghostPhotoEditorView.height)) { @@ -276,21 +279,26 @@ class FrameSaveManager( private suspend fun preparePhotoEditorViewForSnapshot( context: Context, frame: StoryFrameItem, + originalMatrix: Matrix, ghostPhotoEditorView: PhotoEditorView ): FutureTarget { // prepare background val uri = (frame.source as? UriBackgroundSource)?.contentUri ?: (frame.source as FileBackgroundSource).file + val targetView = ghostPhotoEditorView.source + // making use of Glide to decode bitmap and get the right orientation automatically // http://bumptech.github.io/glide/doc/getting-started.html#background-threads val futureTarget = Glide.with(context) .asBitmap() .load(uri) .transform(CenterCrop()) // also use CenterCrop as it's the same the user was seeing as per WYSIWYG - .submit(ghostPhotoEditorView.source.measuredWidth, ghostPhotoEditorView.source.measuredHeight) + .submit(targetView.measuredWidth, targetView.measuredHeight) val bitmap = futureTarget.get() - ghostPhotoEditorView.source.setImageBitmap(bitmap) + targetView.setImageBitmap(bitmap) + + targetView.imageMatrix = originalMatrix // reset old matrix // removeViewFromParent for views that were added in the UI thread need to also run on the main thread // otherwise we'd get a android.view.ViewRootImpl$CalledFromWrongThreadException: From 50a78bfdb477bd24bf2565e6b93f62c8acaf2f2f Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 24 Feb 2021 15:49:30 -0300 Subject: [PATCH 03/48] added BackgroundViewInfo data class to remember backing support PhotoView zoom matrix when switching frames --- .../background/fixed/BackgroundImageView.kt | 2 +- stories/build.gradle | 2 + .../compose/ComposeLoopFrameActivity.kt | 68 +++++++++++++++++++ .../stories/compose/story/StoryFrameItem.kt | 7 ++ 4 files changed, 78 insertions(+), 1 deletion(-) diff --git a/photoeditor/src/main/java/com/automattic/photoeditor/views/background/fixed/BackgroundImageView.kt b/photoeditor/src/main/java/com/automattic/photoeditor/views/background/fixed/BackgroundImageView.kt index 090d72972..b60d64d34 100644 --- a/photoeditor/src/main/java/com/automattic/photoeditor/views/background/fixed/BackgroundImageView.kt +++ b/photoeditor/src/main/java/com/automattic/photoeditor/views/background/fixed/BackgroundImageView.kt @@ -17,7 +17,7 @@ import com.github.chrisbanes.photoview.PhotoView * @version 0.1.2 * @since 5/21/2018 */ -internal class BackgroundImageView : PhotoView { +class BackgroundImageView : PhotoView { private var mOnImageChangedListener: OnImageChangedListener? = null val bitmap: Bitmap? diff --git a/stories/build.gradle b/stories/build.gradle index f8261b93c..779778fdd 100644 --- a/stories/build.gradle +++ b/stories/build.gradle @@ -63,6 +63,8 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion" + implementation 'com.github.chrisbanes:PhotoView:2.3.0' + lintChecks 'org.wordpress:lint:1.0.1' testImplementation 'junit:junit:4.12' diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index 627257e83..daf83583f 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -9,8 +9,10 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.ServiceConnection +import android.graphics.drawable.Drawable import android.graphics.Rect import android.graphics.drawable.ColorDrawable +import android.graphics.Matrix import android.hardware.Camera import android.media.MediaScannerConnection import android.net.Uri @@ -63,8 +65,13 @@ import com.automattic.photoeditor.views.ViewType import com.automattic.photoeditor.views.ViewType.TEXT import com.automattic.photoeditor.views.added.AddedViewList import com.bumptech.glide.Glide +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.load.resource.bitmap.CenterCrop import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.request.RequestListener +import com.bumptech.glide.request.target.Target +import com.github.chrisbanes.photoview.PhotoView import com.wordpress.stories.BuildConfig import com.wordpress.stories.R import com.wordpress.stories.compose.ComposeLoopFrameActivity.ExternalMediaPickerRequestCodesAndExtraKeys @@ -90,6 +97,7 @@ import com.wordpress.stories.compose.story.StoryFrameItem import com.wordpress.stories.compose.story.StoryFrameItem.BackgroundSource import com.wordpress.stories.compose.story.StoryFrameItem.BackgroundSource.FileBackgroundSource import com.wordpress.stories.compose.story.StoryFrameItem.BackgroundSource.UriBackgroundSource +import com.wordpress.stories.compose.story.StoryFrameItem.BackgroundViewInfo import com.wordpress.stories.compose.story.StoryFrameItemType import com.wordpress.stories.compose.story.StoryFrameItemType.IMAGE import com.wordpress.stories.compose.story.StoryFrameItemType.VIDEO @@ -1873,6 +1881,22 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec addCurrentViewsToFrameAtIndex(oldIndex) } + // save current imageMatrix as the background image may have been resized + val oldSelectedFrame = storyViewModel.getSelectedFrame() + if (oldSelectedFrame?.frameItemType is IMAGE) { + val backgroundImageSource = photoEditor.composedCanvas.source as PhotoView + val matrixValues = FloatArray(9) + // backgroundImageSource.imageMatrix.getValues(matrixValues) + val matrix = Matrix() + // fill in matrix with PhotoView Support matrix + backgroundImageSource.getSuppMatrix(matrix) + // extract matrix to float array matrixValues + matrix.getValues(matrixValues) + oldSelectedFrame.source.backgroundViewInfo = BackgroundViewInfo( + imageMatrixValues = matrixValues + ) + } // TODO add else clause and handle VIDEO frameItemType + // This is tricky. See https://stackoverflow.com/questions/45860434/cant-remove-view-from-root-view // we need to disable layout transition animations so changes in views' parent are set // immediately. Otherwise a view's parent will only change once the animation ends, and hence @@ -1903,7 +1927,51 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec Glide.with(this@ComposeLoopFrameActivity) .load(model) .transform(CenterCrop()) + .listener(object : RequestListener { + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target?, + isFirstResource: Boolean + ): Boolean { + // let the default implementation run + return false + } + + override fun onResourceReady( + resource: Drawable?, + model: Any?, + target: Target?, + dataSource: DataSource?, + isFirstResource: Boolean + ): Boolean { + // here setup the PhotoView support matrix + val handler = Handler() + // we use a handler because we need to set the support matrix only once the drawable + // has been set on the PhotoView, otherwise the matrix is not applied + // see + // https://github.com/Baseflow/PhotoView/blob/139a9ffeaf70bd628b015374cb6530fcf7d0bcb7/photoview/src/main/java/com/github/chrisbanes/photoview/PhotoViewAttacher.java#L279-L289 + handler.post { + val backgroundImageSource = photoEditor.composedCanvas.source as PhotoView + val backgroundViewInfo = newSelectedFrame.source.backgroundViewInfo + // load image matrix from data if it exists + backgroundViewInfo?.let { + val matrix = Matrix() + matrix.setValues(it.imageMatrixValues) + backgroundImageSource.apply { + // imageMatrix.setValues(it.imageMatrixValues) + setSuppMatrix(matrix) + // setDisplayMatrix(matrix) + // invalidate() + } + } + } + // return false to let Glide proceed and set the drawable + return false + } + }) .into(photoEditorView.source) + showStaticBackground() } diff --git a/stories/src/main/java/com/wordpress/stories/compose/story/StoryFrameItem.kt b/stories/src/main/java/com/wordpress/stories/compose/story/StoryFrameItem.kt index f47fb9370..cad42a54a 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/story/StoryFrameItem.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/story/StoryFrameItem.kt @@ -27,8 +27,15 @@ data class StoryFrameItem( var composedFrameFile: File? = null, var id: String? = null ) { + + @Serializable + data class BackgroundViewInfo( + val imageMatrixValues: FloatArray + ) + @Serializable sealed class BackgroundSource { + var backgroundViewInfo: BackgroundViewInfo? = null @Serializable data class UriBackgroundSource( @Serializable(with = UriSerializer::class) From 47f5053e02942f9ade0a69989ae434b8e1caaa9f Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 24 Feb 2021 15:55:50 -0300 Subject: [PATCH 04/48] moved RequestListener out to a function to make code more readable --- .../compose/ComposeLoopFrameActivity.kt | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index daf83583f..475c38034 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -1925,52 +1925,21 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec } else { val model = (source as? FileBackgroundSource)?.file ?: (source as UriBackgroundSource).contentUri Glide.with(this@ComposeLoopFrameActivity) - .load(model) - .transform(CenterCrop()) - .listener(object : RequestListener { - override fun onLoadFailed( - e: GlideException?, - model: Any?, - target: Target?, - isFirstResource: Boolean - ): Boolean { - // let the default implementation run - return false - } - - override fun onResourceReady( - resource: Drawable?, - model: Any?, - target: Target?, - dataSource: DataSource?, - isFirstResource: Boolean - ): Boolean { - // here setup the PhotoView support matrix - val handler = Handler() - // we use a handler because we need to set the support matrix only once the drawable - // has been set on the PhotoView, otherwise the matrix is not applied - // see - // https://github.com/Baseflow/PhotoView/blob/139a9ffeaf70bd628b015374cb6530fcf7d0bcb7/photoview/src/main/java/com/github/chrisbanes/photoview/PhotoViewAttacher.java#L279-L289 - handler.post { - val backgroundImageSource = photoEditor.composedCanvas.source as PhotoView - val backgroundViewInfo = newSelectedFrame.source.backgroundViewInfo - // load image matrix from data if it exists - backgroundViewInfo?.let { - val matrix = Matrix() - matrix.setValues(it.imageMatrixValues) - backgroundImageSource.apply { - // imageMatrix.setValues(it.imageMatrixValues) - setSuppMatrix(matrix) - // setDisplayMatrix(matrix) - // invalidate() - } - } + .load(model) + .transform(CenterCrop()) + .listener(provideGlideRequestListenerWithHandler { + val backgroundImageSource = photoEditor.composedCanvas.source as PhotoView + val backgroundViewInfo = newSelectedFrame.source.backgroundViewInfo + // load image matrix from data if it exists + backgroundViewInfo?.let { + val matrix = Matrix() + matrix.setValues(it.imageMatrixValues) + backgroundImageSource.apply { + setSuppMatrix(matrix) } - // return false to let Glide proceed and set the drawable - return false } }) - .into(photoEditorView.source) + .into(photoEditorView.source) showStaticBackground() } @@ -1990,6 +1959,37 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec showRetryButtonAndHideEditControlsForErroredFrame(newSelectedFrame.saveResultReason !is SaveSuccess) } + private fun provideGlideRequestListenerWithHandler(setupPhotoViewMatrix: Runnable) : RequestListener { + return object : RequestListener { + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target?, + isFirstResource: Boolean + ): Boolean { + // let the default implementation run + return false + } + + override fun onResourceReady( + resource: Drawable?, + model: Any?, + target: Target?, + dataSource: DataSource?, + isFirstResource: Boolean + ): Boolean { + // here setup the PhotoView support matrix + // we use a handler because we need to set the support matrix only once the drawable + // has been set on the PhotoView, otherwise the matrix is not applied + // see + // https://github.com/Baseflow/PhotoView/blob/139a9ffeaf70bd628b015374cb6530fcf7d0bcb7/photoview/src/main/java/com/github/chrisbanes/photoview/PhotoViewAttacher.java#L279-L289 + Handler().post(setupPhotoViewMatrix) + // return false to let Glide proceed and set the drawable + return false + } + } + } + override fun onStoryFrameAddTapped() { addCurrentViewsToFrameAtIndex(storyViewModel.getSelectedFrameIndex()) showMediaPicker() From 4eca102b0336157077f62a2398d940a13f2b1e31 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 24 Feb 2021 17:09:14 -0300 Subject: [PATCH 05/48] use getCurrentStoryFrameAt(oldIndex) instead of getSelectedFrame when selection has just changed, because the current selected index is now no longer the oldIndex --- .../com/wordpress/stories/compose/ComposeLoopFrameActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index 475c38034..adf58c5b6 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -1882,7 +1882,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec } // save current imageMatrix as the background image may have been resized - val oldSelectedFrame = storyViewModel.getSelectedFrame() + val oldSelectedFrame = storyViewModel.getCurrentStoryFrameAt(oldIndex) if (oldSelectedFrame?.frameItemType is IMAGE) { val backgroundImageSource = photoEditor.composedCanvas.source as PhotoView val matrixValues = FloatArray(9) From ff08278d55aa568be9644b0da5f61c09ecf65e7d Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 24 Feb 2021 17:31:10 -0300 Subject: [PATCH 06/48] using the bbackgroundViewInfo array to built the imageMatrix when saving IMAGE based frames --- .../com/wordpress/stories/compose/frame/FrameSaveManager.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt b/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt index 78418e493..26a789ee7 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt @@ -143,7 +143,11 @@ class FrameSaveManager( } else { try { // create ghost PhotoEditorView to be used for saving off-screen - val originalMatrix = photoEditor.composedCanvas.source.imageMatrix + val originalMatrix = Matrix() + frame.source.backgroundViewInfo?.let { + originalMatrix.setValues(it.imageMatrixValues) + } + val ghostPhotoEditorView = createGhostPhotoEditor(context, photoEditor.composedCanvas) frameFile = saveImageFrame(context, frame, ghostPhotoEditorView, originalMatrix, frameIndex) frame.composedFrameFile = frameFile From a0fec095b6cd050d22132ab51768e01bb13c24d5 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 24 Feb 2021 17:52:29 -0300 Subject: [PATCH 07/48] removed commented code line --- .../com/wordpress/stories/compose/ComposeLoopFrameActivity.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index adf58c5b6..04d0e9df2 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -1886,7 +1886,6 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec if (oldSelectedFrame?.frameItemType is IMAGE) { val backgroundImageSource = photoEditor.composedCanvas.source as PhotoView val matrixValues = FloatArray(9) - // backgroundImageSource.imageMatrix.getValues(matrixValues) val matrix = Matrix() // fill in matrix with PhotoView Support matrix backgroundImageSource.getSuppMatrix(matrix) From 49fa468c72ba7e4177d882848a45f93566307bb5 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 24 Feb 2021 18:11:43 -0300 Subject: [PATCH 08/48] fixed lint warning' --- .../com/wordpress/stories/compose/ComposeLoopFrameActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index 04d0e9df2..cca9b386f 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -1958,7 +1958,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec showRetryButtonAndHideEditControlsForErroredFrame(newSelectedFrame.saveResultReason !is SaveSuccess) } - private fun provideGlideRequestListenerWithHandler(setupPhotoViewMatrix: Runnable) : RequestListener { + private fun provideGlideRequestListenerWithHandler(setupPhotoViewMatrix: Runnable): RequestListener { return object : RequestListener { override fun onLoadFailed( e: GlideException?, From c5a9dabaa0dfc4361589f64a36494b2bdc77a463 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 24 Feb 2021 18:19:21 -0300 Subject: [PATCH 09/48] encapsulated code in new methods setBackgroundViewInfoOnFrame and setBackgroundViewInfoOnPhotoView --- .../compose/ComposeLoopFrameActivity.kt | 50 ++++++++++++------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index cca9b386f..e85f7c608 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -1884,15 +1884,9 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec // save current imageMatrix as the background image may have been resized val oldSelectedFrame = storyViewModel.getCurrentStoryFrameAt(oldIndex) if (oldSelectedFrame?.frameItemType is IMAGE) { - val backgroundImageSource = photoEditor.composedCanvas.source as PhotoView - val matrixValues = FloatArray(9) - val matrix = Matrix() - // fill in matrix with PhotoView Support matrix - backgroundImageSource.getSuppMatrix(matrix) - // extract matrix to float array matrixValues - matrix.getValues(matrixValues) - oldSelectedFrame.source.backgroundViewInfo = BackgroundViewInfo( - imageMatrixValues = matrixValues + setBackgroundViewInfoOnFrame( + oldSelectedFrame, + photoEditor.composedCanvas.source as PhotoView ) } // TODO add else clause and handle VIDEO frameItemType @@ -1927,16 +1921,10 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec .load(model) .transform(CenterCrop()) .listener(provideGlideRequestListenerWithHandler { - val backgroundImageSource = photoEditor.composedCanvas.source as PhotoView - val backgroundViewInfo = newSelectedFrame.source.backgroundViewInfo - // load image matrix from data if it exists - backgroundViewInfo?.let { - val matrix = Matrix() - matrix.setValues(it.imageMatrixValues) - backgroundImageSource.apply { - setSuppMatrix(matrix) - } - } + setBackgroundViewInfoOnPhotoView( + newSelectedFrame, + photoEditor.composedCanvas.source as PhotoView + ) }) .into(photoEditorView.source) @@ -1958,6 +1946,30 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec showRetryButtonAndHideEditControlsForErroredFrame(newSelectedFrame.saveResultReason !is SaveSuccess) } + private fun setBackgroundViewInfoOnFrame(frame: StoryFrameItem, backgroundImageSource: PhotoView) { + val matrixValues = FloatArray(9) + val matrix = Matrix() + // fill in matrix with PhotoView Support matrix + backgroundImageSource.getSuppMatrix(matrix) + // extract matrix to float array matrixValues + matrix.getValues(matrixValues) + frame.source.backgroundViewInfo = BackgroundViewInfo( + imageMatrixValues = matrixValues + ) + } + + private fun setBackgroundViewInfoOnPhotoView(frame: StoryFrameItem, backgroundImageSource: PhotoView) { + val backgroundViewInfo = frame.source.backgroundViewInfo + // load image matrix from data if it exists + backgroundViewInfo?.let { + val matrix = Matrix() + matrix.setValues(it.imageMatrixValues) + backgroundImageSource.apply { + setSuppMatrix(matrix) + } + } + } + private fun provideGlideRequestListenerWithHandler(setupPhotoViewMatrix: Runnable): RequestListener { return object : RequestListener { override fun onLoadFailed( From 1963373f584a920d242848a18da2dae7418fffc5 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 24 Feb 2021 18:20:59 -0300 Subject: [PATCH 10/48] removed empty line after brace --- .../java/com/wordpress/stories/compose/story/StoryFrameItem.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/story/StoryFrameItem.kt b/stories/src/main/java/com/wordpress/stories/compose/story/StoryFrameItem.kt index cad42a54a..5f5c1dca3 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/story/StoryFrameItem.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/story/StoryFrameItem.kt @@ -27,7 +27,6 @@ data class StoryFrameItem( var composedFrameFile: File? = null, var id: String? = null ) { - @Serializable data class BackgroundViewInfo( val imageMatrixValues: FloatArray From a35fc3b3349d93fff04f8c694d49bb1d26298e72 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 24 Feb 2021 18:43:25 -0300 Subject: [PATCH 11/48] moved oldSelectedFrame getter code into index boundary check --- .../compose/ComposeLoopFrameActivity.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index e85f7c608..c85b11e92 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -1879,16 +1879,16 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec if (oldIndex >= 0) { // only remember added views for frame if current index is valid addCurrentViewsToFrameAtIndex(oldIndex) - } - // save current imageMatrix as the background image may have been resized - val oldSelectedFrame = storyViewModel.getCurrentStoryFrameAt(oldIndex) - if (oldSelectedFrame?.frameItemType is IMAGE) { - setBackgroundViewInfoOnFrame( - oldSelectedFrame, - photoEditor.composedCanvas.source as PhotoView - ) - } // TODO add else clause and handle VIDEO frameItemType + // save current imageMatrix as the background image may have been resized + val oldSelectedFrame = storyViewModel.getCurrentStoryFrameAt(oldIndex) + if (oldSelectedFrame?.frameItemType is IMAGE) { + setBackgroundViewInfoOnFrame( + oldSelectedFrame, + photoEditor.composedCanvas.source as PhotoView + ) + } // TODO add else clause and handle VIDEO frameItemType + } // This is tricky. See https://stackoverflow.com/questions/45860434/cant-remove-view-from-root-view // we need to disable layout transition animations so changes in views' parent are set From b951f97ae231182208dfff81ea108f9e53c49556 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 1 Mar 2021 16:21:30 -0300 Subject: [PATCH 12/48] handling FIT_CENTER scaleType both in edit mode and in FrameSaveManager --- .../photoeditor/views/PhotoEditorView.kt | 4 ++-- .../stories/compose/ComposeLoopFrameActivity.kt | 14 +++++++++++++- .../stories/compose/frame/FrameSaveManager.kt | 9 +++++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt b/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt index 637168fa6..d539dc143 100644 --- a/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt +++ b/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt @@ -12,7 +12,7 @@ import android.view.TextureView.SurfaceTextureListener import android.view.View import android.view.ViewGroup import android.widget.ImageView -import android.widget.ImageView.ScaleType.CENTER_CROP +import android.widget.ImageView.ScaleType.FIT_CENTER import android.widget.ProgressBar import android.widget.RelativeLayout import androidx.annotation.RequiresApi @@ -109,7 +109,7 @@ class PhotoEditorView : RelativeLayout { backgroundImage = BackgroundImageView(context).apply { id = imgSrcId adjustViewBounds = true - scaleType = CENTER_CROP + scaleType = FIT_CENTER } val imgSrcParam = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT).apply { diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index c85b11e92..8a157eb83 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -67,6 +67,7 @@ import com.automattic.photoeditor.views.added.AddedViewList import com.bumptech.glide.Glide import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.engine.GlideException +import com.bumptech.glide.load.resource.bitmap.FitCenter import com.bumptech.glide.load.resource.bitmap.CenterCrop import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.request.RequestListener @@ -1116,6 +1117,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec private fun saveStoryPreHook() { showLoading() + refreshBackgroundViewInfoOnSelectedFrame() // disable layout change animations, we need this to make added views immediately visible, otherwise // we may end up capturing a Bitmap of a backing drawable that still has not been updated // (i.e. no visible added Views) @@ -1124,6 +1126,15 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec preHookRun = true } + private fun refreshBackgroundViewInfoOnSelectedFrame() { + storyViewModel.getSelectedFrame()?.let { + setBackgroundViewInfoOnFrame( + it, + photoEditor.composedCanvas.source as PhotoView + ) + } + } + private fun saveStoryPostHook(result: StorySaveResult) { doUnbindService() // re-enable layout change animations @@ -1919,7 +1930,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec val model = (source as? FileBackgroundSource)?.file ?: (source as UriBackgroundSource).contentUri Glide.with(this@ComposeLoopFrameActivity) .load(model) - .transform(CenterCrop()) + .transform(FitCenter()) .listener(provideGlideRequestListenerWithHandler { setBackgroundViewInfoOnPhotoView( newSelectedFrame, @@ -2003,6 +2014,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec override fun onStoryFrameAddTapped() { addCurrentViewsToFrameAtIndex(storyViewModel.getSelectedFrameIndex()) + refreshBackgroundViewInfoOnSelectedFrame() showMediaPicker() } diff --git a/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt b/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt index 26a789ee7..bdec4d0eb 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt @@ -7,11 +7,13 @@ import android.graphics.Matrix import android.net.Uri import android.view.View import android.view.ViewGroup.LayoutParams +import android.widget.ImageView.ScaleType.FIT_CENTER import android.widget.RelativeLayout import com.automattic.photoeditor.PhotoEditor import com.automattic.photoeditor.PhotoEditor.OnSaveWithCancelAndProgressListener import com.automattic.photoeditor.views.PhotoEditorView import com.automattic.photoeditor.views.ViewType.STICKER_ANIMATED +import com.automattic.photoeditor.views.background.fixed.BackgroundImageView import com.wordpress.stories.compose.story.StoryFrameItem import com.wordpress.stories.compose.story.StoryFrameItem.BackgroundSource.FileBackgroundSource import com.wordpress.stories.compose.story.StoryFrameItem.BackgroundSource.UriBackgroundSource @@ -22,6 +24,7 @@ import com.wordpress.stories.util.cloneViewSpecs import com.wordpress.stories.util.removeViewFromParent import com.bumptech.glide.Glide import com.bumptech.glide.load.resource.bitmap.CenterCrop +import com.bumptech.glide.load.resource.bitmap.FitCenter import com.bumptech.glide.request.FutureTarget import com.wordpress.stories.util.isSizeRatio916 import com.wordpress.stories.util.normalizeSizeExportTo916 @@ -297,12 +300,14 @@ class FrameSaveManager( val futureTarget = Glide.with(context) .asBitmap() .load(uri) - .transform(CenterCrop()) // also use CenterCrop as it's the same the user was seeing as per WYSIWYG .submit(targetView.measuredWidth, targetView.measuredHeight) val bitmap = futureTarget.get() targetView.setImageBitmap(bitmap) - targetView.imageMatrix = originalMatrix // reset old matrix + (targetView as BackgroundImageView).apply { + scaleType = FIT_CENTER + setSuppMatrix(originalMatrix) + } // removeViewFromParent for views that were added in the UI thread need to also run on the main thread // otherwise we'd get a android.view.ViewRootImpl$CalledFromWrongThreadException: From cb057c00e798f3fa3c41df4f0de14df78628250b Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 1 Mar 2021 16:42:26 -0300 Subject: [PATCH 13/48] removed unused imports, added clarifying comment --- .../com/wordpress/stories/compose/frame/FrameSaveManager.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt b/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt index bdec4d0eb..5993deca5 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt @@ -23,8 +23,6 @@ import com.wordpress.stories.compose.story.StoryFrameItemType.VIDEO import com.wordpress.stories.util.cloneViewSpecs import com.wordpress.stories.util.removeViewFromParent import com.bumptech.glide.Glide -import com.bumptech.glide.load.resource.bitmap.CenterCrop -import com.bumptech.glide.load.resource.bitmap.FitCenter import com.bumptech.glide.request.FutureTarget import com.wordpress.stories.util.isSizeRatio916 import com.wordpress.stories.util.normalizeSizeExportTo916 @@ -304,6 +302,8 @@ class FrameSaveManager( val bitmap = futureTarget.get() targetView.setImageBitmap(bitmap) + // IMPORTANT: scaleType and setSuppMatrix should only be called _after_ the bitmap is set on the targetView + // by means of targetView.setImageBitmap(). Calling this before will have no effect due to PhotoView's checks. (targetView as BackgroundImageView).apply { scaleType = FIT_CENTER setSuppMatrix(originalMatrix) From 19066d57106e4fb5d62adb055a6a0ff514e1ebc2 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 2 Mar 2021 08:24:50 -0300 Subject: [PATCH 14/48] limit workingArea in calculateWorkingArea() to the same size as exported output --- .../wordpress/stories/compose/ComposeLoopFrameActivity.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index 8a157eb83..caf32f3be 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -117,6 +117,7 @@ import com.wordpress.stories.util.STATE_KEY_CURRENT_STORY_INDEX import com.wordpress.stories.util.getDisplayPixelSize import com.wordpress.stories.util.getStoryIndexFromIntentOrBundle import com.wordpress.stories.util.isVideo +import com.wordpress.stories.util.normalizeSizeExportTo916 import kotlinx.android.synthetic.main.activity_composer.* import kotlinx.android.synthetic.main.content_composer.* import kotlinx.coroutines.CoroutineScope @@ -312,14 +313,16 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec val width = photoEditorView.measuredWidth val height = photoEditorView.measuredHeight + val normalizedScreenSize = normalizeSizeExportTo916(width, height).toSize() + val bottomAreaHeight = resources.getDimensionPixelSize(R.dimen.bottom_strip_height) + bottomNavigationBarMargin val topAreaHeight = resources.getDimensionPixelSize(R.dimen.edit_mode_button_size) return Rect( xCoord, yCoord + topAreaHeight, - xCoord + width, - yCoord + height - bottomAreaHeight + xCoord + normalizedScreenSize.width, + yCoord + normalizedScreenSize.height - bottomAreaHeight ) } From c7dea6c9fb429558a34aa348babda358edc7abff Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 2 Mar 2021 20:26:24 -0300 Subject: [PATCH 15/48] using jp.wasabeef:glide-transformations library to add blurried image background --- photoeditor/build.gradle | 2 ++ .../photoeditor/views/PhotoEditorView.kt | 32 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/photoeditor/build.gradle b/photoeditor/build.gradle index a5f9ac119..95cc93b7c 100644 --- a/photoeditor/build.gradle +++ b/photoeditor/build.gradle @@ -54,6 +54,8 @@ dependencies { implementation 'com.github.bumptech.glide:glide:4.10.0' kapt 'com.github.bumptech.glide:compiler:4.10.0' + implementation 'jp.wasabeef:glide-transformations:4.3.0' + implementation 'com.github.chrisbanes:PhotoView:2.3.0' implementation project(path: ':mp4compose') diff --git a/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt b/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt index d539dc143..8b7e4c9d5 100644 --- a/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt +++ b/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt @@ -12,10 +12,12 @@ import android.view.TextureView.SurfaceTextureListener import android.view.View import android.view.ViewGroup import android.widget.ImageView +import android.widget.ImageView.ScaleType.CENTER_CROP import android.widget.ImageView.ScaleType.FIT_CENTER import android.widget.ProgressBar import android.widget.RelativeLayout import androidx.annotation.RequiresApi +import androidx.appcompat.widget.AppCompatImageView import com.automattic.photoeditor.OnSaveBitmap import com.automattic.photoeditor.R.styleable import com.automattic.photoeditor.views.background.fixed.BackgroundImageView @@ -24,6 +26,9 @@ import com.automattic.photoeditor.views.brush.BrushDrawingView import com.automattic.photoeditor.views.filter.CustomEffect import com.automattic.photoeditor.views.filter.ImageFilterView import com.automattic.photoeditor.views.filter.PhotoFilter +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import jp.wasabeef.glide.transformations.BlurTransformation /** * @@ -40,9 +45,11 @@ import com.automattic.photoeditor.views.filter.PhotoFilter class PhotoEditorView : RelativeLayout { private lateinit var autoFitTextureView: AutoFitTextureView private lateinit var backgroundImage: BackgroundImageView + private lateinit var backgroundImageBlurred: AppCompatImageView private lateinit var brushDrawingView: BrushDrawingView private lateinit var imageFilterView: ImageFilterView private lateinit var progressBar: ProgressBar + private var attachedToWindow: Boolean = false private var surfaceListeners: ArrayList = ArrayList() @@ -103,8 +110,24 @@ class PhotoEditorView : RelativeLayout { init(attrs) } + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + attachedToWindow = false + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + attachedToWindow = true + } + @SuppressLint("Recycle") private fun init(attrs: AttributeSet?) { + backgroundImageBlurred = AppCompatImageView(context).apply { + id = imgBlurSrcId + adjustViewBounds = true + scaleType = CENTER_CROP + } + // Setup image attributes backgroundImage = BackgroundImageView(context).apply { id = imgSrcId @@ -159,6 +182,11 @@ class PhotoEditorView : RelativeLayout { backgroundImage.setOnImageChangedListener(object : BackgroundImageView.OnImageChangedListener { override fun onBitmapLoaded(sourceBitmap: Bitmap?) { + if (attachedToWindow) { + Glide.with(context).load(sourceBitmap) + .apply(RequestOptions.bitmapTransform(BlurTransformation(25, 3))) + .into(backgroundImageBlurred) + } imageFilterView.setFilterEffect(PhotoFilter.NONE) imageFilterView.setSourceBitmap(sourceBitmap) Log.d(TAG, "onBitmapLoaded() called with: sourceBitmap = [$sourceBitmap]") @@ -179,6 +207,9 @@ class PhotoEditorView : RelativeLayout { // Add camera preview addView(autoFitTextureView, cameraParam) + // Add image source + addView(backgroundImageBlurred, imgSrcParam) + // Add image source addView(backgroundImage, imgSrcParam) @@ -286,6 +317,7 @@ class PhotoEditorView : RelativeLayout { companion object { private val TAG = "PhotoEditorView" + private val imgBlurSrcId = 5 private val imgSrcId = 1 private val brushSrcId = 2 private val glFilterId = 3 From 43755837b440b2d5d0f2ab73f4f12b605ff7c252 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 3 Mar 2021 13:34:13 -0300 Subject: [PATCH 16/48] added an opaque bottom bar on top of background views to match the normalizedExportedSize on screen, for handsets with an aspect ratio taller than 9:16 --- app/src/main/res/values/colors.xml | 1 + .../photoeditor/views/PhotoEditorView.kt | 29 ++++++++++++++++ .../compose/ComposeLoopFrameActivity.kt | 34 +++++++++++++++++++ .../story/StoryFrameSelectorFragment.kt | 11 ++++++ .../com/wordpress/stories/util/ViewUtils.kt | 4 +++ stories/src/main/res/values/colors.xml | 1 + 6 files changed, 80 insertions(+) diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 87ec31ecf..a7d628acf 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -23,6 +23,7 @@ #99000000 #4D000000 @color/black_transp_light + @color/black @color/black_transp_light @color/black_transp_light_darker #4D000000 diff --git a/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt b/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt index d539dc143..b44778159 100644 --- a/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt +++ b/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt @@ -3,6 +3,7 @@ package com.automattic.photoeditor.views import android.annotation.SuppressLint import android.content.Context import android.graphics.Bitmap +import android.graphics.Color import android.graphics.SurfaceTexture import android.os.Build import android.util.AttributeSet @@ -17,6 +18,7 @@ import android.widget.ProgressBar import android.widget.RelativeLayout import androidx.annotation.RequiresApi import com.automattic.photoeditor.OnSaveBitmap +import com.automattic.photoeditor.R import com.automattic.photoeditor.R.styleable import com.automattic.photoeditor.views.background.fixed.BackgroundImageView import com.automattic.photoeditor.views.background.video.AutoFitTextureView @@ -39,6 +41,7 @@ import com.automattic.photoeditor.views.filter.PhotoFilter class PhotoEditorView : RelativeLayout { private lateinit var autoFitTextureView: AutoFitTextureView + private lateinit var bottomOpaqueBar: View private lateinit var backgroundImage: BackgroundImageView private lateinit var brushDrawingView: BrushDrawingView private lateinit var imageFilterView: ImageFilterView @@ -176,6 +179,16 @@ class PhotoEditorView : RelativeLayout { addRule(ALIGN_BOTTOM, imgSrcId) } + // Setup image attributes + bottomOpaqueBar = View(context).apply { + id = bottomOpaqueBarId + setBackgroundColor(Color.parseColor("#000000")) + } + + val bottomOpaqueBarParams = LayoutParams(LayoutParams.MATCH_PARENT, 0).apply { + addRule(ALIGN_PARENT_BOTTOM, TRUE) + } + // Add camera preview addView(autoFitTextureView, cameraParam) @@ -190,6 +203,9 @@ class PhotoEditorView : RelativeLayout { // Add progress view addView(progressBar, progressBarParam) + + // finally add the opaque bar that will be drawn for FIT_SCALE positioning on taller than 9:16 screens + addView(bottomOpaqueBar, bottomOpaqueBarParams) } // added this method as a helper due to the reasons outlined here: @@ -212,6 +228,18 @@ class PhotoEditorView : RelativeLayout { parent.addView(textureView, index, cameraParam) } + fun setOpaqueBarHeight(height: Int) { + bottomOpaqueBar.layoutParams.height = height + } + + fun hideOpaqueBar() { + bottomOpaqueBar.visibility = View.INVISIBLE + } + + fun showOpaqueBar() { + bottomOpaqueBar.visibility = View.VISIBLE + } + internal fun saveFilter(onSaveBitmap: OnSaveBitmap) { // check which background is currently visible: if it's // - imageFilterView: a filter has been applied, process it first @@ -286,6 +314,7 @@ class PhotoEditorView : RelativeLayout { companion object { private val TAG = "PhotoEditorView" + private val bottomOpaqueBarId = 6 private val imgSrcId = 1 private val brushSrcId = 2 private val glFilterId = 3 diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index caf32f3be..49dd99963 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -25,6 +25,7 @@ import android.os.Vibrator import android.provider.Settings import android.text.TextUtils import android.util.Log +import android.util.TypedValue import android.view.GestureDetector import android.view.Gravity import android.view.MotionEvent @@ -116,10 +117,12 @@ import com.wordpress.stories.util.KEY_STORY_SAVE_RESULT import com.wordpress.stories.util.STATE_KEY_CURRENT_STORY_INDEX import com.wordpress.stories.util.getDisplayPixelSize import com.wordpress.stories.util.getStoryIndexFromIntentOrBundle +import com.wordpress.stories.util.isScreenTallerThan916 import com.wordpress.stories.util.isVideo import com.wordpress.stories.util.normalizeSizeExportTo916 import kotlinx.android.synthetic.main.activity_composer.* import kotlinx.android.synthetic.main.content_composer.* +import kotlinx.android.synthetic.main.fragment_story_frame_selector.view.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -200,6 +203,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec private lateinit var backgroundSurfaceManager: BackgroundSurfaceManager private var currentOriginalCapturedFile: File? = null private lateinit var workingAreaRect: Rect + private var bottomOpaqueBarHeight: Int = 0 // default: no opaque bottom bar private val timesUpRunnable = Runnable { stopRecordingVideo(false) // time's up, it's not a cancellation @@ -341,6 +345,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec bottomNavigationBarMargin = insets.systemWindowInsetBottom workingAreaRect = calculateWorkingArea() photoEditor.updateWorkAreaRect(workingAreaRect) + bottomOpaqueBarHeight = preCalculateOpaqueBarHeight() delete_view.addBottomOffset(bottomNavigationBarMargin) delete_slide_view.addBottomOffset(bottomNavigationBarMargin) (bottom_strip_view as StoryFrameSelectorFragment).setBottomOffset(bottomNavigationBarMargin) @@ -552,6 +557,32 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec } } + private fun preCalculateOpaqueBarHeight(): Int { + val width = resources.displayMetrics.widthPixels + val height = resources.displayMetrics.heightPixels + if (isScreenTallerThan916(width, height)) { + val normalizedSize = normalizeSizeExportTo916(width, height).toSize() + val normalizedHeightDp = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, normalizedSize.height.toFloat(), resources.displayMetrics) + val screenHeightDp = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, height.toFloat(), resources.displayMetrics) + return Math.round(screenHeightDp - normalizedHeightDp) // / 2 + } else { + return 0 + } + } + + private fun setOpaqueBarHeightAndStoryFrameSelectorBackgroundColor() { + photoEditorView.setOpaqueBarHeight(bottomOpaqueBarHeight) + val screenWidth = resources.displayMetrics.widthPixels + val screenHeight = resources.displayMetrics.heightPixels + if (isScreenTallerThan916(screenWidth, screenHeight)) { + (bottom_strip_view as StoryFrameSelectorFragment).setBackgroundColor(R.color.black_opaque_story_frame_selector) + } else { + (bottom_strip_view as StoryFrameSelectorFragment).setBackgroundColor(R.color.black_transp_story_frame_selector) + } + } + override fun onStart() { super.onStart() val selectedFrameIndex = storyViewModel.getSelectedFrameIndex() @@ -1597,10 +1628,13 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec private fun hideStoryFrameSelector() { (bottom_strip_view as StoryFrameSelectorFragment).hide() + photoEditorView.hideOpaqueBar() } private fun showStoryFrameSelector() { + setOpaqueBarHeightAndStoryFrameSelectorBackgroundColor() (bottom_strip_view as StoryFrameSelectorFragment).show() + photoEditorView.showOpaqueBar() } private fun hideEditModeUIControls() { diff --git a/stories/src/main/java/com/wordpress/stories/compose/story/StoryFrameSelectorFragment.kt b/stories/src/main/java/com/wordpress/stories/compose/story/StoryFrameSelectorFragment.kt index 22d8d2210..b4a0691f7 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/story/StoryFrameSelectorFragment.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/story/StoryFrameSelectorFragment.kt @@ -31,6 +31,7 @@ interface OnStoryFrameSelectorTappedListener { class StoryFrameSelectorFragment : Fragment() { lateinit var storyViewModel: StoryViewModel private var storyFrameTappedListener: OnStoryFrameSelectorTappedListener? = null + private var backgroundColorResId: Int = R.color.black_transp_story_frame_selector // default background color override fun onCreate(savedInstanceState: Bundle?) { val storyIndex: StoryIndex = getStoryIndexFromIntentOrBundle(savedInstanceState, activity?.intent) @@ -97,6 +98,7 @@ class StoryFrameSelectorFragment : Fragment() { storyViewModel.addButtonClicked.call() } setupItemTouchListener(view) + applyBackgroundColor() view.visibility = View.INVISIBLE return view } @@ -206,6 +208,15 @@ class StoryFrameSelectorFragment : Fragment() { view?.plus_icon?.visibility = View.VISIBLE } + private fun applyBackgroundColor() { + view?.setBackgroundResource(backgroundColorResId) + } + + fun setBackgroundColor(colorResId: Int) { + backgroundColorResId = colorResId + applyBackgroundColor() + } + fun setBottomOffset(offset: Int) { val params = view?.layoutParams as ConstraintLayout.LayoutParams val hasChanged = params.bottomMargin != offset diff --git a/stories/src/main/java/com/wordpress/stories/util/ViewUtils.kt b/stories/src/main/java/com/wordpress/stories/util/ViewUtils.kt index 78175618c..fe45e3af2 100644 --- a/stories/src/main/java/com/wordpress/stories/util/ViewUtils.kt +++ b/stories/src/main/java/com/wordpress/stories/util/ViewUtils.kt @@ -39,6 +39,10 @@ fun isSizeRatio916(originalWidth: Int, originalHeight: Int): Boolean { return (originalWidth.toFloat() / originalHeight.toFloat()) == TARGET_RATIO_9_16 } +fun isScreenTallerThan916(originalWidth: Int, originalHeight: Int): Boolean { + return (originalWidth.toFloat() / originalHeight.toFloat()) < TARGET_RATIO_9_16 +} + fun normalizeSizeExportTo916(originalWidth: Int, originalHeight: Int): ScreenSize { /* 1. if the screen is 16:9, we're OK diff --git a/stories/src/main/res/values/colors.xml b/stories/src/main/res/values/colors.xml index c6f3939bf..c053ab748 100644 --- a/stories/src/main/res/values/colors.xml +++ b/stories/src/main/res/values/colors.xml @@ -19,6 +19,7 @@ #66000000 #99000000 @color/black_transp_light + @color/black @color/black_transp_light @color/black_transp_light_darker From b893880263e788c370fa8290470a77cef77dd60d Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 3 Mar 2021 14:40:47 -0300 Subject: [PATCH 17/48] removed unused import --- .../java/com/automattic/photoeditor/views/PhotoEditorView.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt b/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt index b44778159..19ecc7a9c 100644 --- a/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt +++ b/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt @@ -18,7 +18,6 @@ import android.widget.ProgressBar import android.widget.RelativeLayout import androidx.annotation.RequiresApi import com.automattic.photoeditor.OnSaveBitmap -import com.automattic.photoeditor.R import com.automattic.photoeditor.R.styleable import com.automattic.photoeditor.views.background.fixed.BackgroundImageView import com.automattic.photoeditor.views.background.video.AutoFitTextureView From 62072d03da138380f4841620a8b7926588b753f7 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 3 Mar 2021 14:44:46 -0300 Subject: [PATCH 18/48] fixed line lenght excess --- .../wordpress/stories/compose/ComposeLoopFrameActivity.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index 49dd99963..c79ce2a28 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -577,9 +577,11 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec val screenWidth = resources.displayMetrics.widthPixels val screenHeight = resources.displayMetrics.heightPixels if (isScreenTallerThan916(screenWidth, screenHeight)) { - (bottom_strip_view as StoryFrameSelectorFragment).setBackgroundColor(R.color.black_opaque_story_frame_selector) + (bottom_strip_view as StoryFrameSelectorFragment) + .setBackgroundColor(R.color.black_opaque_story_frame_selector) } else { - (bottom_strip_view as StoryFrameSelectorFragment).setBackgroundColor(R.color.black_transp_story_frame_selector) + (bottom_strip_view as StoryFrameSelectorFragment) + .setBackgroundColor(R.color.black_transp_story_frame_selector) } } From 6afe6122ffdc3597c49068b4169ed24cd095a5ad Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 3 Mar 2021 15:33:21 -0300 Subject: [PATCH 19/48] moved the bottomOpaqueBar up from PhotoEditorView to the container in ComposeLoopFrameActivity as it's easier to handle z-index of AddedViews at that level --- .../photoeditor/views/PhotoEditorView.kt | 27 ------------------- .../compose/ComposeLoopFrameActivity.kt | 10 ++++--- .../src/main/res/layout/content_composer.xml | 9 +++++++ 3 files changed, 16 insertions(+), 30 deletions(-) diff --git a/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt b/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt index 19ecc7a9c..28e4061c9 100644 --- a/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt +++ b/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt @@ -40,7 +40,6 @@ import com.automattic.photoeditor.views.filter.PhotoFilter class PhotoEditorView : RelativeLayout { private lateinit var autoFitTextureView: AutoFitTextureView - private lateinit var bottomOpaqueBar: View private lateinit var backgroundImage: BackgroundImageView private lateinit var brushDrawingView: BrushDrawingView private lateinit var imageFilterView: ImageFilterView @@ -178,16 +177,6 @@ class PhotoEditorView : RelativeLayout { addRule(ALIGN_BOTTOM, imgSrcId) } - // Setup image attributes - bottomOpaqueBar = View(context).apply { - id = bottomOpaqueBarId - setBackgroundColor(Color.parseColor("#000000")) - } - - val bottomOpaqueBarParams = LayoutParams(LayoutParams.MATCH_PARENT, 0).apply { - addRule(ALIGN_PARENT_BOTTOM, TRUE) - } - // Add camera preview addView(autoFitTextureView, cameraParam) @@ -202,9 +191,6 @@ class PhotoEditorView : RelativeLayout { // Add progress view addView(progressBar, progressBarParam) - - // finally add the opaque bar that will be drawn for FIT_SCALE positioning on taller than 9:16 screens - addView(bottomOpaqueBar, bottomOpaqueBarParams) } // added this method as a helper due to the reasons outlined here: @@ -227,18 +213,6 @@ class PhotoEditorView : RelativeLayout { parent.addView(textureView, index, cameraParam) } - fun setOpaqueBarHeight(height: Int) { - bottomOpaqueBar.layoutParams.height = height - } - - fun hideOpaqueBar() { - bottomOpaqueBar.visibility = View.INVISIBLE - } - - fun showOpaqueBar() { - bottomOpaqueBar.visibility = View.VISIBLE - } - internal fun saveFilter(onSaveBitmap: OnSaveBitmap) { // check which background is currently visible: if it's // - imageFilterView: a filter has been applied, process it first @@ -313,7 +287,6 @@ class PhotoEditorView : RelativeLayout { companion object { private val TAG = "PhotoEditorView" - private val bottomOpaqueBarId = 6 private val imgSrcId = 1 private val brushSrcId = 2 private val glFilterId = 3 diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index c79ce2a28..610d7552b 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -573,7 +573,11 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec } private fun setOpaqueBarHeightAndStoryFrameSelectorBackgroundColor() { - photoEditorView.setOpaqueBarHeight(bottomOpaqueBarHeight) + if (bottomOpaqueBarHeight > 0) { + bottom_opaque_bar.layoutParams.height = bottomOpaqueBarHeight + } else { + bottom_opaque_bar.visibility = View.GONE + } val screenWidth = resources.displayMetrics.widthPixels val screenHeight = resources.displayMetrics.heightPixels if (isScreenTallerThan916(screenWidth, screenHeight)) { @@ -1630,13 +1634,13 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec private fun hideStoryFrameSelector() { (bottom_strip_view as StoryFrameSelectorFragment).hide() - photoEditorView.hideOpaqueBar() + bottom_opaque_bar.visibility = View.INVISIBLE } private fun showStoryFrameSelector() { setOpaqueBarHeightAndStoryFrameSelectorBackgroundColor() + bottom_opaque_bar.visibility = View.VISIBLE (bottom_strip_view as StoryFrameSelectorFragment).show() - photoEditorView.showOpaqueBar() } private fun hideEditModeUIControls() { diff --git a/stories/src/main/res/layout/content_composer.xml b/stories/src/main/res/layout/content_composer.xml index d18431d0c..fb8414553 100644 --- a/stories/src/main/res/layout/content_composer.xml +++ b/stories/src/main/res/layout/content_composer.xml @@ -20,6 +20,15 @@ android:animateLayoutChanges="true" android:background="@color/black" /> + + Date: Wed, 3 Mar 2021 15:34:03 -0300 Subject: [PATCH 20/48] removed unused import --- .../java/com/automattic/photoeditor/views/PhotoEditorView.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt b/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt index 28e4061c9..d539dc143 100644 --- a/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt +++ b/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt @@ -3,7 +3,6 @@ package com.automattic.photoeditor.views import android.annotation.SuppressLint import android.content.Context import android.graphics.Bitmap -import android.graphics.Color import android.graphics.SurfaceTexture import android.os.Build import android.util.AttributeSet From 7d77250904812aeb6beb8b8f029d635ed23fbd81 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 3 Mar 2021 17:39:50 -0300 Subject: [PATCH 21/48] added method onComposerDestroyed so we can avoid trying to load any images in the image listener --- .../com/automattic/photoeditor/views/PhotoEditorView.kt | 7 +++++++ .../wordpress/stories/compose/ComposeLoopFrameActivity.kt | 1 + 2 files changed, 8 insertions(+) diff --git a/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt b/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt index 8b7e4c9d5..185600740 100644 --- a/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt +++ b/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt @@ -79,6 +79,9 @@ class PhotoEditorView : RelativeLayout { val source: ImageView get() = backgroundImage + val sourceBlurredBkg: ImageView + get() = backgroundImageBlurred + val brush: BrushDrawingView get() = brushDrawingView @@ -120,6 +123,10 @@ class PhotoEditorView : RelativeLayout { attachedToWindow = true } + fun onComposerDestroyed() { + attachedToWindow = false + } + @SuppressLint("Recycle") private fun init(attrs: AttributeSet?) { backgroundImageBlurred = AppCompatImageView(context).apply { diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index 610d7552b..42781eb19 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -766,6 +766,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec } override fun onDestroy() { + photoEditorView.onComposerDestroyed() doUnbindService() EventBus.getDefault().unregister(this) super.onDestroy() From f99c31a78e157aefe81f2fe3d248acb70db5daa9 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 3 Mar 2021 17:41:24 -0300 Subject: [PATCH 22/48] adding blurry image background loading when saving the output file, refactored to return a Pair of futures so these can be cleared after saving --- stories/build.gradle | 2 ++ .../stories/compose/frame/FrameSaveManager.kt | 29 +++++++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/stories/build.gradle b/stories/build.gradle index 779778fdd..1c02f8175 100644 --- a/stories/build.gradle +++ b/stories/build.gradle @@ -52,6 +52,8 @@ dependencies { implementation 'com.github.bumptech.glide:glide:4.10.0' kapt 'com.github.bumptech.glide:compiler:4.10.0' + implementation 'jp.wasabeef:glide-transformations:4.3.0' + implementation 'org.greenrobot:eventbus:3.1.1' implementation project(path: ':photoeditor') diff --git a/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt b/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt index 5993deca5..c1efc29e7 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt @@ -24,6 +24,7 @@ import com.wordpress.stories.util.cloneViewSpecs import com.wordpress.stories.util.removeViewFromParent import com.bumptech.glide.Glide import com.bumptech.glide.request.FutureTarget +import com.bumptech.glide.request.RequestOptions import com.wordpress.stories.util.isSizeRatio916 import com.wordpress.stories.util.normalizeSizeExportTo916 import kotlinx.coroutines.CoroutineScope @@ -38,6 +39,7 @@ import kotlinx.coroutines.withContext import kotlinx.coroutines.yield import java.io.File import kotlin.coroutines.CoroutineContext +import jp.wasabeef.glide.transformations.BlurTransformation typealias FrameIndex = Int @@ -178,7 +180,7 @@ class FrameSaveManager( frameIndex: FrameIndex ): File { // prepare the ghostview with its background image and the AddedViews on top of it - val futureTarget = preparePhotoEditorViewForSnapshot(context, frame, originalMatrix, ghostPhotoEditorView) + val futureTargetPair = preparePhotoEditorViewForSnapshot(context, frame, originalMatrix, ghostPhotoEditorView) val file = withContext(Dispatchers.IO) { if (normalizeTo916 && !isSizeRatio916(ghostPhotoEditorView.width, ghostPhotoEditorView.height)) { @@ -196,7 +198,8 @@ class FrameSaveManager( } releaseAddedViewsAfterSnapshot(frame) - Glide.with(context).clear(futureTarget) + Glide.with(context).clear(futureTargetPair.first) + Glide.with(context).clear(futureTargetPair.second) return file } @@ -286,11 +289,26 @@ class FrameSaveManager( frame: StoryFrameItem, originalMatrix: Matrix, ghostPhotoEditorView: PhotoEditorView - ): FutureTarget { + ): Pair, FutureTarget> { // prepare background val uri = (frame.source as? UriBackgroundSource)?.contentUri ?: (frame.source as FileBackgroundSource).file + // ----------------------------------- + // first set the background blurred image + val targetBlurredView = ghostPhotoEditorView.sourceBlurredBkg + + // making use of Glide to decode bitmap and get the right orientation automatically + // http://bumptech.github.io/glide/doc/getting-started.html#background-threads + val futureBlurredTarget = Glide.with(context) + .asBitmap() + .load(uri) + .apply(RequestOptions.bitmapTransform(BlurTransformation(25, 3))) + .submit(targetBlurredView.measuredWidth, targetBlurredView.measuredHeight) + targetBlurredView.setImageBitmap(futureBlurredTarget.get()) + + // ----------------------------------- + // now set the actual background image val targetView = ghostPhotoEditorView.source // making use of Glide to decode bitmap and get the right orientation automatically @@ -299,8 +317,7 @@ class FrameSaveManager( .asBitmap() .load(uri) .submit(targetView.measuredWidth, targetView.measuredHeight) - val bitmap = futureTarget.get() - targetView.setImageBitmap(bitmap) + targetView.setImageBitmap(futureTarget.get()) // IMPORTANT: scaleType and setSuppMatrix should only be called _after_ the bitmap is set on the targetView // by means of targetView.setImageBitmap(). Calling this before will have no effect due to PhotoView's checks. @@ -325,7 +342,7 @@ class FrameSaveManager( } } } - return futureTarget + return Pair, FutureTarget>(futureTarget, futureBlurredTarget) } private fun getViewLayoutParams(): LayoutParams { From d28da3a9a69c2f11569093a41d3ba6723d391168 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 3 Mar 2021 17:45:01 -0300 Subject: [PATCH 23/48] fixed lint error --- .../com/wordpress/stories/compose/frame/FrameSaveManager.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt b/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt index c1efc29e7..0e114a310 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt @@ -289,7 +289,7 @@ class FrameSaveManager( frame: StoryFrameItem, originalMatrix: Matrix, ghostPhotoEditorView: PhotoEditorView - ): Pair, FutureTarget> { + ): Pair, FutureTarget> { // prepare background val uri = (frame.source as? UriBackgroundSource)?.contentUri ?: (frame.source as FileBackgroundSource).file From f4745ab690fd74e5806a562350e2f7d29e2d4d80 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 4 Mar 2021 19:24:45 -0300 Subject: [PATCH 24/48] removed the calculations to get real pixels; these were wrong because all calculations are based on dp so no need to convert to real pixels before passing to layoutParams.height --- .../wordpress/stories/compose/ComposeLoopFrameActivity.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index 610d7552b..a7e7ed2e2 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -562,11 +562,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec val height = resources.displayMetrics.heightPixels if (isScreenTallerThan916(width, height)) { val normalizedSize = normalizeSizeExportTo916(width, height).toSize() - val normalizedHeightDp = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, normalizedSize.height.toFloat(), resources.displayMetrics) - val screenHeightDp = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, height.toFloat(), resources.displayMetrics) - return Math.round(screenHeightDp - normalizedHeightDp) // / 2 + return (height - normalizedSize.height) } else { return 0 } From b1d5d8d6b9b06360cd1c59350177a0ff2c89501b Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 4 Mar 2021 19:36:25 -0300 Subject: [PATCH 25/48] removed unused import --- .../com/wordpress/stories/compose/ComposeLoopFrameActivity.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index a7e7ed2e2..7e887fca8 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -25,7 +25,6 @@ import android.os.Vibrator import android.provider.Settings import android.text.TextUtils import android.util.Log -import android.util.TypedValue import android.view.GestureDetector import android.view.Gravity import android.view.MotionEvent From b90e3e1e0da0030d1ee61d892f71092701fbfa55 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 4 Mar 2021 19:38:35 -0300 Subject: [PATCH 26/48] Revert "limit workingArea in calculateWorkingArea() to the same size as exported output" This reverts commit 19066d57106e4fb5d62adb055a6a0ff514e1ebc2. --- .../wordpress/stories/compose/ComposeLoopFrameActivity.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index 7e887fca8..ee9212afb 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -118,7 +118,6 @@ import com.wordpress.stories.util.getDisplayPixelSize import com.wordpress.stories.util.getStoryIndexFromIntentOrBundle import com.wordpress.stories.util.isScreenTallerThan916 import com.wordpress.stories.util.isVideo -import com.wordpress.stories.util.normalizeSizeExportTo916 import kotlinx.android.synthetic.main.activity_composer.* import kotlinx.android.synthetic.main.content_composer.* import kotlinx.android.synthetic.main.fragment_story_frame_selector.view.* @@ -316,16 +315,14 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec val width = photoEditorView.measuredWidth val height = photoEditorView.measuredHeight - val normalizedScreenSize = normalizeSizeExportTo916(width, height).toSize() - val bottomAreaHeight = resources.getDimensionPixelSize(R.dimen.bottom_strip_height) + bottomNavigationBarMargin val topAreaHeight = resources.getDimensionPixelSize(R.dimen.edit_mode_button_size) return Rect( xCoord, yCoord + topAreaHeight, - xCoord + normalizedScreenSize.width, - yCoord + normalizedScreenSize.height - bottomAreaHeight + xCoord + width, + yCoord + height - bottomAreaHeight ) } From f06edcda58d61e4774de59e48a9f35e6aeb6f4b8 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 4 Mar 2021 19:48:01 -0300 Subject: [PATCH 27/48] added missing import --- .../com/wordpress/stories/compose/ComposeLoopFrameActivity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index ee9212afb..1497951e4 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -118,6 +118,7 @@ import com.wordpress.stories.util.getDisplayPixelSize import com.wordpress.stories.util.getStoryIndexFromIntentOrBundle import com.wordpress.stories.util.isScreenTallerThan916 import com.wordpress.stories.util.isVideo +import com.wordpress.stories.util.normalizeSizeExportTo916 import kotlinx.android.synthetic.main.activity_composer.* import kotlinx.android.synthetic.main.content_composer.* import kotlinx.android.synthetic.main.fragment_story_frame_selector.view.* From 57f0f5527cf388f2eda73df162242908d0b158f8 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 5 Mar 2021 11:00:52 -0300 Subject: [PATCH 28/48] leaving StoryFrameSelector background semitransparent --- app/src/main/res/values/colors.xml | 2 +- .../stories/compose/ComposeLoopFrameActivity.kt | 14 ++------------ stories/src/main/res/layout/content_composer.xml | 2 +- stories/src/main/res/values/colors.xml | 2 +- 4 files changed, 5 insertions(+), 15 deletions(-) diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index a7d628acf..48e2411c6 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -23,7 +23,7 @@ #99000000 #4D000000 @color/black_transp_light - @color/black + @color/black @color/black_transp_light @color/black_transp_light_darker #4D000000 diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index 1497951e4..fa58e03d2 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -121,7 +121,6 @@ import com.wordpress.stories.util.isVideo import com.wordpress.stories.util.normalizeSizeExportTo916 import kotlinx.android.synthetic.main.activity_composer.* import kotlinx.android.synthetic.main.content_composer.* -import kotlinx.android.synthetic.main.fragment_story_frame_selector.view.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -565,21 +564,12 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec } } - private fun setOpaqueBarHeightAndStoryFrameSelectorBackgroundColor() { + private fun setOpaqueBarHeight() { if (bottomOpaqueBarHeight > 0) { bottom_opaque_bar.layoutParams.height = bottomOpaqueBarHeight } else { bottom_opaque_bar.visibility = View.GONE } - val screenWidth = resources.displayMetrics.widthPixels - val screenHeight = resources.displayMetrics.heightPixels - if (isScreenTallerThan916(screenWidth, screenHeight)) { - (bottom_strip_view as StoryFrameSelectorFragment) - .setBackgroundColor(R.color.black_opaque_story_frame_selector) - } else { - (bottom_strip_view as StoryFrameSelectorFragment) - .setBackgroundColor(R.color.black_transp_story_frame_selector) - } } override fun onStart() { @@ -1631,7 +1621,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec } private fun showStoryFrameSelector() { - setOpaqueBarHeightAndStoryFrameSelectorBackgroundColor() + setOpaqueBarHeight() bottom_opaque_bar.visibility = View.VISIBLE (bottom_strip_view as StoryFrameSelectorFragment).show() } diff --git a/stories/src/main/res/layout/content_composer.xml b/stories/src/main/res/layout/content_composer.xml index fb8414553..10fadc358 100644 --- a/stories/src/main/res/layout/content_composer.xml +++ b/stories/src/main/res/layout/content_composer.xml @@ -26,7 +26,7 @@ android:layout_height="1dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintRight_toRightOf="parent" - android:background="@color/black_opaque_story_frame_selector" + android:background="@color/black_opaque_bar" /> #66000000 #99000000 @color/black_transp_light - @color/black + @color/black @color/black_transp_light @color/black_transp_light_darker From c166db5cdabf5c8ea01d8720af823df34e356a31 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 5 Mar 2021 11:18:13 -0300 Subject: [PATCH 29/48] removed code for changig StoryFrameSelector's background color --- .../compose/story/StoryFrameSelectorFragment.kt | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/story/StoryFrameSelectorFragment.kt b/stories/src/main/java/com/wordpress/stories/compose/story/StoryFrameSelectorFragment.kt index b4a0691f7..22d8d2210 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/story/StoryFrameSelectorFragment.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/story/StoryFrameSelectorFragment.kt @@ -31,7 +31,6 @@ interface OnStoryFrameSelectorTappedListener { class StoryFrameSelectorFragment : Fragment() { lateinit var storyViewModel: StoryViewModel private var storyFrameTappedListener: OnStoryFrameSelectorTappedListener? = null - private var backgroundColorResId: Int = R.color.black_transp_story_frame_selector // default background color override fun onCreate(savedInstanceState: Bundle?) { val storyIndex: StoryIndex = getStoryIndexFromIntentOrBundle(savedInstanceState, activity?.intent) @@ -98,7 +97,6 @@ class StoryFrameSelectorFragment : Fragment() { storyViewModel.addButtonClicked.call() } setupItemTouchListener(view) - applyBackgroundColor() view.visibility = View.INVISIBLE return view } @@ -208,15 +206,6 @@ class StoryFrameSelectorFragment : Fragment() { view?.plus_icon?.visibility = View.VISIBLE } - private fun applyBackgroundColor() { - view?.setBackgroundResource(backgroundColorResId) - } - - fun setBackgroundColor(colorResId: Int) { - backgroundColorResId = colorResId - applyBackgroundColor() - } - fun setBottomOffset(offset: Int) { val params = view?.layoutParams as ConstraintLayout.LayoutParams val hasChanged = params.bottomMargin != offset From 8c90acab5c4a5c2f7d27fb61819d7d95b73f3daa Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 5 Mar 2021 18:22:02 -0300 Subject: [PATCH 30/48] implemented Glide loading with FitCenter or CenterCrop transforms according to conditions of aspect ratio matching being met --- .../compose/ComposeLoopFrameActivity.kt | 150 +++++++++++++++--- .../com/wordpress/stories/util/ViewUtils.kt | 15 ++ 2 files changed, 140 insertions(+), 25 deletions(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index fa58e03d2..8cd19f5c8 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -25,12 +25,15 @@ import android.os.Vibrator import android.provider.Settings import android.text.TextUtils import android.util.Log +import android.util.Size import android.view.GestureDetector import android.view.Gravity import android.view.MotionEvent import android.view.View import android.view.View.OnClickListener import android.webkit.MimeTypeMap +import android.widget.RelativeLayout +import android.widget.RelativeLayout.LayoutParams import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.constraintlayout.widget.Group @@ -67,6 +70,7 @@ import com.automattic.photoeditor.views.added.AddedViewList import com.bumptech.glide.Glide import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.engine.GlideException +import com.bumptech.glide.load.resource.bitmap.BitmapTransformation import com.bumptech.glide.load.resource.bitmap.FitCenter import com.bumptech.glide.load.resource.bitmap.CenterCrop import com.bumptech.glide.load.resource.bitmap.RoundedCorners @@ -114,8 +118,12 @@ import com.wordpress.stories.compose.text.TextStyleGroupManager import com.wordpress.stories.util.KEY_STORY_EDIT_MODE import com.wordpress.stories.util.KEY_STORY_SAVE_RESULT import com.wordpress.stories.util.STATE_KEY_CURRENT_STORY_INDEX +import com.wordpress.stories.util.TARGET_RATIO_9_16 +import com.wordpress.stories.util.calculateAspectRatioForDrawable import com.wordpress.stories.util.getDisplayPixelSize +import com.wordpress.stories.util.getSizeRatio import com.wordpress.stories.util.getStoryIndexFromIntentOrBundle +import com.wordpress.stories.util.isAspectRatioSimilarByPercentage import com.wordpress.stories.util.isScreenTallerThan916 import com.wordpress.stories.util.isVideo import com.wordpress.stories.util.normalizeSizeExportTo916 @@ -247,6 +255,11 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec private var genericAnnouncementDialogProvider: GenericAnnouncementDialogProvider? = null private var showGenericAnnouncementDialogWhenReady = false private var useTempCaptureFile = true + private var screenWidth: Int = 1080 //default + private var screenHeight: Int = 1920 //default + private var screenSizeRatio: Float = TARGET_RATIO_9_16 + private lateinit var normalizedSize: Size + private val connection = object : ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { @@ -342,6 +355,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec workingAreaRect = calculateWorkingArea() photoEditor.updateWorkAreaRect(workingAreaRect) bottomOpaqueBarHeight = preCalculateOpaqueBarHeight() + screenSizeRatio = getSizeRatio(screenWidth, screenHeight) delete_view.addBottomOffset(bottomNavigationBarMargin) delete_slide_view.addBottomOffset(bottomNavigationBarMargin) (bottom_strip_view as StoryFrameSelectorFragment).setBottomOffset(bottomNavigationBarMargin) @@ -554,11 +568,11 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec } private fun preCalculateOpaqueBarHeight(): Int { - val width = resources.displayMetrics.widthPixels - val height = resources.displayMetrics.heightPixels - if (isScreenTallerThan916(width, height)) { - val normalizedSize = normalizeSizeExportTo916(width, height).toSize() - return (height - normalizedSize.height) + screenWidth = resources.displayMetrics.widthPixels + screenHeight = resources.displayMetrics.heightPixels + if (isScreenTallerThan916(screenWidth, screenHeight)) { + normalizedSize = normalizeSizeExportTo916(screenWidth, screenHeight).toSize() + return (screenHeight - normalizedSize.height) } else { return 0 } @@ -574,11 +588,11 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec override fun onStart() { super.onStart() - val selectedFrameIndex = storyViewModel.getSelectedFrameIndex() - if (!launchCameraRequestPending && !launchVideoPlayerRequestPending && - selectedFrameIndex < storyViewModel.getCurrentStorySize()) { - updateBackgroundSurfaceUIWithStoryFrame(selectedFrameIndex) - } +// val selectedFrameIndex = storyViewModel.getSelectedFrameIndex() +// if (!launchCameraRequestPending && !launchVideoPlayerRequestPending && +// selectedFrameIndex < storyViewModel.getCurrentStorySize()) { +// updateBackgroundSurfaceUIWithStoryFrame(selectedFrameIndex) +// } // upon loading an existing Story, show the generic announcement dialog if present if (showGenericAnnouncementDialogWhenReady) { showGenericAnnouncementDialogWhenReady = false @@ -1953,18 +1967,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec if (newSelectedFrame.frameItemType is VIDEO) { showPlayVideoWithSurfaceSafeguard(source) } else { - val model = (source as? FileBackgroundSource)?.file ?: (source as UriBackgroundSource).contentUri - Glide.with(this@ComposeLoopFrameActivity) - .load(model) - .transform(FitCenter()) - .listener(provideGlideRequestListenerWithHandler { - setBackgroundViewInfoOnPhotoView( - newSelectedFrame, - photoEditor.composedCanvas.source as PhotoView - ) - }) - .into(photoEditorView.source) - + loadImageWithGlide(newSelectedFrame) showStaticBackground() } @@ -2007,7 +2010,100 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec } } - private fun provideGlideRequestListenerWithHandler(setupPhotoViewMatrix: Runnable): RequestListener { + private fun loadImageWithGlide(frame: StoryFrameItem, useFitCenter: Boolean = false) { + val model = (frame.source as? FileBackgroundSource)?.file ?: + (frame.source as UriBackgroundSource).contentUri + // 0. attempt center-crop to pre-load the image and we can retrieve the intrinsic width/height. Also, before + // we know anything about the image Center-crop is our best bet, as it should be the widest use case. + // 1. if the image being loaded matches the aspect ratio of the device screen, then align to top + // (no parts would actually be cropped, given the matching the aspect ratio it should fit) + // 2. if the device is taller than 9:16, just crop the bottom (showing the opaque bar) + // 3. else, load with fit-center + + val transformToUse: BitmapTransformation = if (useFitCenter) { + FitCenter() + } else { + CenterCrop() + } + + // reset opaque bar to GONE + bottom_opaque_bar.visibility = View.GONE + // photoEditorView.source.layoutParams.height = 1920 + //(photoEditorView.source.layoutParams as LayoutParams).addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE) + // addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE) + // center in parent by default + (photoEditorView.source.layoutParams as LayoutParams).addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE) + Glide.with(this@ComposeLoopFrameActivity) + .load(model) + //.transform(FitCenter()) + // .transform(CenterCrop()) + .transform(transformToUse) + .listener(provideGlideRequestListenerWithHandler( + object : ResourceLoader { + override fun onResourceReady( + resource: Drawable?, + model: Any?, + target: Target?, + dataSource: DataSource?, + isFirstResource: Boolean + ): Boolean { + // early exit if fit-centering + if (useFitCenter) { + return false + } + + // 1. if the image being loaded matches the aspect ratio of the device screen, then align to top + // (no parts would actually be cropped, given the matching aspect ratio it should fit) + val drawableAspectRatio = calculateAspectRatioForDrawable(resource!!) + if (isAspectRatioSimilarByPercentage(drawableAspectRatio, screenSizeRatio, 0.001f)) { + (photoEditorView.source.layoutParams as LayoutParams) + .addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE) + bottom_opaque_bar.visibility = View.GONE + } else { + // 2. if the device is taller than 9:16, just crop the bottom (showing the opaque bar) + if (isScreenTallerThan916(screenWidth, screenHeight)) { + (photoEditorView.source.layoutParams as LayoutParams) + .addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE) + bottom_opaque_bar.visibility = View.VISIBLE + photoEditorView.source.layoutParams.height = normalizedSize.height + } else { + // 3. else, load with fit-center + Handler().post { + // we need to call Glide's .into() from the UI thread, so + // this recursive call needs to be made within a Handler() + loadImageWithGlide(frame, true) + } + + // return true tells Glide we're handling things to avoid the drawable being set + // at this time + return true + } + } + // else, return false to make Glide set the bitmap on the target + return false + } + }, object: Runnable { + override fun run() { + setBackgroundViewInfoOnPhotoView( + frame, + photoEditor.composedCanvas.source as PhotoView + ) + } + })) + .into(photoEditorView.source) + } + + interface ResourceLoader { + fun onResourceReady( + resource: Drawable?, + model: Any?, + target: Target?, + dataSource: DataSource?, + isFirstResource: Boolean + ): Boolean + } + + private fun provideGlideRequestListenerWithHandler(doBefore: ResourceLoader, setupPhotoViewMatrix: Runnable): RequestListener { return object : RequestListener { override fun onLoadFailed( e: GlideException?, @@ -2031,9 +2127,13 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec // has been set on the PhotoView, otherwise the matrix is not applied // see // https://github.com/Baseflow/PhotoView/blob/139a9ffeaf70bd628b015374cb6530fcf7d0bcb7/photoview/src/main/java/com/github/chrisbanes/photoview/PhotoViewAttacher.java#L279-L289 - Handler().post(setupPhotoViewMatrix) + val result = doBefore.onResourceReady(resource, model, target, dataSource, isFirstResource) + if (!result) { + // if we're all good, let's setup the ViewMatrix + Handler().post(setupPhotoViewMatrix) + } // return false to let Glide proceed and set the drawable - return false + return result } } } diff --git a/stories/src/main/java/com/wordpress/stories/util/ViewUtils.kt b/stories/src/main/java/com/wordpress/stories/util/ViewUtils.kt index fe45e3af2..b3359616c 100644 --- a/stories/src/main/java/com/wordpress/stories/util/ViewUtils.kt +++ b/stories/src/main/java/com/wordpress/stories/util/ViewUtils.kt @@ -1,5 +1,6 @@ package com.wordpress.stories.util +import android.graphics.drawable.Drawable import android.util.Size import android.view.View import android.view.ViewGroup @@ -69,3 +70,17 @@ fun normalizeSizeExportTo916(originalWidth: Int, originalHeight: Int): ScreenSiz } } } + +fun calculateAspectRatioForDrawable(drawable: Drawable): Float { + val width = drawable.intrinsicWidth + val height = drawable.intrinsicHeight + return width.toFloat() / height.toFloat() +} + +fun isAspectRatioSimilarByPercentage(aspectRatio1: Float, aspectRatio2: Float, percentage: Float): Boolean { + return (Math.abs(aspectRatio1 - aspectRatio2) < percentage) +} + +fun getSizeRatio(originalWidth: Int, originalHeight: Int): Float { + return (originalWidth.toFloat() / originalHeight.toFloat()) +} \ No newline at end of file From 2c071049537950bd6d8f180fea533a5a1a80e48b Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 8 Mar 2021 12:32:47 -0300 Subject: [PATCH 31/48] changed wording in comment --- .../com/wordpress/stories/compose/ComposeLoopFrameActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index 8cd19f5c8..d7d834c56 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -2014,7 +2014,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec val model = (frame.source as? FileBackgroundSource)?.file ?: (frame.source as UriBackgroundSource).contentUri // 0. attempt center-crop to pre-load the image and we can retrieve the intrinsic width/height. Also, before - // we know anything about the image Center-crop is our best bet, as it should be the widest use case. + // we know anything about the image Center-crop is our best bet, as it should be the most common use case. // 1. if the image being loaded matches the aspect ratio of the device screen, then align to top // (no parts would actually be cropped, given the matching the aspect ratio it should fit) // 2. if the device is taller than 9:16, just crop the bottom (showing the opaque bar) From ce82e4535befd837b7ca8b02763f587c46a3e9e0 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 8 Mar 2021 17:23:34 -0300 Subject: [PATCH 32/48] split method into two parts to avoid recursion, using coroutines to first prepare the bitmap then load it --- .../compose/ComposeLoopFrameActivity.kt | 173 +++++++++--------- .../com/wordpress/stories/util/ViewUtils.kt | 15 ++ 2 files changed, 104 insertions(+), 84 deletions(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index d7d834c56..b52d78447 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -32,6 +32,8 @@ import android.view.MotionEvent import android.view.View import android.view.View.OnClickListener import android.webkit.MimeTypeMap +import android.widget.ImageView.ScaleType.CENTER_CROP +import android.widget.ImageView.ScaleType.FIT_CENTER import android.widget.RelativeLayout import android.widget.RelativeLayout.LayoutParams import android.widget.Toast @@ -120,11 +122,13 @@ import com.wordpress.stories.util.KEY_STORY_SAVE_RESULT import com.wordpress.stories.util.STATE_KEY_CURRENT_STORY_INDEX import com.wordpress.stories.util.TARGET_RATIO_9_16 import com.wordpress.stories.util.calculateAspectRatioForDrawable +import com.wordpress.stories.util.calculateAspectRatioForBitmap import com.wordpress.stories.util.getDisplayPixelSize import com.wordpress.stories.util.getSizeRatio import com.wordpress.stories.util.getStoryIndexFromIntentOrBundle import com.wordpress.stories.util.isAspectRatioSimilarByPercentage import com.wordpress.stories.util.isScreenTallerThan916 +import com.wordpress.stories.util.isWidthMultiple import com.wordpress.stories.util.isVideo import com.wordpress.stories.util.normalizeSizeExportTo916 import kotlinx.android.synthetic.main.activity_composer.* @@ -132,6 +136,7 @@ import kotlinx.android.synthetic.main.content_composer.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode @@ -258,6 +263,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec private var screenWidth: Int = 1080 //default private var screenHeight: Int = 1920 //default private var screenSizeRatio: Float = TARGET_RATIO_9_16 + private var originalCanvasHeight = screenHeight private lateinit var normalizedSize: Size @@ -570,6 +576,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec private fun preCalculateOpaqueBarHeight(): Int { screenWidth = resources.displayMetrics.widthPixels screenHeight = resources.displayMetrics.heightPixels + originalCanvasHeight = photoEditorView.source.layoutParams.height if (isScreenTallerThan916(screenWidth, screenHeight)) { normalizedSize = normalizeSizeExportTo916(screenWidth, screenHeight).toSize() return (screenHeight - normalizedSize.height) @@ -1967,7 +1974,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec if (newSelectedFrame.frameItemType is VIDEO) { showPlayVideoWithSurfaceSafeguard(source) } else { - loadImageWithGlide(newSelectedFrame) + loadImageWithGlideToPrepare(newSelectedFrame) showStaticBackground() } @@ -2010,92 +2017,87 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec } } - private fun loadImageWithGlide(frame: StoryFrameItem, useFitCenter: Boolean = false) { + private fun loadImageWithGlideToPrepare(frame: StoryFrameItem) { val model = (frame.source as? FileBackgroundSource)?.file ?: - (frame.source as UriBackgroundSource).contentUri - // 0. attempt center-crop to pre-load the image and we can retrieve the intrinsic width/height. Also, before - // we know anything about the image Center-crop is our best bet, as it should be the most common use case. - // 1. if the image being loaded matches the aspect ratio of the device screen, then align to top - // (no parts would actually be cropped, given the matching the aspect ratio it should fit) - // 2. if the device is taller than 9:16, just crop the bottom (showing the opaque bar) - // 3. else, load with fit-center - - val transformToUse: BitmapTransformation = if (useFitCenter) { - FitCenter() - } else { - CenterCrop() - } - - // reset opaque bar to GONE - bottom_opaque_bar.visibility = View.GONE - // photoEditorView.source.layoutParams.height = 1920 - //(photoEditorView.source.layoutParams as LayoutParams).addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE) - // addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE) - // center in parent by default - (photoEditorView.source.layoutParams as LayoutParams).addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE) - Glide.with(this@ComposeLoopFrameActivity) - .load(model) - //.transform(FitCenter()) - // .transform(CenterCrop()) - .transform(transformToUse) - .listener(provideGlideRequestListenerWithHandler( - object : ResourceLoader { - override fun onResourceReady( - resource: Drawable?, - model: Any?, - target: Target?, - dataSource: DataSource?, - isFirstResource: Boolean - ): Boolean { - // early exit if fit-centering - if (useFitCenter) { - return false - } + (frame.source as UriBackgroundSource).contentUri + + CoroutineScope(Dispatchers.IO).launch { + val futureTarget = Glide.with(this@ComposeLoopFrameActivity) + .asBitmap() + .load(model) + //.submit(screenWidth, screenHeight) + .submit() + val bitmap = futureTarget.get() + + withContext(Dispatchers.Main) { + // targetView.setImageBitmap(bitmap) + // 1. if the image being loaded matches the aspect ratio of the device screen, then align to top + // (no parts would actually be cropped, given the matching aspect ratio it should fit) + val drawableAspectRatio = calculateAspectRatioForBitmap(bitmap) + if (isAspectRatioSimilarByPercentage(drawableAspectRatio, screenSizeRatio, 0.001f)) { + (photoEditorView.source.layoutParams as LayoutParams) + .addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE) + bottom_opaque_bar.visibility = View.GONE + // photoEditorView.source.layoutParams.height = originalCanvasHeight + photoEditorView.source.scaleType = CENTER_CROP + loadImageWithGlideToDraw(frame, false) + } else { + // 2. if the device is taller than 9:16, just crop the bottom (showing the opaque bar) + if (isScreenTallerThan916(screenWidth, screenHeight) + && (isWidthMultiple(screenWidth, bitmap.width) || + isWidthMultiple(bitmap.width, screenWidth)) + ) { + (photoEditorView.source.layoutParams as LayoutParams) + .addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE) + bottom_opaque_bar.visibility = View.VISIBLE + // photoEditorView.source.layoutParams.height = normalizedSize.height + photoEditorView.source.scaleType = CENTER_CROP + // photoEditorView.source.scaleType = FIT_CENTER + loadImageWithGlideToDraw(frame, false) + } else { + // 3. else, load with fit-center + (photoEditorView.source.layoutParams as LayoutParams) + .addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE) + bottom_opaque_bar.visibility = View.GONE + // photoEditorView.source.layoutParams.height = originalCanvasHeight + photoEditorView.source.scaleType = FIT_CENTER + // we need to call Glide's .into() from the UI thread, so + // this recursive call needs to be made within a Handler() + loadImageWithGlideToDraw(frame, true) + } + } + } + Glide.with(this@ComposeLoopFrameActivity).clear(futureTarget) + } + } - // 1. if the image being loaded matches the aspect ratio of the device screen, then align to top - // (no parts would actually be cropped, given the matching aspect ratio it should fit) - val drawableAspectRatio = calculateAspectRatioForDrawable(resource!!) - if (isAspectRatioSimilarByPercentage(drawableAspectRatio, screenSizeRatio, 0.001f)) { - (photoEditorView.source.layoutParams as LayoutParams) - .addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE) - bottom_opaque_bar.visibility = View.GONE - } else { - // 2. if the device is taller than 9:16, just crop the bottom (showing the opaque bar) - if (isScreenTallerThan916(screenWidth, screenHeight)) { - (photoEditorView.source.layoutParams as LayoutParams) - .addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE) - bottom_opaque_bar.visibility = View.VISIBLE - photoEditorView.source.layoutParams.height = normalizedSize.height - } else { - // 3. else, load with fit-center - Handler().post { - // we need to call Glide's .into() from the UI thread, so - // this recursive call needs to be made within a Handler() - loadImageWithGlide(frame, true) - } - - // return true tells Glide we're handling things to avoid the drawable being set - // at this time - return true - } - } - // else, return false to make Glide set the bitmap on the target - return false - } - }, object: Runnable { - override fun run() { + private suspend fun loadImageWithGlideToDraw(frame: StoryFrameItem, useFitCenter: Boolean = false) { + withContext(Dispatchers.Main) { + val model = (frame.source as? FileBackgroundSource)?.file ?: + (frame.source as UriBackgroundSource).contentUri + + val transformToUse: BitmapTransformation = if (useFitCenter) { + FitCenter() + } else { + CenterCrop() + } + + Glide.with(this@ComposeLoopFrameActivity) + .load(model) + .transform(transformToUse) + .listener(provideGlideRequestListenerWithHandler(setupPhotoViewMatrix = { setBackgroundViewInfoOnPhotoView( frame, photoEditor.composedCanvas.source as PhotoView ) - } - })) - .into(photoEditorView.source) + })) + .into(photoEditorView.source) + } } interface ResourceLoader { fun onResourceReady( - resource: Drawable?, + resource: Drawable, model: Any?, target: Target?, dataSource: DataSource?, @@ -2103,7 +2105,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec ): Boolean } - private fun provideGlideRequestListenerWithHandler(doBefore: ResourceLoader, setupPhotoViewMatrix: Runnable): RequestListener { + private fun provideGlideRequestListenerWithHandler(doBefore: ResourceLoader? = null, setupPhotoViewMatrix: Runnable): RequestListener { return object : RequestListener { override fun onLoadFailed( e: GlideException?, @@ -2127,13 +2129,16 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec // has been set on the PhotoView, otherwise the matrix is not applied // see // https://github.com/Baseflow/PhotoView/blob/139a9ffeaf70bd628b015374cb6530fcf7d0bcb7/photoview/src/main/java/com/github/chrisbanes/photoview/PhotoViewAttacher.java#L279-L289 - val result = doBefore.onResourceReady(resource, model, target, dataSource, isFirstResource) - if (!result) { - // if we're all good, let's setup the ViewMatrix - Handler().post(setupPhotoViewMatrix) - } + doBefore?.let { + val result = doBefore.onResourceReady(requireNotNull(resource), model, target, dataSource, isFirstResource) + if (!result) { + // if we're all good, let's setup the ViewMatrix + Handler().post(setupPhotoViewMatrix) + } + return result + } ?: Handler().post(setupPhotoViewMatrix) // return false to let Glide proceed and set the drawable - return result + return false } } } diff --git a/stories/src/main/java/com/wordpress/stories/util/ViewUtils.kt b/stories/src/main/java/com/wordpress/stories/util/ViewUtils.kt index b3359616c..47736a72a 100644 --- a/stories/src/main/java/com/wordpress/stories/util/ViewUtils.kt +++ b/stories/src/main/java/com/wordpress/stories/util/ViewUtils.kt @@ -1,5 +1,6 @@ package com.wordpress.stories.util +import android.graphics.Bitmap import android.graphics.drawable.Drawable import android.util.Size import android.view.View @@ -77,10 +78,24 @@ fun calculateAspectRatioForDrawable(drawable: Drawable): Float { return width.toFloat() / height.toFloat() } +fun calculateAspectRatioForBitmap(bitmap: Bitmap): Float { + val width = bitmap.width + val height = bitmap.height + return width.toFloat() / height.toFloat() +} + fun isAspectRatioSimilarByPercentage(aspectRatio1: Float, aspectRatio2: Float, percentage: Float): Boolean { return (Math.abs(aspectRatio1 - aspectRatio2) < percentage) } +fun isWidthMultiple(width1: Int, width2: Int): Boolean { + return isMultipleOf(width1, width2) || isMultipleOf(width2, width1) +} + +private fun isMultipleOf(num1: Int, num2: Int): Boolean { + return (num1 % num2 == 0) +} + fun getSizeRatio(originalWidth: Int, originalHeight: Int): Float { return (originalWidth.toFloat() / originalHeight.toFloat()) } \ No newline at end of file From 02f98909d60cbeedc92606c3e5762377c9908aa7 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 8 Mar 2021 17:30:28 -0300 Subject: [PATCH 33/48] passing bitmap on glide second pass so it doesn't need be fetched and processed again --- .../stories/compose/ComposeLoopFrameActivity.kt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index b52d78447..d69375ee1 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -9,6 +9,7 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.ServiceConnection +import android.graphics.Bitmap import android.graphics.drawable.Drawable import android.graphics.Rect import android.graphics.drawable.ColorDrawable @@ -2040,7 +2041,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec bottom_opaque_bar.visibility = View.GONE // photoEditorView.source.layoutParams.height = originalCanvasHeight photoEditorView.source.scaleType = CENTER_CROP - loadImageWithGlideToDraw(frame, false) + loadImageWithGlideToDraw(bitmap, frame, false) } else { // 2. if the device is taller than 9:16, just crop the bottom (showing the opaque bar) if (isScreenTallerThan916(screenWidth, screenHeight) @@ -2053,7 +2054,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec // photoEditorView.source.layoutParams.height = normalizedSize.height photoEditorView.source.scaleType = CENTER_CROP // photoEditorView.source.scaleType = FIT_CENTER - loadImageWithGlideToDraw(frame, false) + loadImageWithGlideToDraw(bitmap, frame,false) } else { // 3. else, load with fit-center (photoEditorView.source.layoutParams as LayoutParams) @@ -2063,7 +2064,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec photoEditorView.source.scaleType = FIT_CENTER // we need to call Glide's .into() from the UI thread, so // this recursive call needs to be made within a Handler() - loadImageWithGlideToDraw(frame, true) + loadImageWithGlideToDraw(bitmap, frame,true) } } } @@ -2071,11 +2072,8 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec } } - private suspend fun loadImageWithGlideToDraw(frame: StoryFrameItem, useFitCenter: Boolean = false) { + private suspend fun loadImageWithGlideToDraw(bitmap: Bitmap, frame: StoryFrameItem, useFitCenter: Boolean = false) { withContext(Dispatchers.Main) { - val model = (frame.source as? FileBackgroundSource)?.file ?: - (frame.source as UriBackgroundSource).contentUri - val transformToUse: BitmapTransformation = if (useFitCenter) { FitCenter() } else { @@ -2083,7 +2081,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec } Glide.with(this@ComposeLoopFrameActivity) - .load(model) + .load(bitmap) .transform(transformToUse) .listener(provideGlideRequestListenerWithHandler(setupPhotoViewMatrix = { setBackgroundViewInfoOnPhotoView( From 3da98442c0d01da0b0ead3862640fcb301d4f644 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 9 Mar 2021 14:33:11 -0300 Subject: [PATCH 34/48] using Drawable instead of bitmap, will be easier to use in the future when using animated sources --- .../compose/ComposeLoopFrameActivity.kt | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index d69375ee1..9f1c1e369 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -2024,29 +2024,28 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec CoroutineScope(Dispatchers.IO).launch { val futureTarget = Glide.with(this@ComposeLoopFrameActivity) - .asBitmap() + .asDrawable() .load(model) - //.submit(screenWidth, screenHeight) .submit() - val bitmap = futureTarget.get() + val drawable = futureTarget.get() withContext(Dispatchers.Main) { - // targetView.setImageBitmap(bitmap) // 1. if the image being loaded matches the aspect ratio of the device screen, then align to top // (no parts would actually be cropped, given the matching aspect ratio it should fit) - val drawableAspectRatio = calculateAspectRatioForBitmap(bitmap) + // val drawableAspectRatio = calculateAspectRatioForBitmap(bitmap) + val drawableAspectRatio = calculateAspectRatioForDrawable(drawable) if (isAspectRatioSimilarByPercentage(drawableAspectRatio, screenSizeRatio, 0.001f)) { (photoEditorView.source.layoutParams as LayoutParams) .addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE) bottom_opaque_bar.visibility = View.GONE // photoEditorView.source.layoutParams.height = originalCanvasHeight photoEditorView.source.scaleType = CENTER_CROP - loadImageWithGlideToDraw(bitmap, frame, false) + loadImageWithGlideToDraw(drawable, frame, false) } else { // 2. if the device is taller than 9:16, just crop the bottom (showing the opaque bar) if (isScreenTallerThan916(screenWidth, screenHeight) - && (isWidthMultiple(screenWidth, bitmap.width) || - isWidthMultiple(bitmap.width, screenWidth)) + && (isWidthMultiple(screenWidth, drawable.intrinsicWidth) || + isWidthMultiple(drawable.intrinsicWidth, screenWidth)) ) { (photoEditorView.source.layoutParams as LayoutParams) .addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE) @@ -2054,7 +2053,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec // photoEditorView.source.layoutParams.height = normalizedSize.height photoEditorView.source.scaleType = CENTER_CROP // photoEditorView.source.scaleType = FIT_CENTER - loadImageWithGlideToDraw(bitmap, frame,false) + loadImageWithGlideToDraw(drawable, frame, false) } else { // 3. else, load with fit-center (photoEditorView.source.layoutParams as LayoutParams) @@ -2062,9 +2061,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec bottom_opaque_bar.visibility = View.GONE // photoEditorView.source.layoutParams.height = originalCanvasHeight photoEditorView.source.scaleType = FIT_CENTER - // we need to call Glide's .into() from the UI thread, so - // this recursive call needs to be made within a Handler() - loadImageWithGlideToDraw(bitmap, frame,true) + loadImageWithGlideToDraw(drawable, frame, true) } } } @@ -2072,7 +2069,11 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec } } - private suspend fun loadImageWithGlideToDraw(bitmap: Bitmap, frame: StoryFrameItem, useFitCenter: Boolean = false) { + private suspend fun loadImageWithGlideToDraw( + drawable: Drawable, + frame: StoryFrameItem, + useFitCenter: Boolean = false + ) { withContext(Dispatchers.Main) { val transformToUse: BitmapTransformation = if (useFitCenter) { FitCenter() @@ -2081,7 +2082,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec } Glide.with(this@ComposeLoopFrameActivity) - .load(bitmap) + .load(drawable) .transform(transformToUse) .listener(provideGlideRequestListenerWithHandler(setupPhotoViewMatrix = { setBackgroundViewInfoOnPhotoView( From 3ec01b382b3ffa122590d2c00d701dc5659ee937 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 9 Mar 2021 15:22:14 -0300 Subject: [PATCH 35/48] removed ResourceLoader interface, replaced with simpler ImageLoadedInterface --- .../compose/ComposeLoopFrameActivity.kt | 65 +++++++++---------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index 9f1c1e369..c320ceaf8 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -2029,6 +2029,26 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec .submit() val drawable = futureTarget.get() + val doAfterUse = object : ImageLoadedInterface { + override fun doAfter() { + // here setup the PhotoView support matrix + // we use a Handler because we need to set the support matrix only once the drawable + // has been set on the PhotoView, otherwise the matrix is not applied + // see + // https://github.com/Baseflow/PhotoView/blob/139a9ffeaf70bd628b015374cb6530fcf7d0bcb7/photoview/src/main/java/com/github/chrisbanes/photoview/PhotoViewAttacher.java#L279-L289 + // if we're all good, doAfter() will be callled on Glide's `onResourceReady`, so + // let's setup the ViewMatrix + Handler().post{ + setBackgroundViewInfoOnPhotoView( + frame, + photoEditor.composedCanvas.source as PhotoView + ) + // finally, clean target so resources can be freed up + Glide.with(this@ComposeLoopFrameActivity).clear(futureTarget) + } + } + } + withContext(Dispatchers.Main) { // 1. if the image being loaded matches the aspect ratio of the device screen, then align to top // (no parts would actually be cropped, given the matching aspect ratio it should fit) @@ -2040,7 +2060,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec bottom_opaque_bar.visibility = View.GONE // photoEditorView.source.layoutParams.height = originalCanvasHeight photoEditorView.source.scaleType = CENTER_CROP - loadImageWithGlideToDraw(drawable, frame, false) + loadImageWithGlideToDraw(drawable,false, doAfterUse) } else { // 2. if the device is taller than 9:16, just crop the bottom (showing the opaque bar) if (isScreenTallerThan916(screenWidth, screenHeight) @@ -2053,7 +2073,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec // photoEditorView.source.layoutParams.height = normalizedSize.height photoEditorView.source.scaleType = CENTER_CROP // photoEditorView.source.scaleType = FIT_CENTER - loadImageWithGlideToDraw(drawable, frame, false) + loadImageWithGlideToDraw(drawable, false, doAfterUse) } else { // 3. else, load with fit-center (photoEditorView.source.layoutParams as LayoutParams) @@ -2061,18 +2081,17 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec bottom_opaque_bar.visibility = View.GONE // photoEditorView.source.layoutParams.height = originalCanvasHeight photoEditorView.source.scaleType = FIT_CENTER - loadImageWithGlideToDraw(drawable, frame, true) + loadImageWithGlideToDraw(drawable, true, doAfterUse) } } } - Glide.with(this@ComposeLoopFrameActivity).clear(futureTarget) } } private suspend fun loadImageWithGlideToDraw( drawable: Drawable, - frame: StoryFrameItem, - useFitCenter: Boolean = false + useFitCenter: Boolean = false, + doAfterUse: ImageLoadedInterface ) { withContext(Dispatchers.Main) { val transformToUse: BitmapTransformation = if (useFitCenter) { @@ -2084,27 +2103,17 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec Glide.with(this@ComposeLoopFrameActivity) .load(drawable) .transform(transformToUse) - .listener(provideGlideRequestListenerWithHandler(setupPhotoViewMatrix = { - setBackgroundViewInfoOnPhotoView( - frame, - photoEditor.composedCanvas.source as PhotoView - ) - })) + .listener(provideGlideRequestListener(doAfterUse)) .into(photoEditorView.source) + } } - interface ResourceLoader { - fun onResourceReady( - resource: Drawable, - model: Any?, - target: Target?, - dataSource: DataSource?, - isFirstResource: Boolean - ): Boolean + interface ImageLoadedInterface { + fun doAfter() } - private fun provideGlideRequestListenerWithHandler(doBefore: ResourceLoader? = null, setupPhotoViewMatrix: Runnable): RequestListener { + private fun provideGlideRequestListener(callback: ImageLoadedInterface): RequestListener { return object : RequestListener { override fun onLoadFailed( e: GlideException?, @@ -2123,19 +2132,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec dataSource: DataSource?, isFirstResource: Boolean ): Boolean { - // here setup the PhotoView support matrix - // we use a handler because we need to set the support matrix only once the drawable - // has been set on the PhotoView, otherwise the matrix is not applied - // see - // https://github.com/Baseflow/PhotoView/blob/139a9ffeaf70bd628b015374cb6530fcf7d0bcb7/photoview/src/main/java/com/github/chrisbanes/photoview/PhotoViewAttacher.java#L279-L289 - doBefore?.let { - val result = doBefore.onResourceReady(requireNotNull(resource), model, target, dataSource, isFirstResource) - if (!result) { - // if we're all good, let's setup the ViewMatrix - Handler().post(setupPhotoViewMatrix) - } - return result - } ?: Handler().post(setupPhotoViewMatrix) + callback.doAfter() // return false to let Glide proceed and set the drawable return false } From a654693b717e55989ef5f4518995ad6ffaff1f96 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 9 Mar 2021 21:48:33 -0300 Subject: [PATCH 36/48] several improvements: only loading futurTarget to as max as the screen size since that's the capped output, and using Glide's override() to load for the calculated size. Also using FIT_START for taller than 9:16 devices which makes it occupy the top part of the screen and only crop the bottom --- .../compose/ComposeLoopFrameActivity.kt | 56 +++++++++---------- 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index c320ceaf8..6a2c1eab5 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -35,6 +35,7 @@ import android.view.View.OnClickListener import android.webkit.MimeTypeMap import android.widget.ImageView.ScaleType.CENTER_CROP import android.widget.ImageView.ScaleType.FIT_CENTER +import android.widget.ImageView.ScaleType.FIT_START import android.widget.RelativeLayout import android.widget.RelativeLayout.LayoutParams import android.widget.Toast @@ -2026,7 +2027,8 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec val futureTarget = Glide.with(this@ComposeLoopFrameActivity) .asDrawable() .load(model) - .submit() + .fitCenter() // we use fitCenter at first (instead of cropping) so we don't lose any information + .submit(screenWidth, screenHeight) // we're not going to export images greater than the screen size val drawable = futureTarget.get() val doAfterUse = object : ImageLoadedInterface { @@ -2052,36 +2054,25 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec withContext(Dispatchers.Main) { // 1. if the image being loaded matches the aspect ratio of the device screen, then align to top // (no parts would actually be cropped, given the matching aspect ratio it should fit) - // val drawableAspectRatio = calculateAspectRatioForBitmap(bitmap) val drawableAspectRatio = calculateAspectRatioForDrawable(drawable) - if (isAspectRatioSimilarByPercentage(drawableAspectRatio, screenSizeRatio, 0.001f)) { - (photoEditorView.source.layoutParams as LayoutParams) - .addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE) + if (isAspectRatioSimilarByPercentage(drawableAspectRatio, screenSizeRatio, 0.01f)) { bottom_opaque_bar.visibility = View.GONE - // photoEditorView.source.layoutParams.height = originalCanvasHeight photoEditorView.source.scaleType = CENTER_CROP - loadImageWithGlideToDraw(drawable,false, doAfterUse) + loadImageWithGlideToDraw(drawable, CenterCrop(), screenWidth, originalCanvasHeight, doAfterUse) } else { - // 2. if the device is taller than 9:16, just crop the bottom (showing the opaque bar) - if (isScreenTallerThan916(screenWidth, screenHeight) - && (isWidthMultiple(screenWidth, drawable.intrinsicWidth) || - isWidthMultiple(drawable.intrinsicWidth, screenWidth)) + // 2. if the device is taller than 9:16, and image is portrait + // just crop the bottom (showing the opaque bar) + if (isScreenTallerThan916(screenWidth, screenHeight) && + (drawable.intrinsicHeight > drawable.intrinsicWidth) ) { - (photoEditorView.source.layoutParams as LayoutParams) - .addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE) bottom_opaque_bar.visibility = View.VISIBLE - // photoEditorView.source.layoutParams.height = normalizedSize.height - photoEditorView.source.scaleType = CENTER_CROP - // photoEditorView.source.scaleType = FIT_CENTER - loadImageWithGlideToDraw(drawable, false, doAfterUse) + photoEditorView.source.scaleType = FIT_START + loadImageWithGlideToDraw(drawable, null, normalizedSize.width, normalizedSize.height, doAfterUse) } else { // 3. else, load with fit-center - (photoEditorView.source.layoutParams as LayoutParams) - .addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE) - bottom_opaque_bar.visibility = View.GONE - // photoEditorView.source.layoutParams.height = originalCanvasHeight + bottom_opaque_bar.visibility = View.VISIBLE photoEditorView.source.scaleType = FIT_CENTER - loadImageWithGlideToDraw(drawable, true, doAfterUse) + loadImageWithGlideToDraw(drawable, FitCenter(), screenWidth, originalCanvasHeight, doAfterUse) } } } @@ -2090,22 +2081,25 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec private suspend fun loadImageWithGlideToDraw( drawable: Drawable, - useFitCenter: Boolean = false, + transformToUse: BitmapTransformation? = null, + overrideWidth: Int, + overrideHeight: Int, doAfterUse: ImageLoadedInterface ) { withContext(Dispatchers.Main) { - val transformToUse: BitmapTransformation = if (useFitCenter) { - FitCenter() - } else { - CenterCrop() - } + transformToUse?.let { + Glide.with(this@ComposeLoopFrameActivity) + .load(drawable) + .transform(it) + .listener(provideGlideRequestListener(doAfterUse)) + .override(overrideWidth, overrideHeight) + .into(photoEditorView.source) - Glide.with(this@ComposeLoopFrameActivity) + } ?: Glide.with(this@ComposeLoopFrameActivity) .load(drawable) - .transform(transformToUse) .listener(provideGlideRequestListener(doAfterUse)) + .override(overrideWidth, overrideHeight) .into(photoEditorView.source) - } } From 92ccda0adc8efc8c246810577dc31938e22ef4d4 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 9 Mar 2021 21:49:21 -0300 Subject: [PATCH 37/48] removed unused functions --- .../java/com/wordpress/stories/util/ViewUtils.kt | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/stories/src/main/java/com/wordpress/stories/util/ViewUtils.kt b/stories/src/main/java/com/wordpress/stories/util/ViewUtils.kt index 47736a72a..2f3b3c8f3 100644 --- a/stories/src/main/java/com/wordpress/stories/util/ViewUtils.kt +++ b/stories/src/main/java/com/wordpress/stories/util/ViewUtils.kt @@ -1,6 +1,5 @@ package com.wordpress.stories.util -import android.graphics.Bitmap import android.graphics.drawable.Drawable import android.util.Size import android.view.View @@ -78,24 +77,9 @@ fun calculateAspectRatioForDrawable(drawable: Drawable): Float { return width.toFloat() / height.toFloat() } -fun calculateAspectRatioForBitmap(bitmap: Bitmap): Float { - val width = bitmap.width - val height = bitmap.height - return width.toFloat() / height.toFloat() -} - fun isAspectRatioSimilarByPercentage(aspectRatio1: Float, aspectRatio2: Float, percentage: Float): Boolean { return (Math.abs(aspectRatio1 - aspectRatio2) < percentage) } - -fun isWidthMultiple(width1: Int, width2: Int): Boolean { - return isMultipleOf(width1, width2) || isMultipleOf(width2, width1) -} - -private fun isMultipleOf(num1: Int, num2: Int): Boolean { - return (num1 % num2 == 0) -} - fun getSizeRatio(originalWidth: Int, originalHeight: Int): Float { return (originalWidth.toFloat() / originalHeight.toFloat()) } \ No newline at end of file From 089917adab05bb2b585da922885a781b4a6b1f10 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 9 Mar 2021 21:52:59 -0300 Subject: [PATCH 38/48] removed commented code --- .../wordpress/stories/compose/ComposeLoopFrameActivity.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index 6a2c1eab5..cef566de6 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -597,11 +597,6 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec override fun onStart() { super.onStart() -// val selectedFrameIndex = storyViewModel.getSelectedFrameIndex() -// if (!launchCameraRequestPending && !launchVideoPlayerRequestPending && -// selectedFrameIndex < storyViewModel.getCurrentStorySize()) { -// updateBackgroundSurfaceUIWithStoryFrame(selectedFrameIndex) -// } // upon loading an existing Story, show the generic announcement dialog if present if (showGenericAnnouncementDialogWhenReady) { showGenericAnnouncementDialogWhenReady = false From 6b440d51756832ae541146a86b301189ce969258 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 10 Mar 2021 11:01:03 -0300 Subject: [PATCH 39/48] passing scaleType in BackgroundViewInfo, also now calculating screenHeight from photoEditorView.source.measuredHeight to get the actual real screen height --- .../compose/ComposeLoopFrameActivity.kt | 27 +++++++++---------- .../stories/compose/story/StoryFrameItem.kt | 4 ++- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index cef566de6..aa301a49e 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -9,7 +9,6 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.ServiceConnection -import android.graphics.Bitmap import android.graphics.drawable.Drawable import android.graphics.Rect import android.graphics.drawable.ColorDrawable @@ -36,8 +35,6 @@ import android.webkit.MimeTypeMap import android.widget.ImageView.ScaleType.CENTER_CROP import android.widget.ImageView.ScaleType.FIT_CENTER import android.widget.ImageView.ScaleType.FIT_START -import android.widget.RelativeLayout -import android.widget.RelativeLayout.LayoutParams import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.constraintlayout.widget.Group @@ -124,13 +121,11 @@ import com.wordpress.stories.util.KEY_STORY_SAVE_RESULT import com.wordpress.stories.util.STATE_KEY_CURRENT_STORY_INDEX import com.wordpress.stories.util.TARGET_RATIO_9_16 import com.wordpress.stories.util.calculateAspectRatioForDrawable -import com.wordpress.stories.util.calculateAspectRatioForBitmap import com.wordpress.stories.util.getDisplayPixelSize import com.wordpress.stories.util.getSizeRatio import com.wordpress.stories.util.getStoryIndexFromIntentOrBundle import com.wordpress.stories.util.isAspectRatioSimilarByPercentage import com.wordpress.stories.util.isScreenTallerThan916 -import com.wordpress.stories.util.isWidthMultiple import com.wordpress.stories.util.isVideo import com.wordpress.stories.util.normalizeSizeExportTo916 import kotlinx.android.synthetic.main.activity_composer.* @@ -262,10 +257,9 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec private var genericAnnouncementDialogProvider: GenericAnnouncementDialogProvider? = null private var showGenericAnnouncementDialogWhenReady = false private var useTempCaptureFile = true - private var screenWidth: Int = 1080 //default - private var screenHeight: Int = 1920 //default + private var screenWidth: Int = 1080 // default + private var screenHeight: Int = 1920 // default private var screenSizeRatio: Float = TARGET_RATIO_9_16 - private var originalCanvasHeight = screenHeight private lateinit var normalizedSize: Size @@ -576,9 +570,11 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec } private fun preCalculateOpaqueBarHeight(): Int { - screenWidth = resources.displayMetrics.widthPixels - screenHeight = resources.displayMetrics.heightPixels - originalCanvasHeight = photoEditorView.source.layoutParams.height + // We used to obtain the screen dimensions by querying resources.displayMetrics but this value is the + // actual screen size minus the navigation bar height i.e. on a Pixel device we'd get 1794 instead of 1920. + // Given ComposeLoopFrameActivity is full screen, we can rely on the measuredHeight calculation instead. + screenWidth = photoEditorView.source.measuredWidth + screenHeight = photoEditorView.source.measuredHeight if (isScreenTallerThan916(screenWidth, screenHeight)) { normalizedSize = normalizeSizeExportTo916(screenWidth, screenHeight).toSize() return (screenHeight - normalizedSize.height) @@ -1998,7 +1994,8 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec // extract matrix to float array matrixValues matrix.getValues(matrixValues) frame.source.backgroundViewInfo = BackgroundViewInfo( - imageMatrixValues = matrixValues + imageMatrixValues = matrixValues, + scaleType = backgroundImageSource.scaleType ) } @@ -2010,6 +2007,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec matrix.setValues(it.imageMatrixValues) backgroundImageSource.apply { setSuppMatrix(matrix) + scaleType = it.scaleType } } } @@ -2053,7 +2051,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec if (isAspectRatioSimilarByPercentage(drawableAspectRatio, screenSizeRatio, 0.01f)) { bottom_opaque_bar.visibility = View.GONE photoEditorView.source.scaleType = CENTER_CROP - loadImageWithGlideToDraw(drawable, CenterCrop(), screenWidth, originalCanvasHeight, doAfterUse) + loadImageWithGlideToDraw(drawable, CenterCrop(), screenWidth, screenHeight, doAfterUse) } else { // 2. if the device is taller than 9:16, and image is portrait // just crop the bottom (showing the opaque bar) @@ -2067,7 +2065,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec // 3. else, load with fit-center bottom_opaque_bar.visibility = View.VISIBLE photoEditorView.source.scaleType = FIT_CENTER - loadImageWithGlideToDraw(drawable, FitCenter(), screenWidth, originalCanvasHeight, doAfterUse) + loadImageWithGlideToDraw(drawable, FitCenter(), screenWidth, screenHeight, doAfterUse) } } } @@ -2089,7 +2087,6 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec .listener(provideGlideRequestListener(doAfterUse)) .override(overrideWidth, overrideHeight) .into(photoEditorView.source) - } ?: Glide.with(this@ComposeLoopFrameActivity) .load(drawable) .listener(provideGlideRequestListener(doAfterUse)) diff --git a/stories/src/main/java/com/wordpress/stories/compose/story/StoryFrameItem.kt b/stories/src/main/java/com/wordpress/stories/compose/story/StoryFrameItem.kt index 5f5c1dca3..a9e8a33f6 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/story/StoryFrameItem.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/story/StoryFrameItem.kt @@ -1,6 +1,7 @@ package com.wordpress.stories.compose.story import android.net.Uri +import android.widget.ImageView.ScaleType import com.automattic.photoeditor.views.added.AddedView import com.automattic.photoeditor.views.added.AddedViewList import com.wordpress.stories.compose.frame.StorySaveEvents.SaveResultReason @@ -29,7 +30,8 @@ data class StoryFrameItem( ) { @Serializable data class BackgroundViewInfo( - val imageMatrixValues: FloatArray + val imageMatrixValues: FloatArray, + val scaleType: ScaleType ) @Serializable From d2aa125add64012b37387bcb3976fcce0e670377 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 10 Mar 2021 12:03:36 -0300 Subject: [PATCH 40/48] added more comments --- .../stories/compose/ComposeLoopFrameActivity.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index aa301a49e..e2cf81b81 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -2045,8 +2045,9 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec } withContext(Dispatchers.Main) { - // 1. if the image being loaded matches the aspect ratio of the device screen, then align to top - // (no parts would actually be cropped, given the matching aspect ratio it should fit) + // 1. if the image being loaded matches the aspect ratio of the device screen, use center_crop + // no parts would actually be cropped, given the matching aspect ratio it should fit + // except the opaque bar given we're normalizing to 9:16 on export val drawableAspectRatio = calculateAspectRatioForDrawable(drawable) if (isAspectRatioSimilarByPercentage(drawableAspectRatio, screenSizeRatio, 0.01f)) { bottom_opaque_bar.visibility = View.GONE @@ -2059,10 +2060,11 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec (drawable.intrinsicHeight > drawable.intrinsicWidth) ) { bottom_opaque_bar.visibility = View.VISIBLE - photoEditorView.source.scaleType = FIT_START + photoEditorView.source.scaleType = FIT_START // this aligns to top so there's no top black bar loadImageWithGlideToDraw(drawable, null, normalizedSize.width, normalizedSize.height, doAfterUse) } else { - // 3. else, load with fit-center + // 3. else, load with fit-center (black bars on the side that doesn't fit) + // see https://developer.android.com/reference/android/graphics/Matrix.ScaleToFit#CENTER bottom_opaque_bar.visibility = View.VISIBLE photoEditorView.source.scaleType = FIT_CENTER loadImageWithGlideToDraw(drawable, FitCenter(), screenWidth, screenHeight, doAfterUse) From dab1ca8005dc464ac1281957d8c4299584ecba9e Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 10 Mar 2021 12:04:50 -0300 Subject: [PATCH 41/48] setting scaleType and transform on ghost view when exporting, according to passed values on frame.source.backgroundViewInfo --- .../stories/compose/frame/FrameSaveManager.kt | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt b/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt index 5993deca5..ecfac5b4e 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt @@ -7,7 +7,9 @@ import android.graphics.Matrix import android.net.Uri import android.view.View import android.view.ViewGroup.LayoutParams +import android.widget.ImageView.ScaleType.CENTER_CROP import android.widget.ImageView.ScaleType.FIT_CENTER +import android.widget.ImageView.ScaleType.FIT_START import android.widget.RelativeLayout import com.automattic.photoeditor.PhotoEditor import com.automattic.photoeditor.PhotoEditor.OnSaveWithCancelAndProgressListener @@ -292,20 +294,46 @@ class FrameSaveManager( ?: (frame.source as FileBackgroundSource).file val targetView = ghostPhotoEditorView.source + val scaleType = frame.source.backgroundViewInfo?.scaleType // making use of Glide to decode bitmap and get the right orientation automatically // http://bumptech.github.io/glide/doc/getting-started.html#background-threads - val futureTarget = Glide.with(context) - .asBitmap() - .load(uri) - .submit(targetView.measuredWidth, targetView.measuredHeight) + val futureTarget = when (scaleType) { + FIT_START -> + Glide.with(context) + .asBitmap() + .load(uri) + // no transform used when FIT_START, see correlation in ComposeLoopFrameActivity's + // loadImageWithGlideToPrepare() + .submit(targetView.measuredWidth, targetView.measuredHeight) + FIT_CENTER -> + Glide.with(context) + .asBitmap() + .load(uri) + .fitCenter() // we use fitCenter at first (instead of cropping) so we don't lose any information + .submit(targetView.measuredWidth, targetView.measuredHeight) + CENTER_CROP -> + Glide.with(context) + .asBitmap() + .load(uri) + .centerCrop() // we use fitCenter at first (instead of cropping) so we don't lose any information + .submit(targetView.measuredWidth, targetView.measuredHeight) + else -> // default case with no transform needed so futureTarget is initialized, + // but we don't really expect to get this case + Glide.with(context) + .asBitmap() + .load(uri) + .submit(targetView.measuredWidth, targetView.measuredHeight) + } val bitmap = futureTarget.get() targetView.setImageBitmap(bitmap) // IMPORTANT: scaleType and setSuppMatrix should only be called _after_ the bitmap is set on the targetView // by means of targetView.setImageBitmap(). Calling this before will have no effect due to PhotoView's checks. (targetView as BackgroundImageView).apply { - scaleType = FIT_CENTER + frame.source.backgroundViewInfo?.let { + this.scaleType = it.scaleType + } setSuppMatrix(originalMatrix) } From b9cbe5beec138d44d2d78324caf2e7b199b1067c Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 10 Mar 2021 12:20:46 -0300 Subject: [PATCH 42/48] no need to clear the futureTarget after we're done given it's used and discarded by Glide itself once we use it on an actual drawing operation --- .../com/wordpress/stories/compose/ComposeLoopFrameActivity.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index e2cf81b81..4b85b13be 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -2038,8 +2038,6 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec frame, photoEditor.composedCanvas.source as PhotoView ) - // finally, clean target so resources can be freed up - Glide.with(this@ComposeLoopFrameActivity).clear(futureTarget) } } } From e1b71794392ac41eae025591492edf50b43df196 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 10 Mar 2021 15:25:20 -0300 Subject: [PATCH 43/48] using FIT_START for drawables wider than screen, and FIT_CENTER for drawables narrower than the device screen so it always looks centered --- .../stories/compose/ComposeLoopFrameActivity.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index 4b85b13be..821a37e4c 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -2058,8 +2058,14 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec (drawable.intrinsicHeight > drawable.intrinsicWidth) ) { bottom_opaque_bar.visibility = View.VISIBLE - photoEditorView.source.scaleType = FIT_START // this aligns to top so there's no top black bar - loadImageWithGlideToDraw(drawable, null, normalizedSize.width, normalizedSize.height, doAfterUse) + val transformToUse = if (drawable.intrinsicWidth >= screenWidth) { + photoEditorView.source.scaleType = FIT_START // this aligns to top so there's no top black bar + null + } else { + photoEditorView.source.scaleType = FIT_CENTER + FitCenter() + } + loadImageWithGlideToDraw(drawable, transformToUse, normalizedSize.width, normalizedSize.height, doAfterUse) } else { // 3. else, load with fit-center (black bars on the side that doesn't fit) // see https://developer.android.com/reference/android/graphics/Matrix.ScaleToFit#CENTER From f64aaf6c44f052ccf1c03b1e6975859760133fd2 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 10 Mar 2021 15:27:53 -0300 Subject: [PATCH 44/48] moved normalizedSize calculation up so it's always available --- .../com/wordpress/stories/compose/ComposeLoopFrameActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index 821a37e4c..072038891 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -575,8 +575,8 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec // Given ComposeLoopFrameActivity is full screen, we can rely on the measuredHeight calculation instead. screenWidth = photoEditorView.source.measuredWidth screenHeight = photoEditorView.source.measuredHeight + normalizedSize = normalizeSizeExportTo916(screenWidth, screenHeight).toSize() if (isScreenTallerThan916(screenWidth, screenHeight)) { - normalizedSize = normalizeSizeExportTo916(screenWidth, screenHeight).toSize() return (screenHeight - normalizedSize.height) } else { return 0 From c4347301e78a6fed1b14f9bd8bc8b773ec59f368 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 10 Mar 2021 16:34:36 -0300 Subject: [PATCH 45/48] fixed lint warnings --- .../stories/compose/ComposeLoopFrameActivity.kt | 15 ++++++++------- .../stories/compose/frame/FrameSaveManager.kt | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index 072038891..d47aec644 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -262,7 +262,6 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec private var screenSizeRatio: Float = TARGET_RATIO_9_16 private lateinit var normalizedSize: Size - private val connection = object : ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { Log.d("ComposeLoopFrame", "onServiceConnected()") @@ -2013,14 +2012,14 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec } private fun loadImageWithGlideToPrepare(frame: StoryFrameItem) { - val model = (frame.source as? FileBackgroundSource)?.file ?: - (frame.source as UriBackgroundSource).contentUri + val model = (frame.source as? FileBackgroundSource)?.file + ?: (frame.source as UriBackgroundSource).contentUri CoroutineScope(Dispatchers.IO).launch { val futureTarget = Glide.with(this@ComposeLoopFrameActivity) .asDrawable() .load(model) - .fitCenter() // we use fitCenter at first (instead of cropping) so we don't lose any information + .fitCenter() // we use fitCenter at first (instead of cropping) so we don't lose any information .submit(screenWidth, screenHeight) // we're not going to export images greater than the screen size val drawable = futureTarget.get() @@ -2033,7 +2032,7 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec // https://github.com/Baseflow/PhotoView/blob/139a9ffeaf70bd628b015374cb6530fcf7d0bcb7/photoview/src/main/java/com/github/chrisbanes/photoview/PhotoViewAttacher.java#L279-L289 // if we're all good, doAfter() will be callled on Glide's `onResourceReady`, so // let's setup the ViewMatrix - Handler().post{ + Handler().post { setBackgroundViewInfoOnPhotoView( frame, photoEditor.composedCanvas.source as PhotoView @@ -2059,13 +2058,15 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec ) { bottom_opaque_bar.visibility = View.VISIBLE val transformToUse = if (drawable.intrinsicWidth >= screenWidth) { - photoEditorView.source.scaleType = FIT_START // this aligns to top so there's no top black bar + // this aligns to top so there's no top black bar + photoEditorView.source.scaleType = FIT_START null } else { photoEditorView.source.scaleType = FIT_CENTER FitCenter() } - loadImageWithGlideToDraw(drawable, transformToUse, normalizedSize.width, normalizedSize.height, doAfterUse) + loadImageWithGlideToDraw(drawable, transformToUse, + normalizedSize.width, normalizedSize.height, doAfterUse) } else { // 3. else, load with fit-center (black bars on the side that doesn't fit) // see https://developer.android.com/reference/android/graphics/Matrix.ScaleToFit#CENTER diff --git a/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt b/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt index ecfac5b4e..604025dd7 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/frame/FrameSaveManager.kt @@ -310,13 +310,13 @@ class FrameSaveManager( Glide.with(context) .asBitmap() .load(uri) - .fitCenter() // we use fitCenter at first (instead of cropping) so we don't lose any information + .fitCenter() // we use fitCenter at first (instead of cropping) so we don't lose any information .submit(targetView.measuredWidth, targetView.measuredHeight) CENTER_CROP -> Glide.with(context) .asBitmap() .load(uri) - .centerCrop() // we use fitCenter at first (instead of cropping) so we don't lose any information + .centerCrop() // we use fitCenter at first (instead of cropping) so we don't lose any information .submit(targetView.measuredWidth, targetView.measuredHeight) else -> // default case with no transform needed so futureTarget is initialized, // but we don't really expect to get this case From cc4395555a4989a17dee7fc880e389d8da006dab Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 10 Mar 2021 16:54:12 -0300 Subject: [PATCH 46/48] we want the bottom_opaque_bar visible at all times while editing, it will be hidden when calculating a height of 0 for it --- .../com/wordpress/stories/compose/ComposeLoopFrameActivity.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt index d47aec644..228591c90 100644 --- a/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt +++ b/stories/src/main/java/com/wordpress/stories/compose/ComposeLoopFrameActivity.kt @@ -2046,8 +2046,8 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec // no parts would actually be cropped, given the matching aspect ratio it should fit // except the opaque bar given we're normalizing to 9:16 on export val drawableAspectRatio = calculateAspectRatioForDrawable(drawable) + bottom_opaque_bar.visibility = View.VISIBLE if (isAspectRatioSimilarByPercentage(drawableAspectRatio, screenSizeRatio, 0.01f)) { - bottom_opaque_bar.visibility = View.GONE photoEditorView.source.scaleType = CENTER_CROP loadImageWithGlideToDraw(drawable, CenterCrop(), screenWidth, screenHeight, doAfterUse) } else { @@ -2056,7 +2056,6 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec if (isScreenTallerThan916(screenWidth, screenHeight) && (drawable.intrinsicHeight > drawable.intrinsicWidth) ) { - bottom_opaque_bar.visibility = View.VISIBLE val transformToUse = if (drawable.intrinsicWidth >= screenWidth) { // this aligns to top so there's no top black bar photoEditorView.source.scaleType = FIT_START @@ -2070,7 +2069,6 @@ abstract class ComposeLoopFrameActivity : AppCompatActivity(), OnStoryFrameSelec } else { // 3. else, load with fit-center (black bars on the side that doesn't fit) // see https://developer.android.com/reference/android/graphics/Matrix.ScaleToFit#CENTER - bottom_opaque_bar.visibility = View.VISIBLE photoEditorView.source.scaleType = FIT_CENTER loadImageWithGlideToDraw(drawable, FitCenter(), screenWidth, screenHeight, doAfterUse) } From 4fc499a8d8ff87da08603e6ed2d5b5ea8de8cfdd Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 10 Mar 2021 16:58:42 -0300 Subject: [PATCH 47/48] added newline before EOF --- stories/src/main/java/com/wordpress/stories/util/ViewUtils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stories/src/main/java/com/wordpress/stories/util/ViewUtils.kt b/stories/src/main/java/com/wordpress/stories/util/ViewUtils.kt index 2f3b3c8f3..b821edb1a 100644 --- a/stories/src/main/java/com/wordpress/stories/util/ViewUtils.kt +++ b/stories/src/main/java/com/wordpress/stories/util/ViewUtils.kt @@ -82,4 +82,4 @@ fun isAspectRatioSimilarByPercentage(aspectRatio1: Float, aspectRatio2: Float, p } fun getSizeRatio(originalWidth: Int, originalHeight: Int): Float { return (originalWidth.toFloat() / originalHeight.toFloat()) -} \ No newline at end of file +} From ad2fca55179d42acb9ec6954ccbdab4203a9e365 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 18 Mar 2021 14:57:03 +0100 Subject: [PATCH 48/48] setting backgroundImageBlurred visibility when turning TexturView on/off --- .../java/com/automattic/photoeditor/views/PhotoEditorView.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt b/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt index 185600740..f15e77bec 100644 --- a/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt +++ b/photoeditor/src/main/java/com/automattic/photoeditor/views/PhotoEditorView.kt @@ -292,11 +292,13 @@ class PhotoEditorView : RelativeLayout { internal fun turnTextureViewOn() { backgroundImage.visibility = View.INVISIBLE + backgroundImageBlurred.visibility = View.INVISIBLE autoFitTextureView.visibility = View.VISIBLE } internal fun turnTextureViewOff() { backgroundImage.visibility = View.VISIBLE + backgroundImageBlurred.visibility = View.VISIBLE autoFitTextureView.visibility = View.INVISIBLE } @@ -304,11 +306,13 @@ class PhotoEditorView : RelativeLayout { backgroundImage.visibility = autoFitTextureView.visibility.also { autoFitTextureView.visibility = backgroundImage.visibility } + backgroundImageBlurred.visibility = backgroundImage.visibility return autoFitTextureView.visibility == View.VISIBLE } internal fun turnTextureAndImageViewOff() { backgroundImage.visibility = View.INVISIBLE + backgroundImageBlurred.visibility = View.INVISIBLE autoFitTextureView.visibility = View.INVISIBLE }