diff --git a/app_fenrir/src/main/AndroidManifest.xml b/app_fenrir/src/main/AndroidManifest.xml index 26fb8dcc3..c20ca6b02 100644 --- a/app_fenrir/src/main/AndroidManifest.xml +++ b/app_fenrir/src/main/AndroidManifest.xml @@ -462,7 +462,6 @@ android:configChanges="keyboardHidden|orientation" android:exported="true" android:label="@string/lottie_preview" - android:screenOrientation="portrait" android:windowSoftInputMode="adjustResize|stateHidden" tools:ignore="DiscouragedApi,LockedOrientationActivity"> diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/Constants.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/Constants.kt index dda26d504..135b640bd 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/Constants.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/Constants.kt @@ -17,11 +17,11 @@ object Constants { const val FILE_PROVIDER_AUTHORITY: String = "${BuildConfig.APPLICATION_ID}.file_provider" const val VK_ANDROID_APP_VERSION_NAME = "8.15" - const val VK_ANDROID_APP_VERSION_CODE = "15271" + const val VK_ANDROID_APP_VERSION_CODE = 15271 const val KATE_APP_VERSION_NAME = "118 lite" - const val KATE_APP_VERSION_CODE = "566" + const val KATE_APP_VERSION_CODE = 566 - const val IOS_APP_VERSION_CODE = "3893" + const val IOS_APP_VERSION_CODE = 3893 const val API_ID: Int = BuildConfig.VK_API_APP_ID const val SECRET: String = BuildConfig.VK_CLIENT_SECRET diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/UserAgentTool.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/UserAgentTool.kt index ebe512624..7ff2ebabf 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/UserAgentTool.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/UserAgentTool.kt @@ -20,7 +20,7 @@ object UserAgentTool { private val KATE_USER_AGENT = String.format( Locale.US, - "KateMobileAndroid/%s-%s (Android %s; SDK %d; %s; %s; %s; %s)", + "KateMobileAndroid/%s-%d (Android %s; SDK %d; %s; %s; %s; %s)", Constants.KATE_APP_VERSION_NAME, Constants.KATE_APP_VERSION_CODE, Build.VERSION.RELEASE, @@ -33,7 +33,7 @@ object UserAgentTool { private val KATE_USER_AGENT_FAKE = String.format( Locale.US, - "KateMobileAndroid/%s-%s (Android %s; SDK %d; %s; %s; %s; %s)", + "KateMobileAndroid/%s-%d (Android %s; SDK %d; %s; %s; %s; %s)", Constants.KATE_APP_VERSION_NAME, Constants.KATE_APP_VERSION_CODE, Build.VERSION.RELEASE, @@ -46,7 +46,7 @@ object UserAgentTool { private val VK_ANDROID_USER_AGENT = String.format( Locale.US, - "VKAndroidApp/%s-%s (Android %s; SDK %d; %s; %s; %s; %s)", + "VKAndroidApp/%s-%d (Android %s; SDK %d; %s; %s; %s; %s)", Constants.VK_ANDROID_APP_VERSION_NAME, Constants.VK_ANDROID_APP_VERSION_CODE, Build.VERSION.RELEASE, @@ -59,7 +59,7 @@ object UserAgentTool { private val VK_ANDROID_USER_AGENT_FAKE = String.format( Locale.US, - "VKAndroidApp/%s-%s (Android %s; SDK %d; %s; %s; %s; %s)", + "VKAndroidApp/%s-%d (Android %s; SDK %d; %s; %s; %s; %s)", Constants.VK_ANDROID_APP_VERSION_NAME, Constants.VK_ANDROID_APP_VERSION_CODE, Build.VERSION.RELEASE, @@ -72,7 +72,7 @@ object UserAgentTool { private val VK_iOS_USER_AGENT_FAKE = String.format( Locale.US, - "com.vk.vkclient/%s (iPhone, iOS 16.1, iPhone11,2, Scale/3.0)", + "com.vk.vkclient/%d (iPhone, iOS 16.1, iPhone11,2, Scale/3.0)", Constants.IOS_APP_VERSION_CODE ) @@ -82,7 +82,7 @@ object UserAgentTool { return when (type) { AccountType.KATE_HIDDEN -> String.format( Locale.US, - "KateMobileAndroid/%s-%s (Android %s; SDK %d; %s; %s; %s; %s)", + "KateMobileAndroid/%s-%d (Android %s; SDK %d; %s; %s; %s; %s)", Constants.KATE_APP_VERSION_NAME, Constants.KATE_APP_VERSION_CODE, Build.VERSION.RELEASE, @@ -95,14 +95,14 @@ object UserAgentTool { AccountType.IOS_HIDDEN -> String.format( Locale.US, - "com.vk.vkclient/%s (iPhone, iOS 16.1, %s, Scale/3.0)", + "com.vk.vkclient/%d (iPhone, iOS 16.1, %s, Scale/3.0)", Constants.IOS_APP_VERSION_CODE, device ) else -> String.format( Locale.US, - "VKAndroidApp/%s-%s (Android %s; SDK %d; %s; %s; %s; %s)", + "VKAndroidApp/%s-%d (Android %s; SDK %d; %s; %s; %s; %s)", Constants.VK_ANDROID_APP_VERSION_NAME, Constants.VK_ANDROID_APP_VERSION_CODE, Build.VERSION.RELEASE, diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/LottieActivity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/LottieActivity.kt index b6676a1b3..e80f7c7f7 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/LottieActivity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/LottieActivity.kt @@ -14,10 +14,9 @@ import androidx.documentfile.provider.DocumentFile import com.google.android.material.button.MaterialButton import dev.ragnarok.fenrir.Extra import dev.ragnarok.fenrir.R -import dev.ragnarok.fenrir.module.BufferWriteNative import dev.ragnarok.fenrir.module.FenrirNative -import dev.ragnarok.fenrir.module.rlottie.RLottie2Gif -import dev.ragnarok.fenrir.module.rlottie.RLottie2Gif.Lottie2GifListener +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottie2Gif +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottie2Gif.Lottie2GifListener import dev.ragnarok.fenrir.settings.CurrentTheme import dev.ragnarok.fenrir.settings.ISettings import dev.ragnarok.fenrir.settings.Settings @@ -26,7 +25,11 @@ import dev.ragnarok.fenrir.util.AppPerms import dev.ragnarok.fenrir.util.AppPerms.requestPermissionsAbs import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.util.Utils.hasVanillaIceCreamTarget -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView +import okio.BufferedSource +import okio.buffer +import okio.sink +import okio.source import java.io.File class LottieActivity : AppCompatActivity() { @@ -56,46 +59,40 @@ class LottieActivity : AppCompatActivity() { result.data?.getStringExtra(Extra.PATH), title ) toGif?.isEnabled = false - RLottie2Gif.create( - BufferWriteNative.fromStreamEndlessNull( - contentResolver.openInputStream( - intent.data ?: return@registerForActivityResult - ) ?: return@registerForActivityResult - ) - ).setListener(object : Lottie2GifListener { - var start: Long = 0 - var logs: String? = null - override fun onStarted() { - start = System.currentTimeMillis() - logs = "Wait a moment...\r\n" - log(logs) - } + ThorVGLottie2Gif.create(File(parentDir(), "lottie_view.json")) + .setListener(object : Lottie2GifListener { + var start: Long = 0 + var logs: String? = null + override fun onStarted() { + start = System.currentTimeMillis() + logs = "Wait a moment...\r\n" + log(logs) + } - @SuppressLint("SetTextI18n") - override fun onProgress(frame: Int, totalFrame: Int) { - log(logs + "progress : " + frame + "/" + totalFrame) - } + @SuppressLint("SetTextI18n") + override fun onProgress(frame: Int, totalFrame: Int) { + log(logs + "progress : " + frame + "/" + totalFrame) + } - override fun onFinished() { - logs = - "GIF created (" + (System.currentTimeMillis() - start) + "ms)\r\n" + - "Resolution : " + 500 + "x" + 500 + "\r\n" + - "Path : " + file + "\r\n" + - "File Size : " + file.length() / 1024 + "kb" - log(logs) - toGif?.post { toGif?.isEnabled = true } - } - }) + override fun onFinished() { + logs = + "GIF created (" + (System.currentTimeMillis() - start) + "ms)\r\n" + + "Resolution : " + 500 + "x" + 500 + "\r\n" + + "Path : " + file + "\r\n" + + "File Size : " + file.length() / 1024 + "kb" + log(logs) + toGif?.post { toGif?.isEnabled = true } + } + }) .setBackgroundColor(Color.TRANSPARENT) .setOutputPath(file) .setSize(500, 500) .setBackgroundTask(true) - .setDithering(false) .build() } } - private var lottie: RLottieImageView? = null + private var lottie: ThorVGLottieView? = null private var lg: TextView? = null internal fun log(log: String?) { lg?.post { lg?.text = log?.trim() } @@ -141,7 +138,30 @@ class LottieActivity : AppCompatActivity() { } if (savedInstanceState == null) { handleIntent(intent) + } else { + lottie?.fromFile(File(parentDir(), "lottie_view.json"), true) + lottie?.startAnimation() + } + } + + private fun parentDir(): File { + val file = File(cacheDir, "lottie_cache") + if (file.isFile) { + file.delete() + } + if (!file.exists()) { + file.mkdirs() } + return file + } + + private fun writeTempCacheFile(source: BufferedSource): File { + val file = File(parentDir(), "lottie_view.json") + + file.sink().buffer().use { output -> + output.writeAll(source) + } + return file } private fun handleIntent(intent: Intent?) { @@ -157,15 +177,14 @@ class LottieActivity : AppCompatActivity() { val action = intent.action if (Intent.ACTION_VIEW == action) { try { - lottie?.setAutoRepeat(true) - lottie?.fromString( - BufferWriteNative.fromStreamEndlessNull( - contentResolver.openInputStream( - getIntent().data ?: return - ) ?: return - ), Utils.dp(500f), Utils.dp(500f) + writeTempCacheFile( + (contentResolver.openInputStream( + getIntent().data ?: return + ) ?: return).source().buffer() ) - lottie?.playAnimation() + + lottie?.fromFile(File(parentDir(), "lottie_view.json"), true) + lottie?.startAnimation() } catch (e: Throwable) { e.printStackTrace() } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/MainActivity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/MainActivity.kt index cb67d9c0d..4aee28b55 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/MainActivity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/MainActivity.kt @@ -551,6 +551,10 @@ open class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks, OnSect } private fun updateNotificationCount(account: Long) { + if (account == ISettings.IAccountsSettings.INVALID_ID) { + removeNotificationsBadge() + return + } mCompositeJob.add(CountersInteractor(networkInterfaces).getApiCounters(account) .fromIOToMain({ counters -> updateNotificationsBadge(counters) }) { removeNotificationsBadge() }) } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/SinglePhotoActivity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/SinglePhotoActivity.kt index 21ead2941..a666e253f 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/SinglePhotoActivity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/SinglePhotoActivity.kt @@ -46,7 +46,7 @@ import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.toMain import dev.ragnarok.fenrir.util.toast.CustomToast import dev.ragnarok.fenrir.view.CircleCounterButton import dev.ragnarok.fenrir.view.TouchImageView -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView import dev.ragnarok.fenrir.view.pager.WeakPicassoLoadCallback import java.io.File import java.lang.ref.WeakReference @@ -232,7 +232,7 @@ class SinglePhotoActivity : NoMainActivity(), PlaceProvider, AppStyleable { val reload: FloatingActionButton private val mPicassoLoadCallback: WeakPicassoLoadCallback val photo: TouchImageView - val progress: RLottieImageView + val progress: ThorVGLottieView var animationDispose = CancelableJob() private var mAnimationLoaded = false private var mLoadingNow = false @@ -258,13 +258,19 @@ class SinglePhotoActivity : NoMainActivity(), PlaceProvider, AppStyleable { val k = ObjectAnimator.ofFloat(progress, View.ALPHA, 0.0f).setDuration(1000) k.addListener(object : StubAnimatorListener() { override fun onAnimationEnd(animation: Animator) { - progress.clearAnimationDrawable() + progress.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) progress.visibility = View.GONE progress.alpha = 1f } override fun onAnimationCancel(animation: Animator) { - progress.clearAnimationDrawable() + progress.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) progress.visibility = View.GONE progress.alpha = 1f } @@ -272,7 +278,10 @@ class SinglePhotoActivity : NoMainActivity(), PlaceProvider, AppStyleable { k.start() } else if (mAnimationLoaded && !mLoadingNow) { mAnimationLoaded = false - progress.clearAnimationDrawable() + progress.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) progress.visibility = View.GONE } else if (mLoadingNow) { animationDispose += delayTaskFlow(300).toMain { @@ -280,8 +289,6 @@ class SinglePhotoActivity : NoMainActivity(), PlaceProvider, AppStyleable { progress.visibility = View.VISIBLE progress.fromRes( dev.ragnarok.fenrir_common.R.raw.loading, - Utils.dp(100F), - Utils.dp(100F), intArrayOf( 0x000000, CurrentTheme.getColorPrimary(ref.get()), @@ -289,7 +296,7 @@ class SinglePhotoActivity : NoMainActivity(), PlaceProvider, AppStyleable { CurrentTheme.getColorSecondary(ref.get()) ) ) - progress.playAnimation() + progress.startAnimation() } } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/gifpager/GifPagerActivity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/gifpager/GifPagerActivity.kt index f6ba28b4e..35048d04f 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/gifpager/GifPagerActivity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/gifpager/GifPagerActivity.kt @@ -267,11 +267,12 @@ class GifPagerActivity : AbsDocumentPreviewActivity(), IPhotoPagerView, @@ -252,7 +252,7 @@ class PhotoPagerActivity : BaseMvpActivity private var mButtonLike: CircleCounterButton? = null private var mButtonComments: CircleCounterButton? = null private var buttonShare: CircleCounterButton? = null - private var mLoadingProgressBar: RLottieImageView? = null + private var mLoadingProgressBar: ThorVGLottieView? = null private var mLoadingProgressBarDispose = CancelableJob() private var mLoadingProgressBarLoaded = false private var mToolbar: Toolbar? = null @@ -748,8 +748,6 @@ class PhotoPagerActivity : BaseMvpActivity mLoadingProgressBar?.visibility = View.VISIBLE mLoadingProgressBar?.fromRes( dev.ragnarok.fenrir_common.R.raw.loading, - Utils.dp(100F), - Utils.dp(100F), intArrayOf( 0x000000, Color.WHITE, @@ -757,12 +755,15 @@ class PhotoPagerActivity : BaseMvpActivity Color.WHITE ) ) - mLoadingProgressBar?.playAnimation() + mLoadingProgressBar?.startAnimation() } } else if (mLoadingProgressBarLoaded) { mLoadingProgressBarLoaded = false mLoadingProgressBar?.visibility = View.GONE - mLoadingProgressBar?.clearAnimationDrawable() + mLoadingProgressBar?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } @@ -851,7 +852,7 @@ class PhotoPagerActivity : BaseMvpActivity private val mPicassoLoadCallback: WeakPicassoLoadCallback val photo: TouchImageView val tagsPlaceholder: FrameLayout - val progress: RLottieImageView + val progress: ThorVGLottieView var animationDispose = CancelableJob() private var mAnimationLoaded = false private var mLoadingNow = false @@ -961,13 +962,19 @@ class PhotoPagerActivity : BaseMvpActivity val k = ObjectAnimator.ofFloat(progress, View.ALPHA, 0.0f).setDuration(1000) k.addListener(object : StubAnimatorListener() { override fun onAnimationEnd(animation: Animator) { - progress.clearAnimationDrawable() + progress.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) progress.visibility = View.GONE progress.alpha = 1f } override fun onAnimationCancel(animation: Animator) { - progress.clearAnimationDrawable() + progress.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) progress.visibility = View.GONE progress.alpha = 1f } @@ -975,7 +982,10 @@ class PhotoPagerActivity : BaseMvpActivity k.start() } else if (mAnimationLoaded && !mLoadingNow) { mAnimationLoaded = false - progress.clearAnimationDrawable() + progress.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) progress.visibility = View.GONE } else if (mLoadingNow) { animationDispose += delayTaskFlow(300).toMain { @@ -983,8 +993,6 @@ class PhotoPagerActivity : BaseMvpActivity progress.visibility = View.VISIBLE progress.fromRes( dev.ragnarok.fenrir_common.R.raw.loading, - Utils.dp(100F), - Utils.dp(100F), intArrayOf( 0x000000, CurrentTheme.getColorPrimary(this@PhotoPagerActivity), @@ -992,7 +1000,7 @@ class PhotoPagerActivity : BaseMvpActivity CurrentTheme.getColorSecondary(this@PhotoPagerActivity) ) ) - progress.playAnimation() + progress.startAnimation() } } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/shortvideopager/ShortVideoPagerActivity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/shortvideopager/ShortVideoPagerActivity.kt index f37d62947..fdaea4111 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/shortvideopager/ShortVideoPagerActivity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/shortvideopager/ShortVideoPagerActivity.kt @@ -60,7 +60,7 @@ import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.toMain import dev.ragnarok.fenrir.util.toast.CustomSnackbars import dev.ragnarok.fenrir.view.CircleCounterButton import dev.ragnarok.fenrir.view.ExpandableSurfaceView -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView import java.lang.ref.WeakReference class ShortVideoPagerActivity : BaseMvpActivity(), @@ -81,7 +81,7 @@ class ShortVideoPagerActivity : BaseMvpActivity(R.id.swipe_helper) + val mHelper = findViewById(R.id.swipe_helper) if (HelperSimple.needHelp(HelperSimple.SHORT_VIDEO_HELPER, 2)) { mHelper?.visibility = View.VISIBLE mHelper?.fromRes( dev.ragnarok.fenrir_common.R.raw.story_guide_hand_swipe, - Utils.dp(500F), - Utils.dp(500F), intArrayOf(0x333333, CurrentTheme.getColorSecondary(this)) ) - mHelper?.playAnimation() + mHelper?.startAnimation() helpDisposable += delayTaskFlow(5000).toMain { - mHelper?.clearAnimationDrawable() + mHelper?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) mHelper?.visibility = View.GONE } } else { @@ -220,8 +221,6 @@ class ShortVideoPagerActivity : BaseMvpActivity if (stateSpeed) CurrentTheme.getColorPrimary(this) else "#ffffff".toColor() ) } - val mHelper = findViewById(R.id.swipe_helper) + val mHelper = findViewById(R.id.swipe_helper) if (HelperSimple.needHelp(HelperSimple.STORY_HELPER, 2)) { mHelper?.visibility = View.VISIBLE mHelper?.fromRes( dev.ragnarok.fenrir_common.R.raw.story_guide_hand_swipe, - Utils.dp(500F), - Utils.dp(500F), intArrayOf(0x333333, CurrentTheme.getColorSecondary(this)) ) - mHelper?.playAnimation() + mHelper?.startAnimation() helpDisposable += delayTaskFlow(5000).toMain { - mHelper?.clearAnimationDrawable() + mHelper?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) mHelper?.visibility = View.GONE } } else { @@ -452,7 +453,7 @@ class StoryPagerActivity : BaseMvpActivity private inner class Holder(rootView: View) : MultiHolder(rootView), SurfaceHolder.Callback { val mSurfaceView: ExpandableSurfaceView = rootView.findViewById(R.id.videoSurface) - val mProgressBar: RLottieImageView + val mProgressBar: ThorVGLottieView override var isSurfaceReady = false override fun surfaceCreated(holder: SurfaceHolder) { isSurfaceReady = true @@ -469,8 +470,6 @@ class StoryPagerActivity : BaseMvpActivity if (visible) { mProgressBar.fromRes( dev.ragnarok.fenrir_common.R.raw.loading, - Utils.dp(100F), - Utils.dp(100F), intArrayOf( 0x000000, CurrentTheme.getColorPrimary(this@StoryPagerActivity), @@ -478,9 +477,12 @@ class StoryPagerActivity : BaseMvpActivity CurrentTheme.getColorSecondary(this@StoryPagerActivity) ) ) - mProgressBar.playAnimation() + mProgressBar.startAnimation() } else { - mProgressBar.clearAnimationDrawable() + mProgressBar.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } @@ -504,7 +506,7 @@ class StoryPagerActivity : BaseMvpActivity val reload: FloatingActionButton private val mPicassoLoadCallback: WeakPicassoLoadCallback val photo: TouchImageView - val progress: RLottieImageView + val progress: ThorVGLottieView var animationDispose = CancelableJob() private var mAnimationLoaded = false private var mLoadingNow = false @@ -540,13 +542,19 @@ class StoryPagerActivity : BaseMvpActivity val k = ObjectAnimator.ofFloat(progress, View.ALPHA, 0.0f).setDuration(1000) k.addListener(object : StubAnimatorListener() { override fun onAnimationEnd(animation: Animator) { - progress.clearAnimationDrawable() + progress.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) progress.visibility = View.GONE progress.alpha = 1f } override fun onAnimationCancel(animation: Animator) { - progress.clearAnimationDrawable() + progress.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) progress.visibility = View.GONE progress.alpha = 1f } @@ -554,7 +562,10 @@ class StoryPagerActivity : BaseMvpActivity k.start() } else if (mAnimationLoaded && !mLoadingNow) { mAnimationLoaded = false - progress.clearAnimationDrawable() + progress.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) progress.visibility = View.GONE } else if (mLoadingNow) { animationDispose += delayTaskFlow(300).toMain { @@ -562,8 +573,6 @@ class StoryPagerActivity : BaseMvpActivity progress.visibility = View.VISIBLE progress.fromRes( dev.ragnarok.fenrir_common.R.raw.loading, - Utils.dp(100F), - Utils.dp(100F), intArrayOf( 0x000000, CurrentTheme.getColorPrimary(this@StoryPagerActivity), @@ -571,7 +580,7 @@ class StoryPagerActivity : BaseMvpActivity CurrentTheme.getColorSecondary(this@StoryPagerActivity) ) ) - progress.playAnimation() + progress.startAnimation() } } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AccountApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AccountApi.kt index ca00eacc8..0f1c9534b 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AccountApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AccountApi.kt @@ -58,9 +58,11 @@ internal class AccountApi(accountId: Long, provider: IServiceProvider) : } override fun registerDevice( + api_id: Int?, + app_id: Int?, token: String?, pushes_granted: Int?, - app_version: String?, + app_version: Int?, push_provider: String?, companion_apps: String?, type: Int?, @@ -72,6 +74,8 @@ internal class AccountApi(accountId: Long, provider: IServiceProvider) : return provideService(IAccountService(), TokenType.USER) .flatMapConcat { it.registerDevice( + api_id, + app_id, token, pushes_granted, app_version, diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AuthApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AuthApi.kt index 8799eaeb5..4e7480623 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AuthApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AuthApi.kt @@ -9,10 +9,12 @@ import dev.ragnarok.fenrir.api.CaptchaNeedException import dev.ragnarok.fenrir.api.IDirectLoginServiceProvider import dev.ragnarok.fenrir.api.NeedValidationException import dev.ragnarok.fenrir.api.interfaces.IAuthApi -import dev.ragnarok.fenrir.api.model.AnonymToken import dev.ragnarok.fenrir.api.model.LoginResponse import dev.ragnarok.fenrir.api.model.VKApiValidationResponse +import dev.ragnarok.fenrir.api.model.response.AnonymTokenResponse import dev.ragnarok.fenrir.api.model.response.BaseResponse +import dev.ragnarok.fenrir.api.model.response.GetAuthCodeStatusResponse +import dev.ragnarok.fenrir.api.model.response.SetAuthCodeStatusResponse import dev.ragnarok.fenrir.api.model.response.VKUrlResponse import dev.ragnarok.fenrir.nonNullNoEmpty import dev.ragnarok.fenrir.util.Utils.getDeviceId @@ -162,7 +164,7 @@ class AuthApi(private val service: IDirectLoginServiceProvider) : IAuthApi { clientSecret: String?, v: String?, device_id: String? - ): Flow { + ): Flow { return service.provideAuthService() .flatMapConcat { it.get_anonym_token( @@ -173,16 +175,61 @@ class AuthApi(private val service: IDirectLoginServiceProvider) : IAuthApi { device_id, DEVICE_COUNTRY_CODE ) - .map { s -> - if (s.error != null) { - throw AuthException(s.error.orEmpty(), s.errorDescription) + .map { + if (it.error != null) { + throw AuthException( + it.error.orEmpty(), + it.errorDescription + ) } else { - s + it } } } } + override fun setAuthCodeStatus( + auth_code: String?, + apiId: Int, + device_id: String?, + accessToken: String?, + v: String? + ): Flow { + return service.provideAuthService() + .flatMapConcat { + it.setAuthCodeStatus( + auth_code, + apiId, + device_id, + accessToken, + DEVICE_COUNTRY_CODE, + v + ) + .map(extractResponseWithErrorHandling()) + } + } + + override fun getAuthCodeStatus( + auth_code: String?, + apiId: Int, + device_id: String?, + accessToken: String?, + v: String? + ): Flow { + return service.provideAuthService() + .flatMapConcat { + it.getAuthCodeStatus( + auth_code, + apiId, + device_id, + accessToken, + DEVICE_COUNTRY_CODE, + v + ) + .map(extractResponseWithErrorHandling()) + } + } + companion object { fun extractResponseWithErrorHandling(): (BaseResponse) -> T = { err -> err.error?.let { throw ApiException(it) } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/StoriesShortVideosApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/StoriesShortVideosApi.kt index 8df6b2a3b..e2541e070 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/StoriesShortVideosApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/StoriesShortVideosApi.kt @@ -156,4 +156,20 @@ internal class StoriesShortVideosApi(accountId: Long, provider: IServiceProvider } } } + + override fun subscribe(owner_id: Long?): Flow { + return provideService(IStoriesShortVideosService(), TokenType.USER) + .flatMapConcat { + it.subscribe(owner_id) + .map(extractResponseWithErrorHandling()) + } + } + + override fun unsubscribe(owner_id: Long?): Flow { + return provideService(IStoriesShortVideosService(), TokenType.USER) + .flatMapConcat { + it.unsubscribe(owner_id) + .map(extractResponseWithErrorHandling()) + } + } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/VKApies.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/VKApies.kt index 37ed83246..116601fce 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/VKApies.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/VKApies.kt @@ -31,7 +31,9 @@ import dev.ragnarok.fenrir.api.interfaces.IVideoApi import dev.ragnarok.fenrir.api.interfaces.IWallApi import dev.ragnarok.fenrir.api.rest.IServiceRest import dev.ragnarok.fenrir.api.rest.SimplePostHttp +import dev.ragnarok.fenrir.settings.ISettings import dev.ragnarok.fenrir.util.Utils +import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.toFlowThrowable import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -196,8 +198,13 @@ internal class VKApies private constructor( } fun provideRest(aid: Long, vararg tokenPolicy: Int): Flow { + if (aid == ISettings.IAccountsSettings.INVALID_ID) { + return toFlowThrowable(UnsupportedOperationException("Please select account!")) + } if (useCustomToken) { - return provider.provideCustomRest(aid, customAccessToken!!) + customAccessToken?.let { + return provider.provideCustomRest(aid, it) + } } val isCommunity = aid < 0 return if (isCommunity) { @@ -211,7 +218,7 @@ internal class VKApies private constructor( } else -> { - throw UnsupportedOperationException("Unsupported account_id: $aid with token_policy: " + tokenPolicy.contentToString()) + toFlowThrowable(UnsupportedOperationException("Unsupported account_id: $aid with token_policy: " + tokenPolicy.contentToString())) } } } else { @@ -225,8 +232,10 @@ internal class VKApies private constructor( } else -> { - throw UnsupportedOperationException( - "Unsupported account_id: " + aid + " with token_policy: " + tokenPolicy.contentToString() + toFlowThrowable( + UnsupportedOperationException( + "Unsupported account_id: " + aid + " with token_policy: " + tokenPolicy.contentToString() + ) ) } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IAccountApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IAccountApi.kt index 79bce9c19..e5dddd159 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IAccountApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IAccountApi.kt @@ -24,9 +24,11 @@ interface IAccountApi { @CheckResult fun registerDevice( + api_id: Int?, + app_id: Int?, token: String?, pushes_granted: Int?, - app_version: String?, + app_version: Int?, push_provider: String?, companion_apps: String?, type: Int?, diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IAuthApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IAuthApi.kt index e9bbe8c57..2bf1ae195 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IAuthApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IAuthApi.kt @@ -1,12 +1,16 @@ package dev.ragnarok.fenrir.api.interfaces -import dev.ragnarok.fenrir.api.model.AnonymToken +import androidx.annotation.CheckResult import dev.ragnarok.fenrir.api.model.LoginResponse import dev.ragnarok.fenrir.api.model.VKApiValidationResponse +import dev.ragnarok.fenrir.api.model.response.AnonymTokenResponse +import dev.ragnarok.fenrir.api.model.response.GetAuthCodeStatusResponse +import dev.ragnarok.fenrir.api.model.response.SetAuthCodeStatusResponse import dev.ragnarok.fenrir.api.model.response.VKUrlResponse import kotlinx.coroutines.flow.Flow interface IAuthApi { + @CheckResult fun directLogin( grantType: String?, clientId: Int, @@ -23,6 +27,7 @@ interface IAuthApi { libverify_support: Boolean ): Flow + @CheckResult fun validatePhone( phone: String?, apiId: Int, @@ -34,6 +39,7 @@ interface IAuthApi { allow_callreset: Boolean ): Flow + @CheckResult fun authByExchangeToken( clientId: Int, apiId: Int, @@ -46,11 +52,30 @@ interface IAuthApi { v: String? ): Flow + @CheckResult fun get_anonym_token( apiId: Int, clientId: Int, clientSecret: String?, v: String?, device_id: String? - ): Flow + ): Flow + + @CheckResult + fun setAuthCodeStatus( + auth_code: String?, + apiId: Int, + device_id: String?, + accessToken: String?, + v: String? + ): Flow + + @CheckResult + fun getAuthCodeStatus( + auth_code: String?, + apiId: Int, + device_id: String?, + accessToken: String?, + v: String? + ): Flow } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IStatusApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IStatusApi.kt index 00e5a0217..bb35ffbff 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IStatusApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IStatusApi.kt @@ -5,5 +5,5 @@ import kotlinx.coroutines.flow.Flow interface IStatusApi { @CheckResult - operator fun set(text: String?, groupId: Long?): Flow + fun set(text: String?, groupId: Long?): Flow } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IStoriesShortVideosApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IStoriesShortVideosApi.kt index 9ecd339c3..b42116c1c 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IStoriesShortVideosApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IStoriesShortVideosApi.kt @@ -69,4 +69,10 @@ interface IStoriesShortVideosApi { extended: Int?, fields: String? ): Flow + + @CheckResult + fun subscribe(owner_id: Long?): Flow + + @CheckResult + fun unsubscribe(owner_id: Long?): Flow } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/AnonymToken.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/AnonymTokenResponse.kt similarity index 82% rename from app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/AnonymToken.kt rename to app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/AnonymTokenResponse.kt index b5c1bc0f2..b0ee004aa 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/AnonymToken.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/AnonymTokenResponse.kt @@ -1,10 +1,10 @@ -package dev.ragnarok.fenrir.api.model +package dev.ragnarok.fenrir.api.model.response import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -class AnonymToken { +class AnonymTokenResponse { @SerialName("token") var token: String? = null diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/GetAuthCodeStatusResponse.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/GetAuthCodeStatusResponse.kt new file mode 100644 index 000000000..b0d2517ec --- /dev/null +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/GetAuthCodeStatusResponse.kt @@ -0,0 +1,19 @@ +package dev.ragnarok.fenrir.api.model.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +class GetAuthCodeStatusResponse { + @SerialName("expires_in") + var expires_in: Long = 0 + + @SerialName("access_token") + var access_token: String? = null + + @SerialName("user_id") + var user_id: Long = 0 + + @SerialName("status") + var status: Int = 0 +} diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/SetAuthCodeStatusResponse.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/SetAuthCodeStatusResponse.kt new file mode 100644 index 000000000..bc6e31cc0 --- /dev/null +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/SetAuthCodeStatusResponse.kt @@ -0,0 +1,22 @@ +package dev.ragnarok.fenrir.api.model.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +class SetAuthCodeStatusResponse { + @SerialName("domain") + var domain: String? = null + + @SerialName("expires_in") + var expires_in: Long = 0 + + @SerialName("faq_url") + var faq_url: String? = null + + @SerialName("polling_delay") + var polling_delay: Int = 0 + + @SerialName("status") + var status: Int = 0 +} diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/StoriesResponse.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/StoriesResponse.kt index 9b7cf18ca..ff3f9f5fe 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/StoriesResponse.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/StoriesResponse.kt @@ -8,7 +8,7 @@ import kotlinx.serialization.Serializable @Serializable class StoriesResponse { @SerialName("items") - var items: List? = null + var items: List? = null @SerialName("profiles") var profiles: List? = null diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/StoryBlockResponce.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/StoryBlockResponse.kt similarity index 79% rename from app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/StoryBlockResponce.kt rename to app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/StoryBlockResponse.kt index c7de1c4ba..603a722df 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/StoryBlockResponce.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/StoryBlockResponse.kt @@ -5,10 +5,10 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -class StoryBlockResponce { +class StoryBlockResponse { @SerialName("stories") var stories: List? = null @SerialName("grouped") - var grouped: List? = null + var grouped: List? = null } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAccountService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAccountService.kt index bd3b8d187..24edaf405 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAccountService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAccountService.kt @@ -63,9 +63,11 @@ class IAccountService : IServiceRest() { //https://vk.com/dev/account.registerDevice fun registerDevice( + api_id: Int?, + app_id: Int?, token: String?, pushes_granted: Int?, - app_version: String?, + app_version: Int?, push_provider: String?, companion_apps: String?, type: Int?, @@ -86,6 +88,9 @@ class IAccountService : IServiceRest() { "device_model" to deviceModel, "device_id" to deviceId, "system_version" to systemVersion, + "has_google_services" to 1, + "app_id" to app_id, + "api_id" to api_id, "settings" to settings ), baseInt ) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAuthService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAuthService.kt index f39b65614..ff9525747 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAuthService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAuthService.kt @@ -1,9 +1,11 @@ package dev.ragnarok.fenrir.api.services -import dev.ragnarok.fenrir.api.model.AnonymToken import dev.ragnarok.fenrir.api.model.LoginResponse import dev.ragnarok.fenrir.api.model.VKApiValidationResponse +import dev.ragnarok.fenrir.api.model.response.AnonymTokenResponse import dev.ragnarok.fenrir.api.model.response.BaseResponse +import dev.ragnarok.fenrir.api.model.response.GetAuthCodeStatusResponse +import dev.ragnarok.fenrir.api.model.response.SetAuthCodeStatusResponse import dev.ragnarok.fenrir.api.model.response.VKUrlResponse import dev.ragnarok.fenrir.api.rest.IServiceRest import kotlinx.coroutines.flow.Flow @@ -120,7 +122,7 @@ class IAuthService : IServiceRest() { v: String?, device_id: String?, lang: String? - ): Flow { + ): Flow { return rest.request( "get_anonym_token", form( @@ -131,7 +133,51 @@ class IAuthService : IServiceRest() { "device_id" to device_id, "lang" to lang, "https" to 1 - ), AnonymToken.serializer() + ), AnonymTokenResponse.serializer() + ) + } + + fun setAuthCodeStatus( + auth_code: String?, + apiId: Int, + device_id: String?, + accessToken: String?, + lang: String?, + v: String? + ): Flow> { + return rest.request( + "auth.setAuthCodeStatus", + form( + "api_id" to apiId, + "auth_code" to auth_code, + "device_id" to device_id, + "access_token" to accessToken, + "lang" to lang, + "https" to 1, + "v" to v + ), base(SetAuthCodeStatusResponse.serializer()) + ) + } + + fun getAuthCodeStatus( + auth_code: String?, + apiId: Int, + device_id: String?, + accessToken: String?, + lang: String?, + v: String? + ): Flow> { + return rest.request( + "auth.getAuthCodeStatus", + form( + "api_id" to apiId, + "auth_code" to auth_code, + "device_id" to device_id, + "access_token" to accessToken, + "lang" to lang, + "https" to 1, + "v" to v + ), base(GetAuthCodeStatusResponse.serializer()) ) } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IStatusService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IStatusService.kt index 62a69a5c6..2c6d01ac7 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IStatusService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IStatusService.kt @@ -12,7 +12,7 @@ class IStatusService : IServiceRest() { * @param groupId Identifier of a community to set a status in. If left blank the status is set to current user. * @return 1 */ - operator fun set( + fun set( text: String?, groupId: Long? ): Flow> { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IStoriesShortVideosService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IStoriesShortVideosService.kt index 78e946651..49705d817 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IStoriesShortVideosService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IStoriesShortVideosService.kt @@ -175,4 +175,14 @@ class IStoriesShortVideosService : IServiceRest() { ), base(ShortVideosResponse.serializer()) ) } + + //is_subscribed_stories + + fun subscribe(owner_id: Long?): Flow> { + return rest.request("stories.subscribe", form("owner_id" to owner_id), baseInt) + } + + fun unsubscribe(owner_id: Long?): Flow> { + return rest.request("stories.unsubscribe", form("owner_id" to owner_id), baseInt) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/TempDataStorage.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/TempDataStorage.kt index f1ec03c0d..e85e5eccc 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/TempDataStorage.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/TempDataStorage.kt @@ -245,7 +245,7 @@ class TempDataStorage internal constructor(context: Context) : ITempDataStorage return flow { val db = helper.writableDatabase db.beginTransaction() - if (isActive()) { + if (!isActive()) { db.endTransaction() } else { try { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/IdPairEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/IdPairEntity.kt index bb82b2d8d..cd08e7921 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/IdPairEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/IdPairEntity.kt @@ -12,7 +12,7 @@ class IdPairEntity { var ownerId = 0L private set - operator fun set(id: Int, ownerId: Long): IdPairEntity { + fun set(id: Int, ownerId: Long): IdPairEntity { this.id = id this.ownerId = ownerId return this diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/ArticleDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/ArticleDboEntity.kt index d75e215a9..bffb0403f 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/ArticleDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/ArticleDboEntity.kt @@ -27,7 +27,7 @@ class ArticleDboEntity : DboEntity() { var isFavorite = false private set - operator fun set(id: Int, owner_id: Long): ArticleDboEntity { + fun set(id: Int, owner_id: Long): ArticleDboEntity { this.id = id ownerId = owner_id return this diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/AudioArtistDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/AudioArtistDboEntity.kt index 9b24aed69..aa22d1698 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/AudioArtistDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/AudioArtistDboEntity.kt @@ -40,7 +40,7 @@ class AudioArtistDboEntity : DboEntity() { var height = 0 private set - operator fun set(url: String?, width: Int, height: Int): AudioArtistImageEntity { + fun set(url: String?, width: Int, height: Int): AudioArtistImageEntity { this.url = url this.width = width this.height = height diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/AudioDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/AudioDboEntity.kt index 2ea78eeae..a24f1638c 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/AudioDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/AudioDboEntity.kt @@ -47,7 +47,7 @@ class AudioDboEntity : DboEntity() { var isHq = false private set - operator fun set(id: Int, ownerId: Long): AudioDboEntity { + fun set(id: Int, ownerId: Long): AudioDboEntity { this.id = id this.ownerId = ownerId return this diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/AudioMessageDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/AudioMessageDboEntity.kt index 9b49e0cfc..20bbe3687 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/AudioMessageDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/AudioMessageDboEntity.kt @@ -27,7 +27,7 @@ class AudioMessageDboEntity : DboEntity() { var was_listened = false private set - operator fun set(id: Int, ownerId: Long): AudioMessageDboEntity { + fun set(id: Int, ownerId: Long): AudioMessageDboEntity { this.id = id this.ownerId = ownerId return this diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/CommentEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/CommentEntity.kt index f538d4bb8..2fb7e6c61 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/CommentEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/CommentEntity.kt @@ -67,7 +67,7 @@ class CommentEntity { var threads: List? = null private set - operator fun set( + fun set( sourceId: Int, sourceOwnerId: Long, @CommentedType sourceType: Int, diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/CommunityDetailsEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/CommunityDetailsEntity.kt index 09a399054..9b24a049d 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/CommunityDetailsEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/CommunityDetailsEntity.kt @@ -204,7 +204,7 @@ class CommunityDetailsEntity { var width = 0 private set - operator fun set(url: String?, height: Int, width: Int): CoverImage { + fun set(url: String?, height: Int, width: Int): CoverImage { this.url = url this.height = height this.width = width @@ -226,7 +226,7 @@ class CommunityDetailsEntity { var cover: String? = null private set - operator fun set( + fun set( id: Int, url: String?, title: String?, diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/CountryDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/CountryDboEntity.kt index d5f2ce486..1988a63db 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/CountryDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/CountryDboEntity.kt @@ -11,7 +11,7 @@ class CountryDboEntity { var title: String? = null private set - operator fun set(id: Int, title: String?): CountryDboEntity { + fun set(id: Int, title: String?): CountryDboEntity { this.id = id this.title = title return this diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/DocumentDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/DocumentDboEntity.kt index b9fc9a937..9a3512140 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/DocumentDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/DocumentDboEntity.kt @@ -33,7 +33,7 @@ class DocumentDboEntity : DboEntity() { var video: VideoPreviewDbo? = null private set - operator fun set(id: Int, ownerId: Long): DocumentDboEntity { + fun set(id: Int, ownerId: Long): DocumentDboEntity { this.id = id this.ownerId = ownerId return this @@ -104,7 +104,7 @@ class DocumentDboEntity : DboEntity() { var fileSize: Long = 0 private set - operator fun set(src: String?, width: Int, height: Int, fileSize: Long): VideoPreviewDbo { + fun set(src: String?, width: Int, height: Int, fileSize: Long): VideoPreviewDbo { this.src = src this.width = width this.height = height @@ -123,7 +123,7 @@ class DocumentDboEntity : DboEntity() { var height = 0 private set - operator fun set(src: String?, width: Int, height: Int): GraffitiDbo { + fun set(src: String?, width: Int, height: Int): GraffitiDbo { this.src = src this.width = width this.height = height diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/MarketAlbumDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/MarketAlbumDboEntity.kt index 6a6dd4064..42e538713 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/MarketAlbumDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/MarketAlbumDboEntity.kt @@ -23,7 +23,7 @@ class MarketAlbumDboEntity : DboEntity() { var updated_time: Long = 0 private set - operator fun set(id: Int, owner_id: Long): MarketAlbumDboEntity { + fun set(id: Int, owner_id: Long): MarketAlbumDboEntity { this.id = id this.owner_id = owner_id return this diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/MarketDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/MarketDboEntity.kt index 0c7da220a..7bcb84992 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/MarketDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/MarketDboEntity.kt @@ -37,7 +37,7 @@ class MarketDboEntity : DboEntity() { var photos: List? = null private set - operator fun set(id: Int, owner_id: Long): MarketDboEntity { + fun set(id: Int, owner_id: Long): MarketDboEntity { this.id = id this.owner_id = owner_id return this diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/MessageDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/MessageDboEntity.kt index 50e4ba518..632686796 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/MessageDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/MessageDboEntity.kt @@ -74,7 +74,7 @@ class MessageDboEntity : DboEntity() { var updateTime: Long = 0 private set - operator fun set(id: Int, peerId: Long, fromId: Long): MessageDboEntity { + fun set(id: Int, peerId: Long, fromId: Long): MessageDboEntity { this.id = id this.peerId = peerId this.fromId = fromId diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PageDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PageDboEntity.kt index 73da755ef..87730c8f3 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PageDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PageDboEntity.kt @@ -31,7 +31,7 @@ class PageDboEntity : DboEntity() { var viewUrl: String? = null private set - operator fun set(id: Int, ownerId: Long): PageDboEntity { + fun set(id: Int, ownerId: Long): PageDboEntity { this.id = id this.ownerId = ownerId return this diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PhotoAlbumDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PhotoAlbumDboEntity.kt index 3c6f57359..6a57d0ae7 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PhotoAlbumDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PhotoAlbumDboEntity.kt @@ -35,7 +35,7 @@ class PhotoAlbumDboEntity : DboEntity() { var privacyComment: PrivacyEntity? = null private set - operator fun set(id: Int, ownerId: Long): PhotoAlbumDboEntity { + fun set(id: Int, ownerId: Long): PhotoAlbumDboEntity { this.id = id this.ownerId = ownerId return this diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PhotoDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PhotoDboEntity.kt index ab8dee644..de7ab884e 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PhotoDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PhotoDboEntity.kt @@ -43,7 +43,7 @@ class PhotoDboEntity : DboEntity() { var sizes: PhotoSizeEntity? = null private set - operator fun set(id: Int, ownerId: Long): PhotoDboEntity { + fun set(id: Int, ownerId: Long): PhotoDboEntity { this.id = id this.ownerId = ownerId return this diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PollDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PollDboEntity.kt index eb6a6c81c..a0ce031ee 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PollDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PollDboEntity.kt @@ -40,7 +40,7 @@ class PollDboEntity : DboEntity() { var background: BackgroundEntity? = null private set - operator fun set(id: Int, ownerId: Long): PollDboEntity { + fun set(id: Int, ownerId: Long): PollDboEntity { this.id = id this.ownerId = ownerId return this @@ -156,7 +156,7 @@ class PollDboEntity : DboEntity() { var rate = 0.0 private set - operator fun set(id: Long, text: String?, voteCount: Int, rate: Double): Answer { + fun set(id: Long, text: String?, voteCount: Int, rate: Double): Answer { this.id = id this.text = text this.voteCount = voteCount diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PostDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PostDboEntity.kt index 3e5c3e45c..2ed3844a7 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PostDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PostDboEntity.kt @@ -72,7 +72,7 @@ class PostDboEntity : DboEntity() { var copyHierarchy: List? = null private set - operator fun set(id: Int, ownerId: Long): PostDboEntity { + fun set(id: Int, ownerId: Long): PostDboEntity { this.id = id this.ownerId = ownerId return this @@ -251,7 +251,7 @@ class PostDboEntity : DboEntity() { var url: String? = null private set - operator fun set(type: Int, platform: String?, data: Int, url: String?): SourceDbo { + fun set(type: Int, platform: String?, data: Int, url: String?): SourceDbo { this.type = type this.platform = platform this.data = data diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PrivacyEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PrivacyEntity.kt index a3b2c025c..c67d71ad9 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PrivacyEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PrivacyEntity.kt @@ -12,7 +12,7 @@ class PrivacyEntity { var entries: List? = null private set - operator fun set(type: String?, entries: List?): PrivacyEntity { + fun set(type: String?, entries: List?): PrivacyEntity { this.type = type this.entries = entries return this @@ -30,7 +30,7 @@ class PrivacyEntity { var isAllowed = false private set - operator fun set(type: Int, id: Long, allowed: Boolean): Entry { + fun set(type: Int, id: Long, allowed: Boolean): Entry { this.type = type this.id = id isAllowed = allowed diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/StickerDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/StickerDboEntity.kt index b4cb674cd..6b5473d40 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/StickerDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/StickerDboEntity.kt @@ -57,7 +57,7 @@ class StickerDboEntity : DboEntity() { var url: String? = null private set - operator fun set(url: String?, type: String?): AnimationEntity { + fun set(url: String?, type: String?): AnimationEntity { this.url = url this.type = type return this @@ -76,7 +76,7 @@ class StickerDboEntity : DboEntity() { var height = 0 private set - operator fun set(url: String?, width: Int, height: Int): Img { + fun set(url: String?, width: Int, height: Int): Img { this.url = url this.width = width this.height = height diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/StickerSetEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/StickerSetEntity.kt index bc9b14ba0..f860159a3 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/StickerSetEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/StickerSetEntity.kt @@ -66,7 +66,7 @@ class StickerSetEntity(val id: Int) { var height = 0 private set - operator fun set(url: String?, width: Int, height: Int): Img { + fun set(url: String?, width: Int, height: Int): Img { this.url = url this.width = width this.height = height diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/TopicDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/TopicDboEntity.kt index 0b9e13d9d..3dd4941cb 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/TopicDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/TopicDboEntity.kt @@ -35,7 +35,7 @@ class TopicDboEntity : DboEntity() { var poll: PollDboEntity? = null private set - operator fun set(id: Int, ownerId: Long): TopicDboEntity { + fun set(id: Int, ownerId: Long): TopicDboEntity { this.id = id this.ownerId = ownerId return this diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/UserDetailsEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/UserDetailsEntity.kt index c027a2d30..89e7fcf60 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/UserDetailsEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/UserDetailsEntity.kt @@ -462,7 +462,7 @@ class UserDetailsEntity { var width = 0 private set - operator fun set(url: String?, height: Int, width: Int): CoverImage { + fun set(url: String?, height: Int, width: Int): CoverImage { this.url = url this.height = height this.width = width diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/VideoDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/VideoDboEntity.kt index eee215cf6..91a4f7adc 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/VideoDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/VideoDboEntity.kt @@ -85,7 +85,7 @@ class VideoDboEntity : DboEntity() { var trailer: String? = null private set - operator fun set(id: Int, ownerId: Long): VideoDboEntity { + fun set(id: Int, ownerId: Long): VideoDboEntity { this.id = id this.ownerId = ownerId return this diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/dialog/audioduplicate/AudioDuplicateDialog.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/dialog/audioduplicate/AudioDuplicateDialog.kt index d9a7213ae..e8a988ce3 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/dialog/audioduplicate/AudioDuplicateDialog.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/dialog/audioduplicate/AudioDuplicateDialog.kt @@ -42,7 +42,7 @@ import dev.ragnarok.fenrir.settings.Settings import dev.ragnarok.fenrir.toColor import dev.ragnarok.fenrir.util.AppTextUtils import dev.ragnarok.fenrir.util.Utils -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class AudioDuplicateDialog : BaseMvpDialogFragment(), IAudioDuplicateView { @@ -210,7 +210,7 @@ class AudioDuplicateDialog : val play: View = itemView.findViewById(R.id.item_audio_play) val play_cover: ImageView = itemView.findViewById(R.id.item_audio_play_cover) val selectionView: MaterialCardView = itemView.findViewById(R.id.item_audio_selection) - val visual: RLottieImageView = itemView.findViewById(R.id.item_audio_visual) + val visual: ThorVGLottieView = itemView.findViewById(R.id.item_audio_visual) val time: TextView = itemView.findViewById(R.id.item_audio_time) } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/IStoriesShortVideosInteractor.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/IStoriesShortVideosInteractor.kt index 67acf664a..ec290b943 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/IStoriesShortVideosInteractor.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/IStoriesShortVideosInteractor.kt @@ -34,4 +34,7 @@ interface IStoriesShortVideosInteractor { startFrom: String?, count: Int? ): Flow, String?>> + + fun subscribe(accountId: Long, owner_id: Long): Flow + fun unsubscribe(accountId: Long, owner_id: Long): Flow } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/StoriesShortVideosInteractor.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/StoriesShortVideosInteractor.kt index b0f5df0b4..1e6ae2124 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/StoriesShortVideosInteractor.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/StoriesShortVideosInteractor.kt @@ -4,7 +4,7 @@ import dev.ragnarok.fenrir.api.Fields import dev.ragnarok.fenrir.api.interfaces.INetworker import dev.ragnarok.fenrir.api.model.AccessIdPair import dev.ragnarok.fenrir.api.model.VKApiStory -import dev.ragnarok.fenrir.api.model.response.StoryBlockResponce +import dev.ragnarok.fenrir.api.model.response.StoryBlockResponse import dev.ragnarok.fenrir.domain.IOwnersRepository import dev.ragnarok.fenrir.domain.IStoriesShortVideosInteractor import dev.ragnarok.fenrir.domain.mappers.Dto2Model @@ -64,7 +64,7 @@ class StoriesShortVideosInteractor( } } - private fun parseStoryBlock(resp: StoryBlockResponce, dtos: MutableList) { + private fun parseStoryBlock(resp: StoryBlockResponse, dtos: MutableList) { resp.stories.nonNullNoEmpty { parseParentStory(it, dtos) dtos.addAll(it) @@ -222,4 +222,16 @@ class StoriesShortVideosInteractor( Pair(dbos, nextFrom) } } + + override fun subscribe(accountId: Long, owner_id: Long): Flow { + return networker.vkDefault(accountId) + .stories() + .subscribe(owner_id) + } + + override fun unsubscribe(accountId: Long, owner_id: Long): Flow { + return networker.vkDefault(accountId) + .stories() + .unsubscribe(owner_id) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/PreferencesFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/PreferencesFragment.kt index e5e4afc13..ceae60577 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/PreferencesFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/PreferencesFragment.kt @@ -87,6 +87,7 @@ import dev.ragnarok.fenrir.settings.backup.SettingsBackup import dev.ragnarok.fenrir.util.AppPerms import dev.ragnarok.fenrir.util.AppPerms.requestPermissionsAbs import dev.ragnarok.fenrir.util.Utils +import dev.ragnarok.fenrir.util.coroutines.CancelableJob import dev.ragnarok.fenrir.util.coroutines.CompositeJob import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.delayTaskFlow import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.fromIOToMain @@ -97,7 +98,7 @@ import dev.ragnarok.fenrir.util.serializeble.prefs.Preferences import dev.ragnarok.fenrir.util.toast.CustomSnackbars import dev.ragnarok.fenrir.util.toast.CustomToast.Companion.createCustomToast import dev.ragnarok.fenrir.view.MySearchView -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView import dev.ragnarok.fenrir.view.navigation.AbsNavigationView import java.io.File import java.io.FileOutputStream @@ -109,7 +110,7 @@ class PreferencesFragment : AbsPreferencesFragment(), PreferencesAdapter.OnScree private var layoutManager: LinearLayoutManager? = null private var searchView: MySearchView? = null private val disposables = CompositeJob() - private var sleepDataDisposable = CompositeJob() + private var sleepDataDisposable = CancelableJob() private val SEARCH_DELAY = 2000 override val keyInstanceState: String = "root_preferences" @@ -1954,7 +1955,7 @@ class PreferencesFragment : AbsPreferencesFragment(), PreferencesAdapter.OnScree summary = Utils.getAppVersionName(requireActivity()) onClick { val view = View.inflate(requireActivity(), R.layout.dialog_about_us, null) - val anim: RLottieImageView = view.findViewById(R.id.lottie_animation) + val anim: ThorVGLottieView = view.findViewById(R.id.lottie_animation) val txt: TextView = view.findViewById(dev.ragnarok.fenrir_common.R.id.sub_header) txt.setText(Common.getAboutUsHeader(Settings.get().main().paganSymbol)) @@ -1966,11 +1967,9 @@ class PreferencesFragment : AbsPreferencesFragment(), PreferencesAdapter.OnScree anim.visibility = View.VISIBLE anim.fromRes( cbc.lottieRes, - Utils.dp(cbc.lottie_widthHeight), - Utils.dp(cbc.lottie_widthHeight), cbc.lottie_replacement ) - anim.playAnimation() + anim.startAnimation() } else { anim.visibility = View.GONE } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountAdapter.kt index bdd1b2d98..31623ef43 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountAdapter.kt @@ -19,11 +19,10 @@ import dev.ragnarok.fenrir.settings.CurrentTheme import dev.ragnarok.fenrir.settings.Settings import dev.ragnarok.fenrir.toColor import dev.ragnarok.fenrir.util.UserInfoResolveUtil -import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.util.ViewUtils.displayAvatar import dev.ragnarok.fenrir.util.ViewUtils.getOnlineIcon import dev.ragnarok.fenrir.view.OnlineView -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView import kotlin.math.abs class AccountAdapter( @@ -100,17 +99,18 @@ class AccountAdapter( if (isCurrent) { holder.active.fromRes( dev.ragnarok.fenrir_common.R.raw.select_check_box, - Utils.dp(40f), - Utils.dp(40f), intArrayOf( 0x333333, CurrentTheme.getColorPrimary( context ), 0x777777, CurrentTheme.getColorSecondary(context) ) ) - holder.active.playAnimation() + holder.active.startAnimation() } else { - holder.active.clearAnimationDrawable() + holder.active.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } else { if (isCurrent) { @@ -134,7 +134,7 @@ class AccountAdapter( val vOnline: OnlineView = itemView.findViewById(R.id.item_user_online) val tvLastTime: TextView = itemView.findViewById(R.id.last_time) val avatar: ImageView = itemView.findViewById(R.id.avatar) - val active: RLottieImageView = itemView.findViewById(R.id.active) + val active: ThorVGLottieView = itemView.findViewById(R.id.active) val account: View = itemView.findViewById(R.id.account_select) } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountsFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountsFragment.kt index 947b7d513..2c138ff6e 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountsFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountsFragment.kt @@ -43,6 +43,7 @@ import dev.ragnarok.fenrir.activity.EnterPinActivity import dev.ragnarok.fenrir.activity.FileManagerSelectActivity import dev.ragnarok.fenrir.activity.LoginActivity.Companion.createIntent import dev.ragnarok.fenrir.activity.ProxyManagerActivity +import dev.ragnarok.fenrir.activity.qr.CameraScanActivity import dev.ragnarok.fenrir.api.Auth.scope import dev.ragnarok.fenrir.dialog.directauth.DirectAuthDialog import dev.ragnarok.fenrir.dialog.directauth.DirectAuthDialog.Companion.newInstance @@ -52,6 +53,7 @@ import dev.ragnarok.fenrir.modalbottomsheetdialogfragment.ModalBottomSheetDialog import dev.ragnarok.fenrir.modalbottomsheetdialogfragment.OptionRequest import dev.ragnarok.fenrir.model.Account import dev.ragnarok.fenrir.model.SaveAccount +import dev.ragnarok.fenrir.nonNullNoEmpty import dev.ragnarok.fenrir.place.PlaceFactory.getPreferencesPlace import dev.ragnarok.fenrir.settings.Settings import dev.ragnarok.fenrir.util.AppPerms.hasReadStoragePermission @@ -63,6 +65,8 @@ import dev.ragnarok.fenrir.util.Utils.isHiddenAccount import dev.ragnarok.fenrir.util.ViewUtils.setupSwipeRefreshLayoutWithCurrentTheme import dev.ragnarok.fenrir.util.toast.CustomSnackbars import dev.ragnarok.fenrir.util.toast.CustomToast.Companion.createCustomToast +import java.util.Calendar +import java.util.regex.Pattern class AccountsFragment : BaseMvpFragment(), IAccountsView, AccountAdapter.Callback, @@ -641,6 +645,30 @@ class AccountsFragment : BaseMvpFragment(), IA } } + private val requestQRScan = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { result -> + if (result.resultCode == RESULT_OK) { + val scanner = result.data?.extras?.getString(Extra.URL) + if (scanner.nonNullNoEmpty()) { + val PATTERN: Pattern = Pattern.compile("qr\\.vk\\.com/w2a[?]q=(\\w+)") + val matcher = PATTERN.matcher(scanner) + try { + if (matcher.find()) { + matcher.group(1) + ?.let { + presenter?.fireAuthByQR(it) + return@registerForActivityResult + } + } + showError(R.string.auth_by_qr_error) + } catch (e: Exception) { + showThrowable(e) + } + } + } + } + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { when (menuItem.itemId) { R.id.action_proxy -> { @@ -699,6 +727,22 @@ class AccountsFragment : BaseMvpFragment(), IA return true } + R.id.auth_by_qr -> { + if (Utils.isOfficialVKCurrent && Settings.get() + .accounts().anonymToken.expired_at <= Calendar.getInstance().time.time / 1000 + ) { + showError(R.string.auth_by_qr_error) + return false + } + val intent = + Intent( + requireActivity(), + CameraScanActivity::class.java + ) + requestQRScan.launch(intent) + return true + } + else -> return false } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountsPresenter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountsPresenter.kt index 337435d75..898e25922 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountsPresenter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountsPresenter.kt @@ -8,6 +8,7 @@ import android.widget.Toast import dev.ragnarok.fenrir.AccountType import dev.ragnarok.fenrir.Constants import dev.ragnarok.fenrir.Includes +import dev.ragnarok.fenrir.Includes.provideApplicationContext import dev.ragnarok.fenrir.R import dev.ragnarok.fenrir.api.ApiException import dev.ragnarok.fenrir.api.Auth @@ -19,6 +20,7 @@ import dev.ragnarok.fenrir.api.adapters.AbsDtoAdapter.Companion.hasPrimitive import dev.ragnarok.fenrir.api.interfaces.INetworker import dev.ragnarok.fenrir.api.model.VKApiUser import dev.ragnarok.fenrir.api.model.response.BaseResponse +import dev.ragnarok.fenrir.api.model.response.SetAuthCodeStatusResponse import dev.ragnarok.fenrir.api.rest.HttpException import dev.ragnarok.fenrir.api.util.VKStringUtils import dev.ragnarok.fenrir.db.DBHelper @@ -42,6 +44,7 @@ import dev.ragnarok.fenrir.nonNullNoEmpty import dev.ragnarok.fenrir.nonNullNoEmptyOr import dev.ragnarok.fenrir.requireNonNull import dev.ragnarok.fenrir.service.ErrorLocalizer +import dev.ragnarok.fenrir.settings.AnonymToken import dev.ragnarok.fenrir.settings.Settings import dev.ragnarok.fenrir.settings.backup.SettingsBackup import dev.ragnarok.fenrir.toColor @@ -50,6 +53,8 @@ import dev.ragnarok.fenrir.util.ShortcutUtils import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.fromIOToMain import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.hiddenIO +import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.inMainThread +import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.isActive import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.syncSingle import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.syncSingleSafe import dev.ragnarok.fenrir.util.serializeble.json.Json @@ -66,7 +71,9 @@ import dev.ragnarok.fenrir.util.serializeble.json.longOrNull import dev.ragnarok.fenrir.util.serializeble.json.put import dev.ragnarok.fenrir.util.serializeble.msgpack.MsgPack import dev.ragnarok.fenrir.util.toast.CustomToast +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map @@ -77,6 +84,7 @@ import okio.buffer import okio.source import java.io.File import java.io.FileOutputStream +import java.util.Calendar import kotlin.coroutines.cancellation.CancellationException class AccountsPresenter(savedInstanceState: Bundle?) : @@ -377,7 +385,7 @@ class AccountsPresenter(savedInstanceState: Bundle?) : out.write(bom) out.write(bytes) out.flush() - Includes.provideApplicationContext().sendBroadcast( + provideApplicationContext().sendBroadcast( Intent( Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file) @@ -566,6 +574,112 @@ class AccountsPresenter(savedInstanceState: Bundle?) : }) { saveAccounts(context, file, null) }) } + private fun checkQRAuthState( + q: String, + token: String, + data: SetAuthCodeStatusResponse + ): Flow { + return flow { + delay((data.polling_delay * 1000).toLong()) + if (isActive() && data.expires_in > Calendar.getInstance().time.time / 1000) { + networker.vkAuth().getAuthCodeStatus( + q, Constants.API_ID, Utils.getDeviceId( + Constants.DEFAULT_ACCOUNT_TYPE, + provideApplicationContext() + ), token, Constants.AUTH_API_VERSION + ).catch { + if (Constants.IS_DEBUG) { + it.printStackTrace() + } + inMainThread { + view?.showThrowable(it) + } + emit(false) + }.collect { + if (it.access_token.nonNullNoEmpty()) { + inMainThread { + processNewAccount( + it.user_id, + it.access_token, + Constants.DEFAULT_ACCOUNT_TYPE, + null, + null, + "fenrir_qr", + isCurrent = true, + needSave = false + ) + view?.showColoredSnack(R.string.refreshing_token, "#AA48BE2D".toColor()) + appendJob( + accountsInteractor.getExchangeToken(it.user_id).fromIOToMain { t -> + t.token.nonNullNoEmpty { st -> + appendJob( + networker.vkDirectAuth(Constants.DEFAULT_ACCOUNT_TYPE) + .authByExchangeToken( + Constants.API_ID, + Constants.API_ID, + st, + Auth.scope, + "expired_token", + Utils.getDeviceId( + Constants.DEFAULT_ACCOUNT_TYPE, + provideApplicationContext() + ), + "1.102", + null, + Constants.API_VERSION + ).fromIOToMain({ p -> + val aToken = p.resultUrl?.let { it1 -> + tryExtractAccessToken(it1) + } ?: return@fromIOToMain + Settings.get().accounts() + .storeAccessToken(it.user_id, aToken) + + view?.showColoredSnack( + R.string.success, + "#AA48BE2D".toColor() + ) + }, { + view?.showThrowable(it) + }) + ) + } + } + ) + } + emit(true) + } else { + checkQRAuthState(q, token, data).collect { + emit(it) + } + } + } + } else { + inMainThread { + view?.showError(R.string.auth_by_qr_error) + } + emit(false) + } + } + } + + fun fireAuthByQR(q: String) { + Settings.get().accounts().anonymToken.token?.let { + appendJob( + networker.vkAuth().setAuthCodeStatus( + q, Constants.API_ID, Utils.getDeviceId( + Constants.DEFAULT_ACCOUNT_TYPE, + provideApplicationContext() + ), it, Constants.AUTH_API_VERSION + ) + .fromIOToMain({ rt -> + if (rt.status != 0 && rt.polling_delay > 0) { + appendJob(checkQRAuthState(q, it, rt).hiddenIO()) + } + }, { showError(it) }) + ) + } + } + @Suppress("DEPRECATION") private fun saveAccounts(context: Context, file: File, Users: IOwnersBundle?) { var out: FileOutputStream? = null @@ -636,7 +750,7 @@ class AccountsPresenter(savedInstanceState: Bundle?) : out.write(bom) out.write(bytes) out.flush() - Includes.provideApplicationContext().sendBroadcast( + provideApplicationContext().sendBroadcast( Intent( Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file) @@ -673,7 +787,7 @@ class AccountsPresenter(savedInstanceState: Bundle?) : .add("lang", Constants.DEVICE_COUNTRY_CODE) .add("https", "1") .add( - "device_id", Utils.getDeviceId(type, Includes.provideApplicationContext()) + "device_id", Utils.getDeviceId(type, provideApplicationContext()) ) return Includes.networkInterfaces.getVkRestProvider().provideRawHttpClient(type, null) .flatMapConcat { client -> @@ -722,5 +836,24 @@ class AccountsPresenter(savedInstanceState: Bundle?) : init { fireLoad(false) + + if (Utils.isOfficialVKCurrent && Settings.get() + .accounts().anonymToken.expired_at <= Calendar.getInstance().time.time / 1000 + ) { + appendJob(networker.vkDirectAuth().get_anonym_token( + Constants.API_ID, + Constants.API_ID, + Constants.SECRET, + Constants.AUTH_API_VERSION, + Utils.getDeviceId( + Constants.DEFAULT_ACCOUNT_TYPE, + provideApplicationContext() + ) + ).fromIOToMain { + if (it.token.nonNullNoEmpty() && it.expired_at > Calendar.getInstance().time.time / 1000) { + Settings.get().accounts().anonymToken = AnonymToken().set(it) + } + }) + } } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/AudioPlayerFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/AudioPlayerFragment.kt index 95e13d502..f48fb541d 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/AudioPlayerFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/AudioPlayerFragment.kt @@ -64,7 +64,7 @@ import dev.ragnarok.fenrir.util.toast.CustomSnackbars import dev.ragnarok.fenrir.util.toast.CustomToast.Companion.createCustomToast import dev.ragnarok.fenrir.view.CustomSeekBar import dev.ragnarok.fenrir.view.media.* -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieShapeableImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieShapeableView import kotlinx.coroutines.Job import java.io.File import java.io.FileOutputStream @@ -706,7 +706,7 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee } if (tvAlbum != null) { var album = "" - if (audioTrack?.album_title.nonNullNoEmpty()) album += requireActivity().getString(R.string.album) + " " + audioTrack?.album_title + if (audioTrack?.album_title.nonNullNoEmpty()) album += requireActivity().getString(R.string.album) + " " + audioTrack.album_title tvAlbum?.text = album } tvTitle?.text = audioTrack?.artist @@ -1019,7 +1019,7 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee } private inner class CoverViewHolder(view: View) : RecyclerView.ViewHolder(view) { - val ivCover: RLottieShapeableImageView = view.findViewById(R.id.cover) + val ivCover: ThorVGLottieShapeableView = view.findViewById(R.id.cover) val holderTarget = object : BitmapTarget { override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) { @@ -1038,8 +1038,6 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee if (FenrirNative.isNativeLoaded) { ivCover.fromRes( Common.getPlayerNullArtAnimation(Settings.get().main().paganSymbol), - 450, - 450, intArrayOf( 0x333333, CurrentTheme.getColorSurface(requireActivity()), @@ -1047,7 +1045,7 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee CurrentTheme.getColorOnSurface(requireActivity()) ) ) - ivCover.playAnimation() + ivCover.startAnimation() } else { ivCover.setImageResource(R.drawable.itunes) ivCover.drawable?.setTint( @@ -1077,8 +1075,6 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee if (FenrirNative.isNativeLoaded) { ivCover.fromRes( Common.getPlayerNullArtAnimation(Settings.get().main().paganSymbol), - 450, - 450, intArrayOf( 0x333333, CurrentTheme.getColorSurface(requireActivity()), @@ -1086,7 +1082,7 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee CurrentTheme.getColorOnSurface(requireActivity()) ) ) - ivCover.playAnimation() + ivCover.startAnimation() } else { ivCover.setImageResource(R.drawable.itunes) ivCover.drawable.setTint(CurrentTheme.getColorOnSurface(requireActivity())) @@ -1118,16 +1114,6 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee override fun onViewDetachedFromWindow(holder: CoverViewHolder) { super.onViewDetachedFromWindow(holder) PicassoInstance.with().cancelRequest(holder.ivCover) - if (holder.ivCover.drawable is Animatable) { - (holder.ivCover.drawable as Animatable).stop() - } - } - - override fun onViewAttachedToWindow(holder: CoverViewHolder) { - super.onViewAttachedToWindow(holder) - if (holder.ivCover.drawable is Animatable) { - (holder.ivCover.drawable as Animatable).start() - } } override fun onBindViewHolder(holder: CoverViewHolder, position: Int) { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/audios/AudioRecyclerAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/audios/AudioRecyclerAdapter.kt index 9ac62220a..a27b375c4 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/audios/AudioRecyclerAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/audios/AudioRecyclerAdapter.kt @@ -66,7 +66,7 @@ import dev.ragnarok.fenrir.util.hls.M3U8 import dev.ragnarok.fenrir.util.toast.CustomSnackbars import dev.ragnarok.fenrir.util.toast.CustomToast.Companion.createCustomToast import dev.ragnarok.fenrir.view.WeakViewAnimatorAdapter -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class AudioRecyclerAdapter( context: Context, @@ -843,7 +843,7 @@ class AudioRecyclerAdapter( val title: TextView = itemView.findViewById(R.id.dialog_message) val play: View = itemView.findViewById(R.id.item_audio_play) val play_cover: ImageView = itemView.findViewById(R.id.item_audio_play_cover) - val visual: RLottieImageView = itemView.findViewById(R.id.item_audio_visual) + val visual: ThorVGLottieView = itemView.findViewById(R.id.item_audio_visual) val time: TextView = itemView.findViewById(R.id.item_audio_time) val saved: ImageView = itemView.findViewById(R.id.saved) val lyric: ImageView = itemView.findViewById(R.id.lyric) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/catalog_v2/lists/CatalogV2ListFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/catalog_v2/lists/CatalogV2ListFragment.kt index 707873e5e..82d48ca30 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/catalog_v2/lists/CatalogV2ListFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/catalog_v2/lists/CatalogV2ListFragment.kt @@ -43,13 +43,13 @@ import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.util.coroutines.CancelableJob import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.delayTaskFlow import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.toMain -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class CatalogV2ListFragment : BaseMvpFragment(), ICatalogV2ListView, MenuProvider { private var viewPager: ViewPager2? = null private var mAdapter: Adapter? = null - private var loading: RLottieImageView? = null + private var loading: ThorVGLottieView? = null private var animLoad: ObjectAnimator? = null private var animationDispose = CancelableJob() private var mAnimationLoaded = false @@ -161,13 +161,19 @@ class CatalogV2ListFragment : BaseMvpFragment(private notifyItemRangeInserted(position + headersCount + size, items.size - position) } - operator fun set(position: Int, item: T) { + fun set(position: Int, item: T) { items[position] = item notifyItemChanged(position + headersCount) } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/base/compat/ViewHostDelegate.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/base/compat/ViewHostDelegate.kt index 078220179..2413d40ca 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/base/compat/ViewHostDelegate.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/base/compat/ViewHostDelegate.kt @@ -86,9 +86,9 @@ class ViewHostDelegate

, V : IMvpView> { } fun onSaveInstanceState(outState: Bundle) { - presenter?.run { + presenter?.let { lastKnownPresenterState = Bundle() - saveState(lastKnownPresenterState ?: return@run) + it.saveState(lastKnownPresenterState ?: return@let) } outState.putBundle(SAVE_PRESENTER_STATE, lastKnownPresenterState) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/fave/favephotos/FavePhotosAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/fave/favephotos/FavePhotosAdapter.kt index eb15957b1..e04de9a4f 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/fave/favephotos/FavePhotosAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/fave/favephotos/FavePhotosAdapter.kt @@ -16,7 +16,7 @@ import dev.ragnarok.fenrir.picasso.PicassoInstance.Companion.with import dev.ragnarok.fenrir.settings.CurrentTheme import dev.ragnarok.fenrir.util.AppTextUtils import dev.ragnarok.fenrir.util.Utils -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class FavePhotosAdapter(context: Context, private var data: List) : RecyclerView.Adapter() { @@ -54,15 +54,16 @@ class FavePhotosAdapter(context: Context, private var data: List) : viewHolder.current.visibility = View.VISIBLE viewHolder.current.fromRes( dev.ragnarok.fenrir_common.R.raw.donater_fire, - Utils.dp(100f), - Utils.dp(100f), intArrayOf(0xFF812E, colorPrimary), true ) - viewHolder.current.playAnimation() + viewHolder.current.startAnimation() } else { viewHolder.current.visibility = View.GONE - viewHolder.current.clearAnimationDrawable() + viewHolder.current.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } with() @@ -125,6 +126,6 @@ class FavePhotosAdapter(context: Context, private var data: List) : val tvComment: TextView = itemView.findViewById(R.id.vk_photo_item_comment_counter) val ivLike: ImageView = itemView.findViewById(R.id.vk_photo_item_like) val ivComment: ImageView = itemView.findViewById(R.id.vk_photo_item_comment) - val current: RLottieImageView = itemView.findViewById(R.id.current) + val current: ThorVGLottieView = itemView.findViewById(R.id.current) } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/filemanagerselect/FileManagerSelectFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/filemanagerselect/FileManagerSelectFragment.kt index dab673eec..66da0045c 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/filemanagerselect/FileManagerSelectFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/filemanagerselect/FileManagerSelectFragment.kt @@ -26,13 +26,12 @@ import dev.ragnarok.fenrir.listener.PicassoPauseOnScrollListener import dev.ragnarok.fenrir.listener.UpdatableNavigation import dev.ragnarok.fenrir.model.FileItem import dev.ragnarok.fenrir.settings.CurrentTheme -import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.util.coroutines.CancelableJob import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.delayTaskFlow import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.toMain import dev.ragnarok.fenrir.util.toast.CustomSnackbars import dev.ragnarok.fenrir.view.MySearchView -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView import java.io.File class FileManagerSelectFragment : @@ -41,7 +40,7 @@ class FileManagerSelectFragment : private var mRecyclerView: RecyclerView? = null private var mLayoutManager: GridLayoutManager? = null private var empty: TextView? = null - private var loading: RLottieImageView? = null + private var loading: ThorVGLottieView? = null private var tvCurrentDir: TextView? = null private var mAdapter: FileManagerSelectAdapter? = null private var mSelected: FloatingActionButton? = null @@ -113,13 +112,19 @@ class FileManagerSelectFragment : animLoad = ObjectAnimator.ofFloat(loading, View.ALPHA, 0.0f).setDuration(1000) animLoad?.addListener(object : StubAnimatorListener() { override fun onAnimationEnd(animation: Animator) { - loading?.clearAnimationDrawable() + loading?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) loading?.visibility = View.GONE loading?.alpha = 1f } override fun onAnimationCancel(animation: Animator) { - loading?.clearAnimationDrawable() + loading?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) loading?.visibility = View.GONE loading?.alpha = 1f } @@ -189,8 +194,6 @@ class FileManagerSelectFragment : loading?.alpha = 1f loading?.fromRes( dev.ragnarok.fenrir_common.R.raw.s_loading, - Utils.dp(180f), - Utils.dp(180f), intArrayOf( 0x333333, CurrentTheme.getColorPrimary(requireActivity()), @@ -198,7 +201,7 @@ class FileManagerSelectFragment : CurrentTheme.getColorSecondary(requireActivity()) ) ) - loading?.playAnimation() + loading?.startAnimation() } } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/audioslocalserver/AudioLocalServerRecyclerAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/audioslocalserver/AudioLocalServerRecyclerAdapter.kt index c7fefe1a0..3b2e49d2d 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/audioslocalserver/AudioLocalServerRecyclerAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/audioslocalserver/AudioLocalServerRecyclerAdapter.kt @@ -53,7 +53,7 @@ import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.sharedFlowToMain import dev.ragnarok.fenrir.util.toast.CustomSnackbars import dev.ragnarok.fenrir.util.toast.CustomToast.Companion.createCustomToast import dev.ragnarok.fenrir.view.WeakViewAnimatorAdapter -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow @@ -560,7 +560,7 @@ class AudioLocalServerRecyclerAdapter( view.visibility = View.GONE } } - val visual: RLottieImageView = itemView.findViewById(R.id.item_audio_visual) + val visual: ThorVGLottieView = itemView.findViewById(R.id.item_audio_visual) val time: TextView = itemView.findViewById(R.id.item_audio_time) var animator: ObjectAnimator? = null fun startSelectionAnimation() { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/filemanagerremote/FileManagerRemoteAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/filemanagerremote/FileManagerRemoteAdapter.kt index 730fb14d7..f77de59e5 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/filemanagerremote/FileManagerRemoteAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/filemanagerremote/FileManagerRemoteAdapter.kt @@ -41,7 +41,7 @@ import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.fromIOToMain import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.sharedFlowToMain import dev.ragnarok.fenrir.util.toast.CustomSnackbars import dev.ragnarok.fenrir.util.toast.CustomToast.Companion.createCustomToast -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class FileManagerRemoteAdapter(private var context: Context, private var data: List) : RecyclerView.Adapter() { @@ -435,15 +435,16 @@ class FileManagerRemoteAdapter(private var context: Context, private var data: L holder.current.visibility = View.VISIBLE holder.current.fromRes( dev.ragnarok.fenrir_common.R.raw.select_fire, - Utils.dp(100f), - Utils.dp(100f), intArrayOf(0xFF812E, colorPrimary), true ) - holder.current.playAnimation() + holder.current.startAnimation() } else { holder.current.visibility = View.GONE - holder.current.clearAnimationDrawable() + holder.current.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } holder.fileInfo.setBackgroundColor(messageBubbleColor) @@ -624,15 +625,16 @@ class FileManagerRemoteAdapter(private var context: Context, private var data: L holder.current.visibility = View.VISIBLE holder.current.fromRes( dev.ragnarok.fenrir_common.R.raw.select_fire, - Utils.dp(100f), - Utils.dp(100f), intArrayOf(0xFF812E, colorPrimary), true ) - holder.current.playAnimation() + holder.current.startAnimation() } else { holder.current.visibility = View.GONE - holder.current.clearAnimationDrawable() + holder.current.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } holder.fileInfo.setBackgroundColor(messageBubbleColor) @@ -675,7 +677,7 @@ class FileManagerRemoteAdapter(private var context: Context, private var data: L val fileName: TextView = itemView.findViewById(R.id.item_file_name) val fileDetails: TextView = itemView.findViewById(R.id.item_file_details) val icon: ImageView = itemView.findViewById(R.id.item_file_icon) - val current: RLottieImageView = itemView.findViewById(R.id.current) + val current: ThorVGLottieView = itemView.findViewById(R.id.current) val fileInfo: LinearLayout = itemView.findViewById(R.id.item_file_info) } @@ -683,8 +685,8 @@ class FileManagerRemoteAdapter(private var context: Context, private var data: L val fileName: TextView = itemView.findViewById(R.id.item_file_name) val fileDetails: TextView = itemView.findViewById(R.id.item_file_details) val icon: ImageView = itemView.findViewById(R.id.item_file_icon) - val current: RLottieImageView = itemView.findViewById(R.id.current) - val visual: RLottieImageView = itemView.findViewById(R.id.item_audio_visual) + val current: ThorVGLottieView = itemView.findViewById(R.id.current) + val visual: ThorVGLottieView = itemView.findViewById(R.id.item_audio_visual) val fileInfo: LinearLayout = itemView.findViewById(R.id.item_file_info) } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/filemanagerremote/FileManagerRemoteFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/filemanagerremote/FileManagerRemoteFragment.kt index 2502482db..665ddc146 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/filemanagerremote/FileManagerRemoteFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/filemanagerremote/FileManagerRemoteFragment.kt @@ -40,7 +40,6 @@ import dev.ragnarok.fenrir.place.PlaceFactory import dev.ragnarok.fenrir.service.ErrorLocalizer import dev.ragnarok.fenrir.settings.CurrentTheme import dev.ragnarok.fenrir.settings.Settings -import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.util.ViewUtils import dev.ragnarok.fenrir.util.coroutines.CancelableJob import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.delayTaskFlow @@ -48,7 +47,7 @@ import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.toMain import dev.ragnarok.fenrir.util.toast.CustomSnackbars import dev.ragnarok.fenrir.util.toast.CustomToast import dev.ragnarok.fenrir.view.MySearchView -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class FileManagerRemoteFragment : BaseMvpFragment(), @@ -56,7 +55,7 @@ class FileManagerRemoteFragment : private var mRecyclerView: RecyclerView? = null private var mLayoutManager: GridLayoutManager? = null private var empty: TextView? = null - private var loading: RLottieImageView? = null + private var loading: ThorVGLottieView? = null private var tvCurrentDir: TextView? = null private var mAdapter: FileManagerRemoteAdapter? = null private var mSwipeRefreshLayout: SwipeRefreshLayout? = null @@ -126,13 +125,19 @@ class FileManagerRemoteFragment : animLoad = ObjectAnimator.ofFloat(loading, View.ALPHA, 0.0f).setDuration(1000) animLoad?.addListener(object : StubAnimatorListener() { override fun onAnimationEnd(animation: Animator) { - loading?.clearAnimationDrawable() + loading?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) loading?.visibility = View.GONE loading?.alpha = 1f } override fun onAnimationCancel(animation: Animator) { - loading?.clearAnimationDrawable() + loading?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) loading?.visibility = View.GONE loading?.alpha = 1f } @@ -205,8 +210,6 @@ class FileManagerRemoteFragment : loading?.alpha = 1f loading?.fromRes( dev.ragnarok.fenrir_common.R.raw.s_loading, - Utils.dp(180f), - Utils.dp(180f), intArrayOf( 0x333333, CurrentTheme.getColorPrimary(requireActivity()), @@ -214,7 +217,7 @@ class FileManagerRemoteFragment : CurrentTheme.getColorSecondary(requireActivity()) ) ) - loading?.playAnimation() + loading?.startAnimation() } } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/photoslocalserver/LocalServerPhotosAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/photoslocalserver/LocalServerPhotosAdapter.kt index 5395a6e71..6ae3f1d3d 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/photoslocalserver/LocalServerPhotosAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/photoslocalserver/LocalServerPhotosAdapter.kt @@ -17,7 +17,7 @@ import dev.ragnarok.fenrir.settings.CurrentTheme import dev.ragnarok.fenrir.util.AppTextUtils import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.view.AspectRatioImageView -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class LocalServerPhotosAdapter(private val mContext: Context, private var data: List) : RecyclerView.Adapter() { @@ -47,15 +47,16 @@ class LocalServerPhotosAdapter(private val mContext: Context, private var data: viewHolder.current.visibility = View.VISIBLE viewHolder.current.fromRes( dev.ragnarok.fenrir_common.R.raw.donater_fire, - Utils.dp(100f), - Utils.dp(100f), intArrayOf(0xFF812E, colorPrimary), true ) - viewHolder.current.playAnimation() + viewHolder.current.startAnimation() } else { viewHolder.current.visibility = View.GONE - viewHolder.current.clearAnimationDrawable() + viewHolder.current.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } val targetUrl = photo.getUrlForSize(PhotoSize.X, false) @@ -98,7 +99,7 @@ class LocalServerPhotosAdapter(private val mContext: Context, private var data: val photoImageView: AspectRatioImageView = itemView.findViewById(R.id.imageView) val tvDate: TextView = itemView.findViewById(R.id.vk_photo_item_date) val bottomTop: ViewGroup = itemView.findViewById(R.id.vk_photo_item_top) - val current: RLottieImageView = itemView.findViewById(R.id.current) + val current: ThorVGLottieView = itemView.findViewById(R.id.current) } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/chat/ChatFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/chat/ChatFragment.kt index 16e3ee06f..fbc30e962 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/chat/ChatFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/chat/ChatFragment.kt @@ -136,7 +136,7 @@ import dev.ragnarok.fenrir.view.emoji.BotKeyboardView import dev.ragnarok.fenrir.view.emoji.EmojiconTextView import dev.ragnarok.fenrir.view.emoji.EmojiconsPopup import dev.ragnarok.fenrir.view.emoji.StickersKeyWordsAdapter -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView import me.minetsh.imaging.IMGEditActivity import java.io.File import java.lang.ref.WeakReference @@ -162,7 +162,7 @@ class ChatFragment : PlaceSupportMvpFragment(), IChatV private var inputViewController: InputViewController? = null private var emptyText: TextView? = null - private var emptyAnimation: RLottieImageView? = null + private var emptyAnimation: ThorVGLottieView? = null private var pinnedView: View? = null private var pinnedAvatar: ImageView? = null @@ -1843,8 +1843,6 @@ class ChatFragment : PlaceSupportMvpFragment(), IChatV if (visible) { emptyAnimation?.fromRes( dev.ragnarok.fenrir_common.R.raw.valknut, - Utils.dp(120f), - Utils.dp(120f), intArrayOf( 0x333333, CurrentTheme.getColorPrimary(requireActivity()), @@ -1852,9 +1850,12 @@ class ChatFragment : PlaceSupportMvpFragment(), IChatV CurrentTheme.getColorSecondary(requireActivity()) ) ) - emptyAnimation?.playAnimation() + emptyAnimation?.startAnimation() } else { - emptyAnimation?.clearAnimationDrawable() + emptyAnimation?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/chat/MessagesAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/chat/MessagesAdapter.kt index 8ff1271de..157d6903f 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/chat/MessagesAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/chat/MessagesAdapter.kt @@ -49,7 +49,7 @@ import dev.ragnarok.fenrir.view.ReactionContainer import dev.ragnarok.fenrir.view.emoji.BotKeyboardView import dev.ragnarok.fenrir.view.emoji.BotKeyboardView.BotKeyboardViewDelegate import dev.ragnarok.fenrir.view.emoji.EmojiconTextView -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView import java.text.SimpleDateFormat import java.util.Date @@ -127,8 +127,7 @@ class MessagesAdapter( holder.sticker.fromNet( sticker.getAnimationByType(if (isNightSticker) "dark" else "light"), Utils.createOkHttp(Constants.GIF_TIMEOUT, true), - Utils.dp(128f), - Utils.dp(128f) + true ) } else { val image = sticker?.getImage(256, isNightSticker) @@ -639,7 +638,7 @@ class MessagesAdapter( private class StickerMessageHolder(itemView: View) : BaseMessageHolder(itemView) { - val sticker: RLottieImageView = itemView.findViewById(R.id.sticker) + val sticker: ThorVGLottieView = itemView.findViewById(R.id.sticker) val attachmentsRoot: View = itemView.findViewById(R.id.item_message_attachment_container) val attachmentsHolder: AttachmentsHolder = AttachmentsHolder() val forwardMessagesRoot: ViewGroup = itemView.findViewById(R.id.forward_messages) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/localjsontochat/LocalJsonToChatFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/localjsontochat/LocalJsonToChatFragment.kt index 5b9a65eb1..c2cf1af28 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/localjsontochat/LocalJsonToChatFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/localjsontochat/LocalJsonToChatFragment.kt @@ -37,13 +37,13 @@ import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.util.coroutines.CancelableJob import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.delayTaskFlow import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.toMain -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class LocalJsonToChatFragment : PlaceSupportMvpFragment(), ILocalJsonToChatView, OnMessageActionListener { private var mEmpty: TextView? = null - private var mLoadingProgressBar: RLottieImageView? = null + private var mLoadingProgressBar: ThorVGLottieView? = null private var mLoadingProgressBarDispose = CancelableJob() private var mLoadingProgressBarLoaded = false private var mAdapter: MessagesAdapter? = null @@ -225,13 +225,19 @@ class LocalJsonToChatFragment : val k = ObjectAnimator.ofFloat(mLoadingProgressBar, View.ALPHA, 0.0f).setDuration(1000) k.addListener(object : StubAnimatorListener() { override fun onAnimationEnd(animation: Animator) { - mLoadingProgressBar?.clearAnimationDrawable() + mLoadingProgressBar?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) mLoadingProgressBar?.visibility = View.GONE mLoadingProgressBar?.alpha = 1f } override fun onAnimationCancel(animation: Animator) { - mLoadingProgressBar?.clearAnimationDrawable() + mLoadingProgressBar?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) mLoadingProgressBar?.visibility = View.GONE mLoadingProgressBar?.alpha = 1f } @@ -243,8 +249,6 @@ class LocalJsonToChatFragment : mLoadingProgressBar?.visibility = View.VISIBLE mLoadingProgressBar?.fromRes( dev.ragnarok.fenrir_common.R.raw.loading, - Utils.dp(100F), - Utils.dp(100F), intArrayOf( 0x000000, CurrentTheme.getColorPrimary(requireActivity()), @@ -252,7 +256,7 @@ class LocalJsonToChatFragment : CurrentTheme.getColorSecondary(requireActivity()) ) ) - mLoadingProgressBar?.playAnimation() + mLoadingProgressBar?.startAnimation() } } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/photos/vkphotos/BigVKPhotosAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/photos/vkphotos/BigVKPhotosAdapter.kt index 2748d51d7..f8273e62d 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/photos/vkphotos/BigVKPhotosAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/photos/vkphotos/BigVKPhotosAdapter.kt @@ -25,7 +25,7 @@ import dev.ragnarok.fenrir.util.AppTextUtils import dev.ragnarok.fenrir.util.Logger import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.view.CircleRoadProgress -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class BigVKPhotosAdapter( private val mContext: Context, @@ -126,15 +126,16 @@ class BigVKPhotosAdapter( holder.current.visibility = View.VISIBLE holder.current.fromRes( dev.ragnarok.fenrir_common.R.raw.donater_fire, - Utils.dp(100f), - Utils.dp(100f), intArrayOf(0xFF812E, CurrentTheme.getColorPrimary(mContext)), true ) - holder.current.playAnimation() + holder.current.startAnimation() } else { holder.current.visibility = View.GONE - holder.current.clearAnimationDrawable() + holder.current.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } holder.setSelected(photoWrapper.isSelected) @@ -263,7 +264,7 @@ class BigVKPhotosAdapter( val ivLike: ImageView = itemView.findViewById(R.id.vk_photo_item_like) val ivComment: ImageView = itemView.findViewById(R.id.vk_photo_item_comment) val ivDownload: ImageView = itemView.findViewById(R.id.is_downloaded) - val current: RLottieImageView = itemView.findViewById(R.id.current) + val current: ThorVGLottieView = itemView.findViewById(R.id.current) fun setSelected(selected: Boolean) { index.visibility = if (selected) View.VISIBLE else View.GONE darkView.visibility = if (selected) View.VISIBLE else View.GONE diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/search/photosearch/SearchPhotosAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/search/photosearch/SearchPhotosAdapter.kt index adf536a70..3fdc74484 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/search/photosearch/SearchPhotosAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/search/photosearch/SearchPhotosAdapter.kt @@ -16,7 +16,7 @@ import dev.ragnarok.fenrir.picasso.PicassoInstance.Companion.with import dev.ragnarok.fenrir.settings.CurrentTheme import dev.ragnarok.fenrir.util.AppTextUtils import dev.ragnarok.fenrir.util.Utils -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class SearchPhotosAdapter( private val mContext: Context, @@ -62,15 +62,16 @@ class SearchPhotosAdapter( holder.current.visibility = View.VISIBLE holder.current.fromRes( dev.ragnarok.fenrir_common.R.raw.donater_fire, - Utils.dp(100f), - Utils.dp(100f), intArrayOf(0xFF812E, colorPrimary), true ) - holder.current.playAnimation() + holder.current.startAnimation() } else { holder.current.visibility = View.GONE - holder.current.clearAnimationDrawable() + holder.current.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } holder.tvLike.text = AppTextUtils.getCounterWithK(photo.likesCount) @@ -125,7 +126,7 @@ class SearchPhotosAdapter( val ivLike: ImageView = itemView.findViewById(R.id.vk_photo_item_like) val ivComment: ImageView = itemView.findViewById(R.id.vk_photo_item_comment) val ivDownload: ImageView = itemView.findViewById(R.id.is_downloaded) - val current: RLottieImageView = itemView.findViewById(R.id.current) + val current: ThorVGLottieView = itemView.findViewById(R.id.current) fun setSelected(selected: Boolean) { index.visibility = if (selected) View.VISIBLE else View.GONE darkView.visibility = if (selected) View.VISIBLE else View.GONE diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/theme/ThemeAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/theme/ThemeAdapter.kt index d0b0e8090..9f0e8c6ff 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/theme/ThemeAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/theme/ThemeAdapter.kt @@ -16,8 +16,7 @@ import dev.ragnarok.fenrir.settings.CurrentTheme.getColorSecondary import dev.ragnarok.fenrir.settings.CurrentTheme.getColorWhiteContrastFix import dev.ragnarok.fenrir.settings.Settings.get import dev.ragnarok.fenrir.settings.theme.ThemeValue -import dev.ragnarok.fenrir.util.Utils -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class ThemeAdapter(private var data: List, context: Context) : RecyclerView.Adapter() { @@ -75,8 +74,6 @@ class ThemeAdapter(private var data: List, context: Context) : if (isSelected) { holder.selected.fromRes( dev.ragnarok.fenrir_common.R.raw.theme_selected, - Utils.dp(120f), - Utils.dp(120f), intArrayOf( 0x333333, getColorWhiteContrastFix(holder.selected.context), @@ -86,9 +83,12 @@ class ThemeAdapter(private var data: List, context: Context) : getColorSecondary(holder.selected.context) ) ) - holder.selected.playAnimation() + holder.selected.startAnimation() } else { - holder.selected.clearAnimationDrawable() + holder.selected.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } else { if (isSelected) { @@ -113,8 +113,6 @@ class ThemeAdapter(private var data: List, context: Context) : if (isSelected) { holder.selected.fromRes( dev.ragnarok.fenrir_common.R.raw.theme_selected, - Utils.dp(120f), - Utils.dp(120f), intArrayOf( 0x333333, getColorWhiteContrastFix(holder.selected.context), @@ -124,9 +122,12 @@ class ThemeAdapter(private var data: List, context: Context) : getColorSecondary(holder.selected.context) ) ) - holder.selected.playAnimation() + holder.selected.startAnimation() } else { - holder.selected.clearAnimationDrawable() + holder.selected.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } else { if (isSelected) { @@ -169,7 +170,7 @@ class ThemeAdapter(private var data: List, context: Context) : internal class ThemeHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val primary: ImageView = itemView.findViewById(R.id.theme_icon_primary) val secondary: ImageView = itemView.findViewById(R.id.theme_icon_secondary) - val selected: RLottieImageView = itemView.findViewById(R.id.selected) + val selected: ThorVGLottieView = itemView.findViewById(R.id.selected) val gradient: ImageView = itemView.findViewById(R.id.theme_icon_gradient) val clicked: ViewGroup = itemView.findViewById(R.id.theme_type) val title: TextView = itemView.findViewById(R.id.item_title) @@ -177,7 +178,7 @@ class ThemeAdapter(private var data: List, context: Context) : } internal class SpecialThemeHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - val selected: RLottieImageView = itemView.findViewById(R.id.selected) + val selected: ThorVGLottieView = itemView.findViewById(R.id.selected) val clicked: ViewGroup = itemView.findViewById(R.id.theme_type) val title: TextView = itemView.findViewById(R.id.item_title) val special_title: TextView = itemView.findViewById(R.id.special_text) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/videos/videopreview/VideoPreviewFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/videos/videopreview/VideoPreviewFragment.kt index 570238646..512b1c54f 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/videos/videopreview/VideoPreviewFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/videos/videopreview/VideoPreviewFragment.kt @@ -309,7 +309,7 @@ class VideoPreviewFragment : BaseMvpFragment> : private var mUploadAdapter: DocsUploadAdapter? = null private var mUploadRoot: View? = null - protected fun setupPaganContent(Runes: View?, paganSymbol: RLottieImageView?) { + protected fun setupPaganContent(Runes: View?, paganSymbol: ThorVGLottieView?) { Runes?.visibility = if (Settings.get() .main().isRunes_show ) View.VISIBLE else View.GONE @@ -133,14 +133,12 @@ abstract class AbsWallFragment> : if (pic.isAnimation) { paganSymbol?.fromRes( pic.lottieRes, - dp(pic.lottie_widthHeight), - dp(pic.lottie_widthHeight), pic.lottie_replacement, pic.lottie_useMoveColor ) - paganSymbol?.playAnimation() + paganSymbol?.startAnimation() } else { paganSymbol?.setImageBitmap( - ThorVGRender.createBitmap( + ThorVGSVGRender.createBitmap( pic.iconRes, dp(pic.icon_width), dp(pic.icon_height) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/groupwall/GroupWallFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/groupwall/GroupWallFragment.kt index 69a0c28f1..bd3d05931 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/groupwall/GroupWallFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/groupwall/GroupWallFragment.kt @@ -65,11 +65,10 @@ import dev.ragnarok.fenrir.settings.CurrentTheme import dev.ragnarok.fenrir.settings.Settings import dev.ragnarok.fenrir.toColor import dev.ragnarok.fenrir.util.Utils -import dev.ragnarok.fenrir.util.Utils.dp import dev.ragnarok.fenrir.util.Utils.getVerifiedColor import dev.ragnarok.fenrir.util.Utils.setBackgroundTint import dev.ragnarok.fenrir.view.ProfileCoverDrawable -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView import kotlin.math.abs class GroupWallFragment : AbsWallFragment(), IGroupWallView { @@ -136,7 +135,7 @@ class GroupWallFragment : AbsWallFragment(), val donate_anim = Settings.get().main().donate_anim_set if (donate_anim > 0 && community.isDonated) { mHeaderHolder?.bDonate?.visibility = View.VISIBLE - mHeaderHolder?.bDonate?.setAutoRepeat(true) + mHeaderHolder?.bDonate?.setRepeat(true) if (donate_anim == 2) { val cur = Settings.get().ui().mainThemeKey if ("fire" == cur || "yellow_violet" == cur) { @@ -145,8 +144,6 @@ class GroupWallFragment : AbsWallFragment(), setBackgroundTint(mHeaderHolder?.ivVerified, "#df9d00".toColor()) mHeaderHolder?.bDonate?.fromRes( dev.ragnarok.fenrir_common.R.raw.donater_fire, - dp(100f), - dp(100f) ) } else { mHeaderHolder?.tvName?.setTextColor(CurrentTheme.getColorPrimary(requireActivity())) @@ -161,8 +158,6 @@ class GroupWallFragment : AbsWallFragment(), ) mHeaderHolder?.bDonate?.fromRes( dev.ragnarok.fenrir_common.R.raw.donater_fire, - dp(100f), - dp(100f), intArrayOf(0xFF812E, CurrentTheme.getColorPrimary(requireActivity())), true ) @@ -170,8 +165,6 @@ class GroupWallFragment : AbsWallFragment(), } else { mHeaderHolder?.bDonate?.fromRes( dev.ragnarok.fenrir_common.R.raw.donater, - dp(100f), - dp(100f), intArrayOf( 0xffffff, CurrentTheme.getColorPrimary(requireActivity()), @@ -180,7 +173,7 @@ class GroupWallFragment : AbsWallFragment(), ) ) } - mHeaderHolder?.bDonate?.playAnimation() + mHeaderHolder?.bDonate?.startAnimation() } else { mHeaderHolder?.bDonate?.setImageDrawable(null) mHeaderHolder?.bDonate?.visibility = View.GONE @@ -212,20 +205,18 @@ class GroupWallFragment : AbsWallFragment(), if (community.isBlacklisted) { mHeaderHolder?.blacklisted?.visibility = View.VISIBLE if (FenrirNative.isNativeLoaded) { - mHeaderHolder?.blacklisted?.fromRes( - dev.ragnarok.fenrir_common.R.raw.skull, - dp(48f), - dp(48f), - null - ) - mHeaderHolder?.blacklisted?.playAnimation() + mHeaderHolder?.blacklisted?.fromRes(dev.ragnarok.fenrir_common.R.raw.skull) + mHeaderHolder?.blacklisted?.startAnimation() } else { mHeaderHolder?.blacklisted?.setImageResource(R.drawable.audio_died) mHeaderHolder?.blacklisted?.setColorFilter("#AAFF0000".toColor()) } } else { mHeaderHolder?.blacklisted?.visibility = View.GONE - mHeaderHolder?.blacklisted?.clearAnimationDrawable() + mHeaderHolder?.blacklisted?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } mHeaderHolder?.blacklisted?.visibility = if (community.isBlacklisted) View.VISIBLE else View.GONE @@ -569,11 +560,11 @@ class GroupWallFragment : AbsWallFragment(), } private inner class GroupHeaderHolder(root: View) { - val blacklisted: RLottieImageView = root.findViewById(R.id.item_blacklisted) + val blacklisted: ThorVGLottieView = root.findViewById(R.id.item_blacklisted) val vgCover: ViewGroup = root.findViewById(R.id.cover) val ivAvatar: ImageView = root.findViewById(R.id.header_group_avatar) val ivVerified: ImageView = root.findViewById(R.id.item_verified) - val bDonate: RLottieImageView = root.findViewById(R.id.donated_anim) + val bDonate: ThorVGLottieView = root.findViewById(R.id.donated_anim) val tvName: TextView = root.findViewById(R.id.header_group_name) val tvStatus: TextView = root.findViewById(R.id.header_group_status) val tvAudioStatus: ImageView = root.findViewById(R.id.fragment_group_audio) @@ -599,7 +590,7 @@ class GroupWallFragment : AbsWallFragment(), val mFiltersAdapter: HorizontalOptionsAdapter val mMenuAdapter: HorizontalMenuAdapter val menuList: RecyclerView = root.findViewById(R.id.menu_recyclerview) - val paganSymbol: RLottieImageView = root.findViewById(R.id.pagan_symbol) + val paganSymbol: ThorVGLottieView = root.findViewById(R.id.pagan_symbol) val Runes: View = root.findViewById(R.id.runes_container) init { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/groupwall/GroupWallPresenter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/groupwall/GroupWallPresenter.kt index ede531322..83737047e 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/groupwall/GroupWallPresenter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/groupwall/GroupWallPresenter.kt @@ -506,11 +506,17 @@ class GroupWallPresenter( fun fireSubscribe() { appendJob(wallsRepository.subscribe(accountId, ownerId) .fromIOToMain({ onExecuteComplete() }) { t -> onExecuteError(t) }) + + appendJob(storiesInteractor.subscribe(accountId, ownerId) + .fromIOToMain({ onExecuteComplete() }) { t -> onExecuteError(t) }) } fun fireUnSubscribe() { appendJob(wallsRepository.unsubscribe(accountId, ownerId) .fromIOToMain({ onExecuteComplete() }) { t -> onExecuteError(t) }) + + appendJob(storiesInteractor.unsubscribe(accountId, ownerId) + .fromIOToMain({ onExecuteComplete() }) { t -> onExecuteError(t) }) } fun fireAddToBookmarksClick() { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/userwall/UserWallFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/userwall/UserWallFragment.kt index 319b45f0f..68ce6b5b0 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/userwall/UserWallFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/userwall/UserWallFragment.kt @@ -66,7 +66,6 @@ import dev.ragnarok.fenrir.toColor import dev.ragnarok.fenrir.util.InputTextDialog import dev.ragnarok.fenrir.util.UserInfoResolveUtil import dev.ragnarok.fenrir.util.Utils -import dev.ragnarok.fenrir.util.Utils.dp import dev.ragnarok.fenrir.util.Utils.firstNonEmptyString import dev.ragnarok.fenrir.util.Utils.getVerifiedColor import dev.ragnarok.fenrir.util.Utils.setBackgroundTint @@ -74,7 +73,7 @@ import dev.ragnarok.fenrir.util.ViewUtils.getOnlineIcon import dev.ragnarok.fenrir.util.toast.CustomToast import dev.ragnarok.fenrir.view.OnlineView import dev.ragnarok.fenrir.view.ProfileCoverDrawable -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView import java.io.File class UserWallFragment : AbsWallFragment(), IUserWallView { @@ -156,18 +155,14 @@ class UserWallFragment : AbsWallFragment(), IU val donate_anim = Settings.get().main().donate_anim_set if (donate_anim > 0 && user.isDonated) { mHeaderHolder?.bDonate?.visibility = View.VISIBLE - mHeaderHolder?.bDonate?.setAutoRepeat(true) + mHeaderHolder?.bDonate?.setRepeat(true) if (donate_anim == 2) { val cur = Settings.get().ui().mainThemeKey if ("fire" == cur || "yellow_violet" == cur) { mHeaderHolder?.tvName?.setTextColor("#df9d00".toColor()) mHeaderHolder?.tvScreenName?.setTextColor("#df9d00".toColor()) setBackgroundTint(mHeaderHolder?.ivVerified, "#df9d00".toColor()) - mHeaderHolder?.bDonate?.fromRes( - dev.ragnarok.fenrir_common.R.raw.donater_fire, - dp(100f), - dp(100f) - ) + mHeaderHolder?.bDonate?.fromRes(dev.ragnarok.fenrir_common.R.raw.donater_fire) } else { mHeaderHolder?.tvName?.setTextColor(CurrentTheme.getColorPrimary(requireActivity())) mHeaderHolder?.tvScreenName?.setTextColor( @@ -181,8 +176,6 @@ class UserWallFragment : AbsWallFragment(), IU ) mHeaderHolder?.bDonate?.fromRes( dev.ragnarok.fenrir_common.R.raw.donater_fire, - dp(100f), - dp(100f), intArrayOf(0xFF812E, CurrentTheme.getColorPrimary(requireActivity())), true ) @@ -190,8 +183,6 @@ class UserWallFragment : AbsWallFragment(), IU } else { mHeaderHolder?.bDonate?.fromRes( dev.ragnarok.fenrir_common.R.raw.donater, - dp(100f), - dp(100f), intArrayOf( 0xffffff, CurrentTheme.getColorPrimary(requireActivity()), @@ -200,7 +191,7 @@ class UserWallFragment : AbsWallFragment(), IU ) ) } - mHeaderHolder?.bDonate?.playAnimation() + mHeaderHolder?.bDonate?.startAnimation() } else { mHeaderHolder?.bDonate?.setImageDrawable(null) mHeaderHolder?.bDonate?.visibility = View.GONE @@ -226,20 +217,18 @@ class UserWallFragment : AbsWallFragment(), IU if (user.blacklisted) { mHeaderHolder?.blacklisted?.visibility = View.VISIBLE if (FenrirNative.isNativeLoaded) { - mHeaderHolder?.blacklisted?.fromRes( - dev.ragnarok.fenrir_common.R.raw.skull, - dp(48f), - dp(48f), - null - ) - mHeaderHolder?.blacklisted?.playAnimation() + mHeaderHolder?.blacklisted?.fromRes(dev.ragnarok.fenrir_common.R.raw.skull) + mHeaderHolder?.blacklisted?.startAnimation() } else { mHeaderHolder?.blacklisted?.setImageResource(R.drawable.audio_died) mHeaderHolder?.blacklisted?.setColorFilter("#AAFF0000".toColor()) } } else { mHeaderHolder?.blacklisted?.visibility = View.GONE - mHeaderHolder?.blacklisted?.clearAnimationDrawable() + mHeaderHolder?.blacklisted?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } mHeaderHolder?.blacklisted?.visibility = if (user.blacklisted) View.VISIBLE else View.GONE } @@ -700,7 +689,7 @@ class UserWallFragment : AbsWallFragment(), IU val tvAudioStatus: ImageView = root.findViewById(R.id.fragment_user_profile_audio) val tvLastSeen: TextView = root.findViewById(R.id.fragment_user_profile_activity) val ivOnline: OnlineView = root.findViewById(R.id.header_navi_menu_online) - val blacklisted: RLottieImageView = root.findViewById(R.id.item_blacklisted) + val blacklisted: ThorVGLottieView = root.findViewById(R.id.item_blacklisted) val bFriends: TextView = root.findViewById(R.id.fragment_user_profile_bfriends) val bGroups: TextView = root.findViewById(R.id.fragment_user_profile_bgroups) val bPhotos: TextView = root.findViewById(R.id.fragment_user_profile_bphotos) @@ -714,8 +703,8 @@ class UserWallFragment : AbsWallFragment(), IU root.findViewById(R.id.header_user_profile_fab_message) val fabMoreInfo: FloatingActionButton = root.findViewById(R.id.info_btn) val bPrimaryAction: MaterialButton = root.findViewById(R.id.subscribe_btn) - val bDonate: RLottieImageView = root.findViewById(R.id.donated_anim) - val paganSymbol: RLottieImageView = root.findViewById(R.id.pagan_symbol) + val bDonate: ThorVGLottieView = root.findViewById(R.id.donated_anim) + val paganSymbol: ThorVGLottieView = root.findViewById(R.id.pagan_symbol) val Runes: View = root.findViewById(R.id.runes_container) val mPostFilterAdapter: HorizontalOptionsAdapter diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/userwall/UserWallPresenter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/userwall/UserWallPresenter.kt index 31bb5a82a..66eaa80f1 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/userwall/UserWallPresenter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/userwall/UserWallPresenter.kt @@ -480,11 +480,17 @@ class UserWallPresenter( fun fireSubscribe() { appendJob(wallsRepository.subscribe(accountId, ownerId) .fromIOToMain({ onExecuteComplete() }) { t -> onExecuteError(t) }) + + appendJob(storiesInteractor.subscribe(accountId, ownerId) + .fromIOToMain({ onExecuteComplete() }) { t -> onExecuteError(t) }) } fun fireUnSubscribe() { appendJob(wallsRepository.unsubscribe(accountId, ownerId) .fromIOToMain({ onExecuteComplete() }) { t -> onExecuteError(t) }) + + appendJob(storiesInteractor.unsubscribe(accountId, ownerId) + .fromIOToMain({ onExecuteComplete() }) { t -> onExecuteError(t) }) } private fun executeAddToFriendsRequest(text: String?, follow: Boolean) { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/wallpost/WallPostFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/wallpost/WallPostFragment.kt index cc2dad777..42c053ce6 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/wallpost/WallPostFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/wallpost/WallPostFragment.kt @@ -44,7 +44,6 @@ import dev.ragnarok.fenrir.settings.CurrentTheme import dev.ragnarok.fenrir.settings.Settings import dev.ragnarok.fenrir.util.AppTextUtils.getDateFromUnixTime import dev.ragnarok.fenrir.util.PostDownload -import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.util.ViewUtils.displayAvatar import dev.ragnarok.fenrir.util.coroutines.CancelableJob import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.delayTaskFlow @@ -52,7 +51,7 @@ import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.toMain import dev.ragnarok.fenrir.util.toast.CustomToast import dev.ragnarok.fenrir.view.CircleCounterButton import dev.ragnarok.fenrir.view.emoji.EmojiconTextView -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class WallPostFragment : PlaceSupportMvpFragment(), EmojiconTextView.OnHashTagClickListener, IWallPostView, MenuProvider { @@ -68,7 +67,7 @@ class WallPostFragment : PlaceSupportMvpFragment(R.id.item_progress_text).text = if (messageId != 0) context.getString(messageId) else message - val anim: RLottieImageView = root.findViewById(R.id.lottie_animation) + val anim: ThorVGLottieView = root.findViewById(R.id.lottie_animation) anim.fromRes( dev.ragnarok.fenrir_common.R.raw.s_loading, - dp(180f), - dp(180f), intArrayOf( 0x333333, CurrentTheme.getColorPrimary(context), @@ -163,7 +160,7 @@ class SpotsDialog internal constructor( CurrentTheme.getColorSecondary(context) ) ) - anim.playAnimation() + anim.startAnimation() return MaterialAlertDialogBuilder(context).setView(root) .setCancelable(cancelable) .setOnCancelListener(cancelListener).create() diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/AudioContainer.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/AudioContainer.kt index d12c3391c..c68a45763 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/AudioContainer.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/AudioContainer.kt @@ -66,7 +66,7 @@ import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.sharedFlowToMain import dev.ragnarok.fenrir.util.hls.M3U8 import dev.ragnarok.fenrir.util.toast.CustomSnackbars import dev.ragnarok.fenrir.util.toast.CustomToast.Companion.createCustomToast -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class AudioContainer : LinearLayout { private val mAudioInteractor: IAudioInteractor by lazy { @@ -781,7 +781,7 @@ class AudioContainer : LinearLayout { val selectionView: MaterialCardView = root.findViewById(R.id.item_audio_selection) private val isSelectedView: MaterialCardView = root.findViewById(R.id.item_audio_select_add) private val animationAdapter: Animator.AnimatorListener - val visual: RLottieImageView + val visual: ThorVGLottieView var animator: ObjectAnimator? = null fun startSomeAnimation() { selectionView.setCardBackgroundColor(CurrentTheme.getColorSecondary(context)) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/LoadMoreFooterHelper.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/LoadMoreFooterHelper.kt index 15f3c9e0a..4bee4ef1c 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/LoadMoreFooterHelper.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/LoadMoreFooterHelper.kt @@ -6,8 +6,7 @@ import dev.ragnarok.fenrir.R import dev.ragnarok.fenrir.model.LoadMoreState import dev.ragnarok.fenrir.settings.CurrentTheme import dev.ragnarok.fenrir.settings.Settings -import dev.ragnarok.fenrir.util.Utils -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class LoadMoreFooterHelper { var callback: Callback? = null @@ -28,14 +27,12 @@ class LoadMoreFooterHelper { LoadMoreState.END_OF_LIST -> { holder?.tvEndOfList?.visibility = View.VISIBLE - holder?.tvEndOfList?.setAutoRepeat(false) + holder?.tvEndOfList?.setRepeat(false) when (animation_id) { 0 -> { - holder?.tvEndOfList?.setAutoRepeat(false) + holder?.tvEndOfList?.setRepeat(false) holder?.tvEndOfList?.fromRes( dev.ragnarok.fenrir_common.R.raw.end_list_succes, - Utils.dp(40f), - Utils.dp(40f), intArrayOf( 0xffffff, CurrentTheme.getColorControlNormal( holder?.bLoadMore?.context @@ -45,11 +42,9 @@ class LoadMoreFooterHelper { } 1 -> { - holder?.tvEndOfList?.setAutoRepeat(false) + holder?.tvEndOfList?.setRepeat(false) holder?.tvEndOfList?.fromRes( dev.ragnarok.fenrir_common.R.raw.end_list_balls, - Utils.dp(40f), - Utils.dp(40f), intArrayOf( 0xffffff, CurrentTheme.getColorControlNormal( holder?.bLoadMore?.context @@ -59,11 +54,9 @@ class LoadMoreFooterHelper { } else -> { - holder?.tvEndOfList?.setAutoRepeat(true) + holder?.tvEndOfList?.setRepeat(true) holder?.tvEndOfList?.fromRes( dev.ragnarok.fenrir_common.R.raw.end_list_wave, - Utils.dp(80f), - Utils.dp(40f), intArrayOf( 0x777777, CurrentTheme.getColorPrimary( holder?.bLoadMore?.context @@ -74,7 +67,7 @@ class LoadMoreFooterHelper { ) } } - holder?.tvEndOfList?.playAnimation() + holder?.tvEndOfList?.startAnimation() holder?.bLoadMore?.visibility = View.INVISIBLE holder?.progress?.visibility = View.INVISIBLE } @@ -98,7 +91,7 @@ class LoadMoreFooterHelper { val container: View = root.findViewById(R.id.footer_load_more_root) val progress: CircularProgressIndicator = root.findViewById(R.id.footer_load_more_progress) val bLoadMore: View = root.findViewById(R.id.footer_load_more_run) - val tvEndOfList: RLottieImageView = root.findViewById(R.id.footer_load_more_end_of_list) + val tvEndOfList: ThorVGLottieView = root.findViewById(R.id.footer_load_more_end_of_list) } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/PhotosViewHelper.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/PhotosViewHelper.kt index 81e9f0f2d..23fb30292 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/PhotosViewHelper.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/PhotosViewHelper.kt @@ -97,7 +97,8 @@ class PhotosViewHelper internal constructor( holder.vgVideo.fromNet( (video.ownerId.toString() + "_" + image.attachment.id.toString()), video.trailer, - Utils.createOkHttp(Constants.GIF_TIMEOUT, true) + Utils.createOkHttp(Constants.GIF_TIMEOUT, true), + true ) } else if (url.nonNullNoEmpty()) { PicassoInstance.with() @@ -259,7 +260,8 @@ class PhotosViewHelper internal constructor( holder.vgPhoto.fromNet( ((image.attachment as Document).ownerId.toString() + "_" + image.attachment.id.toString()), image.attachment.videoPreview?.src, - Utils.createOkHttp(Constants.GIF_TIMEOUT, true) + Utils.createOkHttp(Constants.GIF_TIMEOUT, true), + true ) } else if (url.nonNullNoEmpty()) { PicassoInstance.with() diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/ProgressButton.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/ProgressButton.kt index 99dc03de4..bc1fc3c24 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/ProgressButton.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/ProgressButton.kt @@ -7,13 +7,13 @@ import android.widget.FrameLayout import com.google.android.material.button.MaterialButton import dev.ragnarok.fenrir.R import dev.ragnarok.fenrir.module.FenrirNative -import dev.ragnarok.fenrir.module.rlottie.RLottieDrawable +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable import dev.ragnarok.fenrir.settings.CurrentTheme import dev.ragnarok.fenrir.util.Utils class ProgressButton : FrameLayout { private var mButton: MaterialButton? = null - private var animatedDrawable: RLottieDrawable? = null + private var animatedDrawable: ThorVGLottieDrawable? = null private var mProgressNow = true constructor(context: Context) : super(context) { @@ -59,16 +59,25 @@ class ProgressButton : FrameLayout { mButton?.text = charSequence } + private fun clearAnimationDrawable() { + if (mButton?.icon is ThorVGLottieDrawable) { + (mButton?.icon as ThorVGLottieDrawable).release() + } + mButton?.icon = null + if (animatedDrawable != null) { + animatedDrawable?.release() + animatedDrawable = null + } + } + override fun onAttachedToWindow() { super.onAttachedToWindow() - animatedDrawable?.setCurrentParentView(this) - animatedDrawable?.start() + resolveViews() } override fun onDetachedFromWindow() { super.onDetachedFromWindow() - animatedDrawable?.stop() - animatedDrawable?.setCurrentParentView(null) + clearAnimationDrawable() } fun onButtonClick(listener: OnClickListener) { @@ -79,20 +88,13 @@ class ProgressButton : FrameLayout { mButton?.setOnLongClickListener(listener) } - private fun setAnimation(rLottieDrawable: RLottieDrawable) { - mButton?.icon = null - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - rLottieDrawable.setAutoRepeat(1) - rLottieDrawable.setAllowDecodeSingleFrame(true) - rLottieDrawable.setCurrentParentView(this) - rLottieDrawable.start() - animatedDrawable = rLottieDrawable - mButton?.icon = rLottieDrawable + private fun setAnimation(thorVGLottieDrawable: ThorVGLottieDrawable) { + clearAnimationDrawable() + thorVGLottieDrawable.setRepeatCount(Int.MAX_VALUE) + thorVGLottieDrawable.setSize(Utils.dp(24f), Utils.dp(24f)) + thorVGLottieDrawable.start() + animatedDrawable = thorVGLottieDrawable + mButton?.icon = thorVGLottieDrawable } private fun resolveViews() { @@ -101,11 +103,8 @@ class ProgressButton : FrameLayout { mButton?.setIconResource(R.drawable.ic_progress_button_icon_vector) } else { setAnimation( - RLottieDrawable( + ThorVGLottieDrawable( dev.ragnarok.fenrir_common.R.raw.loading, - Utils.dp(40f), - Utils.dp(40f), - false, intArrayOf( 0x000000, CurrentTheme.getColorPrimary(context), @@ -117,13 +116,7 @@ class ProgressButton : FrameLayout { ) } } else { - mButton?.icon = null - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } + clearAnimationDrawable() } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/ReactionContainer.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/ReactionContainer.kt index ab3c6f727..43bdf62a7 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/ReactionContainer.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/ReactionContainer.kt @@ -12,7 +12,7 @@ import dev.ragnarok.fenrir.model.ReactionWithAsset import dev.ragnarok.fenrir.settings.CurrentTheme import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.util.ViewUtils -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class ReactionContainer : RowLayout { private var colorPrimary = 0 @@ -67,7 +67,7 @@ class ReactionContainer : RowLayout { } visibility = VISIBLE val i = reactionsData.size - childCount - for (j in 0 until i) { + (0 until i).forEach { j -> val itemView = LayoutInflater.from(context).inflate(R.layout.item_reaction, this, false) val holder = ReactionHolder(itemView) @@ -95,8 +95,7 @@ class ReactionContainer : RowLayout { reactionHolder.ivReaction.fromNet( reaction.small_animation, Utils.createOkHttp(Constants.GIF_TIMEOUT, true), - Utils.dp(28f), - Utils.dp(28f) + !isEdit ) } root.setOnClickListener { @@ -115,6 +114,6 @@ class ReactionContainer : RowLayout { private inner class ReactionHolder(root: View) { val tvCount: TextView = root.findViewById(R.id.count) - val ivReaction: RLottieImageView = root.findViewById(R.id.reaction) + val ivReaction: ThorVGLottieView = root.findViewById(R.id.reaction) } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/TouchImageView.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/TouchImageView.kt index e11be54ba..5cf9882b1 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/TouchImageView.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/TouchImageView.kt @@ -25,12 +25,16 @@ import android.view.animation.LinearInterpolator import android.widget.OverScroller import androidx.appcompat.widget.AppCompatImageView import com.squareup.picasso3.Rotatable +import dev.ragnarok.fenrir.Constants import dev.ragnarok.fenrir.R import dev.ragnarok.fenrir.getParcelableCompat import dev.ragnarok.fenrir.getSerializableCompat import dev.ragnarok.fenrir.module.FenrirNative import dev.ragnarok.fenrir.module.animation.AnimatedFileDrawable +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.LoadedFrom +import dev.ragnarok.fenrir.nonNullNoEmpty import dev.ragnarok.fenrir.picasso.PicassoInstance +import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.util.coroutines.CancelableJob import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.fromIOToMain import dev.ragnarok.fenrir.view.natives.animation.AnimationNetworkCache @@ -48,12 +52,27 @@ import kotlin.math.min @SuppressLint("ClickableViewAccessibility") @Suppress("unused") -open class TouchImageView @JvmOverloads constructor( +class TouchImageView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : AppCompatImageView(context, attrs, defStyle) { + //%%~Animation + private var mDisposable = CancelableJob() + private val cache: AnimationNetworkCache = AnimationNetworkCache(context) + private var animatedDrawable: AnimatedFileDrawable? = null + private var attachedToWindow = false + //~%%Animation + + @LoadedFrom + private var loadedFrom = LoadedFrom.NO + private var filePathTmp: String? = null + private var keyTmp: String? = null + private var isPlaying: Boolean? = null + private var tmpFade: Boolean? = null + private var fallbackTmp: String? = null + /** * Get the current zoom. This is the zoom relative to the initial * scale, not the original resource. @@ -62,8 +81,6 @@ open class TouchImageView @JvmOverloads constructor( */ // Scale of image ranges from minScale to maxScale, where minScale == 1 // when the image is stretched to fit view. - private var mDisposable = CancelableJob() - private val cache: AnimationNetworkCache by lazy { AnimationNetworkCache(context) } var currentZoom = 0f private set @@ -137,7 +154,7 @@ open class TouchImageView @JvmOverloads constructor( var orientationLocked = OrientationLocked.HORIZONTAL init { - super.setClickable(true) + super.isClickable = true orientation = resources.configuration.orientation scaleDetector = ScaleGestureDetector(context, ScaleListener()) gestureDetector = GestureDetector(context, GestureListener()) @@ -169,90 +186,61 @@ open class TouchImageView @JvmOverloads constructor( } } - fun setRotateImageToFitScreen(rotateImageToFitScreen: Boolean) { - isRotateImageToFitScreen = rotateImageToFitScreen - } - - interface StateListener { - fun onChangeState(imageActionState: ImageActionState, zoomed: Boolean) - } - - fun setOnStateChangeListener(onStateListener: StateListener) { - stateListener = onStateListener - } - - override fun setOnTouchListener(onTouchListener: OnTouchListener?) { - userTouchListener = onTouchListener - } - - fun setOnTouchImageViewListener(onTouchImageViewListener: OnTouchImageViewListener) { - touchImageViewListener = onTouchImageViewListener - } - - fun setOnDoubleTapListener(onDoubleTapListener: OnDoubleTapListener) { - doubleTapListener = onDoubleTapListener - } - - fun setOnTouchCoordinatesListener(onTouchCoordinatesListener: OnTouchCoordinatesListener) { - touchCoordinatesListener = onTouchCoordinatesListener - } - - open fun fromAnimFile(file: File) { + //%%~Animation + private fun setAnimationByUrlCache( + key: String, + fallback: String?, + autoPlay: Boolean, + fade: Boolean + ) { if (!FenrirNative.isNativeLoaded) { return } - clearAnimationDrawable() - setAnimation( - AnimatedFileDrawable( - file, - 0, - 100, - 100, - true, - object : AnimatedFileDrawable.DecoderListener { - override fun onError() { - - } - }) - ) - } - - private fun setAnimationByUrlCache(url: String, fallback: String?, fade: Boolean) { - if (!FenrirNative.isNativeLoaded) { - PicassoInstance.with().load(fallback).into(this) + val ch = cache.fetch(key) + if (ch == null) { return } - val ch = cache.fetch(url) - if (ch == null) { - setImageDrawable(null) - PicassoInstance.with().load(fallback).into(this) + if (filePathTmp == ch.absolutePath && fallbackTmp == fallback && loadedFrom == LoadedFrom.FILE) { return } - val ref = WeakReference(this) - setAnimation( - AnimatedFileDrawable( - ch, - 0, - 100, - 100, - fade, - object : AnimatedFileDrawable.DecoderListener { - override fun onError() { - ref.get()?.let { PicassoInstance.with().load(fallback).into(it) } - } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = false) + loadedFrom = LoadedFrom.FILE + filePathTmp = ch.absolutePath + tmpFade = fade + isPlaying = autoPlay + fallbackTmp = fallback - }) - ) + if (attachedToWindow) { + createAnimationDrawable() + } } - fun fromNet(key: String, url: String?, fallback: String?, client: OkHttpClient.Builder) { + fun fromAnimationNet( + key: String, + url: String?, + fallback: String?, + client: OkHttpClient.Builder, + autoPlay: Boolean + ) { if (!FenrirNative.isNativeLoaded || url.isNullOrEmpty()) { - PicassoInstance.with().load(fallback).into(this) + if (loadedFrom == LoadedFrom.NET) { + loadedFrom = LoadedFrom.NO + } return } - clearAnimationDrawable() + if (filePathTmp == url && keyTmp == key && fallbackTmp == fallback && loadedFrom == LoadedFrom.NET) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + loadedFrom = LoadedFrom.NET + filePathTmp = url + keyTmp = key + isPlaying = autoPlay + tmpFade = true + fallbackTmp = fallback + if (cache.isCachedFile(key)) { - setAnimationByUrlCache(key, fallback, true) + setAnimationByUrlCache(key, fallback, autoPlay, true) return } mDisposable.set(flow { @@ -267,89 +255,207 @@ open class TouchImageView @JvmOverloads constructor( emit(false) return@flow } - cache.writeTempCacheFile(url, response.body.source()) + cache.writeTempCacheFile(key, response.body.source()) response.close() - cache.renameTempFile(url) + cache.renameTempFile(key) emit(true) } catch (e: CancellationException) { call?.cancel() throw e } - }.fromIOToMain { - if (it) { - setAnimationByUrlCache(key, fallback, true) - } else { - PicassoInstance.with().load(fallback).into(this) + }.fromIOToMain { u -> + if (u) { + setAnimationByUrlCache(key, fallback, autoPlay, true) } }) } - open fun clearAnimationDrawable() { - mDisposable.cancel() - if (animDrawable != null) { - animDrawable?.stop() - animDrawable?.callback = null - animDrawable?.recycle() - animDrawable = null + fun clearAnimationDrawable(callSuper: Boolean, clearState: Boolean, cancelTask: Boolean) { + if (cancelTask) { + mDisposable.cancel() + } + if (animatedDrawable != null) { + animatedDrawable?.callback = null + animatedDrawable?.recycle() + animatedDrawable = null + } + if (callSuper) { + super.setImageDrawable(null) + } + if (clearState) { + isPlaying = false + loadedFrom = LoadedFrom.NO + filePathTmp = null + tmpFade = null + fallbackTmp = null } } - private fun setAnimation(videoDrawable: AnimatedFileDrawable) { - if (!videoDrawable.isDecoded) return - animDrawable = videoDrawable - animDrawable?.setAllowDecodeSingleFrame(true) - animDrawable?.callback = this - animDrawable?.start() - setImageDrawable(animDrawable) + private fun createAnimationDrawable() { + if (FenrirNative.isNativeLoaded && attachedToWindow && loadedFrom != LoadedFrom.NO && animatedDrawable == null && filePathTmp != null) { + val ref = WeakReference(this) + animatedDrawable = AnimatedFileDrawable( + filePathTmp ?: "", + 0, + 100, + 100, + tmpFade == true, + object : AnimatedFileDrawable.DecoderListener { + override fun onError() { + ref.get()?.let { + fallbackTmp.nonNullNoEmpty { k -> + PicassoInstance.with().load(k).into(it) + } + } + } + + }) + if (animatedDrawable?.isDecoded != true) { + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + return + } + tmpFade = false + animatedDrawable?.setAllowDecodeSingleFrame(true) + + imageRenderedAtLeastOnce = false + super.setImageDrawable(animatedDrawable) + savePreviousImageValues() + fitImageToView() + + if (isPlaying == true) { + playAnimation() + } + } + } + + fun fromAnimationFile(file: File, autoPlay: Boolean) { + if (!FenrirNative.isNativeLoaded || !file.exists()) { + return + } + if (filePathTmp == file.absolutePath && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + loadedFrom = LoadedFrom.FILE + filePathTmp = file.absolutePath + tmpFade = false + isPlaying = autoPlay + if (attachedToWindow) { + createAnimationDrawable() + } } override fun onAttachedToWindow() { + attachedToWindow = true super.onAttachedToWindow() - animDrawable?.callback = this - animDrawable?.start() + when { + loadedFrom == LoadedFrom.NET -> { + filePathTmp?.let { + keyTmp?.let { s -> + fromAnimationNet( + s, + it, + fallbackTmp, + Utils.createOkHttp(Constants.GIF_TIMEOUT, true), + isPlaying == true, + ) + } + return + } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = false) + } + + loadedFrom != LoadedFrom.NO -> { + createAnimationDrawable() + } + } } override fun onDetachedFromWindow() { + attachedToWindow = false super.onDetachedFromWindow() - mDisposable.cancel() - animDrawable?.stop() - animDrawable?.callback = null + if (loadedFrom != LoadedFrom.NO) { + clearAnimationDrawable(callSuper = true, clearState = false, cancelTask = true) + } } - open fun isPlaying(): Boolean { + fun isPlayingAnimation(): Boolean { return animDrawable?.isRunning == true } + fun playAnimation() { + animatedDrawable?.start() + isPlaying = true + } + + fun resetFrame() { + animatedDrawable?.seekTo(0, true) + } + + fun stopAnimation() { + animatedDrawable?.let { + it.stop() + isPlaying = false + } + } + //~Animation + + fun setRotateImageToFitScreen(rotateImageToFitScreen: Boolean) { + isRotateImageToFitScreen = rotateImageToFitScreen + } + + interface StateListener { + fun onChangeState(imageActionState: ImageActionState, zoomed: Boolean) + } + + fun setOnStateChangeListener(onStateListener: StateListener) { + stateListener = onStateListener + } + + override fun setOnTouchListener(onTouchListener: OnTouchListener?) { + userTouchListener = onTouchListener + } + + fun setOnTouchImageViewListener(onTouchImageViewListener: OnTouchImageViewListener) { + touchImageViewListener = onTouchImageViewListener + } + + fun setOnDoubleTapListener(onDoubleTapListener: OnDoubleTapListener) { + doubleTapListener = onDoubleTapListener + } + + fun setOnTouchCoordinatesListener(onTouchCoordinatesListener: OnTouchCoordinatesListener) { + touchCoordinatesListener = onTouchCoordinatesListener + } + override fun setImageResource(resId: Int) { - clearAnimationDrawable() imageRenderedAtLeastOnce = false super.setImageResource(resId) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) savePreviousImageValues() fitImageToView() } override fun setImageBitmap(bm: Bitmap?) { - clearAnimationDrawable() imageRenderedAtLeastOnce = false super.setImageBitmap(bm) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) savePreviousImageValues() fitImageToView() } override fun setImageDrawable(drawable: Drawable?) { - if (drawable !is AnimatedFileDrawable) { - clearAnimationDrawable() - } imageRenderedAtLeastOnce = false super.setImageDrawable(drawable) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) savePreviousImageValues() fitImageToView() } override fun setImageURI(uri: Uri?) { - clearAnimationDrawable() imageRenderedAtLeastOnce = false super.setImageURI(uri) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) savePreviousImageValues() fitImageToView() } @@ -367,7 +473,7 @@ open class TouchImageView @JvmOverloads constructor( } } - override fun getScaleType() = touchScaleType!! + override fun getScaleType() = touchScaleType ?: ScaleType.MATRIX /** * Returns false if image is in initial, unzoomed state. False, otherwise. @@ -626,21 +732,22 @@ open class TouchImageView @JvmOverloads constructor( } internal fun orientationMismatch(drawable: Drawable?): Boolean { - return viewWidth > viewHeight != drawable!!.intrinsicWidth > drawable.intrinsicHeight + return viewWidth > viewHeight != (drawable?.intrinsicWidth + ?: 0) > (drawable?.intrinsicHeight ?: 0) } private fun getDrawableWidth(drawable: Drawable?): Int { return if (orientationMismatch(drawable) && isRotateImageToFitScreen) { - drawable!!.intrinsicHeight + drawable?.intrinsicHeight ?: 0 } else - drawable!!.intrinsicWidth + drawable?.intrinsicWidth ?: 0 } private fun getDrawableHeight(drawable: Drawable?): Int { return if (orientationMismatch(drawable) && isRotateImageToFitScreen) { - drawable!!.intrinsicWidth + drawable?.intrinsicWidth ?: 0 } else - drawable!!.intrinsicHeight + drawable?.intrinsicHeight ?: 0 } /** @@ -825,14 +932,11 @@ open class TouchImageView @JvmOverloads constructor( } ScaleType.CENTER_INSIDE -> { - run { - scaleY = min(1f, min(scaleX, scaleY)) - scaleX = scaleY - } - run { - scaleY = min(scaleX, scaleY) - scaleX = scaleY - } + scaleY = min(1f, min(scaleX, scaleY)) + scaleX = scaleY + + scaleY = min(scaleX, scaleY) + scaleX = scaleY } ScaleType.FIT_CENTER, ScaleType.FIT_START, ScaleType.FIT_END -> { @@ -1317,7 +1421,7 @@ open class TouchImageView @JvmOverloads constructor( * to the bounds of the bitmap size. * @return Coordinates of the point touched, in the coordinate system of the original drawable. */ - protected fun transformCoordTouchToBitmap(x: Float, y: Float, clipToBitmap: Boolean): PointF { + private fun transformCoordTouchToBitmap(x: Float, y: Float, clipToBitmap: Boolean): PointF { touchMatrix.getValues(floatMatrix) val origW = drawable.intrinsicWidth.toFloat() val origH = drawable.intrinsicHeight.toFloat() @@ -1340,7 +1444,7 @@ open class TouchImageView @JvmOverloads constructor( * @param by y-coordinate in original bitmap coordinate system * @return Coordinates of the point in the view's coordinate system. */ - protected fun transformCoordBitmapToTouch(bx: Float, by: Float): PointF { + private fun transformCoordBitmapToTouch(bx: Float, by: Float): PointF { touchMatrix.getValues(floatMatrix) val origW = drawable.intrinsicWidth.toFloat() val origH = drawable.intrinsicHeight.toFloat() diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/emoji/MyStickersAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/emoji/MyStickersAdapter.kt index cda9eae65..f1ad98bdc 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/emoji/MyStickersAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/emoji/MyStickersAdapter.kt @@ -11,7 +11,7 @@ import dev.ragnarok.fenrir.R import dev.ragnarok.fenrir.picasso.PicassoInstance.Companion.with import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.view.emoji.EmojiconsPopup.OnMyStickerClickedListener -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView import java.io.File class MyStickersAdapter(private val context: Context) : @@ -48,14 +48,14 @@ class MyStickersAdapter(private val context: Context) : when (getItemViewType(position)) { TYPE_ANIMATED -> { val animatedHolder = holder as StickerAnimatedHolder - animatedHolder.animation.fromFile(File(item.path), Utils.dp(128f), Utils.dp(128f)) + animatedHolder.animation.fromFile(File(item.path), true) animatedHolder.root.setOnClickListener { myStickerClickedListener?.onMyStickerClick( item ) } animatedHolder.root.setOnLongClickListener { - animatedHolder.animation.playAnimation() + animatedHolder.animation.startAnimation() true } } @@ -82,14 +82,14 @@ class MyStickersAdapter(private val context: Context) : else -> { val animatedHolder = holder as StickerAnimatedHolder - animatedHolder.animation.fromFile(File(item.path), Utils.dp(128f), Utils.dp(128f)) + animatedHolder.animation.fromFile(File(item.path), true) animatedHolder.root.setOnClickListener { myStickerClickedListener?.onMyStickerClick( item ) } animatedHolder.root.setOnLongClickListener { - animatedHolder.animation.playAnimation() + animatedHolder.animation.startAnimation() true } } @@ -107,7 +107,7 @@ class MyStickersAdapter(private val context: Context) : internal class StickerAnimatedHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val root: View = itemView.rootView - val animation: RLottieImageView = itemView.findViewById(R.id.sticker_animated) + val animation: ThorVGLottieView = itemView.findViewById(R.id.sticker_animated) } companion object { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/emoji/StickersAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/emoji/StickersAdapter.kt index 0a73de5a9..279148c97 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/emoji/StickersAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/emoji/StickersAdapter.kt @@ -14,7 +14,7 @@ import dev.ragnarok.fenrir.picasso.PicassoInstance.Companion.with import dev.ragnarok.fenrir.settings.Settings import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.view.emoji.EmojiconsPopup.OnStickerClickedListener -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class StickersAdapter(private val context: Context, private val stickers: StickerSet) : RecyclerView.Adapter() { @@ -70,8 +70,7 @@ class StickersAdapter(private val context: Context, private val stickers: Sticke animatedHolder.animation.fromNet( item?.getAnimationByType(if (isNightSticker) "dark" else "light"), Utils.createOkHttp(Constants.GIF_TIMEOUT, true), - Utils.dp(128f), - Utils.dp(128f) + false ) animatedHolder.root.setOnClickListener { if (item != null) { @@ -81,7 +80,7 @@ class StickersAdapter(private val context: Context, private val stickers: Sticke } } animatedHolder.root.setOnLongClickListener { - animatedHolder.animation.playAnimation() + animatedHolder.animation.startAnimation() true } } @@ -111,8 +110,7 @@ class StickersAdapter(private val context: Context, private val stickers: Sticke animatedHolder.animation.fromNet( item?.getAnimationByType(if (isNightSticker) "dark" else "light"), Utils.createOkHttp(Constants.GIF_TIMEOUT, true), - Utils.dp(128f), - Utils.dp(128f) + false ) animatedHolder.root.setOnClickListener { if (item != null) { @@ -122,7 +120,7 @@ class StickersAdapter(private val context: Context, private val stickers: Sticke } } animatedHolder.root.setOnLongClickListener { - animatedHolder.animation.playAnimation() + animatedHolder.animation.startAnimation() true } } @@ -132,7 +130,10 @@ class StickersAdapter(private val context: Context, private val stickers: Sticke override fun onViewRecycled(holder: RecyclerView.ViewHolder) { super.onViewRecycled(holder) if (holder is StickerAnimatedHolder) { - holder.animation.clearAnimationDrawable() + holder.animation.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } @@ -147,7 +148,7 @@ class StickersAdapter(private val context: Context, private val stickers: Sticke internal class StickerAnimatedHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val root: View = itemView.rootView - val animation: RLottieImageView = itemView.findViewById(R.id.sticker_animated) + val animation: ThorVGLottieView = itemView.findViewById(R.id.sticker_animated) } companion object { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/media/PathAnimator.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/media/PathAnimator.kt index b27d2b642..5fbf03f87 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/media/PathAnimator.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/media/PathAnimator.kt @@ -63,23 +63,19 @@ class PathAnimator( fun draw(canvas: Canvas, paint: Paint?, time: Float) { var startKeyFrame: KeyFrame? = null var endKeyFrame: KeyFrame? = null - run { - var a = 0 - val N = keyFrames.size - while (a < N) { - val keyFrame = keyFrames[a] - if ((startKeyFrame == null || (startKeyFrame - ?: return@run).time < keyFrame.time) && keyFrame.time <= time - ) { - startKeyFrame = keyFrame - } - if ((endKeyFrame == null || (endKeyFrame - ?: return@run).time > keyFrame.time) && keyFrame.time >= time - ) { - endKeyFrame = keyFrame - } - a++ + var a = 0 + var N = keyFrames.size + while (a < N) { + val keyFrame = keyFrames[a] + if ((startKeyFrame == null || startKeyFrame.time < keyFrame.time) && keyFrame.time <= time + ) { + startKeyFrame = keyFrame + } + if ((endKeyFrame == null || endKeyFrame.time > keyFrame.time) && keyFrame.time >= time + ) { + endKeyFrame = keyFrame } + a++ } if (endKeyFrame === startKeyFrame) { startKeyFrame = null @@ -88,24 +84,22 @@ class PathAnimator( endKeyFrame = startKeyFrame startKeyFrame = null } - if (endKeyFrame == null || startKeyFrame != null && (startKeyFrame - ?: return).commands.size != (endKeyFrame ?: return).commands.size + if (endKeyFrame == null || startKeyFrame != null && startKeyFrame.commands.size != endKeyFrame.commands.size ) { return } path.reset() - var a = 0 - val N = (endKeyFrame ?: return).commands.size + a = 0 + N = endKeyFrame.commands.size while (a < N) { val startCommand = - if (startKeyFrame != null) (startKeyFrame ?: return).commands[a] else null - val endCommand = (endKeyFrame ?: return).commands[a] + if (startKeyFrame != null) startKeyFrame.commands[a] else null + val endCommand = endKeyFrame.commands[a] if (startCommand != null && startCommand.javaClass != endCommand.javaClass) { return } val progress: Float = if (startKeyFrame != null) { - (time - (startKeyFrame ?: return).time) / ((endKeyFrame - ?: return).time - (startKeyFrame ?: return).time) + (time - startKeyFrame.time) / (endKeyFrame.time - startKeyFrame.time) } else { 1.0f } @@ -180,4 +174,4 @@ class PathAnimator( var x2 = 0f var y2 = 0f } -} \ No newline at end of file +} diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/AnimatedShapeableImageView.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/AnimatedShapeableImageView.kt index e53561945..cde3c486e 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/AnimatedShapeableImageView.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/AnimatedShapeableImageView.kt @@ -3,6 +3,8 @@ package dev.ragnarok.fenrir.view.natives.animation import android.content.Context import android.graphics.Bitmap import android.graphics.drawable.Drawable +import android.net.Uri +import android.os.Parcelable import android.util.AttributeSet import androidx.annotation.RawRes import com.google.android.material.imageview.ShapeableImageView @@ -10,6 +12,8 @@ import dev.ragnarok.fenrir.Constants import dev.ragnarok.fenrir.R import dev.ragnarok.fenrir.module.FenrirNative import dev.ragnarok.fenrir.module.animation.AnimatedFileDrawable +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.LoadedFrom +import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.util.coroutines.CancelableJob import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.fromIOToMain import dev.ragnarok.fenrir.view.natives.animation.AnimationNetworkCache.Companion.filenameForRes @@ -32,77 +36,91 @@ open class AnimatedShapeableImageView @JvmOverloads constructor( private val defaultHeight: Int private var animatedDrawable: AnimatedFileDrawable? = null private var attachedToWindow = false - private var playing = false private var decoderCallback: OnDecoderInit? = null private var mDisposable = CancelableJob() + + @LoadedFrom + private var loadedFrom = LoadedFrom.NO + private var filePathTmp: String? = null + private var keyTmp: String? = null + + @RawRes + private var rawResTmp: Int? = null + private var isPlaying: Boolean? = null + private var tmpFade: Boolean? = null + fun setDecoderCallback(decoderCallback: OnDecoderInit?) { this.decoderCallback = decoderCallback } - private fun setAnimationByUrlCache(url: String, fade: Boolean) { + private fun setAnimationByUrlCache(key: String, autoPlay: Boolean, fade: Boolean) { if (!FenrirNative.isNativeLoaded) { decoderCallback?.onLoaded(false) return } - val ch = cache.fetch(url) + val ch = cache.fetch(key) if (ch == null) { - setImageDrawable(null) decoderCallback?.onLoaded(false) return } - setAnimation( - AnimatedFileDrawable( - ch, - 0, - defaultWidth, - defaultHeight, - fade, - object : AnimatedFileDrawable.DecoderListener { - override fun onError() { - decoderCallback?.onLoaded(false) - } + if (filePathTmp == ch.absolutePath && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = false) + loadedFrom = LoadedFrom.FILE + filePathTmp = ch.absolutePath + tmpFade = fade + isPlaying = autoPlay - }) - ) - playAnimation() + if (attachedToWindow) { + createAnimationDrawable() + } } - private fun setAnimationByResCache(@RawRes res: Int, fade: Boolean) { + private fun setAnimationByResCache(@RawRes res: Int, autoPlay: Boolean, fade: Boolean) { if (!FenrirNative.isNativeLoaded) { decoderCallback?.onLoaded(false) return } val ch = cache.fetch(res) if (ch == null) { - setImageDrawable(null) decoderCallback?.onLoaded(false) return } - setAnimation( - AnimatedFileDrawable( - ch, - 0, - defaultWidth, - defaultHeight, - fade, - object : AnimatedFileDrawable.DecoderListener { - override fun onError() { - decoderCallback?.onLoaded(false) - } + if (filePathTmp == ch.absolutePath && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = false) + loadedFrom = LoadedFrom.FILE + filePathTmp = ch.absolutePath + tmpFade = fade + isPlaying = autoPlay - }) - ) - playAnimation() + if (attachedToWindow) { + createAnimationDrawable() + } } - fun fromNet(key: String, url: String?, client: OkHttpClient.Builder) { + fun fromNet(key: String, url: String?, client: OkHttpClient.Builder, autoPlay: Boolean) { if (!FenrirNative.isNativeLoaded || url.isNullOrEmpty()) { + if (loadedFrom == LoadedFrom.NET) { + loadedFrom = LoadedFrom.NO + } decoderCallback?.onLoaded(false) return } - clearAnimationDrawable() + if (filePathTmp == url && keyTmp == key && loadedFrom == LoadedFrom.NET) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + loadedFrom = LoadedFrom.NET + filePathTmp = url + keyTmp = key + isPlaying = autoPlay + tmpFade = true + if (cache.isCachedFile(key)) { - setAnimationByUrlCache(key, true) + setAnimationByUrlCache(key, autoPlay, true) return } mDisposable.set(flow { @@ -127,7 +145,7 @@ open class AnimatedShapeableImageView @JvmOverloads constructor( } }.fromIOToMain({ u -> if (u) { - setAnimationByUrlCache(key, true) + setAnimationByUrlCache(key, autoPlay, true) } else { decoderCallback?.onLoaded(false) } @@ -136,23 +154,34 @@ open class AnimatedShapeableImageView @JvmOverloads constructor( })) } - fun fromRes(@RawRes res: Int) { - if (!FenrirNative.isNativeLoaded) { + fun fromRes(@RawRes resId: Int, autoPlay: Boolean) { + if (!FenrirNative.isNativeLoaded || resId == -1) { + if (loadedFrom == LoadedFrom.RES) { + loadedFrom = LoadedFrom.NO + } decoderCallback?.onLoaded(false) return } - clearAnimationDrawable() - if (cache.isCachedRes(res)) { - setAnimationByResCache(res, true) + if (rawResTmp == resId && loadedFrom == LoadedFrom.RES) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + loadedFrom = LoadedFrom.RES + rawResTmp = resId + tmpFade = true + isPlaying = autoPlay + + if (cache.isCachedRes(resId)) { + setAnimationByResCache(resId, autoPlay, true) return } mDisposable.set(flow { try { - if (!copyRes(res)) { + if (!copyRes(resId)) { emit(false) return@flow } - cache.renameTempFile(res) + cache.renameTempFile(resId) } catch (_: Exception) { emit(false) return@flow @@ -160,69 +189,121 @@ open class AnimatedShapeableImageView @JvmOverloads constructor( emit(true) }.fromIOToMain { if (it) { - setAnimationByResCache(res, true) + setAnimationByResCache(resId, autoPlay, true) } else { decoderCallback?.onLoaded(false) } }) } - private fun setAnimation(videoDrawable: AnimatedFileDrawable) { - decoderCallback?.onLoaded(videoDrawable.isDecoded) - if (!videoDrawable.isDecoded) return - animatedDrawable = videoDrawable - animatedDrawable?.setAllowDecodeSingleFrame(true) - setImageDrawable(animatedDrawable) - } - - fun fromFile(file: File) { - if (!FenrirNative.isNativeLoaded) { - decoderCallback?.onLoaded(false) - return - } - clearAnimationDrawable() - setAnimation( - AnimatedFileDrawable( - file, + private fun createAnimationDrawable() { + if (FenrirNative.isNativeLoaded && attachedToWindow && loadedFrom != LoadedFrom.NO && animatedDrawable == null && filePathTmp != null) { + animatedDrawable = AnimatedFileDrawable( + filePathTmp ?: "", 0, defaultWidth, defaultHeight, - false, + tmpFade == true, object : AnimatedFileDrawable.DecoderListener { override fun onError() { decoderCallback?.onLoaded(false) } }) - ) + decoderCallback?.onLoaded(animatedDrawable?.isDecoded == true) + if (animatedDrawable?.isDecoded != true) { + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + return + } + tmpFade = false + animatedDrawable?.setAllowDecodeSingleFrame(true) + super.setImageDrawable(animatedDrawable) + if (isPlaying == true) { + playAnimation() + } + } } - fun clearAnimationDrawable() { - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() + fun fromFile(file: File) { + if (!FenrirNative.isNativeLoaded || !file.exists()) { + decoderCallback?.onLoaded(false) + return + } + if (filePathTmp == file.absolutePath && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + loadedFrom = LoadedFrom.FILE + filePathTmp = file.absolutePath + tmpFade = false + if (attachedToWindow) { + createAnimationDrawable() + } + } + + fun clearAnimationDrawable(callSuper: Boolean, clearState: Boolean, cancelTask: Boolean) { + if (cancelTask) { + mDisposable.cancel() + } + if (animatedDrawable != null) { + animatedDrawable?.callback = null + animatedDrawable?.recycle() animatedDrawable = null } - setImageDrawable(null) + if (callSuper) { + super.setImageDrawable(null) + } + if (clearState) { + isPlaying = false + loadedFrom = LoadedFrom.NO + filePathTmp = null + rawResTmp = null + tmpFade = null + } } override fun onAttachedToWindow() { - super.onAttachedToWindow() attachedToWindow = true - animatedDrawable?.callback = this - if (playing) { - animatedDrawable?.start() + super.onAttachedToWindow() + when { + loadedFrom == LoadedFrom.NET -> { + filePathTmp?.let { + keyTmp?.let { s -> + fromNet( + s, + it, + Utils.createOkHttp(Constants.GIF_TIMEOUT, true), + isPlaying == true, + ) + } + return + } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = false) + } + + loadedFrom == LoadedFrom.RES -> { + rawResTmp?.let { + fromRes( + it, + isPlaying == true, + ) + return + } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = false) + } + + loadedFrom != LoadedFrom.NO -> { + createAnimationDrawable() + } } } override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - mDisposable.cancel() attachedToWindow = false - animatedDrawable?.stop() - animatedDrawable?.callback = null + super.onDetachedFromWindow() + if (loadedFrom != LoadedFrom.NO) { + clearAnimationDrawable(callSuper = true, clearState = false, cancelTask = true) + } } fun isPlaying(): Boolean { @@ -231,69 +312,48 @@ open class AnimatedShapeableImageView @JvmOverloads constructor( override fun setImageDrawable(dr: Drawable?) { super.setImageDrawable(dr) - if (dr !is AnimatedFileDrawable) { - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) } override fun setImageBitmap(bm: Bitmap?) { super.setImageBitmap(bm) - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) } override fun setImageResource(resId: Int) { super.setImageResource(resId) - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageURI(uri: Uri?) { + super.setImageURI(uri) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) } fun playAnimation() { - if (animatedDrawable == null) { - return - } - playing = true - if (attachedToWindow) { - animatedDrawable?.start() - } + animatedDrawable?.start() + isPlaying = true } fun resetFrame() { - if (animatedDrawable == null) { - return - } - playing = true - if (attachedToWindow) { - animatedDrawable?.seekTo(0, true) - } + animatedDrawable?.seekTo(0, true) } fun stopAnimation() { - if (animatedDrawable == null) { - return - } - playing = false - if (attachedToWindow) { - animatedDrawable?.stop() + animatedDrawable?.let { + it.stop() + isPlaying = false } } + protected override fun onSaveInstanceState(): Parcelable? { + return super.onSaveInstanceState() + } + + protected override fun onRestoreInstanceState(state: Parcelable?) { + super.onRestoreInstanceState(state) + } + private fun copyRes(@RawRes rawRes: Int): Boolean { try { context.resources.openRawResource(rawRes).use { inputStream -> diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieNetworkCache.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/ThorVGLottieNetworkCache.kt similarity index 86% rename from app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieNetworkCache.kt rename to app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/ThorVGLottieNetworkCache.kt index 67b8df661..c63618f42 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieNetworkCache.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/ThorVGLottieNetworkCache.kt @@ -1,4 +1,4 @@ -package dev.ragnarok.fenrir.view.natives.rlottie +package dev.ragnarok.fenrir.view.natives.animation import android.content.Context import android.util.Log @@ -8,13 +8,13 @@ import okio.buffer import okio.sink import java.io.File -class RLottieNetworkCache(context: Context) { +class ThorVGLottieNetworkCache(context: Context) { private val appContext = context.applicationContext fun fetch(url: String): File? { val cachedFile = getCachedFile(url) ?: return null if (Constants.IS_DEBUG) { - Log.d("RLottieNetworkCache", "Cache hit for $url at ${cachedFile.absolutePath}") + Log.d("ThorVGLottieNetworkCache", "Cache hit for $url at ${cachedFile.absolutePath}") } return cachedFile } @@ -40,12 +40,12 @@ class RLottieNetworkCache(context: Context) { val newFile = File(newFileName) val renamed = file.renameTo(newFile) if (Constants.IS_DEBUG) { - Log.d("RLottieNetworkCache", "Copying temp file to real file ($newFile)") + Log.d("ThorVGLottieNetworkCache", "Copying temp file to real file ($newFile)") } if (!renamed) { if (Constants.IS_DEBUG) { Log.w( - "RLottieNetworkCache", + "ThorVGLottieNetworkCache", "Unable to rename cache file ${file.absolutePath} to ${newFile.absolutePath}." ) } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/ThorVGLottieShapeableView.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/ThorVGLottieShapeableView.kt new file mode 100644 index 000000000..fad5045d4 --- /dev/null +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/ThorVGLottieShapeableView.kt @@ -0,0 +1,366 @@ +package dev.ragnarok.fenrir.view.natives.animation + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.net.Uri +import android.os.Parcelable +import android.util.AttributeSet +import androidx.annotation.RawRes +import com.google.android.material.imageview.ShapeableImageView +import dev.ragnarok.fenrir.Constants +import dev.ragnarok.fenrir.R +import dev.ragnarok.fenrir.module.FenrirNative +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.Companion.RESTART +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.LoadedFrom +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.LottieAnimationListener +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.RepeatMode +import dev.ragnarok.fenrir.util.Utils +import dev.ragnarok.fenrir.util.coroutines.CancelableJob +import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.fromIOToMain +import kotlinx.coroutines.flow.flow +import okhttp3.Call +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import java.io.File +import kotlin.coroutines.cancellation.CancellationException + +@SuppressLint("CustomViewStyleable") +class ThorVGLottieShapeableView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null +) : + ShapeableImageView(context, attrs) { + private val cache: ThorVGLottieNetworkCache = ThorVGLottieNetworkCache(context) + private var mDisposable = CancelableJob() + private var animatedDrawable: ThorVGLottieDrawable? = null + private var mListener: LottieAnimationListener? = null + + @LoadedFrom + private var loadedFrom = LoadedFrom.NO + private var colorReplacementTmp: IntArray? = null + private var useMoveColorTmp: Boolean = false + private var deleteInvalidFileTmp: Boolean = false + private var filePathTmp: String? = null + + @RawRes + private var rawResTmp: Int? = null + private var isPlaying: Boolean? = null + private var repeatTmp: Boolean? = null + + @RepeatMode + private var repeatModeTmp: Int? = null + + private var mOnAttached = false + + private fun createLottieDrawable() { + if (FenrirNative.isNativeLoaded && mOnAttached && loadedFrom != LoadedFrom.NO && animatedDrawable == null) { + when (loadedFrom) { + LoadedFrom.RES -> { + animatedDrawable = rawResTmp?.let { + ThorVGLottieDrawable( + it, + colorReplacementTmp, + useMoveColorTmp + ) + } + } + + LoadedFrom.FILE -> { + animatedDrawable = filePathTmp?.let { + ThorVGLottieDrawable( + it, + deleteInvalidFileTmp, + colorReplacementTmp, + useMoveColorTmp + ) + } + } + } + if (animatedDrawable == null) { + return + } + repeatModeTmp?.let { animatedDrawable?.setRepeatMode(it) } + repeatTmp?.let { animatedDrawable?.setRepeatCount(if (it) Int.MAX_VALUE else 1) } + super.setImageDrawable(animatedDrawable) + animatedDrawable?.callback = this + animatedDrawable?.setAnimationListener(mListener) + if (isPlaying == true) { + startAnimation() + } + } + } + + private fun setAnimationByUrlCache( + url: String, + autoPlay: Boolean, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded) { + return + } + val ch = cache.fetch(url) + if (ch == null) { + return + } + if (filePathTmp == ch.absolutePath && deleteInvalidFileTmp == true && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = false) + deleteInvalidFileTmp = true + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.FILE + isPlaying = autoPlay + filePathTmp = ch.absolutePath + if (mOnAttached) { + createLottieDrawable() + } + } + + fun fromNet( + url: String?, + client: OkHttpClient.Builder, + autoPlay: Boolean, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded || url.isNullOrEmpty()) { + if (loadedFrom == LoadedFrom.NET) { + loadedFrom = LoadedFrom.NO + } + return + } + if (filePathTmp == url && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.NET) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.NET + filePathTmp = url + isPlaying = autoPlay + + if (cache.isCachedFile(url)) { + setAnimationByUrlCache(url, autoPlay, colorReplacement, useMoveColor) + return + } + mDisposable.set(flow { + var call: Call? = null + try { + val request: Request = Request.Builder() + .url(url) + .build() + call = client.build().newCall(request) + val response: Response = call.execute() + if (!response.isSuccessful) { + emit(false) + return@flow + } + cache.writeTempCacheFile(url, response.body.source()) + response.close() + cache.renameTempFile(url) + emit(true) + } catch (e: CancellationException) { + call?.cancel() + throw e + } + }.fromIOToMain { + if (it) { + setAnimationByUrlCache(url, autoPlay, colorReplacement, useMoveColor) + } + }) + } + + fun fromRes( + @RawRes resId: Int, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded || resId == -1) { + return + } + if (rawResTmp == resId && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.RES) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.RES + rawResTmp = resId + if (mOnAttached) { + createLottieDrawable() + } + } + + fun fromFile( + file: File, + deleteInvalidFile: Boolean, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded || !file.exists()) { + return + } + if (filePathTmp == file.absolutePath && deleteInvalidFileTmp == deleteInvalidFile && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.FILE + filePathTmp = file.absolutePath + deleteInvalidFileTmp = deleteInvalidFile + if (mOnAttached) { + createLottieDrawable() + } + } + + fun isPlaying(): Boolean { + return animatedDrawable != null && animatedDrawable?.isRunning == true + } + + fun setRepeat(repeat: Boolean) { + animatedDrawable?.setRepeatCount(if (repeat) Int.MAX_VALUE else 1) + repeatTmp = repeat + } + + fun setRepeatMode(@RepeatMode repeatMode: Int) { + animatedDrawable?.setRepeatMode(repeatMode) + repeatModeTmp = repeatMode + } + + fun startAnimation() { + animatedDrawable?.start() + isPlaying = true + } + + fun stopAnimation() { + animatedDrawable?.let { + it.stop() + isPlaying = false + } + } + + fun pauseAnimation() { + animatedDrawable?.pause() + } + + fun resumeAnimation() { + animatedDrawable?.resume() + } + + fun clearAnimationDrawable(callSuper: Boolean, clearState: Boolean, cancelTask: Boolean) { + if (cancelTask) { + mDisposable.cancel() + } + if (animatedDrawable != null) { + animatedDrawable?.callback = null + animatedDrawable?.release() + animatedDrawable = null + } + if (callSuper) { + super.setImageDrawable(null) + } + if (clearState) { + isPlaying = false + loadedFrom = LoadedFrom.NO + filePathTmp = null + rawResTmp = null + deleteInvalidFileTmp = false + } + } + + override fun setImageDrawable(dr: Drawable?) { + super.setImageDrawable(dr) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageBitmap(bm: Bitmap?) { + super.setImageBitmap(bm) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageResource(resId: Int) { + super.setImageResource(resId) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageURI(uri: Uri?) { + super.setImageURI(uri) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + if (measuredWidth <= 0 || measuredHeight <= 0) { + return + } + animatedDrawable?.setSize(measuredWidth, measuredHeight) + } + + override fun onAttachedToWindow() { + mOnAttached = true + + super.onAttachedToWindow() + + if (loadedFrom == LoadedFrom.NET) { + filePathTmp?.let { + fromNet( + it, + Utils.createOkHttp(Constants.GIF_TIMEOUT, true), + isPlaying == true, + colorReplacementTmp, + useMoveColorTmp + ) + return + } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = false) + } else if (loadedFrom != LoadedFrom.NO) { + createLottieDrawable() + } + } + + override fun onDetachedFromWindow() { + mOnAttached = false + super.onDetachedFromWindow() + if (loadedFrom != LoadedFrom.NO) { + clearAnimationDrawable(callSuper = true, clearState = false, cancelTask = true) + } + } + + fun setAnimationListener(listener: LottieAnimationListener?) { + mListener = listener + } + + protected override fun onSaveInstanceState(): Parcelable? { + return super.onSaveInstanceState() + } + + protected override fun onRestoreInstanceState(state: Parcelable?) { + super.onRestoreInstanceState(state) + } + + init { + @SuppressLint("CustomViewStyleable") val a = + context.obtainStyledAttributes(attrs, R.styleable.ThorVGLottieView) + rawResTmp = a.getResourceId(R.styleable.ThorVGLottieView_fromRes, 0) + if (rawResTmp == 0) { + rawResTmp = null + } + repeatTmp = a.getBoolean(R.styleable.ThorVGLottieView_loopAnimation, false) + repeatModeTmp = a.getInt(R.styleable.ThorVGLottieView_loopMode, RESTART) + a.recycle() + if (FenrirNative.isNativeLoaded && rawResTmp != null) { + loadedFrom = LoadedFrom.RES + isPlaying = true + } else { + rawResTmp = null + } + } +} diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/ThorVGLottieView.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/ThorVGLottieView.kt new file mode 100644 index 000000000..e995b0d15 --- /dev/null +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/ThorVGLottieView.kt @@ -0,0 +1,362 @@ +package dev.ragnarok.fenrir.view.natives.animation + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.net.Uri +import android.os.Parcelable +import android.util.AttributeSet +import androidx.annotation.RawRes +import androidx.appcompat.widget.AppCompatImageView +import dev.ragnarok.fenrir.Constants +import dev.ragnarok.fenrir.R +import dev.ragnarok.fenrir.module.FenrirNative +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.Companion.RESTART +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.LoadedFrom +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.LottieAnimationListener +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.RepeatMode +import dev.ragnarok.fenrir.util.Utils +import dev.ragnarok.fenrir.util.coroutines.CancelableJob +import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.fromIOToMain +import kotlinx.coroutines.flow.flow +import okhttp3.Call +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import java.io.File +import kotlin.coroutines.cancellation.CancellationException + +@SuppressLint("CustomViewStyleable") +class ThorVGLottieView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : + AppCompatImageView(context, attrs) { + private val cache: ThorVGLottieNetworkCache = ThorVGLottieNetworkCache(context) + private var mDisposable = CancelableJob() + private var animatedDrawable: ThorVGLottieDrawable? = null + private var mListener: LottieAnimationListener? = null + + @LoadedFrom + private var loadedFrom = LoadedFrom.NO + private var colorReplacementTmp: IntArray? = null + private var useMoveColorTmp: Boolean = false + private var deleteInvalidFileTmp: Boolean = false + private var filePathTmp: String? = null + + @RawRes + private var rawResTmp: Int? = null + private var isPlaying: Boolean? = null + private var repeatTmp: Boolean? = null + + @RepeatMode + private var repeatModeTmp: Int? = null + + private var mOnAttached = false + + private fun createLottieDrawable() { + if (FenrirNative.isNativeLoaded && mOnAttached && loadedFrom != LoadedFrom.NO && animatedDrawable == null) { + when (loadedFrom) { + LoadedFrom.RES -> { + animatedDrawable = rawResTmp?.let { + ThorVGLottieDrawable( + it, + colorReplacementTmp, + useMoveColorTmp + ) + } + } + + LoadedFrom.FILE -> { + animatedDrawable = filePathTmp?.let { + ThorVGLottieDrawable( + it, + deleteInvalidFileTmp, + colorReplacementTmp, + useMoveColorTmp + ) + } + } + } + if (animatedDrawable == null) { + return + } + repeatModeTmp?.let { animatedDrawable?.setRepeatMode(it) } + repeatTmp?.let { animatedDrawable?.setRepeatCount(if (it) Int.MAX_VALUE else 1) } + super.setImageDrawable(animatedDrawable) + animatedDrawable?.callback = this + animatedDrawable?.setAnimationListener(mListener) + if (isPlaying == true) { + startAnimation() + } + } + } + + private fun setAnimationByUrlCache( + url: String, + autoPlay: Boolean, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded) { + return + } + val ch = cache.fetch(url) + if (ch == null) { + return + } + if (filePathTmp == ch.absolutePath && deleteInvalidFileTmp == true && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = false) + deleteInvalidFileTmp = true + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.FILE + isPlaying = autoPlay + filePathTmp = ch.absolutePath + if (mOnAttached) { + createLottieDrawable() + } + } + + fun fromNet( + url: String?, + client: OkHttpClient.Builder, + autoPlay: Boolean, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded || url.isNullOrEmpty()) { + if (loadedFrom == LoadedFrom.NET) { + loadedFrom = LoadedFrom.NO + } + return + } + if (filePathTmp == url && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.NET) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.NET + filePathTmp = url + isPlaying = autoPlay + + if (cache.isCachedFile(url)) { + setAnimationByUrlCache(url, autoPlay, colorReplacement, useMoveColor) + return + } + mDisposable.set(flow { + var call: Call? = null + try { + val request: Request = Request.Builder() + .url(url) + .build() + call = client.build().newCall(request) + val response: Response = call.execute() + if (!response.isSuccessful) { + emit(false) + return@flow + } + cache.writeTempCacheFile(url, response.body.source()) + response.close() + cache.renameTempFile(url) + emit(true) + } catch (e: CancellationException) { + call?.cancel() + throw e + } + }.fromIOToMain { + if (it) { + setAnimationByUrlCache(url, autoPlay, colorReplacement, useMoveColor) + } + }) + } + + fun fromRes( + @RawRes resId: Int, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded || resId == -1) { + return + } + if (rawResTmp == resId && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.RES) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.RES + rawResTmp = resId + if (mOnAttached) { + createLottieDrawable() + } + } + + fun fromFile( + file: File, + deleteInvalidFile: Boolean, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded || !file.exists()) { + return + } + if (filePathTmp == file.absolutePath && deleteInvalidFileTmp == deleteInvalidFile && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.FILE + filePathTmp = file.absolutePath + deleteInvalidFileTmp = deleteInvalidFile + if (mOnAttached) { + createLottieDrawable() + } + } + + fun isPlaying(): Boolean { + return animatedDrawable != null && animatedDrawable?.isRunning == true + } + + fun setRepeat(repeat: Boolean) { + animatedDrawable?.setRepeatCount(if (repeat) Int.MAX_VALUE else 1) + repeatTmp = repeat + } + + fun setRepeatMode(@RepeatMode repeatMode: Int) { + animatedDrawable?.setRepeatMode(repeatMode) + repeatModeTmp = repeatMode + } + + fun startAnimation() { + animatedDrawable?.start() + isPlaying = true + } + + fun stopAnimation() { + animatedDrawable?.let { + it.stop() + isPlaying = false + } + } + + fun pauseAnimation() { + animatedDrawable?.pause() + } + + fun resumeAnimation() { + animatedDrawable?.resume() + } + + fun clearAnimationDrawable(callSuper: Boolean, clearState: Boolean, cancelTask: Boolean) { + if (cancelTask) { + mDisposable.cancel() + } + if (animatedDrawable != null) { + animatedDrawable?.callback = null + animatedDrawable?.release() + animatedDrawable = null + } + if (callSuper) { + super.setImageDrawable(null) + } + if (clearState) { + isPlaying = false + loadedFrom = LoadedFrom.NO + filePathTmp = null + rawResTmp = null + deleteInvalidFileTmp = false + } + } + + override fun setImageDrawable(dr: Drawable?) { + super.setImageDrawable(dr) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageBitmap(bm: Bitmap?) { + super.setImageBitmap(bm) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageResource(resId: Int) { + super.setImageResource(resId) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageURI(uri: Uri?) { + super.setImageURI(uri) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + if (measuredWidth <= 0 || measuredHeight <= 0) { + return + } + animatedDrawable?.setSize(measuredWidth, measuredHeight) + } + + override fun onAttachedToWindow() { + mOnAttached = true + + super.onAttachedToWindow() + + if (loadedFrom == LoadedFrom.NET) { + filePathTmp?.let { + fromNet( + it, + Utils.createOkHttp(Constants.GIF_TIMEOUT, true), + isPlaying == true, + colorReplacementTmp, + useMoveColorTmp + ) + return + } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = false) + } else if (loadedFrom != LoadedFrom.NO) { + createLottieDrawable() + } + } + + override fun onDetachedFromWindow() { + mOnAttached = false + super.onDetachedFromWindow() + if (loadedFrom != LoadedFrom.NO) { + clearAnimationDrawable(callSuper = true, clearState = false, cancelTask = true) + } + } + + fun setAnimationListener(listener: LottieAnimationListener?) { + mListener = listener + } + + protected override fun onSaveInstanceState(): Parcelable? { + return super.onSaveInstanceState() + } + + protected override fun onRestoreInstanceState(state: Parcelable?) { + super.onRestoreInstanceState(state) + } + + init { + val a = context.obtainStyledAttributes(attrs, R.styleable.ThorVGLottieView) + rawResTmp = a.getResourceId(R.styleable.ThorVGLottieView_fromRes, 0) + if (rawResTmp == 0) { + rawResTmp = null + } + repeatTmp = a.getBoolean(R.styleable.ThorVGLottieView_loopAnimation, false) + repeatModeTmp = a.getInt(R.styleable.ThorVGLottieView_loopMode, RESTART) + a.recycle() + if (FenrirNative.isNativeLoaded && rawResTmp != null) { + loadedFrom = LoadedFrom.RES + isPlaying = true + } else { + rawResTmp = null + } + } +} diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieImageView.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieImageView.kt deleted file mode 100644 index 6b38d240e..000000000 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieImageView.kt +++ /dev/null @@ -1,294 +0,0 @@ -package dev.ragnarok.fenrir.view.natives.rlottie - -import android.content.Context -import android.graphics.Bitmap -import android.graphics.drawable.Drawable -import android.util.AttributeSet -import androidx.annotation.RawRes -import androidx.appcompat.widget.AppCompatImageView -import dev.ragnarok.fenrir.R -import dev.ragnarok.fenrir.module.BufferWriteNative -import dev.ragnarok.fenrir.module.FenrirNative -import dev.ragnarok.fenrir.module.rlottie.RLottieDrawable -import dev.ragnarok.fenrir.util.coroutines.CancelableJob -import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.fromIOToMain -import kotlinx.coroutines.flow.flow -import okhttp3.Call -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import java.io.File -import kotlin.coroutines.cancellation.CancellationException - -class RLottieImageView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : - AppCompatImageView(context, attrs) { - private val cache: RLottieNetworkCache = RLottieNetworkCache(context) - private var layerColors: HashMap? = null - private var animatedDrawable: RLottieDrawable? = null - private var autoRepeat: Boolean - private var attachedToWindow = false - private var playing = false - private var mDisposable = CancelableJob() - fun clearLayerColors() { - layerColors?.clear() - } - - fun setLayerColor(layer: String, color: Int) { - if (layerColors == null) { - layerColors = HashMap() - } - (layerColors ?: return)[layer] = color - animatedDrawable?.setLayerColor(layer, color) - } - - fun replaceColors(colors: IntArray?) { - animatedDrawable?.replaceColors(colors) - } - - private fun setAnimationByUrlCache(url: String, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded) { - return - } - val ch = cache.fetch(url) - if (ch == null) { - setImageDrawable(null) - return - } - autoRepeat = false - setAnimation( - RLottieDrawable( - ch, true, w, h, - limitFps = false, - colorReplacement = null, - useMoveColor = false - ) - ) - playAnimation() - } - - fun fromNet(url: String?, client: OkHttpClient.Builder, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded || url.isNullOrEmpty()) { - return - } - clearAnimationDrawable() - if (cache.isCachedFile(url)) { - setAnimationByUrlCache(url, w, h) - return - } - mDisposable.set(flow { - var call: Call? = null - try { - val request: Request = Request.Builder() - .url(url) - .build() - call = client.build().newCall(request) - val response: Response = call.execute() - if (!response.isSuccessful) { - emit(false) - return@flow - } - cache.writeTempCacheFile(url, response.body.source()) - response.close() - cache.renameTempFile(url) - emit(true) - } catch (e: CancellationException) { - call?.cancel() - throw e - } - }.fromIOToMain { - if (it) { - setAnimationByUrlCache(url, w, h) - } - }) - } - - private fun setAnimation(rLottieDrawable: RLottieDrawable) { - animatedDrawable = rLottieDrawable - animatedDrawable?.setAutoRepeat(if (autoRepeat) 1 else 0) - if (layerColors != null) { - animatedDrawable?.beginApplyLayerColors() - for ((key, value) in layerColors ?: return) { - animatedDrawable?.setLayerColor(key, value) - } - animatedDrawable?.commitApplyLayerColors() - } - animatedDrawable?.setAllowDecodeSingleFrame(true) - animatedDrawable?.setCurrentParentView(this) - setImageDrawable(animatedDrawable) - } - - @JvmOverloads - fun fromRes( - @RawRes resId: Int, - w: Int, - h: Int, - colorReplacement: IntArray? = null, - useMoveColor: Boolean = false - ) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - RLottieDrawable( - resId, - w, - h, - false, - colorReplacement, - useMoveColor - ) - ) - } - - fun fromFile(file: File, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - RLottieDrawable( - file, false, w, h, - limitFps = false, - colorReplacement = null, - useMoveColor = false - ) - ) - } - - fun fromString(jsonString: BufferWriteNative, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - RLottieDrawable( - jsonString, w, h, - limitFps = false, - colorReplacement = null, - useMoveColor = false - ) - ) - } - - fun clearAnimationDrawable() { - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - setImageDrawable(null) - } - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - attachedToWindow = true - animatedDrawable?.setCurrentParentView(this) - if (playing) { - animatedDrawable?.start() - } - } - - override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - mDisposable.cancel() - attachedToWindow = false - animatedDrawable?.stop() - animatedDrawable?.setCurrentParentView(null) - } - - fun isPlaying(): Boolean { - return animatedDrawable != null && animatedDrawable?.isRunning == true - } - - fun setAutoRepeat(repeat: Boolean) { - autoRepeat = repeat - } - - fun setProgress(progress: Float) { - animatedDrawable?.setProgress(progress) - } - - override fun setImageDrawable(dr: Drawable?) { - super.setImageDrawable(dr) - if (dr !is RLottieDrawable) { - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } - } - - override fun setImageBitmap(bm: Bitmap?) { - super.setImageBitmap(bm) - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } - - override fun setImageResource(resId: Int) { - super.setImageResource(resId) - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } - - fun playAnimation() { - playing = true - if (attachedToWindow) { - animatedDrawable?.start() - } - } - - fun replayAnimation() { - if (animatedDrawable == null) { - return - } - playing = true - if (attachedToWindow) { - animatedDrawable?.stop() - animatedDrawable?.setAutoRepeat(1) - animatedDrawable?.start() - } - } - - fun resetFrame() { - playing = true - if (attachedToWindow) { - animatedDrawable?.setProgress(0f) - } - } - - fun stopAnimation() { - playing = false - if (attachedToWindow) { - animatedDrawable?.stop() - } - } - - init { - val a = context.obtainStyledAttributes(attrs, R.styleable.RLottieImageView) - val animRes = a.getResourceId(R.styleable.RLottieImageView_fromRes, 0) - autoRepeat = a.getBoolean(R.styleable.RLottieImageView_loopAnimation, false) - val width = a.getDimension(R.styleable.RLottieImageView_w, 28f).toInt() - val height = a.getDimension(R.styleable.RLottieImageView_h, 28f).toInt() - a.recycle() - if (FenrirNative.isNativeLoaded && animRes != 0) { - setAnimation(RLottieDrawable(animRes, width, height, false, null, false)) - playAnimation() - } - } -} diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieShapeableImageView.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieShapeableImageView.kt deleted file mode 100644 index 42561a439..000000000 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieShapeableImageView.kt +++ /dev/null @@ -1,297 +0,0 @@ -package dev.ragnarok.fenrir.view.natives.rlottie - -import android.annotation.SuppressLint -import android.content.Context -import android.graphics.Bitmap -import android.graphics.drawable.Drawable -import android.util.AttributeSet -import androidx.annotation.RawRes -import com.google.android.material.imageview.ShapeableImageView -import dev.ragnarok.fenrir.R -import dev.ragnarok.fenrir.module.BufferWriteNative -import dev.ragnarok.fenrir.module.FenrirNative -import dev.ragnarok.fenrir.module.rlottie.RLottieDrawable -import dev.ragnarok.fenrir.util.coroutines.CancelableJob -import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.fromIOToMain -import kotlinx.coroutines.flow.flow -import okhttp3.Call -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import java.io.File -import kotlin.coroutines.cancellation.CancellationException - -class RLottieShapeableImageView @JvmOverloads constructor( - context: Context, attrs: AttributeSet? = null -) : ShapeableImageView(context, attrs) { - private val cache: RLottieNetworkCache = RLottieNetworkCache(context) - private var layerColors: HashMap? = null - private var animatedDrawable: RLottieDrawable? = null - private var autoRepeat: Boolean - private var attachedToWindow = false - private var playing = false - private var mDisposable = CancelableJob() - fun clearLayerColors() { - layerColors?.clear() - } - - fun setLayerColor(layer: String, color: Int) { - if (layerColors == null) { - layerColors = HashMap() - } - (layerColors ?: return)[layer] = color - animatedDrawable?.setLayerColor(layer, color) - } - - fun replaceColors(colors: IntArray?) { - animatedDrawable?.replaceColors(colors) - } - - private fun setAnimationByUrlCache(url: String, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded) { - return - } - val ch = cache.fetch(url) - if (ch == null) { - setImageDrawable(null) - return - } - autoRepeat = false - setAnimation( - RLottieDrawable( - ch, true, w, h, - limitFps = false, - colorReplacement = null, - useMoveColor = false - ) - ) - playAnimation() - } - - fun fromNet(url: String?, client: OkHttpClient.Builder, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded || url.isNullOrEmpty()) { - return - } - clearAnimationDrawable() - if (cache.isCachedFile(url)) { - setAnimationByUrlCache(url, w, h) - return - } - mDisposable.set(flow { - var call: Call? = null - try { - val request: Request = Request.Builder() - .url(url) - .build() - call = client.build().newCall(request) - val response: Response = call.execute() - if (!response.isSuccessful) { - emit(false) - return@flow - } - cache.writeTempCacheFile(url, response.body.source()) - response.close() - cache.renameTempFile(url) - emit(true) - } catch (e: CancellationException) { - call?.cancel() - throw e - } - }.fromIOToMain { - if (it) { - setAnimationByUrlCache(url, w, h) - } - }) - } - - private fun setAnimation(rLottieDrawable: RLottieDrawable) { - animatedDrawable = rLottieDrawable - animatedDrawable?.setAutoRepeat(if (autoRepeat) 1 else 0) - if (layerColors != null) { - animatedDrawable?.beginApplyLayerColors() - for ((key, value) in layerColors ?: return) { - animatedDrawable?.setLayerColor(key, value) - } - animatedDrawable?.commitApplyLayerColors() - } - animatedDrawable?.setAllowDecodeSingleFrame(true) - animatedDrawable?.setCurrentParentView(this) - setImageDrawable(animatedDrawable) - } - - @JvmOverloads - fun fromRes( - @RawRes resId: Int, - w: Int, - h: Int, - colorReplacement: IntArray? = null, - useMoveColor: Boolean = false - ) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - RLottieDrawable( - resId, - w, - h, - false, - colorReplacement, - useMoveColor - ) - ) - } - - fun fromFile(file: File, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - RLottieDrawable( - file, false, w, h, - limitFps = false, - colorReplacement = null, - useMoveColor = false - ) - ) - } - - fun fromString(jsonString: BufferWriteNative, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - RLottieDrawable( - jsonString, w, h, - limitFps = false, - colorReplacement = null, - useMoveColor = false - ) - ) - } - - fun clearAnimationDrawable() { - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - setImageDrawable(null) - } - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - attachedToWindow = true - animatedDrawable?.setCurrentParentView(this) - if (playing) { - animatedDrawable?.start() - } - } - - override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - mDisposable.cancel() - attachedToWindow = false - animatedDrawable?.stop() - animatedDrawable?.setCurrentParentView(null) - } - - fun isPlaying(): Boolean { - return animatedDrawable != null && animatedDrawable?.isRunning == true - } - - fun setAutoRepeat(repeat: Boolean) { - autoRepeat = repeat - } - - fun setProgress(progress: Float) { - animatedDrawable?.setProgress(progress) - } - - override fun setImageDrawable(dr: Drawable?) { - super.setImageDrawable(dr) - if (dr !is RLottieDrawable) { - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } - } - - override fun setImageBitmap(bm: Bitmap?) { - super.setImageBitmap(bm) - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } - - override fun setImageResource(resId: Int) { - super.setImageResource(resId) - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } - - fun playAnimation() { - playing = true - if (attachedToWindow) { - animatedDrawable?.start() - } - } - - fun replayAnimation() { - if (animatedDrawable == null) { - return - } - playing = true - if (attachedToWindow) { - animatedDrawable?.stop() - animatedDrawable?.setAutoRepeat(1) - animatedDrawable?.start() - } - } - - fun resetFrame() { - playing = true - if (attachedToWindow) { - animatedDrawable?.setProgress(0f) - } - } - - fun stopAnimation() { - playing = false - if (attachedToWindow) { - animatedDrawable?.stop() - } - } - - init { - @SuppressLint("CustomViewStyleable") val a = - context.obtainStyledAttributes(attrs, R.styleable.RLottieImageView) - val animRes = a.getResourceId(R.styleable.RLottieImageView_fromRes, 0) - autoRepeat = a.getBoolean(R.styleable.RLottieImageView_loopAnimation, false) - val width = a.getDimension(R.styleable.RLottieImageView_w, 28f).toInt() - val height = a.getDimension(R.styleable.RLottieImageView_h, 28f).toInt() - a.recycle() - if (FenrirNative.isNativeLoaded && animRes != 0) { - setAnimation(RLottieDrawable(animRes, width, height, false, null, false)) - playAnimation() - } - } -} diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/navigation/SideNavigationView.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/navigation/SideNavigationView.kt index b2cfb14d1..8edd8a8b4 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/navigation/SideNavigationView.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/navigation/SideNavigationView.kt @@ -32,14 +32,13 @@ import dev.ragnarok.fenrir.settings.CurrentTheme import dev.ragnarok.fenrir.settings.ISettings import dev.ragnarok.fenrir.settings.Settings import dev.ragnarok.fenrir.toColor -import dev.ragnarok.fenrir.util.Utils.dp import dev.ragnarok.fenrir.util.Utils.firstNonEmptyString import dev.ragnarok.fenrir.util.Utils.getVerifiedColor import dev.ragnarok.fenrir.util.Utils.setBackgroundTint import dev.ragnarok.fenrir.util.coroutines.CompositeJob import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.fromIOToMain import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.sharedFlowToMain -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class SideNavigationView : AbsNavigationView, MenuListAdapter.ActionListener { private val mCompositeJob = CompositeJob() @@ -47,7 +46,7 @@ class SideNavigationView : AbsNavigationView, MenuListAdapter.ActionListener { private var mDrawerLayout: DrawerLayout? = null private var ivHeaderAvatar: ImageView? = null private var ivVerified: ImageView? = null - private var bDonate: RLottieImageView? = null + private var bDonate: ThorVGLottieView? = null private var tvUserName: TextView? = null private var tvDomain: TextView? = null private var mRecentChats: MutableList? = null @@ -345,27 +344,20 @@ class SideNavigationView : AbsNavigationView, MenuListAdapter.ActionListener { val donate_anim = Settings.get().main().donate_anim_set if (donate_anim > 0 && user.isDonated) { bDonate?.visibility = VISIBLE - bDonate?.setAutoRepeat(true) + bDonate?.setRepeat(true) if (donate_anim == 2) { val cur = Settings.get().ui().mainThemeKey if ("fire" == cur || "orange" == cur || "orange_gray" == cur || "yellow_violet" == cur) { tvUserName?.setTextColor("#df9d00".toColor()) tvDomain?.setTextColor("#df9d00".toColor()) setBackgroundTint(ivVerified, "#df9d00".toColor()) - bDonate?.fromRes( - dev.ragnarok.fenrir_common.R.raw.donater_fire, - dp(100f), - dp(100f), - null - ) + bDonate?.fromRes(dev.ragnarok.fenrir_common.R.raw.donater_fire) } else { tvUserName?.setTextColor(CurrentTheme.getColorPrimary(context)) tvDomain?.setTextColor(CurrentTheme.getColorPrimary(context)) setBackgroundTint(ivVerified, CurrentTheme.getColorPrimary(context)) bDonate?.fromRes( dev.ragnarok.fenrir_common.R.raw.donater_fire, - dp(100f), - dp(100f), intArrayOf(0xFF812E, CurrentTheme.getColorPrimary(context)), true ) @@ -373,8 +365,6 @@ class SideNavigationView : AbsNavigationView, MenuListAdapter.ActionListener { } else { bDonate?.fromRes( dev.ragnarok.fenrir_common.R.raw.donater, - dp(100f), - dp(100f), intArrayOf( 0xffffff, CurrentTheme.getColorPrimary(context), @@ -383,7 +373,7 @@ class SideNavigationView : AbsNavigationView, MenuListAdapter.ActionListener { ) ) } - bDonate?.playAnimation() + bDonate?.startAnimation() } else { bDonate?.setImageDrawable(null) bDonate?.visibility = GONE diff --git a/app_fenrir/src/main/res/layout/activity_lottie.xml b/app_fenrir/src/main/res/layout/activity_lottie.xml index 98b6fec9f..1ea14fc5f 100644 --- a/app_fenrir/src/main/res/layout/activity_lottie.xml +++ b/app_fenrir/src/main/res/layout/activity_lottie.xml @@ -1,19 +1,21 @@ - + android:adjustViewBounds="true" + app:loopAnimation="true" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app_fenrir/src/main/res/layout/item_message_my_sticker.xml b/app_fenrir/src/main/res/layout/item_message_my_sticker.xml index df7487b70..5d87cd373 100644 --- a/app_fenrir/src/main/res/layout/item_message_my_sticker.xml +++ b/app_fenrir/src/main/res/layout/item_message_my_sticker.xml @@ -61,7 +61,7 @@ android:gravity="end" android:orientation="vertical"> - diff --git a/app_fenrir/src/main/res/layout/item_reaction.xml b/app_fenrir/src/main/res/layout/item_reaction.xml index a11a11c35..1159ca6ca 100644 --- a/app_fenrir/src/main/res/layout/item_reaction.xml +++ b/app_fenrir/src/main/res/layout/item_reaction.xml @@ -11,7 +11,7 @@ android:paddingRight="8dp" android:paddingBottom="2dp"> - diff --git a/app_fenrir/src/main/res/layout/item_special_theme.xml b/app_fenrir/src/main/res/layout/item_special_theme.xml index 67edf4444..be23512ee 100644 --- a/app_fenrir/src/main/res/layout/item_special_theme.xml +++ b/app_fenrir/src/main/res/layout/item_special_theme.xml @@ -39,7 +39,7 @@ tools:text="Theme Name" /> - - - - - - - + android:layout_width="84dp" + android:layout_height="84dp" /> \ No newline at end of file diff --git a/app_fenrir/src/main/res/layout/vk_photo_item.xml b/app_fenrir/src/main/res/layout/vk_photo_item.xml index 73fed2a1c..b909f6478 100644 --- a/app_fenrir/src/main/res/layout/vk_photo_item.xml +++ b/app_fenrir/src/main/res/layout/vk_photo_item.xml @@ -25,7 +25,7 @@ app:aspectRatioW="1" app:dominantMeasurement="width" /> - - + android:id="@+id/action_proxy" + android:title="@string/button_proxy" /> + + @drawable/runes_dark @drawable/background_rectangle_dash @style/CustomBottomSheetDialog + @color/m3_ref_palette_dynamic_secondary30 diff --git a/app_fenrir/src/main/res/values-ru/strings.xml b/app_fenrir/src/main/res/values-ru/strings.xml index dfc9bfe70..d8b4b039a 100644 --- a/app_fenrir/src/main/res/values-ru/strings.xml +++ b/app_fenrir/src/main/res/values-ru/strings.xml @@ -700,7 +700,7 @@ Добавить в закладки Убрать из закладок Уведомлять о записях - Неуведомлять о записях + Не уведомлять о записях Загрузка Все друзья Поделиться ссылкой @@ -1695,4 +1695,7 @@ Однострочные элементы Видео Фото + Вход по qr + Вход по qr не возможен! + Перевыпуск токена… \ No newline at end of file diff --git a/app_fenrir/src/main/res/values-v31/styles.xml b/app_fenrir/src/main/res/values-v31/styles.xml index 51a046a11..db6f53912 100644 --- a/app_fenrir/src/main/res/values-v31/styles.xml +++ b/app_fenrir/src/main/res/values-v31/styles.xml @@ -57,5 +57,6 @@ @drawable/background_rectangle_dash_inverse @style/CustomBottomSheetDialog + @color/m3_ref_palette_dynamic_secondary90 diff --git a/app_fenrir/src/main/res/values/attrs.xml b/app_fenrir/src/main/res/values/attrs.xml index 1115499e5..4587bac80 100644 --- a/app_fenrir/src/main/res/values/attrs.xml +++ b/app_fenrir/src/main/res/values/attrs.xml @@ -199,11 +199,13 @@ - + + + + + - - diff --git a/app_fenrir/src/main/res/values/strings.xml b/app_fenrir/src/main/res/values/strings.xml index 99578a9dd..246c3cb9b 100644 --- a/app_fenrir/src/main/res/values/strings.xml +++ b/app_fenrir/src/main/res/values/strings.xml @@ -1933,4 +1933,7 @@ Single line elements Videos Photos + Auth by qr + QR code entry is not possible! + Refreshing Token… diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/activity/photopager/PhotoPagerActivity.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/activity/photopager/PhotoPagerActivity.kt index 6a577f469..264138ea2 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/activity/photopager/PhotoPagerActivity.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/activity/photopager/PhotoPagerActivity.kt @@ -63,7 +63,7 @@ import dev.ragnarok.filegallery.util.coroutines.CoroutinesUtils.delayTaskFlow import dev.ragnarok.filegallery.util.coroutines.CoroutinesUtils.toMain import dev.ragnarok.filegallery.util.toast.CustomToast.Companion.createCustomToast import dev.ragnarok.filegallery.view.TouchImageView -import dev.ragnarok.filegallery.view.natives.rlottie.RLottieImageView +import dev.ragnarok.filegallery.view.natives.animation.ThorVGLottieView import dev.ragnarok.filegallery.view.pager.WeakPicassoLoadCallback class PhotoPagerActivity : BaseMvpActivity(), IPhotoPagerView, @@ -107,7 +107,7 @@ class PhotoPagerActivity : BaseMvpActivity private var mViewPager: ViewPager2? = null private var mContentRoot: RelativeLayout? = null - private var mLoadingProgressBar: RLottieImageView? = null + private var mLoadingProgressBar: ThorVGLottieView? = null private var mLoadingProgressBarDispose = CancelableJob() private var mLoadingProgressBarLoaded = false private var mToolbar: Toolbar? = null @@ -352,8 +352,6 @@ class PhotoPagerActivity : BaseMvpActivity mLoadingProgressBar?.visibility = View.VISIBLE mLoadingProgressBar?.fromRes( R.raw.loading, - Utils.dp(100F), - Utils.dp(100F), intArrayOf( 0x000000, Color.WHITE, @@ -361,12 +359,15 @@ class PhotoPagerActivity : BaseMvpActivity Color.WHITE ) ) - mLoadingProgressBar?.playAnimation() + mLoadingProgressBar?.startAnimation() }) } else if (mLoadingProgressBarLoaded) { mLoadingProgressBarLoaded = false mLoadingProgressBar?.visibility = View.GONE - mLoadingProgressBar?.clearAnimationDrawable() + mLoadingProgressBar?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } @@ -449,7 +450,7 @@ class PhotoPagerActivity : BaseMvpActivity private val mPicassoLoadCallback: WeakPicassoLoadCallback val photo: TouchImageView val selected: View - val progress: RLottieImageView + val progress: ThorVGLottieView var animationDispose = CancelableJob() private var mAnimationLoaded = false private var mLoadingNow = false @@ -500,13 +501,19 @@ class PhotoPagerActivity : BaseMvpActivity val k = ObjectAnimator.ofFloat(progress, View.ALPHA, 0.0f).setDuration(1000) k.addListener(object : StubAnimatorListener() { override fun onAnimationEnd(animation: Animator) { - progress.clearAnimationDrawable() + progress.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) progress.visibility = View.GONE progress.alpha = 1f } override fun onAnimationCancel(animation: Animator) { - progress.clearAnimationDrawable() + progress.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) progress.visibility = View.GONE progress.alpha = 1f } @@ -514,7 +521,10 @@ class PhotoPagerActivity : BaseMvpActivity k.start() } else if (mAnimationLoaded && !mLoadingNow) { mAnimationLoaded = false - progress.clearAnimationDrawable() + progress.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) progress.visibility = View.GONE } else if (mLoadingNow) { animationDispose.set(delayTaskFlow(300).toMain { @@ -522,8 +532,6 @@ class PhotoPagerActivity : BaseMvpActivity progress.visibility = View.VISIBLE progress.fromRes( R.raw.loading, - Utils.dp(100F), - Utils.dp(100F), intArrayOf( 0x000000, CurrentTheme.getColorPrimary(this@PhotoPagerActivity), @@ -531,7 +539,7 @@ class PhotoPagerActivity : BaseMvpActivity CurrentTheme.getColorSecondary(this@PhotoPagerActivity) ) ) - progress.playAnimation() + progress.startAnimation() }) } } @@ -553,7 +561,7 @@ class PhotoPagerActivity : BaseMvpActivity .load(image.photo_url) .into(photo, mPicassoLoadCallback) } else { - photo.fromAnimFile(Uri.parse(image.photo_url).toFile()) + photo.fromAnimationFile(Uri.parse(image.photo_url).toFile(), true) mLoadingNow = false resolveProgressVisibility(true) } diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/AudioPlayerFragment.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/AudioPlayerFragment.kt index 3d47792db..1ac7ac514 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/AudioPlayerFragment.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/AudioPlayerFragment.kt @@ -59,7 +59,7 @@ import dev.ragnarok.filegallery.view.media.PlayPauseButton import dev.ragnarok.filegallery.view.media.RepeatButton import dev.ragnarok.filegallery.view.media.RepeatingImageButton import dev.ragnarok.filegallery.view.media.ShuffleButton -import dev.ragnarok.filegallery.view.natives.rlottie.RLottieShapeableImageView +import dev.ragnarok.filegallery.view.natives.animation.ThorVGLottieShapeableView import kotlinx.coroutines.Job import kotlin.math.min @@ -94,7 +94,7 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee private var ivBackground: View? = null // Handler used to update the current time - private var mRefreshDisposable = CancelableJob() + private var mRefreshJob = CancelableJob() private var mStartSeekPos: Long = 0 private var mLastSeekEventTime: Long = 0 private var coverAdapter: CoverAdapter? = null @@ -259,10 +259,9 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee super.onPageSelected(position) if (currentPage != position) { currentPage = position - playDispose.set( - delayTaskFlow(400) - .toMain { MusicPlaybackController.skip(position) } - ) + playDispose.cancel() + playDispose += delayTaskFlow(400) + .toMain { MusicPlaybackController.skip(position) } ivCoverPager?.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) } } @@ -363,7 +362,7 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee override fun onDestroy() { playDispose.cancel() mCompositeJob.cancel() - mRefreshDisposable.cancel() + mRefreshJob.cancel() PicassoInstance.with().cancelTag(PLAYER_TAG) super.onDestroy() } @@ -520,10 +519,11 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee * @param delay When to update */ private fun queueNextRefresh(delay: Long) { - mRefreshDisposable.cancel() - mRefreshDisposable.set(delayTaskFlow(delay) - .toMain { queueNextRefresh(refreshCurrentTime()) } - ) + mRefreshJob.cancel() + mRefreshJob += delayTaskFlow(delay) + .toMain { + queueNextRefresh(refreshCurrentTime()) + } } private fun resolveControlViews() { @@ -677,7 +677,7 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee } private inner class CoverViewHolder(view: View) : RecyclerView.ViewHolder(view) { - val ivCover: RLottieShapeableImageView = view.findViewById(R.id.cover) + val ivCover: ThorVGLottieShapeableView = view.findViewById(R.id.cover) val holderTarget = object : BitmapTarget { override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) { @@ -696,8 +696,6 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee if (FenrirNative.isNativeLoaded) { ivCover.fromRes( R.raw.auidio_no_cover, - 450, - 450, intArrayOf( 0x333333, CurrentTheme.getColorSurface(requireActivity()), @@ -705,7 +703,7 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee CurrentTheme.getColorOnSurface(requireActivity()) ) ) - ivCover.playAnimation() + ivCover.startAnimation() } else { ivCover.setImageResource(R.drawable.itunes) ivCover.drawable?.setTint( @@ -731,8 +729,6 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee if (FenrirNative.isNativeLoaded) { ivCover.fromRes( R.raw.auidio_no_cover, - 450, - 450, intArrayOf( 0x333333, CurrentTheme.getColorSurface(requireActivity()), @@ -740,7 +736,7 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee CurrentTheme.getColorOnSurface(requireActivity()) ) ) - ivCover.playAnimation() + ivCover.startAnimation() } else { ivCover.setImageResource(R.drawable.itunes) ivCover.drawable.setTint(CurrentTheme.getColorOnSurface(requireActivity())) @@ -772,16 +768,6 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee override fun onViewDetachedFromWindow(holder: CoverViewHolder) { super.onViewDetachedFromWindow(holder) PicassoInstance.with().cancelRequest(holder.ivCover) - if (holder.ivCover.drawable is Animatable) { - (holder.ivCover.drawable as Animatable).stop() - } - } - - override fun onViewAttachedToWindow(holder: CoverViewHolder) { - super.onViewAttachedToWindow(holder) - if (holder.ivCover.drawable is Animatable) { - (holder.ivCover.drawable as Animatable).start() - } } override fun onBindViewHolder(holder: CoverViewHolder, position: Int) { diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/PreferencesFragment.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/PreferencesFragment.kt index 73dc61606..11258aa78 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/PreferencesFragment.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/PreferencesFragment.kt @@ -75,7 +75,7 @@ import dev.ragnarok.filegallery.util.serializeble.prefs.Preferences import dev.ragnarok.filegallery.util.toast.CustomSnackbars import dev.ragnarok.filegallery.util.toast.CustomToast.Companion.createCustomToast import dev.ragnarok.filegallery.view.MySearchView -import dev.ragnarok.filegallery.view.natives.rlottie.RLottieImageView +import dev.ragnarok.filegallery.view.natives.animation.ThorVGLottieView import okio.buffer import okio.source import java.io.File @@ -940,12 +940,10 @@ class PreferencesFragment : AbsPreferencesFragment(), PreferencesAdapter.OnScree summary = BuildConfig.VERSION_NAME onClick { val view = View.inflate(requireActivity(), R.layout.dialog_about_us, null) - val anim: RLottieImageView = view.findViewById(R.id.lottie_animation) + val anim: ThorVGLottieView = view.findViewById(R.id.lottie_animation) if (FenrirNative.isNativeLoaded) { anim.fromRes( R.raw.fenrir, - Utils.dp(170f), - Utils.dp(170f), intArrayOf( 0x333333, getColorPrimary(requireActivity()), @@ -953,7 +951,7 @@ class PreferencesFragment : AbsPreferencesFragment(), PreferencesAdapter.OnScree getColorSecondary(requireActivity()) ) ) - anim.playAnimation() + anim.startAnimation() } MaterialAlertDialogBuilder(requireActivity()) .setView(view) diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/base/compat/ViewHostDelegate.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/base/compat/ViewHostDelegate.kt index bbadc340d..30084c279 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/base/compat/ViewHostDelegate.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/base/compat/ViewHostDelegate.kt @@ -86,9 +86,9 @@ class ViewHostDelegate

, V : IMvpView> { } fun onSaveInstanceState(outState: Bundle) { - presenter?.run { + presenter?.let { lastKnownPresenterState = Bundle() - saveState(lastKnownPresenterState ?: return@run) + it.saveState(lastKnownPresenterState ?: return@let) } outState.putBundle(SAVE_PRESENTER_STATE, lastKnownPresenterState) diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/filemanager/FileManagerAdapter.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/filemanager/FileManagerAdapter.kt index b59de5a47..b9217e0b0 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/filemanager/FileManagerAdapter.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/filemanager/FileManagerAdapter.kt @@ -39,7 +39,7 @@ import dev.ragnarok.filegallery.util.Utils import dev.ragnarok.filegallery.util.coroutines.CancelableJob import dev.ragnarok.filegallery.util.coroutines.CoroutinesUtils.fromIOToMain import dev.ragnarok.filegallery.util.toast.CustomToast -import dev.ragnarok.filegallery.view.natives.rlottie.RLottieImageView +import dev.ragnarok.filegallery.view.natives.animation.ThorVGLottieView import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import java.io.File @@ -408,15 +408,16 @@ class FileManagerAdapter(private var context: Context, private var data: List) : RecyclerView.Adapter() { @@ -410,15 +410,16 @@ class FileManagerRemoteAdapter(private var context: Context, private var data: L holder.current.visibility = View.VISIBLE holder.current.fromRes( R.raw.select_fire, - Utils.dp(100f), - Utils.dp(100f), intArrayOf(0xFF812E, colorPrimary), true ) - holder.current.playAnimation() + holder.current.startAnimation() } else { holder.current.visibility = View.GONE - holder.current.clearAnimationDrawable() + holder.current.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } holder.fileInfo.setBackgroundColor(messageBubbleColor) @@ -619,15 +620,16 @@ class FileManagerRemoteAdapter(private var context: Context, private var data: L holder.current.visibility = View.VISIBLE holder.current.fromRes( R.raw.select_fire, - Utils.dp(100f), - Utils.dp(100f), intArrayOf(0xFF812E, colorPrimary), true ) - holder.current.playAnimation() + holder.current.startAnimation() } else { holder.current.visibility = View.GONE - holder.current.clearAnimationDrawable() + holder.current.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } holder.fileInfo.setBackgroundColor(messageBubbleColor) @@ -671,7 +673,7 @@ class FileManagerRemoteAdapter(private var context: Context, private var data: L val fileName: TextView = itemView.findViewById(R.id.item_file_name) val fileDetails: TextView = itemView.findViewById(R.id.item_file_details) val icon: ImageView = itemView.findViewById(R.id.item_file_icon) - val current: RLottieImageView = itemView.findViewById(R.id.current) + val current: ThorVGLottieView = itemView.findViewById(R.id.current) val tagged: ImageView = itemView.findViewById(R.id.item_tagged) val fileInfo: LinearLayout = itemView.findViewById(R.id.item_file_info) } @@ -680,8 +682,8 @@ class FileManagerRemoteAdapter(private var context: Context, private var data: L val fileName: TextView = itemView.findViewById(R.id.item_file_name) val fileDetails: TextView = itemView.findViewById(R.id.item_file_details) val icon: ImageView = itemView.findViewById(R.id.item_file_icon) - val current: RLottieImageView = itemView.findViewById(R.id.current) - val visual: RLottieImageView = itemView.findViewById(R.id.item_audio_visual) + val current: ThorVGLottieView = itemView.findViewById(R.id.current) + val visual: ThorVGLottieView = itemView.findViewById(R.id.item_audio_visual) val tagged: ImageView = itemView.findViewById(R.id.item_tagged) val fileInfo: LinearLayout = itemView.findViewById(R.id.item_file_info) } diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/localserver/filemanagerremote/FileManagerRemoteFragment.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/localserver/filemanagerremote/FileManagerRemoteFragment.kt index a9d3db7fa..391c34ea0 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/localserver/filemanagerremote/FileManagerRemoteFragment.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/localserver/filemanagerremote/FileManagerRemoteFragment.kt @@ -34,14 +34,13 @@ import dev.ragnarok.filegallery.model.Video import dev.ragnarok.filegallery.place.PlaceFactory import dev.ragnarok.filegallery.settings.CurrentTheme import dev.ragnarok.filegallery.settings.Settings -import dev.ragnarok.filegallery.util.Utils import dev.ragnarok.filegallery.util.ViewUtils import dev.ragnarok.filegallery.util.coroutines.CancelableJob import dev.ragnarok.filegallery.util.coroutines.CoroutinesUtils.delayTaskFlow import dev.ragnarok.filegallery.util.coroutines.CoroutinesUtils.toMain import dev.ragnarok.filegallery.util.toast.CustomToast import dev.ragnarok.filegallery.view.MySearchView -import dev.ragnarok.filegallery.view.natives.rlottie.RLottieImageView +import dev.ragnarok.filegallery.view.natives.animation.ThorVGLottieView class FileManagerRemoteFragment : BaseMvpFragment(), @@ -49,7 +48,7 @@ class FileManagerRemoteFragment : private var mRecyclerView: RecyclerView? = null private var mLayoutManager: GridLayoutManager? = null private var empty: TextView? = null - private var loading: RLottieImageView? = null + private var loading: ThorVGLottieView? = null private var tvCurrentDir: TextView? = null private var mAdapter: FileManagerRemoteAdapter? = null private var mSwipeRefreshLayout: SwipeRefreshLayout? = null @@ -117,13 +116,19 @@ class FileManagerRemoteFragment : animLoad = ObjectAnimator.ofFloat(loading, View.ALPHA, 0.0f).setDuration(1000) animLoad?.addListener(object : StubAnimatorListener() { override fun onAnimationEnd(animation: Animator) { - loading?.clearAnimationDrawable() + loading?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) loading?.visibility = View.GONE loading?.alpha = 1f } override fun onAnimationCancel(animation: Animator) { - loading?.clearAnimationDrawable() + loading?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) loading?.visibility = View.GONE loading?.alpha = 1f } @@ -192,8 +197,6 @@ class FileManagerRemoteFragment : loading?.alpha = 1f loading?.fromRes( R.raw.s_loading, - Utils.dp(180f), - Utils.dp(180f), intArrayOf( 0x333333, CurrentTheme.getColorPrimary(requireActivity()), @@ -201,7 +204,7 @@ class FileManagerRemoteFragment : CurrentTheme.getColorSecondary(requireActivity()) ) ) - loading?.playAnimation() + loading?.startAnimation() }) } } diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/localserver/photoslocalserver/LocalServerPhotosAdapter.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/localserver/photoslocalserver/LocalServerPhotosAdapter.kt index 2eb073b51..68eb4b46d 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/localserver/photoslocalserver/LocalServerPhotosAdapter.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/localserver/photoslocalserver/LocalServerPhotosAdapter.kt @@ -17,7 +17,7 @@ import dev.ragnarok.filegallery.settings.CurrentTheme.getColorSecondary import dev.ragnarok.filegallery.util.AppTextUtils import dev.ragnarok.filegallery.util.Utils import dev.ragnarok.filegallery.view.AspectRatioImageView -import dev.ragnarok.filegallery.view.natives.rlottie.RLottieImageView +import dev.ragnarok.filegallery.view.natives.animation.ThorVGLottieView class LocalServerPhotosAdapter(private val mContext: Context, private var data: List) : RecyclerView.Adapter() { @@ -47,15 +47,16 @@ class LocalServerPhotosAdapter(private val mContext: Context, private var data: viewHolder.current.visibility = View.VISIBLE viewHolder.current.fromRes( R.raw.select_fire, - Utils.dp(100f), - Utils.dp(100f), intArrayOf(0xFF812E, colorPrimary), true ) - viewHolder.current.playAnimation() + viewHolder.current.startAnimation() } else { viewHolder.current.visibility = View.GONE - viewHolder.current.clearAnimationDrawable() + viewHolder.current.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } val targetUrl = photo.preview_url @@ -98,7 +99,7 @@ class LocalServerPhotosAdapter(private val mContext: Context, private var data: val photoImageView: AspectRatioImageView = itemView.findViewById(R.id.imageView) val tvDate: TextView = itemView.findViewById(R.id.vk_photo_item_date) val bottomTop: ViewGroup = itemView.findViewById(R.id.vk_photo_item_top) - val current: RLottieImageView = itemView.findViewById(R.id.current) + val current: ThorVGLottieView = itemView.findViewById(R.id.current) } } \ No newline at end of file diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/tagdir/TagDirAdapter.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/tagdir/TagDirAdapter.kt index 1b03aec8a..467195fb1 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/tagdir/TagDirAdapter.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/tagdir/TagDirAdapter.kt @@ -26,7 +26,7 @@ import dev.ragnarok.filegallery.toColor import dev.ragnarok.filegallery.util.Utils import dev.ragnarok.filegallery.util.coroutines.CancelableJob import dev.ragnarok.filegallery.util.coroutines.CoroutinesUtils.sharedFlowToMain -import dev.ragnarok.filegallery.view.natives.rlottie.RLottieImageView +import dev.ragnarok.filegallery.view.natives.animation.ThorVGLottieView class TagDirAdapter(context: Context, private var data: List) : RecyclerView.Adapter() { @@ -127,15 +127,16 @@ class TagDirAdapter(context: Context, private var data: List) : holder.current.visibility = View.VISIBLE holder.current.fromRes( R.raw.select_fire, - Utils.dp(100f), - Utils.dp(100f), intArrayOf(0xFF812E, colorPrimary), true ) - holder.current.playAnimation() + holder.current.startAnimation() } else { holder.current.visibility = View.GONE - holder.current.clearAnimationDrawable() + holder.current.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } @@ -203,15 +204,16 @@ class TagDirAdapter(context: Context, private var data: List) : holder.current.visibility = View.VISIBLE holder.current.fromRes( R.raw.select_fire, - Utils.dp(100f), - Utils.dp(100f), intArrayOf(0xFF812E, colorPrimary), true ) - holder.current.playAnimation() + holder.current.startAnimation() } else { holder.current.visibility = View.GONE - holder.current.clearAnimationDrawable() + holder.current.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } @@ -265,7 +267,7 @@ class TagDirAdapter(context: Context, private var data: List) : val fileName: TextView = itemView.findViewById(R.id.item_file_name) val fileDetails: TextView = itemView.findViewById(R.id.item_file_details) val icon: ImageView = itemView.findViewById(R.id.item_file_icon) - val current: RLottieImageView = itemView.findViewById(R.id.current) + val current: ThorVGLottieView = itemView.findViewById(R.id.current) init { itemView.setOnCreateContextMenuListener(this) @@ -293,8 +295,8 @@ class TagDirAdapter(context: Context, private var data: List) : val fileName: TextView = itemView.findViewById(R.id.item_file_name) val fileDetails: TextView = itemView.findViewById(R.id.item_file_details) val icon: ImageView = itemView.findViewById(R.id.item_file_icon) - val current: RLottieImageView = itemView.findViewById(R.id.current) - val visual: RLottieImageView = itemView.findViewById(R.id.item_audio_visual) + val current: ThorVGLottieView = itemView.findViewById(R.id.current) + val visual: ThorVGLottieView = itemView.findViewById(R.id.item_audio_visual) init { itemView.setOnCreateContextMenuListener(this) diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/theme/ThemeAdapter.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/theme/ThemeAdapter.kt index 4ebdd989b..8cfdcbd50 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/theme/ThemeAdapter.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/theme/ThemeAdapter.kt @@ -16,8 +16,7 @@ import dev.ragnarok.filegallery.settings.CurrentTheme.getColorSecondary import dev.ragnarok.filegallery.settings.CurrentTheme.getColorWhiteContrastFix import dev.ragnarok.filegallery.settings.Settings.get import dev.ragnarok.filegallery.settings.theme.ThemeValue -import dev.ragnarok.filegallery.util.Utils -import dev.ragnarok.filegallery.view.natives.rlottie.RLottieImageView +import dev.ragnarok.filegallery.view.natives.animation.ThorVGLottieView class ThemeAdapter(private var data: List, context: Context) : RecyclerView.Adapter() { @@ -75,8 +74,6 @@ class ThemeAdapter(private var data: List, context: Context) : if (isSelected) { holder.selected.fromRes( R.raw.theme_selected, - Utils.dp(120f), - Utils.dp(120f), intArrayOf( 0x333333, getColorWhiteContrastFix(holder.selected.context), @@ -86,9 +83,12 @@ class ThemeAdapter(private var data: List, context: Context) : getColorSecondary(holder.selected.context) ) ) - holder.selected.playAnimation() + holder.selected.startAnimation() } else { - holder.selected.clearAnimationDrawable() + holder.selected.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } else { if (isSelected) { @@ -113,8 +113,6 @@ class ThemeAdapter(private var data: List, context: Context) : if (isSelected) { holder.selected.fromRes( R.raw.theme_selected, - Utils.dp(120f), - Utils.dp(120f), intArrayOf( 0x333333, getColorWhiteContrastFix(holder.selected.context), @@ -124,9 +122,12 @@ class ThemeAdapter(private var data: List, context: Context) : getColorSecondary(holder.selected.context) ) ) - holder.selected.playAnimation() + holder.selected.startAnimation() } else { - holder.selected.clearAnimationDrawable() + holder.selected.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } else { if (isSelected) { @@ -169,7 +170,7 @@ class ThemeAdapter(private var data: List, context: Context) : internal class ThemeHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val primary: ImageView = itemView.findViewById(R.id.theme_icon_primary) val secondary: ImageView = itemView.findViewById(R.id.theme_icon_secondary) - val selected: RLottieImageView = itemView.findViewById(R.id.selected) + val selected: ThorVGLottieView = itemView.findViewById(R.id.selected) val gradient: ImageView = itemView.findViewById(R.id.theme_icon_gradient) val clicked: ViewGroup = itemView.findViewById(R.id.theme_type) val title: TextView = itemView.findViewById(R.id.item_title) @@ -177,7 +178,7 @@ class ThemeAdapter(private var data: List, context: Context) : } internal class SpecialThemeHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - val selected: RLottieImageView = itemView.findViewById(R.id.selected) + val selected: ThorVGLottieView = itemView.findViewById(R.id.selected) val clicked: ViewGroup = itemView.findViewById(R.id.theme_type) val title: TextView = itemView.findViewById(R.id.item_title) val special_title: TextView = itemView.findViewById(R.id.special_text) diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/Utils.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/Utils.kt index 198bd4592..2fe1217b3 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/Utils.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/Utils.kt @@ -17,7 +17,6 @@ import androidx.core.graphics.ColorUtils import androidx.media3.common.MediaItem import androidx.viewpager2.widget.ViewPager2 import com.google.android.material.floatingactionbutton.FloatingActionButton -import dev.ragnarok.fenrir.module.rlottie.RLottieDrawable import dev.ragnarok.filegallery.BuildConfig import dev.ragnarok.filegallery.Constants import dev.ragnarok.filegallery.R @@ -25,7 +24,7 @@ import dev.ragnarok.filegallery.media.exo.OkHttpDataSource import dev.ragnarok.filegallery.model.Lang import dev.ragnarok.filegallery.settings.Settings.get import dev.ragnarok.filegallery.util.AppTextUtils.updateDateLang -import dev.ragnarok.filegallery.view.natives.rlottie.RLottieImageView +import dev.ragnarok.filegallery.view.natives.animation.ThorVGLottieView import dev.ragnarok.filegallery.view.pager.* import okhttp3.Interceptor import okhttp3.OkHttpClient @@ -209,28 +208,26 @@ object Utils { } } - fun doWavesLottie(visual: RLottieImageView, Play: Boolean) { - visual.clearAnimationDrawable() + fun doWavesLottie(visual: ThorVGLottieView?, Play: Boolean) { if (Play) { - visual.setAutoRepeat(true) - visual.fromRes(R.raw.waves, dp(28f), dp(28f)) + visual?.setRepeat(true) + visual?.fromRes(R.raw.waves) } else { - visual.setAutoRepeat(false) - visual.fromRes(R.raw.waves_end, dp(28f), dp(28f)) + visual?.setRepeat(false) + visual?.fromRes(R.raw.waves_end) } - visual.playAnimation() + visual?.startAnimation() } - fun doWavesLottieBig(visual: RLottieImageView, Play: Boolean) { - visual.clearAnimationDrawable() + fun doWavesLottieBig(visual: ThorVGLottieView?, Play: Boolean) { if (Play) { - visual.setAutoRepeat(true) - visual.fromRes(R.raw.s_waves, dp(128f), dp(128f)) + visual?.setRepeat(true) + visual?.fromRes(R.raw.s_waves) } else { - visual.setAutoRepeat(false) - visual.fromRes(R.raw.s_waves_end, dp(128f), dp(128f)) + visual?.setRepeat(false) + visual?.fromRes(R.raw.s_waves_end) } - visual.playAnimation() + visual?.startAnimation() } fun isColorDark(color: Int): Boolean { @@ -263,7 +260,6 @@ object Utils { } } if (display != null) { - RLottieDrawable.updateScreenRefreshRate(display.refreshRate.toInt()) val configuration = context.resources.configuration if (configuration.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED) { val newSize = ceil((configuration.screenWidthDp * density).toDouble()) diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/TouchImageView.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/TouchImageView.kt index fcee6f785..c84f4645d 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/TouchImageView.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/TouchImageView.kt @@ -27,22 +27,52 @@ import androidx.appcompat.widget.AppCompatImageView import com.squareup.picasso3.Rotatable import dev.ragnarok.fenrir.module.FenrirNative import dev.ragnarok.fenrir.module.animation.AnimatedFileDrawable +import dev.ragnarok.fenrir.module.animation.AnimatedFileDrawable.LoadedFrom +import dev.ragnarok.filegallery.Constants import dev.ragnarok.filegallery.R import dev.ragnarok.filegallery.getParcelableCompat import dev.ragnarok.filegallery.getSerializableCompat +import dev.ragnarok.filegallery.nonNullNoEmpty +import dev.ragnarok.filegallery.picasso.PicassoInstance +import dev.ragnarok.filegallery.util.Utils +import dev.ragnarok.filegallery.util.coroutines.CancelableJob +import dev.ragnarok.filegallery.util.coroutines.CoroutinesUtils.fromIOToMain +import dev.ragnarok.filegallery.view.natives.animation.AnimationNetworkCache +import kotlinx.coroutines.flow.flow +import okhttp3.Call +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response import java.io.File +import java.lang.ref.WeakReference +import kotlin.coroutines.cancellation.CancellationException import kotlin.math.abs import kotlin.math.max import kotlin.math.min @SuppressLint("ClickableViewAccessibility") @Suppress("unused") -open class TouchImageView @JvmOverloads constructor( +class TouchImageView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : AppCompatImageView(context, attrs, defStyle) { + //%%~Animation + private var mDisposable = CancelableJob() + private val cache: AnimationNetworkCache = AnimationNetworkCache(context) + private var animatedDrawable: AnimatedFileDrawable? = null + private var attachedToWindow = false + //~%%Animation + + @LoadedFrom + private var loadedFrom = LoadedFrom.NO + private var filePathTmp: String? = null + private var keyTmp: String? = null + private var isPlaying: Boolean? = null + private var tmpFade: Boolean? = null + private var fallbackTmp: String? = null + /** * Get the current zoom. This is the zoom relative to the initial * scale, not the original resource. @@ -124,7 +154,7 @@ open class TouchImageView @JvmOverloads constructor( var orientationLocked = OrientationLocked.HORIZONTAL init { - super.setClickable(true) + super.isClickable = true orientation = resources.configuration.orientation scaleDetector = ScaleGestureDetector(context, ScaleListener()) gestureDetector = GestureDetector(context, GestureListener()) @@ -156,118 +186,276 @@ open class TouchImageView @JvmOverloads constructor( } } - fun setRotateImageToFitScreen(rotateImageToFitScreen: Boolean) { - isRotateImageToFitScreen = rotateImageToFitScreen - } - - interface StateListener { - fun onChangeState(imageActionState: ImageActionState, zoomed: Boolean) - } - - fun setOnStateChangeListener(onStateListener: StateListener) { - stateListener = onStateListener - } - - override fun setOnTouchListener(onTouchListener: OnTouchListener?) { - userTouchListener = onTouchListener - } - - fun setOnTouchImageViewListener(onTouchImageViewListener: OnTouchImageViewListener) { - touchImageViewListener = onTouchImageViewListener + //%%~Animation + private fun setAnimationByUrlCache( + key: String, + fallback: String?, + autoPlay: Boolean, + fade: Boolean + ) { + if (!FenrirNative.isNativeLoaded) { + return + } + val ch = cache.fetch(key) + if (ch == null) { + return + } + if (filePathTmp == ch.absolutePath && fallbackTmp == fallback && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = false) + loadedFrom = LoadedFrom.FILE + filePathTmp = ch.absolutePath + tmpFade = fade + isPlaying = autoPlay + fallbackTmp = fallback + + if (attachedToWindow) { + createAnimationDrawable() + } } - fun setOnDoubleTapListener(onDoubleTapListener: OnDoubleTapListener) { - doubleTapListener = onDoubleTapListener + fun fromAnimationNet( + key: String, + url: String?, + fallback: String?, + client: OkHttpClient.Builder, + autoPlay: Boolean + ) { + if (!FenrirNative.isNativeLoaded || url.isNullOrEmpty()) { + if (loadedFrom == LoadedFrom.NET) { + loadedFrom = LoadedFrom.NO + } + return + } + if (filePathTmp == url && keyTmp == key && fallbackTmp == fallback && loadedFrom == LoadedFrom.NET) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + loadedFrom = LoadedFrom.NET + filePathTmp = url + keyTmp = key + isPlaying = autoPlay + tmpFade = true + fallbackTmp = fallback + + if (cache.isCachedFile(key)) { + setAnimationByUrlCache(key, fallback, autoPlay, true) + return + } + mDisposable.set(flow { + var call: Call? = null + try { + val request: Request = Request.Builder() + .url(url) + .build() + call = client.build().newCall(request) + val response: Response = call.execute() + if (!response.isSuccessful) { + emit(false) + return@flow + } + cache.writeTempCacheFile(key, response.body.source()) + response.close() + cache.renameTempFile(key) + emit(true) + } catch (e: CancellationException) { + call?.cancel() + throw e + } + }.fromIOToMain { u -> + if (u) { + setAnimationByUrlCache(key, fallback, autoPlay, true) + } + }) } - fun setOnTouchCoordinatesListener(onTouchCoordinatesListener: OnTouchCoordinatesListener) { - touchCoordinatesListener = onTouchCoordinatesListener + fun clearAnimationDrawable(callSuper: Boolean, clearState: Boolean, cancelTask: Boolean) { + if (cancelTask) { + mDisposable.cancel() + } + if (animatedDrawable != null) { + animatedDrawable?.callback = null + animatedDrawable?.recycle() + animatedDrawable = null + } + if (callSuper) { + super.setImageDrawable(null) + } + if (clearState) { + isPlaying = false + loadedFrom = LoadedFrom.NO + filePathTmp = null + tmpFade = null + fallbackTmp = null + } } - open fun fromAnimFile(file: File) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - AnimatedFileDrawable( - file, + private fun createAnimationDrawable() { + if (FenrirNative.isNativeLoaded && attachedToWindow && loadedFrom != LoadedFrom.NO && animatedDrawable == null && filePathTmp != null) { + val ref = WeakReference(this) + animatedDrawable = AnimatedFileDrawable( + filePathTmp ?: "", 0, 100, 100, - true, + tmpFade == true, object : AnimatedFileDrawable.DecoderListener { override fun onError() { - + ref.get()?.let { + fallbackTmp.nonNullNoEmpty { k -> + PicassoInstance.with().load(k).into(it) + } + } } + }) - ) - } + if (animatedDrawable?.isDecoded != true) { + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + return + } + tmpFade = false + animatedDrawable?.setAllowDecodeSingleFrame(true) - open fun clearAnimationDrawable() { - if (animDrawable != null) { - animDrawable?.stop() - animDrawable?.callback = null - animDrawable?.recycle() - animDrawable = null + imageRenderedAtLeastOnce = false + super.setImageDrawable(animatedDrawable) + savePreviousImageValues() + fitImageToView() + + if (isPlaying == true) { + playAnimation() + } } } - private fun setAnimation(videoDrawable: AnimatedFileDrawable) { - if (!videoDrawable.isDecoded) return - animDrawable = videoDrawable - animDrawable?.setAllowDecodeSingleFrame(true) - animDrawable?.callback = this - animDrawable?.start() - setImageDrawable(animDrawable) + fun fromAnimationFile(file: File, autoPlay: Boolean) { + if (!FenrirNative.isNativeLoaded || !file.exists()) { + return + } + if (filePathTmp == file.absolutePath && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + loadedFrom = LoadedFrom.FILE + filePathTmp = file.absolutePath + tmpFade = false + isPlaying = autoPlay + if (attachedToWindow) { + createAnimationDrawable() + } } override fun onAttachedToWindow() { + attachedToWindow = true super.onAttachedToWindow() - animDrawable?.callback = this - animDrawable?.start() + when { + loadedFrom == LoadedFrom.NET -> { + filePathTmp?.let { + keyTmp?.let { s -> + fromAnimationNet( + s, + it, + fallbackTmp, + Utils.createOkHttp(Constants.PICASSO_TIMEOUT), + isPlaying == true, + ) + } + return + } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = false) + } + + loadedFrom != LoadedFrom.NO -> { + createAnimationDrawable() + } + } } override fun onDetachedFromWindow() { + attachedToWindow = false super.onDetachedFromWindow() - animDrawable?.stop() - animDrawable?.callback = null + if (loadedFrom != LoadedFrom.NO) { + clearAnimationDrawable(callSuper = true, clearState = false, cancelTask = true) + } } - open fun isPlaying(): Boolean { + fun isPlayingAnimation(): Boolean { return animDrawable?.isRunning == true } + fun playAnimation() { + animatedDrawable?.start() + isPlaying = true + } + + fun resetFrame() { + animatedDrawable?.seekTo(0, true) + } + + fun stopAnimation() { + animatedDrawable?.let { + it.stop() + isPlaying = false + } + } + //~Animation + + fun setRotateImageToFitScreen(rotateImageToFitScreen: Boolean) { + isRotateImageToFitScreen = rotateImageToFitScreen + } + + interface StateListener { + fun onChangeState(imageActionState: ImageActionState, zoomed: Boolean) + } + + fun setOnStateChangeListener(onStateListener: StateListener) { + stateListener = onStateListener + } + + override fun setOnTouchListener(onTouchListener: OnTouchListener?) { + userTouchListener = onTouchListener + } + + fun setOnTouchImageViewListener(onTouchImageViewListener: OnTouchImageViewListener) { + touchImageViewListener = onTouchImageViewListener + } + + fun setOnDoubleTapListener(onDoubleTapListener: OnDoubleTapListener) { + doubleTapListener = onDoubleTapListener + } + + fun setOnTouchCoordinatesListener(onTouchCoordinatesListener: OnTouchCoordinatesListener) { + touchCoordinatesListener = onTouchCoordinatesListener + } + override fun setImageResource(resId: Int) { - clearAnimationDrawable() imageRenderedAtLeastOnce = false super.setImageResource(resId) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) savePreviousImageValues() fitImageToView() } override fun setImageBitmap(bm: Bitmap?) { - clearAnimationDrawable() imageRenderedAtLeastOnce = false super.setImageBitmap(bm) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) savePreviousImageValues() fitImageToView() } override fun setImageDrawable(drawable: Drawable?) { - if (drawable !is AnimatedFileDrawable) { - clearAnimationDrawable() - } imageRenderedAtLeastOnce = false super.setImageDrawable(drawable) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) savePreviousImageValues() fitImageToView() } override fun setImageURI(uri: Uri?) { - clearAnimationDrawable() imageRenderedAtLeastOnce = false super.setImageURI(uri) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) savePreviousImageValues() fitImageToView() } @@ -285,7 +473,7 @@ open class TouchImageView @JvmOverloads constructor( } } - override fun getScaleType() = touchScaleType!! + override fun getScaleType() = touchScaleType ?: ScaleType.MATRIX /** * Returns false if image is in initial, unzoomed state. False, otherwise. @@ -544,21 +732,22 @@ open class TouchImageView @JvmOverloads constructor( } internal fun orientationMismatch(drawable: Drawable?): Boolean { - return viewWidth > viewHeight != drawable!!.intrinsicWidth > drawable.intrinsicHeight + return viewWidth > viewHeight != (drawable?.intrinsicWidth + ?: 0) > (drawable?.intrinsicHeight ?: 0) } private fun getDrawableWidth(drawable: Drawable?): Int { return if (orientationMismatch(drawable) && isRotateImageToFitScreen) { - drawable!!.intrinsicHeight + drawable?.intrinsicHeight ?: 0 } else - drawable!!.intrinsicWidth + drawable?.intrinsicWidth ?: 0 } private fun getDrawableHeight(drawable: Drawable?): Int { return if (orientationMismatch(drawable) && isRotateImageToFitScreen) { - drawable!!.intrinsicWidth + drawable?.intrinsicWidth ?: 0 } else - drawable!!.intrinsicHeight + drawable?.intrinsicHeight ?: 0 } /** @@ -743,14 +932,11 @@ open class TouchImageView @JvmOverloads constructor( } ScaleType.CENTER_INSIDE -> { - run { - scaleY = min(1f, min(scaleX, scaleY)) - scaleX = scaleY - } - run { - scaleY = min(scaleX, scaleY) - scaleX = scaleY - } + scaleY = min(1f, min(scaleX, scaleY)) + scaleX = scaleY + + scaleY = min(scaleX, scaleY) + scaleX = scaleY } ScaleType.FIT_CENTER, ScaleType.FIT_START, ScaleType.FIT_END -> { @@ -1235,7 +1421,7 @@ open class TouchImageView @JvmOverloads constructor( * to the bounds of the bitmap size. * @return Coordinates of the point touched, in the coordinate system of the original drawable. */ - protected fun transformCoordTouchToBitmap(x: Float, y: Float, clipToBitmap: Boolean): PointF { + private fun transformCoordTouchToBitmap(x: Float, y: Float, clipToBitmap: Boolean): PointF { touchMatrix.getValues(floatMatrix) val origW = drawable.intrinsicWidth.toFloat() val origH = drawable.intrinsicHeight.toFloat() @@ -1258,7 +1444,7 @@ open class TouchImageView @JvmOverloads constructor( * @param by y-coordinate in original bitmap coordinate system * @return Coordinates of the point in the view's coordinate system. */ - protected fun transformCoordBitmapToTouch(bx: Float, by: Float): PointF { + private fun transformCoordBitmapToTouch(bx: Float, by: Float): PointF { touchMatrix.getValues(floatMatrix) val origW = drawable.intrinsicWidth.toFloat() val origH = drawable.intrinsicHeight.toFloat() diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/media/PathAnimator.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/media/PathAnimator.kt index ab7df1610..1153ed552 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/media/PathAnimator.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/media/PathAnimator.kt @@ -63,23 +63,19 @@ class PathAnimator( fun draw(canvas: Canvas, paint: Paint?, time: Float) { var startKeyFrame: KeyFrame? = null var endKeyFrame: KeyFrame? = null - run { - var a = 0 - val N = keyFrames.size - while (a < N) { - val keyFrame = keyFrames[a] - if ((startKeyFrame == null || (startKeyFrame - ?: return@run).time < keyFrame.time) && keyFrame.time <= time - ) { - startKeyFrame = keyFrame - } - if ((endKeyFrame == null || (endKeyFrame - ?: return@run).time > keyFrame.time) && keyFrame.time >= time - ) { - endKeyFrame = keyFrame - } - a++ + var a = 0 + var N = keyFrames.size + while (a < N) { + val keyFrame = keyFrames[a] + if ((startKeyFrame == null || startKeyFrame.time < keyFrame.time) && keyFrame.time <= time + ) { + startKeyFrame = keyFrame + } + if ((endKeyFrame == null || endKeyFrame.time > keyFrame.time) && keyFrame.time >= time + ) { + endKeyFrame = keyFrame } + a++ } if (endKeyFrame === startKeyFrame) { startKeyFrame = null @@ -88,24 +84,22 @@ class PathAnimator( endKeyFrame = startKeyFrame startKeyFrame = null } - if (endKeyFrame == null || startKeyFrame != null && (startKeyFrame - ?: return).commands.size != (endKeyFrame ?: return).commands.size + if (endKeyFrame == null || startKeyFrame != null && startKeyFrame.commands.size != endKeyFrame.commands.size ) { return } path.reset() - var a = 0 - val N = (endKeyFrame ?: return).commands.size + a = 0 + N = endKeyFrame.commands.size while (a < N) { val startCommand = - if (startKeyFrame != null) (startKeyFrame ?: return).commands[a] else null - val endCommand = (endKeyFrame ?: return).commands[a] + if (startKeyFrame != null) startKeyFrame.commands[a] else null + val endCommand = endKeyFrame.commands[a] if (startCommand != null && startCommand.javaClass != endCommand.javaClass) { return } val progress: Float = if (startKeyFrame != null) { - (time - (startKeyFrame ?: return).time) / ((endKeyFrame - ?: return).time - (startKeyFrame ?: return).time) + (time - startKeyFrame.time) / (endKeyFrame.time - startKeyFrame.time) } else { 1.0f } diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/AnimatedShapeableImageView.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/AnimatedShapeableImageView.kt index ff43604fe..d3d3ae5d8 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/AnimatedShapeableImageView.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/AnimatedShapeableImageView.kt @@ -3,13 +3,17 @@ package dev.ragnarok.filegallery.view.natives.animation import android.content.Context import android.graphics.Bitmap import android.graphics.drawable.Drawable +import android.net.Uri +import android.os.Parcelable import android.util.AttributeSet import androidx.annotation.RawRes import com.google.android.material.imageview.ShapeableImageView import dev.ragnarok.fenrir.module.FenrirNative import dev.ragnarok.fenrir.module.animation.AnimatedFileDrawable +import dev.ragnarok.fenrir.module.animation.AnimatedFileDrawable.LoadedFrom import dev.ragnarok.filegallery.Constants import dev.ragnarok.filegallery.R +import dev.ragnarok.filegallery.util.Utils import dev.ragnarok.filegallery.util.coroutines.CancelableJob import dev.ragnarok.filegallery.util.coroutines.CoroutinesUtils.fromIOToMain import dev.ragnarok.filegallery.view.natives.animation.AnimationNetworkCache.Companion.filenameForRes @@ -32,77 +36,91 @@ open class AnimatedShapeableImageView @JvmOverloads constructor( private val defaultHeight: Int private var animatedDrawable: AnimatedFileDrawable? = null private var attachedToWindow = false - private var playing = false private var decoderCallback: OnDecoderInit? = null private var mDisposable = CancelableJob() + + @LoadedFrom + private var loadedFrom = LoadedFrom.NO + private var filePathTmp: String? = null + private var keyTmp: String? = null + + @RawRes + private var rawResTmp: Int? = null + private var isPlaying: Boolean? = null + private var tmpFade: Boolean? = null + fun setDecoderCallback(decoderCallback: OnDecoderInit?) { this.decoderCallback = decoderCallback } - private fun setAnimationByUrlCache(url: String, fade: Boolean) { + private fun setAnimationByUrlCache(key: String, autoPlay: Boolean, fade: Boolean) { if (!FenrirNative.isNativeLoaded) { decoderCallback?.onLoaded(false) return } - val ch = cache.fetch(url) + val ch = cache.fetch(key) if (ch == null) { - setImageDrawable(null) decoderCallback?.onLoaded(false) return } - setAnimation( - AnimatedFileDrawable( - ch, - 0, - defaultWidth, - defaultHeight, - fade, - object : AnimatedFileDrawable.DecoderListener { - override fun onError() { - decoderCallback?.onLoaded(false) - } + if (filePathTmp == ch.absolutePath && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = false) + loadedFrom = LoadedFrom.FILE + filePathTmp = ch.absolutePath + tmpFade = fade + isPlaying = autoPlay - }) - ) - playAnimation() + if (attachedToWindow) { + createAnimationDrawable() + } } - private fun setAnimationByResCache(@RawRes res: Int, fade: Boolean) { + private fun setAnimationByResCache(@RawRes res: Int, autoPlay: Boolean, fade: Boolean) { if (!FenrirNative.isNativeLoaded) { decoderCallback?.onLoaded(false) return } val ch = cache.fetch(res) if (ch == null) { - setImageDrawable(null) decoderCallback?.onLoaded(false) return } - setAnimation( - AnimatedFileDrawable( - ch, - 0, - defaultWidth, - defaultHeight, - fade, - object : AnimatedFileDrawable.DecoderListener { - override fun onError() { - decoderCallback?.onLoaded(false) - } + if (filePathTmp == ch.absolutePath && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = false) + loadedFrom = LoadedFrom.FILE + filePathTmp = ch.absolutePath + tmpFade = fade + isPlaying = autoPlay - }) - ) - playAnimation() + if (attachedToWindow) { + createAnimationDrawable() + } } - fun fromNet(key: String, url: String?, client: OkHttpClient.Builder) { + fun fromNet(key: String, url: String?, client: OkHttpClient.Builder, autoPlay: Boolean) { if (!FenrirNative.isNativeLoaded || url.isNullOrEmpty()) { + if (loadedFrom == LoadedFrom.NET) { + loadedFrom = LoadedFrom.NO + } decoderCallback?.onLoaded(false) return } - clearAnimationDrawable() + if (filePathTmp == url && keyTmp == key && loadedFrom == LoadedFrom.NET) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + loadedFrom = LoadedFrom.NET + filePathTmp = url + keyTmp = key + isPlaying = autoPlay + tmpFade = true + if (cache.isCachedFile(key)) { - setAnimationByUrlCache(key, true) + setAnimationByUrlCache(key, autoPlay, true) return } mDisposable.set(flow { @@ -127,7 +145,7 @@ open class AnimatedShapeableImageView @JvmOverloads constructor( } }.fromIOToMain({ u -> if (u) { - setAnimationByUrlCache(key, true) + setAnimationByUrlCache(key, autoPlay, true) } else { decoderCallback?.onLoaded(false) } @@ -136,23 +154,34 @@ open class AnimatedShapeableImageView @JvmOverloads constructor( })) } - fun fromRes(@RawRes res: Int) { - if (!FenrirNative.isNativeLoaded) { + fun fromRes(@RawRes resId: Int, autoPlay: Boolean) { + if (!FenrirNative.isNativeLoaded || resId == -1) { + if (loadedFrom == LoadedFrom.RES) { + loadedFrom = LoadedFrom.NO + } decoderCallback?.onLoaded(false) return } - clearAnimationDrawable() - if (cache.isCachedRes(res)) { - setAnimationByResCache(res, true) + if (rawResTmp == resId && loadedFrom == LoadedFrom.RES) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + loadedFrom = LoadedFrom.RES + rawResTmp = resId + tmpFade = true + isPlaying = autoPlay + + if (cache.isCachedRes(resId)) { + setAnimationByResCache(resId, autoPlay, true) return } mDisposable.set(flow { try { - if (!copyRes(res)) { + if (!copyRes(resId)) { emit(false) return@flow } - cache.renameTempFile(res) + cache.renameTempFile(resId) } catch (_: Exception) { emit(false) return@flow @@ -160,69 +189,121 @@ open class AnimatedShapeableImageView @JvmOverloads constructor( emit(true) }.fromIOToMain { if (it) { - setAnimationByResCache(res, true) + setAnimationByResCache(resId, autoPlay, true) } else { decoderCallback?.onLoaded(false) } }) } - private fun setAnimation(videoDrawable: AnimatedFileDrawable) { - decoderCallback?.onLoaded(videoDrawable.isDecoded) - if (!videoDrawable.isDecoded) return - animatedDrawable = videoDrawable - animatedDrawable?.setAllowDecodeSingleFrame(true) - setImageDrawable(animatedDrawable) - } - - fun fromFile(file: File) { - if (!FenrirNative.isNativeLoaded) { - decoderCallback?.onLoaded(false) - return - } - clearAnimationDrawable() - setAnimation( - AnimatedFileDrawable( - file, + private fun createAnimationDrawable() { + if (FenrirNative.isNativeLoaded && attachedToWindow && loadedFrom != LoadedFrom.NO && animatedDrawable == null && filePathTmp != null) { + animatedDrawable = AnimatedFileDrawable( + filePathTmp ?: "", 0, defaultWidth, defaultHeight, - false, + tmpFade == true, object : AnimatedFileDrawable.DecoderListener { override fun onError() { decoderCallback?.onLoaded(false) } }) - ) + decoderCallback?.onLoaded(animatedDrawable?.isDecoded == true) + if (animatedDrawable?.isDecoded != true) { + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + return + } + tmpFade = false + animatedDrawable?.setAllowDecodeSingleFrame(true) + super.setImageDrawable(animatedDrawable) + if (isPlaying == true) { + playAnimation() + } + } } - fun clearAnimationDrawable() { - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() + fun fromFile(file: File) { + if (!FenrirNative.isNativeLoaded || !file.exists()) { + decoderCallback?.onLoaded(false) + return + } + if (filePathTmp == file.absolutePath && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + loadedFrom = LoadedFrom.FILE + filePathTmp = file.absolutePath + tmpFade = false + if (attachedToWindow) { + createAnimationDrawable() + } + } + + fun clearAnimationDrawable(callSuper: Boolean, clearState: Boolean, cancelTask: Boolean) { + if (cancelTask) { + mDisposable.cancel() + } + if (animatedDrawable != null) { + animatedDrawable?.callback = null + animatedDrawable?.recycle() animatedDrawable = null } - setImageDrawable(null) + if (callSuper) { + super.setImageDrawable(null) + } + if (clearState) { + isPlaying = false + loadedFrom = LoadedFrom.NO + filePathTmp = null + rawResTmp = null + tmpFade = null + } } override fun onAttachedToWindow() { - super.onAttachedToWindow() attachedToWindow = true - animatedDrawable?.callback = this - if (playing) { - animatedDrawable?.start() + super.onAttachedToWindow() + when { + loadedFrom == LoadedFrom.NET -> { + filePathTmp?.let { + keyTmp?.let { s -> + fromNet( + s, + it, + Utils.createOkHttp(Constants.PICASSO_TIMEOUT), + isPlaying == true, + ) + } + return + } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = false) + } + + loadedFrom == LoadedFrom.RES -> { + rawResTmp?.let { + fromRes( + it, + isPlaying == true, + ) + return + } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = false) + } + + loadedFrom != LoadedFrom.NO -> { + createAnimationDrawable() + } } } override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - mDisposable.cancel() attachedToWindow = false - animatedDrawable?.stop() - animatedDrawable?.callback = null + super.onDetachedFromWindow() + if (loadedFrom != LoadedFrom.NO) { + clearAnimationDrawable(callSuper = true, clearState = false, cancelTask = true) + } } fun isPlaying(): Boolean { @@ -231,69 +312,48 @@ open class AnimatedShapeableImageView @JvmOverloads constructor( override fun setImageDrawable(dr: Drawable?) { super.setImageDrawable(dr) - if (dr !is AnimatedFileDrawable) { - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) } override fun setImageBitmap(bm: Bitmap?) { super.setImageBitmap(bm) - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) } override fun setImageResource(resId: Int) { super.setImageResource(resId) - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageURI(uri: Uri?) { + super.setImageURI(uri) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) } fun playAnimation() { - if (animatedDrawable == null) { - return - } - playing = true - if (attachedToWindow) { - animatedDrawable?.start() - } + animatedDrawable?.start() + isPlaying = true } fun resetFrame() { - if (animatedDrawable == null) { - return - } - playing = true - if (attachedToWindow) { - animatedDrawable?.seekTo(0, true) - } + animatedDrawable?.seekTo(0, true) } fun stopAnimation() { - if (animatedDrawable == null) { - return - } - playing = false - if (attachedToWindow) { - animatedDrawable?.stop() + animatedDrawable?.let { + it.stop() + isPlaying = false } } + protected override fun onSaveInstanceState(): Parcelable? { + return super.onSaveInstanceState() + } + + protected override fun onRestoreInstanceState(state: Parcelable?) { + super.onRestoreInstanceState(state) + } + private fun copyRes(@RawRes rawRes: Int): Boolean { try { context.resources.openRawResource(rawRes).use { inputStream -> diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/rlottie/RLottieNetworkCache.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/ThorVGLottieNetworkCache.kt similarity index 86% rename from app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/rlottie/RLottieNetworkCache.kt rename to app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/ThorVGLottieNetworkCache.kt index a8d89eaa3..95073bc2c 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/rlottie/RLottieNetworkCache.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/ThorVGLottieNetworkCache.kt @@ -1,4 +1,4 @@ -package dev.ragnarok.filegallery.view.natives.rlottie +package dev.ragnarok.filegallery.view.natives.animation import android.content.Context import android.util.Log @@ -8,13 +8,13 @@ import okio.buffer import okio.sink import java.io.File -class RLottieNetworkCache(context: Context) { +class ThorVGLottieNetworkCache(context: Context) { private val appContext = context.applicationContext fun fetch(url: String): File? { val cachedFile = getCachedFile(url) ?: return null if (Constants.IS_DEBUG) { - Log.d("RLottieNetworkCache", "Cache hit for $url at ${cachedFile.absolutePath}") + Log.d("ThorVGLottieNetworkCache", "Cache hit for $url at ${cachedFile.absolutePath}") } return cachedFile } @@ -40,12 +40,12 @@ class RLottieNetworkCache(context: Context) { val newFile = File(newFileName) val renamed = file.renameTo(newFile) if (Constants.IS_DEBUG) { - Log.d("RLottieNetworkCache", "Copying temp file to real file ($newFile)") + Log.d("ThorVGLottieNetworkCache", "Copying temp file to real file ($newFile)") } if (!renamed) { if (Constants.IS_DEBUG) { Log.w( - "RLottieNetworkCache", + "ThorVGLottieNetworkCache", "Unable to rename cache file ${file.absolutePath} to ${newFile.absolutePath}." ) } diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/ThorVGLottieShapeableView.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/ThorVGLottieShapeableView.kt new file mode 100644 index 000000000..47b2f3375 --- /dev/null +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/ThorVGLottieShapeableView.kt @@ -0,0 +1,366 @@ +package dev.ragnarok.filegallery.view.natives.animation + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.net.Uri +import android.os.Parcelable +import android.util.AttributeSet +import androidx.annotation.RawRes +import com.google.android.material.imageview.ShapeableImageView +import dev.ragnarok.fenrir.module.FenrirNative +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.Companion.RESTART +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.LoadedFrom +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.LottieAnimationListener +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.RepeatMode +import dev.ragnarok.filegallery.Constants +import dev.ragnarok.filegallery.R +import dev.ragnarok.filegallery.util.Utils +import dev.ragnarok.filegallery.util.coroutines.CancelableJob +import dev.ragnarok.filegallery.util.coroutines.CoroutinesUtils.fromIOToMain +import kotlinx.coroutines.flow.flow +import okhttp3.Call +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import java.io.File +import kotlin.coroutines.cancellation.CancellationException + +@SuppressLint("CustomViewStyleable") +class ThorVGLottieShapeableView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null +) : + ShapeableImageView(context, attrs) { + private val cache: ThorVGLottieNetworkCache = ThorVGLottieNetworkCache(context) + private var mDisposable = CancelableJob() + private var animatedDrawable: ThorVGLottieDrawable? = null + private var mListener: LottieAnimationListener? = null + + @LoadedFrom + private var loadedFrom = LoadedFrom.NO + private var colorReplacementTmp: IntArray? = null + private var useMoveColorTmp: Boolean = false + private var deleteInvalidFileTmp: Boolean = false + private var filePathTmp: String? = null + + @RawRes + private var rawResTmp: Int? = null + private var isPlaying: Boolean? = null + private var repeatTmp: Boolean? = null + + @RepeatMode + private var repeatModeTmp: Int? = null + + private var mOnAttached = false + + private fun createLottieDrawable() { + if (FenrirNative.isNativeLoaded && mOnAttached && loadedFrom != LoadedFrom.NO && animatedDrawable == null) { + when (loadedFrom) { + LoadedFrom.RES -> { + animatedDrawable = rawResTmp?.let { + ThorVGLottieDrawable( + it, + colorReplacementTmp, + useMoveColorTmp + ) + } + } + + LoadedFrom.FILE -> { + animatedDrawable = filePathTmp?.let { + ThorVGLottieDrawable( + it, + deleteInvalidFileTmp, + colorReplacementTmp, + useMoveColorTmp + ) + } + } + } + if (animatedDrawable == null) { + return + } + repeatModeTmp?.let { animatedDrawable?.setRepeatMode(it) } + repeatTmp?.let { animatedDrawable?.setRepeatCount(if (it) Int.MAX_VALUE else 1) } + super.setImageDrawable(animatedDrawable) + animatedDrawable?.callback = this + animatedDrawable?.setAnimationListener(mListener) + if (isPlaying == true) { + startAnimation() + } + } + } + + private fun setAnimationByUrlCache( + url: String, + autoPlay: Boolean, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded) { + return + } + val ch = cache.fetch(url) + if (ch == null) { + return + } + if (filePathTmp == ch.absolutePath && deleteInvalidFileTmp == true && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = false) + deleteInvalidFileTmp = true + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.FILE + isPlaying = autoPlay + filePathTmp = ch.absolutePath + if (mOnAttached) { + createLottieDrawable() + } + } + + fun fromNet( + url: String?, + client: OkHttpClient.Builder, + autoPlay: Boolean, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded || url.isNullOrEmpty()) { + if (loadedFrom == LoadedFrom.NET) { + loadedFrom = LoadedFrom.NO + } + return + } + if (filePathTmp == url && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.NET) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.NET + filePathTmp = url + isPlaying = autoPlay + + if (cache.isCachedFile(url)) { + setAnimationByUrlCache(url, autoPlay, colorReplacement, useMoveColor) + return + } + mDisposable.set(flow { + var call: Call? = null + try { + val request: Request = Request.Builder() + .url(url) + .build() + call = client.build().newCall(request) + val response: Response = call.execute() + if (!response.isSuccessful) { + emit(false) + return@flow + } + cache.writeTempCacheFile(url, response.body.source()) + response.close() + cache.renameTempFile(url) + emit(true) + } catch (e: CancellationException) { + call?.cancel() + throw e + } + }.fromIOToMain { + if (it) { + setAnimationByUrlCache(url, autoPlay, colorReplacement, useMoveColor) + } + }) + } + + fun fromRes( + @RawRes resId: Int, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded || resId == -1) { + return + } + if (rawResTmp == resId && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.RES) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.RES + rawResTmp = resId + if (mOnAttached) { + createLottieDrawable() + } + } + + fun fromFile( + file: File, + deleteInvalidFile: Boolean, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded || !file.exists()) { + return + } + if (filePathTmp == file.absolutePath && deleteInvalidFileTmp == deleteInvalidFile && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.FILE + filePathTmp = file.absolutePath + deleteInvalidFileTmp = deleteInvalidFile + if (mOnAttached) { + createLottieDrawable() + } + } + + fun isPlaying(): Boolean { + return animatedDrawable != null && animatedDrawable?.isRunning == true + } + + fun setRepeat(repeat: Boolean) { + animatedDrawable?.setRepeatCount(if (repeat) Int.MAX_VALUE else 1) + repeatTmp = repeat + } + + fun setRepeatMode(@RepeatMode repeatMode: Int) { + animatedDrawable?.setRepeatMode(repeatMode) + repeatModeTmp = repeatMode + } + + fun startAnimation() { + animatedDrawable?.start() + isPlaying = true + } + + fun stopAnimation() { + animatedDrawable?.let { + it.stop() + isPlaying = false + } + } + + fun pauseAnimation() { + animatedDrawable?.pause() + } + + fun resumeAnimation() { + animatedDrawable?.resume() + } + + fun clearAnimationDrawable(callSuper: Boolean, clearState: Boolean, cancelTask: Boolean) { + if (cancelTask) { + mDisposable.cancel() + } + if (animatedDrawable != null) { + animatedDrawable?.callback = null + animatedDrawable?.release() + animatedDrawable = null + } + if (callSuper) { + super.setImageDrawable(null) + } + if (clearState) { + isPlaying = false + loadedFrom = LoadedFrom.NO + filePathTmp = null + rawResTmp = null + deleteInvalidFileTmp = false + } + } + + override fun setImageDrawable(dr: Drawable?) { + super.setImageDrawable(dr) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageBitmap(bm: Bitmap?) { + super.setImageBitmap(bm) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageResource(resId: Int) { + super.setImageResource(resId) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageURI(uri: Uri?) { + super.setImageURI(uri) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + if (measuredWidth <= 0 || measuredHeight <= 0) { + return + } + animatedDrawable?.setSize(measuredWidth, measuredHeight) + } + + override fun onAttachedToWindow() { + mOnAttached = true + + super.onAttachedToWindow() + + if (loadedFrom == LoadedFrom.NET) { + filePathTmp?.let { + fromNet( + it, + Utils.createOkHttp(Constants.PICASSO_TIMEOUT), + isPlaying == true, + colorReplacementTmp, + useMoveColorTmp + ) + return + } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = false) + } else if (loadedFrom != LoadedFrom.NO) { + createLottieDrawable() + } + } + + override fun onDetachedFromWindow() { + mOnAttached = false + super.onDetachedFromWindow() + if (loadedFrom != LoadedFrom.NO) { + clearAnimationDrawable(callSuper = true, clearState = false, cancelTask = true) + } + } + + fun setAnimationListener(listener: LottieAnimationListener?) { + mListener = listener + } + + protected override fun onSaveInstanceState(): Parcelable? { + return super.onSaveInstanceState() + } + + protected override fun onRestoreInstanceState(state: Parcelable?) { + super.onRestoreInstanceState(state) + } + + init { + @SuppressLint("CustomViewStyleable") val a = + context.obtainStyledAttributes(attrs, R.styleable.ThorVGLottieView) + rawResTmp = a.getResourceId(R.styleable.ThorVGLottieView_fromRes, 0) + if (rawResTmp == 0) { + rawResTmp = null + } + repeatTmp = a.getBoolean(R.styleable.ThorVGLottieView_loopAnimation, false) + repeatModeTmp = a.getInt(R.styleable.ThorVGLottieView_loopMode, RESTART) + a.recycle() + if (FenrirNative.isNativeLoaded && rawResTmp != null) { + loadedFrom = LoadedFrom.RES + isPlaying = true + } else { + rawResTmp = null + } + } +} diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/ThorVGLottieView.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/ThorVGLottieView.kt new file mode 100644 index 000000000..a655f4e0e --- /dev/null +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/ThorVGLottieView.kt @@ -0,0 +1,362 @@ +package dev.ragnarok.filegallery.view.natives.animation + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.net.Uri +import android.os.Parcelable +import android.util.AttributeSet +import androidx.annotation.RawRes +import androidx.appcompat.widget.AppCompatImageView +import dev.ragnarok.fenrir.module.FenrirNative +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.Companion.RESTART +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.LoadedFrom +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.LottieAnimationListener +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.RepeatMode +import dev.ragnarok.filegallery.Constants +import dev.ragnarok.filegallery.R +import dev.ragnarok.filegallery.util.Utils +import dev.ragnarok.filegallery.util.coroutines.CancelableJob +import dev.ragnarok.filegallery.util.coroutines.CoroutinesUtils.fromIOToMain +import kotlinx.coroutines.flow.flow +import okhttp3.Call +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import java.io.File +import kotlin.coroutines.cancellation.CancellationException + +@SuppressLint("CustomViewStyleable") +class ThorVGLottieView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : + AppCompatImageView(context, attrs) { + private val cache: ThorVGLottieNetworkCache = ThorVGLottieNetworkCache(context) + private var mDisposable = CancelableJob() + private var animatedDrawable: ThorVGLottieDrawable? = null + private var mListener: LottieAnimationListener? = null + + @LoadedFrom + private var loadedFrom = LoadedFrom.NO + private var colorReplacementTmp: IntArray? = null + private var useMoveColorTmp: Boolean = false + private var deleteInvalidFileTmp: Boolean = false + private var filePathTmp: String? = null + + @RawRes + private var rawResTmp: Int? = null + private var isPlaying: Boolean? = null + private var repeatTmp: Boolean? = null + + @RepeatMode + private var repeatModeTmp: Int? = null + + private var mOnAttached = false + + private fun createLottieDrawable() { + if (FenrirNative.isNativeLoaded && mOnAttached && loadedFrom != LoadedFrom.NO && animatedDrawable == null) { + when (loadedFrom) { + LoadedFrom.RES -> { + animatedDrawable = rawResTmp?.let { + ThorVGLottieDrawable( + it, + colorReplacementTmp, + useMoveColorTmp + ) + } + } + + LoadedFrom.FILE -> { + animatedDrawable = filePathTmp?.let { + ThorVGLottieDrawable( + it, + deleteInvalidFileTmp, + colorReplacementTmp, + useMoveColorTmp + ) + } + } + } + if (animatedDrawable == null) { + return + } + repeatModeTmp?.let { animatedDrawable?.setRepeatMode(it) } + repeatTmp?.let { animatedDrawable?.setRepeatCount(if (it) Int.MAX_VALUE else 1) } + super.setImageDrawable(animatedDrawable) + animatedDrawable?.callback = this + animatedDrawable?.setAnimationListener(mListener) + if (isPlaying == true) { + startAnimation() + } + } + } + + private fun setAnimationByUrlCache( + url: String, + autoPlay: Boolean, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded) { + return + } + val ch = cache.fetch(url) + if (ch == null) { + return + } + if (filePathTmp == ch.absolutePath && deleteInvalidFileTmp == true && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = false) + deleteInvalidFileTmp = true + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.FILE + isPlaying = autoPlay + filePathTmp = ch.absolutePath + if (mOnAttached) { + createLottieDrawable() + } + } + + fun fromNet( + url: String?, + client: OkHttpClient.Builder, + autoPlay: Boolean, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded || url.isNullOrEmpty()) { + if (loadedFrom == LoadedFrom.NET) { + loadedFrom = LoadedFrom.NO + } + return + } + if (filePathTmp == url && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.NET) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.NET + filePathTmp = url + isPlaying = autoPlay + + if (cache.isCachedFile(url)) { + setAnimationByUrlCache(url, autoPlay, colorReplacement, useMoveColor) + return + } + mDisposable.set(flow { + var call: Call? = null + try { + val request: Request = Request.Builder() + .url(url) + .build() + call = client.build().newCall(request) + val response: Response = call.execute() + if (!response.isSuccessful) { + emit(false) + return@flow + } + cache.writeTempCacheFile(url, response.body.source()) + response.close() + cache.renameTempFile(url) + emit(true) + } catch (e: CancellationException) { + call?.cancel() + throw e + } + }.fromIOToMain { + if (it) { + setAnimationByUrlCache(url, autoPlay, colorReplacement, useMoveColor) + } + }) + } + + fun fromRes( + @RawRes resId: Int, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded || resId == -1) { + return + } + if (rawResTmp == resId && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.RES) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.RES + rawResTmp = resId + if (mOnAttached) { + createLottieDrawable() + } + } + + fun fromFile( + file: File, + deleteInvalidFile: Boolean, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded || !file.exists()) { + return + } + if (filePathTmp == file.absolutePath && deleteInvalidFileTmp == deleteInvalidFile && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.FILE + filePathTmp = file.absolutePath + deleteInvalidFileTmp = deleteInvalidFile + if (mOnAttached) { + createLottieDrawable() + } + } + + fun isPlaying(): Boolean { + return animatedDrawable != null && animatedDrawable?.isRunning == true + } + + fun setRepeat(repeat: Boolean) { + animatedDrawable?.setRepeatCount(if (repeat) Int.MAX_VALUE else 1) + repeatTmp = repeat + } + + fun setRepeatMode(@RepeatMode repeatMode: Int) { + animatedDrawable?.setRepeatMode(repeatMode) + repeatModeTmp = repeatMode + } + + fun startAnimation() { + animatedDrawable?.start() + isPlaying = true + } + + fun stopAnimation() { + animatedDrawable?.let { + it.stop() + isPlaying = false + } + } + + fun pauseAnimation() { + animatedDrawable?.pause() + } + + fun resumeAnimation() { + animatedDrawable?.resume() + } + + fun clearAnimationDrawable(callSuper: Boolean, clearState: Boolean, cancelTask: Boolean) { + if (cancelTask) { + mDisposable.cancel() + } + if (animatedDrawable != null) { + animatedDrawable?.callback = null + animatedDrawable?.release() + animatedDrawable = null + } + if (callSuper) { + super.setImageDrawable(null) + } + if (clearState) { + isPlaying = false + loadedFrom = LoadedFrom.NO + filePathTmp = null + rawResTmp = null + deleteInvalidFileTmp = false + } + } + + override fun setImageDrawable(dr: Drawable?) { + super.setImageDrawable(dr) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageBitmap(bm: Bitmap?) { + super.setImageBitmap(bm) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageResource(resId: Int) { + super.setImageResource(resId) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageURI(uri: Uri?) { + super.setImageURI(uri) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + if (measuredWidth <= 0 || measuredHeight <= 0) { + return + } + animatedDrawable?.setSize(measuredWidth, measuredHeight) + } + + override fun onAttachedToWindow() { + mOnAttached = true + + super.onAttachedToWindow() + + if (loadedFrom == LoadedFrom.NET) { + filePathTmp?.let { + fromNet( + it, + Utils.createOkHttp(Constants.PICASSO_TIMEOUT), + isPlaying == true, + colorReplacementTmp, + useMoveColorTmp + ) + return + } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = false) + } else if (loadedFrom != LoadedFrom.NO) { + createLottieDrawable() + } + } + + override fun onDetachedFromWindow() { + mOnAttached = false + super.onDetachedFromWindow() + if (loadedFrom != LoadedFrom.NO) { + clearAnimationDrawable(callSuper = true, clearState = false, cancelTask = true) + } + } + + fun setAnimationListener(listener: LottieAnimationListener?) { + mListener = listener + } + + protected override fun onSaveInstanceState(): Parcelable? { + return super.onSaveInstanceState() + } + + protected override fun onRestoreInstanceState(state: Parcelable?) { + super.onRestoreInstanceState(state) + } + + init { + val a = context.obtainStyledAttributes(attrs, R.styleable.ThorVGLottieView) + rawResTmp = a.getResourceId(R.styleable.ThorVGLottieView_fromRes, 0) + if (rawResTmp == 0) { + rawResTmp = null + } + repeatTmp = a.getBoolean(R.styleable.ThorVGLottieView_loopAnimation, false) + repeatModeTmp = a.getInt(R.styleable.ThorVGLottieView_loopMode, RESTART) + a.recycle() + if (FenrirNative.isNativeLoaded && rawResTmp != null) { + loadedFrom = LoadedFrom.RES + isPlaying = true + } else { + rawResTmp = null + } + } +} diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/rlottie/RLottieImageView.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/rlottie/RLottieImageView.kt deleted file mode 100644 index 916b6db6c..000000000 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/rlottie/RLottieImageView.kt +++ /dev/null @@ -1,294 +0,0 @@ -package dev.ragnarok.filegallery.view.natives.rlottie - -import android.content.Context -import android.graphics.Bitmap -import android.graphics.drawable.Drawable -import android.util.AttributeSet -import androidx.annotation.RawRes -import androidx.appcompat.widget.AppCompatImageView -import dev.ragnarok.fenrir.module.BufferWriteNative -import dev.ragnarok.fenrir.module.FenrirNative -import dev.ragnarok.fenrir.module.rlottie.RLottieDrawable -import dev.ragnarok.filegallery.R -import dev.ragnarok.filegallery.util.coroutines.CancelableJob -import dev.ragnarok.filegallery.util.coroutines.CoroutinesUtils.fromIOToMain -import kotlinx.coroutines.flow.flow -import okhttp3.Call -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import java.io.File -import kotlin.coroutines.cancellation.CancellationException - -class RLottieImageView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : - AppCompatImageView(context, attrs) { - private val cache: RLottieNetworkCache = RLottieNetworkCache(context) - private var layerColors: HashMap? = null - private var animatedDrawable: RLottieDrawable? = null - private var autoRepeat: Boolean - private var attachedToWindow = false - private var playing = false - private var mDisposable = CancelableJob() - fun clearLayerColors() { - layerColors?.clear() - } - - fun setLayerColor(layer: String, color: Int) { - if (layerColors == null) { - layerColors = HashMap() - } - (layerColors ?: return)[layer] = color - animatedDrawable?.setLayerColor(layer, color) - } - - fun replaceColors(colors: IntArray?) { - animatedDrawable?.replaceColors(colors) - } - - private fun setAnimationByUrlCache(url: String, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded) { - return - } - val ch = cache.fetch(url) - if (ch == null) { - setImageDrawable(null) - return - } - autoRepeat = false - setAnimation( - RLottieDrawable( - ch, true, w, h, - limitFps = false, - colorReplacement = null, - useMoveColor = false - ) - ) - playAnimation() - } - - fun fromNet(url: String?, client: OkHttpClient.Builder, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded || url.isNullOrEmpty()) { - return - } - clearAnimationDrawable() - if (cache.isCachedFile(url)) { - setAnimationByUrlCache(url, w, h) - return - } - mDisposable.set(flow { - var call: Call? = null - try { - val request: Request = Request.Builder() - .url(url) - .build() - call = client.build().newCall(request) - val response: Response = call.execute() - if (!response.isSuccessful) { - emit(false) - return@flow - } - cache.writeTempCacheFile(url, response.body.source()) - response.close() - cache.renameTempFile(url) - emit(true) - } catch (e: CancellationException) { - call?.cancel() - throw e - } - }.fromIOToMain { - if (it) { - setAnimationByUrlCache(url, w, h) - } - }) - } - - private fun setAnimation(rLottieDrawable: RLottieDrawable) { - animatedDrawable = rLottieDrawable - animatedDrawable?.setAutoRepeat(if (autoRepeat) 1 else 0) - if (layerColors != null) { - animatedDrawable?.beginApplyLayerColors() - for ((key, value) in layerColors ?: return) { - animatedDrawable?.setLayerColor(key, value) - } - animatedDrawable?.commitApplyLayerColors() - } - animatedDrawable?.setAllowDecodeSingleFrame(true) - animatedDrawable?.setCurrentParentView(this) - setImageDrawable(animatedDrawable) - } - - @JvmOverloads - fun fromRes( - @RawRes resId: Int, - w: Int, - h: Int, - colorReplacement: IntArray? = null, - useMoveColor: Boolean = false - ) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - RLottieDrawable( - resId, - w, - h, - false, - colorReplacement, - useMoveColor - ) - ) - } - - fun fromFile(file: File, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - RLottieDrawable( - file, false, w, h, - limitFps = false, - colorReplacement = null, - useMoveColor = false - ) - ) - } - - fun fromString(jsonString: BufferWriteNative, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - RLottieDrawable( - jsonString, w, h, - limitFps = false, - colorReplacement = null, - useMoveColor = false - ) - ) - } - - fun clearAnimationDrawable() { - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - setImageDrawable(null) - } - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - attachedToWindow = true - animatedDrawable?.setCurrentParentView(this) - if (playing) { - animatedDrawable?.start() - } - } - - override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - mDisposable.cancel() - attachedToWindow = false - animatedDrawable?.stop() - animatedDrawable?.setCurrentParentView(null) - } - - fun isPlaying(): Boolean { - return animatedDrawable != null && animatedDrawable?.isRunning == true - } - - fun setAutoRepeat(repeat: Boolean) { - autoRepeat = repeat - } - - fun setProgress(progress: Float) { - animatedDrawable?.setProgress(progress) - } - - override fun setImageDrawable(dr: Drawable?) { - super.setImageDrawable(dr) - if (dr !is RLottieDrawable) { - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } - } - - override fun setImageBitmap(bm: Bitmap?) { - super.setImageBitmap(bm) - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } - - override fun setImageResource(resId: Int) { - super.setImageResource(resId) - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } - - fun playAnimation() { - playing = true - if (attachedToWindow) { - animatedDrawable?.start() - } - } - - fun replayAnimation() { - if (animatedDrawable == null) { - return - } - playing = true - if (attachedToWindow) { - animatedDrawable?.stop() - animatedDrawable?.setAutoRepeat(1) - animatedDrawable?.start() - } - } - - fun resetFrame() { - playing = true - if (attachedToWindow) { - animatedDrawable?.setProgress(0f) - } - } - - fun stopAnimation() { - playing = false - if (attachedToWindow) { - animatedDrawable?.stop() - } - } - - init { - val a = context.obtainStyledAttributes(attrs, R.styleable.RLottieImageView) - val animRes = a.getResourceId(R.styleable.RLottieImageView_fromRes, 0) - autoRepeat = a.getBoolean(R.styleable.RLottieImageView_loopAnimation, false) - val width = a.getDimension(R.styleable.RLottieImageView_w, 28f).toInt() - val height = a.getDimension(R.styleable.RLottieImageView_h, 28f).toInt() - a.recycle() - if (FenrirNative.isNativeLoaded && animRes != 0) { - setAnimation(RLottieDrawable(animRes, width, height, false, null, false)) - playAnimation() - } - } -} diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/rlottie/RLottieShapeableImageView.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/rlottie/RLottieShapeableImageView.kt deleted file mode 100644 index a0f32599f..000000000 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/rlottie/RLottieShapeableImageView.kt +++ /dev/null @@ -1,297 +0,0 @@ -package dev.ragnarok.filegallery.view.natives.rlottie - -import android.annotation.SuppressLint -import android.content.Context -import android.graphics.Bitmap -import android.graphics.drawable.Drawable -import android.util.AttributeSet -import androidx.annotation.RawRes -import com.google.android.material.imageview.ShapeableImageView -import dev.ragnarok.fenrir.module.BufferWriteNative -import dev.ragnarok.fenrir.module.FenrirNative -import dev.ragnarok.fenrir.module.rlottie.RLottieDrawable -import dev.ragnarok.filegallery.R -import dev.ragnarok.filegallery.util.coroutines.CancelableJob -import dev.ragnarok.filegallery.util.coroutines.CoroutinesUtils.fromIOToMain -import kotlinx.coroutines.flow.flow -import okhttp3.Call -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import java.io.File -import kotlin.coroutines.cancellation.CancellationException - -class RLottieShapeableImageView @JvmOverloads constructor( - context: Context, attrs: AttributeSet? = null -) : ShapeableImageView(context, attrs) { - private val cache: RLottieNetworkCache = RLottieNetworkCache(context) - private var layerColors: HashMap? = null - private var animatedDrawable: RLottieDrawable? = null - private var autoRepeat: Boolean - private var attachedToWindow = false - private var playing = false - private var mDisposable = CancelableJob() - fun clearLayerColors() { - layerColors?.clear() - } - - fun setLayerColor(layer: String, color: Int) { - if (layerColors == null) { - layerColors = HashMap() - } - (layerColors ?: return)[layer] = color - animatedDrawable?.setLayerColor(layer, color) - } - - fun replaceColors(colors: IntArray?) { - animatedDrawable?.replaceColors(colors) - } - - private fun setAnimationByUrlCache(url: String, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded) { - return - } - val ch = cache.fetch(url) - if (ch == null) { - setImageDrawable(null) - return - } - autoRepeat = false - setAnimation( - RLottieDrawable( - ch, true, w, h, - limitFps = false, - colorReplacement = null, - useMoveColor = false - ) - ) - playAnimation() - } - - fun fromNet(url: String?, client: OkHttpClient.Builder, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded || url.isNullOrEmpty()) { - return - } - clearAnimationDrawable() - if (cache.isCachedFile(url)) { - setAnimationByUrlCache(url, w, h) - return - } - mDisposable.set(flow { - var call: Call? = null - try { - val request: Request = Request.Builder() - .url(url) - .build() - call = client.build().newCall(request) - val response: Response = call.execute() - if (!response.isSuccessful) { - emit(false) - return@flow - } - cache.writeTempCacheFile(url, response.body.source()) - response.close() - cache.renameTempFile(url) - emit(true) - } catch (e: CancellationException) { - call?.cancel() - throw e - } - }.fromIOToMain { - if (it) { - setAnimationByUrlCache(url, w, h) - } - }) - } - - private fun setAnimation(rLottieDrawable: RLottieDrawable) { - animatedDrawable = rLottieDrawable - animatedDrawable?.setAutoRepeat(if (autoRepeat) 1 else 0) - if (layerColors != null) { - animatedDrawable?.beginApplyLayerColors() - for ((key, value) in layerColors ?: return) { - animatedDrawable?.setLayerColor(key, value) - } - animatedDrawable?.commitApplyLayerColors() - } - animatedDrawable?.setAllowDecodeSingleFrame(true) - animatedDrawable?.setCurrentParentView(this) - setImageDrawable(animatedDrawable) - } - - @JvmOverloads - fun fromRes( - @RawRes resId: Int, - w: Int, - h: Int, - colorReplacement: IntArray? = null, - useMoveColor: Boolean = false - ) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - RLottieDrawable( - resId, - w, - h, - false, - colorReplacement, - useMoveColor - ) - ) - } - - fun fromFile(file: File, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - RLottieDrawable( - file, false, w, h, - limitFps = false, - colorReplacement = null, - useMoveColor = false - ) - ) - } - - fun fromString(jsonString: BufferWriteNative, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - RLottieDrawable( - jsonString, w, h, - limitFps = false, - colorReplacement = null, - useMoveColor = false - ) - ) - } - - fun clearAnimationDrawable() { - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - setImageDrawable(null) - } - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - attachedToWindow = true - animatedDrawable?.setCurrentParentView(this) - if (playing) { - animatedDrawable?.start() - } - } - - override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - mDisposable.cancel() - attachedToWindow = false - animatedDrawable?.stop() - animatedDrawable?.setCurrentParentView(null) - } - - fun isPlaying(): Boolean { - return animatedDrawable != null && animatedDrawable?.isRunning == true - } - - fun setAutoRepeat(repeat: Boolean) { - autoRepeat = repeat - } - - fun setProgress(progress: Float) { - animatedDrawable?.setProgress(progress) - } - - override fun setImageDrawable(dr: Drawable?) { - super.setImageDrawable(dr) - if (dr !is RLottieDrawable) { - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } - } - - override fun setImageBitmap(bm: Bitmap?) { - super.setImageBitmap(bm) - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } - - override fun setImageResource(resId: Int) { - super.setImageResource(resId) - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } - - fun playAnimation() { - playing = true - if (attachedToWindow) { - animatedDrawable?.start() - } - } - - fun replayAnimation() { - if (animatedDrawable == null) { - return - } - playing = true - if (attachedToWindow) { - animatedDrawable?.stop() - animatedDrawable?.setAutoRepeat(1) - animatedDrawable?.start() - } - } - - fun resetFrame() { - playing = true - if (attachedToWindow) { - animatedDrawable?.setProgress(0f) - } - } - - fun stopAnimation() { - playing = false - if (attachedToWindow) { - animatedDrawable?.stop() - } - } - - init { - @SuppressLint("CustomViewStyleable") val a = - context.obtainStyledAttributes(attrs, R.styleable.RLottieImageView) - val animRes = a.getResourceId(R.styleable.RLottieImageView_fromRes, 0) - autoRepeat = a.getBoolean(R.styleable.RLottieImageView_loopAnimation, false) - val width = a.getDimension(R.styleable.RLottieImageView_w, 28f).toInt() - val height = a.getDimension(R.styleable.RLottieImageView_h, 28f).toInt() - a.recycle() - if (FenrirNative.isNativeLoaded && animRes != 0) { - setAnimation(RLottieDrawable(animRes, width, height, false, null, false)) - playAnimation() - } - } -} diff --git a/app_filegallery/src/main/res/layout/activity_photo_pager.xml b/app_filegallery/src/main/res/layout/activity_photo_pager.xml index ec7a94b24..4414b6657 100644 --- a/app_filegallery/src/main/res/layout/activity_photo_pager.xml +++ b/app_filegallery/src/main/res/layout/activity_photo_pager.xml @@ -13,7 +13,7 @@ - - - - - - - - - - - - - - -#151515 #313131 @style/CustomBottomSheetDialog + @color/m3_ref_palette_dynamic_secondary30 diff --git a/app_filegallery/src/main/res/values-v31/styles.xml b/app_filegallery/src/main/res/values-v31/styles.xml index 53f2b73af..a63fd9a99 100644 --- a/app_filegallery/src/main/res/values-v31/styles.xml +++ b/app_filegallery/src/main/res/values-v31/styles.xml @@ -41,5 +41,6 @@ ?attr/colorPrimary @style/CustomBottomSheetDialog + @color/m3_ref_palette_dynamic_secondary90 diff --git a/app_filegallery/src/main/res/values/attrs.xml b/app_filegallery/src/main/res/values/attrs.xml index 59c94fa11..28466435a 100644 --- a/app_filegallery/src/main/res/values/attrs.xml +++ b/app_filegallery/src/main/res/values/attrs.xml @@ -103,11 +103,13 @@ - + + + + + - - diff --git a/build.gradle b/build.gradle index 918316a90..9a6261394 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ buildscript { ext.appCompileSDK = 35 ext.appBuildTools = "35.0.0" - ext.appNdk = "28.0.12433566" + ext.appNdk = "28.0.12674087" ext.appMinSDK = is_developer_build ? 29 : 26 ext.appTargetSDK = 31 ext.appFenrirVersionCode = 999 @@ -13,40 +13,40 @@ buildscript { ext.appFileGalleryVersionName = "1.999" //androidx libraries - ext.activityVersion = "1.9.3" - ext.annotationVersion = "1.9.0" + ext.activityVersion = "1.10.0-beta01" + ext.annotationVersion = "1.9.1" ext.appcompatVersion = "1.7.0" ext.biometricVersion = "1.4.0-alpha02" ext.browserVersion = "1.8.0" ext.cardviewVersion = "1.0.0" - ext.collectionVersion = "1.5.0-alpha04" + ext.collectionVersion = "1.5.0-alpha06" ext.concurentVersion = "1.2.0" - ext.constraintlayoutVersion = "2.2.0-rc01" + ext.constraintlayoutVersion = "2.2.0" ext.coordinatorlayoutVersion = "1.3.0-alpha02" - ext.coreVersion = "1.15.0-rc01" + ext.coreVersion = "1.15.0" ext.customviewVersion = "1.2.0-alpha02" ext.customviewPoolingcontainerVersion = "1.0.0" ext.documentfile = "1.0.1" ext.drawerlayoutVersion = "1.2.0" ext.dynamicanimationVersion = "1.1.0-alpha03" - ext.exifinterfaceVersion = "1.3.7" - ext.fragmentVersion = "1.8.4" + ext.exifinterfaceVersion = "1.4.0-alpha01" + ext.fragmentVersion = "1.8.5" ext.graphicsShapesVersion = "1.0.1" - ext.lifecycleVersion = "2.8.6" + ext.lifecycleVersion = "2.8.7" ext.mediaVersion = "1.7.0" - ext.media3Version = "1.5.0-alpha01" + ext.media3Version = "1.5.0-rc02" ext.resourceInspectionAnnotation = "1.0.1" - ext.savedStateVersion = "1.3.0-alpha03" + ext.savedStateVersion = "1.3.0-alpha05" ext.swiperefreshlayoutVersion = "1.2.0-alpha01" ext.tracingVersion = "1.2.0" ext.transitionVersion = "1.5.1" ext.vectordrawableVersion = "1.2.0" ext.webkitVersion = "1.12.1" - ext.workVersion = "2.10.0-beta01" + ext.workVersion = "2.10.0" //firebase libraries ext.firebaseDatatransportVersion = "19.0.0" - ext.firebaseMessagingVersion = "24.0.2" + ext.firebaseMessagingVersion = "24.1.0" //firebase common libraries ext.firebaseCommonVersion = "21.0.0" @@ -58,16 +58,16 @@ buildscript { ext.autoValueVersion = "1.11.0" //common libraries - ext.kotlin_version = "2.0.21" + ext.kotlin_version = "2.1.0-RC2" ext.kotlin_coroutines = "1.9.0" ext.kotlin_serializer = "1.7.3" ext.okhttpLibraryVersion = "5.0.0-SNAPSHOT" //ext.okhttpLibraryVersion = "5.0.0-alpha.14" ext.okioVersion = "3.9.1" ext.guavaVersion = "33.3.1-android" - ext.errorProneVersion = "2.33.0" + ext.errorProneVersion = "2.36.0" ext.checkerCompatQualVersion = "2.5.6" - ext.checkerQualAndroidVersion = "3.48.1" + ext.checkerQualAndroidVersion = "3.48.2" //APP_PROPS ext.targetAbi = is_developer_build ? ["arm64-v8a", "x86_64"] : ["arm64-v8a", "armeabi-v7a", "x86_64"] @@ -92,7 +92,7 @@ buildscript { //maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' } } dependencies { - classpath "com.android.tools.build:gradle:8.7.1" + classpath "com.android.tools.build:gradle:8.7.2" classpath "com.google.gms:google-services:4.4.2" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" diff --git a/camera2/build.gradle b/camera2/build.gradle index 2863311b1..e1a76556d 100644 --- a/camera2/build.gradle +++ b/camera2/build.gradle @@ -3,7 +3,7 @@ plugins { id("kotlin-android") } -//1.4.0-rc04 +//1.4.0 android { namespace "androidx.camera" diff --git a/fenrir_common/src/main/kotlin/dev/ragnarok/fenrir/Common.kt b/fenrir_common/src/main/kotlin/dev/ragnarok/fenrir/Common.kt index ae118bbc9..ff3f44772 100644 --- a/fenrir_common/src/main/kotlin/dev/ragnarok/fenrir/Common.kt +++ b/fenrir_common/src/main/kotlin/dev/ragnarok/fenrir/Common.kt @@ -59,7 +59,7 @@ object Common { 8 -> PaganSymbolWall(R.raw.svg_pagan_celtic_flower, 180f, 180f) 9 -> PaganSymbolWall(R.raw.svg_pagan_slepnir, 108f, 108f) 10 -> PaganSymbolWall( - R.raw.fenrir, 140f, intArrayOf( + R.raw.fenrir, intArrayOf( 0x333333, getColorPrimary(context), 0x777777, @@ -78,7 +78,7 @@ object Common { 19 -> PaganSymbolWall(R.raw.svg_pagan_chur, 150f, 150f) 20 -> PaganSymbolWall(R.raw.svg_pagan_fire, 180f, 180f) 21 -> PaganSymbolWall( - R.raw.flame, 140f, intArrayOf( + R.raw.flame, intArrayOf( 0xFF812E, getColorPrimary(context) ), true @@ -115,7 +115,6 @@ object Common { fun getAboutUsAnimation(paganSymbol: Int, context: Context): PaganSymbolWall { return PaganSymbolWall( R.raw.fenrir, - 140f, intArrayOf( 0x333333, getColorPrimary(context), @@ -131,7 +130,6 @@ object Common { iconRes = icon lottieRes = R.raw.fenrir this.lottie_replacement = null - lottie_widthHeight = 140f icon_width = width icon_height = height lottie_useMoveColor = false @@ -139,7 +137,6 @@ object Common { constructor( @RawRes animation: Int, - widthHeight: Float, replacement: IntArray? = null, useMoveColor: Boolean = false ) { @@ -147,7 +144,6 @@ object Common { iconRes = R.raw.svg_pagan_cat lottieRes = animation this.lottie_replacement = replacement - this.lottie_widthHeight = widthHeight this.lottie_useMoveColor = useMoveColor icon_width = 160f icon_height = 160f @@ -169,7 +165,6 @@ object Common { @RawRes val lottieRes: Int val lottie_replacement: IntArray? - val lottie_widthHeight: Float val lottie_useMoveColor: Boolean var icon_width: Float diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 213a76afa..e52cec12e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Wed Mar 20 16:00:00 MSK 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/image/src/main/kotlin/me/minetsh/imaging/core/homing/IMGHoming.kt b/image/src/main/kotlin/me/minetsh/imaging/core/homing/IMGHoming.kt index 35e52f321..70c39a070 100644 --- a/image/src/main/kotlin/me/minetsh/imaging/core/homing/IMGHoming.kt +++ b/image/src/main/kotlin/me/minetsh/imaging/core/homing/IMGHoming.kt @@ -4,7 +4,7 @@ package me.minetsh.imaging.core.homing * Created by felix on 2017/11/28 下午4:14. */ class IMGHoming(var x: Float, var y: Float, var scale: Float, var rotate: Float) { - operator fun set(x: Float, y: Float, scale: Float, rotate: Float) { + fun set(x: Float, y: Float, scale: Float, rotate: Float) { this.x = x this.y = y this.scale = scale diff --git a/image/src/main/kotlin/me/minetsh/imaging/core/homing/IMGHomingEvaluator.kt b/image/src/main/kotlin/me/minetsh/imaging/core/homing/IMGHomingEvaluator.kt index f125bff9b..68aa9a314 100644 --- a/image/src/main/kotlin/me/minetsh/imaging/core/homing/IMGHomingEvaluator.kt +++ b/image/src/main/kotlin/me/minetsh/imaging/core/homing/IMGHomingEvaluator.kt @@ -19,7 +19,7 @@ class IMGHomingEvaluator : TypeEvaluator { val scale = startValue.scale + fraction * (endValue.scale - startValue.scale) val rotate = startValue.rotate + fraction * (endValue.rotate - startValue.rotate) homing?.let { - it[x, y, scale] = rotate + it.set(x, y, scale, rotate) return it } ?: run { IMGHoming(x, y, scale, rotate).let { diff --git a/libfenrir/build.gradle b/libfenrir/build.gradle index 576f87f97..c05556242 100644 --- a/libfenrir/build.gradle +++ b/libfenrir/build.gradle @@ -49,7 +49,7 @@ android { externalNativeBuild { cmake { - version = "3.30.5" + version = "3.31.0" path = file("src/main/jni/CMakeLists.txt") } } diff --git a/libfenrir/ffmpeg.sh b/libfenrir/ffmpeg.sh index 0ecb5c775..f7c12c055 100644 --- a/libfenrir/ffmpeg.sh +++ b/libfenrir/ffmpeg.sh @@ -8,7 +8,7 @@ rm -r -f ".git" ENABLED_DECODERS=(mpeg4 h264 hevc mp3 aac ac3 eac3 flac vorbis alac) HOST_PLATFORM="linux-x86_64" -NDK_PATH="$HOME/Android/Sdk/ndk/28.0.12433566" +NDK_PATH="$HOME/Android/Sdk/ndk/28.0.12674087" echo 'Please input platform version (Example 21 - Android 5.0): ' read ANDROID_PLATFORM diff --git a/libfenrir/src/main/java/com/google/zxing/client/result/EmailDoCoMoResultParser.java b/libfenrir/src/main/java/com/google/zxing/client/result/EmailDoCoMoResultParser.java index ab301f42f..49992df26 100644 --- a/libfenrir/src/main/java/com/google/zxing/client/result/EmailDoCoMoResultParser.java +++ b/libfenrir/src/main/java/com/google/zxing/client/result/EmailDoCoMoResultParser.java @@ -29,8 +29,10 @@ */ public final class EmailDoCoMoResultParser extends AbstractDoCoMoResultParser { - private static final Pattern ATEXT_ALPHANUMERIC = Pattern.compile("[a-zA-Z0-9@.!#$%&'*+\\-/=?^_`{|}~]+"); - + private static final String EMAIL_LOCAL = "[^:]+"; + private static final String EMAIL_DOMAIN = "([0-9a-zA-Z]+[0-9a-zA-Z\\-]+[0-9a-zA-Z]+\\.)+[a-zA-Z]{2,}"; + private static final Pattern EMAIL = Pattern.compile("^" + EMAIL_LOCAL + "@" + EMAIL_DOMAIN + "$"); + @Override public EmailAddressParsedResult parse(Result result) { String rawText = getMassagedText(result); @@ -58,7 +60,7 @@ public EmailAddressParsedResult parse(Result result) { * in a barcode, not "judge" it. */ static boolean isBasicallyValidEmailAddress(String email) { - return email != null && ATEXT_ALPHANUMERIC.matcher(email).matches() && email.indexOf('@') >= 0; + return email != null && EMAIL.matcher(email).matches(); } } diff --git a/libfenrir/src/main/java/com/google/zxing/pdf417/encoder/PDF417.java b/libfenrir/src/main/java/com/google/zxing/pdf417/encoder/PDF417.java index a9d63dc23..3402bb3d1 100644 --- a/libfenrir/src/main/java/com/google/zxing/pdf417/encoder/PDF417.java +++ b/libfenrir/src/main/java/com/google/zxing/pdf417/encoder/PDF417.java @@ -659,7 +659,8 @@ public void generateBarcodeLogic(String msg, int errorCorrectionLevel, boolean a String highLevel = PDF417HighLevelEncoder.encodeHighLevel(msg, compaction, encoding, autoECI); int sourceCodeWords = highLevel.length(); - int[] dimension = determineDimensions(sourceCodeWords, errorCorrectionCodeWords); + int[] dimension = determineDimensions(minCols, maxCols, minRows, maxRows, + sourceCodeWords, errorCorrectionCodeWords); int cols = dimension[0]; int rows = dimension[1]; @@ -692,16 +693,25 @@ public void generateBarcodeLogic(String msg, int errorCorrectionLevel, boolean a * Determine optimal nr of columns and rows for the specified number of * codewords. * + * @param minCols minimum number of columns + * @param maxCols maximum number of columns + * @param minRows minimum number of rows + * @param maxRows maximum number of rows * @param sourceCodeWords number of code words * @param errorCorrectionCodeWords number of error correction code words * @return dimension object containing cols as width and rows as height + * @throws WriterException when dimensions can't be determined */ - private int[] determineDimensions(int sourceCodeWords, int errorCorrectionCodeWords) throws WriterException { + protected static int[] determineDimensions(int minCols, int maxCols, + int minRows, int maxRows, + int sourceCodeWords, int errorCorrectionCodeWords) throws WriterException { float ratio = 0.0f; int[] dimension = null; + int currentCol = minCols; for (int cols = minCols; cols <= maxCols; cols++) { - + currentCol = cols; + int rows = calculateNumberOfRows(sourceCodeWords, errorCorrectionCodeWords, cols); if (rows < minRows) { @@ -725,7 +735,7 @@ private int[] determineDimensions(int sourceCodeWords, int errorCorrectionCodeWo // Handle case when min values were larger than necessary if (dimension == null) { - int rows = calculateNumberOfRows(sourceCodeWords, errorCorrectionCodeWords, minCols); + int rows = calculateNumberOfRows(sourceCodeWords, errorCorrectionCodeWords, currentCol); if (rows < minRows) { dimension = new int[]{minCols, minRows}; } diff --git a/libfenrir/src/main/java/com/google/zxing/pdf417/encoder/PDF417HighLevelEncoder.java b/libfenrir/src/main/java/com/google/zxing/pdf417/encoder/PDF417HighLevelEncoder.java index 564d7c968..8ac202b9d 100644 --- a/libfenrir/src/main/java/com/google/zxing/pdf417/encoder/PDF417HighLevelEncoder.java +++ b/libfenrir/src/main/java/com/google/zxing/pdf417/encoder/PDF417HighLevelEncoder.java @@ -174,16 +174,15 @@ static String encodeHighLevel(String msg, Compaction compaction, Charset encodin if (msg.isEmpty()) { throw new WriterException("Empty message not allowed"); } + + if (Compaction.TEXT == compaction) { + checkCharset(msg, 127, "Consider specifying Compaction.AUTO instead of Compaction.TEXT"); + } if (encoding == null && !autoECI) { - for (int i = 0; i < msg.length(); i++) { - if (msg.charAt(i) > 255) { - throw new WriterException("Non-encodable character detected: " + msg.charAt(i) + " (Unicode: " + - (int) msg.charAt(i) + - "). Consider specifying EncodeHintType.PDF417_AUTO_ECI and/or EncodeTypeHint.CHARACTER_SET."); - } - } + checkCharset(msg, 255, "Consider specifying EncodeHintType.PDF417_AUTO_ECI and/or EncodeTypeHint.CHARACTER_SET"); } + //the codewords 0..928 are encoded as Unicode characters StringBuilder sb = new StringBuilder(msg.length()); @@ -283,6 +282,22 @@ static String encodeHighLevel(String msg, Compaction compaction, Charset encodin return sb.toString(); } + + /** + * Check if input is only made of characters between 0 and the upper limit + * @param input the input + * @param max the upper limit for charset + * @param errorMessage the message to explain the error + * @throws WriterException exception highlighting the offending character and a suggestion to avoid the error + */ + protected static void checkCharset(String input, int max, String errorMessage) throws WriterException { + for (int i = 0; i < input.length(); i++) { + if (input.charAt(i) > max) { + throw new WriterException("Non-encodable character detected: " + input.charAt(i) + " (Unicode: " + + (int) input.charAt(i) + ") at position #" + i + " - " + errorMessage); + } + } + } /** * Encode parts of the message using Text Compaction as described in ISO/IEC 15438:2001(E), diff --git a/libfenrir/src/main/jni/CMakeLists.txt b/libfenrir/src/main/jni/CMakeLists.txt index e9c7944f9..d83d5f6a3 100644 --- a/libfenrir/src/main/jni/CMakeLists.txt +++ b/libfenrir/src/main/jni/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.30.5 FATAL_ERROR) +cmake_minimum_required(VERSION 3.31.0 FATAL_ERROR) project(fenrir_jni C CXX ASM) if (${CMAKE_BUILD_TYPE} STREQUAL "Debug") @@ -64,6 +64,15 @@ add_library(thorvg STATIC animation/thorvg/src/renderer/tvgTaskScheduler.cpp animation/thorvg/src/renderer/tvgText.cpp animation/thorvg/src/renderer/tvgWgCanvas.cpp + animation/thorvg/src/loaders/lottie/tvgLottieAnimation.cpp + animation/thorvg/src/loaders/lottie/tvgLottieBuilder.cpp + animation/thorvg/src/loaders/lottie/tvgLottieExpressions.cpp + animation/thorvg/src/loaders/lottie/tvgLottieInterpolator.cpp + animation/thorvg/src/loaders/lottie/tvgLottieLoader.cpp + animation/thorvg/src/loaders/lottie/tvgLottieModel.cpp + animation/thorvg/src/loaders/lottie/tvgLottieModifier.cpp + animation/thorvg/src/loaders/lottie/tvgLottieParser.cpp + animation/thorvg/src/loaders/lottie/tvgLottieParserHandler.cpp animation/thorvg/src/loaders/raw/tvgRawLoader.cpp animation/thorvg/src/loaders/svg/tvgSvgCssStyle.cpp animation/thorvg/src/loaders/svg/tvgSvgLoader.cpp @@ -76,57 +85,16 @@ add_library(thorvg STATIC animation/thorvg/src/common/tvgStr.cpp) target_compile_options(thorvg PRIVATE - -DTVG_STATIC=1 -fno-exceptions -ffast-math ${OPTIMIZE_NORMAL} -fno-rtti -fno-unwind-tables -fno-asynchronous-unwind-tables -Woverloaded-virtual -Wno-unused-parameter -Wno-nan-infinity-disabled ${SYM_VISIBILITY}) + -DTVG_STATIC=1 -DBIG_ENDIAN_COLORS -fno-exceptions -ffast-math ${OPTIMIZE_NORMAL} -fno-rtti -fno-unwind-tables -fno-asynchronous-unwind-tables -Woverloaded-virtual -Wno-unused-parameter -Wno-nan-infinity-disabled ${SYM_VISIBILITY}) target_include_directories(thorvg PRIVATE animation/thorvg/inc animation/thorvg/src/renderer animation/thorvg/src/renderer/sw_engine + animation/thorvg/src/loaders/lottie animation/thorvg/src/loaders/raw animation/thorvg/src/loaders/svg animation/thorvg/src/common) -add_library(rlottie STATIC - animation/rlottie/src/lottie/lottieanimation.cpp - animation/rlottie/src/lottie/lottieitem.cpp - animation/rlottie/src/lottie/lottiekeypath.cpp - animation/rlottie/src/lottie/lottieloader.cpp - animation/rlottie/src/lottie/lottiemodel.cpp - animation/rlottie/src/lottie/lottieparser.cpp - animation/rlottie/src/lottie/lottieitem_capi.cpp - animation/rlottie/src/vector/freetype/v_ft_math.cpp - animation/rlottie/src/vector/freetype/v_ft_raster.cpp - animation/rlottie/src/vector/freetype/v_ft_stroker.cpp - animation/rlottie/src/vector/stb/stb_image.cpp - animation/rlottie/src/vector/vbezier.cpp - animation/rlottie/src/vector/vbitmap.cpp - animation/rlottie/src/vector/vbrush.cpp - animation/rlottie/src/vector/varenaalloc.cpp - animation/rlottie/src/vector/vdasher.cpp - animation/rlottie/src/vector/vdebug.cpp - animation/rlottie/src/vector/vdrawable.cpp - animation/rlottie/src/vector/vdrawhelper.cpp - animation/rlottie/src/vector/vdrawhelper_sse2.cpp - animation/rlottie/src/vector/vdrawhelper_neon.cpp - animation/rlottie/src/vector/vdrawhelper_common.cpp - animation/rlottie/src/vector/velapsedtimer.cpp - animation/rlottie/src/vector/vimageloader.cpp - animation/rlottie/src/vector/vinterpolator.cpp - animation/rlottie/src/vector/vmatrix.cpp - animation/rlottie/src/vector/vpainter.cpp - animation/rlottie/src/vector/vpath.cpp - animation/rlottie/src/vector/vpathmesure.cpp - animation/rlottie/src/vector/vraster.cpp - animation/rlottie/src/vector/vrect.cpp - animation/rlottie/src/vector/vrle.cpp) -target_compile_options(rlottie PRIVATE - -fno-exceptions -ffast-math ${OPTIMIZE_NORMAL} -fno-rtti -fno-unwind-tables -fno-asynchronous-unwind-tables -Woverloaded-virtual -Wno-unused-parameter -Wno-nan-infinity-disabled ${SYM_VISIBILITY}) -target_include_directories(rlottie PRIVATE - animation/rlottie/inc - animation/rlottie/src/vector - animation/rlottie/src/vector/pixman - animation/rlottie/src/vector/freetype - animation/rlottie/src/vector/stb) - add_library(libyuv STATIC animation/libyuv/source/compare.cc animation/libyuv/source/compare_common.cc @@ -164,6 +132,7 @@ add_library(libyuv STATIC animation/libyuv/source/row_neon64.cc animation/libyuv/source/row_neon.cc animation/libyuv/source/row_rvv.cc + animation/libyuv/source/row_sme.cc animation/libyuv/source/row_sve.cc animation/libyuv/source/scale_any.cc animation/libyuv/source/scale_argb.cc @@ -176,6 +145,7 @@ add_library(libyuv STATIC animation/libyuv/source/scale_neon.cc animation/libyuv/source/scale_rgb.cc animation/libyuv/source/scale_rvv.cc + animation/libyuv/source/scale_sme.cc animation/libyuv/source/scale_uv.cc animation/libyuv/source/video_common.cc) target_compile_options(libyuv PRIVATE -ffast-math ${OPTIMIZE_NORMAL} -funroll-loops -fno-strict-aliasing -fno-math-errno ${SYM_VISIBILITY}) @@ -487,7 +457,6 @@ target_compile_options(common PRIVATE add_library(fenrir_jni SHARED jni_call.cpp - animation/lottie_jni.cpp animation/thorvg_jni.cpp animation/animation_jni.cpp animation/image_processing_util_jni.cpp @@ -511,7 +480,6 @@ add_library(fenrir_jni target_include_directories(fenrir_jni PRIVATE ./ - animation/rlottie/inc animation/thorvg/inc animation/libyuv/include compress/lz4 @@ -544,7 +512,6 @@ target_link_libraries(fenrir_jni PRIVATE swresample ${LTO_LINK} PRIVATE avutil ${LTO_LINK} PRIVATE common ${LTO_LINK} - PRIVATE rlottie ${LTO_LINK} PRIVATE thorvg ${LTO_LINK} PRIVATE libyuv ${LTO_LINK} PRIVATE opus ${LTO_LINK}) diff --git a/libfenrir/src/main/jni/animation/gif_builder.h b/libfenrir/src/main/jni/animation/gif_builder.h deleted file mode 100644 index bd07c896b..000000000 --- a/libfenrir/src/main/jni/animation/gif_builder.h +++ /dev/null @@ -1,836 +0,0 @@ -#ifndef gif_builder_h -#define gif_builder_h - -#include -#include -#include -#include - -#ifndef GIF_TEMP_MALLOC - -#include - -#define GIF_TEMP_MALLOC malloc -#endif - -#ifndef GIF_TEMP_FREE - -#include - -#define GIF_TEMP_FREE free -#endif - -#ifndef GIF_MALLOC - -#include - -#define GIF_MALLOC malloc -#endif - -#ifndef GIF_FREE - -#include - -#define GIF_FREE free -#endif - -#include -#include -#include -#include - -const int kGifTransIndex = 0; - -typedef struct { - int bitDepth; - - uint8_t r[256]; - uint8_t g[256]; - uint8_t b[256]; - - // k-d tree over RGB space, organized in heap fashion - // i.e. left child of node i is node i*2, right child is node i*2+1 - // nodes 256-511 are implicitly the leaves, containing a color - uint8_t treeSplitElt[256]; - uint8_t treeSplit[256]; -} GifPalette; - -// max, min, and abs functions -int GifIMax(int l, int r) { return l > r ? l : r; } - -int GifIMin(int l, int r) { return l < r ? l : r; } - -int GifIAbs(int i) { return i < 0 ? -i : i; } - -// walks the k-d tree to pick the palette entry for a desired color. -// Takes as in/out parameters the current best color and its error - -// only changes them if it finds a better color in its subtree. -// this is the major hotspot in the code at the moment. -void GifGetClosestPaletteColor(GifPalette *pPal, int r, int g, int b, int *bestInd, int *bestDiff, - int treeRoot) { - // base case, reached the bottom of the tree - if (treeRoot > (1 << pPal->bitDepth) - 1) { - int ind = treeRoot - (1 << pPal->bitDepth); - if (ind == kGifTransIndex) return; - - // check whether this color is better than the current winner - int r_err = r - ((int32_t) pPal->r[ind]); - int g_err = g - ((int32_t) pPal->g[ind]); - int b_err = b - ((int32_t) pPal->b[ind]); - int diff = GifIAbs(r_err) + GifIAbs(g_err) + GifIAbs(b_err); - - if (diff < *bestDiff) { - *bestInd = ind; - *bestDiff = diff; - } - - return; - } - - // take the appropriate color (r, g, or b) for this node of the k-d tree - int comps[3]; - comps[0] = r; - comps[1] = g; - comps[2] = b; - int splitComp = comps[pPal->treeSplitElt[treeRoot]]; - - int splitPos = pPal->treeSplit[treeRoot]; - if (splitPos > splitComp) { - // check the left subtree - GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot * 2); - if (*bestDiff > splitPos - splitComp) { - // cannot prove there's not a better value in the right subtree, check that too - GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot * 2 + 1); - } - } else { - GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot * 2 + 1); - if (*bestDiff > splitComp - splitPos) { - GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot * 2); - } - } -} - -void GifSwapPixels(uint8_t *image, int pixA, int pixB) { - uint8_t rA = image[pixA * 4]; - uint8_t gA = image[pixA * 4 + 1]; - uint8_t bA = image[pixA * 4 + 2]; - uint8_t aA = image[pixA * 4 + 3]; - - uint8_t rB = image[pixB * 4]; - uint8_t gB = image[pixB * 4 + 1]; - uint8_t bB = image[pixB * 4 + 2]; - uint8_t aB = image[pixA * 4 + 3]; - - image[pixA * 4] = rB; - image[pixA * 4 + 1] = gB; - image[pixA * 4 + 2] = bB; - image[pixA * 4 + 3] = aB; - - image[pixB * 4] = rA; - image[pixB * 4 + 1] = gA; - image[pixB * 4 + 2] = bA; - image[pixB * 4 + 3] = aA; -} - -// just the partition operation from quicksort -int GifPartition(uint8_t *image, const int left, const int right, const int elt, int pivotIndex) { - const int pivotValue = image[(pivotIndex) * 4 + elt]; - GifSwapPixels(image, pivotIndex, right - 1); - int storeIndex = left; - bool split = 0; - for (int ii = left; ii < right - 1; ++ii) { - int arrayVal = image[ii * 4 + elt]; - if (arrayVal < pivotValue) { - GifSwapPixels(image, ii, storeIndex); - ++storeIndex; - } else if (arrayVal == pivotValue) { - if (split) { - GifSwapPixels(image, ii, storeIndex); - ++storeIndex; - } - split = !split; - } - } - GifSwapPixels(image, storeIndex, right - 1); - return storeIndex; -} - -// Perform an incomplete sort, finding all elements above and below the desired median -void GifPartitionByMedian(uint8_t *image, int left, int right, int com, int neededCenter) { - if (left < right - 1) { - int pivotIndex = left + (right - left) / 2; - - pivotIndex = GifPartition(image, left, right, com, pivotIndex); - - // Only "sort" the section of the array that contains the median - if (pivotIndex > neededCenter) - GifPartitionByMedian(image, left, pivotIndex, com, neededCenter); - - if (pivotIndex < neededCenter) - GifPartitionByMedian(image, pivotIndex + 1, right, com, neededCenter); - } -} - -// Builds a palette by creating a balanced k-d tree of all pixels in the image -void GifSplitPalette(uint8_t *image, int numPixels, int firstElt, int lastElt, int splitElt, - int splitDist, int treeNode, bool buildForDither, GifPalette *pal) { - if (lastElt <= firstElt || numPixels == 0) - return; - - // base case, bottom of the tree - if (lastElt == firstElt + 1) { - if (buildForDither) { - // Dithering needs at least one color as dark as anything - // in the image and at least one brightest color - - // otherwise it builds up error and produces strange artifacts - if (firstElt == 1) { - // special case: the darkest color in the image - uint32_t r = 255, g = 255, b = 255; - for (int ii = 0; ii < numPixels; ++ii) { - r = (uint32_t) GifIMin((int32_t) r, image[ii * 4 + 0]); - g = (uint32_t) GifIMin((int32_t) g, image[ii * 4 + 1]); - b = (uint32_t) GifIMin((int32_t) b, image[ii * 4 + 2]); - } - - pal->r[firstElt] = (uint8_t) r; - pal->g[firstElt] = (uint8_t) g; - pal->b[firstElt] = (uint8_t) b; - - return; - } - - if (firstElt == (1 << pal->bitDepth) - 1) { - // special case: the lightest color in the image - uint32_t r = 0, g = 0, b = 0; - for (int ii = 0; ii < numPixels; ++ii) { - r = (uint32_t) GifIMax((int32_t) r, image[ii * 4 + 0]); - g = (uint32_t) GifIMax((int32_t) g, image[ii * 4 + 1]); - b = (uint32_t) GifIMax((int32_t) b, image[ii * 4 + 2]); - } - - pal->r[firstElt] = (uint8_t) r; - pal->g[firstElt] = (uint8_t) g; - pal->b[firstElt] = (uint8_t) b; - - return; - } - } - - // otherwise, take the average of all colors in this subcube - uint64_t r = 0, g = 0, b = 0; - for (int ii = 0; ii < numPixels; ++ii) { - r += image[ii * 4 + 0]; - g += image[ii * 4 + 1]; - b += image[ii * 4 + 2]; - } - - r += (uint64_t) numPixels / 2; // round to nearest - g += (uint64_t) numPixels / 2; - b += (uint64_t) numPixels / 2; - - r /= (uint64_t) numPixels; - g /= (uint64_t) numPixels; - b /= (uint64_t) numPixels; - - pal->r[firstElt] = (uint8_t) r; - pal->g[firstElt] = (uint8_t) g; - pal->b[firstElt] = (uint8_t) b; - - return; - } - - // Find the axis with the largest range - int minR = 255, maxR = 0; - int minG = 255, maxG = 0; - int minB = 255, maxB = 0; - for (int ii = 0; ii < numPixels; ++ii) { - int r = image[ii * 4 + 0]; - int g = image[ii * 4 + 1]; - int b = image[ii * 4 + 2]; - - if (r > maxR) maxR = r; - if (r < minR) minR = r; - - if (g > maxG) maxG = g; - if (g < minG) minG = g; - - if (b > maxB) maxB = b; - if (b < minB) minB = b; - } - - int rRange = maxR - minR; - int gRange = maxG - minG; - int bRange = maxB - minB; - - // and split along that axis. (incidentally, this means this isn't a "proper" k-d tree but I don't know what else to call it) - int splitCom = 1; - if (bRange > gRange) splitCom = 2; - if (rRange > bRange && rRange > gRange) splitCom = 0; - - int subPixelsA = numPixels * (splitElt - firstElt) / (lastElt - firstElt); - int subPixelsB = numPixels - subPixelsA; - - GifPartitionByMedian(image, 0, numPixels, splitCom, subPixelsA); - - pal->treeSplitElt[treeNode] = (uint8_t) splitCom; - pal->treeSplit[treeNode] = image[subPixelsA * 4 + splitCom]; - - GifSplitPalette(image, subPixelsA, firstElt, splitElt, splitElt - splitDist, splitDist / 2, - treeNode * 2, buildForDither, pal); - GifSplitPalette(image + subPixelsA * 4, subPixelsB, splitElt, lastElt, splitElt + splitDist, - splitDist / 2, treeNode * 2 + 1, buildForDither, pal); -} - -// Finds all pixels that have changed from the previous image and -// moves them to the fromt of th buffer. -// This allows us to build a palette optimized for the colors of the -// changed pixels only. -int GifPickChangedPixels(const uint8_t *lastFrame, uint8_t *frame, int numPixels) { - int numChanged = 0; - uint8_t *writeIter = frame; - - for (int ii = 0; ii < numPixels; ++ii) { - if (lastFrame[0] != frame[0] || - lastFrame[1] != frame[1] || - lastFrame[2] != frame[2]) { - writeIter[0] = frame[0]; - writeIter[1] = frame[1]; - writeIter[2] = frame[2]; - ++numChanged; - writeIter += 4; - } - lastFrame += 4; - frame += 4; - } - - return numChanged; -} - -// Creates a palette by placing all the image pixels in a k-d tree and then averaging the blocks at the bottom. -// This is known as the "modified median split" technique -void -GifMakePalette(const uint8_t *lastFrame, const uint8_t *nextFrame, uint32_t width, uint32_t height, - int bitDepth, bool buildForDither, GifPalette *pPal) { - pPal->bitDepth = bitDepth; - - // SplitPalette is destructive (it sorts the pixels by color) so - // we must create a copy of the image for it to destroy - size_t imageSize = (size_t)(width * height * 4 * sizeof(uint8_t)); - uint8_t *destroyableImage = (uint8_t *) GIF_TEMP_MALLOC(imageSize); - memcpy(destroyableImage, nextFrame, imageSize); - - int numPixels = (int) (width * height); - if (lastFrame) - numPixels = GifPickChangedPixels(lastFrame, destroyableImage, numPixels); - - const int lastElt = 1 << bitDepth; - const int splitElt = lastElt / 2; - const int splitDist = splitElt / 2; - - GifSplitPalette(destroyableImage, numPixels, 1, lastElt, splitElt, splitDist, 1, buildForDither, - pPal); - - GIF_TEMP_FREE(destroyableImage); - - // add the bottom node for the transparency index - pPal->treeSplit[1 << (bitDepth - 1)] = 0; - pPal->treeSplitElt[1 << (bitDepth - 1)] = 0; - - pPal->r[0] = pPal->g[0] = pPal->b[0] = 0; -} - -// Implements Floyd-Steinberg dithering, writes palette value to alpha -void GifDitherImage(const uint8_t *lastFrame, const uint8_t *nextFrame, uint8_t *outFrame, - uint32_t width, uint32_t height, GifPalette *pPal) { - int numPixels = (int) (width * height); - - // quantPixels initially holds color*256 for all pixels - // The extra 8 bits of precision allow for sub-single-color error values - // to be propagated - int32_t *quantPixels = (int32_t *) GIF_TEMP_MALLOC(sizeof(int32_t) * (size_t) numPixels * 4); - - for (int ii = 0; ii < numPixels * 4; ++ii) { - uint8_t pix = nextFrame[ii]; - int32_t pix16 = (int32_t)(pix) * 256; - quantPixels[ii] = pix16; - } - - for (uint32_t yy = 0; yy < height; ++yy) { - for (uint32_t xx = 0; xx < width; ++xx) { - int32_t *nextPix = quantPixels + 4 * (yy * width + xx); - const uint8_t *lastPix = lastFrame ? lastFrame + 4 * (yy * width + xx) : NULL; - - // Compute the colors we want (rounding to nearest) - int32_t rr = (nextPix[0] + 127) / 256; - int32_t gg = (nextPix[1] + 127) / 256; - int32_t bb = (nextPix[2] + 127) / 256; - - // if it happens that we want the color from last frame, then just write out - // a transparent pixel - if (lastFrame && - lastPix[0] == rr && - lastPix[1] == gg && - lastPix[2] == bb) { - nextPix[0] = rr; - nextPix[1] = gg; - nextPix[2] = bb; - nextPix[3] = kGifTransIndex; - continue; - } - - int32_t bestDiff = 1000000; - int32_t bestInd = kGifTransIndex; - - // Search the palete - GifGetClosestPaletteColor(pPal, rr, gg, bb, &bestInd, &bestDiff, 1); - - // Write the result to the temp buffer - int32_t r_err = nextPix[0] - (int32_t)(pPal->r[bestInd]) * 256; - int32_t g_err = nextPix[1] - (int32_t)(pPal->g[bestInd]) * 256; - int32_t b_err = nextPix[2] - (int32_t)(pPal->b[bestInd]) * 256; - - nextPix[0] = pPal->r[bestInd]; - nextPix[1] = pPal->g[bestInd]; - nextPix[2] = pPal->b[bestInd]; - nextPix[3] = bestInd; - - // Propagate the error to the four adjacent locations - // that we haven't touched yet - int quantloc_7 = (int) (yy * width + xx + 1); - int quantloc_3 = (int) (yy * width + width + xx - 1); - int quantloc_5 = (int) (yy * width + width + xx); - int quantloc_1 = (int) (yy * width + width + xx + 1); - - if (quantloc_7 < numPixels) { - int32_t *pix7 = quantPixels + 4 * quantloc_7; - pix7[0] += GifIMax(-pix7[0], r_err * 7 / 16); - pix7[1] += GifIMax(-pix7[1], g_err * 7 / 16); - pix7[2] += GifIMax(-pix7[2], b_err * 7 / 16); - } - - if (quantloc_3 < numPixels) { - int32_t *pix3 = quantPixels + 4 * quantloc_3; - pix3[0] += GifIMax(-pix3[0], r_err * 3 / 16); - pix3[1] += GifIMax(-pix3[1], g_err * 3 / 16); - pix3[2] += GifIMax(-pix3[2], b_err * 3 / 16); - } - - if (quantloc_5 < numPixels) { - int32_t *pix5 = quantPixels + 4 * quantloc_5; - pix5[0] += GifIMax(-pix5[0], r_err * 5 / 16); - pix5[1] += GifIMax(-pix5[1], g_err * 5 / 16); - pix5[2] += GifIMax(-pix5[2], b_err * 5 / 16); - } - - if (quantloc_1 < numPixels) { - int32_t *pix1 = quantPixels + 4 * quantloc_1; - pix1[0] += GifIMax(-pix1[0], r_err / 16); - pix1[1] += GifIMax(-pix1[1], g_err / 16); - pix1[2] += GifIMax(-pix1[2], b_err / 16); - } - } - } - - // Copy the palettized result to the output buffer - for (int ii = 0; ii < numPixels * 4; ++ii) { - outFrame[ii] = (uint8_t) quantPixels[ii]; - } - - GIF_TEMP_FREE(quantPixels); -} - -// Picks palette colors for the image using simple thresholding, no dithering -void GifThresholdImage(const uint8_t *lastFrame, const uint8_t *nextFrame, uint8_t *outFrame, - uint32_t width, uint32_t height, GifPalette *pPal) { - uint32_t numPixels = width * height; - for (uint32_t ii = 0; ii < numPixels; ++ii) { - // if a previous color is available, and it matches the current color, - // set the pixel to transparent - if (lastFrame && - lastFrame[0] == nextFrame[0] && - lastFrame[1] == nextFrame[1] && - lastFrame[2] == nextFrame[2]) { - outFrame[0] = lastFrame[0]; - outFrame[1] = lastFrame[1]; - outFrame[2] = lastFrame[2]; - outFrame[3] = kGifTransIndex; - } else { - // palettize the pixel - int32_t bestDiff = 1000000; - int32_t bestInd = 1; - GifGetClosestPaletteColor(pPal, nextFrame[0], nextFrame[1], nextFrame[2], &bestInd, - &bestDiff, 1); - - // Write the resulting color to the output buffer - outFrame[0] = pPal->r[bestInd]; - outFrame[1] = pPal->g[bestInd]; - outFrame[2] = pPal->b[bestInd]; - outFrame[3] = (uint8_t) bestInd; - } - - if (lastFrame) lastFrame += 4; - outFrame += 4; - nextFrame += 4; - } -} - -// Simple structure to write out the LZW-compressed portion of the image -// one bit at a time -typedef struct { - uint8_t bitIndex; // how many bits in the partial byte written so far - uint8_t byte; // current partial byte - - uint32_t chunkIndex; - uint8_t chunk[256]; // bytes are written in here until we have 256 of them, then written to the file -} GifBitStatus; - -// insert a single bit -void GifWriteBit(GifBitStatus *stat, uint32_t bit) { - bit = bit & 1; - bit = bit << stat->bitIndex; - stat->byte |= bit; - - ++stat->bitIndex; - if (stat->bitIndex > 7) { - // move the newly-finished byte to the chunk buffer - stat->chunk[stat->chunkIndex++] = stat->byte; - // and start a new byte - stat->bitIndex = 0; - stat->byte = 0; - } -} - -// write all bytes so far to the file -void GifWriteChunk(FILE *f, GifBitStatus *stat) { - fputc((int) stat->chunkIndex, f); - fwrite(stat->chunk, 1, stat->chunkIndex, f); - - stat->bitIndex = 0; - stat->byte = 0; - stat->chunkIndex = 0; -} - -void GifWriteCode(FILE *f, GifBitStatus *stat, uint32_t code, uint32_t length) { - for (uint32_t ii = 0; ii < length; ++ii) { - GifWriteBit(stat, code); - code = code >> 1; - - if (stat->chunkIndex == 255) { - GifWriteChunk(f, stat); - } - } -} - -// The LZW dictionary is a 256-ary tree constructed as the file is encoded, -// this is one node -typedef struct { - uint16_t m_next[256]; -} GifLzwNode; - -// write a 256-color (8-bit) image palette to the file -void GifWritePalette(const GifPalette *pPal, FILE *f) { - fputc(0, f); // first color: transparency - fputc(0, f); - fputc(0, f); - - for (int ii = 1; ii < (1 << pPal->bitDepth); ++ii) { - uint32_t r = pPal->r[ii]; - uint32_t g = pPal->g[ii]; - uint32_t b = pPal->b[ii]; - - fputc((int) r, f); - fputc((int) g, f); - fputc((int) b, f); - } -} - -// write the image header, LZW-compress and write out the image -void GifWriteLzwImage(FILE *f, uint8_t *image, uint32_t left, uint32_t top, uint32_t width, - uint32_t height, uint32_t delay, GifPalette *pPal) { - // graphics control extension - fputc(0x21, f); - fputc(0xf9, f); - fputc(0x04, f); - fputc(0x05, f); // leave prev frame in place, this frame has transparency - fputc(delay & 0xff, f); - fputc((delay >> 8) & 0xff, f); - fputc(kGifTransIndex, f); // transparent color index - fputc(0, f); - - fputc(0x2c, f); // image descriptor block - - fputc(left & 0xff, f); // corner of image in canvas space - fputc((left >> 8) & 0xff, f); - fputc(top & 0xff, f); - fputc((top >> 8) & 0xff, f); - - fputc(width & 0xff, f); // width and height of image - fputc((width >> 8) & 0xff, f); - fputc(height & 0xff, f); - fputc((height >> 8) & 0xff, f); - - //fputc(0, f); // no local color table, no transparency - //fputc(0x80, f); // no local color table, but transparency - - fputc(0x80 + pPal->bitDepth - 1, f); // local color table present, 2 ^ bitDepth entries - GifWritePalette(pPal, f); - - const int minCodeSize = pPal->bitDepth; - const uint32_t clearCode = 1 << pPal->bitDepth; - - fputc(minCodeSize, f); // min code size 8 bits - - GifLzwNode *codetree = (GifLzwNode *) GIF_TEMP_MALLOC(sizeof(GifLzwNode) * 4096); - - memset(codetree, 0, sizeof(GifLzwNode) * 4096); - int32_t curCode = -1; - uint32_t codeSize = (uint32_t) minCodeSize + 1; - uint32_t maxCode = clearCode + 1; - - GifBitStatus stat; - stat.byte = 0; - stat.bitIndex = 0; - stat.chunkIndex = 0; - - GifWriteCode(f, &stat, clearCode, codeSize); // start with a fresh LZW dictionary - - for (uint32_t yy = 0; yy < height; ++yy) { - for (uint32_t xx = 0; xx < width; ++xx) { -#ifdef GIF_FLIP_VERT - // bottom-left origin image (such as an OpenGL capture) - uint8_t nextValue = image[((height-1-yy)*width+xx)*4+3]; -#else - // top-left origin - uint8_t nextValue = image[(yy * width + xx) * 4 + 3]; -#endif - - // "loser mode" - no compression, every single code is followed immediately by a clear - //WriteCode( f, stat, nextValue, codeSize ); - //WriteCode( f, stat, 256, codeSize ); - - if (curCode < 0) { - // first value in a new run - curCode = nextValue; - } else if (codetree[curCode].m_next[nextValue]) { - // current run already in the dictionary - curCode = codetree[curCode].m_next[nextValue]; - } else { - // finish the current run, write a code - GifWriteCode(f, &stat, (uint32_t) curCode, codeSize); - - // insert the new run into the dictionary - codetree[curCode].m_next[nextValue] = (uint16_t)++ - maxCode; - - if (maxCode >= (1ul << codeSize)) { - // dictionary entry count has broken a size barrier, - // we need more bits for codes - codeSize++; - } - if (maxCode == 4095) { - // the dictionary is full, clear it out and begin anew - GifWriteCode(f, &stat, clearCode, codeSize); // clear tree - - memset(codetree, 0, sizeof(GifLzwNode) * 4096); - codeSize = (uint32_t)(minCodeSize + 1); - maxCode = clearCode + 1; - } - - curCode = nextValue; - } - } - } - - // compression footer - GifWriteCode(f, &stat, (uint32_t) curCode, codeSize); - GifWriteCode(f, &stat, clearCode, codeSize); - GifWriteCode(f, &stat, clearCode + 1, (uint32_t) minCodeSize + 1); - - // write out the last partial chunk - while (stat.bitIndex) GifWriteBit(&stat, 0); - if (stat.chunkIndex) GifWriteChunk(f, &stat); - - fputc(0, f); // image block terminator - - GIF_TEMP_FREE(codetree); -} - -typedef struct { - FILE *f; - uint8_t *oldImage; - bool firstFrame; -} GifWriter; - -// Creates a gif file. -// The input GIFWriter is assumed to be uninitialized. -// The delay value is the time between frames in hundredths of a second - note that not all viewers pay much attention to this value. -bool -GifBegin(GifWriter *writer, const char *filename, uint32_t width, uint32_t height, uint32_t delay, - int32_t bitDepth = 8, bool dither = false) { - (void) bitDepth; - (void) dither; // Mute "Unused argument" warnings -#if defined(_MSC_VER) && (_MSC_VER >= 1400) - writer->f = 0; - fopen_s(&writer->f, filename, "wb"); -#else - writer->f = fopen(filename, "wb"); -#endif - if (!writer->f) return false; - - writer->firstFrame = true; - - // allocate - writer->oldImage = (uint8_t *) GIF_MALLOC(width * height * 4); - - fputs("GIF89a", writer->f); - - // screen descriptor - fputc(width & 0xff, writer->f); - fputc((width >> 8) & 0xff, writer->f); - fputc(height & 0xff, writer->f); - fputc((height >> 8) & 0xff, writer->f); - - fputc(0xf0, writer->f); // there is an unsorted global color table of 2 entries - fputc(0, writer->f); // background color - fputc(0, writer->f); // pixels are square (we need to specify this because it's 1989) - - // now the "global" palette (really just a dummy palette) - // color 0: black - fputc(0, writer->f); - fputc(0, writer->f); - fputc(0, writer->f); - // color 1: also black - fputc(0, writer->f); - fputc(0, writer->f); - fputc(0, writer->f); - - if (delay != 0) { - // animation header - fputc(0x21, writer->f); // extension - fputc(0xff, writer->f); // application specific - fputc(11, writer->f); // length 11 - fputs("NETSCAPE2.0", writer->f); // yes, really - fputc(3, writer->f); // 3 bytes of NETSCAPE2.0 data - - fputc(1, writer->f); // JUST BECAUSE - fputc(0, writer->f); // loop infinitely (byte 0) - fputc(0, writer->f); // loop infinitely (byte 1) - - fputc(0, writer->f); // block terminator - } - - return true; -} - -// Writes out a new frame to a GIF in progress. -// The GIFWriter should have been created by GIFBegin. -// AFAIK, it is legal to use different bit depths for different frames of an image - -// this may be handy to save bits in animations that don't change much. -bool GifWriteFrame(GifWriter *writer, const uint8_t *image, uint32_t width, uint32_t height, - uint32_t delay, int bitDepth = 8, bool dither = false) { - if (!writer->f) return false; - - const uint8_t *oldImage = writer->firstFrame ? NULL : writer->oldImage; - writer->firstFrame = false; - - GifPalette pal; - GifMakePalette((dither ? NULL : oldImage), image, width, height, bitDepth, dither, &pal); - - if (dither) - GifDitherImage(oldImage, image, writer->oldImage, width, height, &pal); - else - GifThresholdImage(oldImage, image, writer->oldImage, width, height, &pal); - - GifWriteLzwImage(writer->f, writer->oldImage, 0, 0, width, height, delay, &pal); - - return true; -} - -// Writes the EOF code, closes the file handle, and frees temp memory used by a GIF. -// Many if not most viewers will still display a GIF properly if the EOF code is missing, -// but it's still a good idea to write it out. -bool GifEnd(GifWriter *writer) { - if (!writer->f) return false; - - fputc(0x3b, writer->f); // end of file - fclose(writer->f); - GIF_FREE(writer->oldImage); - - writer->f = NULL; - writer->oldImage = NULL; - - return true; -} - -class GifBuilder { -public: - explicit GifBuilder(const std::string &fileName, const uint32_t width, - const uint32_t height, const int bgColor = 0xffffffff, - const uint32_t delay = 2, const int32_t bitDepth = 8, - const bool dither = false) { - GifBegin(&handle, fileName.c_str(), width, height, delay, bitDepth, dither); - bgColorR = (uint8_t)((bgColor & 0xff0000) >> 16); - bgColorG = (uint8_t)((bgColor & 0x00ff00) >> 8); - bgColorB = (uint8_t)((bgColor & 0x0000ff)); - } - - ~GifBuilder() { - GifEnd(&handle); - } - - void - addFrame(uint32_t *buffer, size_t width, size_t height, size_t bytesPerLine, bool transparent, - uint32_t delay = 2, int32_t bitDepth = 8, bool dither = false) { - if (!transparent) { - argbToRGBA(buffer, height, bytesPerLine); - } - GifWriteFrame(&handle, - reinterpret_cast(buffer), - width, - height, - delay, bitDepth, dither); - } - -private: - void argbToRGBA(uint32_t *bufferMP, size_t height, size_t bytesPerLine) { - uint8_t *buffer = reinterpret_cast(bufferMP); - uint32_t totalBytes = height * bytesPerLine; - - for (uint32_t i = 0; i < totalBytes; i += 4) { - unsigned char a = buffer[i + 3]; - // compute only if alpha is non zero - if (a) { - unsigned char r = buffer[i + 2]; - unsigned char g = buffer[i + 1]; - unsigned char b = buffer[i]; - - if (a != 255) { //un premultiply - unsigned char r2 = (unsigned char) ((float) bgColorR * - ((float) (255 - a) / 255)); - unsigned char g2 = (unsigned char) ((float) bgColorG * - ((float) (255 - a) / 255)); - unsigned char b2 = (unsigned char) ((float) bgColorB * - ((float) (255 - a) / 255)); - buffer[i + 2] = r + r2; - buffer[i + 1] = g + g2; - buffer[i] = b + b2; - - } else { - // only swizzle r and b - buffer[i + 2] = r; - buffer[i + 1] = g; - buffer[i] = b; - } - } else { - buffer[i] = bgColorB; - buffer[i + 1] = bgColorG; - buffer[i + 2] = bgColorR; - } - } - } - - GifWriter handle; - uint8_t bgColorR, bgColorG, bgColorB; -}; - -#endif diff --git a/libfenrir/src/main/jni/animation/libyuv/include/libyuv/cpu_id.h b/libfenrir/src/main/jni/animation/libyuv/include/libyuv/cpu_id.h index 8a295658d..57a6a12e9 100644 --- a/libfenrir/src/main/jni/animation/libyuv/include/libyuv/cpu_id.h +++ b/libfenrir/src/main/jni/animation/libyuv/include/libyuv/cpu_id.h @@ -23,49 +23,50 @@ static const int kCpuInitialized = 0x1; // These flags are only valid on Arm processors. static const int kCpuHasARM = 0x2; -static const int kCpuHasNEON = 0x4; -// Leave a gap to avoid setting kCpuHasX86. -static const int kCpuHasNeonDotProd = 0x10; -static const int kCpuHasNeonI8MM = 0x20; -static const int kCpuHasSVE = 0x40; -static const int kCpuHasSVE2 = 0x80; -static const int kCpuHasSME = 0x100; +static const int kCpuHasNEON = 0x100; +static const int kCpuHasNeonDotProd = 0x200; +static const int kCpuHasNeonI8MM = 0x400; +static const int kCpuHasSVE = 0x800; +static const int kCpuHasSVE2 = 0x1000; +static const int kCpuHasSME = 0x2000; + +// These flags are only valid on RISCV processors. +static const int kCpuHasRISCV = 0x4; +static const int kCpuHasRVV = 0x100; +static const int kCpuHasRVVZVFH = 0x200; // These flags are only valid on x86 processors. static const int kCpuHasX86 = 0x8; -static const int kCpuHasSSE2 = 0x10; -static const int kCpuHasSSSE3 = 0x20; -static const int kCpuHasSSE41 = 0x40; -static const int kCpuHasSSE42 = 0x80; -static const int kCpuHasAVX = 0x100; -static const int kCpuHasAVX2 = 0x200; -static const int kCpuHasERMS = 0x400; -static const int kCpuHasFMA3 = 0x800; -static const int kCpuHasF16C = 0x1000; -static const int kCpuHasAVX512BW = 0x2000; -static const int kCpuHasAVX512VL = 0x4000; -static const int kCpuHasAVX512VNNI = 0x8000; -static const int kCpuHasAVX512VBMI = 0x10000; -static const int kCpuHasAVX512VBMI2 = 0x20000; -static const int kCpuHasAVX512VBITALG = 0x40000; -static const int kCpuHasAVX10 = 0x80000; -static const int kCpuHasAVXVNNI = 0x100000; -static const int kCpuHasAVXVNNIINT8 = 0x200000; -static const int kCpuHasAMXINT8 = 0x400000; +static const int kCpuHasSSE2 = 0x100; +static const int kCpuHasSSSE3 = 0x200; +static const int kCpuHasSSE41 = 0x400; +static const int kCpuHasSSE42 = 0x800; +static const int kCpuHasAVX = 0x1000; +static const int kCpuHasAVX2 = 0x2000; +static const int kCpuHasERMS = 0x4000; +static const int kCpuHasFSMR = 0x8000; +static const int kCpuHasFMA3 = 0x10000; +static const int kCpuHasF16C = 0x20000; +static const int kCpuHasAVX512BW = 0x40000; +static const int kCpuHasAVX512VL = 0x80000; +static const int kCpuHasAVX512VNNI = 0x100000; +static const int kCpuHasAVX512VBMI = 0x200000; +static const int kCpuHasAVX512VBMI2 = 0x400000; +static const int kCpuHasAVX512VBITALG = 0x800000; +static const int kCpuHasAVX10 = 0x1000000; +static const int kCpuHasAVXVNNI = 0x2000000; +static const int kCpuHasAVXVNNIINT8 = 0x4000000; +static const int kCpuHasAMXINT8 = 0x8000000; // These flags are only valid on MIPS processors. -static const int kCpuHasMIPS = 0x800000; -static const int kCpuHasMSA = 0x1000000; +static const int kCpuHasMIPS = 0x10; +static const int kCpuHasMSA = 0x100; // These flags are only valid on LOONGARCH processors. -static const int kCpuHasLOONGARCH = 0x2000000; -static const int kCpuHasLSX = 0x4000000; -static const int kCpuHasLASX = 0x8000000; +static const int kCpuHasLOONGARCH = 0x20; +static const int kCpuHasLSX = 0x100; +static const int kCpuHasLASX = 0x200; -// These flags are only valid on RISCV processors. -static const int kCpuHasRISCV = 0x10000000; -static const int kCpuHasRVV = 0x20000000; -static const int kCpuHasRVVZVFH = 0x40000000; // Optional init function. TestCpuFlag does an auto-init. // Returns cpu_info flags. diff --git a/libfenrir/src/main/jni/animation/libyuv/include/libyuv/row.h b/libfenrir/src/main/jni/animation/libyuv/include/libyuv/row.h index 1eda17ad4..70f89134c 100644 --- a/libfenrir/src/main/jni/animation/libyuv/include/libyuv/row.h +++ b/libfenrir/src/main/jni/animation/libyuv/include/libyuv/row.h @@ -265,6 +265,7 @@ extern "C" { #define HAS_SPLITARGBROW_SSE2 #define HAS_SPLITARGBROW_SSSE3 #define HAS_SPLITRGBROW_SSSE3 +#define HAS_SPLITRGBROW_SSE41 #define HAS_SPLITXRGBROW_SSE2 #define HAS_SPLITXRGBROW_SSSE3 #define HAS_SWAPUVROW_SSSE3 @@ -330,6 +331,7 @@ extern "C" { #define HAS_P410TOAR30ROW_AVX2 #define HAS_P410TOARGBROW_AVX2 #define HAS_RGBATOYJROW_AVX2 +#define HAS_SPLITRGBROW_AVX2 #define HAS_SPLITARGBROW_AVX2 #define HAS_SPLITUVROW_16_AVX2 #define HAS_SPLITXRGBROW_AVX2 @@ -353,6 +355,7 @@ extern "C" { // TODO(b/42280744): re-enable HAS_ARGBTORGB24ROW_AVX512VBMI. #if !defined(LIBYUV_DISABLE_X86) && \ (defined(__x86_64__) || defined(__i386__)) && defined(CLANG_HAS_AVX512) +#define HAS_COPYROW_AVX512BW #define HAS_ARGBTORGB24ROW_AVX512VBMI #define HAS_CONVERT16TO8ROW_AVX512BW #define HAS_MERGEUVROW_AVX512BW @@ -548,14 +551,25 @@ extern "C" { #define HAS_AYUVTOUVROW_SVE2 #define HAS_AYUVTOVUROW_SVE2 #define HAS_BGRATOUVROW_SVE2 +#define HAS_DIVIDEROW_16_SVE2 +#define HAS_HALFFLOATROW_SVE2 +#define HAS_I210TOARGBROW_SVE2 #define HAS_I400TOARGBROW_SVE2 #define HAS_I422ALPHATOARGBROW_SVE2 +#define HAS_I422TOARGB1555ROW_SVE2 +#define HAS_I422TOARGB4444ROW_SVE2 #define HAS_I422TOARGBROW_SVE2 +#define HAS_I422TORGB24ROW_SVE2 +#define HAS_I422TORGB565ROW_SVE2 #define HAS_I422TORGBAROW_SVE2 #define HAS_I444ALPHATOARGBROW_SVE2 #define HAS_I444TOARGBROW_SVE2 #define HAS_NV12TOARGBROW_SVE2 #define HAS_NV21TOARGBROW_SVE2 +#define HAS_P210TOAR30ROW_SVE2 +#define HAS_P210TOARGBROW_SVE2 +#define HAS_P410TOAR30ROW_SVE2 +#define HAS_P410TOARGBROW_SVE2 #define HAS_RAWTOARGBROW_SVE2 #define HAS_RAWTORGB24ROW_SVE2 #define HAS_RAWTORGBAROW_SVE2 @@ -565,6 +579,13 @@ extern "C" { #define HAS_YUY2TOARGBROW_SVE2 #endif +// The following are available on AArch64 SME platforms: +#if !defined(LIBYUV_DISABLE_SME) && defined(CLANG_HAS_SME) && \ + defined(__aarch64__) +#define HAS_I422TOARGBROW_SME +#define HAS_I444TOARGBROW_SME +#endif + // The following are available on AArch64 platforms: #if !defined(LIBYUV_DISABLE_NEON) && defined(__aarch64__) #define HAS_GAUSSCOL_F32_NEON @@ -1062,6 +1083,12 @@ void I444ToARGBRow_SVE2(const uint8_t* src_y, uint8_t* dst_argb, const struct YuvConstants* yuvconstants, int width); +void I444ToARGBRow_SME(const uint8_t* src_y, + const uint8_t* src_u, + const uint8_t* src_v, + uint8_t* dst_argb, + const struct YuvConstants* yuvconstants, + int width); void I444ToRGB24Row_NEON(const uint8_t* src_y, const uint8_t* src_u, const uint8_t* src_v, @@ -1074,6 +1101,12 @@ void I210ToARGBRow_NEON(const uint16_t* src_y, uint8_t* rgb_buf, const struct YuvConstants* yuvconstants, int width); +void I210ToARGBRow_SVE2(const uint16_t* src_y, + const uint16_t* src_u, + const uint16_t* src_v, + uint8_t* rgb_buf, + const struct YuvConstants* yuvconstants, + int width); void I410ToARGBRow_NEON(const uint16_t* src_y, const uint16_t* src_u, const uint16_t* src_v, @@ -1116,6 +1149,12 @@ void I422ToARGBRow_SVE2(const uint8_t* src_y, uint8_t* dst_argb, const struct YuvConstants* yuvconstants, int width); +void I422ToARGBRow_SME(const uint8_t* src_y, + const uint8_t* src_u, + const uint8_t* src_v, + uint8_t* dst_argb, + const struct YuvConstants* yuvconstants, + int width); void I422ToAR30Row_NEON(const uint8_t* src_y, const uint8_t* src_u, const uint8_t* src_v, @@ -1168,24 +1207,48 @@ void I422ToRGB24Row_NEON(const uint8_t* src_y, uint8_t* dst_rgb24, const struct YuvConstants* yuvconstants, int width); +void I422ToRGB24Row_SVE2(const uint8_t* src_y, + const uint8_t* src_u, + const uint8_t* src_v, + uint8_t* dst_rgb24, + const struct YuvConstants* yuvconstants, + int width); void I422ToRGB565Row_NEON(const uint8_t* src_y, const uint8_t* src_u, const uint8_t* src_v, uint8_t* dst_rgb565, const struct YuvConstants* yuvconstants, int width); +void I422ToRGB565Row_SVE2(const uint8_t* src_y, + const uint8_t* src_u, + const uint8_t* src_v, + uint8_t* dst_rgb565, + const struct YuvConstants* yuvconstants, + int width); void I422ToARGB1555Row_NEON(const uint8_t* src_y, const uint8_t* src_u, const uint8_t* src_v, uint8_t* dst_argb1555, const struct YuvConstants* yuvconstants, int width); +void I422ToARGB1555Row_SVE2(const uint8_t* src_y, + const uint8_t* src_u, + const uint8_t* src_v, + uint8_t* dst_argb1555, + const struct YuvConstants* yuvconstants, + int width); void I422ToARGB4444Row_NEON(const uint8_t* src_y, const uint8_t* src_u, const uint8_t* src_v, uint8_t* dst_argb4444, const struct YuvConstants* yuvconstants, int width); +void I422ToARGB4444Row_SVE2(const uint8_t* src_y, + const uint8_t* src_u, + const uint8_t* src_v, + uint8_t* dst_argb4444, + const struct YuvConstants* yuvconstants, + int width); void NV12ToARGBRow_NEON(const uint8_t* src_y, const uint8_t* src_uv, uint8_t* dst_argb, @@ -2743,6 +2806,16 @@ void SplitRGBRow_SSSE3(const uint8_t* src_rgb, uint8_t* dst_g, uint8_t* dst_b, int width); +void SplitRGBRow_SSE41(const uint8_t* src_rgb, + uint8_t* dst_r, + uint8_t* dst_g, + uint8_t* dst_b, + int width); +void SplitRGBRow_AVX2(const uint8_t* src_rgb, + uint8_t* dst_r, + uint8_t* dst_g, + uint8_t* dst_b, + int width); void SplitRGBRow_NEON(const uint8_t* src_rgb, uint8_t* dst_r, uint8_t* dst_g, @@ -2758,6 +2831,16 @@ void SplitRGBRow_Any_SSSE3(const uint8_t* src_ptr, uint8_t* dst_g, uint8_t* dst_b, int width); +void SplitRGBRow_Any_SSE41(const uint8_t* src_ptr, + uint8_t* dst_r, + uint8_t* dst_g, + uint8_t* dst_b, + int width); +void SplitRGBRow_Any_AVX2(const uint8_t* src_ptr, + uint8_t* dst_r, + uint8_t* dst_g, + uint8_t* dst_b, + int width); void SplitRGBRow_Any_NEON(const uint8_t* src_ptr, uint8_t* dst_r, uint8_t* dst_g, @@ -3255,6 +3338,10 @@ void DivideRow_16_NEON(const uint16_t* src_y, uint16_t* dst_y, int scale, int width); +void DivideRow_16_SVE2(const uint16_t* src_y, + uint16_t* dst_y, + int scale, + int width); void DivideRow_16_Any_NEON(const uint16_t* src_ptr, uint16_t* dst_ptr, int scale, @@ -3320,6 +3407,7 @@ void Convert16To8Row_Any_NEON(const uint16_t* src_ptr, void CopyRow_SSE2(const uint8_t* src, uint8_t* dst, int width); void CopyRow_AVX(const uint8_t* src, uint8_t* dst, int width); +void CopyRow_AVX512BW(const uint8_t* src, uint8_t* dst, int width); void CopyRow_ERMS(const uint8_t* src, uint8_t* dst, int width); void CopyRow_NEON(const uint8_t* src, uint8_t* dst, int width); void CopyRow_MIPS(const uint8_t* src, uint8_t* dst, int count); @@ -3327,6 +3415,7 @@ void CopyRow_RVV(const uint8_t* src, uint8_t* dst, int count); void CopyRow_C(const uint8_t* src, uint8_t* dst, int count); void CopyRow_Any_SSE2(const uint8_t* src_ptr, uint8_t* dst_ptr, int width); void CopyRow_Any_AVX(const uint8_t* src_ptr, uint8_t* dst_ptr, int width); +void CopyRow_Any_AVX512BW(const uint8_t* src_ptr, uint8_t* dst_ptr, int width); void CopyRow_Any_NEON(const uint8_t* src_ptr, uint8_t* dst_ptr, int width); void CopyRow_16_C(const uint16_t* src, uint16_t* dst, int count); @@ -5323,21 +5412,41 @@ void P210ToARGBRow_NEON(const uint16_t* y_buf, uint8_t* dst_argb, const struct YuvConstants* yuvconstants, int width); +void P210ToARGBRow_SVE2(const uint16_t* y_buf, + const uint16_t* uv_buf, + uint8_t* dst_argb, + const struct YuvConstants* yuvconstants, + int width); void P410ToARGBRow_NEON(const uint16_t* y_buf, const uint16_t* uv_buf, uint8_t* dst_argb, const struct YuvConstants* yuvconstants, int width); +void P410ToARGBRow_SVE2(const uint16_t* y_buf, + const uint16_t* uv_buf, + uint8_t* dst_argb, + const struct YuvConstants* yuvconstants, + int width); void P210ToAR30Row_NEON(const uint16_t* y_buf, const uint16_t* uv_buf, uint8_t* dst_ar30, const struct YuvConstants* yuvconstants, int width); +void P210ToAR30Row_SVE2(const uint16_t* y_buf, + const uint16_t* uv_buf, + uint8_t* dst_ar30, + const struct YuvConstants* yuvconstants, + int width); void P410ToAR30Row_NEON(const uint16_t* y_buf, const uint16_t* uv_buf, uint8_t* dst_ar30, const struct YuvConstants* yuvconstants, int width); +void P410ToAR30Row_SVE2(const uint16_t* y_buf, + const uint16_t* uv_buf, + uint8_t* dst_ar30, + const struct YuvConstants* yuvconstants, + int width); void P210ToARGBRow_Any_NEON(const uint16_t* y_buf, const uint16_t* uv_buf, uint8_t* dst_argb, @@ -6557,6 +6666,10 @@ void HalfFloatRow_Any_NEON(const uint16_t* src_ptr, uint16_t* dst_ptr, float param, int width); +void HalfFloatRow_SVE2(const uint16_t* src, + uint16_t* dst, + float scale, + int width); void HalfFloat1Row_NEON(const uint16_t* src, uint16_t* dst, float scale, @@ -6565,6 +6678,10 @@ void HalfFloat1Row_Any_NEON(const uint16_t* src_ptr, uint16_t* dst_ptr, float param, int width); +void HalfFloat1Row_SVE2(const uint16_t* src, + uint16_t* dst, + float scale, + int width); void HalfFloatRow_MSA(const uint16_t* src, uint16_t* dst, float scale, diff --git a/libfenrir/src/main/jni/animation/libyuv/include/libyuv/scale_row.h b/libfenrir/src/main/jni/animation/libyuv/include/libyuv/scale_row.h index a8ec4776a..101ccbf84 100644 --- a/libfenrir/src/main/jni/animation/libyuv/include/libyuv/scale_row.h +++ b/libfenrir/src/main/jni/animation/libyuv/include/libyuv/scale_row.h @@ -116,6 +116,15 @@ extern "C" { #define HAS_SCALEUVROWUP2_BILINEAR_16_NEON #endif +// The following are available on AArch64 SME platforms: +#if !defined(LIBYUV_DISABLE_SME) && defined(CLANG_HAS_SME) && \ + defined(__aarch64__) +#define HAS_SCALEROWDOWN2_SME +#define HAS_SCALEUVROWDOWN2_SME +#define HAS_SCALEUVROWDOWN2LINEAR_SME +#define HAS_SCALEUVROWDOWN2BOX_SME +#endif + #if !defined(LIBYUV_DISABLE_MSA) && defined(__mips_msa) #define HAS_SCALEADDROW_MSA #define HAS_SCALEARGBCOLS_MSA @@ -1148,14 +1157,26 @@ void ScaleUVRowDown2_NEON(const uint8_t* src_ptr, ptrdiff_t src_stride, uint8_t* dst, int dst_width); +void ScaleUVRowDown2_SME(const uint8_t* src_ptr, + ptrdiff_t src_stride, + uint8_t* dst, + int dst_width); void ScaleUVRowDown2Linear_NEON(const uint8_t* src_ptr, ptrdiff_t src_stride, uint8_t* dst_uv, int dst_width); +void ScaleUVRowDown2Linear_SME(const uint8_t* src_ptr, + ptrdiff_t src_stride, + uint8_t* dst_uv, + int dst_width); void ScaleUVRowDown2Box_NEON(const uint8_t* src_ptr, ptrdiff_t src_stride, uint8_t* dst, int dst_width); +void ScaleUVRowDown2Box_SME(const uint8_t* src_ptr, + ptrdiff_t src_stride, + uint8_t* dst, + int dst_width); void ScaleUVRowDown2_MSA(const uint8_t* src_ptr, ptrdiff_t src_stride, uint8_t* dst_uv, @@ -1397,21 +1418,31 @@ void ScaleUVRowUp2_Bilinear_16_Any_NEON(const uint16_t* src_ptr, int dst_width); // ScaleRowDown2Box also used by planar functions -// NEON downscalers with interpolation. - -// Note - not static due to reuse in convert for 444 to 420. +// NEON/SME downscalers with interpolation. void ScaleRowDown2_NEON(const uint8_t* src_ptr, ptrdiff_t src_stride, uint8_t* dst, int dst_width); +void ScaleRowDown2_SME(const uint8_t* src_ptr, + ptrdiff_t src_stride, + uint8_t* dst, + int dst_width); void ScaleRowDown2Linear_NEON(const uint8_t* src_ptr, ptrdiff_t src_stride, uint8_t* dst, int dst_width); +void ScaleRowDown2Linear_SME(const uint8_t* src_ptr, + ptrdiff_t src_stride, + uint8_t* dst, + int dst_width); void ScaleRowDown2Box_NEON(const uint8_t* src_ptr, ptrdiff_t src_stride, uint8_t* dst, int dst_width); +void ScaleRowDown2Box_SME(const uint8_t* src_ptr, + ptrdiff_t src_stride, + uint8_t* dst, + int dst_width); void ScaleRowDown4_NEON(const uint8_t* src_ptr, ptrdiff_t src_stride, diff --git a/libfenrir/src/main/jni/animation/libyuv/include/libyuv/version.h b/libfenrir/src/main/jni/animation/libyuv/include/libyuv/version.h index 23d3ad67a..1e01bac5a 100644 --- a/libfenrir/src/main/jni/animation/libyuv/include/libyuv/version.h +++ b/libfenrir/src/main/jni/animation/libyuv/include/libyuv/version.h @@ -11,6 +11,6 @@ #ifndef INCLUDE_LIBYUV_VERSION_H_ #define INCLUDE_LIBYUV_VERSION_H_ -#define LIBYUV_VERSION 1897 +#define LIBYUV_VERSION 1898 #endif // INCLUDE_LIBYUV_VERSION_H_ diff --git a/libfenrir/src/main/jni/animation/libyuv/source/convert_argb.cc b/libfenrir/src/main/jni/animation/libyuv/source/convert_argb.cc index a77358df8..7a2f7813f 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/convert_argb.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/convert_argb.cc @@ -114,6 +114,11 @@ int I420ToARGBMatrix(const uint8_t* src_y, I422ToARGBRow = I422ToARGBRow_SVE2; } #endif +#if defined(HAS_I422TOARGBROW_SME) + if (TestCpuFlag(kCpuHasSME)) { + I422ToARGBRow = I422ToARGBRow_SME; + } +#endif #if defined(HAS_I422TOARGBROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I422ToARGBRow = I422ToARGBRow_Any_MSA; @@ -373,6 +378,11 @@ int I422ToARGBMatrix(const uint8_t* src_y, I422ToARGBRow = I422ToARGBRow_SVE2; } #endif +#if defined(HAS_I422TOARGBROW_SME) + if (TestCpuFlag(kCpuHasSME)) { + I422ToARGBRow = I422ToARGBRow_SME; + } +#endif #if defined(HAS_I422TOARGBROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I422ToARGBRow = I422ToARGBRow_Any_MSA; @@ -621,6 +631,11 @@ int I444ToARGBMatrix(const uint8_t* src_y, I444ToARGBRow = I444ToARGBRow_SVE2; } #endif +#if defined(HAS_I444TOARGBROW_SME) + if (TestCpuFlag(kCpuHasSME)) { + I444ToARGBRow = I444ToARGBRow_SME; + } +#endif #if defined(HAS_I444TOARGBROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I444ToARGBRow = I444ToARGBRow_Any_MSA; @@ -1429,6 +1444,11 @@ int I010ToARGBMatrix(const uint16_t* src_y, } } #endif +#if defined(HAS_I210TOARGBROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + I210ToARGBRow = I210ToARGBRow_SVE2; + } +#endif #if defined(HAS_I210TOARGBROW_AVX2) if (TestCpuFlag(kCpuHasAVX2)) { I210ToARGBRow = I210ToARGBRow_Any_AVX2; @@ -1665,6 +1685,11 @@ int I210ToARGBMatrix(const uint16_t* src_y, } } #endif +#if defined(HAS_I210TOARGBROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + I210ToARGBRow = I210ToARGBRow_SVE2; + } +#endif #if defined(HAS_I210TOARGBROW_AVX2) if (TestCpuFlag(kCpuHasAVX2)) { I210ToARGBRow = I210ToARGBRow_Any_AVX2; @@ -1899,6 +1924,11 @@ int P010ToARGBMatrix(const uint16_t* src_y, P210ToARGBRow = P210ToARGBRow_NEON; } } +#endif +#if defined(HAS_P210TOARGBROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + P210ToARGBRow = P210ToARGBRow_SVE2; + } #endif for (y = 0; y < height; ++y) { P210ToARGBRow(src_y, src_uv, dst_argb, yuvconstants, width); @@ -1958,6 +1988,11 @@ int P210ToARGBMatrix(const uint16_t* src_y, P210ToARGBRow = P210ToARGBRow_NEON; } } +#endif +#if defined(HAS_P210TOARGBROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + P210ToARGBRow = P210ToARGBRow_SVE2; + } #endif for (y = 0; y < height; ++y) { P210ToARGBRow(src_y, src_uv, dst_argb, yuvconstants, width); @@ -2015,6 +2050,11 @@ int P010ToAR30Matrix(const uint16_t* src_y, P210ToAR30Row = P210ToAR30Row_NEON; } } +#endif +#if defined(HAS_P210TOAR30ROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + P210ToAR30Row = P210ToAR30Row_SVE2; + } #endif for (y = 0; y < height; ++y) { P210ToAR30Row(src_y, src_uv, dst_ar30, yuvconstants, width); @@ -2074,6 +2114,11 @@ int P210ToAR30Matrix(const uint16_t* src_y, P210ToAR30Row = P210ToAR30Row_NEON; } } +#endif +#if defined(HAS_P210TOAR30ROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + P210ToAR30Row = P210ToAR30Row_SVE2; + } #endif for (y = 0; y < height; ++y) { P210ToAR30Row(src_y, src_uv, dst_ar30, yuvconstants, width); @@ -5362,6 +5407,11 @@ int I420ToRGB24Matrix(const uint8_t* src_y, } } #endif +#if defined(HAS_I422TORGB24ROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + I422ToRGB24Row = I422ToRGB24Row_SVE2; + } +#endif #if defined(HAS_I422TORGB24ROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I422ToRGB24Row = I422ToRGB24Row_Any_MSA; @@ -5564,6 +5614,11 @@ int I422ToRGB24Matrix(const uint8_t* src_y, } } #endif +#if defined(HAS_I422TORGB24ROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + I422ToRGB24Row = I422ToRGB24Row_SVE2; + } +#endif #if defined(HAS_I422TORGB24ROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I422ToRGB24Row = I422ToRGB24Row_Any_MSA; @@ -5691,6 +5746,11 @@ int I420ToARGB1555(const uint8_t* src_y, } } #endif +#if defined(HAS_I422TOARGB1555ROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + I422ToARGB1555Row = I422ToARGB1555Row_SVE2; + } +#endif #if defined(HAS_I422TOARGB1555ROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I422ToARGB1555Row = I422ToARGB1555Row_Any_MSA; @@ -5780,6 +5840,11 @@ int I420ToARGB4444(const uint8_t* src_y, } } #endif +#if defined(HAS_I422TOARGB4444ROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + I422ToARGB4444Row = I422ToARGB4444Row_SVE2; + } +#endif #if defined(HAS_I422TOARGB4444ROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I422ToARGB4444Row = I422ToARGB4444Row_Any_MSA; @@ -5870,6 +5935,11 @@ int I420ToRGB565Matrix(const uint8_t* src_y, } } #endif +#if defined(HAS_I422TORGB565ROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + I422ToRGB565Row = I422ToRGB565Row_SVE2; + } +#endif #if defined(HAS_I422TORGB565ROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I422ToRGB565Row = I422ToRGB565Row_Any_MSA; @@ -6010,6 +6080,11 @@ int I422ToRGB565Matrix(const uint8_t* src_y, } } #endif +#if defined(HAS_I422TORGB565ROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + I422ToRGB565Row = I422ToRGB565Row_SVE2; + } +#endif #if defined(HAS_I422TORGB565ROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I422ToRGB565Row = I422ToRGB565Row_Any_MSA; @@ -6138,6 +6213,11 @@ int I420ToRGB565Dither(const uint8_t* src_y, I422ToARGBRow = I422ToARGBRow_SVE2; } #endif +#if defined(HAS_I422TOARGBROW_SME) + if (TestCpuFlag(kCpuHasSME)) { + I422ToARGBRow = I422ToARGBRow_SME; + } +#endif #if defined(HAS_I422TOARGBROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I422ToARGBRow = I422ToARGBRow_Any_MSA; @@ -6437,6 +6517,11 @@ static int I420ToARGBMatrixBilinear(const uint8_t* src_y, I444ToARGBRow = I444ToARGBRow_SVE2; } #endif +#if defined(HAS_I444TOARGBROW_SME) + if (TestCpuFlag(kCpuHasSME)) { + I444ToARGBRow = I444ToARGBRow_SME; + } +#endif #if defined(HAS_I444TOARGBROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I444ToARGBRow = I444ToARGBRow_Any_MSA; @@ -6589,6 +6674,11 @@ static int I422ToARGBMatrixLinear(const uint8_t* src_y, I444ToARGBRow = I444ToARGBRow_SVE2; } #endif +#if defined(HAS_I444TOARGBROW_SME) + if (TestCpuFlag(kCpuHasSME)) { + I444ToARGBRow = I444ToARGBRow_SME; + } +#endif #if defined(HAS_I444TOARGBROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I444ToARGBRow = I444ToARGBRow_Any_MSA; @@ -7986,6 +8076,11 @@ static int P010ToARGBMatrixBilinear(const uint16_t* src_y, } } #endif +#if defined(HAS_P410TOARGBROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + P410ToARGBRow = P410ToARGBRow_SVE2; + } +#endif #ifdef HAS_SCALEUVROWUP2_BILINEAR_16_SSE41 if (TestCpuFlag(kCpuHasSSE41)) { @@ -8087,6 +8182,11 @@ static int P210ToARGBMatrixLinear(const uint16_t* src_y, } } #endif +#if defined(HAS_P410TOARGBROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + P410ToARGBRow = P410ToARGBRow_SVE2; + } +#endif #ifdef HAS_SCALEUVROWUP2_LINEAR_16_SSE41 if (TestCpuFlag(kCpuHasSSE41)) { @@ -8174,6 +8274,11 @@ static int P010ToAR30MatrixBilinear(const uint16_t* src_y, } } #endif +#if defined(HAS_P410TOAR30ROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + P410ToAR30Row = P410ToAR30Row_SVE2; + } +#endif #ifdef HAS_SCALEUVROWUP2_BILINEAR_16_SSE41 if (TestCpuFlag(kCpuHasSSE41)) { @@ -8275,6 +8380,11 @@ static int P210ToAR30MatrixLinear(const uint16_t* src_y, } } #endif +#if defined(HAS_P410TOAR30ROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + P410ToAR30Row = P410ToAR30Row_SVE2; + } +#endif #ifdef HAS_SCALEUVROWUP2_LINEAR_16_SSE41 if (TestCpuFlag(kCpuHasSSE41)) { diff --git a/libfenrir/src/main/jni/animation/libyuv/source/cpu_id.cc b/libfenrir/src/main/jni/animation/libyuv/source/cpu_id.cc index cd0112af4..48a350c6e 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/cpu_id.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/cpu_id.cc @@ -409,17 +409,20 @@ static SAFEBUFFERS int GetCpuFlags(void) { int cpu_info1[4] = {0, 0, 0, 0}; int cpu_info7[4] = {0, 0, 0, 0}; int cpu_einfo7[4] = {0, 0, 0, 0}; + int cpu_amdinfo21[4] = {0, 0, 0, 0}; CpuId(0, 0, cpu_info0); CpuId(1, 0, cpu_info1); if (cpu_info0[0] >= 7) { CpuId(7, 0, cpu_info7); CpuId(7, 1, cpu_einfo7); + CpuId(0x80000021, 0, cpu_amdinfo21); } cpu_info = kCpuHasX86 | ((cpu_info1[3] & 0x04000000) ? kCpuHasSSE2 : 0) | ((cpu_info1[2] & 0x00000200) ? kCpuHasSSSE3 : 0) | ((cpu_info1[2] & 0x00080000) ? kCpuHasSSE41 : 0) | ((cpu_info1[2] & 0x00100000) ? kCpuHasSSE42 : 0) | - ((cpu_info7[1] & 0x00000200) ? kCpuHasERMS : 0); + ((cpu_info7[1] & 0x00000200) ? kCpuHasERMS : 0) | + ((cpu_info7[3] & 0x00000010) ? kCpuHasFSMR : 0); // AVX requires OS saves YMM registers. if (((cpu_info1[2] & 0x1c000000) == 0x1c000000) && // AVX and OSXSave @@ -430,16 +433,18 @@ static SAFEBUFFERS int GetCpuFlags(void) { ((cpu_einfo7[0] & 0x00000010) ? kCpuHasAVXVNNI : 0) | ((cpu_einfo7[3] & 0x00000010) ? kCpuHasAVXVNNIINT8 : 0); + cpu_info |= ((cpu_amdinfo21[0] & 0x00008000) ? kCpuHasERMS : 0); + // Detect AVX512bw if ((GetXCR0() & 0xe0) == 0xe0) { - cpu_info |= (cpu_info7[1] & 0x40000000) ? kCpuHasAVX512BW : 0; - cpu_info |= (cpu_info7[1] & 0x80000000) ? kCpuHasAVX512VL : 0; - cpu_info |= (cpu_info7[2] & 0x00000002) ? kCpuHasAVX512VBMI : 0; - cpu_info |= (cpu_info7[2] & 0x00000040) ? kCpuHasAVX512VBMI2 : 0; - cpu_info |= (cpu_info7[2] & 0x00000800) ? kCpuHasAVX512VNNI : 0; - cpu_info |= (cpu_info7[2] & 0x00001000) ? kCpuHasAVX512VBITALG : 0; - cpu_info |= (cpu_einfo7[3] & 0x00080000) ? kCpuHasAVX10 : 0; - cpu_info |= (cpu_info7[3] & 0x02000000) ? kCpuHasAMXINT8 : 0; + cpu_info |= ((cpu_info7[1] & 0x40000000) ? kCpuHasAVX512BW : 0) | + ((cpu_info7[1] & 0x80000000) ? kCpuHasAVX512VL : 0) | + ((cpu_info7[2] & 0x00000002) ? kCpuHasAVX512VBMI : 0) | + ((cpu_info7[2] & 0x00000040) ? kCpuHasAVX512VBMI2 : 0) | + ((cpu_info7[2] & 0x00000800) ? kCpuHasAVX512VNNI : 0) | + ((cpu_info7[2] & 0x00001000) ? kCpuHasAVX512VBITALG : 0) | + ((cpu_einfo7[3] & 0x00080000) ? kCpuHasAVX10 : 0) | + ((cpu_info7[3] & 0x02000000) ? kCpuHasAMXINT8 : 0); } } #endif diff --git a/libfenrir/src/main/jni/animation/libyuv/source/planar_functions.cc b/libfenrir/src/main/jni/animation/libyuv/source/planar_functions.cc index 7c50599ad..be67a1ded 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/planar_functions.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/planar_functions.cc @@ -62,6 +62,11 @@ void CopyPlane(const uint8_t* src_y, CopyRow = IS_ALIGNED(width, 64) ? CopyRow_AVX : CopyRow_Any_AVX; } #endif +#if defined(HAS_COPYROW_AVX512BW) + if (TestCpuFlag(kCpuHasAVX512BW)) { + CopyRow = IS_ALIGNED(width, 128) ? CopyRow_AVX512BW : CopyRow_Any_AVX512BW; + } +#endif #if defined(HAS_COPYROW_ERMS) if (TestCpuFlag(kCpuHasERMS)) { CopyRow = CopyRow_ERMS; @@ -877,6 +882,11 @@ void ConvertToLSBPlane_16(const uint16_t* src_y, } } #endif +#if defined(HAS_DIVIDEROW_16_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + DivideRow = DivideRow_16_SVE2; + } +#endif for (y = 0; y < height; ++y) { DivideRow(src_y, dst_y, scale, width); @@ -1280,6 +1290,22 @@ void SplitRGBPlane(const uint8_t* src_rgb, } } #endif +#if defined(HAS_SPLITRGBROW_SSE41) + if (TestCpuFlag(kCpuHasSSE41)) { + SplitRGBRow = SplitRGBRow_Any_SSE41; + if (IS_ALIGNED(width, 16)) { + SplitRGBRow = SplitRGBRow_SSE41; + } + } +#endif +#if defined(HAS_SPLITRGBROW_AVX2) + if (TestCpuFlag(kCpuHasAVX2)) { + SplitRGBRow = SplitRGBRow_Any_AVX2; + if (IS_ALIGNED(width, 32)) { + SplitRGBRow = SplitRGBRow_AVX2; + } + } +#endif #if defined(HAS_SPLITRGBROW_NEON) if (TestCpuFlag(kCpuHasNEON)) { SplitRGBRow = SplitRGBRow_Any_NEON; @@ -5184,12 +5210,17 @@ int HalfFloatPlane(const uint16_t* src_y, #if defined(HAS_HALFFLOATROW_NEON) if (TestCpuFlag(kCpuHasNEON)) { HalfFloatRow = - (scale == 1.0f) ? HalfFloat1Row_Any_NEON : HalfFloatRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - HalfFloatRow = (scale == 1.0f) ? HalfFloat1Row_NEON : HalfFloatRow_NEON; + scale == 1.0f ? HalfFloat1Row_Any_NEON : HalfFloatRow_Any_NEON; + if (IS_ALIGNED(width, 16)) { + HalfFloatRow = scale == 1.0f ? HalfFloat1Row_NEON : HalfFloatRow_NEON; } } #endif +#if defined(HAS_HALFFLOATROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + HalfFloatRow = scale == 1.0f ? HalfFloat1Row_SVE2 : HalfFloatRow_SVE2; + } +#endif #if defined(HAS_HALFFLOATROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { HalfFloatRow = HalfFloatRow_Any_MSA; diff --git a/libfenrir/src/main/jni/animation/libyuv/source/rotate.cc b/libfenrir/src/main/jni/animation/libyuv/source/rotate.cc index 08ec2ccfb..6b0b84f59 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/rotate.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/rotate.cc @@ -234,6 +234,11 @@ void RotatePlane180(const uint8_t* src, CopyRow = IS_ALIGNED(width, 64) ? CopyRow_AVX : CopyRow_Any_AVX; } #endif +#if defined(HAS_COPYROW_AVX512BW) + if (TestCpuFlag(kCpuHasAVX512BW)) { + CopyRow = IS_ALIGNED(width, 128) ? CopyRow_AVX512BW : CopyRow_Any_AVX512BW; + } +#endif #if defined(HAS_COPYROW_ERMS) if (TestCpuFlag(kCpuHasERMS)) { CopyRow = CopyRow_ERMS; diff --git a/libfenrir/src/main/jni/animation/libyuv/source/rotate_argb.cc b/libfenrir/src/main/jni/animation/libyuv/source/rotate_argb.cc index d55fac4f6..7cfaedc52 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/rotate_argb.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/rotate_argb.cc @@ -189,6 +189,11 @@ static int ARGBRotate180(const uint8_t* src_argb, CopyRow = IS_ALIGNED(width * 4, 64) ? CopyRow_AVX : CopyRow_Any_AVX; } #endif +#if defined(HAS_COPYROW_AVX512BW) + if (TestCpuFlag(kCpuHasAVX512BW)) { + CopyRow = IS_ALIGNED(width * 4, 128) ? CopyRow_AVX512BW : CopyRow_Any_AVX512BW; + } +#endif #if defined(HAS_COPYROW_ERMS) if (TestCpuFlag(kCpuHasERMS)) { CopyRow = CopyRow_ERMS; diff --git a/libfenrir/src/main/jni/animation/libyuv/source/row_any.cc b/libfenrir/src/main/jni/animation/libyuv/source/row_any.cc index d2f8a5419..a61ab817c 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/row_any.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/row_any.cc @@ -967,6 +967,9 @@ ANY21PT(MergeUVRow_16_Any_NEON, MergeUVRow_16_NEON, uint16_t, 2, 7) memcpy(dst_ptr + n * BPP, vout, r * BPP); \ } +#ifdef HAS_COPYROW_AVX512BW +ANY11(CopyRow_Any_AVX512BW, CopyRow_AVX512BW, 0, 1, 1, 127) +#endif #ifdef HAS_COPYROW_AVX ANY11(CopyRow_Any_AVX, CopyRow_AVX, 0, 1, 1, 63) #endif @@ -1810,6 +1813,16 @@ ANY11P16(HalfFloat1Row_Any_F16C, 15) #endif #ifdef HAS_HALFFLOATROW_NEON +#ifdef __aarch64__ +ANY11P16(HalfFloatRow_Any_NEON, HalfFloatRow_NEON, uint16_t, uint16_t, 2, 2, 15) +ANY11P16(HalfFloat1Row_Any_NEON, + HalfFloat1Row_NEON, + uint16_t, + uint16_t, + 2, + 2, + 15) +#else ANY11P16(HalfFloatRow_Any_NEON, HalfFloatRow_NEON, uint16_t, uint16_t, 2, 2, 7) ANY11P16(HalfFloat1Row_Any_NEON, HalfFloat1Row_NEON, @@ -1819,6 +1832,7 @@ ANY11P16(HalfFloat1Row_Any_NEON, 2, 7) #endif +#endif #ifdef HAS_HALFFLOATROW_MSA ANY11P16(HalfFloatRow_Any_MSA, HalfFloatRow_MSA, uint16_t, uint16_t, 2, 2, 31) #endif @@ -2194,6 +2208,12 @@ ANY12PT(SplitUVRow_16_Any_NEON, SplitUVRow_16_NEON, uint16_t, 2, 7) #ifdef HAS_SPLITRGBROW_SSSE3 ANY13(SplitRGBRow_Any_SSSE3, SplitRGBRow_SSSE3, 3, 15) #endif +#ifdef HAS_SPLITRGBROW_SSE41 +ANY13(SplitRGBRow_Any_SSE41, SplitRGBRow_SSE41, 3, 15) +#endif +#ifdef HAS_SPLITRGBROW_AVX2 +ANY13(SplitRGBRow_Any_AVX2, SplitRGBRow_AVX2, 3, 31) +#endif #ifdef HAS_SPLITRGBROW_NEON ANY13(SplitRGBRow_Any_NEON, SplitRGBRow_NEON, 3, 15) #endif diff --git a/libfenrir/src/main/jni/animation/libyuv/source/row_gcc.cc b/libfenrir/src/main/jni/animation/libyuv/source/row_gcc.cc index cb757c755..2ec59759f 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/row_gcc.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/row_gcc.cc @@ -3361,7 +3361,7 @@ void OMITFP I422ToRGBARow_SSSE3(const uint8_t* y_buf, "vpunpcklbw %%zmm1,%%zmm3,%%zmm3 \n" \ "vpermq $0xd8,%%zmm3,%%zmm3 \n" \ "vpunpcklwd %%zmm3,%%zmm3,%%zmm3 \n" \ - "vmovdqu8 (%[y_buf]),%%ymm4 \n" \ + "vmovups (%[y_buf]),%%ymm4 \n" \ "vpermq %%zmm4,%%zmm17,%%zmm4 \n" \ "vpermq $0xd8,%%zmm4,%%zmm4 \n" \ "vpunpcklbw %%zmm4,%%zmm4,%%zmm4 \n" \ @@ -3572,17 +3572,17 @@ void OMITFP I422ToRGBARow_SSSE3(const uint8_t* y_buf, "vpbroadcastq %%xmm8, %%zmm8 \n" \ "vpsllw $7,%%xmm13,%%xmm13 \n" \ "vpbroadcastb %%xmm13,%%zmm13 \n" \ - "movq 32(%[yuvconstants]),%%xmm9 \n" \ + "movq 32(%[yuvconstants]),%%xmm9 \n" \ "vpbroadcastq %%xmm9,%%zmm9 \n" \ - "movq 64(%[yuvconstants]),%%xmm10 \n" \ + "movq 64(%[yuvconstants]),%%xmm10 \n" \ "vpbroadcastq %%xmm10,%%zmm10 \n" \ - "movq 96(%[yuvconstants]),%%xmm11 \n" \ + "movq 96(%[yuvconstants]),%%xmm11 \n" \ "vpbroadcastq %%xmm11,%%zmm11 \n" \ - "movq 128(%[yuvconstants]),%%xmm12 \n" \ + "movq 128(%[yuvconstants]),%%xmm12 \n" \ "vpbroadcastq %%xmm12,%%zmm12 \n" \ - "vmovdqu8 (%[quadsplitperm]),%%zmm16 \n" \ - "vmovdqu8 (%[dquadsplitperm]),%%zmm17 \n" \ - "vmovdqu8 (%[unperm]),%%zmm18 \n" + "vmovups (%[quadsplitperm]),%%zmm16 \n" \ + "vmovups (%[dquadsplitperm]),%%zmm17 \n" \ + "vmovups (%[unperm]),%%zmm18 \n" #define YUVTORGB16_AVX2(yuvconstants) \ "vpsubb %%ymm13,%%ymm3,%%ymm3 \n" \ @@ -3672,8 +3672,8 @@ void OMITFP I422ToRGBARow_SSSE3(const uint8_t* y_buf, "vpermq %%zmm2,%%zmm18,%%zmm2 \n" \ "vpunpcklwd %%zmm2,%%zmm0,%%zmm1 \n" \ "vpunpckhwd %%zmm2,%%zmm0,%%zmm0 \n" \ - "vmovdqu8 %%zmm1,(%[dst_argb]) \n" \ - "vmovdqu8 %%zmm0,0x40(%[dst_argb]) \n" \ + "vmovups %%zmm1,(%[dst_argb]) \n" \ + "vmovups %%zmm0,0x40(%[dst_argb]) \n" \ "lea 0x80(%[dst_argb]), %[dst_argb] \n" // Store 16 AR30 values. @@ -5340,15 +5340,15 @@ void Convert16To8Row_AVX512BW(const uint16_t* src_y, // 64 pixels per loop. LABELALIGN "1: \n" - "vmovdqu8 (%0),%%zmm0 \n" - "vmovdqu8 0x40(%0),%%zmm1 \n" + "vmovups (%0),%%zmm0 \n" + "vmovups 0x40(%0),%%zmm1 \n" "add $0x80,%0 \n" "vpmulhuw %%zmm2,%%zmm0,%%zmm0 \n" "vpmulhuw %%zmm2,%%zmm1,%%zmm1 \n" "vpmovuswb %%zmm0,%%ymm0 \n" "vpmovuswb %%zmm1,%%ymm1 \n" - "vmovdqu8 %%ymm0,(%1) \n" - "vmovdqu8 %%ymm1,0x20(%1) \n" + "vmovups %%ymm0,(%1) \n" + "vmovups %%ymm1,0x20(%1) \n" "add $0x40,%1 \n" "sub $0x40,%2 \n" "jg 1b \n" @@ -5503,6 +5503,116 @@ void SplitRGBRow_SSSE3(const uint8_t* src_rgb, } #endif // HAS_SPLITRGBROW_SSSE3 +#ifdef HAS_SPLITRGBROW_SSE41 +// Shuffle table for converting RGB to Planar, SSE4.1. Note: these are used for +// the AVX2 implementation as well. +static const uvec8 kSplitRGBShuffleSSE41[5] = { + {0u, 3u, 6u, 9u, 12u, 15u, 2u, 5u, 8u, 11u, 14u, 1u, 4u, 7u, 10u, 13u}, + {1u, 4u, 7u, 10u, 13u, 0u, 3u, 6u, 9u, 12u, 15u, 2u, 5u, 8u, 11u, 14u}, + {2u, 5u, 8u, 11u, 14u, 1u, 4u, 7u, 10u, 13u, 0u, 3u, 6u, 9u, 12u, 15u}, + {0u, 128u, 0u, 0u, 128u, 0u, 0u, 128u, 0u, 0u, 128u, 0u, 0u, 128u, 0u, 0u}, + {0u, 0u, 128u, 0u, 0u, 128u, 0u, 0u, 128u, 0u, 0u, 128u, 0u, 0u, 128u, 0u}, +}; + +void SplitRGBRow_SSE41(const uint8_t* src_rgb, uint8_t* dst_r, + uint8_t* dst_g, uint8_t* dst_b, int width) { + asm volatile( + "movdqa 48(%5), %%xmm0 \n" + "1: \n" + "movdqu (%0),%%xmm1 \n" + "movdqu 0x10(%0),%%xmm2 \n" + "movdqu 0x20(%0),%%xmm3 \n" + "lea 0x30(%0),%0 \n" + "movdqa %%xmm1, %%xmm4 \n" + "pblendvb %%xmm3, %%xmm1 \n" + "pblendvb %%xmm2, %%xmm3 \n" + "pblendvb %%xmm4, %%xmm2 \n" + "palignr $0xF, %%xmm0, %%xmm0 \n" + "pblendvb %%xmm2, %%xmm1 \n" + "pblendvb %%xmm3, %%xmm2 \n" + "pblendvb %%xmm4, %%xmm3 \n" + "palignr $0x1, %%xmm0, %%xmm0 \n" + "pshufb 0(%5), %%xmm1 \n" + "pshufb 16(%5), %%xmm2 \n" + "pshufb 32(%5), %%xmm3 \n" + "movdqu %%xmm1,(%1) \n" + "lea 0x10(%1),%1 \n" + "movdqu %%xmm2,(%2) \n" + "lea 0x10(%2),%2 \n" + "movdqu %%xmm3,(%3) \n" + "lea 0x10(%3),%3 \n" + "sub $0x10,%4 \n" + "jg 1b \n" + : "+r"(src_rgb), // %0 + "+r"(dst_r), // %1 + "+r"(dst_g), // %2 + "+r"(dst_b), // %3 + "+r"(width) // %4 + : "r"(&kSplitRGBShuffleSSE41[0]) // %5 + : "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4"); +} +#endif // HAS_SPLITRGBROW_SSE41 + +#ifdef HAS_SPLITRGBROW_AVX2 +void SplitRGBRow_AVX2(const uint8_t* src_rgb, uint8_t* dst_r, + uint8_t* dst_g, uint8_t* dst_b, int width) { + asm volatile( + "vbroadcasti128 48(%5), %%ymm0 \n" + "vbroadcasti128 64(%5), %%ymm7 \n" +#if defined(__x86_64__) + "vbroadcasti128 0(%5), %%ymm8 \n" + "vbroadcasti128 16(%5), %%ymm9 \n" + "vbroadcasti128 32(%5), %%ymm10 \n" +#endif + "1: \n" + "vmovdqu (%0),%%ymm4 \n" + "vmovdqu 0x20(%0),%%ymm5 \n" + "vmovdqu 0x40(%0),%%ymm6 \n" + "lea 0x60(%0),%0 \n" + "vpblendd $240, %%ymm5, %%ymm4, %%ymm1 \n" + "vperm2i128 $33, %%ymm6, %%ymm4, %%ymm2 \n" + "vpblendd $240, %%ymm6, %%ymm5, %%ymm3 \n" + "vpblendvb %%ymm0, %%ymm3, %%ymm1, %%ymm4 \n" + "vpblendvb %%ymm0, %%ymm1, %%ymm2, %%ymm5 \n" + "vpblendvb %%ymm0, %%ymm2, %%ymm3, %%ymm6 \n" + "vpblendvb %%ymm7, %%ymm5, %%ymm4, %%ymm1 \n" + "vpblendvb %%ymm7, %%ymm6, %%ymm5, %%ymm2 \n" + "vpblendvb %%ymm7, %%ymm4, %%ymm6, %%ymm3 \n" +#if defined(__x86_64__) + "vpshufb %%ymm8, %%ymm1, %%ymm1 \n" + "vpshufb %%ymm9, %%ymm2, %%ymm2 \n" + "vpshufb %%ymm10, %%ymm3, %%ymm3 \n" +#else + "vbroadcasti128 0(%5), %%ymm4 \n" + "vbroadcasti128 16(%5), %%ymm5 \n" + "vbroadcasti128 32(%5), %%ymm6 \n" + "vpshufb %%ymm4, %%ymm1, %%ymm1 \n" + "vpshufb %%ymm5, %%ymm2, %%ymm2 \n" + "vpshufb %%ymm6, %%ymm3, %%ymm3 \n" +#endif + "vmovdqu %%ymm1,(%1) \n" + "lea 0x20(%1),%1 \n" + "vmovdqu %%ymm2,(%2) \n" + "lea 0x20(%2),%2 \n" + "vmovdqu %%ymm3,(%3) \n" + "lea 0x20(%3),%3 \n" + "sub $0x20,%4 \n" + "jg 1b \n" + : "+r"(src_rgb), // %0 + "+r"(dst_r), // %1 + "+r"(dst_g), // %2 + "+r"(dst_b), // %3 + "+r"(width) // %4 + : "r"(&kSplitRGBShuffleSSE41[0]) // %5 + : "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", + "xmm7" +#if defined(__x86_64__) + , "xmm8", "xmm9", "xmm10" +#endif + ); +} +#endif // HAS_SPLITRGBROW_AVX2 + #ifdef HAS_MERGERGBROW_SSSE3 // Shuffle table for converting Planar to RGB. static const uvec8 kMergeRGBShuffle[9] = { @@ -6395,6 +6505,27 @@ void CopyRow_AVX(const uint8_t* src, uint8_t* dst, int width) { } #endif // HAS_COPYROW_AVX +#ifdef HAS_COPYROW_AVX512BW +void CopyRow_AVX512BW(const uint8_t* src, uint8_t* dst, int width) { + asm volatile ( + "1: \n" + "vmovups (%0),%%zmm0 \n" + "vmovups 0x40(%0),%%zmm1 \n" + "lea 0x80(%0),%0 \n" + "vmovups %%zmm0,(%1) \n" + "vmovups %%zmm1,0x40(%1) \n" + "lea 0x80(%1),%1 \n" + "sub $0x80,%2 \n" + "jg 1b \n" + "vzeroupper \n" + : "+r"(src), // %0 + "+r"(dst), // %1 + "+r"(width) // %2 + : + : "memory", "cc", "xmm0", "xmm1"); +} +#endif // HAS_COPYROW_AVX512 + #ifdef HAS_COPYROW_ERMS // Multiple of 1. void CopyRow_ERMS(const uint8_t* src, uint8_t* dst, int width) { diff --git a/libfenrir/src/main/jni/animation/libyuv/source/row_neon64.cc b/libfenrir/src/main/jni/animation/libyuv/source/row_neon64.cc index 7ad54b430..4b1ed2c0c 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/row_neon64.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/row_neon64.cc @@ -4669,50 +4669,64 @@ void HalfFloat1Row_NEON(const uint16_t* src, uint16_t* dst, float /*unused*/, int width) { - asm volatile ( + asm volatile( "1: \n" - "ld1 {v1.16b}, [%0], #16 \n" // load 8 shorts - "subs %w2, %w2, #8 \n" // 8 pixels per loop - "uxtl v2.4s, v1.4h \n" // 8 int's + "ldp q0, q1, [%0], #32 \n" // load 16 shorts + "subs %w2, %w2, #16 \n" // 16 pixels per loop + "uxtl v2.4s, v0.4h \n" + "uxtl v4.4s, v1.4h \n" + "uxtl2 v3.4s, v0.8h \n" + "uxtl2 v5.4s, v1.8h \n" "prfm pldl1keep, [%0, 448] \n" - "uxtl2 v3.4s, v1.8h \n" - "scvtf v2.4s, v2.4s \n" // 8 floats + "scvtf v2.4s, v2.4s \n" + "scvtf v4.4s, v4.4s \n" "scvtf v3.4s, v3.4s \n" - "fcvtn v1.4h, v2.4s \n" // 8 half floats - "fcvtn2 v1.8h, v3.4s \n" - "st1 {v1.16b}, [%1], #16 \n" // store 8 shorts + "scvtf v5.4s, v5.4s \n" + "fcvtn v0.4h, v2.4s \n" + "fcvtn v1.4h, v4.4s \n" + "fcvtn2 v0.8h, v3.4s \n" + "fcvtn2 v1.8h, v5.4s \n" + "stp q0, q1, [%1], #32 \n" // store 16 shorts "b.gt 1b \n" : "+r"(src), // %0 "+r"(dst), // %1 "+r"(width) // %2 : - : "cc", "memory", "v1", "v2", "v3"); + : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5"); } void HalfFloatRow_NEON(const uint16_t* src, uint16_t* dst, float scale, int width) { - asm volatile ( + asm volatile( "1: \n" - "ld1 {v1.16b}, [%0], #16 \n" // load 8 shorts - "subs %w2, %w2, #8 \n" // 8 pixels per loop - "uxtl v2.4s, v1.4h \n" // 8 int's + "ldp q0, q1, [%0], #32 \n" // load 16 shorts + "subs %w2, %w2, #16 \n" // 16 pixels per loop + "uxtl v2.4s, v0.4h \n" + "uxtl v4.4s, v1.4h \n" + "uxtl2 v3.4s, v0.8h \n" + "uxtl2 v5.4s, v1.8h \n" "prfm pldl1keep, [%0, 448] \n" - "uxtl2 v3.4s, v1.8h \n" - "scvtf v2.4s, v2.4s \n" // 8 floats + "scvtf v2.4s, v2.4s \n" + "scvtf v4.4s, v4.4s \n" "scvtf v3.4s, v3.4s \n" + "scvtf v5.4s, v5.4s \n" "fmul v2.4s, v2.4s, %3.s[0] \n" // adjust exponent + "fmul v4.4s, v4.4s, %3.s[0] \n" "fmul v3.4s, v3.4s, %3.s[0] \n" - "uqshrn v1.4h, v2.4s, #13 \n" // isolate halffloat - "uqshrn2 v1.8h, v3.4s, #13 \n" - "st1 {v1.16b}, [%1], #16 \n" // store 8 shorts + "fmul v5.4s, v5.4s, %3.s[0] \n" + "uqshrn v0.4h, v2.4s, #13 \n" // isolate halffloat + "uqshrn v1.4h, v4.4s, #13 \n" // isolate halffloat + "uqshrn2 v0.8h, v3.4s, #13 \n" + "uqshrn2 v1.8h, v5.4s, #13 \n" + "stp q0, q1, [%1], #32 \n" // store 16 shorts "b.gt 1b \n" : "+r"(src), // %0 "+r"(dst), // %1 "+r"(width) // %2 : "w"(scale * 1.9259299444e-34f) // %3 - : "cc", "memory", "v1", "v2", "v3"); + : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5"); } void ByteToFloatRow_NEON(const uint8_t* src, diff --git a/libfenrir/src/main/jni/animation/libyuv/source/row_sme.cc b/libfenrir/src/main/jni/animation/libyuv/source/row_sme.cc new file mode 100644 index 000000000..7676d9e64 --- /dev/null +++ b/libfenrir/src/main/jni/animation/libyuv/source/row_sme.cc @@ -0,0 +1,225 @@ +/* + * Copyright 2024 The LibYuv Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "libyuv/row.h" + +#ifdef __cplusplus +namespace libyuv { +extern "C" { +#endif + +#if !defined(LIBYUV_DISABLE_SME) && defined(CLANG_HAS_SME) && \ + defined(__aarch64__) + +#define YUVTORGB_SVE_SETUP \ + "ld1rb {z28.b}, p0/z, [%[kUVCoeff], #0] \n" \ + "ld1rb {z29.b}, p0/z, [%[kUVCoeff], #1] \n" \ + "ld1rb {z30.b}, p0/z, [%[kUVCoeff], #2] \n" \ + "ld1rb {z31.b}, p0/z, [%[kUVCoeff], #3] \n" \ + "ld1rh {z24.h}, p0/z, [%[kRGBCoeffBias], #0] \n" \ + "ld1rh {z25.h}, p0/z, [%[kRGBCoeffBias], #2] \n" \ + "ld1rh {z26.h}, p0/z, [%[kRGBCoeffBias], #4] \n" \ + "ld1rh {z27.h}, p0/z, [%[kRGBCoeffBias], #6] \n" + +// Read twice as much data from YUV, putting the even elements from the Y data +// in z0.h and odd elements in z1.h. U/V data is not duplicated, stored in +// z2.h/z3.h. +#define READYUV422_SVE_2X \ + "ld1b {z0.b}, p1/z, [%[src_y]] \n" \ + "ld1b {z2.h}, p1/z, [%[src_u]] \n" \ + "ld1b {z3.h}, p1/z, [%[src_v]] \n" \ + "incb %[src_y] \n" \ + "inch %[src_u] \n" \ + "inch %[src_v] \n" \ + "prfm pldl1keep, [%[src_y], 448] \n" \ + "prfm pldl1keep, [%[src_u], 128] \n" \ + "prfm pldl1keep, [%[src_v], 128] \n" \ + "trn2 z1.b, z0.b, z0.b \n" \ + "trn1 z0.b, z0.b, z0.b \n" + +// Read twice as much data from YUV, putting the even elements from the Y data +// in z0.h and odd elements in z1.h. +#define READYUV444_SVE_2X \ + "ld1b {z0.b}, p1/z, [%[src_y]] \n" \ + "ld1b {z2.b}, p1/z, [%[src_u]] \n" \ + "ld1b {z3.b}, p1/z, [%[src_v]] \n" \ + "incb %[src_y] \n" \ + "incb %[src_u] \n" \ + "incb %[src_v] \n" \ + "prfm pldl1keep, [%[src_y], 448] \n" \ + "prfm pldl1keep, [%[src_u], 128] \n" \ + "prfm pldl1keep, [%[src_v], 128] \n" \ + "trn2 z1.b, z0.b, z0.b \n" \ + "trn1 z0.b, z0.b, z0.b \n" + +// The U/V component multiplies do not need to be duplicated in I422, we just +// need to combine them with Y0/Y1 correctly. +#define I422TORGB_SVE_2X \ + "umulh z0.h, z24.h, z0.h \n" /* Y0 */ \ + "umulh z1.h, z24.h, z1.h \n" /* Y1 */ \ + "umullb z6.h, z30.b, z2.b \n" \ + "umullb z4.h, z28.b, z2.b \n" /* DB */ \ + "umullb z5.h, z29.b, z3.b \n" /* DR */ \ + "umlalb z6.h, z31.b, z3.b \n" /* DG */ \ + \ + "add z17.h, z0.h, z26.h \n" /* G0 */ \ + "add z21.h, z1.h, z26.h \n" /* G1 */ \ + "add z16.h, z0.h, z4.h \n" /* B0 */ \ + "add z20.h, z1.h, z4.h \n" /* B1 */ \ + "add z18.h, z0.h, z5.h \n" /* R0 */ \ + "add z22.h, z1.h, z5.h \n" /* R1 */ \ + "uqsub z17.h, z17.h, z6.h \n" /* G0 */ \ + "uqsub z21.h, z21.h, z6.h \n" /* G1 */ \ + "uqsub z16.h, z16.h, z25.h \n" /* B0 */ \ + "uqsub z20.h, z20.h, z25.h \n" /* B1 */ \ + "uqsub z18.h, z18.h, z27.h \n" /* R0 */ \ + "uqsub z22.h, z22.h, z27.h \n" /* R1 */ + +#define I444TORGB_SVE_2X \ + "umulh z0.h, z24.h, z0.h \n" /* Y0 */ \ + "umulh z1.h, z24.h, z1.h \n" /* Y1 */ \ + "umullb z6.h, z30.b, z2.b \n" \ + "umullt z7.h, z30.b, z2.b \n" \ + "umullb z4.h, z28.b, z2.b \n" /* DB */ \ + "umullt z2.h, z28.b, z2.b \n" /* DB */ \ + "umlalb z6.h, z31.b, z3.b \n" /* DG */ \ + "umlalt z7.h, z31.b, z3.b \n" /* DG */ \ + "umullb z5.h, z29.b, z3.b \n" /* DR */ \ + "umullt z3.h, z29.b, z3.b \n" /* DR */ \ + "add z17.h, z0.h, z26.h \n" /* G */ \ + "add z21.h, z1.h, z26.h \n" /* G */ \ + "add z16.h, z0.h, z4.h \n" /* B */ \ + "add z20.h, z1.h, z2.h \n" /* B */ \ + "add z18.h, z0.h, z5.h \n" /* R */ \ + "add z22.h, z1.h, z3.h \n" /* R */ \ + "uqsub z17.h, z17.h, z6.h \n" /* G */ \ + "uqsub z21.h, z21.h, z7.h \n" /* G */ \ + "uqsub z16.h, z16.h, z25.h \n" /* B */ \ + "uqsub z20.h, z20.h, z25.h \n" /* B */ \ + "uqsub z18.h, z18.h, z27.h \n" /* R */ \ + "uqsub z22.h, z22.h, z27.h \n" /* R */ + +#define RGBTOARGB8_SVE_2X \ + /* Inputs: B: z16.h, G: z17.h, R: z18.h, A: z19.b */ \ + "uqshrnb z16.b, z16.h, #6 \n" /* B0 */ \ + "uqshrnb z17.b, z17.h, #6 \n" /* G0 */ \ + "uqshrnb z18.b, z18.h, #6 \n" /* R0 */ \ + "uqshrnt z16.b, z20.h, #6 \n" /* B1 */ \ + "uqshrnt z17.b, z21.h, #6 \n" /* G1 */ \ + "uqshrnt z18.b, z22.h, #6 \n" /* R1 */ + +#define YUVTORGB_SVE_REGS \ + "z0", "z1", "z2", "z3", "z4", "z5", "z6", "z7", "z16", "z17", "z18", "z19", \ + "z20", "z22", "z23", "z24", "z25", "z26", "z27", "z28", "z29", "z30", \ + "z31", "p0", "p1", "p2", "p3" + +__arm_locally_streaming void I444ToARGBRow_SME( + const uint8_t* src_y, + const uint8_t* src_u, + const uint8_t* src_v, + uint8_t* dst_argb, + const struct YuvConstants* yuvconstants, + int width) { + // Streaming-SVE only, no use of ZA tile. + uint64_t vl; + asm volatile( + "cntb %[vl] \n" + "ptrue p0.b \n" // + YUVTORGB_SVE_SETUP + "dup z19.b, #255 \n" // A + "subs %w[width], %w[width], %w[vl] \n" + "b.lt 2f \n" + + // Run bulk of computation with an all-true predicate to avoid predicate + // generation overhead. + "ptrue p1.b \n" + "1: \n" // + READYUV444_SVE_2X I444TORGB_SVE_2X RGBTOARGB8_SVE_2X + "subs %w[width], %w[width], %w[vl] \n" + "st4b {z16.b, z17.b, z18.b, z19.b}, p1, [%[dst_argb]] \n" + "incb %[dst_argb], all, mul #4 \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl] \n" + "b.eq 99f \n" + + // Calculate a predicate for the final iteration to deal with the tail. + "whilelt p1.b, wzr, %w[width] \n" // + READYUV444_SVE_2X I444TORGB_SVE_2X RGBTOARGB8_SVE_2X + "st4b {z16.b, z17.b, z18.b, z19.b}, p1, [%[dst_argb]] \n" + + "99: \n" + : [src_y] "+r"(src_y), // %[src_y] + [src_u] "+r"(src_u), // %[src_u] + [src_v] "+r"(src_v), // %[src_v] + [dst_argb] "+r"(dst_argb), // %[dst_argb] + [width] "+r"(width), // %[width] + [vl] "=&r"(vl) // %[vl] + : [kUVCoeff] "r"(&yuvconstants->kUVCoeff), // %[kUVCoeff] + [kRGBCoeffBias] "r"(&yuvconstants->kRGBCoeffBias) // %[kRGBCoeffBias] + : "cc", "memory", YUVTORGB_SVE_REGS); +} + +__arm_locally_streaming void I422ToARGBRow_SME( + const uint8_t* src_y, + const uint8_t* src_u, + const uint8_t* src_v, + uint8_t* dst_argb, + const struct YuvConstants* yuvconstants, + int width) { + // Streaming-SVE only, no use of ZA tile. + uint64_t vl; + asm volatile( + "cntb %[vl] \n" + "ptrue p0.b \n" // + YUVTORGB_SVE_SETUP + "dup z19.b, #255 \n" // A0 + "subs %w[width], %w[width], %w[vl] \n" + "b.lt 2f \n" + + // Run bulk of computation with an all-true predicate to avoid predicate + // generation overhead. + "ptrue p1.b \n" + "1: \n" // + READYUV422_SVE_2X I422TORGB_SVE_2X RGBTOARGB8_SVE_2X + "subs %w[width], %w[width], %w[vl] \n" + "st4b {z16.b, z17.b, z18.b, z19.b}, p1, [%[dst_argb]] \n" + "incb %[dst_argb], all, mul #4 \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl] \n" + "b.eq 99f \n" + + // Calculate a predicate for the final iteration to deal with the tail. + "whilelt p1.b, wzr, %w[width] \n" // + READYUV422_SVE_2X I422TORGB_SVE_2X RGBTOARGB8_SVE_2X + "st4b {z16.b, z17.b, z18.b, z19.b}, p1, [%[dst_argb]] \n" + + "99: \n" + : [src_y] "+r"(src_y), // %[src_y] + [src_u] "+r"(src_u), // %[src_u] + [src_v] "+r"(src_v), // %[src_v] + [dst_argb] "+r"(dst_argb), // %[dst_argb] + [width] "+r"(width), // %[width] + [vl] "=&r"(vl) // %[vl] + : [kUVCoeff] "r"(&yuvconstants->kUVCoeff), // %[kUVCoeff] + [kRGBCoeffBias] "r"(&yuvconstants->kRGBCoeffBias) // %[kRGBCoeffBias] + : "cc", "memory", YUVTORGB_SVE_REGS); +} + +#endif // !defined(LIBYUV_DISABLE_SME) && defined(CLANG_HAS_SME) && + // defined(__aarch64__) + +#ifdef __cplusplus +} // extern "C" +} // namespace libyuv +#endif diff --git a/libfenrir/src/main/jni/animation/libyuv/source/row_sve.cc b/libfenrir/src/main/jni/animation/libyuv/source/row_sve.cc index a6da07b98..bfa49d9c2 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/row_sve.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/row_sve.cc @@ -78,6 +78,44 @@ extern "C" { "trn1 z0.b, z0.b, z0.b \n" /* YYYY */ \ "tbl z1.b, {z1.b}, z22.b \n" /* UVUV */ +#define READI210_SVE \ + "ld1h {z3.h}, p1/z, [%[src_y]] \n" \ + "lsl z0.h, z3.h, #6 \n" \ + "usra z0.h, z3.h, #4 \n" \ + "ld1h {z1.s}, p1/z, [%[src_u]] \n" \ + "ld1h {z2.s}, p1/z, [%[src_v]] \n" \ + "incb %[src_y] \n" \ + "inch %[src_u] \n" \ + "inch %[src_v] \n" \ + "prfm pldl1keep, [%[src_y], 448] \n" \ + "prfm pldl1keep, [%[src_u], 128] \n" \ + "prfm pldl1keep, [%[src_v], 128] \n" \ + "trn1 z1.h, z1.h, z1.h \n" \ + "trn1 z2.h, z2.h, z2.h \n" \ + "uqshrnb z1.b, z1.h, #2 \n" \ + "uqshrnb z2.b, z2.h, #2 \n" + +#define READP210_SVE \ + "ld1h {z0.h}, p1/z, [%[src_y]] \n" \ + "ld1h {z1.h}, p2/z, [%[src_uv]] \n" \ + "incb %[src_y] \n" \ + "incb %[src_uv] \n" \ + "prfm pldl1keep, [%[src_y], 448] \n" \ + "prfm pldl1keep, [%[src_uv], 256] \n" \ + "tbl z1.b, {z1.b}, z22.b \n" + +// We need different predicates for the UV components since we are reading +// 32-bit (pairs of UV) elements rather than 16-bit Y elements. +#define READP410_SVE \ + "ld1h {z0.h}, p1/z, [%[src_y]] \n" \ + "ld1w {z1.s}, p2/z, [%[src_uv]] \n" \ + "ld1w {z2.s}, p3/z, [%[src_uv], #1, mul vl] \n" \ + "incb %[src_y] \n" \ + "incb %[src_uv], all, mul #2 \n" \ + "prfm pldl1keep, [%[src_y], 448] \n" \ + "prfm pldl1keep, [%[src_uv], 256] \n" \ + "uzp2 z1.b, z1.b, z2.b \n" + #define READYUY2_SVE \ "ld1w {z0.s}, p2/z, [%[src_yuy2]] \n" /* YUYV */ \ "incb %[src_yuy2] \n" \ @@ -181,6 +219,15 @@ extern "C" { "uqshrnt z17.b, z21.h, #6 \n" /* G1 */ \ "uqshrnt z18.b, z22.h, #6 \n" /* R1 */ +#define RGBTOARGB8_SVE_TOP_2X \ + /* Inputs: B: z16.h, G: z17.h, R: z18.h */ \ + "uqshl z16.h, p0/m, z16.h, #2 \n" /* B0 */ \ + "uqshl z17.h, p0/m, z17.h, #2 \n" /* G0 */ \ + "uqshl z18.h, p0/m, z18.h, #2 \n" /* R0 */ \ + "uqshl z20.h, p0/m, z20.h, #2 \n" /* B1 */ \ + "uqshl z21.h, p0/m, z21.h, #2 \n" /* G1 */ \ + "uqshl z22.h, p0/m, z22.h, #2 \n" /* R1 */ + // Convert from 2.14 fixed point RGB to 8 bit RGBA, interleaving as AB and GR // pairs to allow us to use ST2 for storing rather than ST4. #define RGBTORGBA8_SVE \ @@ -194,6 +241,18 @@ extern "C" { "z20", "z22", "z23", "z24", "z25", "z26", "z27", "z28", "z29", "z30", \ "z31", "p0", "p1", "p2", "p3" +// Store AR30 elements +#define STOREAR30_SVE \ + "uqshl z16.h, p0/m, z16.h, #2 \n" /* bbbbbbbbbbxxxxxx */ \ + "uqshl z17.h, p0/m, z17.h, #2 \n" /* ggggggggggxxxxxx */ \ + "umin z18.h, p0/m, z18.h, z23.h \n" /* 00rrrrrrrrrrxxxx */ \ + "orr z18.h, z18.h, #0xc000 \n" /* 11rrrrrrrrrrxxxx */ \ + "sri z18.h, z17.h, #12 \n" /* 11rrrrrrrrrrgggg */ \ + "lsl z17.h, z17.h, #4 \n" /* ggggggxxxxxx0000 */ \ + "sri z17.h, z16.h, #6 \n" /* ggggggbbbbbbbbbb */ \ + "st2h {z17.h, z18.h}, p1, [%[dst_ar30]] \n" \ + "incb %[dst_ar30], all, mul #2 \n" + void I444ToARGBRow_SVE2(const uint8_t* src_y, const uint8_t* src_u, const uint8_t* src_v, @@ -332,6 +391,216 @@ void I422ToARGBRow_SVE2(const uint8_t* src_y, : "cc", "memory", YUVTORGB_SVE_REGS); } +void I422ToRGB24Row_SVE2(const uint8_t* src_y, + const uint8_t* src_u, + const uint8_t* src_v, + uint8_t* dst_argb, + const struct YuvConstants* yuvconstants, + int width) { + uint64_t vl; + asm volatile( + "cntb %[vl] \n" + "ptrue p0.b \n" YUVTORGB_SVE_SETUP + "subs %w[width], %w[width], %w[vl] \n" + "b.lt 2f \n" + + // Run bulk of computation with an all-true predicate to avoid predicate + // generation overhead. + "ptrue p1.b \n" + "1: \n" READYUV422_SVE_2X + I422TORGB_SVE_2X RGBTOARGB8_SVE_2X + "subs %w[width], %w[width], %w[vl] \n" + "st3b {z16.b, z17.b, z18.b}, p1, [%[dst_argb]] \n" + "incb %[dst_argb], all, mul #3 \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl] \n" + "b.eq 99f \n" + + // Calculate a predicate for the final iteration to deal with the tail. + "cnth %[vl] \n" + "whilelt p1.b, wzr, %w[width] \n" READYUV422_SVE_2X + I422TORGB_SVE_2X RGBTOARGB8_SVE_2X + "st3b {z16.b, z17.b, z18.b}, p1, [%[dst_argb]] \n" + + "99: \n" + : [src_y] "+r"(src_y), // %[src_y] + [src_u] "+r"(src_u), // %[src_u] + [src_v] "+r"(src_v), // %[src_v] + [dst_argb] "+r"(dst_argb), // %[dst_argb] + [width] "+r"(width), // %[width] + [vl] "=&r"(vl) // %[vl] + : [kUVCoeff] "r"(&yuvconstants->kUVCoeff), // %[kUVCoeff] + [kRGBCoeffBias] "r"(&yuvconstants->kRGBCoeffBias) // %[kRGBCoeffBias] + : "cc", "memory", YUVTORGB_SVE_REGS); +} + +#define RGB8TORGB565_SVE_FROM_TOP_2X \ + "sri z18.h, z17.h, #5 \n" /* rrrrrgggggg00000 */ \ + "sri z22.h, z21.h, #5 \n" /* rrrrrgggggg00000 */ \ + "sri z18.h, z16.h, #11 \n" /* rrrrrggggggbbbbb */ \ + "sri z22.h, z20.h, #11 \n" /* rrrrrggggggbbbbb */ \ + "mov z19.d, z22.d \n" + +void I422ToRGB565Row_SVE2(const uint8_t* src_y, + const uint8_t* src_u, + const uint8_t* src_v, + uint8_t* dst_rgb565, + const struct YuvConstants* yuvconstants, + int width) { + uint64_t vl; + asm volatile( + "cntb %[vl] \n" + "ptrue p0.b \n" YUVTORGB_SVE_SETUP + "subs %w[width], %w[width], %w[vl] \n" + "b.lt 2f \n" + + // Run bulk of computation with an all-true predicate to avoid predicate + // generation overhead. + "ptrue p1.b \n" + "1: \n" READYUV422_SVE_2X + I422TORGB_SVE_2X RGBTOARGB8_SVE_TOP_2X + "subs %w[width], %w[width], %w[vl] \n" // + RGB8TORGB565_SVE_FROM_TOP_2X + "st2h {z18.h, z19.h}, p1, [%[dst]] \n" + "incb %[dst], all, mul #2 \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl] \n" + "b.eq 99f \n" + + // Calculate a predicate for the final iteration to deal with the tail. + "cnth %[vl] \n" + "whilelt p1.b, wzr, %w[width] \n" READYUV422_SVE_2X + I422TORGB_SVE_2X RGBTOARGB8_SVE_TOP_2X RGB8TORGB565_SVE_FROM_TOP_2X + "st2h {z18.h, z19.h}, p1, [%[dst]] \n" + + "99: \n" + : [src_y] "+r"(src_y), // %[src_y] + [src_u] "+r"(src_u), // %[src_u] + [src_v] "+r"(src_v), // %[src_v] + [dst] "+r"(dst_rgb565), // %[dst] + [width] "+r"(width), // %[width] + [vl] "=&r"(vl) // %[vl] + : [kUVCoeff] "r"(&yuvconstants->kUVCoeff), // %[kUVCoeff] + [kRGBCoeffBias] "r"(&yuvconstants->kRGBCoeffBias) // %[kRGBCoeffBias] + : "cc", "memory", YUVTORGB_SVE_REGS); +} + +#define RGB8TOARGB1555_SVE_FROM_TOP_2X \ + "dup z0.h, #0x8000 \n" /* 1000000000000000 */ \ + "dup z1.h, #0x8000 \n" /* 1000000000000000 */ \ + "sri z0.h, z18.h, #1 \n" /* 1rrrrrxxxxxxxxxx */ \ + "sri z1.h, z22.h, #1 \n" /* 1rrrrrxxxxxxxxxx */ \ + "sri z0.h, z17.h, #6 \n" /* 1rrrrrgggggxxxxx */ \ + "sri z1.h, z21.h, #6 \n" /* 1rrrrrgggggxxxxx */ \ + "sri z0.h, z16.h, #11 \n" /* 1rrrrrgggggbbbbb */ \ + "sri z1.h, z20.h, #11 \n" /* 1rrrrrgggggbbbbb */ + +void I422ToARGB1555Row_SVE2(const uint8_t* src_y, + const uint8_t* src_u, + const uint8_t* src_v, + uint8_t* dst_argb1555, + const struct YuvConstants* yuvconstants, + int width) { + uint64_t vl; + asm volatile( + "cntb %[vl] \n" + "ptrue p0.b \n" YUVTORGB_SVE_SETUP + "subs %w[width], %w[width], %w[vl] \n" + "b.lt 2f \n" + + // Run bulk of computation with an all-true predicate to avoid predicate + // generation overhead. + "ptrue p1.b \n" + "1: \n" READYUV422_SVE_2X + I422TORGB_SVE_2X RGBTOARGB8_SVE_TOP_2X + "subs %w[width], %w[width], %w[vl] \n" // + RGB8TOARGB1555_SVE_FROM_TOP_2X + "st2h {z0.h, z1.h}, p1, [%[dst]] \n" + "incb %[dst], all, mul #2 \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl] \n" + "b.eq 99f \n" + + // Calculate a predicate for the final iteration to deal with the tail. + "cnth %[vl] \n" + "whilelt p1.b, wzr, %w[width] \n" READYUV422_SVE_2X + I422TORGB_SVE_2X RGBTOARGB8_SVE_TOP_2X RGB8TOARGB1555_SVE_FROM_TOP_2X + "st2h {z0.h, z1.h}, p1, [%[dst]] \n" + + "99: \n" + : [src_y] "+r"(src_y), // %[src_y] + [src_u] "+r"(src_u), // %[src_u] + [src_v] "+r"(src_v), // %[src_v] + [dst] "+r"(dst_argb1555), // %[dst] + [width] "+r"(width), // %[width] + [vl] "=&r"(vl) // %[vl] + : [kUVCoeff] "r"(&yuvconstants->kUVCoeff), // %[kUVCoeff] + [kRGBCoeffBias] "r"(&yuvconstants->kRGBCoeffBias) // %[kRGBCoeffBias] + : "cc", "memory", YUVTORGB_SVE_REGS); +} + +#define RGB8TOARGB4444_SVE_FROM_TOP_2X \ + "dup z0.h, #0xf000 \n" /* 1111000000000000 */ \ + "dup z1.h, #0xf000 \n" /* 1111000000000000 */ \ + "sri z0.h, z18.h, #4 \n" /* 1111rrrrxxxxxxxx */ \ + "sri z1.h, z22.h, #4 \n" /* 1111rrrrxxxxxxxx */ \ + "sri z0.h, z17.h, #8 \n" /* 1111rrrrggggxxxx */ \ + "sri z1.h, z21.h, #8 \n" /* 1111rrrrggggxxxx */ \ + "sri z0.h, z16.h, #12 \n" /* 1111rrrrggggbbbb */ \ + "sri z1.h, z20.h, #12 \n" /* 1111rrrrggggbbbb */ + +void I422ToARGB4444Row_SVE2(const uint8_t* src_y, + const uint8_t* src_u, + const uint8_t* src_v, + uint8_t* dst_argb4444, + const struct YuvConstants* yuvconstants, + int width) { + uint64_t vl; + asm volatile( + "cntb %[vl] \n" + "ptrue p0.b \n" YUVTORGB_SVE_SETUP + "subs %w[width], %w[width], %w[vl] \n" + "b.lt 2f \n" + + // Run bulk of computation with an all-true predicate to avoid predicate + // generation overhead. + "ptrue p1.b \n" + "1: \n" READYUV422_SVE_2X + I422TORGB_SVE_2X RGBTOARGB8_SVE_TOP_2X + "subs %w[width], %w[width], %w[vl] \n" // + RGB8TOARGB4444_SVE_FROM_TOP_2X + "st2h {z0.h, z1.h}, p1, [%[dst]] \n" + "incb %[dst], all, mul #2 \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl] \n" + "b.eq 99f \n" + + // Calculate a predicate for the final iteration to deal with the tail. + "cnth %[vl] \n" + "whilelt p1.b, wzr, %w[width] \n" READYUV422_SVE_2X + I422TORGB_SVE_2X RGBTOARGB8_SVE_TOP_2X RGB8TOARGB4444_SVE_FROM_TOP_2X + "st2h {z0.h, z1.h}, p1, [%[dst]] \n" + + "99: \n" + : [src_y] "+r"(src_y), // %[src_y] + [src_u] "+r"(src_u), // %[src_u] + [src_v] "+r"(src_v), // %[src_v] + [dst] "+r"(dst_argb4444), // %[dst] + [width] "+r"(width), // %[width] + [vl] "=&r"(vl) // %[vl] + : [kUVCoeff] "r"(&yuvconstants->kUVCoeff), // %[kUVCoeff] + [kRGBCoeffBias] "r"(&yuvconstants->kRGBCoeffBias) // %[kRGBCoeffBias] + : "cc", "memory", YUVTORGB_SVE_REGS); +} + void I422ToRGBARow_SVE2(const uint8_t* src_y, const uint8_t* src_u, const uint8_t* src_v, @@ -1401,6 +1670,436 @@ void ARGBToRAWRow_SVE2(const uint8_t* src_argb, uint8_t* dst_rgb, int width) { ARGBToXYZRow_SVE2(src_argb, dst_rgb, width, kARGBToRAWRowIndices); } +void DivideRow_16_SVE2(const uint16_t* src_y, + uint16_t* dst_y, + int scale, + int width) { + uint64_t vl; + asm volatile( + "cnth %x[vl] \n" + "dup z0.h, %w[scale] \n" + "subs %w[width], %w[width], %w[vl], lsl #1 \n" + "b.le 2f \n" + + // Run bulk of computation with the same predicates to avoid predicate + // generation overhead. + "ptrue p0.h \n" + "1: \n" + "ld1h {z1.h}, p0/z, [%[src]] \n" + "ld1h {z2.h}, p0/z, [%[src], #1, mul vl] \n" + "incb %[src], all, mul #2 \n" + "umulh z1.h, z1.h, z0.h \n" + "umulh z2.h, z2.h, z0.h \n" + "subs %w[width], %w[width], %w[vl], lsl #1 \n" + "st1h {z1.h}, p0, [%[dst]] \n" + "st1h {z2.h}, p0, [%[dst], #1, mul vl] \n" + "incb %[dst], all, mul #2 \n" + "b.gt 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl], lsl #1 \n" + "b.eq 99f \n" + + // Calculate a pair of predicates for the final iteration to deal with + // the tail. + "whilelt p0.h, wzr, %w[width] \n" + "whilelt p1.h, %w[vl], %w[width] \n" + "ld1h {z1.h}, p0/z, [%[src]] \n" + "ld1h {z2.h}, p1/z, [%[src], #1, mul vl] \n" + "umulh z1.h, z1.h, z0.h \n" + "umulh z2.h, z2.h, z0.h \n" + "st1h {z1.h}, p0, [%[dst]] \n" + "st1h {z2.h}, p1, [%[dst], #1, mul vl] \n" + + "99: \n" + : [src] "+r"(src_y), // %[src] + [dst] "+r"(dst_y), // %[dst] + [width] "+r"(width), // %[width] + [vl] "=&r"(vl) // %[vl] + : [scale] "r"(scale) // %[scale] + : "cc", "memory", "z0", "z1", "z2", "p0", "p1"); +} + +#define HALFFLOAT_SVE \ + "scvtf z0.s, p0/m, z0.s \n" \ + "scvtf z1.s, p0/m, z1.s \n" \ + "scvtf z2.s, p0/m, z2.s \n" \ + "scvtf z3.s, p0/m, z3.s \n" \ + "fmul z0.s, z0.s, z4.s \n" \ + "fmul z1.s, z1.s, z4.s \n" \ + "fmul z2.s, z2.s, z4.s \n" \ + "fmul z3.s, z3.s, z4.s \n" \ + "uqshrnb z0.h, z0.s, #13 \n" \ + "uqshrnb z1.h, z1.s, #13 \n" \ + "uqshrnb z2.h, z2.s, #13 \n" \ + "uqshrnb z3.h, z3.s, #13 \n" + +void HalfFloatRow_SVE2(const uint16_t* src, + uint16_t* dst, + float scale, + int width) { + uint64_t vl; + asm("cntw %x0" : "=r"(vl)); + asm volatile( + "mov z4.s, %s[scale] \n" + "subs %w[width], %w[width], %w[vl], lsl #2 \n" + "b.lt 2f \n" + + // Run bulk of computation with all-true predicates to avoid predicate + // generation overhead. + "ptrue p0.s \n" + "1: \n" + "ld1h {z0.s}, p0/z, [%[src]] \n" + "ld1h {z1.s}, p0/z, [%[src], #1, mul vl] \n" + "ld1h {z2.s}, p0/z, [%[src], #2, mul vl] \n" + "ld1h {z3.s}, p0/z, [%[src], #3, mul vl] \n" + "incb %[src], all, mul #2 \n" HALFFLOAT_SVE + "subs %w[width], %w[width], %w[vl], lsl #2 \n" + "st1h {z0.s}, p0, [%[dst]] \n" + "st1h {z1.s}, p0, [%[dst], #1, mul vl] \n" + "st1h {z2.s}, p0, [%[dst], #2, mul vl] \n" + "st1h {z3.s}, p0, [%[dst], #3, mul vl] \n" + "incb %[dst], all, mul #2 \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl], lsl #2 \n" + "b.eq 99f \n" + + // Calculate predicates for the final iteration to deal with the tail. + "whilelt p0.s, wzr, %w[width] \n" + "whilelt p1.s, %w[vl], %w[width] \n" + "whilelt p2.s, %w[vl2], %w[width] \n" + "whilelt p3.s, %w[vl3], %w[width] \n" + "ld1h {z0.s}, p0/z, [%[src]] \n" + "ld1h {z1.s}, p1/z, [%[src], #1, mul vl] \n" + "ld1h {z2.s}, p2/z, [%[src], #2, mul vl] \n" + "ld1h {z3.s}, p3/z, [%[src], #3, mul vl] \n" HALFFLOAT_SVE + "st1h {z0.s}, p0, [%[dst]] \n" + "st1h {z1.s}, p1, [%[dst], #1, mul vl] \n" + "st1h {z2.s}, p2, [%[dst], #2, mul vl] \n" + "st1h {z3.s}, p3, [%[dst], #3, mul vl] \n" + + "99: \n" + : [src] "+r"(src), // %[src] + [dst] "+r"(dst), // %[dst] + [width] "+r"(width) // %[width] + : [vl] "r"(vl), // %[vl] + [vl2] "r"(vl * 2), // %[vl2] + [vl3] "r"(vl * 3), // %[vl3] + [scale] "w"(scale * 1.9259299444e-34f) // %[scale] + : "cc", "memory", "z0", "z1", "z2", "z3", "z4", "p0", "p1", "p2", "p3"); +} + +void HalfFloat1Row_SVE2(const uint16_t* src, + uint16_t* dst, + float scale, + int width) { + uint64_t vl; + asm volatile( + "cnth %x[vl] \n" + "subs %w[width], %w[width], %w[vl], lsl #1 \n" + "b.lt 2f \n" + + // Run bulk of computation with all-true predicates to avoid predicate + // generation overhead. + "ptrue p0.h \n" + "1: \n" + "ld1h {z0.h}, p0/z, [%[src]] \n" + "ld1h {z1.h}, p0/z, [%[src], #1, mul vl] \n" + "incb %[src], all, mul #2 \n" + "ucvtf z0.h, p0/m, z0.h \n" + "ucvtf z1.h, p0/m, z1.h \n" + "subs %w[width], %w[width], %w[vl], lsl #1 \n" + "st1h {z0.h}, p0, [%[dst]] \n" + "st1h {z1.h}, p0, [%[dst], #1, mul vl] \n" + "incb %[dst], all, mul #2 \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl], lsl #1 \n" + "b.eq 99f \n" + + // Calculate predicates for the final iteration to deal with the tail. + "whilelt p0.h, wzr, %w[width] \n" + "whilelt p1.h, %w[vl], %w[width] \n" + "ld1h {z0.h}, p0/z, [%[src]] \n" + "ld1h {z1.h}, p1/z, [%[src], #1, mul vl] \n" + "ucvtf z0.h, p0/m, z0.h \n" + "ucvtf z1.h, p0/m, z1.h \n" + "st1h {z0.h}, p0, [%[dst]] \n" + "st1h {z1.h}, p1, [%[dst], #1, mul vl] \n" + + "99: \n" + : [src] "+r"(src), // %[src] + [dst] "+r"(dst), // %[dst] + [width] "+r"(width), // %[width] + [vl] "=&r"(vl) // %[vl] + : + : "cc", "memory", "z0", "z1", "p0", "p1"); +} + +void I210ToARGBRow_SVE2(const uint16_t* src_y, + const uint16_t* src_u, + const uint16_t* src_v, + uint8_t* dst_argb, + const struct YuvConstants* yuvconstants, + int width) { + uint64_t vl; + asm("cnth %0" : "=r"(vl)); + int width_last_y = width & (vl - 1); + width_last_y = width_last_y == 0 ? vl : width_last_y; + asm volatile( + "ptrue p0.b \n" YUVTORGB_SVE_SETUP + "dup z19.b, #255 \n" // A + "subs %w[width], %w[width], %w[vl] \n" + "b.lt 2f \n" + + // Run bulk of computation with an all-true predicate to avoid predicate + // generation overhead. + "ptrue p1.h \n" + "1: \n" // + READI210_SVE I4XXTORGB_SVE RGBTOARGB8_SVE + "subs %w[width], %w[width], %w[vl] \n" + "st2h {z16.h, z17.h}, p1, [%[dst_argb]] \n" + "add %[dst_argb], %[dst_argb], %[vl], lsl #2 \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl] \n" + "b.eq 99f \n" + + // Calculate a predicate for the final iteration to deal with the tail. + "whilelt p1.h, wzr, %w[width_last_y] \n" // + READI210_SVE I4XXTORGB_SVE RGBTOARGB8_SVE + "st2h {z16.h, z17.h}, p1, [%[dst_argb]] \n" + + "99: \n" + : [src_y] "+r"(src_y), // %[src_y] + [src_u] "+r"(src_u), // %[src_u] + [src_v] "+r"(src_v), // %[src_v] + [dst_argb] "+r"(dst_argb), // %[dst_argb] + [width] "+r"(width) // %[width] + : [vl] "r"(vl), // %[vl] + [kUVCoeff] "r"(&yuvconstants->kUVCoeff), // %[kUVCoeff] + [kRGBCoeffBias] "r"(&yuvconstants->kRGBCoeffBias), // %[kRGBCoeffBias] + [width_last_y] "r"(width_last_y) // %[width_last_y] + : "cc", "memory", YUVTORGB_SVE_REGS); +} + +// P210 has 10 bits in msb of 16 bit NV12 style layout. +void P210ToARGBRow_SVE2(const uint16_t* src_y, + const uint16_t* src_uv, + uint8_t* dst_argb, + const struct YuvConstants* yuvconstants, + int width) { + uint64_t vl; + asm("cnth %0" : "=r"(vl)); + int width_last_y = width & (vl - 1); + width_last_y = width_last_y == 0 ? vl : width_last_y; + int width_last_uv = width_last_y + (width_last_y & 1); + uint32_t nv_uv_start = 0x03010301U; + uint32_t nv_uv_step = 0x04040404U; + asm volatile( + "ptrue p0.b \n" YUVTORGB_SVE_SETUP + "index z22.s, %w[nv_uv_start], %w[nv_uv_step] \n" + "dup z19.b, #255 \n" // A + "subs %w[width], %w[width], %w[vl] \n" + "b.lt 2f \n" + + // Run bulk of computation with an all-true predicate to avoid predicate + // generation overhead. + "ptrue p1.h \n" + "ptrue p2.h \n" + "1: \n" // + READP210_SVE NVTORGB_SVE RGBTOARGB8_SVE + "subs %w[width], %w[width], %w[vl] \n" + "st2h {z16.h, z17.h}, p1, [%[dst_argb]] \n" + "add %[dst_argb], %[dst_argb], %[vl], lsl #2 \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl] \n" + "b.eq 99f \n" + + // Calculate a predicate for the final iteration to deal with the tail. + "whilelt p1.h, wzr, %w[width_last_y] \n" + "whilelt p2.h, wzr, %w[width_last_uv] \n" // + READP210_SVE NVTORGB_SVE RGBTOARGB8_SVE + "st2h {z16.h, z17.h}, p1, [%[dst_argb]] \n" + + "99: \n" + : [src_y] "+r"(src_y), // %[src_y] + [src_uv] "+r"(src_uv), // %[src_uv] + [dst_argb] "+r"(dst_argb), // %[dst_argb] + [width] "+r"(width) // %[width] + : [vl] "r"(vl), // %[vl] + [kUVCoeff] "r"(&yuvconstants->kUVCoeff), // %[kUVCoeff] + [kRGBCoeffBias] "r"(&yuvconstants->kRGBCoeffBias), // %[kRGBCoeffBias] + [nv_uv_start] "r"(nv_uv_start), // %[nv_uv_start] + [nv_uv_step] "r"(nv_uv_step), // %[nv_uv_step] + [width_last_y] "r"(width_last_y), // %[width_last_y] + [width_last_uv] "r"(width_last_uv) // %[width_last_uv] + : "cc", "memory", YUVTORGB_SVE_REGS); +} + +void P210ToAR30Row_SVE2(const uint16_t* src_y, + const uint16_t* src_uv, + uint8_t* dst_ar30, + const struct YuvConstants* yuvconstants, + int width) { + uint64_t vl; + asm("cnth %0" : "=r"(vl)); + int width_last_y = width & (vl - 1); + width_last_y = width_last_y == 0 ? vl : width_last_y; + int width_last_uv = width_last_y + (width_last_y & 1); + uint32_t nv_uv_start = 0x03010301U; + uint32_t nv_uv_step = 0x04040404U; + uint16_t limit = 0x3ff0; + asm volatile( + "ptrue p0.b \n" YUVTORGB_SVE_SETUP + "index z22.s, %w[nv_uv_start], %w[nv_uv_step] \n" + "dup z23.h, %w[limit] \n" + "subs %w[width], %w[width], %w[vl] \n" + "b.lt 2f \n" + + // Run bulk of computation with an all-true predicate to avoid predicate + // generation overhead. + "ptrue p1.h \n" + "ptrue p2.h \n" + "1: \n" // + READP210_SVE NVTORGB_SVE + "subs %w[width], %w[width], %w[vl] \n" // + STOREAR30_SVE + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl] \n" + "b.eq 99f \n" + + // Calculate a predicate for the final iteration to deal with the tail. + "whilelt p1.h, wzr, %w[width_last_y] \n" + "whilelt p2.h, wzr, %w[width_last_uv] \n" // + READP210_SVE NVTORGB_SVE STOREAR30_SVE + + "99: \n" + : [src_y] "+r"(src_y), // %[src_y] + [src_uv] "+r"(src_uv), // %[src_uv] + [dst_ar30] "+r"(dst_ar30), // %[dst_ar30] + [width] "+r"(width) // %[width] + : [vl] "r"(vl), // %[vl] + [kUVCoeff] "r"(&yuvconstants->kUVCoeff), // %[kUVCoeff] + [kRGBCoeffBias] "r"(&yuvconstants->kRGBCoeffBias), // %[kRGBCoeffBias] + [nv_uv_start] "r"(nv_uv_start), // %[nv_uv_start] + [nv_uv_step] "r"(nv_uv_step), // %[nv_uv_step] + [width_last_y] "r"(width_last_y), // %[width_last_y] + [width_last_uv] "r"(width_last_uv), // %[width_last_uv] + [limit] "r"(limit) // %[limit] + : "cc", "memory", YUVTORGB_SVE_REGS); +} + +void P410ToARGBRow_SVE2(const uint16_t* src_y, + const uint16_t* src_uv, + uint8_t* dst_argb, + const struct YuvConstants* yuvconstants, + int width) { + uint64_t vl; + asm("cnth %0" : "=r"(vl)); + int width_last_y = width & (vl - 1); + width_last_y = width_last_y == 0 ? vl : width_last_y; + asm volatile( + "ptrue p0.b \n" YUVTORGB_SVE_SETUP + "dup z19.b, #255 \n" // A + "subs %w[width], %w[width], %w[vl] \n" + "b.lt 2f \n" + + // Run bulk of computation with an all-true predicate to avoid predicate + // generation overhead. + "ptrue p1.h \n" + "ptrue p2.s \n" + "ptrue p3.s \n" + "1: \n" // + READP410_SVE NVTORGB_SVE RGBTOARGB8_SVE + "subs %w[width], %w[width], %w[vl] \n" + "st2h {z16.h, z17.h}, p1, [%[dst_argb]] \n" + "add %[dst_argb], %[dst_argb], %[vl], lsl #2 \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl] \n" + "b.eq 99f \n" + + // Calculate a predicate for the final iteration to deal with the tail. + "whilelt p1.h, wzr, %w[width_last_y] \n" + "whilelt p2.s, wzr, %w[width_last_y] \n" + "cntw %x[vl] \n" + "whilelt p3.s, %w[vl], %w[width_last_y] \n" // + READP410_SVE NVTORGB_SVE RGBTOARGB8_SVE + "st2h {z16.h, z17.h}, p1, [%[dst_argb]] \n" + + "99: \n" + : [src_y] "+r"(src_y), // %[src_y] + [src_uv] "+r"(src_uv), // %[src_uv] + [dst_argb] "+r"(dst_argb), // %[dst_argb] + [width] "+r"(width) // %[width] + : [vl] "r"(vl), // %[vl] + [kUVCoeff] "r"(&yuvconstants->kUVCoeff), // %[kUVCoeff] + [kRGBCoeffBias] "r"(&yuvconstants->kRGBCoeffBias), // %[kRGBCoeffBias] + [width_last_y] "r"(width_last_y) // %[width_last_y] + : "cc", "memory", YUVTORGB_SVE_REGS); +} + +void P410ToAR30Row_SVE2(const uint16_t* src_y, + const uint16_t* src_uv, + uint8_t* dst_ar30, + const struct YuvConstants* yuvconstants, + int width) { + uint64_t vl; + asm("cnth %0" : "=r"(vl)); + int width_last_y = width & (vl - 1); + width_last_y = width_last_y == 0 ? vl : width_last_y; + uint16_t limit = 0x3ff0; + asm volatile( + "ptrue p0.b \n" YUVTORGB_SVE_SETUP + "dup z23.h, %w[limit] \n" + "subs %w[width], %w[width], %w[vl] \n" + "b.lt 2f \n" + + // Run bulk of computation with an all-true predicate to avoid predicate + // generation overhead. + "ptrue p1.h \n" + "ptrue p2.s \n" + "ptrue p3.s \n" + "1: \n" // + READP410_SVE NVTORGB_SVE + "subs %w[width], %w[width], %w[vl] \n" // + STOREAR30_SVE + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl] \n" + "b.eq 99f \n" + + // Calculate a predicate for the final iteration to deal with the tail. + "whilelt p1.h, wzr, %w[width_last_y] \n" + "whilelt p2.s, wzr, %w[width_last_y] \n" + "cntw %x[vl] \n" + "whilelt p3.s, %w[vl], %w[width_last_y] \n" // + READP410_SVE NVTORGB_SVE STOREAR30_SVE + + "99: \n" + : [src_y] "+r"(src_y), // %[src_y] + [src_uv] "+r"(src_uv), // %[src_uv] + [dst_ar30] "+r"(dst_ar30), // %[dst_ar30] + [width] "+r"(width) // %[width] + : [vl] "r"(vl), // %[vl] + [kUVCoeff] "r"(&yuvconstants->kUVCoeff), // %[kUVCoeff] + [kRGBCoeffBias] "r"(&yuvconstants->kRGBCoeffBias), // %[kRGBCoeffBias] + [width_last_y] "r"(width_last_y), // %[width_last_y] + [limit] "r"(limit) // %[limit] + : "cc", "memory", YUVTORGB_SVE_REGS); +} + #endif // !defined(LIBYUV_DISABLE_SVE) && defined(__aarch64__) #ifdef __cplusplus diff --git a/libfenrir/src/main/jni/animation/libyuv/source/scale.cc b/libfenrir/src/main/jni/animation/libyuv/source/scale.cc index f4d1053e4..661224166 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/scale.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/scale.cc @@ -74,6 +74,13 @@ static void ScalePlaneDown2(int src_width, } } #endif +#if defined(HAS_SCALEROWDOWN2_SME) + if (TestCpuFlag(kCpuHasSME)) { + ScaleRowDown2 = filtering == kFilterNone ? ScaleRowDown2_SME + : filtering == kFilterLinear ? ScaleRowDown2Linear_SME + : ScaleRowDown2Box_SME; + } +#endif #if defined(HAS_SCALEROWDOWN2_SSSE3) if (TestCpuFlag(kCpuHasSSSE3)) { ScaleRowDown2 = diff --git a/libfenrir/src/main/jni/animation/libyuv/source/scale_argb.cc b/libfenrir/src/main/jni/animation/libyuv/source/scale_argb.cc index e95aa596f..0268b1afc 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/scale_argb.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/scale_argb.cc @@ -706,6 +706,11 @@ static int ScaleYUVToARGBBilinearUp(int src_width, I422ToARGBRow = I422ToARGBRow_SVE2; } #endif +#if defined(HAS_I422TOARGBROW_SME) + if (TestCpuFlag(kCpuHasSME)) { + I422ToARGBRow = I422ToARGBRow_SME; + } +#endif #if defined(HAS_I422TOARGBROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I422ToARGBRow = I422ToARGBRow_Any_MSA; diff --git a/libfenrir/src/main/jni/animation/libyuv/source/scale_neon64.cc b/libfenrir/src/main/jni/animation/libyuv/source/scale_neon64.cc index 2ad0c8152..c125c6c09 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/scale_neon64.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/scale_neon64.cc @@ -322,14 +322,11 @@ void ScaleRowDown34_1_Box_NEON(const uint8_t* src_ptr, static const uvec8 kShuf38 = {0, 3, 6, 8, 11, 14, 16, 19, 22, 24, 27, 30, 0, 0, 0, 0}; -static const uvec8 kShuf38_2 = {0, 16, 32, 2, 18, 33, 4, 20, - 34, 6, 22, 35, 0, 0, 0, 0}; -static const vec16 kMult38_Div6 = {65536 / 12, 65536 / 12, 65536 / 12, - 65536 / 12, 65536 / 12, 65536 / 12, - 65536 / 12, 65536 / 12}; -static const vec16 kMult38_Div9 = {65536 / 18, 65536 / 18, 65536 / 18, - 65536 / 18, 65536 / 18, 65536 / 18, - 65536 / 18, 65536 / 18}; +static const vec16 kMult38_Div664 = { + 65536 / 12, 65536 / 12, 65536 / 8, 65536 / 12, 65536 / 12, 65536 / 8, 0, 0}; +static const vec16 kMult38_Div996 = {65536 / 18, 65536 / 18, 65536 / 12, + 65536 / 18, 65536 / 18, 65536 / 12, + 0, 0}; // 32 -> 12 void ScaleRowDown38_NEON(const uint8_t* src_ptr, @@ -337,247 +334,163 @@ void ScaleRowDown38_NEON(const uint8_t* src_ptr, uint8_t* dst_ptr, int dst_width) { (void)src_stride; - asm volatile ( - "ld1 {v3.16b}, [%3] \n" - "1: \n" - "ld1 {v0.16b,v1.16b}, [%0], #32 \n" - "subs %w2, %w2, #12 \n" - "tbl v2.16b, {v0.16b,v1.16b}, v3.16b \n" - "prfm pldl1keep, [%0, 448] \n" // prefetch 7 lines ahead - "st1 {v2.8b}, [%1], #8 \n" - "st1 {v2.s}[2], [%1], #4 \n" - "b.gt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - : "r"(&kShuf38) // %3 + asm volatile( + "ld1 {v3.16b}, [%[kShuf38]] \n" + "subs %w[width], %w[width], #12 \n" + "b.eq 2f \n" + + "1: \n" + "ldp q0, q1, [%[src_ptr]], #32 \n" + "subs %w[width], %w[width], #12 \n" + "tbl v2.16b, {v0.16b, v1.16b}, v3.16b \n" + "prfm pldl1keep, [%[src_ptr], 448] \n" // prefetch 7 lines ahead + "str q2, [%[dst_ptr]] \n" + "add %[dst_ptr], %[dst_ptr], #12 \n" + "b.gt 1b \n" + + // Store exactly 12 bytes on the final iteration to avoid writing past + // the end of the array. + "2: \n" + "ldp q0, q1, [%[src_ptr]] \n" + "tbl v2.16b, {v0.16b, v1.16b}, v3.16b \n" + "st1 {v2.8b}, [%[dst_ptr]], #8 \n" + "st1 {v2.s}[2], [%[dst_ptr]] \n" + : [src_ptr] "+r"(src_ptr), // %[src_ptr] + [dst_ptr] "+r"(dst_ptr), // %[dst_ptr] + [width] "+r"(dst_width) // %[width] + : [kShuf38] "r"(&kShuf38) // %[kShuf38] : "memory", "cc", "v0", "v1", "v2", "v3"); } -// 32x3 -> 12x1 -void OMITFP ScaleRowDown38_3_Box_NEON(const uint8_t* src_ptr, - ptrdiff_t src_stride, - uint8_t* dst_ptr, - int dst_width) { - const uint8_t* src_ptr1 = src_ptr + src_stride * 2; - ptrdiff_t tmp_src_stride = src_stride; - - asm volatile ( - "ld1 {v29.8h}, [%5] \n" - "ld1 {v30.16b}, [%6] \n" - "ld1 {v31.8h}, [%7] \n" - "add %2, %2, %0 \n" - "1: \n" - - // 00 40 01 41 02 42 03 43 - // 10 50 11 51 12 52 13 53 - // 20 60 21 61 22 62 23 63 - // 30 70 31 71 32 72 33 73 - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" - "ld4 {v4.8b,v5.8b,v6.8b,v7.8b}, [%2], #32 \n" - "ld4 {v16.8b,v17.8b,v18.8b,v19.8b}, [%3], #32 \n" - "subs %w4, %w4, #12 \n" - - // Shuffle the input data around to get align the data - // so adjacent data can be added. 0,1 - 2,3 - 4,5 - 6,7 - // 00 10 01 11 02 12 03 13 - // 40 50 41 51 42 52 43 53 - "trn1 v20.8b, v0.8b, v1.8b \n" - "trn2 v21.8b, v0.8b, v1.8b \n" - "trn1 v22.8b, v4.8b, v5.8b \n" - "trn2 v23.8b, v4.8b, v5.8b \n" - "trn1 v24.8b, v16.8b, v17.8b \n" - "trn2 v25.8b, v16.8b, v17.8b \n" - - // 20 30 21 31 22 32 23 33 - // 60 70 61 71 62 72 63 73 - "trn1 v0.8b, v2.8b, v3.8b \n" - "trn2 v1.8b, v2.8b, v3.8b \n" - "trn1 v4.8b, v6.8b, v7.8b \n" - "trn2 v5.8b, v6.8b, v7.8b \n" - "trn1 v16.8b, v18.8b, v19.8b \n" - "trn2 v17.8b, v18.8b, v19.8b \n" - - // 00+10 01+11 02+12 03+13 - // 40+50 41+51 42+52 43+53 - "uaddlp v20.4h, v20.8b \n" - "uaddlp v21.4h, v21.8b \n" - "uaddlp v22.4h, v22.8b \n" - "uaddlp v23.4h, v23.8b \n" - "uaddlp v24.4h, v24.8b \n" - "uaddlp v25.4h, v25.8b \n" - - // 60+70 61+71 62+72 63+73 - "uaddlp v1.4h, v1.8b \n" - "uaddlp v5.4h, v5.8b \n" - "uaddlp v17.4h, v17.8b \n" - - // combine source lines - "add v20.4h, v20.4h, v22.4h \n" - "add v21.4h, v21.4h, v23.4h \n" - "add v20.4h, v20.4h, v24.4h \n" - "add v21.4h, v21.4h, v25.4h \n" - "add v2.4h, v1.4h, v5.4h \n" - "add v2.4h, v2.4h, v17.4h \n" - - // dst_ptr[3] = (s[6 + st * 0] + s[7 + st * 0] - // + s[6 + st * 1] + s[7 + st * 1] - // + s[6 + st * 2] + s[7 + st * 2]) / 6 - "sqrdmulh v2.8h, v2.8h, v29.8h \n" - "xtn v2.8b, v2.8h \n" - - // Shuffle 2,3 reg around so that 2 can be added to the - // 0,1 reg and 3 can be added to the 4,5 reg. This - // requires expanding from u8 to u16 as the 0,1 and 4,5 - // registers are already expanded. Then do transposes - // to get aligned. - // xx 20 xx 30 xx 21 xx 31 xx 22 xx 32 xx 23 xx 33 - "ushll v16.8h, v16.8b, #0 \n" - "uaddl v0.8h, v0.8b, v4.8b \n" - - // combine source lines - "add v0.8h, v0.8h, v16.8h \n" - - // xx 20 xx 21 xx 22 xx 23 - // xx 30 xx 31 xx 32 xx 33 - "trn1 v1.8h, v0.8h, v0.8h \n" - "trn2 v4.8h, v0.8h, v0.8h \n" - "xtn v0.4h, v1.4s \n" - "xtn v4.4h, v4.4s \n" - "prfm pldl1keep, [%0, 448] \n" // prefetch 7 lines ahead - - // 0+1+2, 3+4+5 - "add v20.8h, v20.8h, v0.8h \n" - "add v21.8h, v21.8h, v4.8h \n" - "prfm pldl1keep, [%2, 448] \n" - - // Need to divide, but can't downshift as the the value - // isn't a power of 2. So multiply by 65536 / n - // and take the upper 16 bits. - "sqrdmulh v0.8h, v20.8h, v31.8h \n" - "sqrdmulh v1.8h, v21.8h, v31.8h \n" - "prfm pldl1keep, [%3, 448] \n" - - // Align for table lookup, vtbl requires registers to be adjacent - "tbl v3.16b, {v0.16b, v1.16b, v2.16b}, v30.16b \n" +static const uvec8 kScaleRowDown38_3_BoxIndices1[] = { + 0, 1, 6, 7, 12, 13, 16, 17, 22, 23, 28, 29, 255, 255, 255, 255}; +static const uvec8 kScaleRowDown38_3_BoxIndices2[] = { + 2, 3, 8, 9, 14, 15, 18, 19, 24, 25, 30, 31, 255, 255, 255, 255}; +static const uvec8 kScaleRowDown38_3_BoxIndices3[] = { + 4, 5, 10, 11, 255, 255, 20, 21, 26, 27, 255, 255, 255, 255, 255, 255}; +static const uvec8 kScaleRowDown38_NarrowIndices[] = { + 0, 2, 4, 6, 8, 10, 16, 18, 20, 22, 24, 26, 255, 255, 255, 255}; - "st1 {v3.8b}, [%1], #8 \n" - "st1 {v3.s}[2], [%1], #4 \n" - "b.gt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(tmp_src_stride), // %2 - "+r"(src_ptr1), // %3 - "+r"(dst_width) // %4 - : "r"(&kMult38_Div6), // %5 - "r"(&kShuf38_2), // %6 - "r"(&kMult38_Div9) // %7 - : "memory", "cc", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v16", - "v17", "v18", "v19", "v20", "v21", "v22", "v23", "v24", "v25", "v29", - "v30", "v31"); +void ScaleRowDown38_3_Box_NEON(const uint8_t* src_ptr, + ptrdiff_t src_stride, + uint8_t* dst_ptr, + int dst_width) { + const uint8_t* src_ptr1 = src_ptr + src_stride; + const uint8_t* src_ptr2 = src_ptr + src_stride * 2; + asm volatile( + "ld1 {v27.16b}, [%[tblArray1]] \n" + "ld1 {v28.16b}, [%[tblArray2]] \n" + "ld1 {v29.16b}, [%[tblArray3]] \n" + "ld1 {v31.16b}, [%[tblArray4]] \n" + "ld1 {v30.16b}, [%[div996]] \n" + + "1: \n" + "ldp q20, q0, [%[src_ptr]], #32 \n" + "ldp q21, q1, [%[src_ptr1]], #32 \n" + "ldp q22, q2, [%[src_ptr2]], #32 \n" + + "subs %w[width], %w[width], #12 \n" + + // Add across strided rows first. + "uaddl v23.8h, v20.8b, v21.8b \n" + "uaddl v3.8h, v0.8b, v1.8b \n" + "uaddl2 v24.8h, v20.16b, v21.16b \n" + "uaddl2 v4.8h, v0.16b, v1.16b \n" + + "uaddw v23.8h, v23.8h, v22.8b \n" + "uaddw v3.8h, v3.8h, v2.8b \n" + "uaddw2 v24.8h, v24.8h, v22.16b \n" // abcdefgh ... + "uaddw2 v4.8h, v4.8h, v2.16b \n" + + // Permute groups of {three,three,two} into separate vectors to sum. + "tbl v20.16b, {v23.16b, v24.16b}, v27.16b \n" // a d g ... + "tbl v0.16b, {v3.16b, v4.16b}, v27.16b \n" + "tbl v21.16b, {v23.16b, v24.16b}, v28.16b \n" // b e h ... + "tbl v1.16b, {v3.16b, v4.16b}, v28.16b \n" + "tbl v22.16b, {v23.16b, v24.16b}, v29.16b \n" // c f 0... + "tbl v2.16b, {v3.16b, v4.16b}, v29.16b \n" + + "add v23.8h, v20.8h, v21.8h \n" + "add v3.8h, v0.8h, v1.8h \n" + "add v24.8h, v23.8h, v22.8h \n" // a+b+c d+e+f g+h + "add v4.8h, v3.8h, v2.8h \n" + + "sqrdmulh v24.8h, v24.8h, v30.8h \n" // v /= {9,9,6} + "sqrdmulh v25.8h, v4.8h, v30.8h \n" + "tbl v21.16b, {v24.16b, v25.16b}, v31.16b \n" // Narrow. + "st1 {v21.d}[0], [%[dst_ptr]], #8 \n" + "st1 {v21.s}[2], [%[dst_ptr]], #4 \n" + "b.gt 1b \n" + : [src_ptr] "+r"(src_ptr), // %[src_ptr] + [dst_ptr] "+r"(dst_ptr), // %[dst_ptr] + [src_ptr1] "+r"(src_ptr1), // %[src_ptr1] + [src_ptr2] "+r"(src_ptr2), // %[src_ptr2] + [width] "+r"(dst_width) // %[width] + : [div996] "r"(&kMult38_Div996), // %[div996] + [tblArray1] "r"(kScaleRowDown38_3_BoxIndices1), // %[tblArray1] + [tblArray2] "r"(kScaleRowDown38_3_BoxIndices2), // %[tblArray2] + [tblArray3] "r"(kScaleRowDown38_3_BoxIndices3), // %[tblArray3] + [tblArray4] "r"(kScaleRowDown38_NarrowIndices) // %[tblArray4] + : "memory", "cc", "v0", "v1", "v2", "v3", "v4", "v20", "v21", "22", "23", + "24", "v27", "v28", "v29", "v30", "v31"); } -// 32x2 -> 12x1 +static const uvec8 kScaleRowDown38_2_BoxIndices1[] = { + 0, 1, 3, 4, 6, 7, 8, 9, 11, 12, 14, 15, 255, 255, 255, 255}; +static const uvec8 kScaleRowDown38_2_BoxIndices2[] = { + 2, 18, 5, 21, 255, 255, 10, 26, 13, 29, 255, 255, 255, 255, 255, 255}; + void ScaleRowDown38_2_Box_NEON(const uint8_t* src_ptr, ptrdiff_t src_stride, uint8_t* dst_ptr, int dst_width) { - // TODO(fbarchard): use src_stride directly for clang 3.5+. - ptrdiff_t tmp_src_stride = src_stride; - asm volatile ( - "ld1 {v30.8h}, [%4] \n" - "ld1 {v31.16b}, [%5] \n" - "add %2, %2, %0 \n" - "1: \n" - - // 00 40 01 41 02 42 03 43 - // 10 50 11 51 12 52 13 53 - // 20 60 21 61 22 62 23 63 - // 30 70 31 71 32 72 33 73 - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" - "ld4 {v4.8b,v5.8b,v6.8b,v7.8b}, [%2], #32 \n" - "subs %w3, %w3, #12 \n" - - // Shuffle the input data around to get align the data - // so adjacent data can be added. 0,1 - 2,3 - 4,5 - 6,7 - // 00 10 01 11 02 12 03 13 - // 40 50 41 51 42 52 43 53 - "trn1 v16.8b, v0.8b, v1.8b \n" - "trn2 v17.8b, v0.8b, v1.8b \n" - "trn1 v18.8b, v4.8b, v5.8b \n" - "trn2 v19.8b, v4.8b, v5.8b \n" - - // 20 30 21 31 22 32 23 33 - // 60 70 61 71 62 72 63 73 - "trn1 v0.8b, v2.8b, v3.8b \n" - "trn2 v1.8b, v2.8b, v3.8b \n" - "trn1 v4.8b, v6.8b, v7.8b \n" - "trn2 v5.8b, v6.8b, v7.8b \n" - - // 00+10 01+11 02+12 03+13 - // 40+50 41+51 42+52 43+53 - "uaddlp v16.4h, v16.8b \n" - "uaddlp v17.4h, v17.8b \n" - "uaddlp v18.4h, v18.8b \n" - "uaddlp v19.4h, v19.8b \n" - - // 60+70 61+71 62+72 63+73 - "uaddlp v1.4h, v1.8b \n" - "uaddlp v5.4h, v5.8b \n" - - // combine source lines - "add v16.4h, v16.4h, v18.4h \n" - "add v17.4h, v17.4h, v19.4h \n" - "add v2.4h, v1.4h, v5.4h \n" - - // dst_ptr[3] = (s[6] + s[7] + s[6+st] + s[7+st]) / 4 - "uqrshrn v2.8b, v2.8h, #2 \n" - - // Shuffle 2,3 reg around so that 2 can be added to the - // 0,1 reg and 3 can be added to the 4,5 reg. This - // requires expanding from u8 to u16 as the 0,1 and 4,5 - // registers are already expanded. Then do transposes - // to get aligned. - // xx 20 xx 30 xx 21 xx 31 xx 22 xx 32 xx 23 xx 33 - - // combine source lines - "uaddl v0.8h, v0.8b, v4.8b \n" - - // xx 20 xx 21 xx 22 xx 23 - // xx 30 xx 31 xx 32 xx 33 - "trn1 v1.8h, v0.8h, v0.8h \n" - "trn2 v4.8h, v0.8h, v0.8h \n" - "xtn v0.4h, v1.4s \n" - "xtn v4.4h, v4.4s \n" - "prfm pldl1keep, [%0, 448] \n" // prefetch 7 lines ahead - - // 0+1+2, 3+4+5 - "add v16.8h, v16.8h, v0.8h \n" - "add v17.8h, v17.8h, v4.8h \n" - "prfm pldl1keep, [%2, 448] \n" - - // Need to divide, but can't downshift as the the value - // isn't a power of 2. So multiply by 65536 / n - // and take the upper 16 bits. - "sqrdmulh v0.8h, v16.8h, v30.8h \n" - "sqrdmulh v1.8h, v17.8h, v30.8h \n" - - // Align for table lookup, vtbl requires registers to - // be adjacent - - "tbl v3.16b, {v0.16b, v1.16b, v2.16b}, v31.16b \n" - - "st1 {v3.8b}, [%1], #8 \n" - "st1 {v3.s}[2], [%1], #4 \n" - "b.gt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(tmp_src_stride), // %2 - "+r"(dst_width) // %3 - : "r"(&kMult38_Div6), // %4 - "r"(&kShuf38_2) // %5 - : "memory", "cc", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v16", - "v17", "v18", "v19", "v30", "v31"); + const uint8_t* src_ptr1 = src_ptr + src_stride; + asm volatile( + "ld1 {v28.16b}, [%[tblArray1]] \n" + "ld1 {v29.16b}, [%[tblArray2]] \n" + "ld1 {v31.16b}, [%[tblArray3]] \n" + "ld1 {v30.8h}, [%[div664]] \n" + + "1: \n" + "ldp q20, q0, [%[src_ptr]], #32 \n" // abcdefgh ... + "ldp q21, q1, [%[src_ptr1]], #32 \n" // ijklmnop ... + "subs %w[width], %w[width], #12 \n" + + // Permute into groups of six values (three pairs) to be summed. + "tbl v22.16b, {v20.16b}, v28.16b \n" // abdegh ... + "tbl v2.16b, {v0.16b}, v28.16b \n" + "tbl v23.16b, {v21.16b}, v28.16b \n" // ijlmop ... + "tbl v3.16b, {v1.16b}, v28.16b \n" + "tbl v24.16b, {v20.16b, v21.16b}, v29.16b \n" // ckfn00 ... + "tbl v4.16b, {v0.16b, v1.16b}, v29.16b \n" + + "uaddlp v22.8h, v22.16b \n" // a+b d+e g+h ... + "uaddlp v2.8h, v2.16b \n" + "uaddlp v23.8h, v23.16b \n" // i+j l+m o+p ... + "uaddlp v3.8h, v3.16b \n" + "uaddlp v24.8h, v24.16b \n" // c+k f+n 0 ... + "uaddlp v4.8h, v4.16b \n" + "add v20.8h, v22.8h, v23.8h \n" + "add v0.8h, v2.8h, v3.8h \n" + "add v21.8h, v20.8h, v24.8h \n" // a+b+i+j+c+k ... + "add v1.8h, v0.8h, v4.8h \n" + + "sqrdmulh v21.8h, v21.8h, v30.8h \n" // v /= {6,6,4} + "sqrdmulh v22.8h, v1.8h, v30.8h \n" + "tbl v21.16b, {v21.16b, v22.16b}, v31.16b \n" // Narrow. + "st1 {v21.d}[0], [%[dst_ptr]], #8 \n" + "st1 {v21.s}[2], [%[dst_ptr]], #4 \n" + "b.gt 1b \n" + : [src_ptr] "+r"(src_ptr), // %[src_ptr] + [dst_ptr] "+r"(dst_ptr), // %[dst_ptr] + [src_ptr1] "+r"(src_ptr1), // %[src_ptr1] + [width] "+r"(dst_width) // %[width] + : [div664] "r"(&kMult38_Div664), // %[div664] + [tblArray1] "r"(kScaleRowDown38_2_BoxIndices1), // %[tblArray1] + [tblArray2] "r"(kScaleRowDown38_2_BoxIndices2), // %[tblArray2] + [tblArray3] "r"(kScaleRowDown38_NarrowIndices) // %[tblArray3] + : "memory", "cc", "v0", "v1", "v2", "v3", "v4", "v20", "v21", "v22", + "v23", "v24", "v28", "v29", "v30", "v31"); } void ScaleRowUp2_Linear_NEON(const uint8_t* src_ptr, @@ -1092,15 +1005,12 @@ void ScaleAddRow_NEON(const uint8_t* src_ptr, ); } -// TODO(Yang Zhang): Investigate less load instructions for -// the x/dx stepping -#define LOAD2_DATA8_LANE(n) \ - "lsr %5, %3, #16 \n" \ - "add %6, %1, %5 \n" \ - "add %3, %3, %4 \n" \ - "ld2 {v4.b, v5.b}[" #n "], [%6] \n" +#define SCALE_FILTER_COLS_STEP_ADDR \ + "lsr %[tmp_offset], %x[x], #16 \n" \ + "add %[tmp_ptr], %[src_ptr], %[tmp_offset] \n" \ + "add %x[x], %x[x], %x[dx] \n" -// The NEON version mimics this formula (from row_common.cc): +// The Neon version mimics this formula (from scale_common.cc): // #define BLENDER(a, b, f) (uint8_t)((int)(a) + // ((((int)((f)) * ((int)(b) - (int)(a))) + 0x8000) >> 16)) @@ -1110,65 +1020,69 @@ void ScaleFilterCols_NEON(uint8_t* dst_ptr, int x, int dx) { int dx_offset[4] = {0, 1, 2, 3}; - int* tmp = dx_offset; - const uint8_t* src_tmp = src_ptr; - int64_t x64 = (int64_t)x; // NOLINT - int64_t dx64 = (int64_t)dx; // NOLINT - asm volatile ( - "dup v0.4s, %w3 \n" // x - "dup v1.4s, %w4 \n" // dx - "ld1 {v2.4s}, [%5] \n" // 0 1 2 3 + int64_t tmp_offset; + uint8_t* tmp_ptr; + asm volatile( + "dup v0.4s, %w[x] \n" + "dup v1.4s, %w[dx] \n" + "ld1 {v2.4s}, [%[dx_offset]] \n" // 0 1 2 3 "shl v3.4s, v1.4s, #2 \n" // 4 * dx + "shl v22.4s, v1.4s, #3 \n" // 8 * dx + "mul v1.4s, v1.4s, v2.4s \n" - // x , x + 1 * dx, x + 2 * dx, x + 3 * dx + // x , x + 1 * dx, x + 2 * dx, x + 3 * dx "add v1.4s, v1.4s, v0.4s \n" - // x + 4 * dx, x + 5 * dx, x + 6 * dx, x + 7 * dx + // x + 4 * dx, x + 5 * dx, x + 6 * dx, x + 7 * dx "add v2.4s, v1.4s, v3.4s \n" - "shl v0.4s, v3.4s, #1 \n" // 8 * dx - "1: \n" - LOAD2_DATA8_LANE(0) - LOAD2_DATA8_LANE(1) - LOAD2_DATA8_LANE(2) - LOAD2_DATA8_LANE(3) - LOAD2_DATA8_LANE(4) - LOAD2_DATA8_LANE(5) - LOAD2_DATA8_LANE(6) - LOAD2_DATA8_LANE(7) - "mov v6.16b, v1.16b \n" - "mov v7.16b, v2.16b \n" - "uzp1 v6.8h, v6.8h, v7.8h \n" - "ushll v4.8h, v4.8b, #0 \n" - "ushll v5.8h, v5.8b, #0 \n" + + "movi v0.8h, #0 \n" + + // truncate to uint16_t + "trn1 v22.8h, v22.8h, v0.8h \n" + "trn1 v20.8h, v1.8h, v0.8h \n" + "trn1 v21.8h, v2.8h, v0.8h \n" + + "1: \n" SCALE_FILTER_COLS_STEP_ADDR + "ldr h6, [%[tmp_ptr]] \n" SCALE_FILTER_COLS_STEP_ADDR + "ld1 {v6.h}[1], [%[tmp_ptr]] \n" SCALE_FILTER_COLS_STEP_ADDR + "ld1 {v6.h}[2], [%[tmp_ptr]] \n" SCALE_FILTER_COLS_STEP_ADDR + "ld1 {v6.h}[3], [%[tmp_ptr]] \n" SCALE_FILTER_COLS_STEP_ADDR + "ld1 {v6.h}[4], [%[tmp_ptr]] \n" SCALE_FILTER_COLS_STEP_ADDR + "ld1 {v6.h}[5], [%[tmp_ptr]] \n" SCALE_FILTER_COLS_STEP_ADDR + "ld1 {v6.h}[6], [%[tmp_ptr]] \n" SCALE_FILTER_COLS_STEP_ADDR + "ld1 {v6.h}[7], [%[tmp_ptr]] \n" + + "subs %w[width], %w[width], #8 \n" // 8 processed per loop + "trn1 v4.16b, v6.16b, v0.16b \n" + "trn2 v5.16b, v6.16b, v0.16b \n" + "ssubl v16.4s, v5.4h, v4.4h \n" "ssubl2 v17.4s, v5.8h, v4.8h \n" - "ushll v7.4s, v6.4h, #0 \n" - "ushll2 v6.4s, v6.8h, #0 \n" - "mul v16.4s, v16.4s, v7.4s \n" - "mul v17.4s, v17.4s, v6.4s \n" + "mul v16.4s, v16.4s, v20.4s \n" + "mul v17.4s, v17.4s, v21.4s \n" "rshrn v6.4h, v16.4s, #16 \n" "rshrn2 v6.8h, v17.4s, #16 \n" "add v4.8h, v4.8h, v6.8h \n" "xtn v4.8b, v4.8h \n" - "st1 {v4.8b}, [%0], #8 \n" // store pixels - "add v1.4s, v1.4s, v0.4s \n" - "add v2.4s, v2.4s, v0.4s \n" - "subs %w2, %w2, #8 \n" // 8 processed per loop + "add v20.8h, v20.8h, v22.8h \n" + "add v21.8h, v21.8h, v22.8h \n" + + "st1 {v4.8b}, [%[dst_ptr]], #8 \n" // store pixels "b.gt 1b \n" - : "+r"(dst_ptr), // %0 - "+r"(src_ptr), // %1 - "+r"(dst_width), // %2 - "+r"(x64), // %3 - "+r"(dx64), // %4 - "+r"(tmp), // %5 - "+r"(src_tmp) // %6 - : - : "memory", "cc", "v0", "v1", "v2", "v3", - "v4", "v5", "v6", "v7", "v16", "v17" - ); + : [src_ptr] "+r"(src_ptr), // %[src_ptr] + [dst_ptr] "+r"(dst_ptr), // %[dst_ptr] + [width] "+r"(dst_width), // %[width] + [x] "+r"(x), // %[x] + [dx] "+r"(dx), // %[dx] + [tmp_offset] "=&r"(tmp_offset), // %[tmp_offset] + [tmp_ptr] "=&r"(tmp_ptr) // %[tmp_ptr] + : [dx_offset] "r"(dx_offset) // %[dx_offset] + : "memory", "cc", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v16", "v17", + "v20", "v21", "v22"); } -#undef LOAD2_DATA8_LANE +#undef SCALE_FILTER_COLS_STEP_ADDR void ScaleARGBRowDown2_NEON(const uint8_t* src_ptr, ptrdiff_t src_stride, diff --git a/libfenrir/src/main/jni/animation/libyuv/source/scale_sme.cc b/libfenrir/src/main/jni/animation/libyuv/source/scale_sme.cc new file mode 100644 index 000000000..fd364b316 --- /dev/null +++ b/libfenrir/src/main/jni/animation/libyuv/source/scale_sme.cc @@ -0,0 +1,292 @@ +/* + * Copyright 2024 The LibYuv Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "libyuv/scale_row.h" + +#ifdef __cplusplus +namespace libyuv { +extern "C" { +#endif + + +#if !defined(LIBYUV_DISABLE_SME) && defined(CLANG_HAS_SME) && \ + defined(__aarch64__) + +__arm_locally_streaming void ScaleRowDown2_SME(const uint8_t* src_ptr, + ptrdiff_t src_stride, + uint8_t* dst, + int dst_width) { + // Streaming-SVE only, no use of ZA tile. + (void)src_stride; + int vl; + asm volatile( + "cntb %x[vl] \n" + "subs %w[dst_width], %w[dst_width], %w[vl] \n" + "b.lt 2f \n" + + "1: \n" + "ptrue p0.b \n" + "ld2b {z0.b, z1.b}, p0/z, [%[src_ptr]] \n" + "incb %[src_ptr], all, mul #2 \n" + "subs %w[dst_width], %w[dst_width], %w[vl] \n" + "st1b {z1.b}, p0, [%[dst_ptr]] \n" + "incb %[dst_ptr] \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[dst_width], %w[dst_width], %w[vl] \n" + "b.eq 99f \n" + + "whilelt p0.b, wzr, %w[dst_width] \n" + "ld2b {z0.b, z1.b}, p0/z, [%[src_ptr]] \n" + "st1b {z1.b}, p0, [%[dst_ptr]] \n" + + "99: \n" + : [src_ptr] "+r"(src_ptr), // %[src_ptr] + [dst_ptr] "+r"(dst), // %[dst_ptr] + [dst_width] "+r"(dst_width), // %[dst_width] + [vl] "=r"(vl) // %[vl] + : + : "memory", "cc", "z0", "z1", "p0"); +} + +__arm_locally_streaming void ScaleRowDown2Linear_SME(const uint8_t* src_ptr, + ptrdiff_t src_stride, + uint8_t* dst, + int dst_width) { + // Streaming-SVE only, no use of ZA tile. + (void)src_stride; + int vl; + asm volatile( + "cntb %x[vl] \n" + "subs %w[dst_width], %w[dst_width], %w[vl] \n" + "b.lt 2f \n" + + "1: \n" + "ptrue p0.b \n" + "ld2b {z0.b, z1.b}, p0/z, [%[src_ptr]] \n" + "incb %[src_ptr], all, mul #2 \n" + "urhadd z0.b, p0/m, z0.b, z1.b \n" + "subs %w[dst_width], %w[dst_width], %w[vl] \n" + "st1b {z0.b}, p0, [%[dst_ptr]] \n" + "incb %[dst_ptr] \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[dst_width], %w[dst_width], %w[vl] \n" + "b.eq 99f \n" + + "whilelt p0.b, wzr, %w[dst_width] \n" + "ld2b {z0.b, z1.b}, p0/z, [%[src_ptr]] \n" + "urhadd z0.b, p0/m, z0.b, z1.b \n" + "st1b {z0.b}, p0, [%[dst_ptr]] \n" + + "99: \n" + : [src_ptr] "+r"(src_ptr), // %[src_ptr] + [dst_ptr] "+r"(dst), // %[dst_ptr] + [dst_width] "+r"(dst_width), // %[dst_width] + [vl] "=r"(vl) // %[vl] + : + : "memory", "cc", "z0", "z1", "p0"); +} + +#define SCALEROWDOWN2BOX_SVE \ + "ld2b {z0.b, z1.b}, p0/z, [%[src_ptr]] \n" \ + "ld2b {z2.b, z3.b}, p0/z, [%[src2_ptr]] \n" \ + "incb %[src_ptr], all, mul #2 \n" \ + "incb %[src2_ptr], all, mul #2 \n" \ + "uaddlb z4.h, z0.b, z1.b \n" \ + "uaddlt z5.h, z0.b, z1.b \n" \ + "uaddlb z6.h, z2.b, z3.b \n" \ + "uaddlt z7.h, z2.b, z3.b \n" \ + "add z4.h, z4.h, z6.h \n" \ + "add z5.h, z5.h, z7.h \n" \ + "rshrnb z0.b, z4.h, #2 \n" \ + "rshrnt z0.b, z5.h, #2 \n" \ + "subs %w[dst_width], %w[dst_width], %w[vl] \n" \ + "st1b {z0.b}, p0, [%[dst_ptr]] \n" \ + "incb %[dst_ptr] \n" + +__arm_locally_streaming void ScaleRowDown2Box_SME(const uint8_t* src_ptr, + ptrdiff_t src_stride, + uint8_t* dst, + int dst_width) { + // Streaming-SVE only, no use of ZA tile. + const uint8_t* src2_ptr = src_ptr + src_stride; + int vl; + asm volatile( + "cntb %x[vl] \n" + "subs %w[dst_width], %w[dst_width], %w[vl] \n" + "b.lt 2f \n" + + "ptrue p0.b \n" + "1: \n" // + SCALEROWDOWN2BOX_SVE + "b.ge 1b \n" + + "2: \n" + "adds %w[dst_width], %w[dst_width], %w[vl] \n" + "b.eq 99f \n" + + "whilelt p0.b, wzr, %w[dst_width] \n" // + SCALEROWDOWN2BOX_SVE + + "99: \n" + : [src_ptr] "+r"(src_ptr), // %[src_ptr] + [src2_ptr] "+r"(src2_ptr), // %[src2_ptr] + [dst_ptr] "+r"(dst), // %[dst_ptr] + [dst_width] "+r"(dst_width), // %[dst_width] + [vl] "=r"(vl) // %[vl] + : + : "memory", "cc", "z0", "z1", "z2", "z3", "z4", "z5", "z6", "z7", "p0"); +} + +#undef SCALEROWDOWN2BOX_SVE + +__arm_locally_streaming void ScaleUVRowDown2_SME(const uint8_t* src_uv, + ptrdiff_t src_stride, + uint8_t* dst_uv, + int dst_width) { + // Streaming-SVE only, no use of ZA tile. + (void)src_stride; + int vl; + asm volatile( + "cnth %x[vl] \n" + "subs %w[dst_width], %w[dst_width], %w[vl] \n" + "b.lt 2f \n" + + "1: \n" + "ptrue p0.b \n" + "ld2h {z0.h, z1.h}, p0/z, [%[src_uv]] \n" + "incb %[src_uv], all, mul #2 \n" + "subs %w[dst_width], %w[dst_width], %w[vl] \n" + "st1h {z1.h}, p0, [%[dst_uv]] \n" + "incb %[dst_uv] \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[dst_width], %w[dst_width], %w[vl] \n" + "b.eq 99f \n" + + "whilelt p0.h, wzr, %w[dst_width] \n" + "ld2h {z0.h, z1.h}, p0/z, [%[src_uv]] \n" + "st1h {z1.h}, p0, [%[dst_uv]] \n" + + "99: \n" + : [src_uv] "+r"(src_uv), // %[src_uv] + [dst_uv] "+r"(dst_uv), // %[dst_uv] + [dst_width] "+r"(dst_width), // %[dst_width] + [vl] "=r"(vl) // %[vl] + : + : "memory", "cc", "z0", "z1", "p0"); +} + +__arm_locally_streaming void ScaleUVRowDown2Linear_SME(const uint8_t* src_uv, + ptrdiff_t src_stride, + uint8_t* dst_uv, + int dst_width) { + // Streaming-SVE only, no use of ZA tile. + (void)src_stride; + int vl; + asm volatile( + "cnth %x[vl] \n" + "ptrue p1.b \n" + "subs %w[dst_width], %w[dst_width], %w[vl] \n" + "b.lt 2f \n" + + "ptrue p0.h \n" + "1: \n" + "ld2h {z0.h, z1.h}, p0/z, [%[src_uv]] \n" + "incb %[src_uv], all, mul #2 \n" + "urhadd z0.b, p1/m, z0.b, z1.b \n" + "st1h {z0.h}, p0, [%[dst_uv]] \n" + "incb %[dst_uv], all, mul #1 \n" + "subs %w[dst_width], %w[dst_width], %w[vl] \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[dst_width], %w[dst_width], %w[vl] \n" + "b.eq 99f \n" + + "whilelt p0.h, wzr, %w[dst_width] \n" + "ld2h {z0.h, z1.h}, p0/z, [%[src_uv]] \n" + "urhadd z0.b, p1/m, z0.b, z1.b \n" + "st1h {z0.h}, p0, [%[dst_uv]] \n" + + "99: \n" + : [src_uv] "+r"(src_uv), // %[src_uv] + [dst_uv] "+r"(dst_uv), // %[dst_uv] + [dst_width] "+r"(dst_width), // %[dst_width] + [vl] "=r"(vl) // %[vl] + : + : "z0", "z1", "p0", "p1"); +} + +#define SCALEUVROWDOWN2BOX_SVE \ + "ld2h {z0.h, z1.h}, p0/z, [%[src_uv]] \n" \ + "ld2h {z2.h, z3.h}, p0/z, [%[src2_uv]] \n" \ + "incb %[src_uv], all, mul #2 \n" \ + "incb %[src2_uv], all, mul #2 \n" \ + "uaddlb z4.h, z0.b, z1.b \n" \ + "uaddlt z5.h, z0.b, z1.b \n" \ + "uaddlb z6.h, z2.b, z3.b \n" \ + "uaddlt z7.h, z2.b, z3.b \n" \ + "add z4.h, z4.h, z6.h \n" \ + "add z5.h, z5.h, z7.h \n" \ + "rshrnb z0.b, z4.h, #2 \n" \ + "rshrnt z0.b, z5.h, #2 \n" \ + "st1h {z0.h}, p0, [%[dst_uv]] \n" \ + "incb %[dst_uv], all, mul #1 \n" + +__arm_locally_streaming void ScaleUVRowDown2Box_SME(const uint8_t* src_uv, + ptrdiff_t src_stride, + uint8_t* dst_uv, + int dst_width) { + // Streaming-SVE only, no use of ZA tile. + const uint8_t* src2_uv = src_uv + src_stride; + int vl; + asm volatile( + "cnth %x[vl] \n" + "ptrue p1.b \n" + "subs %w[dst_width], %w[dst_width], %w[vl] \n" + "b.lt 2f \n" + + "ptrue p0.h \n" + "1: \n" // + SCALEUVROWDOWN2BOX_SVE + "subs %w[dst_width], %w[dst_width], %w[vl] \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[dst_width], %w[dst_width], %w[vl] \n" + "b.eq 99f \n" + + "whilelt p0.h, wzr, %w[dst_width] \n" // + SCALEUVROWDOWN2BOX_SVE + + "99: \n" + : [src_uv] "+r"(src_uv), // %[src_uv] + [src2_uv] "+r"(src2_uv), // %[src2_uv] + [dst_uv] "+r"(dst_uv), // %[dst_uv] + [dst_width] "+r"(dst_width), // %[dst_width] + [vl] "=r"(vl) // %[vl] + : + : "z0", "z1", "z2", "z3", "z4", "z5", "z6", "z7", "p0", "p1"); +} + +#undef SCALEUVROWDOWN2BOX_SVE + +#endif // !defined(LIBYUV_DISABLE_SME) && defined(CLANG_HAS_SME) && + // defined(__aarch64__) + +#ifdef __cplusplus +} // extern "C" +} // namespace libyuv +#endif diff --git a/libfenrir/src/main/jni/animation/libyuv/source/scale_uv.cc b/libfenrir/src/main/jni/animation/libyuv/source/scale_uv.cc index 5c55aab51..9ef2e1387 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/scale_uv.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/scale_uv.cc @@ -104,14 +104,6 @@ static void ScaleUVDown2(int src_width, } } #endif -#if defined(HAS_SCALEUVROWDOWN2BOX_NEON) - if (TestCpuFlag(kCpuHasNEON) && filtering) { - ScaleUVRowDown2 = ScaleUVRowDown2Box_Any_NEON; - if (IS_ALIGNED(dst_width, 8)) { - ScaleUVRowDown2 = ScaleUVRowDown2Box_NEON; - } - } -#endif #if defined(HAS_SCALEUVROWDOWN2_NEON) if (TestCpuFlag(kCpuHasNEON)) { ScaleUVRowDown2 = @@ -128,6 +120,13 @@ static void ScaleUVDown2(int src_width, } } #endif +#if defined(HAS_SCALEUVROWDOWN2_SME) + if (TestCpuFlag(kCpuHasSME)) { + ScaleUVRowDown2 = filtering == kFilterNone ? ScaleUVRowDown2_SME + : filtering == kFilterLinear ? ScaleUVRowDown2Linear_SME + : ScaleUVRowDown2Box_SME; + } +#endif #if defined(HAS_SCALEUVROWDOWN2_RVV) if (TestCpuFlag(kCpuHasRVV)) { ScaleUVRowDown2 = @@ -242,6 +241,11 @@ static int ScaleUVDown4Box(int src_width, } } #endif +#if defined(HAS_SCALEUVROWDOWN2BOX_SME) + if (TestCpuFlag(kCpuHasSME)) { + ScaleUVRowDown2 = ScaleUVRowDown2Box_SME; + } +#endif #if defined(HAS_SCALEUVROWDOWN2BOX_RVV) if (TestCpuFlag(kCpuHasRVV)) { ScaleUVRowDown2 = ScaleUVRowDown2Box_RVV; diff --git a/libfenrir/src/main/jni/animation/lottie_jni.cpp b/libfenrir/src/main/jni/animation/lottie_jni.cpp deleted file mode 100644 index 56092daa3..000000000 --- a/libfenrir/src/main/jni/animation/lottie_jni.cpp +++ /dev/null @@ -1,337 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "fenrir_native.h" -#include "gif_builder.h" - -using namespace rlottie; - -class LottieInfo { -public: - std::unique_ptr animation; - std::string path; -}; - -std::string doDecompressResource(size_t length, char *bytes, bool &orig) { - orig = false; - std::string data; - if (length >= GZIP_HEADER_LENGTH && memcmp(bytes, GZIP_HEADER, GZIP_HEADER_LENGTH) == 0) { - z_stream zs; - memset(&zs, 0, sizeof(zs)); - - if (inflateInit2(&zs, 15 + 16) != Z_OK) { - return ""; - } - - zs.next_in = (Bytef *) bytes; - zs.avail_in = length; - - int ret; - std::vector outBuffer(32768); - - do { - zs.next_out = reinterpret_cast(outBuffer.data()); - zs.avail_out = outBuffer.size(); - ret = inflate(&zs, 0); - if (data.size() < zs.total_out) { - data.append(outBuffer.data(), zs.total_out - data.size()); - } - - } while (ret == Z_OK); - inflateEnd(&zs); - if (ret != Z_STREAM_END) { - return ""; - } - } else if (length >= MY_LZ4_HEADER_LENGTH && - memcmp(bytes, MY_LZ4_HEADER, MY_LZ4_HEADER_LENGTH) == 0) { - MY_LZ4HDR_PUSH hdr = {}; - memcpy(&hdr, bytes, sizeof(MY_LZ4HDR_PUSH)); - data.resize(hdr.size); - LZ4_decompress_safe(((const char *) bytes + sizeof(MY_LZ4HDR_PUSH)), (char *) data.data(), - (int) length - (int) sizeof(MY_LZ4HDR_PUSH), (int) hdr.size); - } else { - orig = true; - } - return data; -} - -extern "C" JNIEXPORT jlong -Java_dev_ragnarok_fenrir_module_rlottie_RLottieDrawable_create(JNIEnv *env, jobject, jstring src, - jintArray data, - jintArray colorReplacement, - jboolean useMoveColor) { - auto *info = new LottieInfo(); - internal::ColorReplace *colors = nullptr; - if (colorReplacement != nullptr) { - jint *arr = env->GetIntArrayElements(colorReplacement, nullptr); - if (arr != nullptr) { - jsize len = env->GetArrayLength(colorReplacement); - if (len % 2 == 0) { - colors = new internal::ColorReplace(useMoveColor); - for (int32_t a = 0; a < len / 2; a++) { - colors->colorMap[arr[a * 2]] = arr[a * 2 + 1]; - } - } - env->ReleaseIntArrayElements(colorReplacement, arr, 0); - } - } - - char const *srcString = SafeGetStringUTFChars(env, src, nullptr); - info->path = srcString; - if (srcString != nullptr) { - env->ReleaseStringUTFChars(src, srcString); - } - std::ifstream f; - f.open(info->path); - if (!f.is_open()) { - delete info; - return 0; - } - f.seekg(0, std::ios::end); - auto length = f.tellg(); - f.seekg(0, std::ios::beg); - if (length <= 0) { - f.close(); - delete info; - return 0; - } - auto *arr = new char[(size_t) length + 1]; - f.read(arr, length); - f.close(); - arr[length] = '\0'; - bool orig; - std::string jsonString = doDecompressResource(length, arr, orig); - if (orig) { - info->animation = rlottie::Animation::loadFromData(arr, colors); - } - delete[] arr; - if (!orig) { - if (jsonString.empty()) { - delete info; - return 0; - } - info->animation = rlottie::Animation::loadFromData(jsonString.c_str(), colors); - } - if (info->animation == nullptr) { - delete info; - return 0; - } - - jint *dataArr = env->GetIntArrayElements(data, nullptr); - if (dataArr != nullptr) { - dataArr[0] = (jint) info->animation->totalFrame(); - dataArr[1] = (jint) info->animation->frameRate(); - env->ReleaseIntArrayElements(data, dataArr, 0); - } - return (jlong) (intptr_t) info; -} - -extern "C" JNIEXPORT jlong -Java_dev_ragnarok_fenrir_module_rlottie_RLottieDrawable_createWithJson(JNIEnv *env, jobject, - jlong json, - jintArray data, - jintArray colorReplacement, - jboolean useMoveColor) { - internal::ColorReplace *colors = nullptr; - if (colorReplacement != nullptr) { - jint *arr = env->GetIntArrayElements(colorReplacement, nullptr); - if (arr != nullptr) { - jsize len = env->GetArrayLength(colorReplacement); - if (len % 2 == 0) { - colors = new internal::ColorReplace(useMoveColor); - for (int32_t a = 0; a < len / 2; a++) { - colors->colorMap[arr[a * 2]] = arr[a * 2 + 1]; - } - } - env->ReleaseIntArrayElements(colorReplacement, arr, 0); - } - } - - auto *info = new LottieInfo(); - auto u = ((std::vector *) (intptr_t) json); - bool orig; - std::string jsonString = doDecompressResource(u->size(), u->data(), orig); - if (orig) { - info->animation = rlottie::Animation::loadFromData(u->data(), colors); - } else { - info->animation = rlottie::Animation::loadFromData(jsonString.c_str(), colors); - } - if (info->animation == nullptr) { - delete info; - return 0; - } - - jint *dataArr = env->GetIntArrayElements(data, nullptr); - if (dataArr != nullptr) { - dataArr[0] = (int) info->animation->totalFrame(); - dataArr[1] = (int) info->animation->frameRate(); - env->ReleaseIntArrayElements(data, dataArr, 0); - } - return (jlong) (intptr_t) info; -} - -extern "C" JNIEXPORT void -Java_dev_ragnarok_fenrir_module_rlottie_RLottieDrawable_destroy(JNIEnv *, jobject, jlong ptr) { - if (!ptr) { - return; - } - auto *info = (LottieInfo *) (intptr_t) ptr; - delete info; -} - -extern "C" JNIEXPORT void -Java_dev_ragnarok_fenrir_module_rlottie_RLottieDrawable_setLayerColor(JNIEnv *env, jobject, - jlong ptr, - jstring layer, jint color) { - if (!ptr || layer == nullptr) { - return; - } - auto *info = (LottieInfo *) (intptr_t) ptr; - char const *layerString = SafeGetStringUTFChars(env, layer, nullptr); - Color v((float) ((color) & 0xff) / 255.0f, - (float) ((color >> 8) & 0xff) / - 255.0f, - (float) ((color >> 16) & 0xff) / - 255.0f); - - info->animation->setValue(layerString, v); - info->animation->setValue(layerString, v); - if (layerString != nullptr) { - env->ReleaseStringUTFChars(layer, layerString); - } -} - -extern "C" JNIEXPORT void -Java_dev_ragnarok_fenrir_module_rlottie_RLottieDrawable_replaceColors(JNIEnv *env, jobject, - jlong ptr, - jintArray colorReplacement) { - if (!ptr || colorReplacement == nullptr) { - return; - } - auto *info = (LottieInfo *) (intptr_t) ptr; - if (!info->animation->colorMap || info->animation->colorMap->useMoveColor) { - return; - } - jint *arr = env->GetIntArrayElements(colorReplacement, nullptr); - if (arr != nullptr) { - jsize len = env->GetArrayLength(colorReplacement); - if (len % 2 == 0) { - for (int32_t a = 0; a < len / 2; a++) { - info->animation->colorMap->colorMap[arr[a * 2]] = arr[a * 2 + 1]; - } - info->animation->resetCurrentFrame(); - } - env->ReleaseIntArrayElements(colorReplacement, arr, 0); - } -} - -extern "C" JNIEXPORT jint -Java_dev_ragnarok_fenrir_module_rlottie_RLottieDrawable_getFrame(JNIEnv *env, jobject, jlong ptr, - jint frame, - jobject bitmap, jint w, jint h, - jint stride, - jboolean clear) { - if (!ptr || !bitmap || w <= 0 || h <= 0) { - return 0; - } - auto *info = (LottieInfo *) (intptr_t) ptr; - - void *pixels; - if (AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0) { - Surface surface((uint32_t *) pixels, (size_t) w, (size_t) h, (size_t) stride); - info->animation->renderSync((size_t) frame, surface, clear); - AndroidBitmap_unlockPixels(env, bitmap); - } - return frame; -} - -class Lottie2Gif { -public: - static bool render(LottieInfo *player, jobject bitmap, int w, int h, int stride, int bgColor, - bool transparent, const std::string &gifName, int bitDepth, - bool dither, JNIEnv *env, jobject listener) { - void *pixels; - if (AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0) { - size_t frameCount = player->animation->totalFrame(); - uint32_t delay = 2; - GifBuilder builder(gifName, w, h, bgColor, delay, bitDepth, dither); - size_t start = 0, end = frameCount; - - if (listener != nullptr) { - jweak store_Wlistener = env->NewWeakGlobalRef(listener); - jclass clazz = env->GetObjectClass(store_Wlistener); - - jmethodID mth_update = env->GetMethodID(clazz, "onProgress", "(II)V"); - jmethodID mth_start = env->GetMethodID(clazz, "onStarted", "()V"); - jmethodID mth_end = env->GetMethodID(clazz, "onFinished", "()V"); - - env->CallVoidMethod(store_Wlistener, mth_start); - - for (size_t i = start; i < end; i++) { - rlottie::Surface surface((uint32_t *) pixels, (size_t) w, (size_t) h, - (size_t) stride); - player->animation->renderSync(i, surface, true); - builder.addFrame(surface.buffer(), surface.width(), surface.height(), - surface.bytesPerLine(), transparent, delay, bitDepth, dither); - - env->CallVoidMethod(store_Wlistener, mth_update, (jint) (i + 1), - (jint) frameCount); - } - - env->CallVoidMethod(store_Wlistener, mth_end); - } else { - for (size_t i = start; i < end; i++) { - rlottie::Surface surface((uint32_t *) pixels, (size_t) w, (size_t) h, - (size_t) stride); - player->animation->renderSync(i, surface, true); - builder.addFrame(surface.buffer(), surface.width(), surface.height(), - surface.bytesPerLine(), transparent, delay, bitDepth, dither); - } - } - - AndroidBitmap_unlockPixels(env, bitmap); - return true; - } - return false; - } - -}; - -extern "C" JNIEXPORT -jboolean -Java_dev_ragnarok_fenrir_module_rlottie_RLottie2Gif_lottie2gif(JNIEnv *env, jobject, jlong json, - jobject bitmap, jint w, jint h, - jint stride, jint bgColor, - jboolean transparent, - jstring gifName, - jint bitDepth, - jboolean dither, - jobject listener) { - if (!json) { - return false; - } - auto *info = new LottieInfo(); - auto u = ((std::vector *) (intptr_t) json); - bool orig; - std::string jsonString = doDecompressResource(u->size(), u->data(), orig); - if (orig) { - info->animation = rlottie::Animation::loadFromData(u->data(), nullptr); - } else { - info->animation = rlottie::Animation::loadFromData(jsonString.c_str(), nullptr); - } - if (info->animation == nullptr) { - delete info; - return 0; - } - - char const *name = SafeGetStringUTFChars(env, gifName, nullptr); - return Lottie2Gif::render(info, bitmap, w, h, stride, bgColor, (bool) transparent, name, - bitDepth, dither, env, listener); -} \ No newline at end of file diff --git a/libfenrir/src/main/jni/animation/rlottie/inc/colorreplace.h b/libfenrir/src/main/jni/animation/rlottie/inc/colorreplace.h deleted file mode 100644 index 0e4e463d3..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/inc/colorreplace.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef _ColorReplace_H_ -#define _ColorReplace_H_ -#include - -#define RLOTTIE_API - -namespace rlottie { - namespace internal { - class RLOTTIE_API ColorReplace { - public: - ColorReplace(bool useMoveColor = false) { - this->useMoveColor = useMoveColor; - } - ColorReplace(const std::map&colorMap, bool useMoveColor = false) { - this->colorMap = colorMap; - this->useMoveColor = useMoveColor; - } - std::mapcolorMap; - bool useMoveColor; - }; - } -} -#endif diff --git a/libfenrir/src/main/jni/animation/rlottie/inc/rlottie.h b/libfenrir/src/main/jni/animation/rlottie/inc/rlottie.h deleted file mode 100644 index 4cb41dfa3..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/inc/rlottie.h +++ /dev/null @@ -1,456 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef _RLOTTIE_H_ -#define _RLOTTIE_H_ - -#include -#include -#include -#include -#include "colorreplace.h" - -#define RLOTTIE_API - -class AnimationImpl; -struct LOTNode; -struct LOTLayerNode; - -namespace rlottie { - -/** - * @brief Configures rlottie model cache policy. - * - * Provides Library level control to configure model cache - * policy. Setting it to 0 will disable - * the cache as well as flush all the previously cached content. - * - * @param[in] cacheSize Maximum Model Cache size. - * - * @note to disable Caching configure with 0 size. - * @note to flush the current Cache content configure it with 0 and - * then reconfigure with the new size. - * - * @internal - */ - -struct Color { - Color() = default; - Color(float r, float g , float b):_r(r), _g(g), _b(b){} - float r() const {return _r;} - float g() const {return _g;} - float b() const {return _b;} -private: - float _r{0}; - float _g{0}; - float _b{0}; -}; - -struct Size { - Size() = default; - Size(float w, float h):_w(w), _h(h){} - float w() const {return _w;} - float h() const {return _h;} -private: - float _w{0}; - float _h{0}; -}; - -struct Point { - Point() = default; - Point(float x, float y):_x(x), _y(y){} - float x() const {return _x;} - float y() const {return _y;} -private: - float _x{0}; - float _y{0}; -}; - -struct FrameInfo { - explicit FrameInfo(uint32_t frame): _frameNo(frame){} - uint32_t curFrame() const {return _frameNo;} -private: - uint32_t _frameNo; -}; - -enum class Property { - FillColor, /*!< Color property of Fill object , value type is rlottie::Color */ - FillOpacity, /*!< Opacity property of Fill object , value type is float [ 0 .. 100] */ - StrokeColor, /*!< Color property of Stroke object , value type is rlottie::Color */ - StrokeOpacity, /*!< Opacity property of Stroke object , value type is float [ 0 .. 100] */ - StrokeWidth, /*!< stroke width property of Stroke object , value type is float */ - TrAnchor, /*!< Transform Anchor property of Layer and Group object , value type is rlottie::Point */ - TrPosition, /*!< Transform Position property of Layer and Group object , value type is rlottie::Point */ - TrScale, /*!< Transform Scale property of Layer and Group object , value type is rlottie::Size. range[0 ..100] */ - TrRotation, /*!< Transform Rotation property of Layer and Group object , value type is float. range[0 .. 360] in degrees*/ - TrOpacity, /*!< Transform Opacity property of Layer and Group object , value type is float [ 0 .. 100] */ - TrimStart, /*!< Trim Start property of Shape object , value type is float [ 0 .. 100] */ - TrimEnd /*!< Trim End property of Shape object , value type is rlottie::Point [ 0 .. 100] */ -}; - -struct Color_Type{}; -struct Point_Type{}; -struct Size_Type{}; -struct Float_Type{}; -template struct MapType; - -class RLOTTIE_API Surface { -public: - /** - * @brief Surface object constructor. - * - * @param[in] buffer surface buffer. - * @param[in] width surface width. - * @param[in] height surface height. - * @param[in] bytesPerLine number of bytes in a surface scanline. - * - * @note Default surface format is ARGB32_Premultiplied. - * - * @internal - */ - Surface(uint32_t *buffer, size_t width, size_t height, size_t bytesPerLine); - - /** - * @brief Sets the Draw Area available on the Surface. - * - * Lottie will use the draw region size to generate frame image - * and will update only the draw rgion of the surface. - * - * @param[in] x region area x position. - * @param[in] y region area y position. - * @param[in] width region area width. - * @param[in] height region area height. - * - * @note Default surface format is ARGB32_Premultiplied. - * @note Default draw region area is [ 0 , 0, surface width , surface height] - * - * @internal - */ - void setDrawRegion(size_t x, size_t y, size_t width, size_t height); - - /** - * @brief Returns width of the surface. - * - * @return surface width - * - * @internal - * - */ - size_t width() const {return mWidth;} - - /** - * @brief Returns height of the surface. - * - * @return surface height - * - * @internal - */ - size_t height() const {return mHeight;} - - /** - * @brief Returns number of bytes in the surface scanline. - * - * @return number of bytes in scanline. - * - * @internal - */ - size_t bytesPerLine() const {return mBytesPerLine;} - - /** - * @brief Returns buffer attached tp the surface. - * - * @return buffer attaced to the Surface. - * - * @internal - */ - uint32_t *buffer() const {return mBuffer;} - - /** - * @brief Returns drawable area width of the surface. - * - * @return drawable area width - * - * @note Default value is width() of the surface - * - * @internal - * - */ - size_t drawRegionWidth() const {return mDrawArea.w;} - - /** - * @brief Returns drawable area height of the surface. - * - * @return drawable area height - * - * @note Default value is height() of the surface - * - * @internal - */ - size_t drawRegionHeight() const {return mDrawArea.h;} - - /** - * @brief Returns drawable area's x position of the surface. - * - * @return drawable area's x potition. - * - * @note Default value is 0 - * - * @internal - */ - size_t drawRegionPosX() const {return mDrawArea.x;} - - /** - * @brief Returns drawable area's y position of the surface. - * - * @return drawable area's y potition. - * - * @note Default value is 0 - * - * @internal - */ - size_t drawRegionPosY() const {return mDrawArea.y;} - - /** - * @brief Default constructor. - */ - Surface() = default; -private: - uint32_t *mBuffer{nullptr}; - size_t mWidth{0}; - size_t mHeight{0}; - size_t mBytesPerLine{0}; - struct { - size_t x{0}; - size_t y{0}; - size_t w{0}; - size_t h{0}; - }mDrawArea; -}; - -using MarkerList = std::vector>; -/** - * @brief https://helpx.adobe.com/after-effects/using/layer-markers-composition-markers.html - * Markers exported form AE are used to describe a segmnet of an animation {comment/tag , startFrame, endFrame} - * Marker can be use to devide a resource in to separate animations by tagging the segment with comment string , - * start frame and duration of that segment. - */ - -using LayerInfoList = std::vector>; - - -class RLOTTIE_API Animation { -public: - /** - * @brief Constructs an animation object from JSON string data. - * - * @param[in] jsonData The JSON string data. - * @param[in] key the string that will be used to cache the JSON string data. - * @param[in] resourcePath the path will be used to search for external resource. - * @param[in] cachePolicy whether to cache or not the model data. - * use only when need to explicit disabl caching for a - * particular resource. To disable caching at library level - * use @see configureModelCacheSize() instead. - * - * @return Animation object that can render the contents of the - * Lottie resource represented by JSON string data. - * - * @internal - */ - static std::unique_ptr - loadFromData(const char* jsonData, internal::ColorReplace *colorReplacement = nullptr, const std::string &resourcePath = std::string()); - - /** - * @brief Returns default framerate of the Lottie resource. - * - * @return framerate of the Lottie resource - * - * @internal - * - */ - double frameRate() const; - - /** - * @brief Returns total number of frames present in the Lottie resource. - * - * @return frame count of the Lottie resource. - * - * @note frame number starts with 0. - * - * @internal - */ - size_t totalFrame() const; - - /** - * @brief Returns default viewport size of the Lottie resource. - * - * @param[out] width default width of the viewport. - * @param[out] height default height of the viewport. - * - * @internal - * - */ - void size(size_t &width, size_t &height) const; - - /** - * @brief Returns total animation duration of Lottie resource in second. - * it uses totalFrame() and frameRate() to calculate the duration. - * duration = totalFrame() / frameRate(). - * - * @return total animation duration in second. - * @retval 0 if the Lottie resource has no animation. - * - * @see totalFrame() - * @see frameRate() - * - * @internal - */ - double duration() const; - - /** - * @brief Returns frame number for a given position. - * this function helps to map the position value retuned - * by the animator to a frame number in side the Lottie resource. - * frame_number = lerp(start_frame, endframe, pos); - * - * @param[in] pos normalized position value [0 ... 1] - * - * @return frame numer maps to the position value [startFrame .... endFrame] - * - * @internal - */ - size_t frameAtPos(double pos); - - /** - * @brief Renders the content to surface synchronously. - * for performance use the async rendering @see render - * - * @param[in] frameNo Content corresponds to the @p frameNo needs to be drawn - * @param[in] surface Surface in which content will be drawn - * - * @internal - */ - void renderSync(size_t frameNo, Surface &surface, bool clear, bool* result = nullptr); - - /** - * @brief Returns root layer of the composition updated with - * content of the Lottie resource at frame number @p frameNo. - * - * @param[in] frameNo Content corresponds to the @p frameNo needs to be extracted. - * @param[in] width content viewbox width - * @param[in] height content viewbox height - * - * @return Root layer node. - * - * @internal - */ - const LOTLayerNode * renderTree(size_t frameNo, size_t width, size_t height) const; - - /** - * @brief Returns Composition Markers. - * - * - * @return returns MarkerList of the Composition. - * - * @see MarkerList - * @internal - */ - const MarkerList& markers() const; - - /** - * @brief Returns Layer information{name, inFrame, outFrame} of all the child layers of the composition. - * - * - * @return List of Layer Information of the Composition. - * - * @see LayerInfoList - * @internal - */ - const LayerInfoList& layers() const; - - /** - * @brief Sets property value for the specified {@link KeyPath}. This {@link KeyPath} can resolve - * to multiple contents. In that case, the callback's value will apply to all of them. - * - * Keypath should conatin object names separated by (.) and can handle globe(**) or wildchar(*). - * - * @usage - * To change fillcolor property of fill1 object in the layer1->group1->fill1 hirarchy to RED color - * - * player->setValue("layer1.group1.fill1", rlottie::Color(1, 0, 0); - * - * if all the color property inside group1 needs to be changed to GREEN color - * - * player->setValue("**.group1.**", rlottie::Color(0, 1, 0); - * - * @internal - */ - template - void setValue(const std::string &keypath, AnyValue value) - { - setValue(MapType>{}, prop, keypath, value); - } - - /** - * @brief default destructor - * - * @internal - */ - ~Animation(); - - internal::ColorReplace *colorMap{nullptr}; - void resetCurrentFrame(); -private: - void setValue(Color_Type, Property, const std::string &, Color); - void setValue(Float_Type, Property, const std::string &, float); - void setValue(Size_Type, Property, const std::string &, Size); - void setValue(Point_Type, Property, const std::string &, Point); - - void setValue(Color_Type, Property, const std::string &, std::function &&); - void setValue(Float_Type, Property, const std::string &, std::function &&); - void setValue(Size_Type, Property, const std::string &, std::function &&); - void setValue(Point_Type, Property, const std::string &, std::function &&); - /** - * @brief default constructor - * - * @internal - */ - Animation(); - - std::unique_ptr d; -}; - -//Map Property to Value type -template<> struct MapType>: Color_Type{}; -template<> struct MapType>: Color_Type{}; -template<> struct MapType>: Float_Type{}; -template<> struct MapType>: Float_Type{}; -template<> struct MapType>: Float_Type{}; -template<> struct MapType>: Float_Type{}; -template<> struct MapType>: Float_Type{}; -template<> struct MapType>: Point_Type{}; -template<> struct MapType>: Point_Type{}; -template<> struct MapType>: Size_Type{}; -template<> struct MapType>: Float_Type{}; -template<> struct MapType>: Point_Type{}; -} // namespace lotplayer - -#endif // _RLOTTIE_H_ diff --git a/libfenrir/src/main/jni/animation/rlottie/inc/rlottiecommon.h b/libfenrir/src/main/jni/animation/rlottie/inc/rlottiecommon.h deleted file mode 100644 index c5671a3b6..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/inc/rlottiecommon.h +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef _RLOTTIE_COMMON_H_ -#define _RLOTTIE_COMMON_H_ - -#define RLOTTIE_API - -/** - * @defgroup Lottie_Animation Lottie_Animation - * - * Lottie Animation is a modern style vector based animation design. Its animation - * resource(within json format) could be generated by Adobe After Effect using - * bodymovin plugin. You can find a good examples in Lottie Community which - * shares many free resources(see: www.lottiefiles.com). - * - * This Lottie_Animation is a common engine to manipulate, control Lottie - * Animation from the Lottie resource - json file. It provides a scene-graph - * node tree per frames by user demand as well as rasterized frame images. - * - */ - -/** - * @ingroup Lottie_Animation - */ - -typedef enum -{ - BrushSolid = 0, - BrushGradient -} LOTBrushType; - -typedef enum -{ - FillEvenOdd = 0, - FillWinding -} LOTFillRule; - -typedef enum -{ - JoinMiter = 0, - JoinBevel, - JoinRound -} LOTJoinStyle; - -typedef enum -{ - CapFlat = 0, - CapSquare, - CapRound -} LOTCapStyle; - -typedef enum -{ - GradientLinear = 0, - GradientRadial -} LOTGradientType; - -typedef struct LOTGradientStop -{ - float pos; - unsigned char r, g, b, a; -} LOTGradientStop; - -typedef enum -{ - MaskAdd = 0, - MaskSubstract, - MaskIntersect, - MaskDifference -} LOTMaskType; - -typedef struct LOTMask { - struct { - const float *ptPtr; - size_t ptCount; - const char* elmPtr; - size_t elmCount; - } mPath; - LOTMaskType mMode; - unsigned char mAlpha; -}LOTMask; - -typedef enum -{ - MatteNone = 0, - MatteAlpha, - MatteAlphaInv, - MatteLuma, - MatteLumaInv -} LOTMatteType; - -typedef struct LOTMarker { - char *name; - size_t startframe; - size_t endframe; -} LOTMarker; - -typedef struct LOTMarkerList { - LOTMarker *ptr; - size_t size; -} LOTMarkerList; - -typedef struct LOTNode { - -#define ChangeFlagNone 0x0000 -#define ChangeFlagPath 0x0001 -#define ChangeFlagPaint 0x0010 -#define ChangeFlagAll (ChangeFlagPath & ChangeFlagPaint) - - struct { - const float *ptPtr; - size_t ptCount; - const char *elmPtr; - size_t elmCount; - } mPath; - - struct { - unsigned char r, g, b, a; - } mColor; - - struct { - unsigned char enable; - float width; - LOTCapStyle cap; - LOTJoinStyle join; - float miterLimit; - float *dashArray; - int dashArraySize; - } mStroke; - - struct { - LOTGradientType type; - LOTGradientStop *stopPtr; - size_t stopCount; - struct { - float x, y; - } start, end, center, focal; - float cradius; - float fradius; - } mGradient; - - struct { - unsigned char *data; - size_t width; - size_t height; - unsigned char mAlpha; - struct { - float m11; float m12; float m13; - float m21; float m22; float m23; - float m31; float m32; float m33; - } mMatrix; - } mImageInfo; - - int mFlag; - LOTBrushType mBrushType; - LOTFillRule mFillRule; - - const char *keypath; -} LOTNode; - - - -typedef struct LOTLayerNode { - - struct { - LOTMask *ptr; - size_t size; - } mMaskList; - - struct { - const float *ptPtr; - size_t ptCount; - const char *elmPtr; - size_t elmCount; - } mClipPath; - - struct { - struct LOTLayerNode **ptr; - size_t size; - } mLayerList; - - struct { - LOTNode **ptr; - size_t size; - } mNodeList; - - LOTMatteType mMatte; - int mVisible; - unsigned char mAlpha; - const char *keypath; - -} LOTLayerNode; - -/** - * @} - */ - -#endif // _RLOTTIE_COMMON_H_ diff --git a/libfenrir/src/main/jni/animation/rlottie/rlottie-master.zip b/libfenrir/src/main/jni/animation/rlottie/rlottie-master.zip deleted file mode 100644 index 033b9258a..000000000 Binary files a/libfenrir/src/main/jni/animation/rlottie/rlottie-master.zip and /dev/null differ diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottieanimation.cpp b/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottieanimation.cpp deleted file mode 100644 index a82ce741a..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottieanimation.cpp +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#include "config.h" -#include "lottieitem.h" -#include "lottiemodel.h" -#include "rlottie.h" -#include "colorreplace.h" - -#include - -using namespace rlottie; -using namespace rlottie::internal; - -class AnimationImpl { -public: - void init(const std::shared_ptr& composition); - bool update(size_t frameNo, const VSize &size); - VSize size() const { return mModel->size(); } - double duration() const { return mModel->duration(); } - double frameRate() const { return mModel->frameRate(); } - size_t totalFrame() const { return mModel->totalFrame(); } - size_t frameAtPos(double pos) const { return mModel->frameAtPos(pos); } - Surface render(size_t frameNo, const Surface &surface, bool clear, bool* result); - const LOTLayerNode * renderTree(size_t frameNo, const VSize &size); - - const LayerInfoList &layerInfoList() const - { - if (mLayerList.empty()) { - mLayerList = mModel->layerInfoList(); - } - return mLayerList; - } - const MarkerList &markers() const { return mModel->markers(); } - void setValue(const std::string &keypath, LOTVariant &&value); - void resetCurrentFrame(); - -private: - mutable LayerInfoList mLayerList; - model::Composition * mModel{nullptr}; - std::atomic mRenderInProgress{false}; - std::unique_ptr mRenderer{nullptr}; -}; - -void AnimationImpl::setValue(const std::string &keypath, LOTVariant &&value) -{ - if (keypath.empty()) return; - mRenderer->setValue(keypath, value); -} - -void AnimationImpl::resetCurrentFrame() { - mRenderer->resetCurrentFrame(); -} - -const LOTLayerNode *AnimationImpl::renderTree(size_t frameNo, const VSize &size) -{ - if (update(frameNo, size)) { - mRenderer->buildRenderTree(); - } - return mRenderer->renderTree(); -} - -bool AnimationImpl::update(size_t frameNo, const VSize &size) -{ - frameNo += mModel->startFrame(); - - if (frameNo > mModel->endFrame()) frameNo = mModel->endFrame(); - - if (frameNo < mModel->startFrame()) frameNo = mModel->startFrame(); - - return mRenderer->update(int(frameNo), size); -} - -Surface AnimationImpl::render(size_t frameNo, const Surface &surface, bool clear, bool* result) -{ - bool renderInProgress = mRenderInProgress.load(); - if (renderInProgress) { - vCritical << "Already Rendering Scheduled for this Animation"; - if(result != nullptr) { - *result = false; - } - return surface; - } - - mRenderInProgress.store(true); - update( - frameNo, - VSize(int(surface.drawRegionWidth()), int(surface.drawRegionHeight()))); - mRenderer->render(surface, clear); - mRenderInProgress.store(false); - if(result != nullptr) { - *result = true; - } - - return surface; -} - -void AnimationImpl::init(const std::shared_ptr& composition) -{ - mModel = composition.get(); - mRenderer = std::make_unique(composition); - mRenderInProgress = false; -} - - -/** - * \breif Brief abput the Api. - * Description about the setFilePath Api - * @param path add the details - */ -std::unique_ptr Animation::loadFromData( - const char* jsonData, - ColorReplace *colorReplacement, - const std::string &resourcePath) -{ - if (*jsonData == '\0') { - vWarning << "jason data is empty"; - return nullptr; - } - - auto composition = model::loadFromData(jsonData, - colorReplacement, resourcePath); - if (composition) { - auto animation = std::unique_ptr(new Animation); - animation->colorMap = colorReplacement; - animation->d->init(composition); - return animation; - } - delete colorReplacement; - - return nullptr; -} - -void Animation::size(size_t &width, size_t &height) const -{ - VSize sz = d->size(); - - width = sz.width(); - height = sz.height(); -} - -double Animation::duration() const -{ - return d->duration(); -} - -double Animation::frameRate() const -{ - return d->frameRate(); -} - -size_t Animation::totalFrame() const -{ - return d->totalFrame(); -} - -size_t Animation::frameAtPos(double pos) -{ - return d->frameAtPos(pos); -} - -const LOTLayerNode *Animation::renderTree(size_t frameNo, size_t width, - size_t height) const -{ - return d->renderTree(frameNo, VSize(int(width), int(height))); -} - -void Animation::renderSync(size_t frameNo, Surface &surface, bool clear, bool* result) -{ - d->render(frameNo, surface, clear, result); -} - -const LayerInfoList &Animation::layers() const -{ - return d->layerInfoList(); -} - -const MarkerList &Animation::markers() const -{ - return d->markers(); -} - -void Animation::setValue(Color_Type, Property prop, const std::string &keypath, - Color value) -{ - d->setValue(keypath, - LOTVariant(prop, [value](const FrameInfo &) { return value; })); -} - -void Animation::setValue(Float_Type, Property prop, const std::string &keypath, - float value) -{ - d->setValue(keypath, - LOTVariant(prop, [value](const FrameInfo &) { return value; })); -} - -void Animation::setValue(Size_Type, Property prop, const std::string &keypath, - Size value) -{ - d->setValue(keypath, - LOTVariant(prop, [value](const FrameInfo &) { return value; })); -} - -void Animation::setValue(Point_Type, Property prop, const std::string &keypath, - Point value) -{ - d->setValue(keypath, - LOTVariant(prop, [value](const FrameInfo &) { return value; })); -} - -void Animation::setValue(Color_Type, Property prop, const std::string &keypath, - std::function &&value) -{ - d->setValue(keypath, LOTVariant(prop, value)); -} - -void Animation::setValue(Float_Type, Property prop, const std::string &keypath, - std::function &&value) -{ - d->setValue(keypath, LOTVariant(prop, value)); -} - -void Animation::setValue(Size_Type, Property prop, const std::string &keypath, - std::function &&value) -{ - d->setValue(keypath, LOTVariant(prop, value)); -} - -void Animation::setValue(Point_Type, Property prop, const std::string &keypath, - std::function &&value) -{ - d->setValue(keypath, LOTVariant(prop, value)); -} - -Animation::Animation() : d(std::make_unique()) {} - -/* - * this is only to supress build fail - * because unique_ptr expects the destructor in the same translation unit. - */ -Animation::~Animation() { - if (colorMap != nullptr) { - delete colorMap; - colorMap = nullptr; - } -} - -void Animation::resetCurrentFrame() { - d->resetCurrentFrame(); -} - -Surface::Surface(uint32_t *buffer, size_t width, size_t height, - size_t bytesPerLine) - : mBuffer(buffer), - mWidth(width), - mHeight(height), - mBytesPerLine(bytesPerLine) -{ - mDrawArea.w = mWidth; - mDrawArea.h = mHeight; -} - -void Surface::setDrawRegion(size_t x, size_t y, size_t width, size_t height) -{ - if ((x + width > mWidth) || (y + height > mHeight)) return; - - mDrawArea.x = x; - mDrawArea.y = y; - mDrawArea.w = width; - mDrawArea.h = height; -} - -#ifdef LOTTIE_LOGGING_SUPPORT -void initLogging() -{ -#if defined(__ARM_NEON__) || defined(__aarch64__) - set_log_level(LogLevel::OFF); -#else - initialize(GuaranteedLogger(), "/tmp/", "rlottie", 1); - set_log_level(LogLevel::INFO); -#endif -} - -V_CONSTRUCTOR_FUNCTION(initLogging) -#endif diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottiefiltermodel.h b/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottiefiltermodel.h deleted file mode 100644 index b4a3c245a..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottiefiltermodel.h +++ /dev/null @@ -1,453 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef LOTTIEFILTERMODEL_H -#define LOTTIEFILTERMODEL_H - -#include -#include -#include -#include "lottiemodel.h" -#include "rlottie.h" - -using namespace rlottie::internal; -// Naive way to implement std::variant -// refactor it when we move to c++17 -// users should make sure proper combination -// of id and value are passed while creating the object. -class LOTVariant { -public: - using ValueFunc = std::function; - using ColorFunc = std::function; - using PointFunc = std::function; - using SizeFunc = std::function; - - LOTVariant(rlottie::Property prop, const ValueFunc& v) - : mPropery(prop), mTag(Value) - { - construct(impl.valueFunc, v); - } - - LOTVariant(rlottie::Property prop, ValueFunc&& v) - : mPropery(prop), mTag(Value) - { - moveConstruct(impl.valueFunc, std::move(v)); - } - - LOTVariant(rlottie::Property prop, const ColorFunc& v) - : mPropery(prop), mTag(Color) - { - construct(impl.colorFunc, v); - } - - LOTVariant(rlottie::Property prop, ColorFunc&& v) - : mPropery(prop), mTag(Color) - { - moveConstruct(impl.colorFunc, std::move(v)); - } - - LOTVariant(rlottie::Property prop, const PointFunc& v) - : mPropery(prop), mTag(Point) - { - construct(impl.pointFunc, v); - } - - LOTVariant(rlottie::Property prop, PointFunc&& v) - : mPropery(prop), mTag(Point) - { - moveConstruct(impl.pointFunc, std::move(v)); - } - - LOTVariant(rlottie::Property prop, const SizeFunc& v) - : mPropery(prop), mTag(Size) - { - construct(impl.sizeFunc, v); - } - - LOTVariant(rlottie::Property prop, SizeFunc&& v) - : mPropery(prop), mTag(Size) - { - moveConstruct(impl.sizeFunc, std::move(v)); - } - - rlottie::Property property() const { return mPropery; } - - const ColorFunc& color() const - { - assert(mTag == Color); - return impl.colorFunc; - } - - const ValueFunc& value() const - { - assert(mTag == Value); - return impl.valueFunc; - } - - const PointFunc& point() const - { - assert(mTag == Point); - return impl.pointFunc; - } - - const SizeFunc& size() const - { - assert(mTag == Size); - return impl.sizeFunc; - } - - LOTVariant() = default; - ~LOTVariant() noexcept { Destroy(); } - LOTVariant(const LOTVariant& other) { Copy(other); } - LOTVariant(LOTVariant&& other) noexcept { Move(std::move(other)); } - LOTVariant& operator=(LOTVariant&& other) - { - Destroy(); - Move(std::move(other)); - return *this; - } - LOTVariant& operator=(const LOTVariant& other) - { - Destroy(); - Copy(other); - return *this; - } - -private: - template - void construct(T& member, const T& val) - { - new (&member) T(val); - } - - template - void moveConstruct(T& member, T&& val) - { - new (&member) T(std::move(val)); - } - - void Move(LOTVariant&& other) - { - switch (other.mTag) { - case Type::Value: - moveConstruct(impl.valueFunc, std::move(other.impl.valueFunc)); - break; - case Type::Color: - moveConstruct(impl.colorFunc, std::move(other.impl.colorFunc)); - break; - case Type::Point: - moveConstruct(impl.pointFunc, std::move(other.impl.pointFunc)); - break; - case Type::Size: - moveConstruct(impl.sizeFunc, std::move(other.impl.sizeFunc)); - break; - default: - break; - } - mTag = other.mTag; - mPropery = other.mPropery; - other.mTag = MonoState; - } - - void Copy(const LOTVariant& other) - { - switch (other.mTag) { - case Type::Value: - construct(impl.valueFunc, other.impl.valueFunc); - break; - case Type::Color: - construct(impl.colorFunc, other.impl.colorFunc); - break; - case Type::Point: - construct(impl.pointFunc, other.impl.pointFunc); - break; - case Type::Size: - construct(impl.sizeFunc, other.impl.sizeFunc); - break; - default: - break; - } - mTag = other.mTag; - mPropery = other.mPropery; - } - - void Destroy() - { - switch (mTag) { - case MonoState: { - break; - } - case Value: { - impl.valueFunc.~ValueFunc(); - break; - } - case Color: { - impl.colorFunc.~ColorFunc(); - break; - } - case Point: { - impl.pointFunc.~PointFunc(); - break; - } - case Size: { - impl.sizeFunc.~SizeFunc(); - break; - } - } - } - - enum Type { MonoState, Value, Color, Point, Size }; - rlottie::Property mPropery; - Type mTag{MonoState}; - union details { - ColorFunc colorFunc; - ValueFunc valueFunc; - PointFunc pointFunc; - SizeFunc sizeFunc; - details() {} - ~details() noexcept {} - } impl; -}; - -namespace rlottie { - -namespace internal { - -namespace model { - -class FilterData { -public: - void addValue(LOTVariant& value) - { - uint32_t index = static_cast(value.property()); - if (mBitset.test(index)) { - std::replace_if(mFilters.begin(), mFilters.end(), - [&value](const LOTVariant& e) { - return e.property() == value.property(); - }, - value); - } else { - mBitset.set(index); - mFilters.push_back(value); - } - } - - void removeValue(LOTVariant& value) - { - uint32_t index = static_cast(value.property()); - if (mBitset.test(index)) { - mBitset.reset(index); - mFilters.erase(std::remove_if(mFilters.begin(), mFilters.end(), - [&value](const LOTVariant& e) { - return e.property() == - value.property(); - }), - mFilters.end()); - } - } - bool hasFilter(rlottie::Property prop) const - { - return mBitset.test(static_cast(prop)); - } - model::Color color(rlottie::Property prop, int frame) const - { - rlottie::FrameInfo info(frame); - rlottie::Color col = data(prop).color()(info); - return model::Color(col.r(), col.g(), col.b()); - } - VPointF point(rlottie::Property prop, int frame) const - { - rlottie::FrameInfo info(frame); - rlottie::Point pt = data(prop).point()(info); - return VPointF(pt.x(), pt.y()); - } - VSize scale(rlottie::Property prop, int frame) const - { - rlottie::FrameInfo info(frame); - rlottie::Size sz = data(prop).size()(info); - return VSize(sz.w(), sz.h()); - } - float opacity(rlottie::Property prop, int frame) const - { - rlottie::FrameInfo info(frame); - float val = data(prop).value()(info); - return val / 100; - } - float value(rlottie::Property prop, int frame) const - { - rlottie::FrameInfo info(frame); - return data(prop).value()(info); - } - -private: - const LOTVariant& data(rlottie::Property prop) const - { - auto result = std::find_if( - mFilters.begin(), mFilters.end(), - [prop](const LOTVariant& e) { return e.property() == prop; }); - return *result; - } - std::bitset<32> mBitset{0}; - std::vector mFilters; -}; - -template -struct FilterBase -{ - FilterBase(T *model): model_(model){} - - const char* name() const { return model_->name(); } - - FilterData* filter() { - if (!filterData_) filterData_ = std::make_unique(); - return filterData_.get(); - } - - const FilterData * filter() const { return filterData_.get(); } - const T* model() const { return model_;} - - bool hasFilter(rlottie::Property prop) const { - return filterData_ && filterData_->hasFilter(prop); - } - - T* model_{nullptr}; - std::unique_ptr filterData_{nullptr}; -}; - - -template -class Filter : public FilterBase { -public: - Filter(T* model): FilterBase(model){} - model::Color color(int frame) const - { - if (this->hasFilter(rlottie::Property::StrokeColor)) { - return this->filter()->color(rlottie::Property::StrokeColor, frame); - } - return this->model()->color(frame); - } - float opacity(int frame) const - { - if (this->hasFilter(rlottie::Property::StrokeOpacity)) { - return this->filter()->opacity(rlottie::Property::StrokeOpacity, frame); - } - return this->model()->opacity(frame); - } - - float strokeWidth(int frame) const - { - if (this->hasFilter(rlottie::Property::StrokeWidth)) { - return this->filter()->value(rlottie::Property::StrokeWidth, frame); - } - return this->model()->strokeWidth(frame); - } - - float miterLimit() const { return this->model()->miterLimit(); } - CapStyle capStyle() const { return this->model()->capStyle(); } - JoinStyle joinStyle() const { return this->model()->joinStyle(); } - bool hasDashInfo() const { return this->model()->hasDashInfo(); } - void getDashInfo(int frameNo, std::vector& result) const - { - return this->model()->getDashInfo(frameNo, result); - } -}; - - -template <> -class Filter: public FilterBase -{ -public: - Filter(model::Fill* model) : FilterBase(model) {} - - model::Color color(int frame) const - { - if (this->hasFilter(rlottie::Property::FillColor)) { - return this->filter()->color(rlottie::Property::FillColor, frame); - } - return this->model()->color(frame); - } - - float opacity(int frame) const - { - if (this->hasFilter(rlottie::Property::FillOpacity)) { - return this->filter()->opacity(rlottie::Property::FillOpacity, frame); - } - return this->model()->opacity(frame); - } - - FillRule fillRule() const { return this->model()->fillRule(); } -}; - -template <> -class Filter : public FilterBase -{ -public: - Filter(model::Group* model = nullptr) : FilterBase(model) {} - - bool hasModel() const { return this->model() != nullptr; } - - model::Transform* transform() const { return this->model() ? this->model()->mTransform : nullptr; } - VMatrix matrix(int frame) const - { - VMatrix mS, mR, mT; - if (this->hasFilter(rlottie::Property::TrScale)) { - VSize s = this->filter()->scale(rlottie::Property::TrScale, frame); - mS.scale(s.width() / 100.0, s.height() / 100.0); - } - if (this->hasFilter(rlottie::Property::TrRotation)) { - mR.rotate(this->filter()->value(rlottie::Property::TrRotation, frame)); - } - if (this->hasFilter(rlottie::Property::TrPosition)) { - mT.translate(this->filter()->point(rlottie::Property::TrPosition, frame)); - } - - return this->model()->mTransform->matrix(frame) * mS * mR * mT; - } -}; - -template <> -class Filter: public FilterBase -{ -public: - Filter(model::Trim* model) : FilterBase(model) {} - - model::Trim::Segment segment(int frameNo) const - { - if (this->hasFilter(rlottie::Property::TrimStart)) { - this->model_->updateTrimStartValue(this->filter()->value(rlottie::Property::TrimStart, frameNo)); - } - if (this->hasFilter(rlottie::Property::TrimEnd)) { - this->model_->updateTrimEndValue(this->filter()->point(rlottie::Property::TrimEnd, frameNo)); - } - model::Trim::Segment segment = this->model()->segment(frameNo); - return segment; - } -}; - - -} // namespace model - -} // namespace internal - -} // namespace rlottie - -#endif // LOTTIEFILTERMODEL_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottieitem.cpp b/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottieitem.cpp deleted file mode 100644 index bbeab5a8a..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottieitem.cpp +++ /dev/null @@ -1,1558 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "lottieitem.h" -#include "lottiemodel.h" -#include -#include -#include -#include "lottiekeypath.h" -#include "vbitmap.h" -#include "vpainter.h" -#include "vraster.h" - -/* Lottie Layer Rules - * 1. time stretch is pre calculated and applied to all the properties of the - * lottilayer model and all its children - * 2. The frame property could be reversed using,time-reverse layer property in - * AE. which means (start frame > endFrame) 3. - */ - -static bool transformProp(rlottie::Property prop) -{ - switch (prop) { - case rlottie::Property::TrAnchor: - case rlottie::Property::TrScale: - case rlottie::Property::TrOpacity: - case rlottie::Property::TrPosition: - case rlottie::Property::TrRotation: - return true; - default: - return false; - } -} -static bool fillProp(rlottie::Property prop) -{ - switch (prop) { - case rlottie::Property::FillColor: - case rlottie::Property::FillOpacity: - return true; - default: - return false; - } -} - -static bool strokeProp(rlottie::Property prop) -{ - switch (prop) { - case rlottie::Property::StrokeColor: - case rlottie::Property::StrokeOpacity: - case rlottie::Property::StrokeWidth: - return true; - default: - return false; - } -} - -static bool trimProp(rlottie::Property prop) -{ - switch (prop) { - case rlottie::Property::TrimStart: - case rlottie::Property::TrimEnd: - return true; - default: - return false; - } -} - -static renderer::Layer *createLayerItem(model::Layer *layerData, - VArenaAlloc * allocator) -{ - switch (layerData->mLayerType) { - case model::Layer::Type::Precomp: { - return allocator->make(layerData, allocator); - } - case model::Layer::Type::Solid: { - return allocator->make(layerData); - } - case model::Layer::Type::Shape: { - return allocator->make(layerData, allocator); - } - case model::Layer::Type::Null: { - return allocator->make(layerData); - } - case model::Layer::Type::Image: { - return allocator->make(layerData); - } - default: - return nullptr; - break; - } -} - -void renderer::Composition::resetCurrentFrame() -{ - mCurFrameNo = -1; -} - -renderer::Composition::Composition(std::shared_ptr model) - : mCurFrameNo(-1) -{ - mModel = std::move(model); - mRootLayer = createLayerItem(mModel->mRootLayer, &mAllocator); - mRootLayer->setComplexContent(false); - mViewSize = mModel->size(); -} - -void renderer::Composition::setValue(const std::string &keypath, - LOTVariant & value) -{ - mHasDynamicValue = true; - LOTKeyPath key(keypath); - mRootLayer->resolveKeyPath(key, 0, value); -} - -bool renderer::Composition::update(int frameNo, const VSize &size) -{ - // check if cached frame is same as requested frame. - if (!mHasDynamicValue && (mViewSize == size) && (mCurFrameNo == frameNo)) - return false; - - mViewSize = size; - mCurFrameNo = frameNo; - - /* - * if viewbox dosen't scale exactly to the viewport - * we scale the viewbox keeping AspectRatioPreserved and then align the - * viewbox to the viewport using AlignCenter rule. - */ - VMatrix m; - VSize viewPort = mViewSize; - VSize viewBox = mModel->size(); - float sx = float(viewPort.width()) / viewBox.width(); - float sy = float(viewPort.height()) / viewBox.height(); - if (mKeepAspectRatio) { - float scale = std::min(sx, sy); - float tx = (viewPort.width() - viewBox.width() * scale) * 0.5f; - float ty = (viewPort.height() - viewBox.height() * scale) * 0.5f; - m.translate(tx, ty).scale(scale, scale); - } else { - m.scale(sx, sy); - } - mRootLayer->update(frameNo, m, 1.0); - return true; -} - -bool renderer::Composition::render(const rlottie::Surface &surface, bool clear) -{ - mSurface.reset(reinterpret_cast(surface.buffer()), - uint32_t(surface.width()), uint32_t(surface.height()), - uint32_t(surface.bytesPerLine()), - VBitmap::Format::ARGB32_Premultiplied); - - /* schedule all preprocess task for this frame at once. - */ - VRect clip(0, 0, int(surface.drawRegionWidth()), - int(surface.drawRegionHeight())); - mRootLayer->preprocess(clip); - - VPainter painter(&mSurface, clear); - // set sub surface area for drawing. - painter.setDrawRegion( - VRect(int(surface.drawRegionPosX()), int(surface.drawRegionPosY()), - int(surface.drawRegionWidth()), int(surface.drawRegionHeight()))); - mRootLayer->render(&painter, {}, {}, mSurfaceCache); - painter.end(); - return true; -} - -void renderer::Mask::update(int frameNo, const VMatrix &parentMatrix, - float /*parentAlpha*/, const DirtyFlag &flag) -{ - bool dirtyPath = false; - - if (flag.testFlag(DirtyFlagBit::None) && mData->isStatic()) return; - - if (mData->mShape.isStatic()) { - if (mLocalPath.empty()) { - dirtyPath = true; - mData->mShape.value(frameNo, mLocalPath); - } - } else { - dirtyPath = true; - mData->mShape.value(frameNo, mLocalPath); - } - /* mask item dosen't inherit opacity */ - mCombinedAlpha = mData->opacity(frameNo); - - if ( flag.testFlag(DirtyFlagBit::Matrix) || dirtyPath ) { - mFinalPath.clone(mLocalPath); - mFinalPath.transform(parentMatrix); - mRasterRequest = true; - } -} - -VRle renderer::Mask::rle() -{ - if (!vCompare(mCombinedAlpha, 1.0f)) { - VRle obj = mRasterizer.rle(); - obj *= uint8_t(mCombinedAlpha * 255); - return obj; - } else { - return mRasterizer.rle(); - } -} - -void renderer::Mask::preprocess(const VRect &clip) -{ - if (mRasterRequest) - mRasterizer.rasterize(mFinalPath, FillRule::Winding, clip); -} - -void renderer::Layer::render(VPainter *painter, const VRle &inheritMask, - const VRle &matteRle, SurfaceCache &) -{ - auto renderlist = renderList(); - - if (renderlist.empty()) return; - - VRle mask; - if (mLayerMask) { - mask = mLayerMask->maskRle(painter->clipBoundingRect()); - if (!inheritMask.empty()) mask = mask & inheritMask; - // if resulting mask is empty then return. - if (mask.empty()) return; - } else { - mask = inheritMask; - } - - for (auto &i : renderlist) { - painter->setBrush(i->mBrush); - VRle rle = i->rle(); - if (matteRle.empty()) { - if (mask.empty()) { - // no mask no matte - painter->drawRle(VPoint(), rle); - } else { - // only mask - painter->drawRle(rle, mask); - } - - } else { - if (!mask.empty()) rle = rle & mask; - - if (rle.empty()) continue; - if (matteType() == model::MatteType::AlphaInv) { - rle = rle - matteRle; - painter->drawRle(VPoint(), rle); - } else { - // render with matteRle as clip. - painter->drawRle(rle, matteRle); - } - } - } -} - -void renderer::LayerMask::preprocess(const VRect &clip) -{ - for (auto &i : mMasks) { - i.preprocess(clip); - } -} - -renderer::LayerMask::LayerMask(model::Layer *layerData) -{ - if (!layerData->mExtra) return; - - mMasks.reserve(layerData->mExtra->mMasks.size()); - - for (auto &i : layerData->mExtra->mMasks) { - mMasks.emplace_back(i); - mStatic &= i->isStatic(); - } -} - -void renderer::LayerMask::update(int frameNo, const VMatrix &parentMatrix, - float parentAlpha, const DirtyFlag &flag) -{ - if (flag.testFlag(DirtyFlagBit::None) && isStatic()) return; - - for (auto &i : mMasks) { - i.update(frameNo, parentMatrix, parentAlpha, flag); - } - mDirty = true; -} - -VRle renderer::LayerMask::maskRle(const VRect &clipRect) -{ - if (!mDirty) return mRle; - - VRle rle; - for (auto &e : mMasks) { - const auto cur = [&]() { - if (e.inverted()) - return clipRect - e.rle(); - else - return e.rle(); - }(); - - switch (e.maskMode()) { - case model::Mask::Mode::Add: { - rle = rle + cur; - break; - } - case model::Mask::Mode::Substarct: { - if (rle.empty() && !clipRect.empty()) - rle = clipRect - cur; - else - rle = rle - cur; - break; - } - case model::Mask::Mode::Intersect: { - if (rle.empty() && !clipRect.empty()) - rle = clipRect & cur; - else - rle = rle & cur; - break; - } - case model::Mask::Mode::Difference: { - rle = rle ^ cur; - break; - } - default: - break; - } - } - - if (!rle.empty() && !rle.unique()) { - mRle.clone(rle); - } else { - mRle = rle; - } - mDirty = false; - return mRle; -} - -renderer::Layer::Layer(model::Layer *layerData) : mLayerData(layerData) -{ - if (mLayerData->mHasMask) - mLayerMask = std::make_unique(mLayerData); -} - -bool renderer::Layer::resolveKeyPath(LOTKeyPath &keyPath, uint32_t depth, - LOTVariant &value) -{ - if (!keyPath.matches(name(), depth)) { - return false; - } - - if (!keyPath.skip(name())) { - if (keyPath.fullyResolvesTo(name(), depth) && - transformProp(value.property())) { - //@TODO handle propery update. - } - } - return true; -} - -bool renderer::ShapeLayer::resolveKeyPath(LOTKeyPath &keyPath, uint32_t depth, - LOTVariant &value) -{ - if (renderer::Layer::resolveKeyPath(keyPath, depth, value)) { - if (keyPath.propagate(name(), depth)) { - uint32_t newDepth = keyPath.nextDepth(name(), depth); - mRoot->resolveKeyPath(keyPath, newDepth, value); - } - return true; - } - return false; -} - -bool renderer::CompLayer::resolveKeyPath(LOTKeyPath &keyPath, uint32_t depth, - LOTVariant &value) -{ - if (renderer::Layer::resolveKeyPath(keyPath, depth, value)) { - if (keyPath.propagate(name(), depth)) { - uint32_t newDepth = keyPath.nextDepth(name(), depth); - for (const auto &layer : mLayers) { - layer->resolveKeyPath(keyPath, newDepth, value); - } - } - return true; - } - return false; -} - -void renderer::Layer::update(int frameNumber, const VMatrix &parentMatrix, - float parentAlpha) -{ - mFrameNo = frameNumber; - // 1. check if the layer is part of the current frame - if (!visible()) return; - - float alpha = parentAlpha * opacity(frameNo()); - if (vIsZero(alpha)) { - mCombinedAlpha = 0; - return; - } - - // 2. calculate the parent matrix and alpha - VMatrix m = matrix(frameNo()); - m *= parentMatrix; - - // 3. update the dirty flag based on the change - if (mCombinedMatrix != m) { - mDirtyFlag |= DirtyFlagBit::Matrix; - mCombinedMatrix = m; - } - - if (!vCompare(mCombinedAlpha, alpha)) { - mDirtyFlag |= DirtyFlagBit::Alpha; - mCombinedAlpha = alpha; - } - - // 4. update the mask - if (mLayerMask) { - mLayerMask->update(frameNo(), mCombinedMatrix, mCombinedAlpha, - mDirtyFlag); - } - - // 5. if no parent property change and layer is static then nothing to do. - if (!mLayerData->precompLayer() && flag().testFlag(DirtyFlagBit::None) && - isStatic()) - return; - - // 6. update the content of the layer - updateContent(); - - // 7. reset the dirty flag - mDirtyFlag = DirtyFlagBit::None; -} - -VMatrix renderer::Layer::matrix(int frameNo) const -{ - return mParentLayer - ? (mLayerData->matrix(frameNo) * mParentLayer->matrix(frameNo)) - : mLayerData->matrix(frameNo); -} - -bool renderer::Layer::visible() const -{ - return (frameNo() >= mLayerData->inFrame() && - frameNo() <= mLayerData->outFrame()); -} - -void renderer::Layer::preprocess(const VRect &clip) -{ - // layer dosen't contribute to the frame - if (skipRendering()) return; - - // preprocess layer masks - if (mLayerMask) mLayerMask->preprocess(clip); - - preprocessStage(clip); -} - -static bool isGoodParentLayer(renderer::Layer *parent, renderer::Layer *child) { - do { - if (parent == child) { - return false; - } - parent = parent->resolvedParentLayer(); - } while (parent); - return true; -} - -renderer::CompLayer::CompLayer(model::Layer *layerModel, VArenaAlloc *allocator) - : renderer::Layer(layerModel) -{ - if (!mLayerData->mChildren.empty()) - mLayers.reserve(mLayerData->mChildren.size()); - - // 1. keep the layer in back-to-front order. - // as lottie model keeps the data in front-toback-order. - for (auto it = mLayerData->mChildren.crbegin(); - it != mLayerData->mChildren.rend(); ++it) { - if ((*it)->type() != model::Object::Type::Layer) { - continue; - } - auto model = static_cast(*it); - auto item = createLayerItem(model, allocator); - if (item) mLayers.push_back(item); - } - - // 2. update parent layer - for (const auto &layer : mLayers) { - int id = layer->parentId(); - if (id >= 0) { - auto search = - std::find_if(mLayers.begin(), mLayers.end(), - [id](const auto &val) { return val->id() == id; }); - if (search != mLayers.end() && - isGoodParentLayer(*search, layer)) { - layer->setParentLayer(*search); - } - } - } - - // 4. check if its a nested composition - if (!layerModel->layerSize().empty()) { - mClipper = std::make_unique(layerModel->layerSize()); - } - - if (mLayers.size() > 1) setComplexContent(true); -} - -void renderer::CompLayer::render(VPainter *painter, const VRle &inheritMask, - const VRle &matteRle, SurfaceCache &cache) -{ - if (vIsZero(combinedAlpha())) return; - - if (vCompare(combinedAlpha(), 1.0)) { - renderHelper(painter, inheritMask, matteRle, cache); - } else { - if (complexContent()) { - VSize size = painter->clipBoundingRect().size(); - VPainter srcPainter; - VBitmap srcBitmap = cache.make_surface(size.width(), size.height()); - srcPainter.begin(&srcBitmap, true); - renderHelper(&srcPainter, inheritMask, matteRle, cache); - srcPainter.end(); - painter->drawBitmap(VPoint(), srcBitmap, - uint8_t(combinedAlpha() * 255.0f)); - cache.release_surface(srcBitmap); - } else { - renderHelper(painter, inheritMask, matteRle, cache); - } - } -} - -void renderer::CompLayer::renderHelper(VPainter * painter, - const VRle & inheritMask, - const VRle & matteRle, - SurfaceCache &cache) -{ - VRle mask; - if (mLayerMask) { - mask = mLayerMask->maskRle(painter->clipBoundingRect()); - if (!inheritMask.empty()) mask = mask & inheritMask; - // if resulting mask is empty then return. - if (mask.empty()) return; - } else { - mask = inheritMask; - } - - if (mClipper) { - mask = mClipper->rle(mask); - if (mask.empty()) return; - } - - renderer::Layer *matte = nullptr; - for (const auto &layer : mLayers) { - if (layer->hasMatte()) { - matte = layer; - } else { - if (layer->visible()) { - if (matte) { - if (matte->visible()) - renderMatteLayer(painter, mask, matteRle, matte, layer, - cache); - } else { - layer->render(painter, mask, matteRle, cache); - } - } - matte = nullptr; - } - } -} - -void renderer::CompLayer::renderMatteLayer(VPainter *painter, const VRle &mask, - const VRle & matteRle, - renderer::Layer *layer, - renderer::Layer *src, - SurfaceCache & cache) -{ - VSize size = painter->clipBoundingRect().size(); - // Decide if we can use fast matte. - // 1. draw src layer to matte buffer - VPainter srcPainter; - VBitmap srcBitmap = cache.make_surface(size.width(), size.height()); - srcPainter.begin(&srcBitmap, true); - src->render(&srcPainter, mask, matteRle, cache); - srcPainter.end(); - - // 2. draw layer to layer buffer - VPainter layerPainter; - VBitmap layerBitmap = cache.make_surface(size.width(), size.height()); - layerPainter.begin(&layerBitmap, true); - layer->render(&layerPainter, mask, matteRle, cache); - - // 2.1update composition mode - switch (layer->matteType()) { - case model::MatteType::Alpha: - case model::MatteType::Luma: { - layerPainter.setBlendMode(BlendMode::DestIn); - break; - } - case model::MatteType::AlphaInv: - case model::MatteType::LumaInv: { - layerPainter.setBlendMode(BlendMode::DestOut); - break; - } - default: - break; - } - - // 2.2 update srcBuffer if the matte is luma type - if (layer->matteType() == model::MatteType::Luma || - layer->matteType() == model::MatteType::LumaInv) { - srcBitmap.updateLuma(); - } - - auto clip = layerPainter.clipBoundingRect(); - - // if the layer has only one renderer then use it as the clip rect - // when blending 2 buffer and copy back to final buffer to avoid - // unnecessary pixel processing. - if (layer->renderList().size() == 1) - { - clip = layer->renderList()[0]->rle().boundingRect(); - } - - // 2.3 draw src buffer as mask - layerPainter.drawBitmap(clip, srcBitmap, clip); - layerPainter.end(); - // 3. draw the result buffer into painter - painter->drawBitmap(clip, layerBitmap, clip); - - cache.release_surface(srcBitmap); - cache.release_surface(layerBitmap); -} - -void renderer::Clipper::update(const VMatrix &matrix) -{ - mPath.reset(); - mPath.addRect(VRectF(0, 0, mSize.width(), mSize.height())); - mPath.transform(matrix); - mRasterRequest = true; -} - -void renderer::Clipper::preprocess(const VRect &clip) -{ - if (mRasterRequest) mRasterizer.rasterize(mPath, FillRule::Winding, clip); - - mRasterRequest = false; -} - -VRle renderer::Clipper::rle(const VRle &mask) -{ - if (mask.empty()) return mRasterizer.rle(); - - mMaskedRle.clone(mask); - mMaskedRle &= mRasterizer.rle(); - return mMaskedRle; -} - -void renderer::CompLayer::updateContent() -{ - if (mClipper && flag().testFlag(DirtyFlagBit::Matrix)) { - mClipper->update(combinedMatrix()); - } - int mappedFrame = mLayerData->timeRemap(frameNo()); - float alpha = combinedAlpha(); - if (complexContent()) alpha = 1; - for (const auto &layer : mLayers) { - layer->update(mappedFrame, combinedMatrix(), alpha); - } -} - -void renderer::CompLayer::preprocessStage(const VRect &clip) -{ - // if layer has clipper - if (mClipper) mClipper->preprocess(clip); - - renderer::Layer *matte = nullptr; - for (const auto &layer : mLayers) { - if (layer->hasMatte()) { - matte = layer; - } else { - if (layer->visible()) { - if (matte) { - if (matte->visible()) { - layer->preprocess(clip); - matte->preprocess(clip); - } - } else { - layer->preprocess(clip); - } - } - matte = nullptr; - } - } -} - -renderer::SolidLayer::SolidLayer(model::Layer *layerData) - : renderer::Layer(layerData) -{ - mDrawableList = &mRenderNode; -} - -void renderer::SolidLayer::updateContent() -{ - if (flag() & DirtyFlagBit::Matrix) { - mPath.reset(); - mPath.addRect(VRectF(0, 0, mLayerData->layerSize().width(), - mLayerData->layerSize().height())); - mPath.transform(combinedMatrix()); - mRenderNode.mFlag |= VDrawable::DirtyState::Path; - mRenderNode.mPath = mPath; - } - if (flag() & DirtyFlagBit::Alpha) { - model::Color color = mLayerData->solidColor(); - VBrush brush(color.toColor(combinedAlpha())); - mRenderNode.setBrush(brush); - mRenderNode.mFlag |= VDrawable::DirtyState::Brush; - } -} - -void renderer::SolidLayer::preprocessStage(const VRect &clip) -{ - mRenderNode.preprocess(clip); -} - -renderer::DrawableList renderer::SolidLayer::renderList() -{ - if (skipRendering()) return {}; - - return {&mDrawableList, 1}; -} - -renderer::ImageLayer::ImageLayer(model::Layer *layerData) - : renderer::Layer(layerData) -{ - mDrawableList = &mRenderNode; - - if (!mLayerData->asset()) return; - - mTexture.mBitmap = mLayerData->asset()->bitmap(); - VBrush brush(&mTexture); - mRenderNode.setBrush(brush); -} - -void renderer::ImageLayer::updateContent() -{ - if (!mLayerData->asset()) return; - - if (flag() & DirtyFlagBit::Matrix) { - mPath.reset(); - mPath.addRect(VRectF(0, 0, mLayerData->asset()->mWidth, - mLayerData->asset()->mHeight)); - mPath.transform(combinedMatrix()); - mRenderNode.mFlag |= VDrawable::DirtyState::Path; - mRenderNode.mPath = mPath; - mTexture.mMatrix = combinedMatrix(); - } - - if (flag() & DirtyFlagBit::Alpha) { - mTexture.mAlpha = int(combinedAlpha() * 255); - } -} - -void renderer::ImageLayer::preprocessStage(const VRect &clip) -{ - mRenderNode.preprocess(clip); -} - -renderer::DrawableList renderer::ImageLayer::renderList() -{ - if (skipRendering()) return {}; - - return {&mDrawableList, 1}; -} - -renderer::NullLayer::NullLayer(model::Layer *layerData) - : renderer::Layer(layerData) -{ -} -void renderer::NullLayer::updateContent() {} - -static renderer::Object *createContentItem(model::Object *contentData, - VArenaAlloc * allocator) -{ - switch (contentData->type()) { - case model::Object::Type::Group: { - return allocator->make( - static_cast(contentData), allocator); - } - case model::Object::Type::Rect: { - return allocator->make( - static_cast(contentData)); - } - case model::Object::Type::Ellipse: { - return allocator->make( - static_cast(contentData)); - } - case model::Object::Type::Path: { - return allocator->make( - static_cast(contentData)); - } - case model::Object::Type::Polystar: { - return allocator->make( - static_cast(contentData)); - } - case model::Object::Type::Fill: { - return allocator->make( - static_cast(contentData)); - } - case model::Object::Type::GFill: { - return allocator->make( - static_cast(contentData)); - } - case model::Object::Type::Stroke: { - return allocator->make( - static_cast(contentData)); - } - case model::Object::Type::GStroke: { - return allocator->make( - static_cast(contentData)); - } - case model::Object::Type::Repeater: { - return allocator->make( - static_cast(contentData), allocator); - } - case model::Object::Type::Trim: { - return allocator->make( - static_cast(contentData)); - } - default: - return nullptr; - break; - } -} - -renderer::ShapeLayer::ShapeLayer(model::Layer *layerData, - VArenaAlloc * allocator) - : renderer::Layer(layerData), - mRoot(allocator->make(nullptr, allocator)) -{ - mRoot->addChildren(layerData, allocator); - - std::vector list; - mRoot->processPaintItems(list); - - if (layerData->hasPathOperator()) { - list.clear(); - mRoot->processTrimItems(list); - } -} - -void renderer::ShapeLayer::updateContent() -{ - mRoot->update(frameNo(), combinedMatrix(), 1.0f , flag()); - - if (mLayerData->hasPathOperator()) { - mRoot->applyTrim(); - } -} - -void renderer::ShapeLayer::preprocessStage(const VRect &clip) -{ - mDrawableList.clear(); - mRoot->renderList(mDrawableList); - - for (auto &drawable : mDrawableList) drawable->preprocess(clip); -} - -renderer::DrawableList renderer::ShapeLayer::renderList() -{ - if (skipRendering()) return {}; - - mDrawableList.clear(); - mRoot->renderList(mDrawableList); - - if (mDrawableList.empty()) return {}; - - return {mDrawableList.data(), mDrawableList.size()}; -} - -void renderer::ShapeLayer::render(VPainter *painter, const VRle &inheritMask, - const VRle &matteRle, SurfaceCache &cache) -{ - if (vIsZero(combinedAlpha())) return; - - if (vCompare(combinedAlpha(), 1.0)) { - Layer::render(painter, inheritMask, matteRle, cache); - } else { - //do offscreen rendering - VSize size = painter->clipBoundingRect().size(); - VPainter srcPainter; - VBitmap srcBitmap = cache.make_surface(size.width(), size.height()); - srcPainter.begin(&srcBitmap, true); - Layer::render(&srcPainter, inheritMask, matteRle, cache); - srcPainter.end(); - painter->drawBitmap(VPoint(), srcBitmap, - uint8_t(combinedAlpha() * 255.0f)); - cache.release_surface(srcBitmap); - } -} - -bool renderer::Group::resolveKeyPath(LOTKeyPath &keyPath, uint32_t depth, - LOTVariant &value) -{ - if (!keyPath.skip(name())) { - if (!keyPath.matches(mModel.name(), depth)) { - return false; - } - - if (!keyPath.skip(mModel.name())) { - if (keyPath.fullyResolvesTo(mModel.name(), depth) && - transformProp(value.property())) { - mModel.filter()->addValue(value); - } - } - } - - if (keyPath.propagate(name(), depth)) { - uint32_t newDepth = keyPath.nextDepth(name(), depth); - for (auto &child : mContents) { - child->resolveKeyPath(keyPath, newDepth, value); - } - } - return true; -} - -bool renderer::Fill::resolveKeyPath(LOTKeyPath &keyPath, uint32_t depth, - LOTVariant &value) -{ - if (!keyPath.matches(mModel.name(), depth)) { - return false; - } - - if (keyPath.fullyResolvesTo(mModel.name(), depth) && - fillProp(value.property())) { - mModel.filter()->addValue(value); - return true; - } - return false; -} - -bool renderer::Stroke::resolveKeyPath(LOTKeyPath &keyPath, uint32_t depth, - LOTVariant &value) -{ - if (!keyPath.matches(mModel.name(), depth)) { - return false; - } - - if (keyPath.fullyResolvesTo(mModel.name(), depth) && - strokeProp(value.property())) { - mModel.filter()->addValue(value); - return true; - } - return false; -} - -renderer::Group::Group(model::Group *data, VArenaAlloc *allocator) - : mModel(data) -{ - addChildren(data, allocator); -} - -void renderer::Group::addChildren(model::Group *data, VArenaAlloc *allocator) -{ - if (!data) return; - - if (!data->mChildren.empty()) mContents.reserve(data->mChildren.size()); - - // keep the content in back-to-front order. - // as lottie model keeps it in front-to-back order. - for (auto it = data->mChildren.crbegin(); it != data->mChildren.rend(); - ++it) { - auto content = createContentItem(*it, allocator); - if (content) { - mContents.push_back(content); - } - } -} - -void renderer::Group::update(int frameNo, const VMatrix &parentMatrix, - float parentAlpha, const DirtyFlag &flag) -{ - DirtyFlag newFlag = flag; - float alpha; - - if (mModel.hasModel() && mModel.transform()) { - VMatrix m = mModel.matrix(frameNo); - - m *= parentMatrix; - if (!(flag & DirtyFlagBit::Matrix) && !mModel.transform()->isStatic() && - (m != mMatrix)) { - newFlag |= DirtyFlagBit::Matrix; - } - - mMatrix = m; - - alpha = parentAlpha * mModel.transform()->opacity(frameNo); - if (!vCompare(alpha, parentAlpha)) { - newFlag |= DirtyFlagBit::Alpha; - } - } else { - mMatrix = parentMatrix; - alpha = parentAlpha; - } - - for (const auto &content : mContents) { - content->update(frameNo, matrix(), alpha, newFlag); - } -} - -void renderer::Group::applyTrim() -{ - for (auto i = mContents.rbegin(); i != mContents.rend(); ++i) { - auto content = (*i); - switch (content->type()) { - case renderer::Object::Type::Trim: { - static_cast(content)->update(); - break; - } - case renderer::Object::Type::Group: { - static_cast(content)->applyTrim(); - break; - } - default: - break; - } - } -} - -void renderer::Group::renderList(std::vector &list) -{ - for (const auto &content : mContents) { - content->renderList(list); - } -} - -void renderer::Group::processPaintItems(std::vector &list) -{ - size_t curOpCount = list.size(); - for (auto i = mContents.rbegin(); i != mContents.rend(); ++i) { - auto content = (*i); - switch (content->type()) { - case renderer::Object::Type::Shape: { - auto pathItem = static_cast(content); - pathItem->setParent(this); - list.push_back(pathItem); - break; - } - case renderer::Object::Type::Paint: { - static_cast(content)->addPathItems(list, - curOpCount); - break; - } - case renderer::Object::Type::Group: { - static_cast(content)->processPaintItems(list); - break; - } - default: - break; - } - } -} - -void renderer::Group::processTrimItems(std::vector &list) -{ - size_t curOpCount = list.size(); - for (auto i = mContents.rbegin(); i != mContents.rend(); ++i) { - auto content = (*i); - - switch (content->type()) { - case renderer::Object::Type::Shape: { - list.push_back(static_cast(content)); - break; - } - case renderer::Object::Type::Trim: { - static_cast(content)->addPathItems(list, - curOpCount); - break; - } - case renderer::Object::Type::Group: { - static_cast(content)->processTrimItems(list); - break; - } - default: - break; - } - } -} - -/* - * renderer::Shape uses 2 path objects for path object reuse. - * mLocalPath - keeps track of the local path of the item before - * applying path operation and transformation. - * mTemp - keeps a referece to the mLocalPath and can be updated by the - * path operation objects(trim, merge path), - * We update the DirtyPath flag if the path needs to be updated again - * beacuse of local path or matrix or some path operation has changed which - * affects the final path. - * The PaintObject queries the dirty flag to check if it needs to compute the - * final path again and calls finalPath() api to do the same. - * finalPath() api passes a result Object so that we keep only one copy of - * the path object in the paintItem (for memory efficiency). - * NOTE: As path objects are COW objects we have to be - * carefull about the refcount so that we don't generate deep copy while - * modifying the path objects. - */ -void renderer::Shape::update(int frameNo, const VMatrix &, float, - const DirtyFlag &flag) -{ - mDirtyPath = false; - - // 1. update the local path if needed - if (hasChanged(frameNo)) { - // loose the reference to mLocalPath if any - // from the last frame update. - mTemp = VPath(); - - updatePath(mLocalPath, frameNo); - mDirtyPath = true; - } - // 2. keep a reference path in temp in case there is some - // path operation like trim which will update the path. - // we don't want to update the local path. - mTemp = mLocalPath; - - // 3. mark the path dirty if matrix has changed. - if (flag & DirtyFlagBit::Matrix) { - mDirtyPath = true; - } -} - -void renderer::Shape::finalPath(VPath &result) -{ - result.addPath(mTemp, static_cast(parent())->matrix()); -} - -renderer::Rect::Rect(model::Rect *data) - : renderer::Shape(data->isStatic()), mData(data) -{ -} - -void renderer::Rect::updatePath(VPath &path, int frameNo) -{ - VPointF pos = mData->mPos.value(frameNo); - VPointF size = mData->mSize.value(frameNo); - float roundness = mData->roundness(frameNo); - VRectF r(pos.x() - size.x() / 2, pos.y() - size.y() / 2, size.x(), - size.y()); - - path.reset(); - path.addRoundRect(r, roundness, mData->direction()); -} - -renderer::Ellipse::Ellipse(model::Ellipse *data) - : renderer::Shape(data->isStatic()), mData(data) -{ -} - -void renderer::Ellipse::updatePath(VPath &path, int frameNo) -{ - VPointF pos = mData->mPos.value(frameNo); - VPointF size = mData->mSize.value(frameNo); - VRectF r(pos.x() - size.x() / 2, pos.y() - size.y() / 2, size.x(), - size.y()); - - path.reset(); - path.addOval(r, mData->direction()); -} - -renderer::Path::Path(model::Path *data) - : renderer::Shape(data->isStatic()), mData(data) -{ -} - -void renderer::Path::updatePath(VPath &path, int frameNo) -{ - mData->mShape.value(frameNo, path); -} - -renderer::Polystar::Polystar(model::Polystar *data) - : renderer::Shape(data->isStatic()), mData(data) -{ -} - -void renderer::Polystar::updatePath(VPath &path, int frameNo) -{ - VPointF pos = mData->mPos.value(frameNo); - float points = mData->mPointCount.value(frameNo); - float innerRadius = mData->mInnerRadius.value(frameNo); - float outerRadius = mData->mOuterRadius.value(frameNo); - float innerRoundness = mData->mInnerRoundness.value(frameNo); - float outerRoundness = mData->mOuterRoundness.value(frameNo); - float rotation = mData->mRotation.value(frameNo); - - path.reset(); - VMatrix m; - - if (mData->mPolyType == model::Polystar::PolyType::Star) { - path.addPolystar(points, innerRadius, outerRadius, innerRoundness, - outerRoundness, 0.0, 0.0, 0.0, mData->direction()); - } else { - path.addPolygon(points, outerRadius, outerRoundness, 0.0, 0.0, 0.0, - mData->direction()); - } - - m.translate(pos.x(), pos.y()).rotate(rotation); - m.rotate(rotation); - path.transform(m); -} - -/* - * PaintData Node handling - * - */ -renderer::Paint::Paint(bool staticContent) : mStaticContent(staticContent) {} - -void renderer::Paint::update(int frameNo, const VMatrix &parentMatrix, - float parentAlpha, const DirtyFlag & /*flag*/) -{ - mRenderNodeUpdate = true; - mContentToRender = updateContent(frameNo, parentMatrix, parentAlpha); -} - -void renderer::Paint::updateRenderNode() -{ - bool dirty = false; - for (auto &i : mPathItems) { - if (i->dirty()) { - dirty = true; - break; - } - } - - if (dirty) { - mPath.reset(); - for (const auto &i : mPathItems) { - i->finalPath(mPath); - } - mDrawable.setPath(mPath); - } else { - if (mDrawable.mFlag & VDrawable::DirtyState::Path) - mDrawable.mPath = mPath; - } -} - -void renderer::Paint::renderList(std::vector &list) -{ - if (mRenderNodeUpdate) { - updateRenderNode(); - mRenderNodeUpdate = false; - } - - // Q: Why we even update the final path if we don't have content - // to render ? - // Ans: We update the render nodes because we will loose the - // dirty path information at end of this frame. - // so if we return early without updating the final path. - // in the subsequent frame when we have content to render but - // we may not able to update our final path properly as we - // don't know what paths got changed in between. - if (mContentToRender) list.push_back(&mDrawable); -} - -void renderer::Paint::addPathItems(std::vector &list, - size_t startOffset) -{ - std::copy(list.begin() + startOffset, list.end(), - back_inserter(mPathItems)); -} - -renderer::Fill::Fill(model::Fill *data) - : renderer::Paint(data->isStatic()), mModel(data) -{ - mDrawable.setName(mModel.name()); -} - -bool renderer::Fill::updateContent(int frameNo, const VMatrix &, float alpha) -{ - auto combinedAlpha = alpha * mModel.opacity(frameNo); - auto color = mModel.color(frameNo).toColor(combinedAlpha); - - VBrush brush(color); - mDrawable.setBrush(brush); - mDrawable.setFillRule(mModel.fillRule()); - - return !color.isTransparent(); -} - -renderer::GradientFill::GradientFill(model::GradientFill *data) - : renderer::Paint(data->isStatic()), mData(data) -{ - mDrawable.setName(mData->name()); -} - -bool renderer::GradientFill::updateContent(int frameNo, const VMatrix &matrix, - float alpha) -{ - float combinedAlpha = alpha * mData->opacity(frameNo); - - mData->update(mGradient, frameNo); - mGradient->setAlpha(combinedAlpha); - mGradient->mMatrix = matrix; - mDrawable.setBrush(VBrush(mGradient.get())); - mDrawable.setFillRule(mData->fillRule()); - - return !vIsZero(combinedAlpha); -} - -renderer::Stroke::Stroke(model::Stroke *data) - : renderer::Paint(data->isStatic()), mModel(data) -{ - mDrawable.setName(mModel.name()); - if (mModel.hasDashInfo()) { - mDrawable.setType(VDrawable::Type::StrokeWithDash); - } else { - mDrawable.setType(VDrawable::Type::Stroke); - } -} - -static vthread_local std::vector Dash_Vector; - -bool renderer::Stroke::updateContent(int frameNo, const VMatrix &matrix, - float alpha) -{ - auto combinedAlpha = alpha * mModel.opacity(frameNo); - auto color = mModel.color(frameNo).toColor(combinedAlpha); - - VBrush brush(color); - mDrawable.setBrush(brush); - float scale = matrix.scale(); - mDrawable.setStrokeInfo(mModel.capStyle(), mModel.joinStyle(), - mModel.miterLimit(), - mModel.strokeWidth(frameNo) * scale); - - if (mModel.hasDashInfo()) { - Dash_Vector.clear(); - mModel.getDashInfo(frameNo, Dash_Vector); - if (!Dash_Vector.empty()) { - for (auto &elm : Dash_Vector) elm *= scale; - mDrawable.setDashInfo(Dash_Vector); - } - } - - return !color.isTransparent(); -} - -renderer::GradientStroke::GradientStroke(model::GradientStroke *data) - : renderer::Paint(data->isStatic()), mData(data) -{ - mDrawable.setName(mData->name()); - if (mData->hasDashInfo()) { - mDrawable.setType(VDrawable::Type::StrokeWithDash); - } else { - mDrawable.setType(VDrawable::Type::Stroke); - } -} - -bool renderer::GradientStroke::updateContent(int frameNo, const VMatrix &matrix, - float alpha) -{ - float combinedAlpha = alpha * mData->opacity(frameNo); - - mData->update(mGradient, frameNo); - mGradient->setAlpha(combinedAlpha); - mGradient->mMatrix = matrix; - auto scale = mGradient->mMatrix.scale(); - mDrawable.setBrush(VBrush(mGradient.get())); - mDrawable.setStrokeInfo(mData->capStyle(), mData->joinStyle(), - mData->miterLimit(), mData->width(frameNo) * scale); - - if (mData->hasDashInfo()) { - Dash_Vector.clear(); - mData->getDashInfo(frameNo, Dash_Vector); - if (!Dash_Vector.empty()) { - for (auto &elm : Dash_Vector) elm *= scale; - mDrawable.setDashInfo(Dash_Vector); - } - } - - return !vIsZero(combinedAlpha); -} - -bool renderer::Trim::resolveKeyPath(LOTKeyPath &keyPath, uint32_t depth, - LOTVariant &value) -{ - if (!keyPath.matches(mModel.name(), depth)) { - return false; - } - - if (keyPath.fullyResolvesTo(mModel.name(), depth) && - trimProp(value.property())) { - mModel.filter()->addValue(value); - return true; - } - return false; -} - -void renderer::Trim::update(int frameNo, const VMatrix & /*parentMatrix*/, - float /*parentAlpha*/, const DirtyFlag & /*flag*/) -{ - mDirty = false; - - if (mCache.mFrameNo == frameNo) return; - - model::Trim::Segment segment = mModel.segment(frameNo); - - if (!(vCompare(mCache.mSegment.start, segment.start) && - vCompare(mCache.mSegment.end, segment.end))) { - mDirty = true; - mCache.mSegment = segment; - } - mCache.mFrameNo = frameNo; -} - -void renderer::Trim::update() -{ - // when both path and trim are not dirty - if (!(mDirty || pathDirty())) return; - - if (vCompare(mCache.mSegment.start, mCache.mSegment.end)) { - for (auto &i : mPathItems) { - i->updatePath(VPath()); - } - return; - } - - if (vCompare(std::fabs(mCache.mSegment.start - mCache.mSegment.end), 1)) { - for (auto &i : mPathItems) { - i->updatePath(i->localPath()); - } - return; - } - - if (mData->type() == model::Trim::TrimType::Simultaneously) { - for (auto &i : mPathItems) { - mPathMesure.setRange(mCache.mSegment.start, mCache.mSegment.end); - i->updatePath(mPathMesure.trim(i->localPath())); - } - } else { // model::Trim::TrimType::Individually - float totalLength = 0.0; - for (auto &i : mPathItems) { - totalLength += i->localPath().length(); - } - float start = totalLength * mCache.mSegment.start; - float end = totalLength * mCache.mSegment.end; - - if (start < end) { - float curLen = 0.0; - for (auto &i : mPathItems) { - if (curLen > end) { - // update with empty path. - i->updatePath(VPath()); - continue; - } - float len = i->localPath().length(); - - if (curLen < start && curLen + len < start) { - curLen += len; - // update with empty path. - i->updatePath(VPath()); - continue; - } else if (start <= curLen && end >= curLen + len) { - // inside segment - curLen += len; - continue; - } else { - float local_start = start > curLen ? start - curLen : 0; - local_start /= len; - float local_end = curLen + len < end ? len : end - curLen; - local_end /= len; - mPathMesure.setRange(local_start, local_end); - i->updatePath(mPathMesure.trim(i->localPath())); - curLen += len; - } - } - } - } -} - -void renderer::Trim::addPathItems(std::vector &list, - size_t startOffset) -{ - std::copy(list.begin() + startOffset, list.end(), - back_inserter(mPathItems)); -} - -renderer::Repeater::Repeater(model::Repeater *data, VArenaAlloc *allocator) - : mRepeaterData(data) -{ - assert(mRepeaterData->content()); - - mCopies = mRepeaterData->maxCopies(); - - for (int i = 0; i < mCopies; i++) { - auto content = allocator->make( - mRepeaterData->content(), allocator); - // content->setParent(this); - mContents.push_back(content); - } -} - -void renderer::Repeater::update(int frameNo, const VMatrix &parentMatrix, - float parentAlpha, const DirtyFlag &flag) -{ - DirtyFlag newFlag = flag; - - float copies = mRepeaterData->copies(frameNo); - int visibleCopies = int(copies); - - if (visibleCopies == 0) { - mHidden = true; - return; - } - - mHidden = false; - - if (!mRepeaterData->isStatic()) newFlag |= DirtyFlagBit::Matrix; - - float offset = mRepeaterData->offset(frameNo); - float startOpacity = mRepeaterData->mTransform.startOpacity(frameNo); - float endOpacity = mRepeaterData->mTransform.endOpacity(frameNo); - - newFlag |= DirtyFlagBit::Alpha; - - for (int i = 0; i < mCopies; ++i) { - float newAlpha = - parentAlpha * lerp(startOpacity, endOpacity, i / copies); - - // hide rest of the copies , @TODO find a better solution. - if (i >= visibleCopies) newAlpha = 0; - - VMatrix result = mRepeaterData->mTransform.matrix(frameNo, i + offset) * - parentMatrix; - mContents[i]->update(frameNo, result, newAlpha, newFlag); - } -} - -void renderer::Repeater::renderList(std::vector &list) -{ - if (mHidden) return; - return renderer::Group::renderList(list); -} diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottieitem.h b/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottieitem.h deleted file mode 100644 index 4105117bf..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottieitem.h +++ /dev/null @@ -1,637 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef LOTTIEITEM_H -#define LOTTIEITEM_H - -#include -#include - -#include "lottiekeypath.h" -#include "lottiefiltermodel.h" -#include "rlottie.h" -#include "rlottiecommon.h" -#include "varenaalloc.h" -#include "vdrawable.h" -#include "vmatrix.h" -#include "vpainter.h" -#include "vpath.h" -#include "vpathmesure.h" -#include "vpoint.h" - -V_USE_NAMESPACE - -namespace rlottie { - -namespace internal { - -template -class VSpan { -public: - using reference = T &; - using pointer = T *; - using const_pointer = T const *; - using const_reference = T const &; - using index_type = size_t; - - using iterator = pointer; - using const_iterator = const_pointer; - - VSpan() = default; - VSpan(pointer data, index_type size) : _data(data), _size(size) {} - - constexpr pointer data() const noexcept { return _data; } - constexpr index_type size() const noexcept { return _size; } - constexpr bool empty() const noexcept { return size() == 0; } - constexpr iterator begin() const noexcept { return data(); } - constexpr iterator end() const noexcept { return data() + size(); } - constexpr const_iterator cbegin() const noexcept { return data(); } - constexpr const_iterator cend() const noexcept { return data() + size(); } - constexpr reference operator[](index_type idx) const - { - return *(data() + idx); - } - -private: - pointer _data{nullptr}; - index_type _size{0}; -}; - -namespace renderer { - -using DrawableList = VSpan; - -enum class DirtyFlagBit : uint8_t { - None = 0x00, - Matrix = 0x01, - Alpha = 0x02, - All = (Matrix | Alpha) -}; -typedef vFlag DirtyFlag; - -class SurfaceCache { -public: - SurfaceCache() { mCache.reserve(10); } - - VBitmap make_surface( - size_t width, size_t height, - VBitmap::Format format = VBitmap::Format::ARGB32_Premultiplied) - { - if (mCache.empty()) return {width, height, format}; - - auto surface = mCache.back(); - surface.reset(width, height, format); - - mCache.pop_back(); - return surface; - } - - void release_surface(VBitmap &surface) { mCache.push_back(surface); } - -private: - std::vector mCache; -}; - -class Drawable final : public VDrawable { -public: - void sync(); - -public: - std::unique_ptr mCNode{nullptr}; - - ~Drawable() noexcept - { - if (mCNode && mCNode->mGradient.stopPtr) - free(mCNode->mGradient.stopPtr); - } -}; - -struct CApiData { - CApiData(); - LOTLayerNode mLayer; - std::vector mMasks; - std::vector mLayers; - std::vector mCNodeList; -}; - -class Clipper { -public: - explicit Clipper(VSize size) : mSize(size) {} - void update(const VMatrix &matrix); - void preprocess(const VRect &clip); - VRle rle(const VRle &mask); - -public: - VSize mSize; - VPath mPath; - VRle mMaskedRle; - VRasterizer mRasterizer; - bool mRasterRequest{false}; -}; - -class Mask { -public: - explicit Mask(model::Mask *data) : mData(data) {} - void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, - const DirtyFlag &flag); - model::Mask::Mode maskMode() const { return mData->mMode; } - VRle rle(); - void preprocess(const VRect &clip); - bool inverted() const { return mData->mInv; } -public: - model::Mask *mData{nullptr}; - VPath mLocalPath; - VPath mFinalPath; - VRasterizer mRasterizer; - float mCombinedAlpha{0}; - bool mRasterRequest{false}; -}; - -/* - * Handels mask property of a layer item - */ -class LayerMask { -public: - explicit LayerMask(model::Layer *layerData); - void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, - const DirtyFlag &flag); - bool isStatic() const { return mStatic; } - VRle maskRle(const VRect &clipRect); - void preprocess(const VRect &clip); - -public: - std::vector mMasks; - VRle mRle; - bool mStatic{true}; - bool mDirty{true}; -}; - -class Layer; - -class Composition { -public: - explicit Composition(std::shared_ptr composition); - bool update(int frameNo, const VSize &size); - VSize size() const { return mViewSize; } - void buildRenderTree(); - const LOTLayerNode *renderTree() const; - bool render(const rlottie::Surface &surface, bool clear); - void setValue(const std::string &keypath, LOTVariant &value); - void resetCurrentFrame(); - -private: - SurfaceCache mSurfaceCache; - VBitmap mSurface; - VMatrix mScaleMatrix; - VSize mViewSize; - std::shared_ptr mModel; - Layer * mRootLayer{nullptr}; - VArenaAlloc mAllocator{2048}; - int mCurFrameNo; - bool mKeepAspectRatio{true}; - bool mHasDynamicValue{ false }; -}; - -class Layer { -public: - virtual ~Layer() = default; - Layer &operator=(Layer &&) noexcept = delete; - Layer(model::Layer *layerData); - int id() const { return mLayerData->id(); } - int parentId() const { return mLayerData->parentId(); } - void setParentLayer(Layer *parent) { mParentLayer = parent; } - void setComplexContent(bool value) { mComplexContent = value; } - bool complexContent() const { return mComplexContent; } - virtual void update(int frameNo, const VMatrix &parentMatrix, - float parentAlpha); - VMatrix matrix(int frameNo) const; - void preprocess(const VRect &clip); - virtual DrawableList renderList() { return {}; } - virtual void render(VPainter *painter, const VRle &mask, - const VRle &matteRle, SurfaceCache &cache); - bool hasMatte() - { - if (mLayerData->mMatteType == model::MatteType::None) return false; - return true; - } - model::MatteType matteType() const { return mLayerData->mMatteType; } - bool visible() const; - virtual void buildLayerNode(); - LOTLayerNode & clayer() { return mCApiData->mLayer; } - std::vector &clayers() { return mCApiData->mLayers; } - std::vector & cmasks() { return mCApiData->mMasks; } - std::vector & cnodes() { return mCApiData->mCNodeList; } - const char * name() const { return mLayerData->name(); } - virtual bool resolveKeyPath(LOTKeyPath &keyPath, uint32_t depth, - LOTVariant &value); - Layer *resolvedParentLayer() const {return mParentLayer;} - -protected: - virtual void preprocessStage(const VRect &clip) = 0; - virtual void updateContent() = 0; - inline VMatrix combinedMatrix() const { return mCombinedMatrix; } - inline int frameNo() const { return mFrameNo; } - inline float combinedAlpha() const { return mCombinedAlpha; } - inline bool isStatic() const { return mLayerData->isStatic(); } - float opacity(int frameNo) const { return mLayerData->opacity(frameNo); } - inline DirtyFlag flag() const { return mDirtyFlag; } - bool skipRendering() const - { - return (!visible() || vIsZero(combinedAlpha())); - } - -protected: - std::unique_ptr mLayerMask; - model::Layer * mLayerData{nullptr}; - Layer * mParentLayer{nullptr}; - VMatrix mCombinedMatrix; - float mCombinedAlpha{0.0}; - int mFrameNo{-1}; - DirtyFlag mDirtyFlag{DirtyFlagBit::All}; - bool mComplexContent{false}; - std::unique_ptr mCApiData; -}; - -class CompLayer final : public Layer { -public: - explicit CompLayer(model::Layer *layerData, VArenaAlloc *allocator); - - void render(VPainter *painter, const VRle &mask, const VRle &matteRle, - SurfaceCache &cache) final; - void buildLayerNode() final; - bool resolveKeyPath(LOTKeyPath &keyPath, uint32_t depth, - LOTVariant &value) override; - -protected: - void preprocessStage(const VRect &clip) final; - void updateContent() final; - -private: - void renderHelper(VPainter *painter, const VRle &mask, const VRle &matteRle, - SurfaceCache &cache); - static void renderMatteLayer(VPainter *painter, const VRle &inheritMask, - const VRle &matteRle, Layer *layer, Layer *src, - SurfaceCache &cache); - -private: - std::vector mLayers; - std::unique_ptr mClipper; -}; - -class SolidLayer final : public Layer { -public: - explicit SolidLayer(model::Layer *layerData); - void buildLayerNode() final; - DrawableList renderList() final; - -protected: - void preprocessStage(const VRect &clip) final; - void updateContent() final; - -private: - Drawable mRenderNode; - VPath mPath; - VDrawable *mDrawableList{nullptr}; // to work with the Span api -}; - -class Group; - -class ShapeLayer final : public Layer { -public: - explicit ShapeLayer(model::Layer *layerData, VArenaAlloc *allocator); - DrawableList renderList() final; - void buildLayerNode() final; - bool resolveKeyPath(LOTKeyPath &keyPath, uint32_t depth, - LOTVariant &value) override; - void render(VPainter *painter, const VRle &mask, const VRle &matteRle, - SurfaceCache &cache) final; - -protected: - void preprocessStage(const VRect &clip) final; - void updateContent() final; - std::vector mDrawableList; - Group * mRoot{nullptr}; -}; - -class NullLayer final : public Layer { -public: - explicit NullLayer(model::Layer *layerData); - -protected: - void preprocessStage(const VRect &) final {} - void updateContent() final; -}; - -class ImageLayer final : public Layer { -public: - explicit ImageLayer(model::Layer *layerData); - void buildLayerNode() final; - DrawableList renderList() final; - -protected: - void preprocessStage(const VRect &clip) final; - void updateContent() final; - -private: - Drawable mRenderNode; - VTexture mTexture; - VPath mPath; - VDrawable *mDrawableList{nullptr}; // to work with the Span api -}; - -class Object { -public: - enum class Type : uint8_t { Unknown, Group, Shape, Paint, Trim }; - virtual ~Object() = default; - Object & operator=(Object &&) noexcept = delete; - virtual void update(int frameNo, const VMatrix &parentMatrix, - float parentAlpha, const DirtyFlag &flag) = 0; - virtual void renderList(std::vector &) {} - virtual bool resolveKeyPath(LOTKeyPath &, uint32_t, LOTVariant &) - { - return false; - } - virtual Object::Type type() const { return Object::Type::Unknown; } -}; - -class Shape; -class Group : public Object { -public: - Group() = default; - explicit Group(model::Group *data, VArenaAlloc *allocator); - void addChildren(model::Group *data, VArenaAlloc *allocator); - void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, - const DirtyFlag &flag) override; - void applyTrim(); - void processTrimItems(std::vector &list); - void processPaintItems(std::vector &list); - void renderList(std::vector &list) override; - Object::Type type() const final { return Object::Type::Group; } - const VMatrix &matrix() const { return mMatrix; } - const char * name() const - { - static const char *TAG = "__"; - return mModel.hasModel() ? mModel.name() : TAG; - } - bool resolveKeyPath(LOTKeyPath &keyPath, uint32_t depth, - LOTVariant &value) override; - -protected: - std::vector mContents; - VMatrix mMatrix; - -private: - model::Filter mModel; -}; - -class Shape : public Object { -public: - Shape(bool staticPath) : mStaticPath(staticPath) {} - void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, - const DirtyFlag &flag) final; - Object::Type type() const final { return Object::Type::Shape; } - bool dirty() const { return mDirtyPath; } - const VPath &localPath() const { return mTemp; } - void finalPath(VPath &result); - void updatePath(const VPath &path) - { - mTemp = path; - mDirtyPath = true; - } - bool staticPath() const { return mStaticPath; } - void setParent(Group *parent) { mParent = parent; } - Group *parent() const { return mParent; } - -protected: - virtual void updatePath(VPath &path, int frameNo) = 0; - virtual bool hasChanged(int prevFrame, int curFrame) = 0; - -private: - bool hasChanged(int frameNo) - { - int prevFrame = mFrameNo; - mFrameNo = frameNo; - if (prevFrame == -1) return true; - if (mStaticPath || (prevFrame == frameNo)) return false; - return hasChanged(prevFrame, frameNo); - } - Group *mParent{nullptr}; - VPath mLocalPath; - VPath mTemp; - int mFrameNo{-1}; - bool mDirtyPath{true}; - bool mStaticPath; -}; - -class Rect final : public Shape { -public: - explicit Rect(model::Rect *data); - -protected: - void updatePath(VPath &path, int frameNo) final; - model::Rect *mData{nullptr}; - - bool hasChanged(int prevFrame, int curFrame) final - { - return (mData->mPos.changed(prevFrame, curFrame) || - mData->mSize.changed(prevFrame, curFrame) || - mData->roundnessChanged(prevFrame, curFrame)); - } -}; - -class Ellipse final : public Shape { -public: - explicit Ellipse(model::Ellipse *data); - -private: - void updatePath(VPath &path, int frameNo) final; - model::Ellipse *mData{nullptr}; - bool hasChanged(int prevFrame, int curFrame) final - { - return (mData->mPos.changed(prevFrame, curFrame) || - mData->mSize.changed(prevFrame, curFrame)); - } -}; - -class Path final : public Shape { -public: - explicit Path(model::Path *data); - -private: - void updatePath(VPath &path, int frameNo) final; - model::Path *mData{nullptr}; - bool hasChanged(int prevFrame, int curFrame) final - { - return mData->mShape.changed(prevFrame, curFrame); - } -}; - -class Polystar final : public Shape { -public: - explicit Polystar(model::Polystar *data); - -private: - void updatePath(VPath &path, int frameNo) final; - model::Polystar *mData{nullptr}; - - bool hasChanged(int prevFrame, int curFrame) final - { - return (mData->mPos.changed(prevFrame, curFrame) || - mData->mPointCount.changed(prevFrame, curFrame) || - mData->mInnerRadius.changed(prevFrame, curFrame) || - mData->mOuterRadius.changed(prevFrame, curFrame) || - mData->mInnerRoundness.changed(prevFrame, curFrame) || - mData->mOuterRoundness.changed(prevFrame, curFrame) || - mData->mRotation.changed(prevFrame, curFrame)); - } -}; - -class Paint : public Object { -public: - Paint(bool staticContent); - void addPathItems(std::vector &list, size_t startOffset); - void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, - const DirtyFlag &flag) override; - void renderList(std::vector &list) final; - Object::Type type() const final { return Object::Type::Paint; } - -protected: - virtual bool updateContent(int frameNo, const VMatrix &matrix, - float alpha) = 0; - -private: - void updateRenderNode(); - -protected: - std::vector mPathItems; - Drawable mDrawable; - VPath mPath; - DirtyFlag mFlag; - bool mStaticContent; - bool mRenderNodeUpdate{true}; - bool mContentToRender{true}; -}; - -class Fill final : public Paint { -public: - explicit Fill(model::Fill *data); - -protected: - bool updateContent(int frameNo, const VMatrix &matrix, float alpha) final; - bool resolveKeyPath(LOTKeyPath &keyPath, uint32_t depth, - LOTVariant &value) final; - -private: - model::Filter mModel; -}; - -class GradientFill final : public Paint { -public: - explicit GradientFill(model::GradientFill *data); - -protected: - bool updateContent(int frameNo, const VMatrix &matrix, float alpha) final; - -private: - model::GradientFill * mData{nullptr}; - std::unique_ptr mGradient; -}; - -class Stroke : public Paint { -public: - explicit Stroke(model::Stroke *data); - -protected: - bool updateContent(int frameNo, const VMatrix &matrix, float alpha) final; - bool resolveKeyPath(LOTKeyPath &keyPath, uint32_t depth, - LOTVariant &value) final; - -private: - model::Filter mModel; -}; - -class GradientStroke final : public Paint { -public: - explicit GradientStroke(model::GradientStroke *data); - -protected: - bool updateContent(int frameNo, const VMatrix &matrix, float alpha) final; - -private: - model::GradientStroke * mData{nullptr}; - std::unique_ptr mGradient; -}; - -class Trim final : public Object { -public: - explicit Trim(model::Trim *data) : mData(data), mModel(data) {} - void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, - const DirtyFlag &flag) final; - Object::Type type() const final { return Object::Type::Trim; } - void update(); - void addPathItems(std::vector &list, size_t startOffset); - -protected: - bool resolveKeyPath(LOTKeyPath &keyPath, uint32_t depth, - LOTVariant &value) final; - -private: - bool pathDirty() const - { - for (auto &i : mPathItems) { - if (i->dirty()) return true; - } - return false; - } - struct Cache { - int mFrameNo{-1}; - model::Trim::Segment mSegment{}; - }; - Cache mCache; - std::vector mPathItems; - model::Trim * mData{nullptr}; - VPathMesure mPathMesure; - bool mDirty{true}; - - model::Filter mModel; -}; - -class Repeater final : public Group { -public: - explicit Repeater(model::Repeater *data, VArenaAlloc *allocator); - void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, - const DirtyFlag &flag) final; - void renderList(std::vector &list) final; - -private: - model::Repeater *mRepeaterData{nullptr}; - bool mHidden{false}; - int mCopies{0}; -}; - -} // namespace renderer - -} // namespace internal - -} // namespace rlottie - -#endif // LOTTIEITEM_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottieitem_capi.cpp b/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottieitem_capi.cpp deleted file mode 100644 index 6c1a52e37..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottieitem_capi.cpp +++ /dev/null @@ -1,339 +0,0 @@ -/* - * Implements LottieItem functions needed - * to support renderTree() api. - * Moving all those implementation to its own - * file make clear separation as well easy of - * maintenance. - */ - -#include "lottieitem.h" -#include "vdasher.h" - -using namespace rlottie::internal; - -renderer::CApiData::CApiData() -{ - mLayer.mMaskList.ptr = nullptr; - mLayer.mMaskList.size = 0; - mLayer.mLayerList.ptr = nullptr; - mLayer.mLayerList.size = 0; - mLayer.mNodeList.ptr = nullptr; - mLayer.mNodeList.size = 0; - mLayer.mMatte = MatteNone; - mLayer.mVisible = 0; - mLayer.mAlpha = 255; - mLayer.mClipPath.ptPtr = nullptr; - mLayer.mClipPath.elmPtr = nullptr; - mLayer.mClipPath.ptCount = 0; - mLayer.mClipPath.elmCount = 0; - mLayer.keypath = nullptr; -} - -void renderer::Composition::buildRenderTree() -{ - mRootLayer->buildLayerNode(); -} - -const LOTLayerNode *renderer::Composition::renderTree() const -{ - return &mRootLayer->clayer(); -} - -void renderer::CompLayer::buildLayerNode() -{ - renderer::Layer::buildLayerNode(); - if (mClipper) { - const auto &elm = mClipper->mPath.elements(); - const auto &pts = mClipper->mPath.points(); - auto ptPtr = reinterpret_cast(pts.data()); - auto elmPtr = reinterpret_cast(elm.data()); - clayer().mClipPath.ptPtr = ptPtr; - clayer().mClipPath.elmPtr = elmPtr; - clayer().mClipPath.ptCount = 2 * pts.size(); - clayer().mClipPath.elmCount = elm.size(); - } - if (mLayers.size() != clayers().size()) { - for (const auto &layer : mLayers) { - layer->buildLayerNode(); - clayers().push_back(&layer->clayer()); - } - clayer().mLayerList.ptr = clayers().data(); - clayer().mLayerList.size = clayers().size(); - } else { - for (const auto &layer : mLayers) { - layer->buildLayerNode(); - } - } -} - -void renderer::ShapeLayer::buildLayerNode() -{ - renderer::Layer::buildLayerNode(); - - auto renderlist = renderList(); - - cnodes().clear(); - for (auto &i : renderlist) { - auto lotDrawable = static_cast(i); - lotDrawable->sync(); - cnodes().push_back(lotDrawable->mCNode.get()); - } - clayer().mNodeList.ptr = cnodes().data(); - clayer().mNodeList.size = cnodes().size(); -} - -void renderer::Layer::buildLayerNode() -{ - if (!mCApiData) { - mCApiData = std::make_unique(); - clayer().keypath = name(); - } - if (!complexContent()) clayer().mAlpha = uint8_t(combinedAlpha() * 255.f); - clayer().mVisible = visible(); - // update matte - if (hasMatte()) { - switch (mLayerData->mMatteType) { - case model::MatteType::Alpha: - clayer().mMatte = MatteAlpha; - break; - case model::MatteType::AlphaInv: - clayer().mMatte = MatteAlphaInv; - break; - case model::MatteType::Luma: - clayer().mMatte = MatteLuma; - break; - case model::MatteType::LumaInv: - clayer().mMatte = MatteLumaInv; - break; - default: - clayer().mMatte = MatteNone; - break; - } - } - if (mLayerMask) { - cmasks().clear(); - cmasks().resize(mLayerMask->mMasks.size()); - size_t i = 0; - for (const auto &mask : mLayerMask->mMasks) { - auto & cNode = cmasks()[i++]; - const auto &elm = mask.mFinalPath.elements(); - const auto &pts = mask.mFinalPath.points(); - auto ptPtr = reinterpret_cast(pts.data()); - auto elmPtr = reinterpret_cast(elm.data()); - cNode.mPath.ptPtr = ptPtr; - cNode.mPath.ptCount = 2 * pts.size(); - cNode.mPath.elmPtr = elmPtr; - cNode.mPath.elmCount = elm.size(); - cNode.mAlpha = uint8_t(mask.mCombinedAlpha * 255.0f); - switch (mask.maskMode()) { - case model::Mask::Mode::Add: - cNode.mMode = MaskAdd; - break; - case model::Mask::Mode::Substarct: - cNode.mMode = MaskSubstract; - break; - case model::Mask::Mode::Intersect: - cNode.mMode = MaskIntersect; - break; - case model::Mask::Mode::Difference: - cNode.mMode = MaskDifference; - break; - default: - cNode.mMode = MaskAdd; - break; - } - } - clayer().mMaskList.ptr = cmasks().data(); - clayer().mMaskList.size = cmasks().size(); - } -} - -void renderer::SolidLayer::buildLayerNode() -{ - renderer::Layer::buildLayerNode(); - - auto renderlist = renderList(); - - cnodes().clear(); - for (auto &i : renderlist) { - auto lotDrawable = static_cast(i); - lotDrawable->sync(); - cnodes().push_back(lotDrawable->mCNode.get()); - } - clayer().mNodeList.ptr = cnodes().data(); - clayer().mNodeList.size = cnodes().size(); -} - -void renderer::ImageLayer::buildLayerNode() -{ - renderer::Layer::buildLayerNode(); - - auto renderlist = renderList(); - - cnodes().clear(); - for (auto &i : renderlist) { - auto lotDrawable = static_cast(i); - lotDrawable->sync(); - - lotDrawable->mCNode->mImageInfo.data = - lotDrawable->mBrush.mTexture->mBitmap.data(); - lotDrawable->mCNode->mImageInfo.width = - int(lotDrawable->mBrush.mTexture->mBitmap.width()); - lotDrawable->mCNode->mImageInfo.height = - int(lotDrawable->mBrush.mTexture->mBitmap.height()); - - lotDrawable->mCNode->mImageInfo.mMatrix.m11 = combinedMatrix().m_11(); - lotDrawable->mCNode->mImageInfo.mMatrix.m12 = combinedMatrix().m_12(); - lotDrawable->mCNode->mImageInfo.mMatrix.m13 = combinedMatrix().m_13(); - - lotDrawable->mCNode->mImageInfo.mMatrix.m21 = combinedMatrix().m_21(); - lotDrawable->mCNode->mImageInfo.mMatrix.m22 = combinedMatrix().m_22(); - lotDrawable->mCNode->mImageInfo.mMatrix.m23 = combinedMatrix().m_23(); - - lotDrawable->mCNode->mImageInfo.mMatrix.m31 = combinedMatrix().m_tx(); - lotDrawable->mCNode->mImageInfo.mMatrix.m32 = combinedMatrix().m_ty(); - lotDrawable->mCNode->mImageInfo.mMatrix.m33 = combinedMatrix().m_33(); - - // Alpha calculation already combined. - lotDrawable->mCNode->mImageInfo.mAlpha = - uint8_t(lotDrawable->mBrush.mTexture->mAlpha); - - cnodes().push_back(lotDrawable->mCNode.get()); - } - clayer().mNodeList.ptr = cnodes().data(); - clayer().mNodeList.size = cnodes().size(); -} - -static void updateGStops(LOTNode *n, const VGradient *grad) -{ - if (grad->mStops.size() != n->mGradient.stopCount) { - if (n->mGradient.stopCount) free(n->mGradient.stopPtr); - n->mGradient.stopCount = grad->mStops.size(); - n->mGradient.stopPtr = (LOTGradientStop *)malloc( - n->mGradient.stopCount * sizeof(LOTGradientStop)); - } - - LOTGradientStop *ptr = n->mGradient.stopPtr; - for (const auto &i : grad->mStops) { - ptr->pos = i.first; - ptr->a = uint8_t(i.second.alpha() * grad->alpha()); - ptr->r = i.second.red(); - ptr->g = i.second.green(); - ptr->b = i.second.blue(); - ptr++; - } -} - -void renderer::Drawable::sync() -{ - if (!mCNode) { - mCNode = std::make_unique(); - mCNode->mGradient.stopPtr = nullptr; - mCNode->mGradient.stopCount = 0; - } - - mCNode->mFlag = ChangeFlagNone; - if (mFlag & DirtyState::None) return; - - if (mFlag & DirtyState::Path) { - applyDashOp(); - const std::vector &elm = mPath.elements(); - const std::vector & pts = mPath.points(); - const auto *ptPtr = reinterpret_cast(pts.data()); - const char * elmPtr = reinterpret_cast(elm.data()); - mCNode->mPath.elmPtr = elmPtr; - mCNode->mPath.elmCount = elm.size(); - mCNode->mPath.ptPtr = ptPtr; - mCNode->mPath.ptCount = 2 * pts.size(); - mCNode->mFlag |= ChangeFlagPath; - mCNode->keypath = name(); - } - - if (mStrokeInfo) { - mCNode->mStroke.width = mStrokeInfo->width; - mCNode->mStroke.miterLimit = mStrokeInfo->miterLimit; - mCNode->mStroke.enable = 1; - - switch (mStrokeInfo->cap) { - case CapStyle::Flat: - mCNode->mStroke.cap = LOTCapStyle::CapFlat; - break; - case CapStyle::Square: - mCNode->mStroke.cap = LOTCapStyle::CapSquare; - break; - case CapStyle::Round: - mCNode->mStroke.cap = LOTCapStyle::CapRound; - break; - } - - switch (mStrokeInfo->join) { - case JoinStyle::Miter: - mCNode->mStroke.join = LOTJoinStyle::JoinMiter; - break; - case JoinStyle::Bevel: - mCNode->mStroke.join = LOTJoinStyle::JoinBevel; - break; - case JoinStyle::Round: - mCNode->mStroke.join = LOTJoinStyle::JoinRound; - break; - default: - mCNode->mStroke.join = LOTJoinStyle::JoinMiter; - break; - } - } else { - mCNode->mStroke.enable = 0; - } - - switch (mFillRule) { - case FillRule::EvenOdd: - mCNode->mFillRule = LOTFillRule::FillEvenOdd; - break; - default: - mCNode->mFillRule = LOTFillRule::FillWinding; - break; - } - - switch (mBrush.type()) { - case VBrush::Type::Solid: - mCNode->mBrushType = LOTBrushType::BrushSolid; - mCNode->mColor.r = mBrush.mColor.r; - mCNode->mColor.g = mBrush.mColor.g; - mCNode->mColor.b = mBrush.mColor.b; - mCNode->mColor.a = mBrush.mColor.a; - break; - case VBrush::Type::LinearGradient: { - mCNode->mBrushType = LOTBrushType::BrushGradient; - mCNode->mGradient.type = LOTGradientType::GradientLinear; - VPointF s = mBrush.mGradient->mMatrix.map( - {mBrush.mGradient->linear.x1, mBrush.mGradient->linear.y1}); - VPointF e = mBrush.mGradient->mMatrix.map( - {mBrush.mGradient->linear.x2, mBrush.mGradient->linear.y2}); - mCNode->mGradient.start.x = s.x(); - mCNode->mGradient.start.y = s.y(); - mCNode->mGradient.end.x = e.x(); - mCNode->mGradient.end.y = e.y(); - updateGStops(mCNode.get(), mBrush.mGradient); - break; - } - case VBrush::Type::RadialGradient: { - mCNode->mBrushType = LOTBrushType::BrushGradient; - mCNode->mGradient.type = LOTGradientType::GradientRadial; - VPointF c = mBrush.mGradient->mMatrix.map( - {mBrush.mGradient->radial.cx, mBrush.mGradient->radial.cy}); - VPointF f = mBrush.mGradient->mMatrix.map( - {mBrush.mGradient->radial.fx, mBrush.mGradient->radial.fy}); - mCNode->mGradient.center.x = c.x(); - mCNode->mGradient.center.y = c.y(); - mCNode->mGradient.focal.x = f.x(); - mCNode->mGradient.focal.y = f.y(); - - float scale = mBrush.mGradient->mMatrix.scale(); - mCNode->mGradient.cradius = mBrush.mGradient->radial.cradius * scale; - mCNode->mGradient.fradius = mBrush.mGradient->radial.fradius * scale; - updateGStops(mCNode.get(), mBrush.mGradient); - break; - } - default: - break; - } -} diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottiekeypath.cpp b/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottiekeypath.cpp deleted file mode 100644 index e1132c71e..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottiekeypath.cpp +++ /dev/null @@ -1,86 +0,0 @@ -#include "lottiekeypath.h" - -#include - -LOTKeyPath::LOTKeyPath(const std::string &keyPath) -{ - std::stringstream ss(keyPath); - std::string item; - - while (getline(ss, item, '.')) { - mKeys.push_back(item); - } -} - -bool LOTKeyPath::matches(const std::string &key, uint32_t depth) -{ - if (skip(key)) { - // This is an object we programatically create. - return true; - } - if (depth > size()) { - return false; - } - if ((mKeys[depth] == key) || (mKeys[depth] == "*") || - (mKeys[depth] == "**")) { - return true; - } - return false; -} - -uint32_t LOTKeyPath::nextDepth(const std::string& key, uint32_t depth) -{ - if (skip(key)) { - // If it's a container then we added programatically and it isn't a part - // of the keypath. - return depth; - } - if (mKeys[depth] != "**") { - // If it's not a globstar then it is part of the keypath. - return depth + 1; - } - if (depth == size()) { - // The last key is a globstar. - return depth; - } - if (mKeys[depth + 1] == key) { - // We are a globstar and the next key is our current key so consume - // both. - return depth + 2; - } - return depth; -} - -bool LOTKeyPath::fullyResolvesTo(const std::string& key, uint32_t depth) -{ - if (depth > mKeys.size()) { - return false; - } - - bool isLastDepth = (depth == size()); - - if (!isGlobstar(depth)) { - bool matches = (mKeys[depth] == key) || isGlob(depth); - return (isLastDepth || (depth == size() - 1 && endsWithGlobstar())) && - matches; - } - - bool isGlobstarButNextKeyMatches = !isLastDepth && mKeys[depth + 1] == key; - if (isGlobstarButNextKeyMatches) { - return depth == size() - 1 || - (depth == size() - 2 && endsWithGlobstar()); - } - - if (isLastDepth) { - return true; - } - - if (depth + 1 < size()) { - // We are a globstar but there is more than 1 key after the globstar we - // we can't fully match. - return false; - } - // Return whether the next key (which we now know is the last one) is the - // same as the current key. - return mKeys[depth + 1] == key; -} diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottiekeypath.h b/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottiekeypath.h deleted file mode 100644 index f6a4f4f8b..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottiekeypath.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef LOTTIEKEYPATH_H -#define LOTTIEKEYPATH_H - -#include -#include -#include "vglobal.h" - -class LOTKeyPath { -public: - LOTKeyPath(const std::string &keyPath); - bool matches(const std::string &key, uint32_t depth); - uint32_t nextDepth(const std::string& key, uint32_t depth); - bool fullyResolvesTo(const std::string& key, uint32_t depth); - - bool propagate(const std::string key, uint32_t depth) - { - return skip(key) || (depth < size()) || (mKeys[depth] == "**"); - } - bool skip(const std::string &key) const { return key == "__"; } - -private: - bool isGlobstar(uint32_t depth) const { return mKeys[depth] == "**"; } - bool isGlob(uint32_t depth) const { return mKeys[depth] == "*"; } - bool endsWithGlobstar() const { return mKeys.back() == "**"; } - size_t size() const { return mKeys.size() - 1; } - -private: - std::vector mKeys; -}; - -#endif // LOTTIEKEYPATH_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottieloader.cpp b/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottieloader.cpp deleted file mode 100644 index 47c22e47a..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottieloader.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include -#include -#include - -#include "lottiemodel.h" -#include "colorreplace.h" - -using namespace rlottie::internal; - -std::shared_ptr model::loadFromData(const char*jsonData, ColorReplace *colorReplacement, - const std::string &resourcePath) -{ - - auto obj = internal::model::parse(jsonData, - resourcePath, colorReplacement); - - return obj; -} diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottiemodel.cpp b/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottiemodel.cpp deleted file mode 100644 index a15a67b88..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottiemodel.cpp +++ /dev/null @@ -1,379 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "lottiemodel.h" -#include -#include -#include -#include "vimageloader.h" -#include "vline.h" - -using namespace rlottie::internal; - -/* - * We process the iterator objects in the children list - * by iterating from back to front. when we find a repeater object - * we remove the objects from satrt till repeater object and then place - * under a new shape group object which we add it as children to the repeater - * object. - * Then we visit the childrens of the newly created shape group object to - * process the remaining repeater object(when children list contains more than - * one repeater). - * - */ -class LottieRepeaterProcesser { -public: - void visitChildren(model::Group *obj) - { - for (auto i = obj->mChildren.rbegin(); i != obj->mChildren.rend(); - ++i) { - auto child = (*i); - if (child->type() == model::Object::Type::Repeater) { - auto *repeater = - static_cast(child); - // check if this repeater is already processed - // can happen if the layer is an asset and referenced by - // multiple layer. - if (repeater->processed()) continue; - - repeater->markProcessed(); - - auto content = repeater->content(); - // 1. increment the reverse iterator to point to the - // object before the repeater - ++i; - // 2. move all the children till repater to the group - std::move(obj->mChildren.begin(), i.base(), - back_inserter(content->mChildren)); - // 3. erase the objects from the original children list - obj->mChildren.erase(obj->mChildren.begin(), i.base()); - - // 5. visit newly created group to process remaining repeater - // object. - visitChildren(content); - // 6. exit the loop as the current iterators are invalid - break; - } - visit(child); - } - } - - void visit(model::Object *obj) - { - switch (obj->type()) { - case model::Object::Type::Group: - case model::Object::Type::Layer: { - visitChildren(static_cast(obj)); - break; - } - default: - break; - } - } -}; - -class LottieUpdateStatVisitor { - model::Composition::Stats *stat; - -public: - explicit LottieUpdateStatVisitor(model::Composition::Stats *s) : stat(s) {} - void visitChildren(model::Group *obj) - { - for (const auto &child : obj->mChildren) { - if (child) visit(child); - } - } - void visitLayer(model::Layer *layer) - { - switch (layer->mLayerType) { - case model::Layer::Type::Precomp: - stat->precompLayerCount++; - break; - case model::Layer::Type::Null: - stat->nullLayerCount++; - break; - case model::Layer::Type::Shape: - stat->shapeLayerCount++; - break; - case model::Layer::Type::Solid: - stat->solidLayerCount++; - break; - case model::Layer::Type::Image: - stat->imageLayerCount++; - break; - default: - break; - } - visitChildren(layer); - } - void visit(model::Object *obj) - { - switch (obj->type()) { - case model::Object::Type::Layer: { - visitLayer(static_cast(obj)); - break; - } - case model::Object::Type::Repeater: { - visitChildren(static_cast(obj)->content()); - break; - } - case model::Object::Type::Group: { - visitChildren(static_cast(obj)); - break; - } - default: - break; - } - } -}; - -void model::Composition::processRepeaterObjects() const -{ - LottieRepeaterProcesser visitor; - visitor.visit(mRootLayer); -} - -void model::Composition::updateStats() -{ - LottieUpdateStatVisitor visitor(&mStats); - visitor.visit(mRootLayer); -} - -VMatrix model::Repeater::Transform::matrix(int frameNo, float multiplier) const -{ - VPointF scale = mScale.value(frameNo) / 100.f; - scale.setX(std::pow(scale.x(), multiplier)); - scale.setY(std::pow(scale.y(), multiplier)); - VMatrix m; - m.translate(mPosition.value(frameNo) * multiplier) - .translate(mAnchor.value(frameNo)) - .scale(scale) - .rotate(mRotation.value(frameNo) * multiplier) - .translate(-mAnchor.value(frameNo)); - - return m; -} - -VMatrix model::Transform::Data::matrix(int frameNo, bool autoOrient) const -{ - VMatrix m; - VPointF position; - if (mExtra && mExtra->mSeparate) { - position.setX(mExtra->mSeparateX.value(frameNo)); - position.setY(mExtra->mSeparateY.value(frameNo)); - } else { - position = mPosition.value(frameNo); - } - - float angle = autoOrient ? mPosition.angle(frameNo) : 0; - if (mExtra && mExtra->m3DData) { - m.translate(position) - .rotate(mExtra->m3DRz.value(frameNo) + angle) - .rotate(mExtra->m3DRy.value(frameNo), VMatrix::Axis::Y) - .rotate(mExtra->m3DRx.value(frameNo), VMatrix::Axis::X) - .scale(mScale.value(frameNo) / 100.f) - .translate(-mAnchor.value(frameNo)); - } else { - m.translate(position) - .rotate(mRotation.value(frameNo) + angle) - .scale(mScale.value(frameNo) / 100.f) - .translate(-mAnchor.value(frameNo)); - } - return m; -} - -void model::Dash::getDashInfo(int frameNo, std::vector &result) const -{ - result.clear(); - - if (mData.size() <= 1) return; - - if (result.capacity() < mData.size()) result.reserve(mData.size() + 1); - - for (const auto &elm : mData) result.push_back(elm.value(frameNo)); - - // if the size is even then we are missing last - // gap information which is same as the last dash value - // copy it from the last dash value. - // NOTE: last value is the offset and last-1 is the last dash value. - auto size = result.size(); - if ((size % 2) == 0) { - // copy offset value to end. - result.push_back(result.back()); - // copy dash value to gap. - result[size - 1] = result[size - 2]; - } -} - -/** - * Both the color stops and opacity stops are in the same array. - * There are {@link #colorPoints} colors sequentially as: - * [ - * ..., - * position, - * red, - * green, - * blue, - * ... - * ] - * - * The remainder of the array is the opacity stops sequentially as: - * [ - * ..., - * position, - * opacity, - * ... - * ] - */ -void model::Gradient::populate(VGradientStops &stops, int frameNo) const -{ - model::Gradient::Data gradData = mGradient.value(frameNo); - auto size = gradData.mGradient.size(); - float * ptr = gradData.mGradient.data(); - if (ptr == nullptr) { - return; - } - int colorPoints = mColorPoints; - if (colorPoints < 0 || colorPoints > size / 4) { // for legacy bodymovin (ref: lottie-android) - colorPoints = int(size / 4); - } - auto opacityArraySize = size - colorPoints * 4; - if (opacityArraySize % 2 != 0 || (colorPoints > opacityArraySize / 2 && opacityArraySize < 4)) { - opacityArraySize = 0; - } - float *opacityPtr = ptr + (colorPoints * 4); - stops.clear(); - for (int i = 0; i < colorPoints; i++) { - float colorStop = ptr[0]; -#ifdef BIG_ENDIAN_COLORS - model::Color color = model::Color(ptr[3], ptr[2], ptr[1]); -#else - model::Color color = model::Color(ptr[1], ptr[2], ptr[3]); -#endif - color.colorMap = colorMap; - if (opacityArraySize) { - float opacity = getOpacityAtPosition(opacityPtr, opacityArraySize, colorStop); - stops.push_back(std::make_pair(colorStop, color.toColor(opacity))); - } else { - stops.push_back(std::make_pair(colorStop, color.toColor())); - } - ptr += 4; - if (stops.empty()) { - stops.push_back(std::make_pair(0.0f, VColor(255, 255, 255, 255))); - } - } -} - -float model::Gradient::getOpacityAtPosition(float *opacities, size_t opacityArraySize, float position) -{ - for (size_t i = 2; i < opacityArraySize; i += 2) - { - float lastPosition = opacities[i - 2]; - float thisPosition = opacities[i]; - if (opacities[i] >= position) { - float progress = (position - lastPosition) / (thisPosition - lastPosition); - progress = progress < 0.0f ? 0.0f : 1.0f < progress ? 1.0f : progress; //clamp(progress, 0, 1) - return opacities[i - 1] + progress * (opacities[i + 1] - opacities[i - 1]); - } - } - return 0.0f; -} - -void model::Gradient::update(std::unique_ptr &grad, int frameNo) -{ - bool init = false; - if (!grad) { - if (mGradientType == 1) - grad = std::make_unique(VGradient::Type::Linear); - else - grad = std::make_unique(VGradient::Type::Radial); - grad->mSpread = VGradient::Spread::Pad; - init = true; - } - - if (!mGradient.isStatic() || init) { - populate(grad->mStops, frameNo); - } - - if (mGradientType == 1) { // linear gradient - VPointF start = mStartPoint.value(frameNo); - VPointF end = mEndPoint.value(frameNo); - grad->linear.x1 = start.x(); - grad->linear.y1 = start.y(); - grad->linear.x2 = end.x(); - grad->linear.y2 = end.y(); - } else { // radial gradient - VPointF start = mStartPoint.value(frameNo); - VPointF end = mEndPoint.value(frameNo); - grad->radial.cx = start.x(); - grad->radial.cy = start.y(); - grad->radial.cradius = - VLine::length(start.x(), start.y(), end.x(), end.y()); - /* - * Focal point is the point lives in highlight length distance from - * center along the line (start, end) and rotated by highlight angle. - * below calculation first finds the quadrant(angle) on which the point - * lives by applying inverse slope formula then adds the rotation angle - * to find the final angle. then point is retrived using circle equation - * of center, angle and distance. - */ - float progress = mHighlightLength.value(frameNo) / 100.0f; - if (vCompare(progress, 1.0f)) progress = 0.99f; - float startAngle = VLine(start, end).angle(); - float highlightAngle = mHighlightAngle.value(frameNo); - static constexpr float K_PI = 3.1415926f; - float angle = (startAngle + highlightAngle) * (K_PI / 180.0f); - grad->radial.fx = - grad->radial.cx + std::cos(angle) * progress * grad->radial.cradius; - grad->radial.fy = - grad->radial.cy + std::sin(angle) * progress * grad->radial.cradius; - // Lottie dosen't have any focal radius concept. - grad->radial.fradius = 0; - } -} - -void model::Asset::loadImageData(const std::string &data) -{ - if (!data.empty()) - mBitmap = VImageLoader::instance().load(data.c_str(), data.length()); -} - -void model::Asset::loadImagePath(const std::string &path) -{ - if (!path.empty()) mBitmap = VImageLoader::instance().load(path.c_str()); -} - -std::vector model::Composition::layerInfoList() const -{ - if (!mRootLayer || mRootLayer->mChildren.empty()) return {}; - - std::vector result; - - result.reserve(mRootLayer->mChildren.size()); - - for (auto it : mRootLayer->mChildren) { - auto layer = static_cast(it); - result.emplace_back(layer->name(), layer->mInFrame, layer->mOutFrame); - } - - return result; -} diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottiemodel.h b/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottiemodel.h deleted file mode 100644 index b210835e4..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottiemodel.h +++ /dev/null @@ -1,1344 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef LOTModel_H -#define LOTModel_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include "varenaalloc.h" -#include "vbezier.h" -#include "vbrush.h" -#include "vinterpolator.h" -#include "vmatrix.h" -#include "vpath.h" -#include "vpoint.h" -#include "vrect.h" -#include "colorreplace.h" - -V_USE_NAMESPACE - -namespace rlottie { - -namespace internal { - -using Marker = std::tuple; - -using LayerInfo = Marker; - -template -inline T lerp(const T &start, const T &end, float t) -{ - return start + t * (end - start); -} - -namespace model { - -enum class MatteType : uint8_t { None = 0, Alpha = 1, AlphaInv, Luma, LumaInv }; - -enum class BlendMode : uint8_t { - Normal = 0, - Multiply = 1, - Screen = 2, - OverLay = 3 -}; - - class hsv_model { - public: - hsv_model() { - h = 0.f; - s = 0.f; - v = 0.f; - } - float h; - float s; - float v; - }; - - class rgb_model { - public: - rgb_model() { - r = 0; - g = 0; - b = 0; - } - rgb_model(int32_t color) { -#ifdef BIG_ENDIAN_COLORS - r = (color & 0xff); - g = ((color >> 8) & 0xff); - b = ((color >> 16) & 0xff); -#else - r = ((color >> 16) & 0xff); - g = ((color >> 8) & 0xff); - b = (color & 0xff); -#endif - } - rgb_model(float fR, float fG, float fB) { - r = (uint8_t)(fR * 255) & 0xff; - g = (uint8_t)(fG * 255) & 0xff; - b = (uint8_t)(fB * 255) & 0xff; - } - void toFloat(float& fR, float& fG, float& fB) const { - fR = (float)r / 255.0f; - fG = (float)g / 255.0f; - fB = (float)b / 255.0f; - } - int32_t toInt32() const { -#ifdef BIG_ENDIAN_COLORS - return (int32_t)((r << 16) | (g << 8) | b); -#else - return (int32_t)(r | (g << 8) | (b << 16)); -#endif - } - uint8_t r; - uint8_t g; - uint8_t b; - }; - inline uint8_t clamp(float v) - { - if (v < 0.f) - return 0; - if (v > 255.f) - return 255; - return (uint8_t)v; - } - - inline float clampf(float v) - { - if (v < 0.f) - return 0.f; - if (v > 1.f) - return 1.f; - return v; - } - static hsv_model rgb2hsv(const rgb_model& color) - { - hsv_model out; - float min, max, delta; - - min = color.r < color.g ? color.r : color.g; - min = min < color.b ? min : color.b; - - max = color.r > color.g ? color.r : color.g; - max = max > color.b ? max : color.b; - - out.v = max / 255.0f; - delta = max - min; - if (delta < 0.00001f) - { - out.s = 0; - out.h = 0; - return out; - } - if (max > 0.0f) { - out.s = (delta / max); - } - else { - out.s = 0.0f; - out.h = NAN; - return out; - } - if (color.r >= max) - out.h = (color.g - color.b) / delta; - else - if (color.g >= max) - out.h = 2.0f + (color.b - color.r) / delta; - else - out.h = 4.0f + (color.r - color.g) / delta; - - out.h *= 60.0f; - - if (out.h < 0.0) - out.h += 360.0f; - - return out; - } - - static rgb_model change_hsv_c(const rgb_model& color, const float fHue, const float fSat, const float fVal) - { - rgb_model out; - const float cosA = fSat * cos(fHue * 3.14159265f / 180); - const float sinA = fSat * sin(fHue * 3.14159265f / 180); - - const float aThird = 1.0f / 3.0f; - const float rootThird = sqrtf(aThird); - const float oneMinusCosA = (1.0f - cosA); - const float aThirdOfOneMinusCosA = aThird * oneMinusCosA; - const float rootThirdTimesSinA = rootThird * sinA; - const float plus = aThirdOfOneMinusCosA + rootThirdTimesSinA; - const float minus = aThirdOfOneMinusCosA - rootThirdTimesSinA; - - float matrix[3][3] = { - { cosA + oneMinusCosA / 3.0f , minus , plus }, - { plus , cosA + aThirdOfOneMinusCosA , minus }, - { minus , plus , cosA + aThirdOfOneMinusCosA } - }; - out.r = clamp((color.r * matrix[0][0] + color.g * matrix[0][1] + color.b * matrix[0][2]) * fVal); - out.g = clamp((color.r * matrix[1][0] + color.g * matrix[1][1] + color.b * matrix[1][2]) * fVal); - out.b = clamp((color.r * matrix[2][0] + color.g * matrix[2][1] + color.b * matrix[2][2]) * fVal); - return out; - } - - class Color { - public: - Color() = default; - - Color(float red, float green, float blue) : r(red), g(green), b(blue) {} - - VColor toColor(float a = 1) { - float r1, g1, b1; - getColorReplacement(colorMap, *this, r1, g1, b1); - return VColor(uint8_t(255 * r1), uint8_t(255 * g1), uint8_t(255 * b1), - uint8_t(255 * a)); - } - - friend inline Color operator+(const Color& c1, const Color& c2); - - friend inline Color operator-(const Color& c1, const Color& c2); - - friend inline void - getColorReplacement(ColorReplace* colorMap, const Color& c, float& r, - float& g, float& b); - - public: - ColorReplace* colorMap{ nullptr }; - float r{ 1 }; - float g{ 1 }; - float b{ 1 }; - }; - - inline void switchColorModel(const std::map &colorMap, const Color& c, float& r, float& g, float& b) { - int32_t from = colorMap.begin()->first; - int32_t to = colorMap.begin()->second; - - hsv_model frm = rgb2hsv(rgb_model(from)); - hsv_model tom = rgb2hsv(rgb_model(to)); - hsv_model clrr = rgb2hsv(rgb_model(c.r, c.g, c.b)); - change_hsv_c(rgb_model(c.r, c.g, c.b), tom.h - frm.h, clampf(clrr.s + (tom.s - frm.s)), clampf(clrr.v + (tom.v - frm.v))).toFloat(r, g, b); - } - - inline void - getColorReplacement(ColorReplace* colorMap, const Color& c, float& r, float& g, float& b) { - if (colorMap != nullptr && !colorMap->colorMap.empty()) { - if (colorMap->useMoveColor) { - switchColorModel(colorMap->colorMap, c, r, g, b); - return; - } - auto it = colorMap->colorMap.find(rgb_model(c.r, c.g, c.b).toInt32()); - if (it != colorMap->colorMap.end()) { - rgb_model(it->second).toFloat(r, g, b); - return; - } - } - r = c.r; - g = c.g; - b = c.b; - } - -inline Color operator-(const Color &c1, const Color &c2) -{ - float r1, g1, b1; - float r2, g2, b2; - getColorReplacement(c1.colorMap, c1, r1, g1, b1); - getColorReplacement(c2.colorMap, c2, r2, g2, b2); - return Color(r1 - r2, g1 - g2, b1 - b2); -} -inline Color operator+(const Color &c1, const Color &c2) -{ - float r1, g1, b1; - float r2, g2, b2; - getColorReplacement(c1.colorMap, c1, r1, g1, b1); - getColorReplacement(c2.colorMap, c2, r2, g2, b2); - return Color(r1 + r2, g1 + g2, b1 + b2); -} - -inline const Color operator*(const Color &c, float m) -{ - float r1, g1, b1; - getColorReplacement(c.colorMap, c, r1, g1, b1); - return Color(r1 * m, g1 * m, b1 * m); -} - -inline const Color operator*(float m, const Color &c) -{ - float r1, g1, b1; - getColorReplacement(c.colorMap, c, r1, g1, b1); - return Color(r1 * m, g1 * m, b1 * m); -} - -struct PathData { - std::vector mPoints; - bool mClosed = false; /* "c" */ - void reserve(size_t size) { mPoints.reserve(mPoints.size() + size); } - static void lerp(const PathData &start, const PathData &end, float t, - VPath &result) - { - result.reset(); - // test for empty animation data. - if (start.mPoints.empty() || end.mPoints.empty()) - { - return; - } - auto size = std::min(start.mPoints.size(), end.mPoints.size()); - /* reserve exact memory requirement at once - * ptSize = size + 1(size + close) - * elmSize = size/3 cubic + 1 move + 1 close - */ - result.reserve(size + 1, size / 3 + 2); - result.moveTo(start.mPoints[0] + - t * (end.mPoints[0] - start.mPoints[0])); - for (size_t i = 1; i < size; i += 3) { - result.cubicTo( - start.mPoints[i] + t * (end.mPoints[i] - start.mPoints[i]), - start.mPoints[i + 1] + - t * (end.mPoints[i + 1] - start.mPoints[i + 1]), - start.mPoints[i + 2] + - t * (end.mPoints[i + 2] - start.mPoints[i + 2])); - } - if (start.mClosed) result.close(); - } - void toPath(VPath &path) const - { - path.reset(); - - if (mPoints.empty()) return; - - auto size = mPoints.size(); - auto points = mPoints.data(); - /* reserve exact memory requirement at once - * ptSize = size + 1(size + close) - * elmSize = size/3 cubic + 1 move + 1 close - */ - path.reserve(size + 1, size / 3 + 2); - path.moveTo(points[0]); - for (size_t i = 1; i < size; i += 3) { - path.cubicTo(points[i], points[i + 1], points[i + 2]); - } - if (mClosed) path.close(); - } -}; - -template -struct Value { - T start_; - T end_; - T at(float t) const { return lerp(start_, end_, t); } - float angle(float) const { return 0; } - void cache() {} -}; - -struct Position; - -template -struct Value { - T start_; - T end_; - T inTangent_; - T outTangent_; - float length_{0}; - bool hasTangent_{false}; - - void cache() - { - if (hasTangent_) { - inTangent_ = end_ + inTangent_; - outTangent_ = start_ + outTangent_; - length_ = VBezier::fromPoints(start_, outTangent_, inTangent_, end_) - .length(); - if (vIsZero(length_)) { - // this segment has zero length. - // so disable expensive path computaion. - hasTangent_ = false; - } - } - } - - T at(float t) const - { - if (hasTangent_) { - /* - * position along the path calcualated - * using bezier at progress length (t * bezlen) - */ - VBezier b = - VBezier::fromPoints(start_, outTangent_, inTangent_, end_); - return b.pointAt(b.tAtLength(t * length_, length_)); - } - return lerp(start_, end_, t); - } - - float angle(float t) const - { - if (hasTangent_) { - VBezier b = - VBezier::fromPoints(start_, outTangent_, inTangent_, end_); - return b.angleAt(b.tAtLength(t * length_, length_)); - } - return 0; - } -}; - -template -class KeyFrames { -public: - struct Frame { - float progress(int frameNo) const - { - return interpolator_ ? interpolator_->value((frameNo - start_) / - (end_ - start_)) - : 0; - } - T value(int frameNo) const { return value_.at(progress(frameNo)); } - float angle(int frameNo) const - { - return value_.angle(progress(frameNo)); - } - - float start_{0}; - float end_{0}; - VInterpolator *interpolator_{nullptr}; - Value value_; - }; - - T value(int frameNo) const - { - if (!frames_.empty() && frames_.front().start_ >= frameNo) - return frames_.front().value_.start_; - if (!frames_.empty() && frames_.back().end_ <= frameNo) return frames_.back().value_.end_; - - for (const auto &keyFrame : frames_) { - if (frameNo >= keyFrame.start_ && frameNo < keyFrame.end_) { - return keyFrame.value(frameNo); - } - } - return {}; - } - - float angle(int frameNo) const - { - if (frames_.empty() ||(frames_.front().start_ >= frameNo) || - (frames_.back().end_ <= frameNo)) - return 0; - - for (const auto &frame : frames_) { - if (frameNo >= frame.start_ && frameNo < frame.end_) - return frame.angle(frameNo); - } - return 0; - } - - bool changed(int prevFrame, int curFrame) const - { - if (frames_.empty()) { - return false; - } - auto first = frames_.front().start_; - auto last = frames_.back().end_; - - return !((first > prevFrame && first > curFrame) || - (last < prevFrame && last < curFrame)); - } - void cache() - { - for (auto &e : frames_) e.value_.cache(); - } - -public: - std::vector frames_; -}; - -template -class Property { -public: - using Animation = KeyFrames; - - Property() { construct(impl_.value_, {}); } - explicit Property(T value) { construct(impl_.value_, std::move(value)); } - - const Animation &animation() const { return *(impl_.animation_.get()); } - const T & value() const { return impl_.value_; } - - Animation &animation() - { - if (isValue_) { - destroy(); - construct(impl_.animation_, std::make_unique()); - isValue_ = false; - } - return *(impl_.animation_.get()); - } - - T &value() - { - assert(isValue_); - return impl_.value_; - } - - Property(Property &&other) noexcept - { - if (!other.isValue_) { - construct(impl_.animation_, std::move(other.impl_.animation_)); - isValue_ = false; - } else { - construct(impl_.value_, std::move(other.impl_.value_)); - isValue_ = true; - } - } - // delete special member functions - Property(const Property &) = delete; - Property &operator=(const Property &) = delete; - Property &operator=(Property &&) = delete; - - ~Property() { destroy(); } - - bool isStatic() const { return isValue_; } - - T value(int frameNo) const - { - return isStatic() ? value() : animation().value(frameNo); - } - - // special function only for type T=PathData - template - auto value(int frameNo, VPath &path) const -> - typename std::enable_if_t::value, void> - { - if (isStatic()) { - value().toPath(path); - } else { - const auto &vec = animation().frames_; - if (!vec.empty() && vec.front().start_ >= frameNo) - return vec.front().value_.start_.toPath(path); - if (!vec.empty() && vec.back().end_ <= frameNo) - return vec.back().value_.end_.toPath(path); - - for (const auto &keyFrame : vec) { - if (frameNo >= keyFrame.start_ && frameNo < keyFrame.end_) { - T::lerp(keyFrame.value_.start_, keyFrame.value_.end_, - keyFrame.progress(frameNo), path); - } - } - } - } - - float angle(int frameNo) const - { - return isStatic() ? 0 : animation().angle(frameNo); - } - - bool changed(int prevFrame, int curFrame) const - { - return !isStatic() && animation().changed(prevFrame, curFrame); - } - void cache() - { - if (!isStatic()) animation().cache(); - } - -private: - template - void construct(Tp &member, Tp &&val) - { - new (&member) Tp(std::move(val)); - } - - void destroy() - { - if (isValue_) { - impl_.value_.~T(); - } else { - using std::unique_ptr; - impl_.animation_.~unique_ptr(); - } - } - union details { - std::unique_ptr animation_; - T value_; - details(){}; - details(const details &) = delete; - details(details &&) = delete; - details &operator=(details &&) = delete; - details &operator=(const details &) = delete; - ~details() noexcept {}; - } impl_; - bool isValue_{true}; -}; - -class Path; -struct PathData; -struct Dash { - std::vector> mData; - bool empty() const { return mData.empty(); } - size_t size() const { return mData.size(); } - bool isStatic() const - { - for (const auto &elm : mData) - if (!elm.isStatic()) return false; - return true; - } - void getDashInfo(int frameNo, std::vector &result) const; -}; - -class Mask { -public: - enum class Mode { None, Add, Substarct, Intersect, Difference }; - float opacity(int frameNo) const - { - return mOpacity.value(frameNo) / 100.0f; - } - bool isStatic() const { return mIsStatic; } - -public: - Property mShape; - Property mOpacity{100}; - bool mInv{false}; - bool mIsStatic{true}; - Mask::Mode mMode; -}; - -class Object { -public: - enum class Type : uint8_t { - Composition = 1, - Layer, - Group, - Transform, - Fill, - Stroke, - GFill, - GStroke, - Rect, - Ellipse, - Path, - Polystar, - Trim, - Repeater, - RoundedCorner - }; - - explicit Object(Object::Type type) : mPtr(nullptr) - { - mData._type = type; - mData._static = true; - mData._shortString = true; - mData._hidden = false; - } - ~Object() noexcept - { - if (!shortString() && mPtr) free(mPtr); - } - Object(const Object &) = delete; - Object &operator=(const Object &) = delete; - - void setStatic(bool value) { mData._static = value; } - bool isStatic() const { return mData._static; } - bool hidden() const { return mData._hidden; } - void setHidden(bool value) { mData._hidden = value; } - void setType(Object::Type type) { mData._type = type; } - Object::Type type() const { return mData._type; } - void setName(const char *name) - { - if (name) { - auto len = strlen(name); - if (len < maxShortStringLength) { - setShortString(true); - strncpy(mData._buffer, name, len + 1); - } else { - setShortString(false); - mPtr = strdup(name); - } - } - } - const char *name() const { return shortString() ? mData._buffer : mPtr; } - -private: - static constexpr uint8_t maxShortStringLength = 14; - void setShortString(bool value) { mData._shortString = value; } - bool shortString() const { return mData._shortString; } - struct Data { - char _buffer[maxShortStringLength]; - Object::Type _type; - bool _static : 1; - bool _hidden : 1; - bool _shortString : 1; - }; - union { - Data mData; - char *mPtr{nullptr}; - }; -}; - -struct Asset { - enum class Type : uint8_t { Precomp, Image, Char }; - bool isStatic() const { return mStatic; } - void setStatic(bool value) { mStatic = value; } - VBitmap bitmap() const { return mBitmap; } - void loadImageData(const std::string &data); - void loadImagePath(const std::string &Path); - Type mAssetType{Type::Precomp}; - bool mStatic{true}; - std::string mRefId; // ref id - std::vector mLayers; - // image asset data - int mWidth{0}; - int mHeight{0}; - VBitmap mBitmap; -}; - -class Layer; - -class Composition : public Object { -public: - Composition() : Object(Object::Type::Composition) {} - std::vector layerInfoList() const; - const std::vector &markers() const { return mMarkers; } - double duration() const - { - return frameDuration() / frameRate(); // in second - } - size_t frameAtPos(double pos) const - { - if (pos < 0) pos = 0; - if (pos > 1) pos = 1; - return size_t(round(pos * frameDuration())); - } - long frameAtTime(double timeInSec) const - { - return long(frameAtPos(timeInSec / duration())); - } - size_t totalFrame() const { return mEndFrame - mStartFrame + 1; } - long frameDuration() const { return mEndFrame - mStartFrame; } - float frameRate() const { return mFrameRate; } - size_t startFrame() const { return mStartFrame; } - size_t endFrame() const { return mEndFrame; } - VSize size() const { return mSize; } - void processRepeaterObjects() const; - void updateStats(); - -public: - struct Stats { - uint16_t precompLayerCount{0}; - uint16_t solidLayerCount{0}; - uint16_t shapeLayerCount{0}; - uint16_t imageLayerCount{0}; - uint16_t nullLayerCount{0}; - }; - -public: - std::string mVersion; - VSize mSize; - long mStartFrame{0}; - long mEndFrame{0}; - float mFrameRate{60}; - BlendMode mBlendMode{BlendMode::Normal}; - Layer * mRootLayer{nullptr}; - std::unordered_map mAssets; - - std::vector mMarkers; - VArenaAlloc mArenaAlloc{2048}; - Stats mStats; -}; - -class Transform : public Object { -public: - struct Data { - struct Extra { - Property m3DRx{0}; - Property m3DRy{0}; - Property m3DRz{0}; - Property mSeparateX{0}; - Property mSeparateY{0}; - bool mSeparate{false}; - bool m3DData{false}; - }; - VMatrix matrix(int frameNo, bool autoOrient = false) const; - float opacity(int frameNo) const - { - return mOpacity.value(frameNo) / 100.0f; - } - void createExtraData() - { - if (!mExtra) mExtra = std::make_unique(); - } - Property mRotation{0}; /* "r" */ - Property mScale{{100, 100}}; /* "s" */ - Property mPosition; /* "p" */ - Property mAnchor; /* "a" */ - Property mOpacity{100}; /* "o" */ - std::unique_ptr mExtra; - }; - - Transform() : Object(Object::Type::Transform) {} - void set(Transform::Data *data, bool staticFlag) - { - setStatic(staticFlag); - if (isStatic()) { - new (&impl.mStaticData) - StaticData(data->matrix(0), data->opacity(0)); - } else { - impl.mData = data; - } - } - VMatrix matrix(int frameNo, bool autoOrient = false) const - { - if (isStatic()) return impl.mStaticData.mMatrix; - return impl.mData->matrix(frameNo, autoOrient); - } - float opacity(int frameNo) const - { - if (isStatic()) return impl.mStaticData.mOpacity; - return impl.mData->opacity(frameNo); - } - Transform(const Transform &) = delete; - Transform(Transform &&) = delete; - Transform &operator=(Transform &) = delete; - Transform &operator=(Transform &&) = delete; - ~Transform() noexcept { destroy(); } - -private: - void destroy() - { - if (isStatic()) { - impl.mStaticData.~StaticData(); - } - } - struct StaticData { - StaticData(VMatrix &&m, float opacity) - : mOpacity(opacity), mMatrix(std::move(m)) - { - } - float mOpacity; - VMatrix mMatrix; - }; - union details { - Data * mData{nullptr}; - StaticData mStaticData; - details(){}; - details(const details &) = delete; - details(details &&) = delete; - details &operator=(details &&) = delete; - details &operator=(const details &) = delete; - ~details() noexcept {}; - } impl; -}; - -class Group : public Object { -public: - Group() : Object(Object::Type::Group) {} - explicit Group(Object::Type type) : Object(type) {} - -public: - std::vector mChildren; - Transform * mTransform{nullptr}; -}; - -class Layer : public Group { -public: - enum class Type : uint8_t { - Precomp = 0, - Solid = 1, - Image = 2, - Null = 3, - Shape = 4, - Text = 5 - }; - Layer() : Group(Object::Type::Layer) {} - bool hasRoundedCorner() const noexcept { return mHasRoundedCorner; } - bool hasPathOperator() const noexcept { return mHasPathOperator; } - bool hasGradient() const noexcept { return mHasGradient; } - bool hasMask() const noexcept { return mHasMask; } - bool hasRepeater() const noexcept { return mHasRepeater; } - int id() const noexcept { return mId; } - int parentId() const noexcept { return mParentId; } - bool hasParent() const noexcept { return mParentId != -1; } - int inFrame() const noexcept { return mInFrame; } - int outFrame() const noexcept { return mOutFrame; } - int startFrame() const noexcept { return mStartFrame; } - Color solidColor() const noexcept - { - return mExtra ? mExtra->mSolidColor : Color(); - } - bool autoOrient() const noexcept { return mAutoOrient; } - int timeRemap(int frameNo) const; - VSize layerSize() const { return mLayerSize; } - bool precompLayer() const { return mLayerType == Type::Precomp; } - VMatrix matrix(int frameNo) const - { - return mTransform ? mTransform->matrix(frameNo, autoOrient()) - : VMatrix{}; - } - float opacity(int frameNo) const - { - return mTransform ? mTransform->opacity(frameNo) : 1.0f; - } - Asset *asset() const { return mExtra ? mExtra->mAsset : nullptr; } - struct Extra { - Color mSolidColor; - std::string mPreCompRefId; - Property mTimeRemap; /* "tm" */ - Composition * mCompRef{nullptr}; - Asset * mAsset{nullptr}; - std::vector mMasks; - }; - - Layer::Extra *extra() - { - if (!mExtra) mExtra = std::make_unique(); - return mExtra.get(); - } - -public: - MatteType mMatteType{MatteType::None}; - Type mLayerType{Layer::Type::Null}; - BlendMode mBlendMode{BlendMode::Normal}; - bool mHasRoundedCorner{false}; - bool mHasPathOperator{false}; - bool mHasMask{false}; - bool mHasRepeater{false}; - bool mHasGradient{false}; - bool mAutoOrient{false}; - VSize mLayerSize; - int mParentId{-1}; // Lottie the id of the parent in the composition - int mId{-1}; // Lottie the group id used for parenting. - float mTimeStreatch{1.0f}; - int mInFrame{0}; - int mOutFrame{0}; - int mStartFrame{0}; - std::unique_ptr mExtra{nullptr}; -}; - -/** - * TimeRemap has the value in time domain(in sec) - * To get the proper mapping first we get the mapped time at the current frame - * Number then we need to convert mapped time to frame number using the - * composition time line Ex: at frame 10 the mappend time is 0.5(500 ms) which - * will be convert to frame number 30 if the frame rate is 60. or will result to - * frame number 15 if the frame rate is 30. - */ -inline int Layer::timeRemap(int frameNo) const -{ - /* - * only consider startFrame() when there is no timeRemap. - * when a layer has timeremap bodymovin updates the startFrame() - * of all child layer so we don't have to take care of it. - */ - if (!mExtra || mExtra->mTimeRemap.isStatic()) - frameNo = frameNo - startFrame(); - else - frameNo = - mExtra->mCompRef->frameAtTime(mExtra->mTimeRemap.value(frameNo)); - /* Apply time streatch if it has any. - * Time streatch is just a factor by which the animation will speedup or - * slow down with respect to the overal animation. Time streach factor is - * already applied to the layers inFrame and outFrame. - * @TODO need to find out if timestreatch also affects the in and out frame - * of the child layers or not. */ - return int(frameNo / mTimeStreatch); -} - -class Stroke : public Object { -public: - Stroke() : Object(Object::Type::Stroke) {} - Color color(int frameNo) const { return mColor.value(frameNo); } - float opacity(int frameNo) const - { - return mOpacity.value(frameNo) / 100.0f; - } - float strokeWidth(int frameNo) const { return mWidth.value(frameNo); } - CapStyle capStyle() const { return mCapStyle; } - JoinStyle joinStyle() const { return mJoinStyle; } - float miterLimit() const { return mMiterLimit; } - bool hasDashInfo() const { return !mDash.empty(); } - void getDashInfo(int frameNo, std::vector &result) const - { - return mDash.getDashInfo(frameNo, result); - } - -public: - Property mColor; /* "c" */ - Property mOpacity{100}; /* "o" */ - Property mWidth{0}; /* "w" */ - CapStyle mCapStyle{CapStyle::Flat}; /* "lc" */ - JoinStyle mJoinStyle{JoinStyle::Miter}; /* "lj" */ - float mMiterLimit{0}; /* "ml" */ - Dash mDash; - bool mEnabled{true}; /* "fillEnabled" */ -}; - -class Gradient : public Object { -public: - class Data { - public: - friend inline Gradient::Data operator+(const Gradient::Data &g1, - const Gradient::Data &g2); - friend inline Gradient::Data operator-(const Gradient::Data &g1, - const Gradient::Data &g2); - friend inline Gradient::Data operator*(float m, - const Gradient::Data &g); - - public: - std::vector mGradient; - }; - explicit Gradient(Object::Type type) : Object(type) {} - inline float opacity(int frameNo) const - { - return mOpacity.value(frameNo) / 100.0f; - } - void update(std::unique_ptr &grad, int frameNo); - -private: - void populate(VGradientStops &stops, int frameNo) const; - static float getOpacityAtPosition(float *opacities, size_t opacityArraySize, float position); - -public: - int mGradientType{1}; /* "t" Linear=1 , Radial = 2*/ - Property mStartPoint; /* "s" */ - Property mEndPoint; /* "e" */ - Property mHighlightLength{0}; /* "h" */ - Property mHighlightAngle{0}; /* "a" */ - Property mOpacity{100}; /* "o" */ - Property mGradient; /* "g" */ - int mColorPoints{-1}; - bool mEnabled{true}; /* "fillEnabled" */ - ColorReplace* colorMap{ nullptr }; -}; - -class GradientStroke : public Gradient { -public: - GradientStroke() : Gradient(Object::Type::GStroke) {} - float width(int frameNo) const { return mWidth.value(frameNo); } - CapStyle capStyle() const { return mCapStyle; } - JoinStyle joinStyle() const { return mJoinStyle; } - float miterLimit() const { return mMiterLimit; } - bool hasDashInfo() const { return !mDash.empty(); } - void getDashInfo(int frameNo, std::vector &result) const - { - return mDash.getDashInfo(frameNo, result); - } - -public: - Property mWidth; /* "w" */ - CapStyle mCapStyle{CapStyle::Flat}; /* "lc" */ - JoinStyle mJoinStyle{JoinStyle::Miter}; /* "lj" */ - float mMiterLimit{0}; /* "ml" */ - Dash mDash; -}; - -class GradientFill : public Gradient { -public: - GradientFill() : Gradient(Object::Type::GFill) {} - FillRule fillRule() const { return mFillRule; } - -public: - FillRule mFillRule{FillRule::Winding}; /* "r" */ -}; - -class Fill : public Object { -public: - Fill() : Object(Object::Type::Fill) {} - Color color(int frameNo) const { return mColor.value(frameNo); } - float opacity(int frameNo) const - { - return mOpacity.value(frameNo) / 100.0f; - } - FillRule fillRule() const { return mFillRule; } - -public: - FillRule mFillRule{FillRule::Winding}; /* "r" */ - bool mEnabled{true}; /* "fillEnabled" */ - Property mColor; /* "c" */ - Property mOpacity{100}; /* "o" */ -}; - -class Shape : public Object { -public: - explicit Shape(Object::Type type) : Object(type) {} - VPath::Direction direction() - { - return (mDirection == 3) ? VPath::Direction::CCW : VPath::Direction::CW; - } - -public: - int mDirection{1}; -}; - -class Path : public Shape { -public: - Path() : Shape(Object::Type::Path) {} - -public: - Property mShape; -}; - -class RoundedCorner : public Object { -public: - RoundedCorner() : Object(Object::Type::RoundedCorner) {} - float radius(int frameNo) const { return mRadius.value(frameNo);} -public: - Property mRadius{0}; -}; - -class Rect : public Shape { -public: - Rect() : Shape(Object::Type::Rect) {} - float roundness(int frameNo) - { - return mRoundedCorner ? mRoundedCorner->radius(frameNo) : - mRound.value(frameNo); - } - - bool roundnessChanged(int prevFrame, int curFrame) - { - return mRoundedCorner ? mRoundedCorner->mRadius.changed(prevFrame, curFrame) : - mRound.changed(prevFrame, curFrame); - } -public: - RoundedCorner* mRoundedCorner{nullptr}; - Property mPos; - Property mSize; - Property mRound{0}; -}; - -class Ellipse : public Shape { -public: - Ellipse() : Shape(Object::Type::Ellipse) {} - -public: - Property mPos; - Property mSize; -}; - -class Polystar : public Shape { -public: - enum class PolyType { Star = 1, Polygon = 2 }; - Polystar() : Shape(Object::Type::Polystar) {} - -public: - Polystar::PolyType mPolyType{PolyType::Polygon}; - Property mPos; - Property mPointCount{0}; - Property mInnerRadius{0}; - Property mOuterRadius{0}; - Property mInnerRoundness{0}; - Property mOuterRoundness{0}; - Property mRotation{0}; -}; - -class Repeater : public Object { -public: - struct Transform { - VMatrix matrix(int frameNo, float multiplier) const; - float startOpacity(int frameNo) const - { - return mStartOpacity.value(frameNo) / 100; - } - float endOpacity(int frameNo) const - { - return mEndOpacity.value(frameNo) / 100; - } - bool isStatic() const - { - return mRotation.isStatic() && mScale.isStatic() && - mPosition.isStatic() && mAnchor.isStatic() && - mStartOpacity.isStatic() && mEndOpacity.isStatic(); - } - Property mRotation{0}; /* "r" */ - Property mScale{{100, 100}}; /* "s" */ - Property mPosition; /* "p" */ - Property mAnchor; /* "a" */ - Property mStartOpacity{100}; /* "so" */ - Property mEndOpacity{100}; /* "eo" */ - }; - Repeater() : Object(Object::Type::Repeater) {} - Group *content() const { return mContent ? mContent : nullptr; } - void setContent(Group *content) { mContent = content; } - int maxCopies() const { return int(mMaxCopies); } - float copies(int frameNo) const { return mCopies.value(frameNo); } - float offset(int frameNo) const { return mOffset.value(frameNo); } - bool processed() const { return mProcessed; } - void markProcessed() { mProcessed = true; } - -public: - Group * mContent{nullptr}; - Transform mTransform; - Property mCopies{0}; - Property mOffset{0}; - float mMaxCopies{0.0}; - bool mProcessed{false}; -}; - -class Trim : public Object { -public: - struct Segment { - float start{0}; - float end{0}; - Segment() = default; - explicit Segment(float s, float e) : start(s), end(e) {} - }; - enum class TrimType { Simultaneously, Individually }; - Trim() : Object(Object::Type::Trim) {} - - void updateTrimStartValue(float start) - { - mStart.value() = start; - } - - void updateTrimEndValue(VPointF pos) - { - for (auto &keyFrame : mEnd.animation().frames_) { - keyFrame.value_.start_ = pos.x(); - keyFrame.value_.end_ = pos.y(); - } - } - - /* - * if start > end vector trims the path as a loop ( 2 segment) - * if start < end vector trims the path without loop ( 1 segment). - * if no offset then there is no loop. - */ - Segment segment(int frameNo) const - { - float start = mStart.value(frameNo) / 100.0f; - float end = mEnd.value(frameNo) / 100.0f; - float offset = std::fmod(mOffset.value(frameNo), 360.0f) / 360.0f; - - float diff = std::abs(start - end); - if (vCompare(diff, 0.0f)) return Segment(0, 0); - if (vCompare(diff, 1.0f)) return Segment(0, 1); - - if (offset > 0) { - start += offset; - end += offset; - if (start <= 1 && end <= 1) { - return noloop(start, end); - } else if (start > 1 && end > 1) { - return noloop(start - 1, end - 1); - } else { - return (start > 1) ? loop(start - 1, end) - : loop(start, end - 1); - } - } else { - start += offset; - end += offset; - if (start >= 0 && end >= 0) { - return noloop(start, end); - } else if (start < 0 && end < 0) { - return noloop(1 + start, 1 + end); - } else { - return (start < 0) ? loop(1 + start, end) - : loop(start, 1 + end); - } - } - } - Trim::TrimType type() const { return mTrimType; } - -private: - Segment noloop(float start, float end) const - { - assert(start >= 0); - assert(end >= 0); - Segment s; - s.start = std::min(start, end); - s.end = std::max(start, end); - return s; - } - Segment loop(float start, float end) const - { - assert(start >= 0); - assert(end >= 0); - Segment s; - s.start = std::max(start, end); - s.end = std::min(start, end); - return s; - } - -public: - Property mStart{0}; - Property mEnd{0}; - Property mOffset{0}; - Trim::TrimType mTrimType{TrimType::Simultaneously}; -}; - -inline Gradient::Data operator+(const Gradient::Data &g1, - const Gradient::Data &g2) -{ - if (g1.mGradient.size() != g2.mGradient.size()) return g1; - - Gradient::Data newG; - newG.mGradient = g1.mGradient; - - auto g2It = g2.mGradient.begin(); - for (auto &i : newG.mGradient) { - i = i + *g2It; - g2It++; - } - - return newG; -} - -inline Gradient::Data operator-(const Gradient::Data &g1, - const Gradient::Data &g2) -{ - if (g1.mGradient.size() != g2.mGradient.size()) return g1; - Gradient::Data newG; - newG.mGradient = g1.mGradient; - - auto g2It = g2.mGradient.begin(); - for (auto &i : newG.mGradient) { - i = i - *g2It; - g2It++; - } - - return newG; -} - -inline Gradient::Data operator*(float m, const Gradient::Data &g) -{ - Gradient::Data newG; - newG.mGradient = g.mGradient; - - for (auto &i : newG.mGradient) { - i = i * m; - } - return newG; -} - -using ColorFilter = std::function; - -std::shared_ptr loadFromData(const char*jsonData, ColorReplace* colorReplacement, const std::string &resourcePath); - -std::shared_ptr parse(const char *str, std::string dir_path, - ColorReplace *colorReplacement); - -} // namespace model - -} // namespace internal - -} // namespace rlottie - -#endif // LOTModel_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottieparser.cpp b/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottieparser.cpp deleted file mode 100644 index f7dcb0c4a..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/lottie/lottieparser.cpp +++ /dev/null @@ -1,2853 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -//#define DEBUG_PARSER - -// This parser implements JSON token-by-token parsing with an API that is -// more direct; we don't have to create handler object and -// callbacks. Instead, we retrieve values from the JSON stream by calling -// GetInt(), GetDouble(), GetString() and GetBool(), traverse into structures -// by calling EnterObject() and EnterArray(), and skip over unwanted data by -// calling SkipValue(). As we know the lottie file structure this way will be -// the efficient way of parsing the file. -// -// If you aren't sure of what's next in the JSON data, you can use PeekType() -// and PeekValue() to look ahead to the next object before reading it. -// -// If you call the wrong retrieval method--e.g. GetInt when the next JSON token -// is not an int, EnterObject or EnterArray when there isn't actually an object -// or array to read--the stream parsing will end immediately and no more data -// will be delivered. -// -// After calling EnterObject, you retrieve keys via NextObjectKey() and values -// via the normal getters. When NextObjectKey() returns null, you have exited -// the object, or you can call SkipObject() to skip to the end of the object -// immediately. If you fetch the entire object (i.e. NextObjectKey() returned -// null), you should not call SkipObject(). -// -// After calling EnterArray(), you must alternate between calling -// NextArrayValue() to see if the array has more data, and then retrieving -// values via the normal getters. You can call SkipArray() to skip to the end of -// the array immediately. If you fetch the entire array (i.e. NextArrayValue() -// returned null), you should not call SkipArray(). -// -// This parser uses in-situ strings, so the JSON buffer will be altered during -// the parse. - -#include - -#include "lottiemodel.h" -#include "colorreplace.h" -#include "rapidjson/document.h" - -RAPIDJSON_DIAG_PUSH -#ifdef __GNUC__ -RAPIDJSON_DIAG_OFF(effc++) -#endif - -using namespace rapidjson; - -using namespace rlottie::internal; - -class LookaheadParserHandler { -public: - bool Null() - { - st_ = kHasNull; - v_.SetNull(); - return true; - } - bool Bool(bool b) - { - st_ = kHasBool; - v_.SetBool(b); - return true; - } - bool Int(int i) - { - st_ = kHasNumber; - v_.SetInt(i); - return true; - } - bool Uint(unsigned u) - { - st_ = kHasNumber; - v_.SetUint(u); - return true; - } - bool Int64(int64_t i) - { - st_ = kHasNumber; - v_.SetInt64(i); - return true; - } - bool Uint64(uint64_t u) - { - st_ = kHasNumber; - v_.SetUint64(u); - return true; - } - bool Double(double d) - { - st_ = kHasNumber; - v_.SetDouble(d); - return true; - } - bool RawNumber(const char *, SizeType, bool) { return false; } - bool String(const char *str, SizeType length, bool) - { - st_ = kHasString; - v_.SetString(str, length); - return true; - } - bool StartObject() - { - st_ = kEnteringObject; - return true; - } - bool Key(const char *str, SizeType length, bool) - { - st_ = kHasKey; - v_.SetString(str, length); - return true; - } - bool EndObject(SizeType) - { - st_ = kExitingObject; - return true; - } - bool StartArray() - { - st_ = kEnteringArray; - return true; - } - bool EndArray(SizeType) - { - st_ = kExitingArray; - return true; - } - - void Error() - { - st_ = kError; - } -protected: - explicit LookaheadParserHandler(const char *str); - -protected: - enum LookaheadParsingState { - kInit, - kError, - kHasNull, - kHasBool, - kHasNumber, - kHasString, - kHasKey, - kEnteringObject, - kExitingObject, - kEnteringArray, - kExitingArray - }; - - Value v_; - LookaheadParsingState st_; - Reader r_; - InsituStringStream ss_; - - static const int parseFlags = kParseDefaultFlags | kParseInsituFlag; -}; - -class LottieParserImpl : public LookaheadParserHandler { -public: - LottieParserImpl(const char *str, std::string dir_path, ColorReplace *colorReplacement) - : LookaheadParserHandler(str), - colorMap(colorReplacement), - mDirPath(std::move(dir_path)) - { - } - bool VerifyType(); - bool ParseNext(); - -public: - VArenaAlloc &allocator() { return compRef->mArenaAlloc; } - bool EnterObject(); - bool EnterArray(); - const char * NextObjectKey(); - bool NextArrayValue(); - int GetInt(); - double GetDouble(); - const char * GetString(); - std::string GetStringObject(); - bool GetBool(); - void GetNull(); - - void SkipObject(); - void SkipArray(); - void SkipValue(); - Value *PeekValue(); - int PeekType() const; - bool IsValid() { return st_ != kError; } - - void Skip(const char *key); - model::BlendMode getBlendMode(); - CapStyle getLineCap(); - JoinStyle getLineJoin(); - FillRule getFillRule(); - model::Trim::TrimType getTrimType(); - model::MatteType getMatteType(); - model::Layer::Type getLayerType(); - - std::shared_ptr composition() const - { - return mComposition; - } - void parseComposition(); - void parseMarkers(); - void parseMarker(); - void parseAssets(model::Composition *comp); - model::Asset * parseAsset(); - void parseLayers(model::Composition *comp); - model::Layer * parseLayer(); - void parseMaskProperty(model::Layer *layer); - void parseShapesAttr(model::Layer *layer); - void parseObject(model::Group *parent); - model::Mask * parseMaskObject(); - model::Object * parseObjectTypeAttr(); - model::Object * parseGroupObject(); - model::Rect * parseRectObject(); - model::RoundedCorner * parseRoundedCorner(); - void updateRoundedCorner(model::Group *parent, model::RoundedCorner *rc); - - model::Ellipse * parseEllipseObject(); - model::Path * parseShapeObject(); - model::Polystar *parsePolystarObject(); - - model::Transform * parseTransformObject(bool ddd = false); - model::Fill * parseFillObject(); - model::GradientFill * parseGFillObject(); - model::Stroke * parseStrokeObject(); - model::GradientStroke *parseGStrokeObject(); - model::Trim * parseTrimObject(); - model::Repeater * parseReapeaterObject(); - - void parseGradientProperty(model::Gradient *gradient, const char *key); - - VPointF parseInperpolatorPoint(); - - void getValue(VPointF &pt); - void getValue(float &fval); - void getValue(model::Color &color); - void getValue(int &ival); - void getValue(model::PathData &shape); - void getValue(model::Gradient::Data &gradient); - void getValue(std::vector &v); - void getValue(model::Repeater::Transform &); - - template - bool parseKeyFrameValue(const char *, model::Value &) - { - return false; - } - - template - bool parseKeyFrameValue(const char * key, - model::Value &value); - template - void parseKeyFrame(model::KeyFrames &obj); - template - void parseProperty(model::Property &obj); - template - void parsePropertyHelper(model::Property &obj); - - void parseShapeProperty(model::Property &obj); - void parseDashProperty(model::Dash &dash); - - VInterpolator *interpolator(VPointF, VPointF, std::string); - - static model::Color toColor(const char *str); - - void resolveLayerRefs(); - void parsePathInfo(); - bool hasParsingError() const { return parsingError; }; - -private: - ColorReplace *colorMap{ nullptr }; - struct { - std::vector mInPoint; /* "i" */ - std::vector mOutPoint; /* "o" */ - std::vector mVertices; /* "v" */ - std::vector mResult; - bool mClosed{false}; - - void convert() - { - // shape data could be empty. - if (mInPoint.empty() || mOutPoint.empty() || mVertices.empty()) { - mResult.clear(); - return; - } - - /* - * Convert the AE shape format to - * list of bazier curves - * The final structure will be Move +size*Cubic + Cubic (if the path - * is closed one) - */ - if (mInPoint.size() != mOutPoint.size() || - mInPoint.size() != mVertices.size()) { - mResult.clear(); - } else { - auto size = mVertices.size(); - mResult.push_back(mVertices[0]); - for (size_t i = 1; i < size; i++) { - mResult.push_back( - mVertices[i - 1] + - mOutPoint[i - 1]); // CP1 = start + outTangent - mResult.push_back(mVertices[i] + - mInPoint[i]); // CP2 = end + inTangent - mResult.push_back(mVertices[i]); // end point - } - - if (mClosed) { - mResult.push_back( - mVertices[size - 1] + - mOutPoint[size - 1]); // CP1 = start + outTangent - mResult.push_back(mVertices[0] + - mInPoint[0]); // CP2 = end + inTangent - mResult.push_back(mVertices[0]); // end point - } - } - } - void reset() - { - mInPoint.clear(); - mOutPoint.clear(); - mVertices.clear(); - mResult.clear(); - mClosed = false; - } - void updatePath(VPath &out) - { - if (mResult.empty()) return; - - auto size = mResult.size(); - auto points = mResult.data(); - /* reserve exact memory requirement at once - * ptSize = size + 1(size + close) - * elmSize = size/3 cubic + 1 move + 1 close - */ - out.reserve(size + 1, size / 3 + 2); - out.moveTo(points[0]); - for (size_t i = 1; i < size; i += 3) { - out.cubicTo(points[i], points[i + 1], points[i + 2]); - } - if (mClosed) out.close(); - } - } mPathInfo; - -protected: - std::unordered_map mInterpolatorCache; - std::shared_ptr mComposition; - model::Composition * compRef{nullptr}; - model::Layer * curLayerRef{nullptr}; - std::vector mLayersToUpdate; - std::string mDirPath; - void SkipOut(int depth); - bool parsingError{false}; -}; - -LookaheadParserHandler::LookaheadParserHandler(const char *str) - : v_(), st_(kInit), ss_(const_cast(str)) -{ - r_.IterativeParseInit(); -} - -bool LottieParserImpl::VerifyType() -{ - /* Verify the media type is lottie json. - Could add more strict check. */ - return ParseNext(); -} - -bool LottieParserImpl::ParseNext() -{ - if (r_.HasParseError()) { - st_ = kError; - return false; - } - - if (!r_.IterativeParseNext(ss_, *this)) { - vCritical << "Lottie file parsing error"; - st_ = kError; - return false; - } - return true; -} - -bool LottieParserImpl::EnterObject() -{ - if (st_ != kEnteringObject) { - st_ = kError; - return false; - } - - ParseNext(); - return true; -} - -bool LottieParserImpl::EnterArray() -{ - if (st_ != kEnteringArray) { - st_ = kError; - return false; - } - - ParseNext(); - return true; -} - -const char *LottieParserImpl::NextObjectKey() -{ - if (st_ == kHasKey) { - const char *result = v_.GetString(); - ParseNext(); - return result; - } - - /* SPECIAL CASE - * The parser works with a prdefined rule that it will be only - * while (NextObjectKey()) for each object but in case of our nested group - * object we can call multiple time NextObjectKey() while exiting the object - * so ignore those and don't put parser in the error state. - * */ - if (st_ == kExitingArray || st_ == kEnteringObject) { - // #ifdef DEBUG_PARSER - // vDebug<<"Object: Exiting nested loop"; - // #endif - return nullptr; - } - - if (st_ != kExitingObject) { - st_ = kError; - return nullptr; - } - - ParseNext(); - return nullptr; -} - -bool LottieParserImpl::NextArrayValue() -{ - if (st_ == kExitingArray) { - ParseNext(); - return false; - } - - /* SPECIAL CASE - * same as NextObjectKey() - */ - if (st_ == kExitingObject) { - return false; - } - - if (st_ == kError || st_ == kHasKey) { - st_ = kError; - return false; - } - - return true; -} - -int LottieParserImpl::GetInt() -{ - if (st_ != kHasNumber || !v_.IsInt()) { - st_ = kError; - return 0; - } - - int result = v_.GetInt(); - ParseNext(); - return result; -} - -double LottieParserImpl::GetDouble() -{ - if (st_ != kHasNumber) { - st_ = kError; - return 0.; - } - - double result = v_.GetDouble(); - ParseNext(); - return result; -} - -bool LottieParserImpl::GetBool() -{ - if (st_ != kHasBool) { - st_ = kError; - return false; - } - - bool result = v_.GetBool(); - ParseNext(); - return result; -} - -void LottieParserImpl::GetNull() -{ - if (st_ != kHasNull) { - st_ = kError; - return; - } - - ParseNext(); -} - -const char *LottieParserImpl::GetString() -{ - if (st_ != kHasString) { - st_ = kError; - return nullptr; - } - - const char *result = v_.GetString(); - ParseNext(); - return result; -} - -std::string LottieParserImpl::GetStringObject() -{ - auto str = GetString(); - - if (str) { - return std::string(str); - } - - return {}; -} - -void LottieParserImpl::SkipOut(int depth) -{ - do { - if (st_ == kEnteringArray || st_ == kEnteringObject) { - ++depth; - } else if (st_ == kExitingArray || st_ == kExitingObject) { - --depth; - } else if (st_ == kError) { - return; - } - - ParseNext(); - } while (depth > 0); -} - -void LottieParserImpl::SkipValue() -{ - SkipOut(0); -} - -void LottieParserImpl::SkipArray() -{ - SkipOut(1); -} - -void LottieParserImpl::SkipObject() -{ - SkipOut(1); -} - -Value *LottieParserImpl::PeekValue() -{ - if (st_ >= kHasNull && st_ <= kHasKey) { - return &v_; - } - - return nullptr; -} - -// returns a rapidjson::Type, or -1 for no value (at end of -// object/array) -int LottieParserImpl::PeekType() const -{ - if (st_ >= kHasNull && st_ <= kHasKey) { - return v_.GetType(); - } - - if (st_ == kEnteringArray) { - return kArrayType; - } - - if (st_ == kEnteringObject) { - return kObjectType; - } - - return -1; -} - -void LottieParserImpl::Skip(const char * /*key*/) -{ - if (PeekType() == kArrayType) { - EnterArray(); - SkipArray(); - } else if (PeekType() == kObjectType) { - EnterObject(); - SkipObject(); - } else { - SkipValue(); - } -} - -model::BlendMode LottieParserImpl::getBlendMode() -{ - auto mode = model::BlendMode::Normal; - if (PeekType() != kNumberType) { - parsingError = true; - return mode; - } - - switch (GetInt()) { - case 1: - mode = model::BlendMode::Multiply; - break; - case 2: - mode = model::BlendMode::Screen; - break; - case 3: - mode = model::BlendMode::OverLay; - break; - default: - break; - } - return mode; -} - -void LottieParserImpl::resolveLayerRefs() -{ - for (const auto &layer : mLayersToUpdate) { - auto search = compRef->mAssets.find(layer->extra()->mPreCompRefId); - if (search != compRef->mAssets.end()) { - if (layer->mLayerType == model::Layer::Type::Image) { - layer->extra()->mAsset = search->second; - } else if (layer->mLayerType == model::Layer::Type::Precomp) { - layer->mChildren = search->second->mLayers; - layer->setStatic(layer->isStatic() && - search->second->isStatic()); - } - } - } -} - -void LottieParserImpl::parseComposition() -{ - if (PeekType() != kObjectType) { - parsingError = true; - return; - } - EnterObject(); - std::shared_ptr sharedComposition = - std::make_shared(); - model::Composition *comp = sharedComposition.get(); - compRef = comp; - while (const char *key = NextObjectKey()) { - if (0 == strcmp(key, "v")) { - if (PeekType() != kStringType) { - parsingError = true; - return; - } - comp->mVersion = GetStringObject(); - } else if (0 == strcmp(key, "w")) { - if (PeekType() != kNumberType) { - parsingError = true; - return; - } - comp->mSize.setWidth(GetInt()); - } else if (0 == strcmp(key, "h")) { - if (PeekType() != kNumberType) { - parsingError = true; - return; - } - comp->mSize.setHeight(GetInt()); - } else if (0 == strcmp(key, "ip")) { - if (PeekType() != kNumberType) { - parsingError = true; - return; - } - comp->mStartFrame = std::lround(GetDouble()); - } else if (0 == strcmp(key, "op")) { - if (PeekType() != kNumberType) { - parsingError = true; - return; - } - comp->mEndFrame = std::lround(GetDouble()); - } else if (0 == strcmp(key, "fr")) { - if (PeekType() != kNumberType) { - parsingError = true; - return; - } - comp->mFrameRate = GetDouble(); - } else if (0 == strcmp(key, "assets")) { - parseAssets(comp); - } else if (0 == strcmp(key, "layers")) { - parseLayers(comp); - } else if (0 == strcmp(key, "markers")) { - parseMarkers(); - } else { -#ifdef DEBUG_PARSER - vWarning << "Composition Attribute Skipped : " << key; -#endif - Skip(key); - } - } - if (!IsValid()) { - parsingError = true; - return; - } - if (comp->mVersion.empty() || !comp->mRootLayer) { - // don't have a valid bodymovin header - return; - } - if (comp->mStartFrame > comp->mEndFrame) { - // reveresed animation? missing data? - return; - } - resolveLayerRefs(); - comp->setStatic(comp->mRootLayer->isStatic()); - comp->mRootLayer->mInFrame = comp->mStartFrame; - comp->mRootLayer->mOutFrame = comp->mEndFrame; - - mComposition = sharedComposition; -} - -void LottieParserImpl::parseMarker() -{ - if (PeekType() != kObjectType) { - return; - } - EnterObject(); - std::string comment; - int timeframe{0}; - int duration{0}; - while (const char *key = NextObjectKey()) { - if (0 == strcmp(key, "cm")) { - if (PeekType() != kStringType) { - return; - } - comment = GetStringObject(); - } else if (0 == strcmp(key, "tm")) { - if (PeekType() != kNumberType) { - return; - } - timeframe = GetDouble(); - } else if (0 == strcmp(key, "dr")) { - if (PeekType() != kNumberType) { - return; - } - duration = GetDouble(); - - } else { -#ifdef DEBUG_PARSER - vWarning << "Marker Attribute Skipped : " << key; -#endif - Skip(key); - } - } - compRef->mMarkers.emplace_back(std::move(comment), timeframe, - timeframe + duration); -} - -void LottieParserImpl::parseMarkers() -{ - if (PeekType() != kArrayType) { - return; - } - EnterArray(); - while (NextArrayValue()) { - parseMarker(); - } - // update the precomp layers with the actual layer object -} - -void LottieParserImpl::parseAssets(model::Composition *composition) -{ - if (PeekType() != kArrayType) { - parsingError = true; - return; - } - EnterArray(); - while (NextArrayValue()) { - if (parsingError) { - return; - } - auto asset = parseAsset(); - composition->mAssets[asset->mRefId] = asset; - } - if (!IsValid()) { - parsingError = true; - return; - } - // update the precomp layers with the actual layer object -} - -static constexpr const unsigned char B64index[256] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 62, 63, 62, 62, 63, 52, 53, 54, 55, 56, 57, - 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, - 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, - 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, - 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51}; - -std::string b64decode(const char *data, const size_t len) -{ - auto p = reinterpret_cast(data); - int pad = len > 0 && (len % 4 || p[len - 1] == '='); - const size_t L = ((len + 3) / 4 - pad) * 4; - std::string str(L / 4 * 3 + pad, '\0'); - - for (size_t i = 0, j = 0; i < L; i += 4) { - int n = B64index[p[i]] << 18 | B64index[p[i + 1]] << 12 | - B64index[p[i + 2]] << 6 | B64index[p[i + 3]]; - str[j++] = n >> 16; - str[j++] = n >> 8 & 0xFF; - str[j++] = n & 0xFF; - } - if (pad) { - int n = B64index[p[L]] << 18 | B64index[p[L + 1]] << 12; - str[str.size() - 1] = n >> 16; - - if (len > L + 2 && p[L + 2] != '=') { - n |= B64index[p[L + 2]] << 6; - str.push_back(n >> 8 & 0xFF); - } - } - return str; -} - -static std::string convertFromBase64(const std::string &str) -{ - // usual header look like "data:image/png;base64," - // so need to skip till ','. - size_t startIndex = str.find(',', 0); - startIndex += 1; // skip "," - size_t length = str.length() - startIndex; - - const char *b64Data = str.c_str() + startIndex; - - return b64decode(b64Data, length); -} - -/* - * std::to_string() function is missing in VS2017 - * so this is workaround for windows build - */ -#include -template -static std::string toString(const T &value) -{ - std::ostringstream os; - os << value; - return os.str(); -} - -/* - * https://github.com/airbnb/lottie-web/blob/master/docs/json/layers/shape.json - * - */ -model::Asset *LottieParserImpl::parseAsset() -{ - auto asset = allocator().make(); - if (PeekType() != kObjectType) { - parsingError = true; - return asset; - } - std::string filename; - std::string relativePath; - bool embededResource = false; - EnterObject(); - while (const char *key = NextObjectKey()) { - if (0 == strcmp(key, "w")) { - if (PeekType() != kNumberType) { - parsingError = true; - return asset; - } - asset->mWidth = GetInt(); - } else if (0 == strcmp(key, "h")) { - if (PeekType() != kNumberType) { - parsingError = true; - return asset; - } - asset->mHeight = GetInt(); - } else if (0 == strcmp(key, "p")) { /* image name */ - asset->mAssetType = model::Asset::Type::Image; - if (PeekType() != kStringType) { - parsingError = true; - return asset; - } - filename = GetStringObject(); - } else if (0 == strcmp(key, "u")) { /* relative image path */ - if (PeekType() != kStringType) { - parsingError = true; - return asset; - } - relativePath = GetStringObject(); - } else if (0 == strcmp(key, "e")) { /* relative image path */ - embededResource = GetInt(); - } else if (0 == strcmp(key, "id")) { /* reference id*/ - if (PeekType() == kStringType) { - asset->mRefId = GetStringObject(); - } else { - if (PeekType() != kNumberType) { - parsingError = true; - return asset; - } - asset->mRefId = toString(GetInt()); - } - } else if (0 == strcmp(key, "layers")) { - asset->mAssetType = model::Asset::Type::Precomp; - if (PeekType() != kArrayType) { - parsingError = true; - return asset; - } - EnterArray(); - bool staticFlag = true; - while (NextArrayValue()) { - if (parsingError) { - return asset; - } - auto layer = parseLayer(); - if (layer) { - staticFlag = staticFlag && layer->isStatic(); - asset->mLayers.push_back(layer); - } - } - if (!IsValid()) { - parsingError = true; - return asset; - } - asset->setStatic(staticFlag); - } else { -#ifdef DEBUG_PARSER - vWarning << "Asset Attribute Skipped : " << key; -#endif - Skip(key); - } - } - if (!IsValid()) { - parsingError = true; - return asset; - } - - if (asset->mAssetType == model::Asset::Type::Image) { - if (embededResource) { - // embeder resource should start with "data:" - if (filename.compare(0, 5, "data:") == 0) { - asset->loadImageData(convertFromBase64(filename)); - } - } else { - asset->loadImagePath(mDirPath + relativePath + filename); - } - } - - return asset; -} - -void LottieParserImpl::parseLayers(model::Composition *comp) -{ - comp->mRootLayer = allocator().make(); - comp->mRootLayer->mLayerType = model::Layer::Type::Precomp; - comp->mRootLayer->setName("__"); - bool staticFlag = true; - if (PeekType() != kArrayType) { - parsingError = true; - return; - } - EnterArray(); - while (NextArrayValue()) { - if (parsingError) { - return; - } - auto layer = parseLayer(); - if (layer) { - staticFlag = staticFlag && layer->isStatic(); - comp->mRootLayer->mChildren.push_back(layer); - } - } - if (!IsValid()) { - parsingError = true; - return; - } - comp->mRootLayer->setStatic(staticFlag); -} - -model::Color LottieParserImpl::toColor(const char *str) -{ - if (!str) return {}; - - model::Color color; - auto len = strlen(str); - - // some resource has empty color string - // return a default color for those cases. - if (len != 7 || str[0] != '#') return color; - - char tmp[3] = {'\0', '\0', '\0'}; - tmp[0] = str[1]; - tmp[1] = str[2]; - color.r = std::strtol(tmp, nullptr, 16) / 255.0f; - - tmp[0] = str[3]; - tmp[1] = str[4]; - color.g = std::strtol(tmp, nullptr, 16) / 255.0f; - - tmp[0] = str[5]; - tmp[1] = str[6]; - color.b = std::strtol(tmp, nullptr, 16) / 255.0f; - - return color; -} - -model::MatteType LottieParserImpl::getMatteType() -{ - if (PeekType() != kNumberType) { - parsingError = true; - return model::MatteType::None; - } - switch (GetInt()) { - case 1: - return model::MatteType::Alpha; - break; - case 2: - return model::MatteType::AlphaInv; - break; - case 3: - return model::MatteType::Luma; - break; - case 4: - return model::MatteType::LumaInv; - break; - default: - return model::MatteType::None; - break; - } -} - -model::Layer::Type LottieParserImpl::getLayerType() -{ - if (PeekType() != kNumberType) { - parsingError = true; - return model::Layer::Type::Null; - } - switch (GetInt()) { - case 0: - return model::Layer::Type::Precomp; - break; - case 1: - return model::Layer::Type::Solid; - break; - case 2: - return model::Layer::Type::Image; - break; - case 3: - return model::Layer::Type::Null; - break; - case 4: - return model::Layer::Type::Shape; - break; - case 5: - return model::Layer::Type::Text; - break; - default: - return model::Layer::Type::Null; - break; - } -} - -/* - * https://github.com/airbnb/lottie-web/blob/master/docs/json/layers/shape.json - * - */ -model::Layer *LottieParserImpl::parseLayer() -{ - auto *layer = allocator().make(); - if (PeekType() != kObjectType) { - parsingError = true; - return layer; - } - curLayerRef = layer; - bool ddd = true; - EnterObject(); - while (const char *key = NextObjectKey()) { - if (0 == strcmp(key, "ty")) { /* Type of layer*/ - layer->mLayerType = getLayerType(); - } else if (0 == strcmp(key, "nm")) { /*Layer name*/ - if (PeekType() != kStringType) { - parsingError = true; - return layer; - } - layer->setName(GetString()); - } else if (0 == strcmp(key, "ind")) { /*Layer index in AE. Used for - parenting and expressions.*/ - if (PeekType() != kNumberType) { - parsingError = true; - return layer; - } - layer->mId = GetInt(); - } else if (0 == strcmp(key, "ddd")) { /*3d layer */ - if (PeekType() != kNumberType) { - parsingError = true; - return layer; - } - ddd = GetInt(); - } else if (0 == - strcmp(key, - "parent")) { /*Layer Parent. Uses "ind" of parent.*/ - if (PeekType() != kNumberType) { - parsingError = true; - return layer; - } - layer->mParentId = GetInt(); - } else if (0 == strcmp(key, "refId")) { /*preComp Layer reference id*/ - if (PeekType() != kStringType) { - parsingError = true; - return layer; - } - layer->extra()->mPreCompRefId = GetStringObject(); - layer->mHasGradient = true; - mLayersToUpdate.push_back(layer); - } else if (0 == strcmp(key, "sr")) { // "Layer Time Stretching" - if (PeekType() != kNumberType) { - parsingError = true; - return layer; - } - layer->mTimeStreatch = GetDouble(); - } else if (0 == strcmp(key, "tm")) { // time remapping - parseProperty(layer->extra()->mTimeRemap); - } else if (0 == strcmp(key, "ip")) { - if (PeekType() != kNumberType) { - parsingError = true; - return layer; - } - layer->mInFrame = std::lround(GetDouble()); - } else if (0 == strcmp(key, "op")) { - if (PeekType() != kNumberType) { - parsingError = true; - return layer; - } - layer->mOutFrame = std::lround(GetDouble()); - } else if (0 == strcmp(key, "st")) { - if (PeekType() != kNumberType) { - parsingError = true; - return layer; - } - layer->mStartFrame = GetDouble(); - } else if (0 == strcmp(key, "bm")) { - layer->mBlendMode = getBlendMode(); - } else if (0 == strcmp(key, "ks")) { - if (PeekType() != kObjectType) { - parsingError = true; - return layer; - } - EnterObject(); - layer->mTransform = parseTransformObject(ddd); - } else if (0 == strcmp(key, "shapes")) { - parseShapesAttr(layer); - } else if (0 == strcmp(key, "w")) { - layer->mLayerSize.setWidth(GetInt()); - } else if (0 == strcmp(key, "h")) { - layer->mLayerSize.setHeight(GetInt()); - } else if (0 == strcmp(key, "sw")) { - layer->mLayerSize.setWidth(GetInt()); - } else if (0 == strcmp(key, "sh")) { - layer->mLayerSize.setHeight(GetInt()); - } else if (0 == strcmp(key, "sc")) { - layer->extra()->mSolidColor = toColor(GetString()); - } else if (0 == strcmp(key, "tt")) { - layer->mMatteType = getMatteType(); - } else if (0 == strcmp(key, "hasMask")) { - layer->mHasMask = GetBool(); - } else if (0 == strcmp(key, "masksProperties")) { - parseMaskProperty(layer); - } else if (0 == strcmp(key, "ao")) { - layer->mAutoOrient = GetInt(); - } else if (0 == strcmp(key, "hd")) { - layer->setHidden(GetBool()); - } else { -#ifdef DEBUG_PARSER - vWarning << "Layer Attribute Skipped : " << key; -#endif - Skip(key); - } - } - - if (!IsValid() || !layer->mTransform) { - // not a valid layer - return nullptr; - } - - // make sure layer data is not corrupted. - if (layer->hasParent() && (layer->id() == layer->parentId())) return nullptr; - - if (layer->mExtra) layer->mExtra->mCompRef = compRef; - - if (layer->hidden()) { - // if layer is hidden, only data that is usefull is its - // transform matrix(when it is a parent of some other layer) - // so force it to be a Null Layer and release all resource. - layer->setStatic(layer->mTransform->isStatic()); - layer->mLayerType = model::Layer::Type::Null; - layer->mChildren = {}; - return layer; - } - - // update the static property of layer - bool staticFlag = true; - for (const auto &child : layer->mChildren) { - staticFlag &= child->isStatic(); - } - - if (layer->hasMask() && layer->mExtra) { - for (const auto &mask : layer->mExtra->mMasks) { - staticFlag &= mask->isStatic(); - } - } - - layer->setStatic(staticFlag && layer->mTransform->isStatic()); - - return layer; -} - -void LottieParserImpl::parseMaskProperty(model::Layer *layer) -{ - if (PeekType() != kArrayType) { - parsingError = true; - return; - } - EnterArray(); - while (NextArrayValue()) { - if (parsingError) { - return; - } - layer->extra()->mMasks.push_back(parseMaskObject()); - } - if (!IsValid()) { - parsingError = true; - return; - } -} - -model::Mask *LottieParserImpl::parseMaskObject() -{ - auto obj = allocator().make(); - - if (PeekType() != kObjectType) { - parsingError = true; - return obj; - } - EnterObject(); - while (const char *key = NextObjectKey()) { - if (0 == strcmp(key, "inv")) { - obj->mInv = GetBool(); - } else if (0 == strcmp(key, "mode")) { - const char *str = GetString(); - if (!str) { - obj->mMode = model::Mask::Mode::None; - continue; - } - switch (str[0]) { - case 'n': - obj->mMode = model::Mask::Mode::None; - break; - case 'a': - obj->mMode = model::Mask::Mode::Add; - break; - case 's': - obj->mMode = model::Mask::Mode::Substarct; - break; - case 'i': - obj->mMode = model::Mask::Mode::Intersect; - break; - case 'f': - obj->mMode = model::Mask::Mode::Difference; - break; - default: - obj->mMode = model::Mask::Mode::None; - break; - } - } else if (0 == strcmp(key, "pt")) { - parseShapeProperty(obj->mShape); - } else if (0 == strcmp(key, "o")) { - parseProperty(obj->mOpacity); - } else { - Skip(key); - } - } - if (!IsValid()) { - parsingError = true; - return obj; - } - obj->mIsStatic = obj->mShape.isStatic() && obj->mOpacity.isStatic(); - return obj; -} - -void LottieParserImpl::parseShapesAttr(model::Layer *layer) -{ - if (PeekType() != kArrayType) { - parsingError = true; - return; - } - EnterArray(); - while (NextArrayValue()) { - if (parsingError) { - return; - } - parseObject(layer); - } - if (!IsValid()) { - parsingError = true; - return; - } -} - -model::Object *LottieParserImpl::parseObjectTypeAttr() -{ - if (PeekType() != kStringType) { - parsingError = true; - return nullptr; - } - const char *type = GetString(); - if (0 == strcmp(type, "gr")) { - return parseGroupObject(); - } else if (0 == strcmp(type, "rc")) { - return parseRectObject(); - } else if (0 == strcmp(type, "rd")) { - curLayerRef->mHasRoundedCorner = true; - return parseRoundedCorner(); - } else if (0 == strcmp(type, "el")) { - return parseEllipseObject(); - } else if (0 == strcmp(type, "tr")) { - return parseTransformObject(); - } else if (0 == strcmp(type, "fl")) { - return parseFillObject(); - } else if (0 == strcmp(type, "st")) { - return parseStrokeObject(); - } else if (0 == strcmp(type, "gf")) { - curLayerRef->mHasGradient = true; - return parseGFillObject(); - } else if (0 == strcmp(type, "gs")) { - curLayerRef->mHasGradient = true; - return parseGStrokeObject(); - } else if (0 == strcmp(type, "sh")) { - return parseShapeObject(); - } else if (0 == strcmp(type, "sr")) { - return parsePolystarObject(); - } else if (0 == strcmp(type, "tm")) { - curLayerRef->mHasPathOperator = true; - return parseTrimObject(); - } else if (0 == strcmp(type, "rp")) { - curLayerRef->mHasRepeater = true; - return parseReapeaterObject(); - } else if (0 == strcmp(type, "mm")) { - vWarning << "Merge Path is not supported yet"; - return nullptr; - } else { -#ifdef DEBUG_PARSER - vDebug << "The Object Type not yet handled = " << type; -#endif - return nullptr; - } -} - -void LottieParserImpl::parseObject(model::Group *parent) -{ - if (PeekType() != kObjectType) { - parsingError = true; - return; - } - EnterObject(); - while (const char *key = NextObjectKey()) { - if (0 == strcmp(key, "ty")) { - auto child = parseObjectTypeAttr(); - if (child && !child->hidden()) { - if (child->type() == model::Object::Type::RoundedCorner) { - updateRoundedCorner(parent, static_cast(child)); - } - parent->mChildren.push_back(child); - } - } else { - Skip(key); - } - } -} - -void LottieParserImpl::updateRoundedCorner(model::Group *group, model::RoundedCorner *rc) -{ - for(auto &e : group->mChildren) - { - if (e->type() == model::Object::Type::Rect) { - static_cast(e)->mRoundedCorner = rc; - if (!rc->isStatic()) { - e->setStatic(false); - group->setStatic(false); - //@TODO need to propagate. - } - } else if ( e->type() == model::Object::Type::Group) { - updateRoundedCorner(static_cast(e), rc); - } - } -} - -model::Object *LottieParserImpl::parseGroupObject() -{ - auto group = allocator().make(); - - while (const char *key = NextObjectKey()) { - if (0 == strcmp(key, "nm")) { - group->setName(GetString()); - } else if (0 == strcmp(key, "it")) { - if (PeekType() != kArrayType) { - parsingError = true; - return group; - } - EnterArray(); - while (NextArrayValue()) { - if (parsingError) { - return group; - } - if (PeekType() != kObjectType) { - parsingError = true; - return group; - } - parseObject(group); - } - if (!IsValid()) { - parsingError = true; - return group; - } - if (!group->mChildren.empty() - && group->mChildren.back()->type() - == model::Object::Type::Transform) { - group->mTransform = - static_cast(group->mChildren.back()); - group->mChildren.pop_back(); - } - } else { - Skip(key); - } - } - if (!IsValid()) { - parsingError = true; - return group; - } - bool staticFlag = true; - for (const auto &child : group->mChildren) { - staticFlag &= child->isStatic(); - } - - if (group->mTransform) { - group->setStatic(staticFlag && group->mTransform->isStatic()); - } - - return group; -} - -/* - * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/rect.json - */ -model::Rect *LottieParserImpl::parseRectObject() -{ - auto obj = allocator().make(); - - while (const char *key = NextObjectKey()) { - if (0 == strcmp(key, "nm")) { - obj->setName(GetString()); - } else if (0 == strcmp(key, "p")) { - parseProperty(obj->mPos); - } else if (0 == strcmp(key, "s")) { - parseProperty(obj->mSize); - } else if (0 == strcmp(key, "r")) { - parseProperty(obj->mRound); - } else if (0 == strcmp(key, "d")) { - obj->mDirection = GetInt(); - } else if (0 == strcmp(key, "hd")) { - obj->setHidden(GetBool()); - } else { - Skip(key); - } - } - if (!IsValid()) { - parsingError = true; - return obj; - } - obj->setStatic(obj->mPos.isStatic() && obj->mSize.isStatic() && - obj->mRound.isStatic()); - return obj; -} - -/* - * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/rect.json - */ -model::RoundedCorner *LottieParserImpl::parseRoundedCorner() -{ - auto obj = allocator().make(); - - while (const char *key = NextObjectKey()) { - if (0 == strcmp(key, "nm")) { - obj->setName(GetString()); - } else if (0 == strcmp(key, "r")) { - parseProperty(obj->mRadius); - } else if (0 == strcmp(key, "hd")) { - obj->setHidden(GetBool()); - } else { - Skip(key); - } - } - obj->setStatic(obj->mRadius.isStatic()); - if (!IsValid()) { - parsingError = true; - return obj; - } - return obj; -} - -/* - * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/ellipse.json - */ -model::Ellipse *LottieParserImpl::parseEllipseObject() -{ - auto obj = allocator().make(); - - while (const char *key = NextObjectKey()) { - if (0 == strcmp(key, "nm")) { - obj->setName(GetString()); - } else if (0 == strcmp(key, "p")) { - parseProperty(obj->mPos); - } else if (0 == strcmp(key, "s")) { - parseProperty(obj->mSize); - } else if (0 == strcmp(key, "d")) { - obj->mDirection = GetInt(); - } else if (0 == strcmp(key, "hd")) { - obj->setHidden(GetBool()); - } else { - Skip(key); - } - } - if (!IsValid()) { - parsingError = true; - return obj; - } - obj->setStatic(obj->mPos.isStatic() && obj->mSize.isStatic()); - return obj; -} - -/* - * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/shape.json - */ -model::Path *LottieParserImpl::parseShapeObject() -{ - auto obj = allocator().make(); - - while (const char *key = NextObjectKey()) { - if (0 == strcmp(key, "nm")) { - obj->setName(GetString()); - } else if (0 == strcmp(key, "ks")) { - parseShapeProperty(obj->mShape); - } else if (0 == strcmp(key, "d")) { - obj->mDirection = GetInt(); - } else if (0 == strcmp(key, "hd")) { - obj->setHidden(GetBool()); - } else { -#ifdef DEBUG_PARSER - vDebug << "Shape property ignored :" << key; -#endif - Skip(key); - } - } - if (!IsValid()) { - parsingError = true; - return obj; - } - obj->setStatic(obj->mShape.isStatic()); - - return obj; -} - -/* - * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/star.json - */ -model::Polystar *LottieParserImpl::parsePolystarObject() -{ - auto obj = allocator().make(); - - while (const char *key = NextObjectKey()) { - if (0 == strcmp(key, "nm")) { - obj->setName(GetString()); - } else if (0 == strcmp(key, "p")) { - parseProperty(obj->mPos); - } else if (0 == strcmp(key, "pt")) { - parseProperty(obj->mPointCount); - } else if (0 == strcmp(key, "ir")) { - parseProperty(obj->mInnerRadius); - } else if (0 == strcmp(key, "is")) { - parseProperty(obj->mInnerRoundness); - } else if (0 == strcmp(key, "or")) { - parseProperty(obj->mOuterRadius); - } else if (0 == strcmp(key, "os")) { - parseProperty(obj->mOuterRoundness); - } else if (0 == strcmp(key, "r")) { - parseProperty(obj->mRotation); - } else if (0 == strcmp(key, "sy")) { - int starType = GetInt(); - if (starType == 1) obj->mPolyType = model::Polystar::PolyType::Star; - if (starType == 2) - obj->mPolyType = model::Polystar::PolyType::Polygon; - } else if (0 == strcmp(key, "d")) { - obj->mDirection = GetInt(); - } else if (0 == strcmp(key, "hd")) { - obj->setHidden(GetBool()); - } else { -#ifdef DEBUG_PARSER - vDebug << "Polystar property ignored :" << key; -#endif - Skip(key); - } - } - if (!IsValid()) { - parsingError = true; - return obj; - } - obj->setStatic( - obj->mPos.isStatic() && obj->mPointCount.isStatic() && - obj->mInnerRadius.isStatic() && obj->mInnerRoundness.isStatic() && - obj->mOuterRadius.isStatic() && obj->mOuterRoundness.isStatic() && - obj->mRotation.isStatic()); - - return obj; -} - -model::Trim::TrimType LottieParserImpl::getTrimType() -{ - if (PeekType() != kNumberType) { - parsingError = true; - return model::Trim::TrimType::Individually; - } - switch (GetInt()) { - case 1: - return model::Trim::TrimType::Simultaneously; - break; - case 2: - return model::Trim::TrimType::Individually; - break; - default: - Error(); - return model::Trim::TrimType::Simultaneously; - break; - } -} - -/* - * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/trim.json - */ -model::Trim *LottieParserImpl::parseTrimObject() -{ - auto obj = allocator().make(); - - while (const char *key = NextObjectKey()) { - if (0 == strcmp(key, "nm")) { - obj->setName(GetString()); - } else if (0 == strcmp(key, "s")) { - parseProperty(obj->mStart); - } else if (0 == strcmp(key, "e")) { - parseProperty(obj->mEnd); - } else if (0 == strcmp(key, "o")) { - parseProperty(obj->mOffset); - } else if (0 == strcmp(key, "m")) { - obj->mTrimType = getTrimType(); - } else if (0 == strcmp(key, "hd")) { - obj->setHidden(GetBool()); - } else { -#ifdef DEBUG_PARSER - vDebug << "Trim property ignored :" << key; -#endif - Skip(key); - } - } - if (!IsValid()) { - parsingError = true; - return obj; - } - obj->setStatic(obj->mStart.isStatic() && obj->mEnd.isStatic() && - obj->mOffset.isStatic()); - return obj; -} - -void LottieParserImpl::getValue(model::Repeater::Transform &obj) -{ - EnterObject(); - - while (const char *key = NextObjectKey()) { - if (0 == strcmp(key, "a")) { - parseProperty(obj.mAnchor); - } else if (0 == strcmp(key, "p")) { - parseProperty(obj.mPosition); - } else if (0 == strcmp(key, "r")) { - parseProperty(obj.mRotation); - } else if (0 == strcmp(key, "s")) { - parseProperty(obj.mScale); - } else if (0 == strcmp(key, "so")) { - parseProperty(obj.mStartOpacity); - } else if (0 == strcmp(key, "eo")) { - parseProperty(obj.mEndOpacity); - } else { - Skip(key); - } - } - if (!IsValid()) { - parsingError = true; - } -} - -model::Repeater *LottieParserImpl::parseReapeaterObject() -{ - auto obj = allocator().make(); - - obj->setContent(allocator().make()); - - while (const char *key = NextObjectKey()) { - if (0 == strcmp(key, "nm")) { - obj->setName(GetString()); - } else if (0 == strcmp(key, "c")) { - parseProperty(obj->mCopies); - float maxCopy = 0.0; - if (!obj->mCopies.isStatic()) { - for (auto &keyFrame : obj->mCopies.animation().frames_) { - if (maxCopy < keyFrame.value_.start_) - maxCopy = keyFrame.value_.start_; - if (maxCopy < keyFrame.value_.end_) - maxCopy = keyFrame.value_.end_; - } - } else { - maxCopy = obj->mCopies.value(); - } - obj->mMaxCopies = maxCopy; - } else if (0 == strcmp(key, "o")) { - parseProperty(obj->mOffset); - } else if (0 == strcmp(key, "tr")) { - getValue(obj->mTransform); - } else if (0 == strcmp(key, "hd")) { - obj->setHidden(GetBool()); - } else { -#ifdef DEBUG_PARSER - vDebug << "Repeater property ignored :" << key; -#endif - Skip(key); - } - } - if (!IsValid()) { - parsingError = true; - return obj; - } - obj->setStatic(obj->mCopies.isStatic() && obj->mOffset.isStatic() && - obj->mTransform.isStatic()); - - return obj; -} - -/* - * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/transform.json - */ -model::Transform *LottieParserImpl::parseTransformObject(bool ddd) -{ - auto objT = allocator().make(); - - auto obj = allocator().make(); - if (ddd) { - obj->createExtraData(); - obj->mExtra->m3DData = true; - } - - while (const char *key = NextObjectKey()) { - if (0 == strcmp(key, "nm")) { - objT->setName(GetString()); - } else if (0 == strcmp(key, "a")) { - parseProperty(obj->mAnchor); - } else if (0 == strcmp(key, "p")) { - EnterObject(); - bool separate = false; - while (const char *key1 = NextObjectKey()) { - if (0 == strcmp(key1, "k")) { - parsePropertyHelper(obj->mPosition); - } else if (0 == strcmp(key1, "s")) { - obj->createExtraData(); - obj->mExtra->mSeparate = GetBool(); - separate = true; - } else if (separate && (0 == strcmp(key1, "x"))) { - parseProperty(obj->mExtra->mSeparateX); - } else if (separate && (0 == strcmp(key1, "y"))) { - parseProperty(obj->mExtra->mSeparateY); - } else { - Skip(key1); - } - } - if (!IsValid()) { - parsingError = true; - return objT; - } - } else if (0 == strcmp(key, "r")) { - parseProperty(obj->mRotation); - } else if (0 == strcmp(key, "s")) { - parseProperty(obj->mScale); - } else if (0 == strcmp(key, "o")) { - parseProperty(obj->mOpacity); - } else if (0 == strcmp(key, "hd")) { - objT->setHidden(GetBool()); - } else if (0 == strcmp(key, "rx")) { - if (!obj->mExtra) return nullptr; - parseProperty(obj->mExtra->m3DRx); - } else if (0 == strcmp(key, "ry")) { - if (!obj->mExtra) return nullptr; - parseProperty(obj->mExtra->m3DRy); - } else if (0 == strcmp(key, "rz")) { - if (!obj->mExtra) return nullptr; - parseProperty(obj->mExtra->m3DRz); - } else { - Skip(key); - } - } - if (!IsValid()) { - parsingError = true; - return objT; - } - bool isStatic = obj->mAnchor.isStatic() && obj->mPosition.isStatic() && - obj->mRotation.isStatic() && obj->mScale.isStatic() && - obj->mOpacity.isStatic(); - if (obj->mExtra) { - isStatic = isStatic && obj->mExtra->m3DRx.isStatic() && - obj->mExtra->m3DRy.isStatic() && - obj->mExtra->m3DRz.isStatic() && - obj->mExtra->mSeparateX.isStatic() && - obj->mExtra->mSeparateY.isStatic(); - } - - objT->set(obj, isStatic); - - return objT; -} - -/* - * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/fill.json - */ -model::Fill *LottieParserImpl::parseFillObject() -{ - auto obj = allocator().make(); - - while (const char *key = NextObjectKey()) { - if (0 == strcmp(key, "nm")) { - obj->setName(GetString()); - } else if (0 == strcmp(key, "c")) { - parseProperty(obj->mColor); - } else if (0 == strcmp(key, "o")) { - parseProperty(obj->mOpacity); - } else if (0 == strcmp(key, "fillEnabled")) { - obj->mEnabled = GetBool(); - } else if (0 == strcmp(key, "r")) { - obj->mFillRule = getFillRule(); - } else if (0 == strcmp(key, "hd")) { - obj->setHidden(GetBool()); - } else { -#ifdef DEBUG_PARSER - vWarning << "Fill property skipped = " << key; -#endif - Skip(key); - } - } - if (!IsValid()) { - parsingError = true; - return obj; - } - obj->setStatic(obj->mColor.isStatic() && obj->mOpacity.isStatic()); - - return obj; -} - -/* - * https://github.com/airbnb/lottie-web/blob/master/docs/json/helpers/lineCap.json - */ -CapStyle LottieParserImpl::getLineCap() -{ - if (PeekType() != kNumberType) { - parsingError = true; - return CapStyle::Square; - } - switch (GetInt()) { - case 1: - return CapStyle::Flat; - break; - case 2: - return CapStyle::Round; - break; - default: - return CapStyle::Square; - break; - } -} - -FillRule LottieParserImpl::getFillRule() -{ - if (PeekType() != kNumberType) { - parsingError = true; - return FillRule::Winding; - } - switch (GetInt()) { - case 1: - return FillRule::Winding; - break; - case 2: - return FillRule::EvenOdd; - break; - default: - return FillRule::Winding; - break; - } -} - -/* - * https://github.com/airbnb/lottie-web/blob/master/docs/json/helpers/lineJoin.json - */ -JoinStyle LottieParserImpl::getLineJoin() -{ - if (PeekType() != kNumberType) { - parsingError = true; - return JoinStyle::Bevel; - } - switch (GetInt()) { - case 1: - return JoinStyle::Miter; - break; - case 2: - return JoinStyle::Round; - break; - default: - return JoinStyle::Bevel; - break; - } -} - -/* - * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/stroke.json - */ -model::Stroke *LottieParserImpl::parseStrokeObject() -{ - auto obj = allocator().make(); - - while (const char *key = NextObjectKey()) { - if (0 == strcmp(key, "nm")) { - obj->setName(GetString()); - } else if (0 == strcmp(key, "c")) { - parseProperty(obj->mColor); - } else if (0 == strcmp(key, "o")) { - parseProperty(obj->mOpacity); - } else if (0 == strcmp(key, "w")) { - parseProperty(obj->mWidth); - } else if (0 == strcmp(key, "fillEnabled")) { - obj->mEnabled = GetBool(); - } else if (0 == strcmp(key, "lc")) { - obj->mCapStyle = getLineCap(); - } else if (0 == strcmp(key, "lj")) { - obj->mJoinStyle = getLineJoin(); - } else if (0 == strcmp(key, "ml")) { - if (PeekType() != kNumberType) { - parsingError = true; - return obj; - } - obj->mMiterLimit = GetDouble(); - } else if (0 == strcmp(key, "d")) { - parseDashProperty(obj->mDash); - } else if (0 == strcmp(key, "hd")) { - obj->setHidden(GetBool()); - } else { -#ifdef DEBUG_PARSER - vWarning << "Stroke property skipped = " << key; -#endif - Skip(key); - } - } - if (!IsValid()) { - parsingError = true; - return obj; - } - obj->setStatic(obj->mColor.isStatic() && obj->mOpacity.isStatic() && - obj->mWidth.isStatic() && obj->mDash.isStatic()); - return obj; -} - -void LottieParserImpl::parseGradientProperty(model::Gradient *obj, - const char * key) -{ - obj->colorMap = colorMap; - if (0 == strcmp(key, "t")) { - if (PeekType() != kNumberType) { - parsingError = true; - return; - } - obj->mGradientType = GetInt(); - } else if (0 == strcmp(key, "o")) { - parseProperty(obj->mOpacity); - } else if (0 == strcmp(key, "s")) { - parseProperty(obj->mStartPoint); - } else if (0 == strcmp(key, "e")) { - parseProperty(obj->mEndPoint); - } else if (0 == strcmp(key, "h")) { - parseProperty(obj->mHighlightLength); - } else if (0 == strcmp(key, "a")) { - parseProperty(obj->mHighlightAngle); - } else if (0 == strcmp(key, "g")) { - EnterObject(); - while (const char *key1 = NextObjectKey()) { - if (0 == strcmp(key1, "k")) { - parseProperty(obj->mGradient); - } else if (0 == strcmp(key1, "p")) { - obj->mColorPoints = GetInt(); - } else { - Skip(nullptr); - } - } - if (!IsValid()) { - parsingError = true; - return; - } - } else if (0 == strcmp(key, "hd")) { - obj->setHidden(GetBool()); - } else { -#ifdef DEBUG_PARSER - vWarning << "Gradient property skipped = " << key; -#endif - Skip(key); - } - if (!IsValid()) { - parsingError = true; - return; - } - obj->setStatic( - obj->mOpacity.isStatic() && obj->mStartPoint.isStatic() && - obj->mEndPoint.isStatic() && obj->mHighlightAngle.isStatic() && - obj->mHighlightLength.isStatic() && obj->mGradient.isStatic()); -} - -/* - * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/gfill.json - */ -model::GradientFill *LottieParserImpl::parseGFillObject() -{ - auto obj = allocator().make(); - - while (const char *key = NextObjectKey()) { - if (0 == strcmp(key, "nm")) { - obj->setName(GetString()); - } else if (0 == strcmp(key, "r")) { - obj->mFillRule = getFillRule(); - } else { - parseGradientProperty(obj, key); - } - } - if (!IsValid()) { - parsingError = true; - } - return obj; -} - -void LottieParserImpl::parseDashProperty(model::Dash &dash) -{ - EnterArray(); - while (NextArrayValue()) { - if (parsingError) { - return; - } - if (PeekType() != kObjectType) { - parsingError = true; - return; - } - EnterObject(); - while (const char *key = NextObjectKey()) { - if (0 == strcmp(key, "v")) { - if (dash.size() > 4) { - return; - } - dash.mData.emplace_back(); - parseProperty(dash.mData.back()); - } else { - Skip(key); - } - } - if (!IsValid()) { - parsingError = true; - return; - } - } - if (!IsValid()) { - parsingError = true; - return; - } -} - -/* - * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/gstroke.json - */ -model::GradientStroke *LottieParserImpl::parseGStrokeObject() -{ - auto obj = allocator().make(); - - while (const char *key = NextObjectKey()) { - if (0 == strcmp(key, "nm")) { - obj->setName(GetString()); - } else if (0 == strcmp(key, "w")) { - parseProperty(obj->mWidth); - } else if (0 == strcmp(key, "lc")) { - obj->mCapStyle = getLineCap(); - } else if (0 == strcmp(key, "lj")) { - obj->mJoinStyle = getLineJoin(); - } else if (0 == strcmp(key, "ml")) { - if (PeekType() != kNumberType) { - parsingError = true; - return obj; - } - obj->mMiterLimit = GetDouble(); - } else if (0 == strcmp(key, "d")) { - parseDashProperty(obj->mDash); - } else { - parseGradientProperty(obj, key); - } - } - if (!IsValid()) { - parsingError = true; - return obj; - } - - obj->setStatic(obj->isStatic() && obj->mWidth.isStatic() && - obj->mDash.isStatic()); - return obj; -} - -void LottieParserImpl::getValue(std::vector &v) -{ - if (PeekType() != kArrayType) { - parsingError = true; - return; - } - EnterArray(); - while (NextArrayValue()) { - if (parsingError) { - return; - } - if (PeekType() != kArrayType) { - parsingError = true; - return; - } - EnterArray(); - VPointF pt; - getValue(pt); - v.push_back(pt); - } - if (!IsValid()) { - parsingError = true; - return; - } -} - -void LottieParserImpl::getValue(VPointF &pt) -{ - float val[4] = {0.f}; - int i = 0; - - if (PeekType() == kArrayType) EnterArray(); - - while (NextArrayValue()) { - if (parsingError) { - return; - } - const auto value = GetDouble(); - if (i < 4) { - val[i++] = value; - } - } - if (!IsValid()) { - parsingError = true; - return; - } - - pt.setX(val[0]); - pt.setY(val[1]); -} - -void LottieParserImpl::getValue(float &val) -{ - if (PeekType() == kArrayType) { - EnterArray(); - if (NextArrayValue()) val = GetDouble(); - // discard rest - while (NextArrayValue()) { - if (parsingError) { - return; - } - GetDouble(); - } - if (!IsValid()) { - parsingError = true; - return; - } - } else if (PeekType() == kNumberType) { - val = GetDouble(); - } else { - parsingError = true; - Error(); - } -} - -void LottieParserImpl::getValue(model::Color &color) -{ - float val[4] = {0.f}; - int i = 0; - if (PeekType() == kArrayType) EnterArray(); - - while (NextArrayValue()) { - if (parsingError) { - return; - } - const auto value = GetDouble(); - if (i < 4) { - val[i++] = value; - } - } - if (!IsValid()) { - parsingError = true; - return; - } -#ifdef BIG_ENDIAN_COLORS - color.r = val[2]; - color.g = val[1]; - color.b = val[0]; -#else - color.r = val[0]; - color.g = val[1]; - color.b = val[2]; -#endif - color.colorMap = colorMap; -} - -void LottieParserImpl::getValue(model::Gradient::Data &grad) -{ - if (PeekType() == kArrayType) EnterArray(); - - while (NextArrayValue()) { - if (parsingError) { - return; - } - grad.mGradient.push_back(GetDouble()); - } - if (!IsValid()) { - parsingError = true; - return; - } -} - -void LottieParserImpl::getValue(int &val) -{ - if (PeekType() == kArrayType) { - EnterArray(); - while (NextArrayValue()) { - if (parsingError) { - return; - } - val = GetInt(); - } - if (!IsValid()) { - parsingError = true; - return; - } - } else if (PeekType() == kNumberType) { - val = GetInt(); - } else { - parsingError = true; - Error(); - } -} - -void LottieParserImpl::parsePathInfo() -{ - mPathInfo.reset(); - - /* - * The shape object could be wrapped by a array - * if its part of the keyframe object - */ - bool arrayWrapper = (PeekType() == kArrayType); - if (arrayWrapper) EnterArray(); - - if (PeekType() != kObjectType) { - parsingError = true; - return; - } - EnterObject(); - while (const char *key = NextObjectKey()) { - if (0 == strcmp(key, "i")) { - getValue(mPathInfo.mInPoint); - } else if (0 == strcmp(key, "o")) { - getValue(mPathInfo.mOutPoint); - } else if (0 == strcmp(key, "v")) { - getValue(mPathInfo.mVertices); - } else if (0 == strcmp(key, "c")) { - mPathInfo.mClosed = GetBool(); - } else { - Error(); - Skip(nullptr); - } - } - if (!IsValid()) { - parsingError = true; - return; - } - // exit properly from the array - if (arrayWrapper) NextArrayValue(); - - mPathInfo.convert(); -} - -void LottieParserImpl::getValue(model::PathData &obj) -{ - parsePathInfo(); - obj.mPoints = mPathInfo.mResult; - obj.mClosed = mPathInfo.mClosed; -} - -VPointF LottieParserImpl::parseInperpolatorPoint() -{ - VPointF cp; - if (PeekType() != kObjectType) { - parsingError = true; - return cp; - } - EnterObject(); - while (const char *key = NextObjectKey()) { - if (0 == strcmp(key, "x")) { - getValue(cp.rx()); - } - if (0 == strcmp(key, "y")) { - getValue(cp.ry()); - } - } - if (!IsValid()) { - parsingError = true; - } - return cp; -} - -template -bool LottieParserImpl::parseKeyFrameValue( - const char *key, model::Value &value) -{ - if (0 == strcmp(key, "ti")) { - value.hasTangent_ = true; - getValue(value.inTangent_); - } else if (0 == strcmp(key, "to")) { - value.hasTangent_ = true; - getValue(value.outTangent_); - } else { - return false; - } - return true; -} - -VInterpolator *LottieParserImpl::interpolator(VPointF inTangent, - VPointF outTangent, - std::string key) -{ - if (key.empty()) { - std::array temp; - snprintf(temp.data(), temp.size(), "%.2f_%.2f_%.2f_%.2f", inTangent.x(), - inTangent.y(), outTangent.x(), outTangent.y()); - key = temp.data(); - } - - auto search = mInterpolatorCache.find(key); - - if (search != mInterpolatorCache.end()) { - return search->second; - } - - auto obj = allocator().make(outTangent, inTangent); - mInterpolatorCache[std::move(key)] = obj; - return obj; -} - -/* - * https://github.com/airbnb/lottie-web/blob/master/docs/json/properties/multiDimensionalKeyframed.json - */ -template -void LottieParserImpl::parseKeyFrame(model::KeyFrames &obj) -{ - struct ParsedField { - std::string interpolatorKey; - bool interpolator{false}; - bool value{false}; - bool hold{false}; - bool noEndValue{true}; - }; - - EnterObject(); - ParsedField parsed; - typename model::KeyFrames::Frame keyframe; - VPointF inTangent; - VPointF outTangent; - - while (const char *key = NextObjectKey()) { - if (0 == strcmp(key, "i")) { - parsed.interpolator = true; - inTangent = parseInperpolatorPoint(); - } else if (0 == strcmp(key, "o")) { - outTangent = parseInperpolatorPoint(); - } else if (0 == strcmp(key, "t")) { - keyframe.start_ = GetDouble(); - keyframe.end_ = keyframe.start_; - } else if (0 == strcmp(key, "s")) { - parsed.value = true; - getValue(keyframe.value_.start_); - continue; - } else if (0 == strcmp(key, "e")) { - parsed.noEndValue = false; - getValue(keyframe.value_.end_); - continue; - } else if (0 == strcmp(key, "n")) { - if (PeekType() == kStringType) { - parsed.interpolatorKey = GetStringObject(); - } else { - if (PeekType() != kArrayType) { - parsingError = true; - return; - } - EnterArray(); - while (NextArrayValue()) { - if (parsingError) { - return; - } - if (PeekType() != kStringType) { - parsingError = true; - return; - } - if (parsed.interpolatorKey.empty()) { - parsed.interpolatorKey = GetStringObject(); - } else { - // skip rest of the string - Skip(nullptr); - } - } - if (!IsValid()) { - parsingError = true; - return; - } - } - continue; - } else if (parseKeyFrameValue(key, keyframe.value_)) { - continue; - } else if (0 == strcmp(key, "h")) { - parsed.hold = GetInt(); - continue; - } else { -#ifdef DEBUG_PARSER - vDebug << "key frame property skipped = " << key; -#endif - Skip(key); - } - } - if (!IsValid()) { - parsingError = true; - return; - } - - auto &list = obj.frames_; - if (!list.empty()) { - // update the endFrame value of current keyframe - list.back().end_ = keyframe.start_; - // if no end value provided, copy start value to previous frame - if (parsed.value && parsed.noEndValue) { - list.back().value_.end_ = keyframe.value_.start_; - } - } - - if (parsed.hold) { - keyframe.value_.end_ = keyframe.value_.start_; - keyframe.end_ = keyframe.start_; - list.push_back(std::move(keyframe)); - } else if (parsed.interpolator) { - keyframe.interpolator_ = interpolator( - inTangent, outTangent, std::move(parsed.interpolatorKey)); - list.push_back(std::move(keyframe)); - } else { - // its the last frame discard. - } -} - -/* - * https://github.com/airbnb/lottie-web/blob/master/docs/json/properties/shapeKeyframed.json - */ - -/* - * https://github.com/airbnb/lottie-web/blob/master/docs/json/properties/shape.json - */ -void LottieParserImpl::parseShapeProperty(model::Property &obj) -{ - EnterObject(); - while (const char *key = NextObjectKey()) { - if (0 == strcmp(key, "k")) { - if (PeekType() == kArrayType) { - EnterArray(); - while (NextArrayValue()) { - if (parsingError) { - return; - } - if (PeekType() != kObjectType) { - parsingError = true; - return; - } - parseKeyFrame(obj.animation()); - } - if (!IsValid()) { - parsingError = true; - return; - } - } else { - if (!obj.isStatic()) { - parsingError = true; - st_ = kError; - return; - } - getValue(obj.value()); - } - } else { -#ifdef DEBUG_PARSER - vDebug << "shape property ignored = " << key; -#endif - Skip(nullptr); - } - } - if (!IsValid()) { - parsingError = true; - } else { - obj.cache(); - } -} - -template -void LottieParserImpl::parsePropertyHelper(model::Property &obj) -{ - if (PeekType() == kNumberType) { - if (!obj.isStatic()) { - parsingError = true; - st_ = kError; - return; - } - /*single value property with no animation*/ - getValue(obj.value()); - } else { - if (PeekType() != kArrayType) { - parsingError = true; - return; - } - EnterArray(); - while (NextArrayValue()) { - if (parsingError) { - return; - } - /* property with keyframe info*/ - if (PeekType() == kObjectType) { - parseKeyFrame(obj.animation()); - } else { - /* Read before modifying. - * as there is no way of knowing if the - * value of the array is either array of numbers - * or array of object without entering the array - * thats why this hack is there - */ - if (PeekType() != kNumberType) { - parsingError = true; - return; - } - /*multi value property with no animation*/ - if (!obj.isStatic()) { - parsingError = true; - st_ = kError; - return; - } - getValue(obj.value()); - /*break here as we already reached end of array*/ - break; - } - } - if (!IsValid()) { - parsingError = true; - return; - } else { - obj.cache(); - } - } -} - -/* - * https://github.com/airbnb/lottie-web/tree/master/docs/json/properties - */ -template -void LottieParserImpl::parseProperty(model::Property &obj) -{ - EnterObject(); - while (const char *key = NextObjectKey()) { - if (parsingError) { - return; - } - if (0 == strcmp(key, "k")) { - parsePropertyHelper(obj); - } else { - Skip(key); - } - } - if (!IsValid()) { - parsingError = true; - } -} - -#ifdef LOTTIE_DUMP_TREE_SUPPORT - -class ObjectInspector { -public: - void visit(model::Composition *obj, std::string level) - { - vDebug << " { " << level << "Composition:: a: " << !obj->isStatic() - << ", v: " << obj->mVersion << ", stFm: " << obj->startFrame() - << ", endFm: " << obj->endFrame() - << ", W: " << obj->size().width() - << ", H: " << obj->size().height() << "\n"; - level.append("\t"); - visit(obj->mRootLayer, level); - level.erase(level.end() - 1, level.end()); - vDebug << " } " << level << "Composition End\n"; - } - void visit(model::Layer *obj, std::string level) - { - vDebug << level << "{ " << layerType(obj->mLayerType) - << ", name: " << obj->name() << ", id:" << obj->mId - << " Pid:" << obj->mParentId << ", a:" << !obj->isStatic() - << ", " << matteType(obj->mMatteType) - << ", mask:" << obj->hasMask() << ", inFm:" << obj->mInFrame - << ", outFm:" << obj->mOutFrame << ", stFm:" << obj->mStartFrame - << ", ts:" << obj->mTimeStreatch << ", ao:" << obj->autoOrient() - << ", W:" << obj->layerSize().width() - << ", H:" << obj->layerSize().height(); - - if (obj->mLayerType == model::Layer::Type::Image) - vDebug << level << "\t{ " - << "ImageInfo:" - << " W :" << obj->extra()->mAsset->mWidth - << ", H :" << obj->extra()->mAsset->mHeight << " }" - << "\n"; - else { - vDebug << level; - } - visitChildren(static_cast(obj), level); - vDebug << level << "} " << layerType(obj->mLayerType).c_str() - << ", id: " << obj->mId << "\n"; - } - void visitChildren(model::Group *obj, std::string level) - { - level.append("\t"); - for (const auto &child : obj->mChildren) visit(child, level); - if (obj->mTransform) visit(obj->mTransform, level); - } - - void visit(model::Object *obj, std::string level) - { - switch (obj->type()) { - case model::Object::Type::Repeater: { - auto r = static_cast(obj); - vDebug << level << "{ Repeater: name: " << obj->name() - << " , a:" << !obj->isStatic() - << ", copies:" << r->maxCopies() - << ", offset:" << r->offset(0); - visitChildren(r->mContent, level); - vDebug << level << "} Repeater"; - break; - } - case model::Object::Type::Group: { - vDebug << level << "{ Group: name: " << obj->name() - << " , a:" << !obj->isStatic(); - visitChildren(static_cast(obj), level); - vDebug << level << "} Group"; - break; - } - case model::Object::Type::Layer: { - visit(static_cast(obj), level); - break; - } - case model::Object::Type::Trim: { - vDebug << level << "{ Trim: name: " << obj->name() - << " , a:" << !obj->isStatic() << " }"; - break; - } - case model::Object::Type::Rect: { - vDebug << level << "{ Rect: name: " << obj->name() - << " , a:" << !obj->isStatic() << " }"; - break; - } - case model::Object::Type::RoundedCorner: { - vDebug << level << "{ RoundedCorner: name: " << obj->name() - << " , a:" << !obj->isStatic() << " }"; - break; - } - case model::Object::Type::Ellipse: { - vDebug << level << "{ Ellipse: name: " << obj->name() - << " , a:" << !obj->isStatic() << " }"; - break; - } - case model::Object::Type::Path: { - vDebug << level << "{ Shape: name: " << obj->name() - << " , a:" << !obj->isStatic() << " }"; - break; - } - case model::Object::Type::Polystar: { - vDebug << level << "{ Polystar: name: " << obj->name() - << " , a:" << !obj->isStatic() << " }"; - break; - } - case model::Object::Type::Transform: { - vDebug << level << "{ Transform: name: " << obj->name() - << " , a: " << !obj->isStatic() << " }"; - break; - } - case model::Object::Type::Stroke: { - vDebug << level << "{ Stroke: name: " << obj->name() - << " , a:" << !obj->isStatic() << " }"; - break; - } - case model::Object::Type::GStroke: { - vDebug << level << "{ GStroke: name: " << obj->name() - << " , a:" << !obj->isStatic() << " }"; - break; - } - case model::Object::Type::Fill: { - vDebug << level << "{ Fill: name: " << obj->name() - << " , a:" << !obj->isStatic() << " }"; - break; - } - case model::Object::Type::GFill: { - auto f = static_cast(obj); - vDebug << level << "{ GFill: name: " << obj->name() - << " , a:" << !f->isStatic() << ", ty:" << f->mGradientType - << ", s:" << f->mStartPoint.value(0) - << ", e:" << f->mEndPoint.value(0) << " }"; - break; - } - default: - break; - } - } - - std::string matteType(model::MatteType type) - { - switch (type) { - case model::MatteType::None: - return "Matte::None"; - break; - case model::MatteType::Alpha: - return "Matte::Alpha"; - break; - case model::MatteType::AlphaInv: - return "Matte::AlphaInv"; - break; - case model::MatteType::Luma: - return "Matte::Luma"; - break; - case model::MatteType::LumaInv: - return "Matte::LumaInv"; - break; - default: - return "Matte::Unknown"; - break; - } - } - std::string layerType(model::Layer::Type type) - { - switch (type) { - case model::Layer::Type::Precomp: - return "Layer::Precomp"; - break; - case model::Layer::Type::Null: - return "Layer::Null"; - break; - case model::Layer::Type::Shape: - return "Layer::Shape"; - break; - case model::Layer::Type::Solid: - return "Layer::Solid"; - break; - case model::Layer::Type::Image: - return "Layer::Image"; - break; - case model::Layer::Type::Text: - return "Layer::Text"; - break; - default: - return "Layer::Unknown"; - break; - } - } -}; - -#endif - -std::shared_ptr model::parse(const char * str, - std::string dir_path, - ColorReplace *colorReplacement) -{ - LottieParserImpl obj(str, std::move(dir_path), colorReplacement); - - if (obj.VerifyType()) { - obj.parseComposition(); - if (obj.hasParsingError()) { - vWarning << "Input data corrupt!"; - return {}; - } - auto composition = obj.composition(); - if (composition) { - composition->processRepeaterObjects(); - composition->updateStats(); - -#ifdef LOTTIE_DUMP_TREE_SUPPORT - ObjectInspector inspector; - inspector.visit(composition.get(), ""); -#endif - - return composition; - } - } - - vWarning << "Input data is not Lottie format!"; - return {}; -} - -RAPIDJSON_DIAG_POP diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/config.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/config.h deleted file mode 100644 index 4e90f1e8a..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/config.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef CONFIG_H -#define CONFIG_H - -//enable logging -//#define LOTTIE_LOGGING_SUPPORT - -//#define LOTTIE_THREAD_SUPPORT - -#define LOTTIE_THREAD_SAFE - -#define BIG_ENDIAN_COLORS - -#endif // CONFIG_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_math.cpp b/libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_math.cpp deleted file mode 100644 index a3f0af2e4..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_math.cpp +++ /dev/null @@ -1,461 +0,0 @@ -/***************************************************************************/ -/* */ -/* fttrigon.c */ -/* */ -/* FreeType trigonometric functions (body). */ -/* */ -/* Copyright 2001-2005, 2012-2013 by */ -/* David Turner, Robert Wilhelm, and Werner Lemberg. */ -/* */ -/* This file is part of the FreeType project, and may only be used, */ -/* modified, and distributed under the terms of the FreeType project */ -/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ -/* this file you indicate that you have read the license and */ -/* understand and accept it fully. */ -/* */ -/***************************************************************************/ - -#include "v_ft_math.h" -#include - -//form https://github.com/chromium/chromium/blob/59afd8336009c9d97c22854c52e0382b62b3aa5e/third_party/abseil-cpp/absl/base/internal/bits.h - -#if defined(_MSC_VER) -#include -static unsigned int __inline clz(unsigned int x) { - unsigned long r = 0; - if (x != 0) - { - _BitScanReverse(&r, x); - } - return r; -} -#define SW_FT_MSB(x) (clz(x)) -#elif defined(__GNUC__) -#define SW_FT_MSB(x) (31 - __builtin_clz(x)) -#else -static unsigned int __inline clz(unsigned int x) { - int c = 31; - x &= ~x + 1; - if (n & 0x0000FFFF) c -= 16; - if (n & 0x00FF00FF) c -= 8; - if (n & 0x0F0F0F0F) c -= 4; - if (n & 0x33333333) c -= 2; - if (n & 0x55555555) c -= 1; - return c; -} -#define SW_FT_MSB(x) (clz(x)) -#endif - - - - - -#define SW_FT_PAD_FLOOR(x, n) ((x) & ~((n)-1)) -#define SW_FT_PAD_ROUND(x, n) SW_FT_PAD_FLOOR((x) + ((n) / 2), n) -#define SW_FT_PAD_CEIL(x, n) SW_FT_PAD_FLOOR((x) + ((n)-1), n) - -#define SW_FT_BEGIN_STMNT do { -#define SW_FT_END_STMNT \ - } \ - while (0) -/* transfer sign leaving a positive number */ -#define SW_FT_MOVE_SIGN(x, s) \ - SW_FT_BEGIN_STMNT \ - if (x < 0) { \ - x = -x; \ - s = -s; \ - } \ - SW_FT_END_STMNT - -SW_FT_Long SW_FT_MulFix(SW_FT_Long a, SW_FT_Long b) -{ - SW_FT_Int s = 1; - SW_FT_Long c; - - SW_FT_MOVE_SIGN(a, s); - SW_FT_MOVE_SIGN(b, s); - - c = (SW_FT_Long)(((SW_FT_Int64)a * b + 0x8000L) >> 16); - - return (s > 0) ? c : -c; -} - -SW_FT_Long SW_FT_MulDiv(SW_FT_Long a, SW_FT_Long b, SW_FT_Long c) -{ - SW_FT_Int s = 1; - SW_FT_Long d; - - SW_FT_MOVE_SIGN(a, s); - SW_FT_MOVE_SIGN(b, s); - SW_FT_MOVE_SIGN(c, s); - - d = (SW_FT_Long)(c > 0 ? ((SW_FT_Int64)a * b + (c >> 1)) / c : 0x7FFFFFFFL); - - return (s > 0) ? d : -d; -} - -SW_FT_Long SW_FT_DivFix(SW_FT_Long a, SW_FT_Long b) -{ - SW_FT_Int s = 1; - SW_FT_Long q; - - SW_FT_MOVE_SIGN(a, s); - SW_FT_MOVE_SIGN(b, s); - - q = (SW_FT_Long)(b > 0 ? (((SW_FT_UInt64)a << 16) + (b >> 1)) / b - : 0x7FFFFFFFL); - - return (s < 0 ? -q : q); -} - -/*************************************************************************/ -/* */ -/* This is a fixed-point CORDIC implementation of trigonometric */ -/* functions as well as transformations between Cartesian and polar */ -/* coordinates. The angles are represented as 16.16 fixed-point values */ -/* in degrees, i.e., the angular resolution is 2^-16 degrees. Note that */ -/* only vectors longer than 2^16*180/pi (or at least 22 bits) on a */ -/* discrete Cartesian grid can have the same or better angular */ -/* resolution. Therefore, to maintain this precision, some functions */ -/* require an interim upscaling of the vectors, whereas others operate */ -/* with 24-bit long vectors directly. */ -/* */ -/*************************************************************************/ - -/* the Cordic shrink factor 0.858785336480436 * 2^32 */ -#define SW_FT_TRIG_SCALE 0xDBD95B16UL - -/* the highest bit in overflow-safe vector components, */ -/* MSB of 0.858785336480436 * sqrt(0.5) * 2^30 */ -#define SW_FT_TRIG_SAFE_MSB 29 - -/* this table was generated for SW_FT_PI = 180L << 16, i.e. degrees */ -#define SW_FT_TRIG_MAX_ITERS 23 - -static const SW_FT_Fixed ft_trig_arctan_table[] = { - 1740967L, 919879L, 466945L, 234379L, 117304L, 58666L, 29335L, 14668L, - 7334L, 3667L, 1833L, 917L, 458L, 229L, 115L, 57L, - 29L, 14L, 7L, 4L, 2L, 1L}; - -/* multiply a given value by the CORDIC shrink factor */ -static SW_FT_Fixed ft_trig_downscale(SW_FT_Fixed val) -{ - SW_FT_Fixed s; - SW_FT_Int64 v; - - s = val; - val = SW_FT_ABS(val); - - v = (val * (SW_FT_Int64)SW_FT_TRIG_SCALE) + 0x100000000UL; - val = (SW_FT_Fixed)(v >> 32); - - return (s >= 0) ? val : -val; -} - -/* undefined and never called for zero vector */ -static SW_FT_Int ft_trig_prenorm(SW_FT_Vector* vec) -{ - SW_FT_Pos x, y; - SW_FT_Int shift; - - x = vec->x; - y = vec->y; - - shift = SW_FT_MSB(SW_FT_ABS(x) | SW_FT_ABS(y)); - - if (shift <= SW_FT_TRIG_SAFE_MSB) { - shift = SW_FT_TRIG_SAFE_MSB - shift; - vec->x = (SW_FT_Pos)((SW_FT_ULong)x << shift); - vec->y = (SW_FT_Pos)((SW_FT_ULong)y << shift); - } else { - shift -= SW_FT_TRIG_SAFE_MSB; - vec->x = x >> shift; - vec->y = y >> shift; - shift = -shift; - } - - return shift; -} - -static void ft_trig_pseudo_rotate(SW_FT_Vector* vec, SW_FT_Angle theta) -{ - SW_FT_Int i; - SW_FT_Fixed x, y, xtemp, b; - const SW_FT_Fixed* arctanptr; - - x = vec->x; - y = vec->y; - - /* Rotate inside [-PI/4,PI/4] sector */ - while (theta < -SW_FT_ANGLE_PI4) { - xtemp = y; - y = -x; - x = xtemp; - theta += SW_FT_ANGLE_PI2; - } - - while (theta > SW_FT_ANGLE_PI4) { - xtemp = -y; - y = x; - x = xtemp; - theta -= SW_FT_ANGLE_PI2; - } - - arctanptr = ft_trig_arctan_table; - - /* Pseudorotations, with right shifts */ - for (i = 1, b = 1; i < SW_FT_TRIG_MAX_ITERS; b <<= 1, i++) { - SW_FT_Fixed v1 = ((y + b) >> i); - SW_FT_Fixed v2 = ((x + b) >> i); - if (theta < 0) { - xtemp = x + v1; - y = y - v2; - x = xtemp; - theta += *arctanptr++; - } else { - xtemp = x - v1; - y = y + v2; - x = xtemp; - theta -= *arctanptr++; - } - } - - vec->x = x; - vec->y = y; -} - -static void ft_trig_pseudo_polarize(SW_FT_Vector* vec) -{ - SW_FT_Angle theta; - SW_FT_Int i; - SW_FT_Fixed x, y, xtemp, b; - const SW_FT_Fixed* arctanptr; - - x = vec->x; - y = vec->y; - - /* Get the vector into [-PI/4,PI/4] sector */ - if (y > x) { - if (y > -x) { - theta = SW_FT_ANGLE_PI2; - xtemp = y; - y = -x; - x = xtemp; - } else { - theta = y > 0 ? SW_FT_ANGLE_PI : -SW_FT_ANGLE_PI; - x = -x; - y = -y; - } - } else { - if (y < -x) { - theta = -SW_FT_ANGLE_PI2; - xtemp = -y; - y = x; - x = xtemp; - } else { - theta = 0; - } - } - - arctanptr = ft_trig_arctan_table; - - /* Pseudorotations, with right shifts */ - for (i = 1, b = 1; i < SW_FT_TRIG_MAX_ITERS; b <<= 1, i++) { - SW_FT_Fixed v1 = ((y + b) >> i); - SW_FT_Fixed v2 = ((x + b) >> i); - if (y > 0) { - xtemp = x + v1; - y = y - v2; - x = xtemp; - theta += *arctanptr++; - } else { - xtemp = x - v1; - y = y + v2; - x = xtemp; - theta -= *arctanptr++; - } - } - - /* round theta */ - if (theta >= 0) - theta = SW_FT_PAD_ROUND(theta, 32); - else - theta = -SW_FT_PAD_ROUND(-theta, 32); - - vec->x = x; - vec->y = theta; -} - -/* documentation is in fttrigon.h */ - -SW_FT_Fixed SW_FT_Cos(SW_FT_Angle angle) -{ - SW_FT_Vector v; - - v.x = SW_FT_TRIG_SCALE >> 8; - v.y = 0; - ft_trig_pseudo_rotate(&v, angle); - - return (v.x + 0x80L) >> 8; -} - -/* documentation is in fttrigon.h */ - -SW_FT_Fixed SW_FT_Sin(SW_FT_Angle angle) -{ - return SW_FT_Cos(SW_FT_ANGLE_PI2 - angle); -} - -/* documentation is in fttrigon.h */ - -SW_FT_Fixed SW_FT_Tan(SW_FT_Angle angle) -{ - SW_FT_Vector v; - - v.x = SW_FT_TRIG_SCALE >> 8; - v.y = 0; - ft_trig_pseudo_rotate(&v, angle); - - return SW_FT_DivFix(v.y, v.x); -} - -/* documentation is in fttrigon.h */ - -SW_FT_Angle SW_FT_Atan2(SW_FT_Fixed dx, SW_FT_Fixed dy) -{ - SW_FT_Vector v; - - if (dx == 0 && dy == 0) return 0; - - v.x = dx; - v.y = dy; - ft_trig_prenorm(&v); - ft_trig_pseudo_polarize(&v); - - return v.y; -} - -/* documentation is in fttrigon.h */ - -void SW_FT_Vector_Unit(SW_FT_Vector* vec, SW_FT_Angle angle) -{ - vec->x = SW_FT_TRIG_SCALE >> 8; - vec->y = 0; - ft_trig_pseudo_rotate(vec, angle); - vec->x = (vec->x + 0x80L) >> 8; - vec->y = (vec->y + 0x80L) >> 8; -} - -/* these macros return 0 for positive numbers, - and -1 for negative ones */ -#define SW_FT_SIGN_LONG(x) ((x) >> (SW_FT_SIZEOF_LONG * 8 - 1)) -#define SW_FT_SIGN_INT(x) ((x) >> (SW_FT_SIZEOF_INT * 8 - 1)) -#define SW_FT_SIGN_INT32(x) ((x) >> 31) -#define SW_FT_SIGN_INT16(x) ((x) >> 15) - -/* documentation is in fttrigon.h */ - -void SW_FT_Vector_Rotate(SW_FT_Vector* vec, SW_FT_Angle angle) -{ - SW_FT_Int shift; - SW_FT_Vector v; - - v.x = vec->x; - v.y = vec->y; - - if (angle && (v.x != 0 || v.y != 0)) { - shift = ft_trig_prenorm(&v); - ft_trig_pseudo_rotate(&v, angle); - v.x = ft_trig_downscale(v.x); - v.y = ft_trig_downscale(v.y); - - if (shift > 0) { - SW_FT_Int32 half = (SW_FT_Int32)1L << (shift - 1); - - vec->x = (v.x + half + SW_FT_SIGN_LONG(v.x)) >> shift; - vec->y = (v.y + half + SW_FT_SIGN_LONG(v.y)) >> shift; - } else { - shift = -shift; - vec->x = (SW_FT_Pos)((SW_FT_ULong)v.x << shift); - vec->y = (SW_FT_Pos)((SW_FT_ULong)v.y << shift); - } - } -} - -/* documentation is in fttrigon.h */ - -SW_FT_Fixed SW_FT_Vector_Length(SW_FT_Vector* vec) -{ - SW_FT_Int shift; - SW_FT_Vector v; - - v = *vec; - - /* handle trivial cases */ - if (v.x == 0) { - return SW_FT_ABS(v.y); - } else if (v.y == 0) { - return SW_FT_ABS(v.x); - } - - /* general case */ - shift = ft_trig_prenorm(&v); - ft_trig_pseudo_polarize(&v); - - v.x = ft_trig_downscale(v.x); - - if (shift > 0) return (v.x + (1 << (shift - 1))) >> shift; - - return (SW_FT_Fixed)((SW_FT_UInt32)v.x << -shift); -} - -/* documentation is in fttrigon.h */ - -void SW_FT_Vector_Polarize(SW_FT_Vector* vec, SW_FT_Fixed* length, - SW_FT_Angle* angle) -{ - SW_FT_Int shift; - SW_FT_Vector v; - - v = *vec; - - if (v.x == 0 && v.y == 0) return; - - shift = ft_trig_prenorm(&v); - ft_trig_pseudo_polarize(&v); - - v.x = ft_trig_downscale(v.x); - - *length = (shift >= 0) ? (v.x >> shift) - : (SW_FT_Fixed)((SW_FT_UInt32)v.x << -shift); - *angle = v.y; -} - -/* documentation is in fttrigon.h */ - -void SW_FT_Vector_From_Polar(SW_FT_Vector* vec, SW_FT_Fixed length, - SW_FT_Angle angle) -{ - vec->x = length; - vec->y = 0; - - SW_FT_Vector_Rotate(vec, angle); -} - -/* documentation is in fttrigon.h */ - -SW_FT_Angle SW_FT_Angle_Diff( SW_FT_Angle angle1, SW_FT_Angle angle2 ) -{ - SW_FT_Angle delta = angle2 - angle1; - - while ( delta <= -SW_FT_ANGLE_PI ) - delta += SW_FT_ANGLE_2PI; - - while ( delta > SW_FT_ANGLE_PI ) - delta -= SW_FT_ANGLE_2PI; - - return delta; -} - -/* END */ diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_math.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_math.h deleted file mode 100644 index b4611d8d4..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_math.h +++ /dev/null @@ -1,438 +0,0 @@ -#ifndef V_FT_MATH_H -#define V_FT_MATH_H - -/***************************************************************************/ -/* */ -/* fttrigon.h */ -/* */ -/* FreeType trigonometric functions (specification). */ -/* */ -/* Copyright 2001, 2003, 2005, 2007, 2013 by */ -/* David Turner, Robert Wilhelm, and Werner Lemberg. */ -/* */ -/* This file is part of the FreeType project, and may only be used, */ -/* modified, and distributed under the terms of the FreeType project */ -/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ -/* this file you indicate that you have read the license and */ -/* understand and accept it fully. */ -/* */ -/***************************************************************************/ - -#include "v_ft_types.h" - - -/*************************************************************************/ -/* */ -/* The min and max functions missing in C. As usual, be careful not to */ -/* write things like SW_FT_MIN( a++, b++ ) to avoid side effects. */ -/* */ -#define SW_FT_MIN( a, b ) ( (a) < (b) ? (a) : (b) ) -#define SW_FT_MAX( a, b ) ( (a) > (b) ? (a) : (b) ) - -#define SW_FT_ABS( a ) ( (a) < 0 ? -(a) : (a) ) - -/* - * Approximate sqrt(x*x+y*y) using the `alpha max plus beta min' - * algorithm. We use alpha = 1, beta = 3/8, giving us results with a - * largest error less than 7% compared to the exact value. - */ -#define SW_FT_HYPOT( x, y ) \ - ( x = SW_FT_ABS( x ), \ - y = SW_FT_ABS( y ), \ - x > y ? x + ( 3 * y >> 3 ) \ - : y + ( 3 * x >> 3 ) ) - -/*************************************************************************/ -/* */ -/* */ -/* SW_FT_MulFix */ -/* */ -/* */ -/* A very simple function used to perform the computation */ -/* `(a*b)/0x10000' with maximum accuracy. Most of the time this is */ -/* used to multiply a given value by a 16.16 fixed-point factor. */ -/* */ -/* */ -/* a :: The first multiplier. */ -/* b :: The second multiplier. Use a 16.16 factor here whenever */ -/* possible (see note below). */ -/* */ -/* */ -/* The result of `(a*b)/0x10000'. */ -/* */ -/* */ -/* This function has been optimized for the case where the absolute */ -/* value of `a' is less than 2048, and `b' is a 16.16 scaling factor. */ -/* As this happens mainly when scaling from notional units to */ -/* fractional pixels in FreeType, it resulted in noticeable speed */ -/* improvements between versions 2.x and 1.x. */ -/* */ -/* As a conclusion, always try to place a 16.16 factor as the */ -/* _second_ argument of this function; this can make a great */ -/* difference. */ -/* */ -SW_FT_Long -SW_FT_MulFix( SW_FT_Long a, - SW_FT_Long b ); - -/*************************************************************************/ -/* */ -/* */ -/* SW_FT_MulDiv */ -/* */ -/* */ -/* A very simple function used to perform the computation `(a*b)/c' */ -/* with maximum accuracy (it uses a 64-bit intermediate integer */ -/* whenever necessary). */ -/* */ -/* This function isn't necessarily as fast as some processor specific */ -/* operations, but is at least completely portable. */ -/* */ -/* */ -/* a :: The first multiplier. */ -/* b :: The second multiplier. */ -/* c :: The divisor. */ -/* */ -/* */ -/* The result of `(a*b)/c'. This function never traps when trying to */ -/* divide by zero; it simply returns `MaxInt' or `MinInt' depending */ -/* on the signs of `a' and `b'. */ -/* */ -SW_FT_Long -SW_FT_MulDiv( SW_FT_Long a, - SW_FT_Long b, - SW_FT_Long c ); - -/*************************************************************************/ -/* */ -/* */ -/* SW_FT_DivFix */ -/* */ -/* */ -/* A very simple function used to perform the computation */ -/* `(a*0x10000)/b' with maximum accuracy. Most of the time, this is */ -/* used to divide a given value by a 16.16 fixed-point factor. */ -/* */ -/* */ -/* a :: The numerator. */ -/* b :: The denominator. Use a 16.16 factor here. */ -/* */ -/* */ -/* The result of `(a*0x10000)/b'. */ -/* */ -SW_FT_Long -SW_FT_DivFix( SW_FT_Long a, - SW_FT_Long b ); - - - - /*************************************************************************/ - /* */ - /*
*/ - /* computations */ - /* */ - /*************************************************************************/ - - - /************************************************************************* - * - * @type: - * SW_FT_Angle - * - * @description: - * This type is used to model angle values in FreeType. Note that the - * angle is a 16.16 fixed-point value expressed in degrees. - * - */ - typedef SW_FT_Fixed SW_FT_Angle; - - - /************************************************************************* - * - * @macro: - * SW_FT_ANGLE_PI - * - * @description: - * The angle pi expressed in @SW_FT_Angle units. - * - */ -#define SW_FT_ANGLE_PI ( 180L << 16 ) - - - /************************************************************************* - * - * @macro: - * SW_FT_ANGLE_2PI - * - * @description: - * The angle 2*pi expressed in @SW_FT_Angle units. - * - */ -#define SW_FT_ANGLE_2PI ( SW_FT_ANGLE_PI * 2 ) - - - /************************************************************************* - * - * @macro: - * SW_FT_ANGLE_PI2 - * - * @description: - * The angle pi/2 expressed in @SW_FT_Angle units. - * - */ -#define SW_FT_ANGLE_PI2 ( SW_FT_ANGLE_PI / 2 ) - - - /************************************************************************* - * - * @macro: - * SW_FT_ANGLE_PI4 - * - * @description: - * The angle pi/4 expressed in @SW_FT_Angle units. - * - */ -#define SW_FT_ANGLE_PI4 ( SW_FT_ANGLE_PI / 4 ) - - - /************************************************************************* - * - * @function: - * SW_FT_Sin - * - * @description: - * Return the sinus of a given angle in fixed-point format. - * - * @input: - * angle :: - * The input angle. - * - * @return: - * The sinus value. - * - * @note: - * If you need both the sinus and cosinus for a given angle, use the - * function @SW_FT_Vector_Unit. - * - */ - SW_FT_Fixed - SW_FT_Sin( SW_FT_Angle angle ); - - - /************************************************************************* - * - * @function: - * SW_FT_Cos - * - * @description: - * Return the cosinus of a given angle in fixed-point format. - * - * @input: - * angle :: - * The input angle. - * - * @return: - * The cosinus value. - * - * @note: - * If you need both the sinus and cosinus for a given angle, use the - * function @SW_FT_Vector_Unit. - * - */ - SW_FT_Fixed - SW_FT_Cos( SW_FT_Angle angle ); - - - /************************************************************************* - * - * @function: - * SW_FT_Tan - * - * @description: - * Return the tangent of a given angle in fixed-point format. - * - * @input: - * angle :: - * The input angle. - * - * @return: - * The tangent value. - * - */ - SW_FT_Fixed - SW_FT_Tan( SW_FT_Angle angle ); - - - /************************************************************************* - * - * @function: - * SW_FT_Atan2 - * - * @description: - * Return the arc-tangent corresponding to a given vector (x,y) in - * the 2d plane. - * - * @input: - * x :: - * The horizontal vector coordinate. - * - * y :: - * The vertical vector coordinate. - * - * @return: - * The arc-tangent value (i.e. angle). - * - */ - SW_FT_Angle - SW_FT_Atan2( SW_FT_Fixed x, - SW_FT_Fixed y ); - - - /************************************************************************* - * - * @function: - * SW_FT_Angle_Diff - * - * @description: - * Return the difference between two angles. The result is always - * constrained to the ]-PI..PI] interval. - * - * @input: - * angle1 :: - * First angle. - * - * angle2 :: - * Second angle. - * - * @return: - * Constrained value of `value2-value1'. - * - */ - SW_FT_Angle - SW_FT_Angle_Diff( SW_FT_Angle angle1, - SW_FT_Angle angle2 ); - - - /************************************************************************* - * - * @function: - * SW_FT_Vector_Unit - * - * @description: - * Return the unit vector corresponding to a given angle. After the - * call, the value of `vec.x' will be `sin(angle)', and the value of - * `vec.y' will be `cos(angle)'. - * - * This function is useful to retrieve both the sinus and cosinus of a - * given angle quickly. - * - * @output: - * vec :: - * The address of target vector. - * - * @input: - * angle :: - * The input angle. - * - */ - void - SW_FT_Vector_Unit( SW_FT_Vector* vec, - SW_FT_Angle angle ); - - - /************************************************************************* - * - * @function: - * SW_FT_Vector_Rotate - * - * @description: - * Rotate a vector by a given angle. - * - * @inout: - * vec :: - * The address of target vector. - * - * @input: - * angle :: - * The input angle. - * - */ - void - SW_FT_Vector_Rotate( SW_FT_Vector* vec, - SW_FT_Angle angle ); - - - /************************************************************************* - * - * @function: - * SW_FT_Vector_Length - * - * @description: - * Return the length of a given vector. - * - * @input: - * vec :: - * The address of target vector. - * - * @return: - * The vector length, expressed in the same units that the original - * vector coordinates. - * - */ - SW_FT_Fixed - SW_FT_Vector_Length( SW_FT_Vector* vec ); - - - /************************************************************************* - * - * @function: - * SW_FT_Vector_Polarize - * - * @description: - * Compute both the length and angle of a given vector. - * - * @input: - * vec :: - * The address of source vector. - * - * @output: - * length :: - * The vector length. - * - * angle :: - * The vector angle. - * - */ - void - SW_FT_Vector_Polarize( SW_FT_Vector* vec, - SW_FT_Fixed *length, - SW_FT_Angle *angle ); - - - /************************************************************************* - * - * @function: - * SW_FT_Vector_From_Polar - * - * @description: - * Compute vector coordinates from a length and angle. - * - * @output: - * vec :: - * The address of source vector. - * - * @input: - * length :: - * The vector length. - * - * angle :: - * The vector angle. - * - */ - void - SW_FT_Vector_From_Polar( SW_FT_Vector* vec, - SW_FT_Fixed length, - SW_FT_Angle angle ); - - -#endif // V_FT_MATH_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_raster.cpp b/libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_raster.cpp deleted file mode 100644 index 62299897d..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_raster.cpp +++ /dev/null @@ -1,1423 +0,0 @@ -/***************************************************************************/ -/* */ -/* ftgrays.c */ -/* */ -/* A new `perfect' anti-aliasing renderer (body). */ -/* */ -/* Copyright 2000-2003, 2005-2014 by */ -/* David Turner, Robert Wilhelm, and Werner Lemberg. */ -/* */ -/* This file is part of the FreeType project, and may only be used, */ -/* modified, and distributed under the terms of the FreeType project */ -/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ -/* this file you indicate that you have read the license and */ -/* understand and accept it fully. */ -/* */ -/***************************************************************************/ - -/*************************************************************************/ -/* */ -/* This is a new anti-aliasing scan-converter for FreeType 2. The */ -/* algorithm used here is _very_ different from the one in the standard */ -/* `ftraster' module. Actually, `ftgrays' computes the _exact_ */ -/* coverage of the outline on each pixel cell. */ -/* */ -/* It is based on ideas that I initially found in Raph Levien's */ -/* excellent LibArt graphics library (see http://www.levien.com/libart */ -/* for more information, though the web pages do not tell anything */ -/* about the renderer; you'll have to dive into the source code to */ -/* understand how it works). */ -/* */ -/* Note, however, that this is a _very_ different implementation */ -/* compared to Raph's. Coverage information is stored in a very */ -/* different way, and I don't use sorted vector paths. Also, it doesn't */ -/* use floating point values. */ -/* */ -/* This renderer has the following advantages: */ -/* */ -/* - It doesn't need an intermediate bitmap. Instead, one can supply a */ -/* callback function that will be called by the renderer to draw gray */ -/* spans on any target surface. You can thus do direct composition on */ -/* any kind of bitmap, provided that you give the renderer the right */ -/* callback. */ -/* */ -/* - A perfect anti-aliaser, i.e., it computes the _exact_ coverage on */ -/* each pixel cell. */ -/* */ -/* - It performs a single pass on the outline (the `standard' FT2 */ -/* renderer makes two passes). */ -/* */ -/* - It can easily be modified to render to _any_ number of gray levels */ -/* cheaply. */ -/* */ -/* - For small (< 20) pixel sizes, it is faster than the standard */ -/* renderer. */ -/* */ -/*************************************************************************/ - -#include "v_ft_raster.h" -#include "v_ft_math.h" - -/* Auxiliary macros for token concatenation. */ -#define SW_FT_ERR_XCAT(x, y) x##y -#define SW_FT_ERR_CAT(x, y) SW_FT_ERR_XCAT(x, y) - -#define SW_FT_BEGIN_STMNT do { -#define SW_FT_END_STMNT \ - } \ - while (0) - -#include -#include -#include -#include -#define SW_FT_UINT_MAX UINT_MAX -#define SW_FT_INT_MAX INT_MAX -#define SW_FT_ULONG_MAX ULONG_MAX -#define SW_FT_CHAR_BIT CHAR_BIT - -#define ft_memset memset - -#define ft_setjmp setjmp -#define ft_longjmp longjmp -#define ft_jmp_buf jmp_buf - -typedef ptrdiff_t SW_FT_PtrDist; - -#define ErrRaster_Invalid_Mode -2 -#define ErrRaster_Invalid_Outline -1 -#define ErrRaster_Invalid_Argument -3 -#define ErrRaster_Memory_Overflow -4 - -#define SW_FT_BEGIN_HEADER -#define SW_FT_END_HEADER - -/* This macro is used to indicate that a function parameter is unused. */ -/* Its purpose is simply to reduce compiler warnings. Note also that */ -/* simply defining it as `(void)x' doesn't avoid warnings with certain */ -/* ANSI compilers (e.g. LCC). */ -#define SW_FT_UNUSED(x) (x) = (x) - -#define SW_FT_THROW(e) SW_FT_ERR_CAT(ErrRaster_, e) - -/* The size in bytes of the render pool used by the scan-line converter */ -/* to do all of its work. */ -#define SW_FT_RENDER_POOL_SIZE 16384L - -typedef int (*SW_FT_Outline_MoveToFunc)(const SW_FT_Vector* to, void* user); - -#define SW_FT_Outline_MoveTo_Func SW_FT_Outline_MoveToFunc - -typedef int (*SW_FT_Outline_LineToFunc)(const SW_FT_Vector* to, void* user); - -#define SW_FT_Outline_LineTo_Func SW_FT_Outline_LineToFunc - -typedef int (*SW_FT_Outline_ConicToFunc)(const SW_FT_Vector* control, - const SW_FT_Vector* to, void* user); - -#define SW_FT_Outline_ConicTo_Func SW_FT_Outline_ConicToFunc - -typedef int (*SW_FT_Outline_CubicToFunc)(const SW_FT_Vector* control1, - const SW_FT_Vector* control2, - const SW_FT_Vector* to, void* user); - -#define SW_FT_Outline_CubicTo_Func SW_FT_Outline_CubicToFunc - -typedef struct SW_FT_Outline_Funcs_ { - SW_FT_Outline_MoveToFunc move_to; - SW_FT_Outline_LineToFunc line_to; - SW_FT_Outline_ConicToFunc conic_to; - SW_FT_Outline_CubicToFunc cubic_to; - - int shift; - SW_FT_Pos delta; - -} SW_FT_Outline_Funcs; - -#define SW_FT_DEFINE_OUTLINE_FUNCS(class_, move_to_, line_to_, conic_to_, \ - cubic_to_, shift_, delta_) \ - static const SW_FT_Outline_Funcs class_ = {move_to_, line_to_, conic_to_, \ - cubic_to_, shift_, delta_}; - -#define SW_FT_DEFINE_RASTER_FUNCS(class_, raster_new_, raster_reset_, \ - raster_render_, raster_done_) \ - const SW_FT_Raster_Funcs class_ = {raster_new_, raster_reset_, \ - raster_render_, raster_done_}; - -#ifndef SW_FT_MEM_SET -#define SW_FT_MEM_SET(d, s, c) ft_memset(d, s, c) -#endif - -#ifndef SW_FT_MEM_ZERO -#define SW_FT_MEM_ZERO(dest, count) SW_FT_MEM_SET(dest, 0, count) -#endif - -/* as usual, for the speed hungry :-) */ - -#undef RAS_ARG -#undef RAS_ARG_ -#undef RAS_VAR -#undef RAS_VAR_ - -#ifndef SW_FT_STATIC_RASTER - -#define RAS_ARG gray_PWorker worker -#define RAS_ARG_ gray_PWorker worker, - -#define RAS_VAR worker -#define RAS_VAR_ worker, - -#else /* SW_FT_STATIC_RASTER */ - -#define RAS_ARG /* empty */ -#define RAS_ARG_ /* empty */ -#define RAS_VAR /* empty */ -#define RAS_VAR_ /* empty */ - -#endif /* SW_FT_STATIC_RASTER */ - -/* must be at least 6 bits! */ -#define PIXEL_BITS 8 - -#undef FLOOR -#undef CEILING -#undef TRUNC -#undef SCALED - -#define ONE_PIXEL (1L << PIXEL_BITS) -#define PIXEL_MASK (-1L << PIXEL_BITS) -#define TRUNC(x) ((TCoord)((x) >> PIXEL_BITS)) -#define SUBPIXELS(x) ((TPos)(x) << PIXEL_BITS) -#define FLOOR(x) ((x) & -ONE_PIXEL) -#define CEILING(x) (((x) + ONE_PIXEL - 1) & -ONE_PIXEL) -#define ROUND(x) (((x) + ONE_PIXEL / 2) & -ONE_PIXEL) - -#if PIXEL_BITS >= 6 -#define UPSCALE(x) ((x) << (PIXEL_BITS - 6)) -#define DOWNSCALE(x) ((x) >> (PIXEL_BITS - 6)) -#else -#define UPSCALE(x) ((x) >> (6 - PIXEL_BITS)) -#define DOWNSCALE(x) ((x) << (6 - PIXEL_BITS)) -#endif - -/* Compute `dividend / divisor' and return both its quotient and */ -/* remainder, cast to a specific type. This macro also ensures that */ -/* the remainder is always positive. */ -#define SW_FT_DIV_MOD(type, dividend, divisor, quotient, remainder) \ - SW_FT_BEGIN_STMNT(quotient) = (type)((dividend) / (divisor)); \ - (remainder) = (type)((dividend) % (divisor)); \ - if ((remainder) < 0) { \ - (quotient)--; \ - (remainder) += (type)(divisor); \ - } \ - SW_FT_END_STMNT - -#ifdef __arm__ -/* Work around a bug specific to GCC which make the compiler fail to */ -/* optimize a division and modulo operation on the same parameters */ -/* into a single call to `__aeabi_idivmod'. See */ -/* */ -/* http://gcc.gnu.org/bugzilla/show_bug.cgi?id=43721 */ -#undef SW_FT_DIV_MOD -#define SW_FT_DIV_MOD(type, dividend, divisor, quotient, remainder) \ - SW_FT_BEGIN_STMNT(quotient) = (type)((dividend) / (divisor)); \ - (remainder) = (type)((dividend) - (quotient) * (divisor)); \ - if ((remainder) < 0) { \ - (quotient)--; \ - (remainder) += (type)(divisor); \ - } \ - SW_FT_END_STMNT -#endif /* __arm__ */ - -/* These macros speed up repetitive divisions by replacing them */ -/* with multiplications and right shifts. */ -#define SW_FT_UDIVPREP(b) \ - long b##_r = (long)(SW_FT_ULONG_MAX >> PIXEL_BITS) / (b) -#define SW_FT_UDIV(a, b) \ - (((unsigned long)(a) * (unsigned long)(b##_r)) >> \ - (sizeof(long) * SW_FT_CHAR_BIT - PIXEL_BITS)) - -/*************************************************************************/ -/* */ -/* TYPE DEFINITIONS */ -/* */ - -/* don't change the following types to SW_FT_Int or SW_FT_Pos, since we might */ -/* need to define them to "float" or "double" when experimenting with */ -/* new algorithms */ - -typedef long TCoord; /* integer scanline/pixel coordinate */ -typedef long TPos; /* sub-pixel coordinate */ - -/* determine the type used to store cell areas. This normally takes at */ -/* least PIXEL_BITS*2 + 1 bits. On 16-bit systems, we need to use */ -/* `long' instead of `int', otherwise bad things happen */ - -#if PIXEL_BITS <= 7 - -typedef int TArea; - -#else /* PIXEL_BITS >= 8 */ - -/* approximately determine the size of integers using an ANSI-C header */ -#if SW_FT_UINT_MAX == 0xFFFFU -typedef long TArea; -#else -typedef int TArea; -#endif - -#endif /* PIXEL_BITS >= 8 */ - -/* maximum number of gray spans in a call to the span callback */ -#define SW_FT_MAX_GRAY_SPANS 256 - -typedef struct TCell_* PCell; - -typedef struct TCell_ { - TPos x; /* same with gray_TWorker.ex */ - TCoord cover; /* same with gray_TWorker.cover */ - TArea area; - PCell next; - -} TCell; - -#if defined(_MSC_VER) /* Visual C++ (and Intel C++) */ -/* We disable the warning `structure was padded due to */ -/* __declspec(align())' in order to compile cleanly with */ -/* the maximum level of warnings. */ -#pragma warning(push) -#pragma warning(disable : 4324) -#endif /* _MSC_VER */ - -typedef struct gray_TWorker_ { - TCoord ex, ey; - TPos min_ex, max_ex; - TPos min_ey, max_ey; - TPos count_ex, count_ey; - - TArea area; - TCoord cover; - int invalid; - - PCell cells; - SW_FT_PtrDist max_cells; - SW_FT_PtrDist num_cells; - - TPos x, y; - - SW_FT_Vector bez_stack[32 * 3 + 1]; - int lev_stack[32]; - - SW_FT_Outline outline; - SW_FT_BBox clip_box; - - int bound_left; - int bound_top; - int bound_right; - int bound_bottom; - - SW_FT_Span gray_spans[SW_FT_MAX_GRAY_SPANS]; - int num_gray_spans; - - SW_FT_Raster_Span_Func render_span; - void* render_span_data; - - int band_size; - int band_shoot; - - ft_jmp_buf jump_buffer; - - void* buffer; - long buffer_size; - - PCell* ycells; - TPos ycount; - -} gray_TWorker, *gray_PWorker; - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif - -#ifndef SW_FT_STATIC_RASTER -#define ras (*worker) -#else -static gray_TWorker ras; -#endif - -typedef struct gray_TRaster_ { - void* memory; - -} gray_TRaster, *gray_PRaster; - -/*************************************************************************/ -/* */ -/* Initialize the cells table. */ -/* */ -static void gray_init_cells(RAS_ARG_ void* buffer, long byte_size) -{ - ras.buffer = buffer; - ras.buffer_size = byte_size; - - ras.ycells = (PCell*)buffer; - ras.cells = NULL; - ras.max_cells = 0; - ras.num_cells = 0; - ras.area = 0; - ras.cover = 0; - ras.invalid = 1; - - ras.bound_left = INT_MAX; - ras.bound_top = INT_MAX; - ras.bound_right = INT_MIN; - ras.bound_bottom = INT_MIN; -} - -/*************************************************************************/ -/* */ -/* Compute the outline bounding box. */ -/* */ -static void gray_compute_cbox(RAS_ARG) -{ - SW_FT_Outline* outline = &ras.outline; - SW_FT_Vector* vec = outline->points; - SW_FT_Vector* limit = vec + outline->n_points; - - if (outline->n_points <= 0) { - ras.min_ex = ras.max_ex = 0; - ras.min_ey = ras.max_ey = 0; - return; - } - - ras.min_ex = ras.max_ex = vec->x; - ras.min_ey = ras.max_ey = vec->y; - - vec++; - - for (; vec < limit; vec++) { - TPos x = vec->x; - TPos y = vec->y; - - if (x < ras.min_ex) ras.min_ex = x; - if (x > ras.max_ex) ras.max_ex = x; - if (y < ras.min_ey) ras.min_ey = y; - if (y > ras.max_ey) ras.max_ey = y; - } - - /* truncate the bounding box to integer pixels */ - ras.min_ex = ras.min_ex >> 6; - ras.min_ey = ras.min_ey >> 6; - ras.max_ex = (ras.max_ex + 63) >> 6; - ras.max_ey = (ras.max_ey + 63) >> 6; -} - -/*************************************************************************/ -/* */ -/* Record the current cell in the table. */ -/* */ -static PCell gray_find_cell(RAS_ARG) -{ - PCell *pcell, cell; - TPos x = ras.ex; - - if (x > ras.count_ex) x = ras.count_ex; - - pcell = &ras.ycells[ras.ey]; - for (;;) { - cell = *pcell; - if (cell == NULL || cell->x > x) break; - - if (cell->x == x) goto Exit; - - pcell = &cell->next; - } - - if (ras.num_cells >= ras.max_cells) ft_longjmp(ras.jump_buffer, 1); - - cell = ras.cells + ras.num_cells++; - cell->x = x; - cell->area = 0; - cell->cover = 0; - - cell->next = *pcell; - *pcell = cell; - -Exit: - return cell; -} - -static void gray_record_cell(RAS_ARG) -{ - if (ras.area | ras.cover) { - PCell cell = gray_find_cell(RAS_VAR); - - cell->area += ras.area; - cell->cover += ras.cover; - } -} - -/*************************************************************************/ -/* */ -/* Set the current cell to a new position. */ -/* */ -static void gray_set_cell(RAS_ARG_ TCoord ex, TCoord ey) -{ - /* Move the cell pointer to a new position. We set the `invalid' */ - /* flag to indicate that the cell isn't part of those we're interested */ - /* in during the render phase. This means that: */ - /* */ - /* . the new vertical position must be within min_ey..max_ey-1. */ - /* . the new horizontal position must be strictly less than max_ex */ - /* */ - /* Note that if a cell is to the left of the clipping region, it is */ - /* actually set to the (min_ex-1) horizontal position. */ - - /* All cells that are on the left of the clipping region go to the */ - /* min_ex - 1 horizontal position. */ - ey -= ras.min_ey; - - if (ex > ras.max_ex) ex = ras.max_ex; - - ex -= ras.min_ex; - if (ex < 0) ex = -1; - - /* are we moving to a different cell ? */ - if (ex != ras.ex || ey != ras.ey) { - /* record the current one if it is valid */ - if (!ras.invalid) gray_record_cell(RAS_VAR); - - ras.area = 0; - ras.cover = 0; - ras.ex = ex; - ras.ey = ey; - } - - ras.invalid = - ((unsigned)ey >= (unsigned)ras.count_ey || ex >= ras.count_ex); -} - -/*************************************************************************/ -/* */ -/* Start a new contour at a given cell. */ -/* */ -static void gray_start_cell(RAS_ARG_ TCoord ex, TCoord ey) -{ - if (ex > ras.max_ex) ex = (TCoord)(ras.max_ex); - - if (ex < ras.min_ex) ex = (TCoord)(ras.min_ex - 1); - - ras.area = 0; - ras.cover = 0; - ras.ex = ex - ras.min_ex; - ras.ey = ey - ras.min_ey; - ras.invalid = 0; - - gray_set_cell(RAS_VAR_ ex, ey); -} - -/*************************************************************************/ -/* */ -/* Render a straight line across multiple cells in any direction. */ -/* */ -static void gray_render_line(RAS_ARG_ TPos to_x, TPos to_y) -{ - TPos dx, dy, fx1, fy1, fx2, fy2; - TCoord ex1, ex2, ey1, ey2; - - ex1 = TRUNC(ras.x); - ex2 = TRUNC(to_x); - ey1 = TRUNC(ras.y); - ey2 = TRUNC(to_y); - - /* perform vertical clipping */ - if ((ey1 >= ras.max_ey && ey2 >= ras.max_ey) || - (ey1 < ras.min_ey && ey2 < ras.min_ey)) - goto End; - - dx = to_x - ras.x; - dy = to_y - ras.y; - - fx1 = ras.x - SUBPIXELS(ex1); - fy1 = ras.y - SUBPIXELS(ey1); - - if (ex1 == ex2 && ey1 == ey2) /* inside one cell */ - ; - else if (dy == 0) /* ex1 != ex2 */ /* any horizontal line */ - { - ex1 = ex2; - gray_set_cell(RAS_VAR_ ex1, ey1); - } else if (dx == 0) { - if (dy > 0) /* vertical line up */ - do { - fy2 = ONE_PIXEL; - ras.cover += (fy2 - fy1); - ras.area += (fy2 - fy1) * fx1 * 2; - fy1 = 0; - ey1++; - gray_set_cell(RAS_VAR_ ex1, ey1); - } while (ey1 != ey2); - else /* vertical line down */ - do { - fy2 = 0; - ras.cover += (fy2 - fy1); - ras.area += (fy2 - fy1) * fx1 * 2; - fy1 = ONE_PIXEL; - ey1--; - gray_set_cell(RAS_VAR_ ex1, ey1); - } while (ey1 != ey2); - } else /* any other line */ - { - TArea prod = dx * fy1 - dy * fx1; - SW_FT_UDIVPREP(dx); - SW_FT_UDIVPREP(dy); - - /* The fundamental value `prod' determines which side and the */ - /* exact coordinate where the line exits current cell. It is */ - /* also easily updated when moving from one cell to the next. */ - do { - if (prod <= 0 && prod - dx * ONE_PIXEL > 0) /* left */ - { - fx2 = 0; - fy2 = (TPos)SW_FT_UDIV(-prod, -dx); - prod -= dy * ONE_PIXEL; - ras.cover += (fy2 - fy1); - ras.area += (fy2 - fy1) * (fx1 + fx2); - fx1 = ONE_PIXEL; - fy1 = fy2; - ex1--; - } else if (prod - dx * ONE_PIXEL <= 0 && - prod - dx * ONE_PIXEL + dy * ONE_PIXEL > 0) /* up */ - { - prod -= dx * ONE_PIXEL; - fx2 = (TPos)SW_FT_UDIV(-prod, dy); - fy2 = ONE_PIXEL; - ras.cover += (fy2 - fy1); - ras.area += (fy2 - fy1) * (fx1 + fx2); - fx1 = fx2; - fy1 = 0; - ey1++; - } else if (prod - dx * ONE_PIXEL + dy * ONE_PIXEL <= 0 && - prod + dy * ONE_PIXEL >= 0) /* right */ - { - prod += dy * ONE_PIXEL; - fx2 = ONE_PIXEL; - fy2 = (TPos)SW_FT_UDIV(prod, dx); - ras.cover += (fy2 - fy1); - ras.area += (fy2 - fy1) * (fx1 + fx2); - fx1 = 0; - fy1 = fy2; - ex1++; - } else /* ( prod + dy * ONE_PIXEL < 0 && - prod > 0 ) down */ - { - fx2 = (TPos)SW_FT_UDIV(prod, -dy); - fy2 = 0; - prod += dx * ONE_PIXEL; - ras.cover += (fy2 - fy1); - ras.area += (fy2 - fy1) * (fx1 + fx2); - fx1 = fx2; - fy1 = ONE_PIXEL; - ey1--; - } - - gray_set_cell(RAS_VAR_ ex1, ey1); - } while (ex1 != ex2 || ey1 != ey2); - } - - fx2 = to_x - SUBPIXELS(ex2); - fy2 = to_y - SUBPIXELS(ey2); - - ras.cover += (fy2 - fy1); - ras.area += (fy2 - fy1) * (fx1 + fx2); - -End: - ras.x = to_x; - ras.y = to_y; -} - -static void gray_split_conic(SW_FT_Vector* base) -{ - TPos a, b; - - base[4].x = base[2].x; - a = base[0].x + base[1].x; - b = base[1].x + base[2].x; - base[3].x = b >> 1; - base[2].x = ( a + b ) >> 2; - base[1].x = a >> 1; - - base[4].y = base[2].y; - a = base[0].y + base[1].y; - b = base[1].y + base[2].y; - base[3].y = b >> 1; - base[2].y = ( a + b ) >> 2; - base[1].y = a >> 1; -} - -static void gray_render_conic(RAS_ARG_ const SW_FT_Vector* control, - const SW_FT_Vector* to) -{ - TPos dx, dy; - TPos min, max, y; - int top, level; - int* levels; - SW_FT_Vector* arc; - - levels = ras.lev_stack; - - arc = ras.bez_stack; - arc[0].x = UPSCALE(to->x); - arc[0].y = UPSCALE(to->y); - arc[1].x = UPSCALE(control->x); - arc[1].y = UPSCALE(control->y); - arc[2].x = ras.x; - arc[2].y = ras.y; - top = 0; - - dx = SW_FT_ABS(arc[2].x + arc[0].x - 2 * arc[1].x); - dy = SW_FT_ABS(arc[2].y + arc[0].y - 2 * arc[1].y); - if (dx < dy) dx = dy; - - if (dx < ONE_PIXEL / 4) goto Draw; - - /* short-cut the arc that crosses the current band */ - min = max = arc[0].y; - - y = arc[1].y; - if (y < min) min = y; - if (y > max) max = y; - - y = arc[2].y; - if (y < min) min = y; - if (y > max) max = y; - - if (TRUNC(min) >= ras.max_ey || TRUNC(max) < ras.min_ey) goto Draw; - - level = 0; - do { - dx >>= 2; - level++; - } while (dx > ONE_PIXEL / 4); - - levels[0] = level; - - do { - level = levels[top]; - if (level > 0) { - gray_split_conic(arc); - arc += 2; - top++; - levels[top] = levels[top - 1] = level - 1; - continue; - } - - Draw: - gray_render_line(RAS_VAR_ arc[0].x, arc[0].y); - top--; - arc -= 2; - - } while (top >= 0); -} - -static void gray_split_cubic(SW_FT_Vector* base) -{ - TPos a, b, c; - - - base[6].x = base[3].x; - a = base[0].x + base[1].x; - b = base[1].x + base[2].x; - c = base[2].x + base[3].x; - base[5].x = c >> 1; - c += b; - base[4].x = c >> 2; - base[1].x = a >> 1; - a += b; - base[2].x = a >> 2; - base[3].x = ( a + c ) >> 3; - - base[6].y = base[3].y; - a = base[0].y + base[1].y; - b = base[1].y + base[2].y; - c = base[2].y + base[3].y; - base[5].y = c >> 1; - c += b; - base[4].y = c >> 2; - base[1].y = a >> 1; - a += b; - base[2].y = a >> 2; - base[3].y = ( a + c ) >> 3; -} - - -static void -gray_render_cubic(RAS_ARG_ const SW_FT_Vector* control1, - const SW_FT_Vector* control2, - const SW_FT_Vector* to) -{ - SW_FT_Vector* arc = ras.bez_stack; - - arc[0].x = UPSCALE( to->x ); - arc[0].y = UPSCALE( to->y ); - arc[1].x = UPSCALE( control2->x ); - arc[1].y = UPSCALE( control2->y ); - arc[2].x = UPSCALE( control1->x ); - arc[2].y = UPSCALE( control1->y ); - arc[3].x = ras.x; - arc[3].y = ras.y; - - /* short-cut the arc that crosses the current band */ - if ( ( TRUNC( arc[0].y ) >= ras.max_ey && - TRUNC( arc[1].y ) >= ras.max_ey && - TRUNC( arc[2].y ) >= ras.max_ey && - TRUNC( arc[3].y ) >= ras.max_ey ) || - ( TRUNC( arc[0].y ) < ras.min_ey && - TRUNC( arc[1].y ) < ras.min_ey && - TRUNC( arc[2].y ) < ras.min_ey && - TRUNC( arc[3].y ) < ras.min_ey ) ) - { - ras.x = arc[0].x; - ras.y = arc[0].y; - return; - } - - for (;;) - { - /* with each split, control points quickly converge towards */ - /* chord trisection points and the vanishing distances below */ - /* indicate when the segment is flat enough to draw */ - if ( SW_FT_ABS( 2 * arc[0].x - 3 * arc[1].x + arc[3].x ) > ONE_PIXEL / 2 || - SW_FT_ABS( 2 * arc[0].y - 3 * arc[1].y + arc[3].y ) > ONE_PIXEL / 2 || - SW_FT_ABS( arc[0].x - 3 * arc[2].x + 2 * arc[3].x ) > ONE_PIXEL / 2 || - SW_FT_ABS( arc[0].y - 3 * arc[2].y + 2 * arc[3].y ) > ONE_PIXEL / 2 ) - goto Split; - - gray_render_line( RAS_VAR_ arc[0].x, arc[0].y ); - - if ( arc == ras.bez_stack ) - return; - - arc -= 3; - continue; - - Split: - gray_split_cubic( arc ); - arc += 3; - } -} - -static int gray_move_to(const SW_FT_Vector* to, gray_PWorker worker) -{ - TPos x, y; - - /* record current cell, if any */ - if (!ras.invalid) gray_record_cell(RAS_VAR); - - /* start to a new position */ - x = UPSCALE(to->x); - y = UPSCALE(to->y); - - gray_start_cell(RAS_VAR_ TRUNC(x), TRUNC(y)); - - worker->x = x; - worker->y = y; - return 0; -} - -static int gray_line_to(const SW_FT_Vector* to, gray_PWorker worker) -{ - gray_render_line(RAS_VAR_ UPSCALE(to->x), UPSCALE(to->y)); - return 0; -} - -static int gray_conic_to(const SW_FT_Vector* control, const SW_FT_Vector* to, - gray_PWorker worker) -{ - gray_render_conic(RAS_VAR_ control, to); - return 0; -} - -static int gray_cubic_to(const SW_FT_Vector* control1, - const SW_FT_Vector* control2, const SW_FT_Vector* to, - gray_PWorker worker) -{ - gray_render_cubic(RAS_VAR_ control1, control2, to); - return 0; -} - -static void gray_hline(RAS_ARG_ TCoord x, TCoord y, TPos area, TCoord acount) -{ - int coverage; - - /* compute the coverage line's coverage, depending on the */ - /* outline fill rule */ - /* */ - /* the coverage percentage is area/(PIXEL_BITS*PIXEL_BITS*2) */ - /* */ - coverage = (int)(area >> (PIXEL_BITS * 2 + 1 - 8)); - /* use range 0..256 */ - if (coverage < 0) coverage = -coverage; - - if (ras.outline.flags & SW_FT_OUTLINE_EVEN_ODD_FILL) { - coverage &= 511; - - if (coverage > 256) - coverage = 512 - coverage; - else if (coverage == 256) - coverage = 255; - } else { - /* normal non-zero winding rule */ - if (coverage >= 256) coverage = 255; - } - - y += (TCoord)ras.min_ey; - x += (TCoord)ras.min_ex; - - /* SW_FT_Span.x is a 16-bit short, so limit our coordinates appropriately */ - if (x >= 32767) x = 32767; - - /* SW_FT_Span.y is an integer, so limit our coordinates appropriately */ - if (y >= SW_FT_INT_MAX) y = SW_FT_INT_MAX; - - if (coverage) { - SW_FT_Span* span; - int count; - - // update bounding box. - if (x < ras.bound_left) ras.bound_left = x; - if (y < ras.bound_top) ras.bound_top = y; - if (y > ras.bound_bottom) ras.bound_bottom = y; - if (x + acount > ras.bound_right) ras.bound_right = x + acount; - - /* see whether we can add this span to the current list */ - count = ras.num_gray_spans; - span = ras.gray_spans + count - 1; - if (count > 0 && span->y == y && (int)span->x + span->len == (int)x && - span->coverage == coverage) { - span->len = (unsigned short)(span->len + acount); - return; - } - - if (count >= SW_FT_MAX_GRAY_SPANS) { - if (ras.render_span && count > 0) - ras.render_span(count, ras.gray_spans, ras.render_span_data); - -#ifdef DEBUG_GRAYS - - if (1) { - int n; - - fprintf(stderr, "count = %3d ", count); - span = ras.gray_spans; - for (n = 0; n < count; n++, span++) - fprintf(stderr, "[%d , %d..%d] : %d ", span->y, span->x, - span->x + span->len - 1, span->coverage); - fprintf(stderr, "\n"); - } - -#endif /* DEBUG_GRAYS */ - - ras.num_gray_spans = 0; - - span = ras.gray_spans; - } else - span++; - - /* add a gray span to the current list */ - span->x = (short)x; - span->y = (short)y; - span->len = (unsigned short)acount; - span->coverage = (unsigned char)coverage; - - ras.num_gray_spans++; - } -} - -static void gray_sweep(RAS_ARG) -{ - int yindex; - - if (ras.num_cells == 0) return; - - ras.num_gray_spans = 0; - - for (yindex = 0; yindex < ras.ycount; yindex++) { - PCell cell = ras.ycells[yindex]; - TCoord cover = 0; - TCoord x = 0; - - for (; cell != NULL; cell = cell->next) { - TPos area; - - if (cell->x > x && cover != 0) - gray_hline(RAS_VAR_ x, yindex, cover * (ONE_PIXEL * 2), - cell->x - x); - - cover += cell->cover; - area = cover * (ONE_PIXEL * 2) - cell->area; - - if (area != 0 && cell->x >= 0) - gray_hline(RAS_VAR_ cell->x, yindex, area, 1); - - x = cell->x + 1; - } - - if (cover != 0) - gray_hline(RAS_VAR_ x, yindex, cover * (ONE_PIXEL * 2), - ras.count_ex - x); - } - - if (ras.render_span && ras.num_gray_spans > 0) - ras.render_span(ras.num_gray_spans, ras.gray_spans, - ras.render_span_data); -} - -/*************************************************************************/ -/* */ -/* The following function should only compile in stand-alone mode, */ -/* i.e., when building this component without the rest of FreeType. */ -/* */ -/*************************************************************************/ - -/*************************************************************************/ -/* */ -/* */ -/* SW_FT_Outline_Decompose */ -/* */ -/* */ -/* Walk over an outline's structure to decompose it into individual */ -/* segments and Bézier arcs. This function is also able to emit */ -/* `move to' and `close to' operations to indicate the start and end */ -/* of new contours in the outline. */ -/* */ -/* */ -/* outline :: A pointer to the source target. */ -/* */ -/* func_interface :: A table of `emitters', i.e., function pointers */ -/* called during decomposition to indicate path */ -/* operations. */ -/* */ -/* */ -/* user :: A typeless pointer which is passed to each */ -/* emitter during the decomposition. It can be */ -/* used to store the state during the */ -/* decomposition. */ -/* */ -/* */ -/* Error code. 0 means success. */ -/* */ -static int SW_FT_Outline_Decompose(const SW_FT_Outline* outline, - const SW_FT_Outline_Funcs* func_interface, - void* user) -{ -#undef SCALED -#define SCALED(x) (((x) << shift) - delta) - - SW_FT_Vector v_last; - SW_FT_Vector v_control; - SW_FT_Vector v_start; - - SW_FT_Vector* point; - SW_FT_Vector* limit; - char* tags; - - int error; - - int n; /* index of contour in outline */ - int first; /* index of first point in contour */ - char tag; /* current point's state */ - - int shift; - TPos delta; - - if (!outline || !func_interface) return SW_FT_THROW(Invalid_Argument); - - shift = func_interface->shift; - delta = func_interface->delta; - first = 0; - - for (n = 0; n < outline->n_contours; n++) { - int last; /* index of last point in contour */ - - last = outline->contours[n]; - if (last < 0) goto Invalid_Outline; - limit = outline->points + last; - - v_start = outline->points[first]; - v_start.x = SCALED(v_start.x); - v_start.y = SCALED(v_start.y); - - v_last = outline->points[last]; - v_last.x = SCALED(v_last.x); - v_last.y = SCALED(v_last.y); - - v_control = v_start; - - point = outline->points + first; - tags = outline->tags + first; - tag = SW_FT_CURVE_TAG(tags[0]); - - /* A contour cannot start with a cubic control point! */ - if (tag == SW_FT_CURVE_TAG_CUBIC) goto Invalid_Outline; - - /* check first point to determine origin */ - if (tag == SW_FT_CURVE_TAG_CONIC) { - /* first point is conic control. Yes, this happens. */ - if (SW_FT_CURVE_TAG(outline->tags[last]) == SW_FT_CURVE_TAG_ON) { - /* start at last point if it is on the curve */ - v_start = v_last; - limit--; - } else { - /* if both first and last points are conic, */ - /* start at their middle and record its position */ - /* for closure */ - v_start.x = (v_start.x + v_last.x) / 2; - v_start.y = (v_start.y + v_last.y) / 2; - } - point--; - tags--; - } - - error = func_interface->move_to(&v_start, user); - if (error) goto Exit; - - while (point < limit) { - point++; - tags++; - - tag = SW_FT_CURVE_TAG(tags[0]); - switch (tag) { - case SW_FT_CURVE_TAG_ON: /* emit a single line_to */ - { - SW_FT_Vector vec; - - vec.x = SCALED(point->x); - vec.y = SCALED(point->y); - - error = func_interface->line_to(&vec, user); - if (error) goto Exit; - continue; - } - - case SW_FT_CURVE_TAG_CONIC: /* consume conic arcs */ - v_control.x = SCALED(point->x); - v_control.y = SCALED(point->y); - - Do_Conic: - if (point < limit) { - SW_FT_Vector vec; - SW_FT_Vector v_middle; - - point++; - tags++; - tag = SW_FT_CURVE_TAG(tags[0]); - - vec.x = SCALED(point->x); - vec.y = SCALED(point->y); - - if (tag == SW_FT_CURVE_TAG_ON) { - error = - func_interface->conic_to(&v_control, &vec, user); - if (error) goto Exit; - continue; - } - - if (tag != SW_FT_CURVE_TAG_CONIC) goto Invalid_Outline; - - v_middle.x = (v_control.x + vec.x) / 2; - v_middle.y = (v_control.y + vec.y) / 2; - - error = - func_interface->conic_to(&v_control, &v_middle, user); - if (error) goto Exit; - - v_control = vec; - goto Do_Conic; - } - - error = func_interface->conic_to(&v_control, &v_start, user); - goto Close; - - default: /* SW_FT_CURVE_TAG_CUBIC */ - { - SW_FT_Vector vec1, vec2; - - if (point + 1 > limit || - SW_FT_CURVE_TAG(tags[1]) != SW_FT_CURVE_TAG_CUBIC) - goto Invalid_Outline; - - point += 2; - tags += 2; - - vec1.x = SCALED(point[-2].x); - vec1.y = SCALED(point[-2].y); - - vec2.x = SCALED(point[-1].x); - vec2.y = SCALED(point[-1].y); - - if (point <= limit) { - SW_FT_Vector vec; - - vec.x = SCALED(point->x); - vec.y = SCALED(point->y); - - error = func_interface->cubic_to(&vec1, &vec2, &vec, user); - if (error) goto Exit; - continue; - } - - error = func_interface->cubic_to(&vec1, &vec2, &v_start, user); - goto Close; - } - } - } - - /* close the contour with a line segment */ - error = func_interface->line_to(&v_start, user); - - Close: - if (error) goto Exit; - - first = last + 1; - } - - return 0; - -Exit: - return error; - -Invalid_Outline: - return SW_FT_THROW(Invalid_Outline); -} - -typedef struct gray_TBand_ { - TPos min, max; - -} gray_TBand; - -SW_FT_DEFINE_OUTLINE_FUNCS(func_interface, - (SW_FT_Outline_MoveTo_Func)gray_move_to, - (SW_FT_Outline_LineTo_Func)gray_line_to, - (SW_FT_Outline_ConicTo_Func)gray_conic_to, - (SW_FT_Outline_CubicTo_Func)gray_cubic_to, 0, 0) - -static int gray_convert_glyph_inner(RAS_ARG) -{ - int error; - - if (ft_setjmp(ras.jump_buffer) == 0) { - error = SW_FT_Outline_Decompose(&ras.outline, &func_interface, &ras); - if (!ras.invalid) gray_record_cell(RAS_VAR); - } else - error = SW_FT_THROW(Memory_Overflow); - - return error; -} - -static int gray_convert_glyph(RAS_ARG) -{ - gray_TBand bands[40]; - gray_TBand* band; - int n, num_bands; - TPos min, max, max_y; - SW_FT_BBox* clip; - - /* Set up state in the raster object */ - gray_compute_cbox(RAS_VAR); - - /* clip to target bitmap, exit if nothing to do */ - clip = &ras.clip_box; - - if (ras.max_ex <= clip->xMin || ras.min_ex >= clip->xMax || - ras.max_ey <= clip->yMin || ras.min_ey >= clip->yMax) - return 0; - - if (ras.min_ex < clip->xMin) ras.min_ex = clip->xMin; - if (ras.min_ey < clip->yMin) ras.min_ey = clip->yMin; - - if (ras.max_ex > clip->xMax) ras.max_ex = clip->xMax; - if (ras.max_ey > clip->yMax) ras.max_ey = clip->yMax; - - ras.count_ex = ras.max_ex - ras.min_ex; - ras.count_ey = ras.max_ey - ras.min_ey; - - /* set up vertical bands */ - num_bands = (int)((ras.max_ey - ras.min_ey) / ras.band_size); - if (num_bands == 0) num_bands = 1; - if (num_bands >= 39) num_bands = 39; - - ras.band_shoot = 0; - - min = ras.min_ey; - max_y = ras.max_ey; - - for (n = 0; n < num_bands; n++, min = max) { - max = min + ras.band_size; - if (n == num_bands - 1 || max > max_y) max = max_y; - - bands[0].min = min; - bands[0].max = max; - band = bands; - - while (band >= bands) { - TPos bottom, top, middle; - int error; - - { - PCell cells_max; - int yindex; - long cell_start, cell_end, cell_mod; - - ras.ycells = (PCell*)ras.buffer; - ras.ycount = band->max - band->min; - - cell_start = sizeof(PCell) * ras.ycount; - cell_mod = cell_start % sizeof(TCell); - if (cell_mod > 0) cell_start += sizeof(TCell) - cell_mod; - - cell_end = ras.buffer_size; - cell_end -= cell_end % sizeof(TCell); - - cells_max = (PCell)((char*)ras.buffer + cell_end); - ras.cells = (PCell)((char*)ras.buffer + cell_start); - if (ras.cells >= cells_max) goto ReduceBands; - - ras.max_cells = cells_max - ras.cells; - if (ras.max_cells < 2) goto ReduceBands; - - for (yindex = 0; yindex < ras.ycount; yindex++) - ras.ycells[yindex] = NULL; - } - - ras.num_cells = 0; - ras.invalid = 1; - ras.min_ey = band->min; - ras.max_ey = band->max; - ras.count_ey = band->max - band->min; - - error = gray_convert_glyph_inner(RAS_VAR); - - if (!error) { - gray_sweep(RAS_VAR); - band--; - continue; - } else if (error != ErrRaster_Memory_Overflow) - return 1; - - ReduceBands: - /* render pool overflow; we will reduce the render band by half */ - bottom = band->min; - top = band->max; - middle = bottom + ((top - bottom) >> 1); - - /* This is too complex for a single scanline; there must */ - /* be some problems. */ - if (middle == bottom) { - return 1; - } - - if (bottom - top >= ras.band_size) ras.band_shoot++; - - band[1].min = bottom; - band[1].max = middle; - band[0].min = middle; - band[0].max = top; - band++; - } - } - - if (ras.band_shoot > 8 && ras.band_size > 16) - ras.band_size = ras.band_size / 2; - - return 0; -} - -static int gray_raster_render(gray_PRaster raster, - const SW_FT_Raster_Params* params) -{ - SW_FT_UNUSED(raster); - const SW_FT_Outline* outline = (const SW_FT_Outline*)params->source; - - gray_TWorker worker[1]; - - TCell buffer[SW_FT_RENDER_POOL_SIZE / sizeof(TCell)]; - long buffer_size = sizeof(buffer); - int band_size = (int)(buffer_size / (long)(sizeof(TCell) * 8)); - - if (!outline) return SW_FT_THROW(Invalid_Outline); - - /* return immediately if the outline is empty */ - if (outline->n_points == 0 || outline->n_contours <= 0) return 0; - - if (!outline->contours || !outline->points) - return SW_FT_THROW(Invalid_Outline); - - if (outline->n_points != outline->contours[outline->n_contours - 1] + 1) - return SW_FT_THROW(Invalid_Outline); - - /* this version does not support monochrome rendering */ - if (!(params->flags & SW_FT_RASTER_FLAG_AA)) - return SW_FT_THROW(Invalid_Mode); - - if (params->flags & SW_FT_RASTER_FLAG_CLIP) - ras.clip_box = params->clip_box; - else { - ras.clip_box.xMin = -32768L; - ras.clip_box.yMin = -32768L; - ras.clip_box.xMax = 32767L; - ras.clip_box.yMax = 32767L; - } - - gray_init_cells(RAS_VAR_ buffer, buffer_size); - - ras.outline = *outline; - ras.num_cells = 0; - ras.invalid = 1; - ras.band_size = band_size; - ras.num_gray_spans = 0; - - ras.render_span = (SW_FT_Raster_Span_Func)params->gray_spans; - ras.render_span_data = params->user; - - gray_convert_glyph(RAS_VAR); - params->bbox_cb(ras.bound_left, ras.bound_top, - ras.bound_right - ras.bound_left, - ras.bound_bottom - ras.bound_top + 1, params->user); - return 1; -} - -/**** RASTER OBJECT CREATION: In stand-alone mode, we simply use *****/ -/**** a static object. *****/ - -static int gray_raster_new(SW_FT_Raster* araster) -{ - static gray_TRaster the_raster; - - *araster = (SW_FT_Raster)&the_raster; - SW_FT_MEM_ZERO(&the_raster, sizeof(the_raster)); - - return 0; -} - -static void gray_raster_done(SW_FT_Raster raster) -{ - /* nothing */ - SW_FT_UNUSED(raster); -} - -static void gray_raster_reset(SW_FT_Raster raster, char* pool_base, - long pool_size) -{ - SW_FT_UNUSED(raster); - SW_FT_UNUSED(pool_base); - SW_FT_UNUSED(pool_size); -} - -SW_FT_DEFINE_RASTER_FUNCS(sw_ft_grays_raster, - - (SW_FT_Raster_New_Func)gray_raster_new, - (SW_FT_Raster_Reset_Func)gray_raster_reset, - (SW_FT_Raster_Render_Func)gray_raster_render, - (SW_FT_Raster_Done_Func)gray_raster_done) - -/* END */ diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_raster.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_raster.h deleted file mode 100644 index 7897289fc..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_raster.h +++ /dev/null @@ -1,607 +0,0 @@ -#ifndef V_FT_IMG_H -#define V_FT_IMG_H -/***************************************************************************/ -/* */ -/* ftimage.h */ -/* */ -/* FreeType glyph image formats and default raster interface */ -/* (specification). */ -/* */ -/* Copyright 1996-2010, 2013 by */ -/* David Turner, Robert Wilhelm, and Werner Lemberg. */ -/* */ -/* This file is part of the FreeType project, and may only be used, */ -/* modified, and distributed under the terms of the FreeType project */ -/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ -/* this file you indicate that you have read the license and */ -/* understand and accept it fully. */ -/* */ -/***************************************************************************/ - - /*************************************************************************/ - /* */ - /* Note: A `raster' is simply a scan-line converter, used to render */ - /* SW_FT_Outlines into SW_FT_Bitmaps. */ - /* */ - /*************************************************************************/ - -#include "v_ft_types.h" - - /*************************************************************************/ - /* */ - /* */ - /* FT_BBox */ - /* */ - /* */ - /* A structure used to hold an outline's bounding box, i.e., the */ - /* coordinates of its extrema in the horizontal and vertical */ - /* directions. */ - /* */ - /* */ - /* xMin :: The horizontal minimum (left-most). */ - /* */ - /* yMin :: The vertical minimum (bottom-most). */ - /* */ - /* xMax :: The horizontal maximum (right-most). */ - /* */ - /* yMax :: The vertical maximum (top-most). */ - /* */ - /* */ - /* The bounding box is specified with the coordinates of the lower */ - /* left and the upper right corner. In PostScript, those values are */ - /* often called (llx,lly) and (urx,ury), respectively. */ - /* */ - /* If `yMin' is negative, this value gives the glyph's descender. */ - /* Otherwise, the glyph doesn't descend below the baseline. */ - /* Similarly, if `ymax' is positive, this value gives the glyph's */ - /* ascender. */ - /* */ - /* `xMin' gives the horizontal distance from the glyph's origin to */ - /* the left edge of the glyph's bounding box. If `xMin' is negative, */ - /* the glyph extends to the left of the origin. */ - /* */ - typedef struct SW_FT_BBox_ - { - SW_FT_Pos xMin, yMin; - SW_FT_Pos xMax, yMax; - - } SW_FT_BBox; - -/*************************************************************************/ -/* */ -/* */ -/* SW_FT_Outline */ -/* */ -/* */ -/* This structure is used to describe an outline to the scan-line */ -/* converter. */ -/* */ -/* */ -/* n_contours :: The number of contours in the outline. */ -/* */ -/* n_points :: The number of points in the outline. */ -/* */ -/* points :: A pointer to an array of `n_points' @SW_FT_Vector */ -/* elements, giving the outline's point coordinates. */ -/* */ -/* tags :: A pointer to an array of `n_points' chars, giving */ -/* each outline point's type. */ -/* */ -/* If bit~0 is unset, the point is `off' the curve, */ -/* i.e., a Bézier control point, while it is `on' if */ -/* set. */ -/* */ -/* Bit~1 is meaningful for `off' points only. If set, */ -/* it indicates a third-order Bézier arc control point; */ -/* and a second-order control point if unset. */ -/* */ -/* If bit~2 is set, bits 5-7 contain the drop-out mode */ -/* (as defined in the OpenType specification; the value */ -/* is the same as the argument to the SCANMODE */ -/* instruction). */ -/* */ -/* Bits 3 and~4 are reserved for internal purposes. */ -/* */ -/* contours :: An array of `n_contours' shorts, giving the end */ -/* point of each contour within the outline. For */ -/* example, the first contour is defined by the points */ -/* `0' to `contours[0]', the second one is defined by */ -/* the points `contours[0]+1' to `contours[1]', etc. */ -/* */ -/* flags :: A set of bit flags used to characterize the outline */ -/* and give hints to the scan-converter and hinter on */ -/* how to convert/grid-fit it. See @SW_FT_OUTLINE_FLAGS.*/ -/* */ -typedef struct SW_FT_Outline_ -{ - short n_contours; /* number of contours in glyph */ - short n_points; /* number of points in the glyph */ - - SW_FT_Vector* points; /* the outline's points */ - char* tags; /* the points flags */ - short* contours; /* the contour end points */ - char* contours_flag; /* the contour open flags */ - - int flags; /* outline masks */ - -} SW_FT_Outline; - - - /*************************************************************************/ - /* */ - /* */ - /* SW_FT_OUTLINE_FLAGS */ - /* */ - /* */ - /* A list of bit-field constants use for the flags in an outline's */ - /* `flags' field. */ - /* */ - /* */ - /* SW_FT_OUTLINE_NONE :: */ - /* Value~0 is reserved. */ - /* */ - /* SW_FT_OUTLINE_OWNER :: */ - /* If set, this flag indicates that the outline's field arrays */ - /* (i.e., `points', `flags', and `contours') are `owned' by the */ - /* outline object, and should thus be freed when it is destroyed. */ - /* */ - /* SW_FT_OUTLINE_EVEN_ODD_FILL :: */ - /* By default, outlines are filled using the non-zero winding rule. */ - /* If set to 1, the outline will be filled using the even-odd fill */ - /* rule (only works with the smooth rasterizer). */ - /* */ - /* SW_FT_OUTLINE_REVERSE_FILL :: */ - /* By default, outside contours of an outline are oriented in */ - /* clock-wise direction, as defined in the TrueType specification. */ - /* This flag is set if the outline uses the opposite direction */ - /* (typically for Type~1 fonts). This flag is ignored by the scan */ - /* converter. */ - /* */ - /* */ - /* */ - /* There exists a second mechanism to pass the drop-out mode to the */ - /* B/W rasterizer; see the `tags' field in @SW_FT_Outline. */ - /* */ - /* Please refer to the description of the `SCANTYPE' instruction in */ - /* the OpenType specification (in file `ttinst1.doc') how simple */ - /* drop-outs, smart drop-outs, and stubs are defined. */ - /* */ -#define SW_FT_OUTLINE_NONE 0x0 -#define SW_FT_OUTLINE_OWNER 0x1 -#define SW_FT_OUTLINE_EVEN_ODD_FILL 0x2 -#define SW_FT_OUTLINE_REVERSE_FILL 0x4 - - /* */ - -#define SW_FT_CURVE_TAG( flag ) ( flag & 3 ) - -#define SW_FT_CURVE_TAG_ON 1 -#define SW_FT_CURVE_TAG_CONIC 0 -#define SW_FT_CURVE_TAG_CUBIC 2 - - -#define SW_FT_Curve_Tag_On SW_FT_CURVE_TAG_ON -#define SW_FT_Curve_Tag_Conic SW_FT_CURVE_TAG_CONIC -#define SW_FT_Curve_Tag_Cubic SW_FT_CURVE_TAG_CUBIC - - /*************************************************************************/ - /* */ - /* A raster is a scan converter, in charge of rendering an outline into */ - /* a a bitmap. This section contains the public API for rasters. */ - /* */ - /* Note that in FreeType 2, all rasters are now encapsulated within */ - /* specific modules called `renderers'. See `ftrender.h' for more */ - /* details on renderers. */ - /* */ - /*************************************************************************/ - - - /*************************************************************************/ - /* */ - /* */ - /* SW_FT_Raster */ - /* */ - /* */ - /* A handle (pointer) to a raster object. Each object can be used */ - /* independently to convert an outline into a bitmap or pixmap. */ - /* */ - typedef struct SW_FT_RasterRec_* SW_FT_Raster; - - - /*************************************************************************/ - /* */ - /* */ - /* SW_FT_Span */ - /* */ - /* */ - /* A structure used to model a single span of gray (or black) pixels */ - /* when rendering a monochrome or anti-aliased bitmap. */ - /* */ - /* */ - /* x :: The span's horizontal start position. */ - /* */ - /* len :: The span's length in pixels. */ - /* */ - /* coverage :: The span color/coverage, ranging from 0 (background) */ - /* to 255 (foreground). Only used for anti-aliased */ - /* rendering. */ - /* */ - /* */ - /* This structure is used by the span drawing callback type named */ - /* @SW_FT_SpanFunc that takes the y~coordinate of the span as a */ - /* parameter. */ - /* */ - /* The coverage value is always between 0 and 255. If you want less */ - /* gray values, the callback function has to reduce them. */ - /* */ - typedef struct SW_FT_Span_ - { - short x; - short y; - unsigned short len; - unsigned char coverage; - - } SW_FT_Span; - - - /*************************************************************************/ - /* */ - /* */ - /* SW_FT_SpanFunc */ - /* */ - /* */ - /* A function used as a call-back by the anti-aliased renderer in */ - /* order to let client applications draw themselves the gray pixel */ - /* spans on each scan line. */ - /* */ - /* */ - /* y :: The scanline's y~coordinate. */ - /* */ - /* count :: The number of spans to draw on this scanline. */ - /* */ - /* spans :: A table of `count' spans to draw on the scanline. */ - /* */ - /* user :: User-supplied data that is passed to the callback. */ - /* */ - /* */ - /* This callback allows client applications to directly render the */ - /* gray spans of the anti-aliased bitmap to any kind of surfaces. */ - /* */ - /* This can be used to write anti-aliased outlines directly to a */ - /* given background bitmap, and even perform translucency. */ - /* */ - /* Note that the `count' field cannot be greater than a fixed value */ - /* defined by the `SW_FT_MAX_GRAY_SPANS' configuration macro in */ - /* `ftoption.h'. By default, this value is set to~32, which means */ - /* that if there are more than 32~spans on a given scanline, the */ - /* callback is called several times with the same `y' parameter in */ - /* order to draw all callbacks. */ - /* */ - /* Otherwise, the callback is only called once per scan-line, and */ - /* only for those scanlines that do have `gray' pixels on them. */ - /* */ - typedef void - (*SW_FT_SpanFunc)( int count, - const SW_FT_Span* spans, - void* user ); - - typedef void - (*SW_FT_BboxFunc)( int x, int y, int w, int h, - void* user); - -#define SW_FT_Raster_Span_Func SW_FT_SpanFunc - - - - /*************************************************************************/ - /* */ - /* */ - /* SW_FT_RASTER_FLAG_XXX */ - /* */ - /* */ - /* A list of bit flag constants as used in the `flags' field of a */ - /* @SW_FT_Raster_Params structure. */ - /* */ - /* */ - /* SW_FT_RASTER_FLAG_DEFAULT :: This value is 0. */ - /* */ - /* SW_FT_RASTER_FLAG_AA :: This flag is set to indicate that an */ - /* anti-aliased glyph image should be */ - /* generated. Otherwise, it will be */ - /* monochrome (1-bit). */ - /* */ - /* SW_FT_RASTER_FLAG_DIRECT :: This flag is set to indicate direct */ - /* rendering. In this mode, client */ - /* applications must provide their own span */ - /* callback. This lets them directly */ - /* draw or compose over an existing bitmap. */ - /* If this bit is not set, the target */ - /* pixmap's buffer _must_ be zeroed before */ - /* rendering. */ - /* */ - /* Note that for now, direct rendering is */ - /* only possible with anti-aliased glyphs. */ - /* */ - /* SW_FT_RASTER_FLAG_CLIP :: This flag is only used in direct */ - /* rendering mode. If set, the output will */ - /* be clipped to a box specified in the */ - /* `clip_box' field of the */ - /* @SW_FT_Raster_Params structure. */ - /* */ - /* Note that by default, the glyph bitmap */ - /* is clipped to the target pixmap, except */ - /* in direct rendering mode where all spans */ - /* are generated if no clipping box is set. */ - /* */ -#define SW_FT_RASTER_FLAG_DEFAULT 0x0 -#define SW_FT_RASTER_FLAG_AA 0x1 -#define SW_FT_RASTER_FLAG_DIRECT 0x2 -#define SW_FT_RASTER_FLAG_CLIP 0x4 - - - /*************************************************************************/ - /* */ - /* */ - /* SW_FT_Raster_Params */ - /* */ - /* */ - /* A structure to hold the arguments used by a raster's render */ - /* function. */ - /* */ - /* */ - /* target :: The target bitmap. */ - /* */ - /* source :: A pointer to the source glyph image (e.g., an */ - /* @SW_FT_Outline). */ - /* */ - /* flags :: The rendering flags. */ - /* */ - /* gray_spans :: The gray span drawing callback. */ - /* */ - /* black_spans :: The black span drawing callback. UNIMPLEMENTED! */ - /* */ - /* bit_test :: The bit test callback. UNIMPLEMENTED! */ - /* */ - /* bit_set :: The bit set callback. UNIMPLEMENTED! */ - /* */ - /* user :: User-supplied data that is passed to each drawing */ - /* callback. */ - /* */ - /* clip_box :: An optional clipping box. It is only used in */ - /* direct rendering mode. Note that coordinates here */ - /* should be expressed in _integer_ pixels (and not in */ - /* 26.6 fixed-point units). */ - /* */ - /* */ - /* An anti-aliased glyph bitmap is drawn if the @SW_FT_RASTER_FLAG_AA */ - /* bit flag is set in the `flags' field, otherwise a monochrome */ - /* bitmap is generated. */ - /* */ - /* If the @SW_FT_RASTER_FLAG_DIRECT bit flag is set in `flags', the */ - /* raster will call the `gray_spans' callback to draw gray pixel */ - /* spans, in the case of an aa glyph bitmap, it will call */ - /* `black_spans', and `bit_test' and `bit_set' in the case of a */ - /* monochrome bitmap. This allows direct composition over a */ - /* pre-existing bitmap through user-provided callbacks to perform the */ - /* span drawing/composition. */ - /* */ - /* Note that the `bit_test' and `bit_set' callbacks are required when */ - /* rendering a monochrome bitmap, as they are crucial to implement */ - /* correct drop-out control as defined in the TrueType specification. */ - /* */ - typedef struct SW_FT_Raster_Params_ - { - const void* source; - int flags; - SW_FT_SpanFunc gray_spans; - SW_FT_BboxFunc bbox_cb; - void* user; - SW_FT_BBox clip_box; - - } SW_FT_Raster_Params; - - -/*************************************************************************/ -/* */ -/* */ -/* SW_FT_Outline_Check */ -/* */ -/* */ -/* Check the contents of an outline descriptor. */ -/* */ -/* */ -/* outline :: A handle to a source outline. */ -/* */ -/* */ -/* FreeType error code. 0~means success. */ -/* */ -SW_FT_Error -SW_FT_Outline_Check( SW_FT_Outline* outline ); - - -/*************************************************************************/ -/* */ -/* */ -/* SW_FT_Outline_Get_CBox */ -/* */ -/* */ -/* Return an outline's `control box'. The control box encloses all */ -/* the outline's points, including Bézier control points. Though it */ -/* coincides with the exact bounding box for most glyphs, it can be */ -/* slightly larger in some situations (like when rotating an outline */ -/* that contains Bézier outside arcs). */ -/* */ -/* Computing the control box is very fast, while getting the bounding */ -/* box can take much more time as it needs to walk over all segments */ -/* and arcs in the outline. To get the latter, you can use the */ -/* `ftbbox' component, which is dedicated to this single task. */ -/* */ -/* */ -/* outline :: A pointer to the source outline descriptor. */ -/* */ -/* */ -/* acbox :: The outline's control box. */ -/* */ -/* */ -/* See @SW_FT_Glyph_Get_CBox for a discussion of tricky fonts. */ -/* */ -void -SW_FT_Outline_Get_CBox( const SW_FT_Outline* outline, - SW_FT_BBox *acbox ); - - - /*************************************************************************/ - /* */ - /* */ - /* SW_FT_Raster_NewFunc */ - /* */ - /* */ - /* A function used to create a new raster object. */ - /* */ - /* */ - /* memory :: A handle to the memory allocator. */ - /* */ - /* */ - /* raster :: A handle to the new raster object. */ - /* */ - /* */ - /* Error code. 0~means success. */ - /* */ - /* */ - /* The `memory' parameter is a typeless pointer in order to avoid */ - /* un-wanted dependencies on the rest of the FreeType code. In */ - /* practice, it is an @SW_FT_Memory object, i.e., a handle to the */ - /* standard FreeType memory allocator. However, this field can be */ - /* completely ignored by a given raster implementation. */ - /* */ - typedef int - (*SW_FT_Raster_NewFunc)( SW_FT_Raster* raster ); - -#define SW_FT_Raster_New_Func SW_FT_Raster_NewFunc - - - /*************************************************************************/ - /* */ - /* */ - /* SW_FT_Raster_DoneFunc */ - /* */ - /* */ - /* A function used to destroy a given raster object. */ - /* */ - /* */ - /* raster :: A handle to the raster object. */ - /* */ - typedef void - (*SW_FT_Raster_DoneFunc)( SW_FT_Raster raster ); - -#define SW_FT_Raster_Done_Func SW_FT_Raster_DoneFunc - - - /*************************************************************************/ - /* */ - /* */ - /* SW_FT_Raster_ResetFunc */ - /* */ - /* */ - /* FreeType provides an area of memory called the `render pool', */ - /* available to all registered rasters. This pool can be freely used */ - /* during a given scan-conversion but is shared by all rasters. Its */ - /* content is thus transient. */ - /* */ - /* This function is called each time the render pool changes, or just */ - /* after a new raster object is created. */ - /* */ - /* */ - /* raster :: A handle to the new raster object. */ - /* */ - /* pool_base :: The address in memory of the render pool. */ - /* */ - /* pool_size :: The size in bytes of the render pool. */ - /* */ - /* */ - /* Rasters can ignore the render pool and rely on dynamic memory */ - /* allocation if they want to (a handle to the memory allocator is */ - /* passed to the raster constructor). However, this is not */ - /* recommended for efficiency purposes. */ - /* */ - typedef void - (*SW_FT_Raster_ResetFunc)( SW_FT_Raster raster, - unsigned char* pool_base, - unsigned long pool_size ); - -#define SW_FT_Raster_Reset_Func SW_FT_Raster_ResetFunc - - - /*************************************************************************/ - /* */ - /* */ - /* SW_FT_Raster_RenderFunc */ - /* */ - /* */ - /* Invoke a given raster to scan-convert a given glyph image into a */ - /* target bitmap. */ - /* */ - /* */ - /* raster :: A handle to the raster object. */ - /* */ - /* params :: A pointer to an @SW_FT_Raster_Params structure used to */ - /* store the rendering parameters. */ - /* */ - /* */ - /* Error code. 0~means success. */ - /* */ - /* */ - /* The exact format of the source image depends on the raster's glyph */ - /* format defined in its @SW_FT_Raster_Funcs structure. It can be an */ - /* @SW_FT_Outline or anything else in order to support a large array of */ - /* glyph formats. */ - /* */ - /* Note also that the render function can fail and return a */ - /* `SW_FT_Err_Unimplemented_Feature' error code if the raster used does */ - /* not support direct composition. */ - /* */ - /* XXX: For now, the standard raster doesn't support direct */ - /* composition but this should change for the final release (see */ - /* the files `demos/src/ftgrays.c' and `demos/src/ftgrays2.c' */ - /* for examples of distinct implementations that support direct */ - /* composition). */ - /* */ - typedef int - (*SW_FT_Raster_RenderFunc)( SW_FT_Raster raster, - const SW_FT_Raster_Params* params ); - -#define SW_FT_Raster_Render_Func SW_FT_Raster_RenderFunc - - - /*************************************************************************/ - /* */ - /* */ - /* SW_FT_Raster_Funcs */ - /* */ - /* */ - /* A structure used to describe a given raster class to the library. */ - /* */ - /* */ - /* glyph_format :: The supported glyph format for this raster. */ - /* */ - /* raster_new :: The raster constructor. */ - /* */ - /* raster_reset :: Used to reset the render pool within the raster. */ - /* */ - /* raster_render :: A function to render a glyph into a given bitmap. */ - /* */ - /* raster_done :: The raster destructor. */ - /* */ - typedef struct SW_FT_Raster_Funcs_ - { - SW_FT_Raster_NewFunc raster_new; - SW_FT_Raster_ResetFunc raster_reset; - SW_FT_Raster_RenderFunc raster_render; - SW_FT_Raster_DoneFunc raster_done; - - } SW_FT_Raster_Funcs; - - -extern const SW_FT_Raster_Funcs sw_ft_grays_raster; - -#endif // V_FT_IMG_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_stroker.cpp b/libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_stroker.cpp deleted file mode 100644 index 3160f84f9..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_stroker.cpp +++ /dev/null @@ -1,1936 +0,0 @@ - -/***************************************************************************/ -/* */ -/* ftstroke.c */ -/* */ -/* FreeType path stroker (body). */ -/* */ -/* Copyright 2002-2006, 2008-2011, 2013 by */ -/* David Turner, Robert Wilhelm, and Werner Lemberg. */ -/* */ -/* This file is part of the FreeType project, and may only be used, */ -/* modified, and distributed under the terms of the FreeType project */ -/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ -/* this file you indicate that you have read the license and */ -/* understand and accept it fully. */ -/* */ -/***************************************************************************/ - -#include "v_ft_stroker.h" -#include -#include -#include -#include "v_ft_math.h" - -/*************************************************************************/ -/*************************************************************************/ -/***** *****/ -/***** BEZIER COMPUTATIONS *****/ -/***** *****/ -/*************************************************************************/ -/*************************************************************************/ - -#define SW_FT_SMALL_CONIC_THRESHOLD (SW_FT_ANGLE_PI / 6) -#define SW_FT_SMALL_CUBIC_THRESHOLD (SW_FT_ANGLE_PI / 8) - -#define SW_FT_EPSILON 2 - -#define SW_FT_IS_SMALL(x) ((x) > -SW_FT_EPSILON && (x) < SW_FT_EPSILON) - -static SW_FT_Pos ft_pos_abs(SW_FT_Pos x) -{ - return x >= 0 ? x : -x; -} - -static void ft_conic_split(SW_FT_Vector* base) -{ - SW_FT_Pos a, b; - - base[4].x = base[2].x; - a = base[0].x + base[1].x; - b = base[1].x + base[2].x; - base[3].x = b >> 1; - base[2].x = ( a + b ) >> 2; - base[1].x = a >> 1; - - base[4].y = base[2].y; - a = base[0].y + base[1].y; - b = base[1].y + base[2].y; - base[3].y = b >> 1; - base[2].y = ( a + b ) >> 2; - base[1].y = a >> 1; -} - -static SW_FT_Bool ft_conic_is_small_enough(SW_FT_Vector* base, - SW_FT_Angle* angle_in, - SW_FT_Angle* angle_out) -{ - SW_FT_Vector d1, d2; - SW_FT_Angle theta; - SW_FT_Int close1, close2; - - d1.x = base[1].x - base[2].x; - d1.y = base[1].y - base[2].y; - d2.x = base[0].x - base[1].x; - d2.y = base[0].y - base[1].y; - - close1 = SW_FT_IS_SMALL(d1.x) && SW_FT_IS_SMALL(d1.y); - close2 = SW_FT_IS_SMALL(d2.x) && SW_FT_IS_SMALL(d2.y); - - if (close1) { - if (close2) { - /* basically a point; */ - /* do nothing to retain original direction */ - } else { - *angle_in = *angle_out = SW_FT_Atan2(d2.x, d2.y); - } - } else /* !close1 */ - { - if (close2) { - *angle_in = *angle_out = SW_FT_Atan2(d1.x, d1.y); - } else { - *angle_in = SW_FT_Atan2(d1.x, d1.y); - *angle_out = SW_FT_Atan2(d2.x, d2.y); - } - } - - theta = ft_pos_abs(SW_FT_Angle_Diff(*angle_in, *angle_out)); - - return SW_FT_BOOL(theta < SW_FT_SMALL_CONIC_THRESHOLD); -} - -static void ft_cubic_split(SW_FT_Vector* base) -{ - SW_FT_Pos a, b, c; - - base[6].x = base[3].x; - a = base[0].x + base[1].x; - b = base[1].x + base[2].x; - c = base[2].x + base[3].x; - base[5].x = c >> 1; - c += b; - base[4].x = c >> 2; - base[1].x = a >> 1; - a += b; - base[2].x = a >> 2; - base[3].x = ( a + c ) >> 3; - - base[6].y = base[3].y; - a = base[0].y + base[1].y; - b = base[1].y + base[2].y; - c = base[2].y + base[3].y; - base[5].y = c >> 1; - c += b; - base[4].y = c >> 2; - base[1].y = a >> 1; - a += b; - base[2].y = a >> 2; - base[3].y = ( a + c ) >> 3; -} - -/* Return the average of `angle1' and `angle2'. */ -/* This gives correct result even if `angle1' and `angle2' */ -/* have opposite signs. */ -static SW_FT_Angle ft_angle_mean(SW_FT_Angle angle1, SW_FT_Angle angle2) -{ - return angle1 + SW_FT_Angle_Diff(angle1, angle2) / 2; -} - -static SW_FT_Bool ft_cubic_is_small_enough(SW_FT_Vector* base, - SW_FT_Angle* angle_in, - SW_FT_Angle* angle_mid, - SW_FT_Angle* angle_out) -{ - SW_FT_Vector d1, d2, d3; - SW_FT_Angle theta1, theta2; - SW_FT_Int close1, close2, close3; - - d1.x = base[2].x - base[3].x; - d1.y = base[2].y - base[3].y; - d2.x = base[1].x - base[2].x; - d2.y = base[1].y - base[2].y; - d3.x = base[0].x - base[1].x; - d3.y = base[0].y - base[1].y; - - close1 = SW_FT_IS_SMALL(d1.x) && SW_FT_IS_SMALL(d1.y); - close2 = SW_FT_IS_SMALL(d2.x) && SW_FT_IS_SMALL(d2.y); - close3 = SW_FT_IS_SMALL(d3.x) && SW_FT_IS_SMALL(d3.y); - - if (close1) { - if (close2) { - if (close3) { - /* basically a point; */ - /* do nothing to retain original direction */ - } else /* !close3 */ - { - *angle_in = *angle_mid = *angle_out = SW_FT_Atan2(d3.x, d3.y); - } - } else /* !close2 */ - { - if (close3) { - *angle_in = *angle_mid = *angle_out = SW_FT_Atan2(d2.x, d2.y); - } else /* !close3 */ - { - *angle_in = *angle_mid = SW_FT_Atan2(d2.x, d2.y); - *angle_out = SW_FT_Atan2(d3.x, d3.y); - } - } - } else /* !close1 */ - { - if (close2) { - if (close3) { - *angle_in = *angle_mid = *angle_out = SW_FT_Atan2(d1.x, d1.y); - } else /* !close3 */ - { - *angle_in = SW_FT_Atan2(d1.x, d1.y); - *angle_out = SW_FT_Atan2(d3.x, d3.y); - *angle_mid = ft_angle_mean(*angle_in, *angle_out); - } - } else /* !close2 */ - { - if (close3) { - *angle_in = SW_FT_Atan2(d1.x, d1.y); - *angle_mid = *angle_out = SW_FT_Atan2(d2.x, d2.y); - } else /* !close3 */ - { - *angle_in = SW_FT_Atan2(d1.x, d1.y); - *angle_mid = SW_FT_Atan2(d2.x, d2.y); - *angle_out = SW_FT_Atan2(d3.x, d3.y); - } - } - } - - theta1 = ft_pos_abs(SW_FT_Angle_Diff(*angle_in, *angle_mid)); - theta2 = ft_pos_abs(SW_FT_Angle_Diff(*angle_mid, *angle_out)); - - return SW_FT_BOOL(theta1 < SW_FT_SMALL_CUBIC_THRESHOLD && - theta2 < SW_FT_SMALL_CUBIC_THRESHOLD); -} - -/*************************************************************************/ -/*************************************************************************/ -/***** *****/ -/***** STROKE BORDERS *****/ -/***** *****/ -/*************************************************************************/ -/*************************************************************************/ - -typedef enum SW_FT_StrokeTags_ { - SW_FT_STROKE_TAG_ON = 1, /* on-curve point */ - SW_FT_STROKE_TAG_CUBIC = 2, /* cubic off-point */ - SW_FT_STROKE_TAG_BEGIN = 4, /* sub-path start */ - SW_FT_STROKE_TAG_END = 8 /* sub-path end */ - -} SW_FT_StrokeTags; - -#define SW_FT_STROKE_TAG_BEGIN_END \ - (SW_FT_STROKE_TAG_BEGIN | SW_FT_STROKE_TAG_END) - -typedef struct SW_FT_StrokeBorderRec_ { - SW_FT_UInt num_points; - SW_FT_UInt max_points; - SW_FT_Vector* points; - SW_FT_Byte* tags; - SW_FT_Bool movable; /* TRUE for ends of lineto borders */ - SW_FT_Int start; /* index of current sub-path start point */ - SW_FT_Bool valid; - -} SW_FT_StrokeBorderRec, *SW_FT_StrokeBorder; - -SW_FT_Error SW_FT_Outline_Check(SW_FT_Outline* outline) -{ - if (outline) { - SW_FT_Int n_points = outline->n_points; - SW_FT_Int n_contours = outline->n_contours; - SW_FT_Int end0, end; - SW_FT_Int n; - - /* empty glyph? */ - if (n_points == 0 && n_contours == 0) return 0; - - /* check point and contour counts */ - if (n_points <= 0 || n_contours <= 0) goto Bad; - - end0 = end = -1; - for (n = 0; n < n_contours; n++) { - end = outline->contours[n]; - - /* note that we don't accept empty contours */ - if (end <= end0 || end >= n_points) goto Bad; - - end0 = end; - } - - if (end != n_points - 1) goto Bad; - - /* XXX: check the tags array */ - return 0; - } - -Bad: - return -1; // SW_FT_THROW( Invalid_Argument ); -} - -void SW_FT_Outline_Get_CBox(const SW_FT_Outline* outline, SW_FT_BBox* acbox) -{ - SW_FT_Pos xMin, yMin, xMax, yMax; - - if (outline && acbox) { - if (outline->n_points == 0) { - xMin = 0; - yMin = 0; - xMax = 0; - yMax = 0; - } else { - SW_FT_Vector* vec = outline->points; - SW_FT_Vector* limit = vec + outline->n_points; - - xMin = xMax = vec->x; - yMin = yMax = vec->y; - vec++; - - for (; vec < limit; vec++) { - SW_FT_Pos x, y; - - x = vec->x; - if (x < xMin) xMin = x; - if (x > xMax) xMax = x; - - y = vec->y; - if (y < yMin) yMin = y; - if (y > yMax) yMax = y; - } - } - acbox->xMin = xMin; - acbox->xMax = xMax; - acbox->yMin = yMin; - acbox->yMax = yMax; - } -} - -static SW_FT_Error ft_stroke_border_grow(SW_FT_StrokeBorder border, - SW_FT_UInt new_points) -{ - SW_FT_UInt old_max = border->max_points; - SW_FT_UInt new_max = border->num_points + new_points; - SW_FT_Error error = 0; - - if (new_max > old_max) { - SW_FT_UInt cur_max = old_max; - - while (cur_max < new_max) cur_max += (cur_max >> 1) + 16; - - border->points = (SW_FT_Vector*)realloc(border->points, - cur_max * sizeof(SW_FT_Vector)); - border->tags = - (SW_FT_Byte*)realloc(border->tags, cur_max * sizeof(SW_FT_Byte)); - - if (!border->points || !border->tags) goto Exit; - - border->max_points = cur_max; - } - -Exit: - return error; -} - -static void ft_stroke_border_close(SW_FT_StrokeBorder border, - SW_FT_Bool reverse) -{ - SW_FT_UInt start = border->start; - SW_FT_UInt count = border->num_points; - - assert(border->start >= 0); - - /* don't record empty paths! */ - if (count <= start + 1U) - border->num_points = start; - else { - /* copy the last point to the start of this sub-path, since */ - /* it contains the `adjusted' starting coordinates */ - border->num_points = --count; - border->points[start] = border->points[count]; - - if (reverse) { - /* reverse the points */ - { - SW_FT_Vector* vec1 = border->points + start + 1; - SW_FT_Vector* vec2 = border->points + count - 1; - - for (; vec1 < vec2; vec1++, vec2--) { - SW_FT_Vector tmp; - - tmp = *vec1; - *vec1 = *vec2; - *vec2 = tmp; - } - } - - /* then the tags */ - { - SW_FT_Byte* tag1 = border->tags + start + 1; - SW_FT_Byte* tag2 = border->tags + count - 1; - - for (; tag1 < tag2; tag1++, tag2--) { - SW_FT_Byte tmp; - - tmp = *tag1; - *tag1 = *tag2; - *tag2 = tmp; - } - } - } - - border->tags[start] |= SW_FT_STROKE_TAG_BEGIN; - border->tags[count - 1] |= SW_FT_STROKE_TAG_END; - } - - border->start = -1; - border->movable = FALSE; -} - -static SW_FT_Error ft_stroke_border_lineto(SW_FT_StrokeBorder border, - SW_FT_Vector* to, SW_FT_Bool movable) -{ - SW_FT_Error error = 0; - - assert(border->start >= 0); - - if (border->movable) { - /* move last point */ - border->points[border->num_points - 1] = *to; - } else { - /* don't add zero-length lineto */ - if (border->num_points > 0 && - SW_FT_IS_SMALL(border->points[border->num_points - 1].x - to->x) && - SW_FT_IS_SMALL(border->points[border->num_points - 1].y - to->y)) - return error; - - /* add one point */ - error = ft_stroke_border_grow(border, 1); - if (!error) { - SW_FT_Vector* vec = border->points + border->num_points; - SW_FT_Byte* tag = border->tags + border->num_points; - - vec[0] = *to; - tag[0] = SW_FT_STROKE_TAG_ON; - - border->num_points += 1; - } - } - border->movable = movable; - return error; -} - -static SW_FT_Error ft_stroke_border_conicto(SW_FT_StrokeBorder border, - SW_FT_Vector* control, - SW_FT_Vector* to) -{ - SW_FT_Error error; - - assert(border->start >= 0); - - error = ft_stroke_border_grow(border, 2); - if (!error) { - SW_FT_Vector* vec = border->points + border->num_points; - SW_FT_Byte* tag = border->tags + border->num_points; - - vec[0] = *control; - vec[1] = *to; - - tag[0] = 0; - tag[1] = SW_FT_STROKE_TAG_ON; - - border->num_points += 2; - } - - border->movable = FALSE; - - return error; -} - -static SW_FT_Error ft_stroke_border_cubicto(SW_FT_StrokeBorder border, - SW_FT_Vector* control1, - SW_FT_Vector* control2, - SW_FT_Vector* to) -{ - SW_FT_Error error; - - assert(border->start >= 0); - - error = ft_stroke_border_grow(border, 3); - if (!error) { - SW_FT_Vector* vec = border->points + border->num_points; - SW_FT_Byte* tag = border->tags + border->num_points; - - vec[0] = *control1; - vec[1] = *control2; - vec[2] = *to; - - tag[0] = SW_FT_STROKE_TAG_CUBIC; - tag[1] = SW_FT_STROKE_TAG_CUBIC; - tag[2] = SW_FT_STROKE_TAG_ON; - - border->num_points += 3; - } - - border->movable = FALSE; - - return error; -} - -#define SW_FT_ARC_CUBIC_ANGLE (SW_FT_ANGLE_PI / 2) - - -static SW_FT_Error -ft_stroke_border_arcto( SW_FT_StrokeBorder border, - SW_FT_Vector* center, - SW_FT_Fixed radius, - SW_FT_Angle angle_start, - SW_FT_Angle angle_diff ) -{ - SW_FT_Fixed coef; - SW_FT_Vector a0, a1, a2, a3; - SW_FT_Int i, arcs = 1; - SW_FT_Error error = 0; - - - /* number of cubic arcs to draw */ - while ( angle_diff > SW_FT_ARC_CUBIC_ANGLE * arcs || - -angle_diff > SW_FT_ARC_CUBIC_ANGLE * arcs ) - arcs++; - - /* control tangents */ - coef = SW_FT_Tan( angle_diff / ( 4 * arcs ) ); - coef += coef / 3; - - /* compute start and first control point */ - SW_FT_Vector_From_Polar( &a0, radius, angle_start ); - a1.x = SW_FT_MulFix( -a0.y, coef ); - a1.y = SW_FT_MulFix( a0.x, coef ); - - a0.x += center->x; - a0.y += center->y; - a1.x += a0.x; - a1.y += a0.y; - - for ( i = 1; i <= arcs; i++ ) - { - /* compute end and second control point */ - SW_FT_Vector_From_Polar( &a3, radius, - angle_start + i * angle_diff / arcs ); - a2.x = SW_FT_MulFix( a3.y, coef ); - a2.y = SW_FT_MulFix( -a3.x, coef ); - - a3.x += center->x; - a3.y += center->y; - a2.x += a3.x; - a2.y += a3.y; - - /* add cubic arc */ - error = ft_stroke_border_cubicto( border, &a1, &a2, &a3 ); - if ( error ) - break; - - /* a0 = a3; */ - a1.x = a3.x - a2.x + a3.x; - a1.y = a3.y - a2.y + a3.y; - } - - return error; -} - -static SW_FT_Error ft_stroke_border_moveto(SW_FT_StrokeBorder border, - SW_FT_Vector* to) -{ - /* close current open path if any ? */ - if (border->start >= 0) ft_stroke_border_close(border, FALSE); - - border->start = border->num_points; - border->movable = FALSE; - - return ft_stroke_border_lineto(border, to, FALSE); -} - -static void ft_stroke_border_init(SW_FT_StrokeBorder border) -{ - border->points = NULL; - border->tags = NULL; - - border->num_points = 0; - border->max_points = 0; - border->start = -1; - border->valid = FALSE; -} - -static void ft_stroke_border_reset(SW_FT_StrokeBorder border) -{ - border->num_points = 0; - border->start = -1; - border->valid = FALSE; -} - -static void ft_stroke_border_done(SW_FT_StrokeBorder border) -{ - free(border->points); - free(border->tags); - - border->num_points = 0; - border->max_points = 0; - border->start = -1; - border->valid = FALSE; -} - -static SW_FT_Error ft_stroke_border_get_counts(SW_FT_StrokeBorder border, - SW_FT_UInt* anum_points, - SW_FT_UInt* anum_contours) -{ - SW_FT_Error error = 0; - SW_FT_UInt num_points = 0; - SW_FT_UInt num_contours = 0; - - SW_FT_UInt count = border->num_points; - SW_FT_Vector* point = border->points; - SW_FT_Byte* tags = border->tags; - SW_FT_Int in_contour = 0; - - for (; count > 0; count--, num_points++, point++, tags++) { - if (tags[0] & SW_FT_STROKE_TAG_BEGIN) { - if (in_contour != 0) goto Fail; - - in_contour = 1; - } else if (in_contour == 0) - goto Fail; - - if (tags[0] & SW_FT_STROKE_TAG_END) { - in_contour = 0; - num_contours++; - } - } - - if (in_contour != 0) goto Fail; - - border->valid = TRUE; - -Exit: - *anum_points = num_points; - *anum_contours = num_contours; - return error; - -Fail: - num_points = 0; - num_contours = 0; - goto Exit; -} - -static void ft_stroke_border_export(SW_FT_StrokeBorder border, - SW_FT_Outline* outline) -{ - /* copy point locations */ - memcpy(outline->points + outline->n_points, border->points, - border->num_points * sizeof(SW_FT_Vector)); - - /* copy tags */ - { - SW_FT_UInt count = border->num_points; - SW_FT_Byte* read = border->tags; - SW_FT_Byte* write = (SW_FT_Byte*)outline->tags + outline->n_points; - - for (; count > 0; count--, read++, write++) { - if (*read & SW_FT_STROKE_TAG_ON) - *write = SW_FT_CURVE_TAG_ON; - else if (*read & SW_FT_STROKE_TAG_CUBIC) - *write = SW_FT_CURVE_TAG_CUBIC; - else - *write = SW_FT_CURVE_TAG_CONIC; - } - } - - /* copy contours */ - { - SW_FT_UInt count = border->num_points; - SW_FT_Byte* tags = border->tags; - SW_FT_Short* write = outline->contours + outline->n_contours; - SW_FT_Short idx = (SW_FT_Short)outline->n_points; - - for (; count > 0; count--, tags++, idx++) { - if (*tags & SW_FT_STROKE_TAG_END) { - *write++ = idx; - outline->n_contours++; - } - } - } - - outline->n_points = (short)(outline->n_points + border->num_points); - - assert(SW_FT_Outline_Check(outline) == 0); -} - -/*************************************************************************/ -/*************************************************************************/ -/***** *****/ -/***** STROKER *****/ -/***** *****/ -/*************************************************************************/ -/*************************************************************************/ - -#define SW_FT_SIDE_TO_ROTATE(s) (SW_FT_ANGLE_PI2 - (s)*SW_FT_ANGLE_PI) - -typedef struct SW_FT_StrokerRec_ { - SW_FT_Angle angle_in; /* direction into curr join */ - SW_FT_Angle angle_out; /* direction out of join */ - SW_FT_Vector center; /* current position */ - SW_FT_Fixed line_length; /* length of last lineto */ - SW_FT_Bool first_point; /* is this the start? */ - SW_FT_Bool subpath_open; /* is the subpath open? */ - SW_FT_Angle subpath_angle; /* subpath start direction */ - SW_FT_Vector subpath_start; /* subpath start position */ - SW_FT_Fixed subpath_line_length; /* subpath start lineto len */ - SW_FT_Bool handle_wide_strokes; /* use wide strokes logic? */ - - SW_FT_Stroker_LineCap line_cap; - SW_FT_Stroker_LineJoin line_join; - SW_FT_Stroker_LineJoin line_join_saved; - SW_FT_Fixed miter_limit; - SW_FT_Fixed radius; - - SW_FT_StrokeBorderRec borders[2]; -} SW_FT_StrokerRec; - -/* documentation is in ftstroke.h */ - -SW_FT_Error SW_FT_Stroker_New(SW_FT_Stroker* astroker) -{ - SW_FT_Error error = 0; /* assigned in SW_FT_NEW */ - SW_FT_Stroker stroker = NULL; - - stroker = (SW_FT_StrokerRec*)calloc(1, sizeof(SW_FT_StrokerRec)); - if (stroker) { - ft_stroke_border_init(&stroker->borders[0]); - ft_stroke_border_init(&stroker->borders[1]); - } - - *astroker = stroker; - - return error; -} - -void SW_FT_Stroker_Rewind(SW_FT_Stroker stroker) -{ - if (stroker) { - ft_stroke_border_reset(&stroker->borders[0]); - ft_stroke_border_reset(&stroker->borders[1]); - } -} - -/* documentation is in ftstroke.h */ - -void SW_FT_Stroker_Set(SW_FT_Stroker stroker, SW_FT_Fixed radius, - SW_FT_Stroker_LineCap line_cap, - SW_FT_Stroker_LineJoin line_join, - SW_FT_Fixed miter_limit) -{ - stroker->radius = radius; - stroker->line_cap = line_cap; - stroker->line_join = line_join; - stroker->miter_limit = miter_limit; - - /* ensure miter limit has sensible value */ - if (stroker->miter_limit < 0x10000) stroker->miter_limit = 0x10000; - - /* save line join style: */ - /* line join style can be temporarily changed when stroking curves */ - stroker->line_join_saved = line_join; - - SW_FT_Stroker_Rewind(stroker); -} - -/* documentation is in ftstroke.h */ - -void SW_FT_Stroker_Done(SW_FT_Stroker stroker) -{ - if (stroker) { - ft_stroke_border_done(&stroker->borders[0]); - ft_stroke_border_done(&stroker->borders[1]); - - free(stroker); - } -} - -/* create a circular arc at a corner or cap */ -static SW_FT_Error ft_stroker_arcto(SW_FT_Stroker stroker, SW_FT_Int side) -{ - SW_FT_Angle total, rotate; - SW_FT_Fixed radius = stroker->radius; - SW_FT_Error error = 0; - SW_FT_StrokeBorder border = stroker->borders + side; - - rotate = SW_FT_SIDE_TO_ROTATE(side); - - total = SW_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); - if (total == SW_FT_ANGLE_PI) total = -rotate * 2; - - error = ft_stroke_border_arcto(border, &stroker->center, radius, - stroker->angle_in + rotate, total); - border->movable = FALSE; - return error; -} - -/* add a cap at the end of an opened path */ -static SW_FT_Error -ft_stroker_cap(SW_FT_Stroker stroker, - SW_FT_Angle angle, - SW_FT_Int side) -{ - SW_FT_Error error = 0; - - if (stroker->line_cap == SW_FT_STROKER_LINECAP_ROUND) - { - /* add a round cap */ - stroker->angle_in = angle; - stroker->angle_out = angle + SW_FT_ANGLE_PI; - - error = ft_stroker_arcto(stroker, side); - } - else - { - /* add a square or butt cap */ - SW_FT_Vector middle, delta; - SW_FT_Fixed radius = stroker->radius; - SW_FT_StrokeBorder border = stroker->borders + side; - - /* compute middle point and first angle point */ - SW_FT_Vector_From_Polar( &middle, radius, angle ); - delta.x = side ? middle.y : -middle.y; - delta.y = side ? -middle.x : middle.x; - - if ( stroker->line_cap == SW_FT_STROKER_LINECAP_SQUARE ) - { - middle.x += stroker->center.x; - middle.y += stroker->center.y; - } - else /* SW_FT_STROKER_LINECAP_BUTT */ - { - middle.x = stroker->center.x; - middle.y = stroker->center.y; - } - - delta.x += middle.x; - delta.y += middle.y; - - error = ft_stroke_border_lineto( border, &delta, FALSE ); - if ( error ) - goto Exit; - - /* compute second angle point */ - delta.x = middle.x - delta.x + middle.x; - delta.y = middle.y - delta.y + middle.y; - - error = ft_stroke_border_lineto( border, &delta, FALSE ); - } - -Exit: - return error; -} - -/* process an inside corner, i.e. compute intersection */ -static SW_FT_Error ft_stroker_inside(SW_FT_Stroker stroker, SW_FT_Int side, - SW_FT_Fixed line_length) -{ - SW_FT_StrokeBorder border = stroker->borders + side; - SW_FT_Angle phi, theta, rotate; - SW_FT_Fixed length; - SW_FT_Vector sigma, delta; - SW_FT_Error error = 0; - SW_FT_Bool intersect; /* use intersection of lines? */ - - rotate = SW_FT_SIDE_TO_ROTATE(side); - - theta = SW_FT_Angle_Diff(stroker->angle_in, stroker->angle_out) / 2; - - /* Only intersect borders if between two lineto's and both */ - /* lines are long enough (line_length is zero for curves). */ - if (!border->movable || line_length == 0 || - theta > 0x59C000 || theta < -0x59C000 ) - intersect = FALSE; - else { - /* compute minimum required length of lines */ - SW_FT_Fixed min_length; - - - SW_FT_Vector_Unit( &sigma, theta ); - min_length = - ft_pos_abs( SW_FT_MulDiv( stroker->radius, sigma.y, sigma.x ) ); - - intersect = SW_FT_BOOL( min_length && - stroker->line_length >= min_length && - line_length >= min_length ); - } - - if (!intersect) { - SW_FT_Vector_From_Polar(&delta, stroker->radius, - stroker->angle_out + rotate); - delta.x += stroker->center.x; - delta.y += stroker->center.y; - - border->movable = FALSE; - } else { - /* compute median angle */ - phi = stroker->angle_in + theta + rotate; - - length = SW_FT_DivFix( stroker->radius, sigma.x ); - - SW_FT_Vector_From_Polar( &delta, length, phi ); - delta.x += stroker->center.x; - delta.y += stroker->center.y; - } - - error = ft_stroke_border_lineto(border, &delta, FALSE); - - return error; -} - - /* process an outside corner, i.e. compute bevel/miter/round */ -static SW_FT_Error -ft_stroker_outside( SW_FT_Stroker stroker, - SW_FT_Int side, - SW_FT_Fixed line_length ) -{ - SW_FT_StrokeBorder border = stroker->borders + side; - SW_FT_Error error; - SW_FT_Angle rotate; - - - if ( stroker->line_join == SW_FT_STROKER_LINEJOIN_ROUND ) - error = ft_stroker_arcto( stroker, side ); - else - { - /* this is a mitered (pointed) or beveled (truncated) corner */ - SW_FT_Fixed radius = stroker->radius; - SW_FT_Vector sigma; - SW_FT_Angle theta = 0, phi = 0; - SW_FT_Bool bevel, fixed_bevel; - - - rotate = SW_FT_SIDE_TO_ROTATE( side ); - - bevel = - SW_FT_BOOL( stroker->line_join == SW_FT_STROKER_LINEJOIN_BEVEL ); - - fixed_bevel = - SW_FT_BOOL( stroker->line_join != SW_FT_STROKER_LINEJOIN_MITER_VARIABLE ); - - /* check miter limit first */ - if ( !bevel ) - { - theta = SW_FT_Angle_Diff( stroker->angle_in, stroker->angle_out ) / 2; - - if ( theta == SW_FT_ANGLE_PI2 ) - theta = -rotate; - - phi = stroker->angle_in + theta + rotate; - - SW_FT_Vector_From_Polar( &sigma, stroker->miter_limit, theta ); - - /* is miter limit exceeded? */ - if ( sigma.x < 0x10000L ) - { - /* don't create variable bevels for very small deviations; */ - /* FT_Sin(x) = 0 for x <= 57 */ - if ( fixed_bevel || ft_pos_abs( theta ) > 57 ) - bevel = TRUE; - } - } - - if ( bevel ) /* this is a bevel (broken angle) */ - { - if ( fixed_bevel ) - { - /* the outer corners are simply joined together */ - SW_FT_Vector delta; - - - /* add bevel */ - SW_FT_Vector_From_Polar( &delta, - radius, - stroker->angle_out + rotate ); - delta.x += stroker->center.x; - delta.y += stroker->center.y; - - border->movable = FALSE; - error = ft_stroke_border_lineto( border, &delta, FALSE ); - } - else /* variable bevel or clipped miter */ - { - /* the miter is truncated */ - SW_FT_Vector middle, delta; - SW_FT_Fixed coef; - - - /* compute middle point and first angle point */ - SW_FT_Vector_From_Polar( &middle, - SW_FT_MulFix( radius, stroker->miter_limit ), - phi ); - - coef = SW_FT_DivFix( 0x10000L - sigma.x, sigma.y ); - delta.x = SW_FT_MulFix( middle.y, coef ); - delta.y = SW_FT_MulFix( -middle.x, coef ); - - middle.x += stroker->center.x; - middle.y += stroker->center.y; - delta.x += middle.x; - delta.y += middle.y; - - error = ft_stroke_border_lineto( border, &delta, FALSE ); - if ( error ) - goto Exit; - - /* compute second angle point */ - delta.x = middle.x - delta.x + middle.x; - delta.y = middle.y - delta.y + middle.y; - - error = ft_stroke_border_lineto( border, &delta, FALSE ); - if ( error ) - goto Exit; - - /* finally, add an end point; only needed if not lineto */ - /* (line_length is zero for curves) */ - if ( line_length == 0 ) - { - SW_FT_Vector_From_Polar( &delta, - radius, - stroker->angle_out + rotate ); - - delta.x += stroker->center.x; - delta.y += stroker->center.y; - - error = ft_stroke_border_lineto( border, &delta, FALSE ); - } - } - } - else /* this is a miter (intersection) */ - { - SW_FT_Fixed length; - SW_FT_Vector delta; - - - length = SW_FT_MulDiv( stroker->radius, stroker->miter_limit, sigma.x ); - - SW_FT_Vector_From_Polar( &delta, length, phi ); - delta.x += stroker->center.x; - delta.y += stroker->center.y; - - error = ft_stroke_border_lineto( border, &delta, FALSE ); - if ( error ) - goto Exit; - - /* now add an end point; only needed if not lineto */ - /* (line_length is zero for curves) */ - if ( line_length == 0 ) - { - SW_FT_Vector_From_Polar( &delta, - stroker->radius, - stroker->angle_out + rotate ); - delta.x += stroker->center.x; - delta.y += stroker->center.y; - - error = ft_stroke_border_lineto( border, &delta, FALSE ); - } - } - } - - Exit: - return error; -} - -static SW_FT_Error ft_stroker_process_corner(SW_FT_Stroker stroker, - SW_FT_Fixed line_length) -{ - SW_FT_Error error = 0; - SW_FT_Angle turn; - SW_FT_Int inside_side; - - turn = SW_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); - - /* no specific corner processing is required if the turn is 0 */ - if (turn == 0) goto Exit; - - /* when we turn to the right, the inside side is 0 */ - inside_side = 0; - - /* otherwise, the inside side is 1 */ - if (turn < 0) inside_side = 1; - - /* process the inside side */ - error = ft_stroker_inside(stroker, inside_side, line_length); - if (error) goto Exit; - - /* process the outside side */ - error = ft_stroker_outside(stroker, 1 - inside_side, line_length); - -Exit: - return error; -} - -/* add two points to the left and right borders corresponding to the */ -/* start of the subpath */ -static SW_FT_Error ft_stroker_subpath_start(SW_FT_Stroker stroker, - SW_FT_Angle start_angle, - SW_FT_Fixed line_length) -{ - SW_FT_Vector delta; - SW_FT_Vector point; - SW_FT_Error error; - SW_FT_StrokeBorder border; - - SW_FT_Vector_From_Polar(&delta, stroker->radius, - start_angle + SW_FT_ANGLE_PI2); - - point.x = stroker->center.x + delta.x; - point.y = stroker->center.y + delta.y; - - border = stroker->borders; - error = ft_stroke_border_moveto(border, &point); - if (error) goto Exit; - - point.x = stroker->center.x - delta.x; - point.y = stroker->center.y - delta.y; - - border++; - error = ft_stroke_border_moveto(border, &point); - - /* save angle, position, and line length for last join */ - /* (line_length is zero for curves) */ - stroker->subpath_angle = start_angle; - stroker->first_point = FALSE; - stroker->subpath_line_length = line_length; - -Exit: - return error; -} - -/* documentation is in ftstroke.h */ - -SW_FT_Error SW_FT_Stroker_LineTo(SW_FT_Stroker stroker, SW_FT_Vector* to) -{ - SW_FT_Error error = 0; - SW_FT_StrokeBorder border; - SW_FT_Vector delta; - SW_FT_Angle angle; - SW_FT_Int side; - SW_FT_Fixed line_length; - - delta.x = to->x - stroker->center.x; - delta.y = to->y - stroker->center.y; - - /* a zero-length lineto is a no-op; avoid creating a spurious corner */ - if (delta.x == 0 && delta.y == 0) goto Exit; - - /* compute length of line */ - line_length = SW_FT_Vector_Length(&delta); - - angle = SW_FT_Atan2(delta.x, delta.y); - SW_FT_Vector_From_Polar(&delta, stroker->radius, angle + SW_FT_ANGLE_PI2); - - /* process corner if necessary */ - if (stroker->first_point) { - /* This is the first segment of a subpath. We need to */ - /* add a point to each border at their respective starting */ - /* point locations. */ - error = ft_stroker_subpath_start(stroker, angle, line_length); - if (error) goto Exit; - } else { - /* process the current corner */ - stroker->angle_out = angle; - error = ft_stroker_process_corner(stroker, line_length); - if (error) goto Exit; - } - - /* now add a line segment to both the `inside' and `outside' paths */ - for (border = stroker->borders, side = 1; side >= 0; side--, border++) { - SW_FT_Vector point; - - point.x = to->x + delta.x; - point.y = to->y + delta.y; - - /* the ends of lineto borders are movable */ - error = ft_stroke_border_lineto(border, &point, TRUE); - if (error) goto Exit; - - delta.x = -delta.x; - delta.y = -delta.y; - } - - stroker->angle_in = angle; - stroker->center = *to; - stroker->line_length = line_length; - -Exit: - return error; -} - -/* documentation is in ftstroke.h */ - -SW_FT_Error SW_FT_Stroker_ConicTo(SW_FT_Stroker stroker, SW_FT_Vector* control, - SW_FT_Vector* to) -{ - SW_FT_Error error = 0; - SW_FT_Vector bez_stack[34]; - SW_FT_Vector* arc; - SW_FT_Vector* limit = bez_stack + 30; - SW_FT_Bool first_arc = TRUE; - - /* if all control points are coincident, this is a no-op; */ - /* avoid creating a spurious corner */ - if (SW_FT_IS_SMALL(stroker->center.x - control->x) && - SW_FT_IS_SMALL(stroker->center.y - control->y) && - SW_FT_IS_SMALL(control->x - to->x) && - SW_FT_IS_SMALL(control->y - to->y)) { - stroker->center = *to; - goto Exit; - } - - arc = bez_stack; - arc[0] = *to; - arc[1] = *control; - arc[2] = stroker->center; - - while (arc >= bez_stack) { - SW_FT_Angle angle_in, angle_out; - - /* initialize with current direction */ - angle_in = angle_out = stroker->angle_in; - - if (arc < limit && - !ft_conic_is_small_enough(arc, &angle_in, &angle_out)) { - if (stroker->first_point) stroker->angle_in = angle_in; - - ft_conic_split(arc); - arc += 2; - continue; - } - - if (first_arc) { - first_arc = FALSE; - - /* process corner if necessary */ - if (stroker->first_point) - error = ft_stroker_subpath_start(stroker, angle_in, 0); - else { - stroker->angle_out = angle_in; - error = ft_stroker_process_corner(stroker, 0); - } - } else if (ft_pos_abs(SW_FT_Angle_Diff(stroker->angle_in, angle_in)) > - SW_FT_SMALL_CONIC_THRESHOLD / 4) { - /* if the deviation from one arc to the next is too great, */ - /* add a round corner */ - stroker->center = arc[2]; - stroker->angle_out = angle_in; - stroker->line_join = SW_FT_STROKER_LINEJOIN_ROUND; - - error = ft_stroker_process_corner(stroker, 0); - - /* reinstate line join style */ - stroker->line_join = stroker->line_join_saved; - } - - if (error) goto Exit; - - /* the arc's angle is small enough; we can add it directly to each */ - /* border */ - { - SW_FT_Vector ctrl, end; - SW_FT_Angle theta, phi, rotate, alpha0 = 0; - SW_FT_Fixed length; - SW_FT_StrokeBorder border; - SW_FT_Int side; - - theta = SW_FT_Angle_Diff(angle_in, angle_out) / 2; - phi = angle_in + theta; - length = SW_FT_DivFix(stroker->radius, SW_FT_Cos(theta)); - - /* compute direction of original arc */ - if (stroker->handle_wide_strokes) - alpha0 = SW_FT_Atan2(arc[0].x - arc[2].x, arc[0].y - arc[2].y); - - for (border = stroker->borders, side = 0; side <= 1; - side++, border++) { - rotate = SW_FT_SIDE_TO_ROTATE(side); - - /* compute control point */ - SW_FT_Vector_From_Polar(&ctrl, length, phi + rotate); - ctrl.x += arc[1].x; - ctrl.y += arc[1].y; - - /* compute end point */ - SW_FT_Vector_From_Polar(&end, stroker->radius, - angle_out + rotate); - end.x += arc[0].x; - end.y += arc[0].y; - - if (stroker->handle_wide_strokes) { - SW_FT_Vector start; - SW_FT_Angle alpha1; - - /* determine whether the border radius is greater than the - */ - /* radius of curvature of the original arc */ - start = border->points[border->num_points - 1]; - - alpha1 = SW_FT_Atan2(end.x - start.x, end.y - start.y); - - /* is the direction of the border arc opposite to */ - /* that of the original arc? */ - if (ft_pos_abs(SW_FT_Angle_Diff(alpha0, alpha1)) > - SW_FT_ANGLE_PI / 2) { - SW_FT_Angle beta, gamma; - SW_FT_Vector bvec, delta; - SW_FT_Fixed blen, sinA, sinB, alen; - - /* use the sine rule to find the intersection point */ - beta = - SW_FT_Atan2(arc[2].x - start.x, arc[2].y - start.y); - gamma = SW_FT_Atan2(arc[0].x - end.x, arc[0].y - end.y); - - bvec.x = end.x - start.x; - bvec.y = end.y - start.y; - - blen = SW_FT_Vector_Length(&bvec); - - sinA = ft_pos_abs(SW_FT_Sin(alpha1 - gamma)); - sinB = ft_pos_abs(SW_FT_Sin(beta - gamma)); - - alen = SW_FT_MulDiv(blen, sinA, sinB); - - SW_FT_Vector_From_Polar(&delta, alen, beta); - delta.x += start.x; - delta.y += start.y; - - /* circumnavigate the negative sector backwards */ - border->movable = FALSE; - error = ft_stroke_border_lineto(border, &delta, FALSE); - if (error) goto Exit; - error = ft_stroke_border_lineto(border, &end, FALSE); - if (error) goto Exit; - error = ft_stroke_border_conicto(border, &ctrl, &start); - if (error) goto Exit; - /* and then move to the endpoint */ - error = ft_stroke_border_lineto(border, &end, FALSE); - if (error) goto Exit; - - continue; - } - - /* else fall through */ - } - - /* simply add an arc */ - error = ft_stroke_border_conicto(border, &ctrl, &end); - if (error) goto Exit; - } - } - - arc -= 2; - - stroker->angle_in = angle_out; - } - - stroker->center = *to; - -Exit: - return error; -} - -/* documentation is in ftstroke.h */ - -SW_FT_Error SW_FT_Stroker_CubicTo(SW_FT_Stroker stroker, SW_FT_Vector* control1, - SW_FT_Vector* control2, SW_FT_Vector* to) -{ - SW_FT_Error error = 0; - SW_FT_Vector bez_stack[37]; - SW_FT_Vector* arc; - SW_FT_Vector* limit = bez_stack + 32; - SW_FT_Bool first_arc = TRUE; - - /* if all control points are coincident, this is a no-op; */ - /* avoid creating a spurious corner */ - if (SW_FT_IS_SMALL(stroker->center.x - control1->x) && - SW_FT_IS_SMALL(stroker->center.y - control1->y) && - SW_FT_IS_SMALL(control1->x - control2->x) && - SW_FT_IS_SMALL(control1->y - control2->y) && - SW_FT_IS_SMALL(control2->x - to->x) && - SW_FT_IS_SMALL(control2->y - to->y)) { - stroker->center = *to; - goto Exit; - } - - arc = bez_stack; - arc[0] = *to; - arc[1] = *control2; - arc[2] = *control1; - arc[3] = stroker->center; - - while (arc >= bez_stack) { - SW_FT_Angle angle_in, angle_mid, angle_out; - - /* initialize with current direction */ - angle_in = angle_out = angle_mid = stroker->angle_in; - - if (arc < limit && - !ft_cubic_is_small_enough(arc, &angle_in, &angle_mid, &angle_out)) { - if (stroker->first_point) stroker->angle_in = angle_in; - - ft_cubic_split(arc); - arc += 3; - continue; - } - - if (first_arc) { - first_arc = FALSE; - - /* process corner if necessary */ - if (stroker->first_point) - error = ft_stroker_subpath_start(stroker, angle_in, 0); - else { - stroker->angle_out = angle_in; - error = ft_stroker_process_corner(stroker, 0); - } - } else if (ft_pos_abs(SW_FT_Angle_Diff(stroker->angle_in, angle_in)) > - SW_FT_SMALL_CUBIC_THRESHOLD / 4) { - /* if the deviation from one arc to the next is too great, */ - /* add a round corner */ - stroker->center = arc[3]; - stroker->angle_out = angle_in; - stroker->line_join = SW_FT_STROKER_LINEJOIN_ROUND; - - error = ft_stroker_process_corner(stroker, 0); - - /* reinstate line join style */ - stroker->line_join = stroker->line_join_saved; - } - - if (error) goto Exit; - - /* the arc's angle is small enough; we can add it directly to each */ - /* border */ - { - SW_FT_Vector ctrl1, ctrl2, end; - SW_FT_Angle theta1, phi1, theta2, phi2, rotate, alpha0 = 0; - SW_FT_Fixed length1, length2; - SW_FT_StrokeBorder border; - SW_FT_Int side; - - theta1 = SW_FT_Angle_Diff(angle_in, angle_mid) / 2; - theta2 = SW_FT_Angle_Diff(angle_mid, angle_out) / 2; - phi1 = ft_angle_mean(angle_in, angle_mid); - phi2 = ft_angle_mean(angle_mid, angle_out); - length1 = SW_FT_DivFix(stroker->radius, SW_FT_Cos(theta1)); - length2 = SW_FT_DivFix(stroker->radius, SW_FT_Cos(theta2)); - - /* compute direction of original arc */ - if (stroker->handle_wide_strokes) - alpha0 = SW_FT_Atan2(arc[0].x - arc[3].x, arc[0].y - arc[3].y); - - for (border = stroker->borders, side = 0; side <= 1; - side++, border++) { - rotate = SW_FT_SIDE_TO_ROTATE(side); - - /* compute control points */ - SW_FT_Vector_From_Polar(&ctrl1, length1, phi1 + rotate); - ctrl1.x += arc[2].x; - ctrl1.y += arc[2].y; - - SW_FT_Vector_From_Polar(&ctrl2, length2, phi2 + rotate); - ctrl2.x += arc[1].x; - ctrl2.y += arc[1].y; - - /* compute end point */ - SW_FT_Vector_From_Polar(&end, stroker->radius, - angle_out + rotate); - end.x += arc[0].x; - end.y += arc[0].y; - - if (stroker->handle_wide_strokes) { - SW_FT_Vector start; - SW_FT_Angle alpha1; - - /* determine whether the border radius is greater than the - */ - /* radius of curvature of the original arc */ - start = border->points[border->num_points - 1]; - - alpha1 = SW_FT_Atan2(end.x - start.x, end.y - start.y); - - /* is the direction of the border arc opposite to */ - /* that of the original arc? */ - if (ft_pos_abs(SW_FT_Angle_Diff(alpha0, alpha1)) > - SW_FT_ANGLE_PI / 2) { - SW_FT_Angle beta, gamma; - SW_FT_Vector bvec, delta; - SW_FT_Fixed blen, sinA, sinB, alen; - - /* use the sine rule to find the intersection point */ - beta = - SW_FT_Atan2(arc[3].x - start.x, arc[3].y - start.y); - gamma = SW_FT_Atan2(arc[0].x - end.x, arc[0].y - end.y); - - bvec.x = end.x - start.x; - bvec.y = end.y - start.y; - - blen = SW_FT_Vector_Length(&bvec); - - sinA = ft_pos_abs(SW_FT_Sin(alpha1 - gamma)); - sinB = ft_pos_abs(SW_FT_Sin(beta - gamma)); - - alen = SW_FT_MulDiv(blen, sinA, sinB); - - SW_FT_Vector_From_Polar(&delta, alen, beta); - delta.x += start.x; - delta.y += start.y; - - /* circumnavigate the negative sector backwards */ - border->movable = FALSE; - error = ft_stroke_border_lineto(border, &delta, FALSE); - if (error) goto Exit; - error = ft_stroke_border_lineto(border, &end, FALSE); - if (error) goto Exit; - error = ft_stroke_border_cubicto(border, &ctrl2, &ctrl1, - &start); - if (error) goto Exit; - /* and then move to the endpoint */ - error = ft_stroke_border_lineto(border, &end, FALSE); - if (error) goto Exit; - - continue; - } - - /* else fall through */ - } - - /* simply add an arc */ - error = ft_stroke_border_cubicto(border, &ctrl1, &ctrl2, &end); - if (error) goto Exit; - } - } - - arc -= 3; - - stroker->angle_in = angle_out; - } - - stroker->center = *to; - -Exit: - return error; -} - -/* documentation is in ftstroke.h */ - -SW_FT_Error SW_FT_Stroker_BeginSubPath(SW_FT_Stroker stroker, SW_FT_Vector* to, - SW_FT_Bool open) -{ - /* We cannot process the first point, because there is not enough */ - /* information regarding its corner/cap. The latter will be processed */ - /* in the `SW_FT_Stroker_EndSubPath' routine. */ - /* */ - stroker->first_point = TRUE; - stroker->center = *to; - stroker->subpath_open = open; - - /* Determine if we need to check whether the border radius is greater */ - /* than the radius of curvature of a curve, to handle this case */ - /* specially. This is only required if bevel joins or butt caps may */ - /* be created, because round & miter joins and round & square caps */ - /* cover the negative sector created with wide strokes. */ - stroker->handle_wide_strokes = - SW_FT_BOOL(stroker->line_join != SW_FT_STROKER_LINEJOIN_ROUND || - (stroker->subpath_open && - stroker->line_cap == SW_FT_STROKER_LINECAP_BUTT)); - - /* record the subpath start point for each border */ - stroker->subpath_start = *to; - - stroker->angle_in = 0; - - return 0; -} - -static SW_FT_Error ft_stroker_add_reverse_left(SW_FT_Stroker stroker, - SW_FT_Bool open) -{ - SW_FT_StrokeBorder right = stroker->borders + 0; - SW_FT_StrokeBorder left = stroker->borders + 1; - SW_FT_Int new_points; - SW_FT_Error error = 0; - - assert(left->start >= 0); - - new_points = left->num_points - left->start; - if (new_points > 0) { - error = ft_stroke_border_grow(right, (SW_FT_UInt)new_points); - if (error) goto Exit; - - { - SW_FT_Vector* dst_point = right->points + right->num_points; - SW_FT_Byte* dst_tag = right->tags + right->num_points; - SW_FT_Vector* src_point = left->points + left->num_points - 1; - SW_FT_Byte* src_tag = left->tags + left->num_points - 1; - - while (src_point >= left->points + left->start) { - *dst_point = *src_point; - *dst_tag = *src_tag; - - if (open) - dst_tag[0] &= ~SW_FT_STROKE_TAG_BEGIN_END; - else { - SW_FT_Byte ttag = - (SW_FT_Byte)(dst_tag[0] & SW_FT_STROKE_TAG_BEGIN_END); - - /* switch begin/end tags if necessary */ - if (ttag == SW_FT_STROKE_TAG_BEGIN || - ttag == SW_FT_STROKE_TAG_END) - dst_tag[0] ^= SW_FT_STROKE_TAG_BEGIN_END; - } - - src_point--; - src_tag--; - dst_point++; - dst_tag++; - } - } - - left->num_points = left->start; - right->num_points += new_points; - - right->movable = FALSE; - left->movable = FALSE; - } - -Exit: - return error; -} - -/* documentation is in ftstroke.h */ - -/* there's a lot of magic in this function! */ -SW_FT_Error SW_FT_Stroker_EndSubPath(SW_FT_Stroker stroker) -{ - SW_FT_Error error = 0; - - if (stroker->subpath_open) { - SW_FT_StrokeBorder right = stroker->borders; - - /* All right, this is an opened path, we need to add a cap between */ - /* right & left, add the reverse of left, then add a final cap */ - /* between left & right. */ - error = ft_stroker_cap(stroker, stroker->angle_in, 0); - if (error) goto Exit; - - /* add reversed points from `left' to `right' */ - error = ft_stroker_add_reverse_left(stroker, TRUE); - if (error) goto Exit; - - /* now add the final cap */ - stroker->center = stroker->subpath_start; - error = - ft_stroker_cap(stroker, stroker->subpath_angle + SW_FT_ANGLE_PI, 0); - if (error) goto Exit; - - /* Now end the right subpath accordingly. The left one is */ - /* rewind and doesn't need further processing. */ - ft_stroke_border_close(right, FALSE); - } else { - SW_FT_Angle turn; - SW_FT_Int inside_side; - - /* close the path if needed */ - if (stroker->center.x != stroker->subpath_start.x || - stroker->center.y != stroker->subpath_start.y) { - error = SW_FT_Stroker_LineTo(stroker, &stroker->subpath_start); - if (error) goto Exit; - } - - /* process the corner */ - stroker->angle_out = stroker->subpath_angle; - turn = SW_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); - - /* no specific corner processing is required if the turn is 0 */ - if (turn != 0) { - /* when we turn to the right, the inside side is 0 */ - inside_side = 0; - - /* otherwise, the inside side is 1 */ - if (turn < 0) inside_side = 1; - - error = ft_stroker_inside(stroker, inside_side, - stroker->subpath_line_length); - if (error) goto Exit; - - /* process the outside side */ - error = ft_stroker_outside(stroker, 1 - inside_side, - stroker->subpath_line_length); - if (error) goto Exit; - } - - /* then end our two subpaths */ - ft_stroke_border_close(stroker->borders + 0, FALSE); - ft_stroke_border_close(stroker->borders + 1, TRUE); - } - -Exit: - return error; -} - -/* documentation is in ftstroke.h */ - -SW_FT_Error SW_FT_Stroker_GetBorderCounts(SW_FT_Stroker stroker, - SW_FT_StrokerBorder border, - SW_FT_UInt* anum_points, - SW_FT_UInt* anum_contours) -{ - SW_FT_UInt num_points = 0, num_contours = 0; - SW_FT_Error error; - - if (!stroker || border > 1) { - error = -1; // SW_FT_THROW( Invalid_Argument ); - goto Exit; - } - - error = ft_stroke_border_get_counts(stroker->borders + border, &num_points, - &num_contours); -Exit: - if (anum_points) *anum_points = num_points; - - if (anum_contours) *anum_contours = num_contours; - - return error; -} - -/* documentation is in ftstroke.h */ - -SW_FT_Error SW_FT_Stroker_GetCounts(SW_FT_Stroker stroker, - SW_FT_UInt* anum_points, - SW_FT_UInt* anum_contours) -{ - SW_FT_UInt count1, count2, num_points = 0; - SW_FT_UInt count3, count4, num_contours = 0; - SW_FT_Error error; - - error = ft_stroke_border_get_counts(stroker->borders + 0, &count1, &count2); - if (error) goto Exit; - - error = ft_stroke_border_get_counts(stroker->borders + 1, &count3, &count4); - if (error) goto Exit; - - num_points = count1 + count3; - num_contours = count2 + count4; - -Exit: - *anum_points = num_points; - *anum_contours = num_contours; - return error; -} - -/* documentation is in ftstroke.h */ - -void SW_FT_Stroker_ExportBorder(SW_FT_Stroker stroker, - SW_FT_StrokerBorder border, - SW_FT_Outline* outline) -{ - if (border == SW_FT_STROKER_BORDER_LEFT || - border == SW_FT_STROKER_BORDER_RIGHT) { - SW_FT_StrokeBorder sborder = &stroker->borders[border]; - - if (sborder->valid) ft_stroke_border_export(sborder, outline); - } -} - -/* documentation is in ftstroke.h */ - -void SW_FT_Stroker_Export(SW_FT_Stroker stroker, SW_FT_Outline* outline) -{ - SW_FT_Stroker_ExportBorder(stroker, SW_FT_STROKER_BORDER_LEFT, outline); - SW_FT_Stroker_ExportBorder(stroker, SW_FT_STROKER_BORDER_RIGHT, outline); -} - -/* documentation is in ftstroke.h */ - -/* - * The following is very similar to SW_FT_Outline_Decompose, except - * that we do support opened paths, and do not scale the outline. - */ -SW_FT_Error SW_FT_Stroker_ParseOutline(SW_FT_Stroker stroker, - const SW_FT_Outline* outline) -{ - SW_FT_Vector v_last; - SW_FT_Vector v_control; - SW_FT_Vector v_start; - - SW_FT_Vector* point; - SW_FT_Vector* limit; - char* tags; - - SW_FT_Error error; - - SW_FT_Int n; /* index of contour in outline */ - SW_FT_UInt first; /* index of first point in contour */ - SW_FT_Int tag; /* current point's state */ - - if (!outline || !stroker) return -1; // SW_FT_THROW( Invalid_Argument ); - - SW_FT_Stroker_Rewind(stroker); - - first = 0; - - for (n = 0; n < outline->n_contours; n++) { - SW_FT_UInt last; /* index of last point in contour */ - - last = outline->contours[n]; - limit = outline->points + last; - - /* skip empty points; we don't stroke these */ - if (last <= first) { - first = last + 1; - continue; - } - - v_start = outline->points[first]; - v_last = outline->points[last]; - - v_control = v_start; - - point = outline->points + first; - tags = outline->tags + first; - tag = SW_FT_CURVE_TAG(tags[0]); - - /* A contour cannot start with a cubic control point! */ - if (tag == SW_FT_CURVE_TAG_CUBIC) goto Invalid_Outline; - - /* check first point to determine origin */ - if (tag == SW_FT_CURVE_TAG_CONIC) { - /* First point is conic control. Yes, this happens. */ - if (SW_FT_CURVE_TAG(outline->tags[last]) == SW_FT_CURVE_TAG_ON) { - /* start at last point if it is on the curve */ - v_start = v_last; - limit--; - } else { - /* if both first and last points are conic, */ - /* start at their middle */ - v_start.x = (v_start.x + v_last.x) / 2; - v_start.y = (v_start.y + v_last.y) / 2; - } - point--; - tags--; - } - - error = SW_FT_Stroker_BeginSubPath(stroker, &v_start, outline->contours_flag[n]); - if (error) goto Exit; - - while (point < limit) { - point++; - tags++; - - tag = SW_FT_CURVE_TAG(tags[0]); - switch (tag) { - case SW_FT_CURVE_TAG_ON: /* emit a single line_to */ - { - SW_FT_Vector vec; - - vec.x = point->x; - vec.y = point->y; - - error = SW_FT_Stroker_LineTo(stroker, &vec); - if (error) goto Exit; - continue; - } - - case SW_FT_CURVE_TAG_CONIC: /* consume conic arcs */ - v_control.x = point->x; - v_control.y = point->y; - - Do_Conic: - if (point < limit) { - SW_FT_Vector vec; - SW_FT_Vector v_middle; - - point++; - tags++; - tag = SW_FT_CURVE_TAG(tags[0]); - - vec = point[0]; - - if (tag == SW_FT_CURVE_TAG_ON) { - error = - SW_FT_Stroker_ConicTo(stroker, &v_control, &vec); - if (error) goto Exit; - continue; - } - - if (tag != SW_FT_CURVE_TAG_CONIC) goto Invalid_Outline; - - v_middle.x = (v_control.x + vec.x) / 2; - v_middle.y = (v_control.y + vec.y) / 2; - - error = - SW_FT_Stroker_ConicTo(stroker, &v_control, &v_middle); - if (error) goto Exit; - - v_control = vec; - goto Do_Conic; - } - - error = SW_FT_Stroker_ConicTo(stroker, &v_control, &v_start); - goto Close; - - default: /* SW_FT_CURVE_TAG_CUBIC */ - { - SW_FT_Vector vec1, vec2; - - if (point + 1 > limit || - SW_FT_CURVE_TAG(tags[1]) != SW_FT_CURVE_TAG_CUBIC) - goto Invalid_Outline; - - point += 2; - tags += 2; - - vec1 = point[-2]; - vec2 = point[-1]; - - if (point <= limit) { - SW_FT_Vector vec; - - vec = point[0]; - - error = SW_FT_Stroker_CubicTo(stroker, &vec1, &vec2, &vec); - if (error) goto Exit; - continue; - } - - error = SW_FT_Stroker_CubicTo(stroker, &vec1, &vec2, &v_start); - goto Close; - } - } - } - - Close: - if (error) goto Exit; - - /* don't try to end the path if no segments have been generated */ - if (!stroker->first_point) { - error = SW_FT_Stroker_EndSubPath(stroker); - if (error) goto Exit; - } - - first = last + 1; - } - - return 0; - -Exit: - return error; - -Invalid_Outline: - return -2; // SW_FT_THROW( Invalid_Outline ); -} - -/* END */ diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_stroker.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_stroker.h deleted file mode 100644 index 1514cf3d0..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_stroker.h +++ /dev/null @@ -1,319 +0,0 @@ -#ifndef V_FT_STROKER_H -#define V_FT_STROKER_H -/***************************************************************************/ -/* */ -/* ftstroke.h */ -/* */ -/* FreeType path stroker (specification). */ -/* */ -/* Copyright 2002-2006, 2008, 2009, 2011-2012 by */ -/* David Turner, Robert Wilhelm, and Werner Lemberg. */ -/* */ -/* This file is part of the FreeType project, and may only be used, */ -/* modified, and distributed under the terms of the FreeType project */ -/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ -/* this file you indicate that you have read the license and */ -/* understand and accept it fully. */ -/* */ -/***************************************************************************/ - -#include "v_ft_raster.h" - - /************************************************************** - * - * @type: - * SW_FT_Stroker - * - * @description: - * Opaque handler to a path stroker object. - */ - typedef struct SW_FT_StrokerRec_* SW_FT_Stroker; - - - /************************************************************** - * - * @enum: - * SW_FT_Stroker_LineJoin - * - * @description: - * These values determine how two joining lines are rendered - * in a stroker. - * - * @values: - * SW_FT_STROKER_LINEJOIN_ROUND :: - * Used to render rounded line joins. Circular arcs are used - * to join two lines smoothly. - * - * SW_FT_STROKER_LINEJOIN_BEVEL :: - * Used to render beveled line joins. The outer corner of - * the joined lines is filled by enclosing the triangular - * region of the corner with a straight line between the - * outer corners of each stroke. - * - * SW_FT_STROKER_LINEJOIN_MITER_FIXED :: - * Used to render mitered line joins, with fixed bevels if the - * miter limit is exceeded. The outer edges of the strokes - * for the two segments are extended until they meet at an - * angle. If the segments meet at too sharp an angle (such - * that the miter would extend from the intersection of the - * segments a distance greater than the product of the miter - * limit value and the border radius), then a bevel join (see - * above) is used instead. This prevents long spikes being - * created. SW_FT_STROKER_LINEJOIN_MITER_FIXED generates a miter - * line join as used in PostScript and PDF. - * - * SW_FT_STROKER_LINEJOIN_MITER_VARIABLE :: - * SW_FT_STROKER_LINEJOIN_MITER :: - * Used to render mitered line joins, with variable bevels if - * the miter limit is exceeded. The intersection of the - * strokes is clipped at a line perpendicular to the bisector - * of the angle between the strokes, at the distance from the - * intersection of the segments equal to the product of the - * miter limit value and the border radius. This prevents - * long spikes being created. - * SW_FT_STROKER_LINEJOIN_MITER_VARIABLE generates a mitered line - * join as used in XPS. SW_FT_STROKER_LINEJOIN_MITER is an alias - * for SW_FT_STROKER_LINEJOIN_MITER_VARIABLE, retained for - * backwards compatibility. - */ - typedef enum SW_FT_Stroker_LineJoin_ - { - SW_FT_STROKER_LINEJOIN_ROUND = 0, - SW_FT_STROKER_LINEJOIN_BEVEL = 1, - SW_FT_STROKER_LINEJOIN_MITER_VARIABLE = 2, - SW_FT_STROKER_LINEJOIN_MITER = SW_FT_STROKER_LINEJOIN_MITER_VARIABLE, - SW_FT_STROKER_LINEJOIN_MITER_FIXED = 3 - - } SW_FT_Stroker_LineJoin; - - - /************************************************************** - * - * @enum: - * SW_FT_Stroker_LineCap - * - * @description: - * These values determine how the end of opened sub-paths are - * rendered in a stroke. - * - * @values: - * SW_FT_STROKER_LINECAP_BUTT :: - * The end of lines is rendered as a full stop on the last - * point itself. - * - * SW_FT_STROKER_LINECAP_ROUND :: - * The end of lines is rendered as a half-circle around the - * last point. - * - * SW_FT_STROKER_LINECAP_SQUARE :: - * The end of lines is rendered as a square around the - * last point. - */ - typedef enum SW_FT_Stroker_LineCap_ - { - SW_FT_STROKER_LINECAP_BUTT = 0, - SW_FT_STROKER_LINECAP_ROUND, - SW_FT_STROKER_LINECAP_SQUARE - - } SW_FT_Stroker_LineCap; - - - /************************************************************** - * - * @enum: - * SW_FT_StrokerBorder - * - * @description: - * These values are used to select a given stroke border - * in @SW_FT_Stroker_GetBorderCounts and @SW_FT_Stroker_ExportBorder. - * - * @values: - * SW_FT_STROKER_BORDER_LEFT :: - * Select the left border, relative to the drawing direction. - * - * SW_FT_STROKER_BORDER_RIGHT :: - * Select the right border, relative to the drawing direction. - * - * @note: - * Applications are generally interested in the `inside' and `outside' - * borders. However, there is no direct mapping between these and the - * `left' and `right' ones, since this really depends on the glyph's - * drawing orientation, which varies between font formats. - * - * You can however use @SW_FT_Outline_GetInsideBorder and - * @SW_FT_Outline_GetOutsideBorder to get these. - */ - typedef enum SW_FT_StrokerBorder_ - { - SW_FT_STROKER_BORDER_LEFT = 0, - SW_FT_STROKER_BORDER_RIGHT - - } SW_FT_StrokerBorder; - - - /************************************************************** - * - * @function: - * SW_FT_Stroker_New - * - * @description: - * Create a new stroker object. - * - * @input: - * library :: - * FreeType library handle. - * - * @output: - * astroker :: - * A new stroker object handle. NULL in case of error. - * - * @return: - * FreeType error code. 0~means success. - */ - SW_FT_Error - SW_FT_Stroker_New( SW_FT_Stroker *astroker ); - - - /************************************************************** - * - * @function: - * SW_FT_Stroker_Set - * - * @description: - * Reset a stroker object's attributes. - * - * @input: - * stroker :: - * The target stroker handle. - * - * radius :: - * The border radius. - * - * line_cap :: - * The line cap style. - * - * line_join :: - * The line join style. - * - * miter_limit :: - * The miter limit for the SW_FT_STROKER_LINEJOIN_MITER_FIXED and - * SW_FT_STROKER_LINEJOIN_MITER_VARIABLE line join styles, - * expressed as 16.16 fixed-point value. - * - * @note: - * The radius is expressed in the same units as the outline - * coordinates. - */ - void - SW_FT_Stroker_Set( SW_FT_Stroker stroker, - SW_FT_Fixed radius, - SW_FT_Stroker_LineCap line_cap, - SW_FT_Stroker_LineJoin line_join, - SW_FT_Fixed miter_limit ); - - /************************************************************** - * - * @function: - * SW_FT_Stroker_ParseOutline - * - * @description: - * A convenience function used to parse a whole outline with - * the stroker. The resulting outline(s) can be retrieved - * later by functions like @SW_FT_Stroker_GetCounts and @SW_FT_Stroker_Export. - * - * @input: - * stroker :: - * The target stroker handle. - * - * outline :: - * The source outline. - * - * - * @return: - * FreeType error code. 0~means success. - * - * @note: - * If `opened' is~0 (the default), the outline is treated as a closed - * path, and the stroker generates two distinct `border' outlines. - * - * - * This function calls @SW_FT_Stroker_Rewind automatically. - */ - SW_FT_Error - SW_FT_Stroker_ParseOutline( SW_FT_Stroker stroker, - const SW_FT_Outline* outline); - - - /************************************************************** - * - * @function: - * SW_FT_Stroker_GetCounts - * - * @description: - * Call this function once you have finished parsing your paths - * with the stroker. It returns the number of points and - * contours necessary to export all points/borders from the stroked - * outline/path. - * - * @input: - * stroker :: - * The target stroker handle. - * - * @output: - * anum_points :: - * The number of points. - * - * anum_contours :: - * The number of contours. - * - * @return: - * FreeType error code. 0~means success. - */ - SW_FT_Error - SW_FT_Stroker_GetCounts( SW_FT_Stroker stroker, - SW_FT_UInt *anum_points, - SW_FT_UInt *anum_contours ); - - - /************************************************************** - * - * @function: - * SW_FT_Stroker_Export - * - * @description: - * Call this function after @SW_FT_Stroker_GetBorderCounts to - * export all borders to your own @SW_FT_Outline structure. - * - * Note that this function appends the border points and - * contours to your outline, but does not try to resize its - * arrays. - * - * @input: - * stroker :: - * The target stroker handle. - * - * outline :: - * The target outline handle. - */ - void - SW_FT_Stroker_Export( SW_FT_Stroker stroker, - SW_FT_Outline* outline ); - - - /************************************************************** - * - * @function: - * SW_FT_Stroker_Done - * - * @description: - * Destroy a stroker object. - * - * @input: - * stroker :: - * A stroker handle. Can be NULL. - */ - void - SW_FT_Stroker_Done( SW_FT_Stroker stroker ); - - -#endif // V_FT_STROKER_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_types.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_types.h deleted file mode 100644 index a01c4f287..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_types.h +++ /dev/null @@ -1,160 +0,0 @@ -#ifndef V_FT_TYPES_H -#define V_FT_TYPES_H - -/*************************************************************************/ -/* */ -/* */ -/* SW_FT_Fixed */ -/* */ -/* */ -/* This type is used to store 16.16 fixed-point values, like scaling */ -/* values or matrix coefficients. */ -/* */ -typedef signed long SW_FT_Fixed; - - -/*************************************************************************/ -/* */ -/* */ -/* SW_FT_Int */ -/* */ -/* */ -/* A typedef for the int type. */ -/* */ -typedef signed int SW_FT_Int; - - -/*************************************************************************/ -/* */ -/* */ -/* SW_FT_UInt */ -/* */ -/* */ -/* A typedef for the unsigned int type. */ -/* */ -typedef unsigned int SW_FT_UInt; - - -/*************************************************************************/ -/* */ -/* */ -/* SW_FT_Long */ -/* */ -/* */ -/* A typedef for signed long. */ -/* */ -typedef signed long SW_FT_Long; - - -/*************************************************************************/ -/* */ -/* */ -/* SW_FT_ULong */ -/* */ -/* */ -/* A typedef for unsigned long. */ -/* */ -typedef unsigned long SW_FT_ULong; - -/*************************************************************************/ -/* */ -/* */ -/* SW_FT_Short */ -/* */ -/* */ -/* A typedef for signed short. */ -/* */ -typedef signed short SW_FT_Short; - - -/*************************************************************************/ -/* */ -/* */ -/* SW_FT_Byte */ -/* */ -/* */ -/* A simple typedef for the _unsigned_ char type. */ -/* */ -typedef unsigned char SW_FT_Byte; - - -/*************************************************************************/ -/* */ -/* */ -/* SW_FT_Bool */ -/* */ -/* */ -/* A typedef of unsigned char, used for simple booleans. As usual, */ -/* values 1 and~0 represent true and false, respectively. */ -/* */ -typedef unsigned char SW_FT_Bool; - - - -/*************************************************************************/ -/* */ -/* */ -/* SW_FT_Error */ -/* */ -/* */ -/* The FreeType error code type. A value of~0 is always interpreted */ -/* as a successful operation. */ -/* */ -typedef int SW_FT_Error; - - -/*************************************************************************/ -/* */ -/* */ -/* SW_FT_Pos */ -/* */ -/* */ -/* The type SW_FT_Pos is used to store vectorial coordinates. Depending */ -/* on the context, these can represent distances in integer font */ -/* units, or 16.16, or 26.6 fixed-point pixel coordinates. */ -/* */ -typedef signed long SW_FT_Pos; - - -/*************************************************************************/ -/* */ -/* */ -/* SW_FT_Vector */ -/* */ -/* */ -/* A simple structure used to store a 2D vector; coordinates are of */ -/* the SW_FT_Pos type. */ -/* */ -/* */ -/* x :: The horizontal coordinate. */ -/* y :: The vertical coordinate. */ -/* */ -typedef struct SW_FT_Vector_ -{ - SW_FT_Pos x; - SW_FT_Pos y; - -} SW_FT_Vector; - - -typedef long long int SW_FT_Int64; -typedef unsigned long long int SW_FT_UInt64; - -typedef signed int SW_FT_Int32; -typedef unsigned int SW_FT_UInt32; - - -#define SW_FT_BOOL( x ) ( (SW_FT_Bool)( x ) ) - -#define SW_FT_SIZEOF_LONG 4 - -#ifndef TRUE -#define TRUE 1 -#endif - -#ifndef FALSE -#define FALSE 0 -#endif - - -#endif // V_FT_TYPES_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/stb/stb_image.cpp b/libfenrir/src/main/jni/animation/rlottie/src/vector/stb/stb_image.cpp deleted file mode 100644 index 46f69e456..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/stb/stb_image.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * configure stb_image about - * the image we will support - */ -#define STB_IMAGE_IMPLEMENTATION - -#define STBI_ONLY_JPEG -#define STBI_ONLY_PNG -#define STBI_ONLY_BMP -#define STBI_ONLY_HDR -#define STBI_NO_PSD -#define STBI_ONLY_GIF -#define STBI_ONLY_PIC -#define STBI_ONLY_TGA -#define STBI_ONLY_ZLIB -#define STBI_ONLY_PNM - -#include "stb_image.h" - -#define RLOTTIE_API - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * exported function wrapper from the library - */ - -RLOTTIE_API unsigned char *lottie_image_load(char const *filename, int *x, - int *y, int *comp, int req_comp) -{ - return stbi_load(filename, x, y, comp, req_comp); -} - -RLOTTIE_API unsigned char *lottie_image_load_from_data(const char *imageData, - int len, int *x, int *y, - int *comp, int req_comp) -{ - auto *data = (unsigned char *)imageData; - return stbi_load_from_memory(data, len, x, y, comp, req_comp); -} - -RLOTTIE_API void lottie_image_free(unsigned char *data) -{ - stbi_image_free(data); -} - -#ifdef __cplusplus -} -#endif diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/stb/stb_image.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/stb/stb_image.h deleted file mode 100644 index 1fd772dd1..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/stb/stb_image.h +++ /dev/null @@ -1,7517 +0,0 @@ -/* stb_image - v2.19 - public domain image loader - http://nothings.org/stb - no warranty implied; use at your own risk - - Do this: - #define STB_IMAGE_IMPLEMENTATION - before you include this file in *one* C or C++ file to create the implementation. - - // i.e. it should look like this: - #include ... - #include ... - #include ... - #define STB_IMAGE_IMPLEMENTATION - #include "stb_image.h" - - You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. - And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free - - - QUICK NOTES: - Primarily of interest to game developers and other people who can - avoid problematic images and only need the trivial interface - - JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) - PNG 1/2/4/8/16-bit-per-channel - - TGA (not sure what subset, if a subset) - BMP non-1bpp, non-RLE - PSD (composited view only, no extra channels, 8/16 bit-per-channel) - - GIF (*comp always reports as 4-channel) - HDR (radiance rgbE format) - PIC (Softimage PIC) - PNM (PPM and PGM binary only) - - Animated GIF still needs a proper API, but here's one way to do it: - http://gist.github.com/urraka/685d9a6340b26b830d49 - - - decode from memory or through FILE (define STBI_NO_STDIO to remove code) - - decode from arbitrary I/O callbacks - - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) - - Full documentation under "DOCUMENTATION" below. - - -LICENSE - - See end of file for license information. - -RECENT REVISION HISTORY: - - 2.19 (2018-02-11) fix warning - 2.18 (2018-01-30) fix warnings - 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings - 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes - 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC - 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs - 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes - 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes - 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 - RGB-format JPEG; remove white matting in PSD; - allocate large structures on the stack; - correct channel count for PNG & BMP - 2.10 (2016-01-22) avoid warning introduced in 2.09 - 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED - - See end of file for full revision history. - - - ============================ Contributors ========================= - - Image formats Extensions, features - Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) - Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) - Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) - Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) - Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) - Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) - Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) - github:urraka (animated gif) Junggon Kim (PNM comments) - Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) - socks-the-fox (16-bit PNG) - Jeremy Sawicki (handle all ImageNet JPGs) - Optimizations & bugfixes Mikhail Morozov (1-bit BMP) - Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) - Arseny Kapoulkine - John-Mark Allen - - Bug & warning fixes - Marc LeBlanc David Woo Guillaume George Martins Mozeiko - Christpher Lloyd Jerry Jansson Joseph Thomson Phil Jordan - Dave Moore Roy Eltham Hayaki Saito Nathan Reed - Won Chun Luke Graham Johan Duparc Nick Verigakis - the Horde3D community Thomas Ruf Ronny Chevalier github:rlyeh - Janez Zemva John Bartholomew Michal Cichon github:romigrou - Jonathan Blow Ken Hamada Tero Hanninen github:svdijk - Laurent Gomila Cort Stratton Sergio Gonzalez github:snagar - Aruelien Pocheville Thibault Reuille Cass Everitt github:Zelex - Ryamond Barbiero Paul Du Bois Engin Manap github:grim210 - Aldo Culquicondor Philipp Wiesemann Dale Weiler github:sammyhw - Oriol Ferrer Mesia Josh Tobin Matthew Gregan github:phprus - Julian Raschke Gregory Mullen Baldur Karlsson github:poppolopoppo - Christian Floisand Kevin Schmidt github:darealshinji - Blazej Dariusz Roszkowski github:Michaelangel007 -*/ - -#ifndef STBI_INCLUDE_STB_IMAGE_H -#define STBI_INCLUDE_STB_IMAGE_H - -// DOCUMENTATION -// -// Limitations: -// - no 12-bit-per-channel JPEG -// - no JPEGs with arithmetic coding -// - GIF always returns *comp=4 -// -// Basic usage (see HDR discussion below for HDR usage): -// int x,y,n; -// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); -// // ... process data if not NULL ... -// // ... x = width, y = height, n = # 8-bit components per pixel ... -// // ... replace '0' with '1'..'4' to force that many components per pixel -// // ... but 'n' will always be the number that it would have been if you said 0 -// stbi_image_free(data) -// -// Standard parameters: -// int *x -- outputs image width in pixels -// int *y -- outputs image height in pixels -// int *channels_in_file -- outputs # of image components in image file -// int desired_channels -- if non-zero, # of image components requested in result -// -// The return value from an image loader is an 'unsigned char *' which points -// to the pixel data, or NULL on an allocation failure or if the image is -// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, -// with each pixel consisting of N interleaved 8-bit components; the first -// pixel pointed to is top-left-most in the image. There is no padding between -// image scanlines or between pixels, regardless of format. The number of -// components N is 'desired_channels' if desired_channels is non-zero, or -// *channels_in_file otherwise. If desired_channels is non-zero, -// *channels_in_file has the number of components that _would_ have been -// output otherwise. E.g. if you set desired_channels to 4, you will always -// get RGBA output, but you can check *channels_in_file to see if it's trivially -// opaque because e.g. there were only 3 channels in the source image. -// -// An output image with N components has the following components interleaved -// in this order in each pixel: -// -// N=#comp components -// 1 grey -// 2 grey, alpha -// 3 red, green, blue -// 4 red, green, blue, alpha -// -// If image loading fails for any reason, the return value will be NULL, -// and *x, *y, *channels_in_file will be unchanged. The function -// stbi_failure_reason() can be queried for an extremely brief, end-user -// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS -// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly -// more user-friendly ones. -// -// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. -// -// =========================================================================== -// -// Philosophy -// -// stb libraries are designed with the following priorities: -// -// 1. easy to use -// 2. easy to maintain -// 3. good performance -// -// Sometimes I let "good performance" creep up in priority over "easy to maintain", -// and for best performance I may provide less-easy-to-use APIs that give higher -// performance, in addition to the easy to use ones. Nevertheless, it's important -// to keep in mind that from the standpoint of you, a client of this library, -// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. -// -// Some secondary priorities arise directly from the first two, some of which -// make more explicit reasons why performance can't be emphasized. -// -// - Portable ("ease of use") -// - Small source code footprint ("easy to maintain") -// - No dependencies ("ease of use") -// -// =========================================================================== -// -// I/O callbacks -// -// I/O callbacks allow you to read from arbitrary sources, like packaged -// files or some other source. Data read from callbacks are processed -// through a small internal buffer (currently 128 bytes) to try to reduce -// overhead. -// -// The three functions you must define are "read" (reads some bytes of data), -// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). -// -// =========================================================================== -// -// SIMD support -// -// The JPEG decoder will try to automatically use SIMD kernels on x86 when -// supported by the compiler. For ARM Neon support, you must explicitly -// request it. -// -// (The old do-it-yourself SIMD API is no longer supported in the current -// code.) -// -// On x86, SSE2 will automatically be used when available based on a run-time -// test; if not, the generic C versions are used as a fall-back. On ARM targets, -// the typical path is to have separate builds for NEON and non-NEON devices -// (at least this is true for iOS and Android). Therefore, the NEON support is -// toggled by a build flag: define STBI_NEON to get NEON loops. -// -// If for some reason you do not want to use any of SIMD code, or if -// you have issues compiling it, you can disable it entirely by -// defining STBI_NO_SIMD. -// -// =========================================================================== -// -// HDR image support (disable by defining STBI_NO_HDR) -// -// stb_image now supports loading HDR images in general, and currently -// the Radiance .HDR file format, although the support is provided -// generically. You can still load any file through the existing interface; -// if you attempt to load an HDR file, it will be automatically remapped to -// LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; -// both of these constants can be reconfigured through this interface: -// -// stbi_hdr_to_ldr_gamma(2.2f); -// stbi_hdr_to_ldr_scale(1.0f); -// -// (note, do not use _inverse_ constants; stbi_image will invert them -// appropriately). -// -// Additionally, there is a new, parallel interface for loading files as -// (linear) floats to preserve the full dynamic range: -// -// float *data = stbi_loadf(filename, &x, &y, &n, 0); -// -// If you load LDR images through this interface, those images will -// be promoted to floating point values, run through the inverse of -// constants corresponding to the above: -// -// stbi_ldr_to_hdr_scale(1.0f); -// stbi_ldr_to_hdr_gamma(2.2f); -// -// Finally, given a filename (or an open file or memory block--see header -// file for details) containing image data, you can query for the "most -// appropriate" interface to use (that is, whether the image is HDR or -// not), using: -// -// stbi_is_hdr(char *filename); -// -// =========================================================================== -// -// iPhone PNG support: -// -// By default we convert iphone-formatted PNGs back to RGB, even though -// they are internally encoded differently. You can disable this conversion -// by by calling stbi_convert_iphone_png_to_rgb(0), in which case -// you will always just get the native iphone "format" through (which -// is BGR stored in RGB). -// -// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per -// pixel to remove any premultiplied alpha *only* if the image file explicitly -// says there's premultiplied data (currently only happens in iPhone images, -// and only if iPhone convert-to-rgb processing is on). -// -// =========================================================================== -// -// ADDITIONAL CONFIGURATION -// -// - You can suppress implementation of any of the decoders to reduce -// your code footprint by #defining one or more of the following -// symbols before creating the implementation. -// -// STBI_NO_JPEG -// STBI_NO_PNG -// STBI_NO_BMP -// STBI_NO_PSD -// STBI_NO_TGA -// STBI_NO_GIF -// STBI_NO_HDR -// STBI_NO_PIC -// STBI_NO_PNM (.ppm and .pgm) -// -// - You can request *only* certain decoders and suppress all other ones -// (this will be more forward-compatible, as addition of new decoders -// doesn't require you to disable them explicitly): -// -// STBI_ONLY_JPEG -// STBI_ONLY_PNG -// STBI_ONLY_BMP -// STBI_ONLY_PSD -// STBI_ONLY_TGA -// STBI_ONLY_GIF -// STBI_ONLY_HDR -// STBI_ONLY_PIC -// STBI_ONLY_PNM (.ppm and .pgm) -// -// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still -// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB -// - - -#ifndef STBI_NO_STDIO -#include -#endif // STBI_NO_STDIO - -#if defined _WIN32 || defined __CYGWIN__ -#include -#endif // defined _WIN32 || defined __CYGWIN__ - -#define STBI_VERSION 1 - -enum -{ - STBI_default = 0, // only used for desired_channels - - STBI_grey = 1, - STBI_grey_alpha = 2, - STBI_rgb = 3, - STBI_rgb_alpha = 4 -}; - -typedef unsigned char stbi_uc; -typedef unsigned short stbi_us; - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef STB_IMAGE_STATIC -#define STBIDEF static -#else -#define STBIDEF extern -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// PRIMARY API - works on images of any type -// - -// -// load image by filename, open file, or memory buffer -// - -typedef struct -{ - int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read - void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative - int (*eof) (void *user); // returns nonzero if we are at end of file/data -} stbi_io_callbacks; - -//////////////////////////////////// -// -// 8-bits-per-channel interface -// - -STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); -#ifndef STBI_NO_GIF -STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); -#endif - - -#ifndef STBI_NO_STDIO -STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); -// for stbi_load_from_file, file pointer is left pointing immediately after image -#endif - -//////////////////////////////////// -// -// 16-bits-per-channel interface -// - -STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); - -#ifndef STBI_NO_STDIO -STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); -#endif - -//////////////////////////////////// -// -// float-per-channel interface -// -#ifndef STBI_NO_LINEAR - STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); - STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); - - #ifndef STBI_NO_STDIO - STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); - STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); - #endif -#endif - -#ifndef STBI_NO_HDR - STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); - STBIDEF void stbi_hdr_to_ldr_scale(float scale); -#endif // STBI_NO_HDR - -#ifndef STBI_NO_LINEAR - STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); - STBIDEF void stbi_ldr_to_hdr_scale(float scale); -#endif // STBI_NO_LINEAR - -// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR -STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); -STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); -#ifndef STBI_NO_STDIO -STBIDEF int stbi_is_hdr (char const *filename); -STBIDEF int stbi_is_hdr_from_file(FILE *f); -#endif // STBI_NO_STDIO - - -// get a VERY brief reason for failure -// NOT THREADSAFE -STBIDEF const char *stbi_failure_reason (void); - -// free the loaded image -- this is just free() -STBIDEF void stbi_image_free (void *retval_from_stbi_load); - -// get image dimensions & components without fully decoding -STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); -STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); -STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); -STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); - -#ifndef STBI_NO_STDIO -STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); -STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); -STBIDEF int stbi_is_16_bit (char const *filename); -STBIDEF int stbi_is_16_bit_from_file(FILE *f); -#endif - - - -// for image formats that explicitly notate that they have premultiplied alpha, -// we just return the colors as stored in the file. set this flag to force -// unpremultiplication. results are undefined if the unpremultiply overflow. -STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); - -// indicate whether we should process iphone images back to canonical format, -// or just pass them through "as-is" -STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); - -// flip the image vertically, so the first pixel in the output array is the bottom left -STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); - -// ZLIB client - used by PNG, available for other purposes - -STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); -STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); -STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); -STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); - -STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); -STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); - - -#ifdef __cplusplus -} -#endif - -// -// -//// end header file ///////////////////////////////////////////////////// -#endif // STBI_INCLUDE_STB_IMAGE_H - -#ifdef STB_IMAGE_IMPLEMENTATION - -#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ - || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ - || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ - || defined(STBI_ONLY_ZLIB) - #ifndef STBI_ONLY_JPEG - #define STBI_NO_JPEG - #endif - #ifndef STBI_ONLY_PNG - #define STBI_NO_PNG - #endif - #ifndef STBI_ONLY_BMP - #define STBI_NO_BMP - #endif - #ifndef STBI_ONLY_PSD - #define STBI_NO_PSD - #endif - #ifndef STBI_ONLY_TGA - #define STBI_NO_TGA - #endif - #ifndef STBI_ONLY_GIF - #define STBI_NO_GIF - #endif - #ifndef STBI_ONLY_HDR - #define STBI_NO_HDR - #endif - #ifndef STBI_ONLY_PIC - #define STBI_NO_PIC - #endif - #ifndef STBI_ONLY_PNM - #define STBI_NO_PNM - #endif -#endif - -#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) -#define STBI_NO_ZLIB -#endif - - -#include -#include // ptrdiff_t on osx -#include -#include -#include - -#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) -#include // ldexp, pow -#endif - -#ifndef STBI_NO_STDIO -#include -#endif - -#ifndef STBI_ASSERT -#include -#define STBI_ASSERT(x) assert(x) -#endif - - -#ifndef _MSC_VER - #ifdef __cplusplus - #define stbi_inline inline - #else - #define stbi_inline - #endif -#else - #define stbi_inline __forceinline -#endif - - -#ifdef _MSC_VER -typedef unsigned short stbi__uint16; -typedef signed short stbi__int16; -typedef unsigned int stbi__uint32; -typedef signed int stbi__int32; -#else -#include -typedef uint16_t stbi__uint16; -typedef int16_t stbi__int16; -typedef uint32_t stbi__uint32; -typedef int32_t stbi__int32; -#endif - -// should produce compiler error if size is wrong -typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; - -#ifdef _MSC_VER -#define STBI_NOTUSED(v) (void)(v) -#else -#define STBI_NOTUSED(v) (void)sizeof(v) -#endif - -#ifdef _MSC_VER -#define STBI_HAS_LROTL -#endif - -#ifdef STBI_HAS_LROTL - #define stbi_lrot(x,y) _lrotl(x,y) -#else - #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) -#endif - -#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) -// ok -#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) -// ok -#else -#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." -#endif - -#ifndef STBI_MALLOC -#define STBI_MALLOC(sz) malloc(sz) -#define STBI_REALLOC(p,newsz) realloc(p,newsz) -#define STBI_FREE(p) free(p) -#endif - -#ifndef STBI_REALLOC_SIZED -#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) -#endif - -// x86/x64 detection -#if defined(__x86_64__) || defined(_M_X64) -#define STBI__X64_TARGET -#elif defined(__i386) || defined(_M_IX86) -#define STBI__X86_TARGET -#endif - -#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) -// gcc doesn't support sse2 intrinsics unless you compile with -msse2, -// which in turn means it gets to use SSE2 everywhere. This is unfortunate, -// but previous attempts to provide the SSE2 functions with runtime -// detection caused numerous issues. The way architecture extensions are -// exposed in GCC/Clang is, sadly, not really suited for one-file libs. -// New behavior: if compiled with -msse2, we use SSE2 without any -// detection; if not, we don't use it at all. -#define STBI_NO_SIMD -#endif - -#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) -// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET -// -// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the -// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. -// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not -// simultaneously enabling "-mstackrealign". -// -// See https://github.com/nothings/stb/issues/81 for more information. -// -// So default to no SSE2 on 32-bit MinGW. If you've read this far and added -// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. -#define STBI_NO_SIMD -#endif - -#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) -#define STBI_SSE2 -#include - -#ifdef _MSC_VER - -#if _MSC_VER >= 1400 // not VC6 -#include // __cpuid -static int stbi__cpuid3(void) -{ - int info[4]; - __cpuid(info,1); - return info[3]; -} -#else -static int stbi__cpuid3(void) -{ - int res; - __asm { - mov eax,1 - cpuid - mov res,edx - } - return res; -} -#endif - -#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name - -static int stbi__sse2_available(void) -{ - int info3 = stbi__cpuid3(); - return ((info3 >> 26) & 1) != 0; -} -#else // assume GCC-style if not VC++ -#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) - -static int stbi__sse2_available(void) -{ - // If we're even attempting to compile this on GCC/Clang, that means - // -msse2 is on, which means the compiler is allowed to use SSE2 - // instructions at will, and so are we. - return 1; -} -#endif -#endif - -// ARM NEON -#if defined(STBI_NO_SIMD) && defined(STBI_NEON) -#undef STBI_NEON -#endif - -#ifdef STBI_NEON -#include -// assume GCC or Clang on ARM targets -#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) -#endif - -#ifndef STBI_SIMD_ALIGN -#define STBI_SIMD_ALIGN(type, name) type name -#endif - -/////////////////////////////////////////////// -// -// stbi__context struct and start_xxx functions - -// stbi__context structure is our basic context used by all images, so it -// contains all the IO context, plus some basic image information -typedef struct -{ - stbi__uint32 img_x, img_y; - int img_n, img_out_n; - - stbi_io_callbacks io; - void *io_user_data; - - int read_from_callbacks; - int buflen; - stbi_uc buffer_start[128]; - - stbi_uc *img_buffer, *img_buffer_end; - stbi_uc *img_buffer_original, *img_buffer_original_end; -} stbi__context; - - -static void stbi__refill_buffer(stbi__context *s); - -// initialize a memory-decode context -static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) -{ - s->io.read = NULL; - s->read_from_callbacks = 0; - s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; - s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; -} - -// initialize a callback-based context -static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) -{ - s->io = *c; - s->io_user_data = user; - s->buflen = sizeof(s->buffer_start); - s->read_from_callbacks = 1; - s->img_buffer_original = s->buffer_start; - stbi__refill_buffer(s); - s->img_buffer_original_end = s->img_buffer_end; -} - -// this is not threadsafe -static const char *stbi__g_failure_reason; - -STBIDEF const char *stbi_failure_reason(void) -{ - return stbi__g_failure_reason; -} - -static int stbi__err(const char *str) -{ - stbi__g_failure_reason = str; - return 0; -} - -static void *stbi__malloc(size_t size) -{ - return STBI_MALLOC(size); -} - - -#ifndef STBI_NO_STDIO - -static int stbi__stdio_read(void *user, char *data, int size) -{ - return (int) fread(data,1,size,(FILE*) user); -} - -static void stbi__stdio_skip(void *user, int n) -{ - if (fseek((FILE*) user, n, SEEK_CUR) == -1) - stbi__err("fseek() error"); -} - -static int stbi__stdio_eof(void *user) -{ - return feof((FILE*) user); -} - -static stbi_io_callbacks stbi__stdio_callbacks = -{ - stbi__stdio_read, - stbi__stdio_skip, - stbi__stdio_eof, -}; - -static void stbi__start_file(stbi__context *s, FILE *f) -{ - stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); -} - -//static void stop_file(stbi__context *s) { } - -#endif // !STBI_NO_STDIO - -static void stbi__rewind(stbi__context *s) -{ - // conceptually rewind SHOULD rewind to the beginning of the stream, - // but we just rewind to the beginning of the initial buffer, because - // we only use it after doing 'test', which only ever looks at at most 92 bytes - s->img_buffer = s->img_buffer_original; - s->img_buffer_end = s->img_buffer_original_end; -} - -enum -{ - STBI_ORDER_RGB, - STBI_ORDER_BGR -}; - -typedef struct -{ - int bits_per_channel; - int num_channels; - int channel_order; -} stbi__result_info; - -#ifndef STBI_NO_JPEG -static int stbi__jpeg_test(stbi__context *s); -static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PNG -static int stbi__png_test(stbi__context *s); -static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); -static int stbi__png_is16(stbi__context *s); -#endif - -#ifndef STBI_NO_BMP -static int stbi__bmp_test(stbi__context *s); -static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_TGA -static int stbi__tga_test(stbi__context *s); -static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PSD -static int stbi__psd_test(stbi__context *s); -static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); -static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); -static int stbi__psd_is16(stbi__context *s); -#endif - -#ifndef STBI_NO_HDR -static int stbi__hdr_test(stbi__context *s); -static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PIC -static int stbi__pic_test(stbi__context *s); -static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_GIF -static int stbi__gif_test(stbi__context *s); -static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); -static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -#ifndef STBI_NO_PNM -static int stbi__pnm_test(stbi__context *s); -static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); -#endif - -// stb_image uses ints pervasively, including for offset calculations. -// therefore the largest decoded image size we can support with the -// current code, even on 64-bit targets, is INT_MAX. this is not a -// significant limitation for the intended use case. -// -// we do, however, need to make sure our size calculations don't -// overflow. hence a few helper functions for size calculations that -// multiply integers together, making sure that they're non-negative -// and no overflow occurs. - -// return 1 if the sum is valid, 0 on overflow. -// negative terms are considered invalid. -static int stbi__addsizes_valid(int a, int b) -{ - if (b < 0) return 0; - // now 0 <= b <= INT_MAX, hence also - // 0 <= INT_MAX - b <= INTMAX. - // And "a + b <= INT_MAX" (which might overflow) is the - // same as a <= INT_MAX - b (no overflow) - return a <= INT_MAX - b; -} - -// returns 1 if the product is valid, 0 on overflow. -// negative factors are considered invalid. -static int stbi__mul2sizes_valid(int a, int b) -{ - if (a < 0 || b < 0) return 0; - if (b == 0) return 1; // mul-by-0 is always safe - // portable way to check for no overflows in a*b - return a <= INT_MAX/b; -} - -// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow -static int stbi__mad2sizes_valid(int a, int b, int add) -{ - return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); -} - -// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow -static int stbi__mad3sizes_valid(int a, int b, int c, int add) -{ - return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && - stbi__addsizes_valid(a*b*c, add); -} - -// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow -#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) -static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) -{ - return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && - stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); -} -#endif - -// mallocs with size overflow checking -static void *stbi__malloc_mad2(int a, int b, int add) -{ - if (!stbi__mad2sizes_valid(a, b, add)) return NULL; - return stbi__malloc(a*b + add); -} - -static void *stbi__malloc_mad3(int a, int b, int c, int add) -{ - if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; - return stbi__malloc(a*b*c + add); -} - -#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) -static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) -{ - if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; - return stbi__malloc(a*b*c*d + add); -} -#endif - -// stbi__err - error -// stbi__errpf - error returning pointer to float -// stbi__errpuc - error returning pointer to unsigned char - -#ifdef STBI_NO_FAILURE_STRINGS - #define stbi__err(x,y) 0 -#elif defined(STBI_FAILURE_USERMSG) - #define stbi__err(x,y) stbi__err(y) -#else - #define stbi__err(x,y) stbi__err(x) -#endif - -#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) -#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) - -STBIDEF void stbi_image_free(void *retval_from_stbi_load) -{ - STBI_FREE(retval_from_stbi_load); -} - -#ifndef STBI_NO_LINEAR -static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); -#endif - -#ifndef STBI_NO_HDR -static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); -#endif - -static int stbi__vertically_flip_on_load = 0; - -STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) -{ - stbi__vertically_flip_on_load = flag_true_if_should_flip; -} - -static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int /*bpc*/) -{ - memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields - ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed - ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order - ri->num_channels = 0; - - #ifndef STBI_NO_JPEG - if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_PNG - if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_BMP - if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_GIF - if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_PSD - if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); - #endif - #ifndef STBI_NO_PIC - if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); - #endif - #ifndef STBI_NO_PNM - if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); - #endif - - #ifndef STBI_NO_HDR - if (stbi__hdr_test(s)) { - float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); - return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); - } - #endif - - #ifndef STBI_NO_TGA - // test tga last because it's a crappy test! - if (stbi__tga_test(s)) - return stbi__tga_load(s,x,y,comp,req_comp, ri); - #endif - - return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); -} - -static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) -{ - int i; - int img_len = w * h * channels; - stbi_uc *reduced; - - reduced = (stbi_uc *) stbi__malloc(img_len); - if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); - - for (i = 0; i < img_len; ++i) - reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling - - STBI_FREE(orig); - return reduced; -} - -static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) -{ - int i; - int img_len = w * h * channels; - stbi__uint16 *enlarged; - - enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); - if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); - - for (i = 0; i < img_len; ++i) - enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff - - STBI_FREE(orig); - return enlarged; -} - -static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) -{ - int row; - size_t bytes_per_row = (size_t)w * bytes_per_pixel; - stbi_uc temp[2048]; - stbi_uc *bytes = (stbi_uc *)image; - - for (row = 0; row < (h>>1); row++) { - stbi_uc *row0 = bytes + row*bytes_per_row; - stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; - // swap row0 with row1 - size_t bytes_left = bytes_per_row; - while (bytes_left) { - size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); - memcpy(temp, row0, bytes_copy); - memcpy(row0, row1, bytes_copy); - memcpy(row1, temp, bytes_copy); - row0 += bytes_copy; - row1 += bytes_copy; - bytes_left -= bytes_copy; - } - } -} - -#ifndef STBI_NO_GIF -static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) -{ - int slice; - int slice_size = w * h * bytes_per_pixel; - - stbi_uc *bytes = (stbi_uc *)image; - for (slice = 0; slice < z; ++slice) { - stbi__vertical_flip(bytes, w, h, bytes_per_pixel); - bytes += slice_size; - } -} -#endif - -static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - stbi__result_info ri; - void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); - - if (result == NULL) - return NULL; - - if (ri.bits_per_channel != 8) { - STBI_ASSERT(ri.bits_per_channel == 16); - result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); - ri.bits_per_channel = 8; - } - - // @TODO: move stbi__convert_format to here - - if (stbi__vertically_flip_on_load) { - int channels = req_comp ? req_comp : *comp; - stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); - } - - return (unsigned char *) result; -} - -static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - stbi__result_info ri; - void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); - - if (result == NULL) - return NULL; - - if (ri.bits_per_channel != 16) { - STBI_ASSERT(ri.bits_per_channel == 8); - result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); - ri.bits_per_channel = 16; - } - - // @TODO: move stbi__convert_format16 to here - // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision - - if (stbi__vertically_flip_on_load) { - int channels = req_comp ? req_comp : *comp; - stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); - } - - return (stbi__uint16 *) result; -} - -#if !defined(STBI_NO_HDR) || !defined(STBI_NO_LINEAR) -static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) -{ - if (stbi__vertically_flip_on_load && result != NULL) { - int channels = req_comp ? req_comp : *comp; - stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); - } -} -#endif - -#ifndef STBI_NO_STDIO - -static FILE *stbi__fopen(char const *filename, char const *mode) -{ - FILE *f; -#if defined(_MSC_VER) - DWORD cch = - MultiByteToWideChar(CP_UTF8, 0, filename, -1, nullptr, 0); - wchar_t *filenameU = new wchar_t[cch]; - if(!filenameU) - { - return 0; - } - MultiByteToWideChar(CP_UTF8, 0, filename, -1, filenameU, cch); - cch = MultiByteToWideChar(CP_UTF8, 0, mode, -1, nullptr, 0); - wchar_t *modeU = new wchar_t[cch]; - if(!modeU) - { - delete[] filenameU; - return 0; - } - MultiByteToWideChar(CP_UTF8, 0, mode, -1, modeU, cch); -#if _MSC_VER >= 1400 - if(0 != _wfopen_s(&f, filenameU, modeU)) - f = 0; - delete[] filenameU; - delete[] modeU; -#else // _MSC_VER >= 1400 - f = _wfopen(filenameU, modeU); - delete[] filenameU; - delete[] modeU; -#endif // _MSC_VER >= 1400 -#else // _MSC_VER - f = fopen(filename, mode); -#endif //_MSC_VER - return f; -} - - -STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - FILE *f = stbi__fopen(filename, "rb"); - unsigned char *result; - if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); - result = stbi_load_from_file(f,x,y,comp,req_comp); - fclose(f); - return result; -} - -STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - unsigned char *result; - stbi__context s; - stbi__start_file(&s,f); - result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); - if (result) { - // need to 'unget' all the characters in the IO buffer - if (fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR) == -1) { - stbi_image_free(result); - return stbi__errpuc("fseek() error", "File Seek Fail"); - } - } - return result; -} - -STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - stbi__uint16 *result; - stbi__context s; - stbi__start_file(&s,f); - result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); - if (result) { - // need to 'unget' all the characters in the IO buffer - if (fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR) == -1) { - stbi_image_free(result); - return (stbi__uint16 *) stbi__errpuc("fseek() error", "File Seek Fail"); - } - } - return result; -} - -STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - FILE *f = stbi__fopen(filename, "rb"); - stbi__uint16 *result; - if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); - result = stbi_load_from_file_16(f,x,y,comp,req_comp); - fclose(f); - return result; -} - - -#endif //!STBI_NO_STDIO - -STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); -} - -STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); - return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); -} - -STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); -} - -STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); -} - -#ifndef STBI_NO_GIF -STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) -{ - unsigned char *result; - stbi__context s; - stbi__start_mem(&s,buffer,len); - - result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); - if (stbi__vertically_flip_on_load) { - stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); - } - - return result; -} -#endif - -#ifndef STBI_NO_LINEAR -static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - unsigned char *data; - #ifndef STBI_NO_HDR - if (stbi__hdr_test(s)) { - stbi__result_info ri; - float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); - if (hdr_data) - stbi__float_postprocess(hdr_data,x,y,comp,req_comp); - return hdr_data; - } - #endif - data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); - if (data) - return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); - return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); -} - -STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__loadf_main(&s,x,y,comp,req_comp); -} - -STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__loadf_main(&s,x,y,comp,req_comp); -} - -#ifndef STBI_NO_STDIO -STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - float *result; - FILE *f = stbi__fopen(filename, "rb"); - if (!f) return stbi__errpf("can't fopen", "Unable to open file"); - result = stbi_loadf_from_file(f,x,y,comp,req_comp); - fclose(f); - return result; -} - -STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - stbi__context s; - stbi__start_file(&s,f); - return stbi__loadf_main(&s,x,y,comp,req_comp); -} -#endif // !STBI_NO_STDIO - -#endif // !STBI_NO_LINEAR - -// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is -// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always -// reports false! - -STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) -{ - #ifndef STBI_NO_HDR - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__hdr_test(&s); - #else - STBI_NOTUSED(buffer); - STBI_NOTUSED(len); - return 0; - #endif -} - -#ifndef STBI_NO_STDIO -STBIDEF int stbi_is_hdr (char const *filename) -{ - FILE *f = stbi__fopen(filename, "rb"); - int result=0; - if (f) { - result = stbi_is_hdr_from_file(f); - fclose(f); - } - return result; -} - -STBIDEF int stbi_is_hdr_from_file(FILE *f) -{ - #ifndef STBI_NO_HDR - long pos = ftell(f); - int res; - stbi__context s; - stbi__start_file(&s,f); - res = stbi__hdr_test(&s); - if (fseek(f, pos, SEEK_SET) == -1) return stbi__err("fseek() error", "File Seek Fail"); - return res; - #else - STBI_NOTUSED(f); - return 0; - #endif -} -#endif // !STBI_NO_STDIO - -STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) -{ - #ifndef STBI_NO_HDR - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__hdr_test(&s); - #else - STBI_NOTUSED(clbk); - STBI_NOTUSED(user); - return 0; - #endif -} - -#ifndef STBI_NO_LINEAR -static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; - -STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } -STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } -#endif - -static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; - -STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } -STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } - - -////////////////////////////////////////////////////////////////////////////// -// -// Common code used by all image loaders -// - -enum -{ - STBI__SCAN_load=0, - STBI__SCAN_type, - STBI__SCAN_header -}; - -static void stbi__refill_buffer(stbi__context *s) -{ - int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); - if (n == 0) { - // at end of file, treat same as if from memory, but need to handle case - // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file - s->read_from_callbacks = 0; - s->img_buffer = s->buffer_start; - s->img_buffer_end = s->buffer_start+1; - *s->img_buffer = 0; - } else { - s->img_buffer = s->buffer_start; - s->img_buffer_end = s->buffer_start + n; - } -} - -stbi_inline static stbi_uc stbi__get8(stbi__context *s) -{ - if (s->img_buffer < s->img_buffer_end) - return *s->img_buffer++; - if (s->read_from_callbacks) { - stbi__refill_buffer(s); - return *s->img_buffer++; - } - return 0; -} - -stbi_inline static int stbi__at_eof(stbi__context *s) -{ - if (s->io.read) { - if (!(s->io.eof)(s->io_user_data)) return 0; - // if feof() is true, check if buffer = end - // special case: we've only got the special 0 character at the end - if (s->read_from_callbacks == 0) return 1; - } - - return s->img_buffer >= s->img_buffer_end; -} - -static void stbi__skip(stbi__context *s, int n) -{ - if (n < 0) { - s->img_buffer = s->img_buffer_end; - return; - } - if (s->io.read) { - int blen = (int) (s->img_buffer_end - s->img_buffer); - if (blen < n) { - s->img_buffer = s->img_buffer_end; - (s->io.skip)(s->io_user_data, n - blen); - return; - } - } - s->img_buffer += n; -} - -static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) -{ - if (s->io.read) { - int blen = (int) (s->img_buffer_end - s->img_buffer); - if (blen < n) { - int res, count; - - memcpy(buffer, s->img_buffer, blen); - - count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); - res = (count == (n-blen)); - s->img_buffer = s->img_buffer_end; - return res; - } - } - - if (s->img_buffer+n <= s->img_buffer_end) { - memcpy(buffer, s->img_buffer, n); - s->img_buffer += n; - return 1; - } else - return 0; -} - -static int stbi__get16be(stbi__context *s) -{ - int z = stbi__get8(s); - return (z << 8) + stbi__get8(s); -} - -static stbi__uint32 stbi__get32be(stbi__context *s) -{ - stbi__uint32 z = stbi__get16be(s); - return (z << 16) + stbi__get16be(s); -} - -#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) -// nothing -#else -static int stbi__get16le(stbi__context *s) -{ - int z = stbi__get8(s); - return z + (stbi__get8(s) << 8); -} -#endif - -#ifndef STBI_NO_BMP -static stbi__uint32 stbi__get32le(stbi__context *s) -{ - stbi__uint32 z = stbi__get16le(s); - return z + (stbi__get16le(s) << 16); -} -#endif - -#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings - - -////////////////////////////////////////////////////////////////////////////// -// -// generic converter from built-in img_n to req_comp -// individual types do this automatically as much as possible (e.g. jpeg -// does all cases internally since it needs to colorspace convert anyway, -// and it never has alpha, so very few cases ). png can automatically -// interleave an alpha=255 channel, but falls back to this for other cases -// -// assume data buffer is malloced, so malloc a new one and free that one -// only failure mode is malloc failing - -static stbi_uc stbi__compute_y(int r, int g, int b) -{ - return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); -} - -static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) -{ - int i,j; - unsigned char *good; - - if (req_comp == img_n) return data; - STBI_ASSERT(req_comp >= 1 && req_comp <= 4); - - good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); - if (good == NULL) { - STBI_FREE(data); - return stbi__errpuc("outofmem", "Out of memory"); - } - - for (j=0; j < (int) y; ++j) { - unsigned char *src = data + j * x * img_n ; - unsigned char *dest = good + j * x * req_comp; - - #define STBI__COMBO(a,b) ((a)*8+(b)) - #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) - // convert source image with img_n components to one with req_comp components; - // avoid switch per pixel, so use switch per scanline and massive macros - switch (STBI__COMBO(img_n, req_comp)) { - STBI__CASE(1,2) { dest[0]=src[0], dest[1]=255; } break; - STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; } break; - STBI__CASE(2,1) { dest[0]=src[0]; } break; - STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; - STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; } break; - STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; - STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = 255; } break; - STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; - STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = src[3]; } break; - STBI__CASE(4,3) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; } break; - default: STBI_ASSERT(0); - } - #undef STBI__CASE - } - - STBI_FREE(data); - return good; -} - -static stbi__uint16 stbi__compute_y_16(int r, int g, int b) -{ - return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); -} - -static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) -{ - int i,j; - stbi__uint16 *good; - - if (req_comp == img_n) return data; - STBI_ASSERT(req_comp >= 1 && req_comp <= 4); - - good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); - if (good == NULL) { - STBI_FREE(data); - return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); - } - - for (j=0; j < (int) y; ++j) { - stbi__uint16 *src = data + j * x * img_n ; - stbi__uint16 *dest = good + j * x * req_comp; - - #define STBI__COMBO(a,b) ((a)*8+(b)) - #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) - // convert source image with img_n components to one with req_comp components; - // avoid switch per pixel, so use switch per scanline and massive macros - switch (STBI__COMBO(img_n, req_comp)) { - STBI__CASE(1,2) { dest[0]=src[0], dest[1]=0xffff; } break; - STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=0xffff; } break; - STBI__CASE(2,1) { dest[0]=src[0]; } break; - STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; - STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=0xffff; } break; - STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; - STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]), dest[1] = 0xffff; } break; - STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; - STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]), dest[1] = src[3]; } break; - STBI__CASE(4,3) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; } break; - default: STBI_ASSERT(0); - } - #undef STBI__CASE - } - - STBI_FREE(data); - return good; -} - -#ifndef STBI_NO_LINEAR -static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) -{ - int i,k,n; - float *output; - if (!data) return NULL; - output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); - if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } - // compute number of non-alpha components - if (comp & 1) n = comp; else n = comp-1; - for (i=0; i < x*y; ++i) { - for (k=0; k < n; ++k) { - output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); - } - if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; - } - STBI_FREE(data); - return output; -} -#endif - -#ifndef STBI_NO_HDR -#define stbi__float2int(x) ((int) (x)) -static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) -{ - int i,k,n; - stbi_uc *output; - if (!data) return NULL; - output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); - if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } - // compute number of non-alpha components - if (comp & 1) n = comp; else n = comp-1; - for (i=0; i < x*y; ++i) { - for (k=0; k < n; ++k) { - float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; - if (z < 0) z = 0; - if (z > 255) z = 255; - output[i*comp + k] = (stbi_uc) stbi__float2int(z); - } - if (k < comp) { - float z = data[i*comp+k] * 255 + 0.5f; - if (z < 0) z = 0; - if (z > 255) z = 255; - output[i*comp + k] = (stbi_uc) stbi__float2int(z); - } - } - STBI_FREE(data); - return output; -} -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// "baseline" JPEG/JFIF decoder -// -// simple implementation -// - doesn't support delayed output of y-dimension -// - simple interface (only one output format: 8-bit interleaved RGB) -// - doesn't try to recover corrupt jpegs -// - doesn't allow partial loading, loading multiple at once -// - still fast on x86 (copying globals into locals doesn't help x86) -// - allocates lots of intermediate memory (full size of all components) -// - non-interleaved case requires this anyway -// - allows good upsampling (see next) -// high-quality -// - upsampled channels are bilinearly interpolated, even across blocks -// - quality integer IDCT derived from IJG's 'slow' -// performance -// - fast huffman; reasonable integer IDCT -// - some SIMD kernels for common paths on targets with SSE2/NEON -// - uses a lot of intermediate memory, could cache poorly - -#ifndef STBI_NO_JPEG - -// huffman decoding acceleration -#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache - -typedef struct -{ - stbi_uc fast[1 << FAST_BITS]; - // weirdly, repacking this into AoS is a 10% speed loss, instead of a win - stbi__uint16 code[256]; - stbi_uc values[256]; - stbi_uc size[257]; - unsigned int maxcode[18]; - int delta[17]; // old 'firstsymbol' - old 'firstcode' -} stbi__huffman; - -typedef struct -{ - stbi__context *s; - stbi__huffman huff_dc[4]; - stbi__huffman huff_ac[4]; - stbi__uint16 dequant[4][64]; - stbi__int16 fast_ac[4][1 << FAST_BITS]; - -// sizes for components, interleaved MCUs - int img_h_max, img_v_max; - int img_mcu_x, img_mcu_y; - int img_mcu_w, img_mcu_h; - -// definition of jpeg image component - struct - { - int id; - int h,v; - int tq; - int hd,ha; - int dc_pred; - - int x,y,w2,h2; - stbi_uc *data; - void *raw_data, *raw_coeff; - stbi_uc *linebuf; - short *coeff; // progressive only - int coeff_w, coeff_h; // number of 8x8 coefficient blocks - } img_comp[4]; - - stbi__uint32 code_buffer; // jpeg entropy-coded buffer - int code_bits; // number of valid bits - unsigned char marker; // marker seen while filling entropy buffer - int nomore; // flag if we saw a marker so must stop - - int progressive; - int spec_start; - int spec_end; - int succ_high; - int succ_low; - int eob_run; - int jfif; - int app14_color_transform; // Adobe APP14 tag - int rgb; - - int scan_n, order[4]; - int restart_interval, todo; - -// kernels - void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); - void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); - stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); -} stbi__jpeg; - -static int stbi__build_huffman(stbi__huffman *h, int *count) -{ - int i,j,k=0; - unsigned int code; - // build size list for each symbol (from JPEG spec) - for (i=0; i < 16; ++i) - for (j=0; j < count[i]; ++j) - h->size[k++] = (stbi_uc) (i+1); - h->size[k] = 0; - - // compute actual symbols (from jpeg spec) - code = 0; - k = 0; - for(j=1; j <= 16; ++j) { - // compute delta to add to code to compute symbol id - h->delta[j] = k - code; - if (h->size[k] == j) { - while (h->size[k] == j) - h->code[k++] = (stbi__uint16) (code++); - if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); - } - // compute largest code + 1 for this size, preshifted as needed later - h->maxcode[j] = code << (16-j); - code <<= 1; - } - h->maxcode[j] = 0xffffffff; - - // build non-spec acceleration table; 255 is flag for not-accelerated - memset(h->fast, 255, 1 << FAST_BITS); - for (i=0; i < k; ++i) { - int s = h->size[i]; - if (s <= FAST_BITS) { - int c = h->code[i] << (FAST_BITS-s); - int m = 1 << (FAST_BITS-s); - for (j=0; j < m; ++j) { - h->fast[c+j] = (stbi_uc) i; - } - } - } - return 1; -} - -// build a table that decodes both magnitude and value of small ACs in -// one go. -static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) -{ - int i; - for (i=0; i < (1 << FAST_BITS); ++i) { - stbi_uc fast = h->fast[i]; - fast_ac[i] = 0; - if (fast < 255) { - int rs = h->values[fast]; - int run = (rs >> 4) & 15; - int magbits = rs & 15; - int len = h->size[fast]; - - if (magbits && len + magbits <= FAST_BITS) { - // magnitude code followed by receive_extend code - int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); - int m = 1 << (magbits - 1); - if (k < m) k += (~0U << magbits) + 1; - // if the result is small enough, we can fit it in fast_ac table - if (k >= -128 && k <= 127) - fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); - } - } - } -} - -static void stbi__grow_buffer_unsafe(stbi__jpeg *j) -{ - do { - unsigned int b = j->nomore ? 0 : stbi__get8(j->s); - if (b == 0xff) { - int c = stbi__get8(j->s); - while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes - if (c != 0) { - j->marker = (unsigned char) c; - j->nomore = 1; - return; - } - } - j->code_buffer |= b << (24 - j->code_bits); - j->code_bits += 8; - } while (j->code_bits <= 24); -} - -// (1 << n) - 1 -static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; - -// decode a jpeg huffman value from the bitstream -stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) -{ - unsigned int temp; - int c,k; - - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - - // look at the top FAST_BITS and determine what symbol ID it is, - // if the code is <= FAST_BITS - c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); - k = h->fast[c]; - if (k < 255) { - int s = h->size[k]; - if (s > j->code_bits) - return -1; - j->code_buffer <<= s; - j->code_bits -= s; - return h->values[k]; - } - - // naive test is to shift the code_buffer down so k bits are - // valid, then test against maxcode. To speed this up, we've - // preshifted maxcode left so that it has (16-k) 0s at the - // end; in other words, regardless of the number of bits, it - // wants to be compared against something shifted to have 16; - // that way we don't need to shift inside the loop. - temp = j->code_buffer >> 16; - for (k=FAST_BITS+1 ; ; ++k) - if (temp < h->maxcode[k]) - break; - if (k == 17) { - // error! code not found - j->code_bits -= 16; - return -1; - } - - if (k > j->code_bits) - return -1; - - // convert the huffman code to the symbol id - c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; - STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); - - // convert the id to a symbol - j->code_bits -= k; - j->code_buffer <<= k; - return h->values[c]; -} - -// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); - - sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB - k = stbi_lrot(j->code_buffer, n); - STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))); - j->code_buffer = k & ~stbi__bmask[n]; - k &= stbi__bmask[n]; - j->code_bits -= n; - return k + (stbi__jbias[n] & ~sgn); -} - -// get some unsigned bits -stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) -{ - unsigned int k; - if (j->code_bits < n) stbi__grow_buffer_unsafe(j); - k = stbi_lrot(j->code_buffer, n); - j->code_buffer = k & ~stbi__bmask[n]; - k &= stbi__bmask[n]; - j->code_bits -= n; - return k; -} - -stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) -{ - unsigned int k; - if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); - k = j->code_buffer; - j->code_buffer <<= 1; - --j->code_bits; - return k & 0x80000000; -} - -// given a value that's at position X in the zigzag stream, -// where does it appear in the 8x8 matrix coded as row-major? -static const stbi_uc stbi__jpeg_dezigzag[64+15] = -{ - 0, 1, 8, 16, 9, 2, 3, 10, - 17, 24, 32, 25, 18, 11, 4, 5, - 12, 19, 26, 33, 40, 48, 41, 34, - 27, 20, 13, 6, 7, 14, 21, 28, - 35, 42, 49, 56, 57, 50, 43, 36, - 29, 22, 15, 23, 30, 37, 44, 51, - 58, 59, 52, 45, 38, 31, 39, 46, - 53, 60, 61, 54, 47, 55, 62, 63, - // let corrupt input sample past end - 63, 63, 63, 63, 63, 63, 63, 63, - 63, 63, 63, 63, 63, 63, 63 -}; - -// decode one 64-entry block-- -static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) -{ - int diff,dc,k; - int t; - - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - t = stbi__jpeg_huff_decode(j, hdc); - if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - - // 0 all the ac values now so we can do it 32-bits at a time - memset(data,0,64*sizeof(data[0])); - - diff = t ? stbi__extend_receive(j, t) : 0; - dc = j->img_comp[b].dc_pred + diff; - j->img_comp[b].dc_pred = dc; - data[0] = (short) (dc * dequant[0]); - - // decode AC components, see JPEG spec - k = 1; - do { - unsigned int zig; - int c,r,s; - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); - r = fac[c]; - if (r) { // fast-AC path - k += (r >> 4) & 15; // run - s = r & 15; // combined length - j->code_buffer <<= s; - j->code_bits -= s; - // decode into unzigzag'd location - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) ((r >> 8) * dequant[zig]); - } else { - int rs = stbi__jpeg_huff_decode(j, hac); - if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - s = rs & 15; - r = rs >> 4; - if (s == 0) { - if (rs != 0xf0) break; // end block - k += 16; - } else { - k += r; - // decode into unzigzag'd location - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); - } - } - } while (k < 64); - return 1; -} - -static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) -{ - int diff,dc; - int t; - if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); - - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - - if (j->succ_high == 0) { - // first scan for DC coefficient, must be first - memset(data,0,64*sizeof(data[0])); // 0 all the ac values now - t = stbi__jpeg_huff_decode(j, hdc); - diff = t ? stbi__extend_receive(j, t) : 0; - - dc = j->img_comp[b].dc_pred + diff; - j->img_comp[b].dc_pred = dc; - data[0] = (short) (dc << j->succ_low); - } else { - // refinement scan for DC coefficient - if (stbi__jpeg_get_bit(j)) - data[0] += (short) (1 << j->succ_low); - } - return 1; -} - -// @OPTIMIZE: store non-zigzagged during the decode passes, -// and only de-zigzag when dequantizing -static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) -{ - int k; - if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); - - if (j->succ_high == 0) { - int shift = j->succ_low; - - if (j->eob_run) { - --j->eob_run; - return 1; - } - - k = j->spec_start; - do { - unsigned int zig; - int c,r,s; - if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); - c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); - r = fac[c]; - if (r) { // fast-AC path - k += (r >> 4) & 15; // run - s = r & 15; // combined length - j->code_buffer <<= s; - j->code_bits -= s; - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) ((r >> 8) << shift); - } else { - int rs = stbi__jpeg_huff_decode(j, hac); - if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - s = rs & 15; - r = rs >> 4; - if (s == 0) { - if (r < 15) { - j->eob_run = (1 << r); - if (r) - j->eob_run += stbi__jpeg_get_bits(j, r); - --j->eob_run; - break; - } - k += 16; - } else { - k += r; - zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) (stbi__extend_receive(j,s) << shift); - } - } - } while (k <= j->spec_end); - } else { - // refinement scan for these AC coefficients - - short bit = (short) (1 << j->succ_low); - - if (j->eob_run) { - --j->eob_run; - for (k = j->spec_start; k <= j->spec_end; ++k) { - short *p = &data[stbi__jpeg_dezigzag[k]]; - if (*p != 0) - if (stbi__jpeg_get_bit(j)) - if ((*p & bit)==0) { - if (*p > 0) - *p += bit; - else - *p -= bit; - } - } - } else { - k = j->spec_start; - do { - int r,s; - int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh - if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - s = rs & 15; - r = rs >> 4; - if (s == 0) { - if (r < 15) { - j->eob_run = (1 << r) - 1; - if (r) - j->eob_run += stbi__jpeg_get_bits(j, r); - r = 64; // force end of block - } else { - // r=15 s=0 should write 16 0s, so we just do - // a run of 15 0s and then write s (which is 0), - // so we don't have to do anything special here - } - } else { - if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); - // sign bit - if (stbi__jpeg_get_bit(j)) - s = bit; - else - s = -bit; - } - - // advance by r - while (k <= j->spec_end) { - short *p = &data[stbi__jpeg_dezigzag[k++]]; - if (*p != 0) { - if (stbi__jpeg_get_bit(j)) - if ((*p & bit)==0) { - if (*p > 0) - *p += bit; - else - *p -= bit; - } - } else { - if (r == 0) { - *p = (short) s; - break; - } - --r; - } - } - } while (k <= j->spec_end); - } - } - return 1; -} - -// take a -128..127 value and stbi__clamp it and convert to 0..255 -stbi_inline static stbi_uc stbi__clamp(int x) -{ - // trick to use a single test to catch both cases - if ((unsigned int) x > 255) { - if (x < 0) return 0; - if (x > 255) return 255; - } - return (stbi_uc) x; -} - -#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) -#define stbi__fsh(x) ((x) * 4096) - -// derived from jidctint -- DCT_ISLOW -#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ - int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ - p2 = s2; \ - p3 = s6; \ - p1 = (p2+p3) * stbi__f2f(0.5411961f); \ - t2 = p1 + p3*stbi__f2f(-1.847759065f); \ - t3 = p1 + p2*stbi__f2f( 0.765366865f); \ - p2 = s0; \ - p3 = s4; \ - t0 = stbi__fsh(p2+p3); \ - t1 = stbi__fsh(p2-p3); \ - x0 = t0+t3; \ - x3 = t0-t3; \ - x1 = t1+t2; \ - x2 = t1-t2; \ - t0 = s7; \ - t1 = s5; \ - t2 = s3; \ - t3 = s1; \ - p3 = t0+t2; \ - p4 = t1+t3; \ - p1 = t0+t3; \ - p2 = t1+t2; \ - p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ - t0 = t0*stbi__f2f( 0.298631336f); \ - t1 = t1*stbi__f2f( 2.053119869f); \ - t2 = t2*stbi__f2f( 3.072711026f); \ - t3 = t3*stbi__f2f( 1.501321110f); \ - p1 = p5 + p1*stbi__f2f(-0.899976223f); \ - p2 = p5 + p2*stbi__f2f(-2.562915447f); \ - p3 = p3*stbi__f2f(-1.961570560f); \ - p4 = p4*stbi__f2f(-0.390180644f); \ - t3 += p1+p4; \ - t2 += p2+p3; \ - t1 += p2+p4; \ - t0 += p1+p3; - -static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) -{ - int i,val[64],*v=val; - stbi_uc *o; - short *d = data; - - // columns - for (i=0; i < 8; ++i,++d, ++v) { - // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing - if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 - && d[40]==0 && d[48]==0 && d[56]==0) { - // no shortcut 0 seconds - // (1|2|3|4|5|6|7)==0 0 seconds - // all separate -0.047 seconds - // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds - int dcterm = d[0]*4; - v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; - } else { - STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) - // constants scaled things up by 1<<12; let's bring them back - // down, but keep 2 extra bits of precision - x0 += 512; x1 += 512; x2 += 512; x3 += 512; - v[ 0] = (x0+t3) >> 10; - v[56] = (x0-t3) >> 10; - v[ 8] = (x1+t2) >> 10; - v[48] = (x1-t2) >> 10; - v[16] = (x2+t1) >> 10; - v[40] = (x2-t1) >> 10; - v[24] = (x3+t0) >> 10; - v[32] = (x3-t0) >> 10; - } - } - - for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { - // no fast case since the first 1D IDCT spread components out - STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) - // constants scaled things up by 1<<12, plus we had 1<<2 from first - // loop, plus horizontal and vertical each scale by sqrt(8) so together - // we've got an extra 1<<3, so 1<<17 total we need to remove. - // so we want to round that, which means adding 0.5 * 1<<17, - // aka 65536. Also, we'll end up with -128 to 127 that we want - // to encode as 0..255 by adding 128, so we'll add that before the shift - x0 += 65536 + (128<<17); - x1 += 65536 + (128<<17); - x2 += 65536 + (128<<17); - x3 += 65536 + (128<<17); - // tried computing the shifts into temps, or'ing the temps to see - // if any were out of range, but that was slower - o[0] = stbi__clamp((x0+t3) >> 17); - o[7] = stbi__clamp((x0-t3) >> 17); - o[1] = stbi__clamp((x1+t2) >> 17); - o[6] = stbi__clamp((x1-t2) >> 17); - o[2] = stbi__clamp((x2+t1) >> 17); - o[5] = stbi__clamp((x2-t1) >> 17); - o[3] = stbi__clamp((x3+t0) >> 17); - o[4] = stbi__clamp((x3-t0) >> 17); - } -} - -#ifdef STBI_SSE2 -// sse2 integer IDCT. not the fastest possible implementation but it -// produces bit-identical results to the generic C version so it's -// fully "transparent". -static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) -{ - // This is constructed to match our regular (generic) integer IDCT exactly. - __m128i row0, row1, row2, row3, row4, row5, row6, row7; - __m128i tmp; - - // dot product constant: even elems=x, odd elems=y - #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) - - // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) - // out(1) = c1[even]*x + c1[odd]*y - #define dct_rot(out0,out1, x,y,c0,c1) \ - __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ - __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ - __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ - __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ - __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ - __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) - - // out = in << 12 (in 16-bit, out 32-bit) - #define dct_widen(out, in) \ - __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ - __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) - - // wide add - #define dct_wadd(out, a, b) \ - __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ - __m128i out##_h = _mm_add_epi32(a##_h, b##_h) - - // wide sub - #define dct_wsub(out, a, b) \ - __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ - __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) - - // butterfly a/b, add bias, then shift by "s" and pack - #define dct_bfly32o(out0, out1, a,b,bias,s) \ - { \ - __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ - __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ - dct_wadd(sum, abiased, b); \ - dct_wsub(dif, abiased, b); \ - out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ - out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ - } - - // 8-bit interleave step (for transposes) - #define dct_interleave8(a, b) \ - tmp = a; \ - a = _mm_unpacklo_epi8(a, b); \ - b = _mm_unpackhi_epi8(tmp, b) - - // 16-bit interleave step (for transposes) - #define dct_interleave16(a, b) \ - tmp = a; \ - a = _mm_unpacklo_epi16(a, b); \ - b = _mm_unpackhi_epi16(tmp, b) - - #define dct_pass(bias,shift) \ - { \ - /* even part */ \ - dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ - __m128i sum04 = _mm_add_epi16(row0, row4); \ - __m128i dif04 = _mm_sub_epi16(row0, row4); \ - dct_widen(t0e, sum04); \ - dct_widen(t1e, dif04); \ - dct_wadd(x0, t0e, t3e); \ - dct_wsub(x3, t0e, t3e); \ - dct_wadd(x1, t1e, t2e); \ - dct_wsub(x2, t1e, t2e); \ - /* odd part */ \ - dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ - dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ - __m128i sum17 = _mm_add_epi16(row1, row7); \ - __m128i sum35 = _mm_add_epi16(row3, row5); \ - dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ - dct_wadd(x4, y0o, y4o); \ - dct_wadd(x5, y1o, y5o); \ - dct_wadd(x6, y2o, y5o); \ - dct_wadd(x7, y3o, y4o); \ - dct_bfly32o(row0,row7, x0,x7,bias,shift); \ - dct_bfly32o(row1,row6, x1,x6,bias,shift); \ - dct_bfly32o(row2,row5, x2,x5,bias,shift); \ - dct_bfly32o(row3,row4, x3,x4,bias,shift); \ - } - - __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); - __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); - __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); - __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); - __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); - __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); - __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); - __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); - - // rounding biases in column/row passes, see stbi__idct_block for explanation. - __m128i bias_0 = _mm_set1_epi32(512); - __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); - - // load - row0 = _mm_load_si128((const __m128i *) (data + 0*8)); - row1 = _mm_load_si128((const __m128i *) (data + 1*8)); - row2 = _mm_load_si128((const __m128i *) (data + 2*8)); - row3 = _mm_load_si128((const __m128i *) (data + 3*8)); - row4 = _mm_load_si128((const __m128i *) (data + 4*8)); - row5 = _mm_load_si128((const __m128i *) (data + 5*8)); - row6 = _mm_load_si128((const __m128i *) (data + 6*8)); - row7 = _mm_load_si128((const __m128i *) (data + 7*8)); - - // column pass - dct_pass(bias_0, 10); - - { - // 16bit 8x8 transpose pass 1 - dct_interleave16(row0, row4); - dct_interleave16(row1, row5); - dct_interleave16(row2, row6); - dct_interleave16(row3, row7); - - // transpose pass 2 - dct_interleave16(row0, row2); - dct_interleave16(row1, row3); - dct_interleave16(row4, row6); - dct_interleave16(row5, row7); - - // transpose pass 3 - dct_interleave16(row0, row1); - dct_interleave16(row2, row3); - dct_interleave16(row4, row5); - dct_interleave16(row6, row7); - } - - // row pass - dct_pass(bias_1, 17); - - { - // pack - __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 - __m128i p1 = _mm_packus_epi16(row2, row3); - __m128i p2 = _mm_packus_epi16(row4, row5); - __m128i p3 = _mm_packus_epi16(row6, row7); - - // 8bit 8x8 transpose pass 1 - dct_interleave8(p0, p2); // a0e0a1e1... - dct_interleave8(p1, p3); // c0g0c1g1... - - // transpose pass 2 - dct_interleave8(p0, p1); // a0c0e0g0... - dct_interleave8(p2, p3); // b0d0f0h0... - - // transpose pass 3 - dct_interleave8(p0, p2); // a0b0c0d0... - dct_interleave8(p1, p3); // a4b4c4d4... - - // store - _mm_storel_epi64((__m128i *) out, p0); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; - _mm_storel_epi64((__m128i *) out, p2); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; - _mm_storel_epi64((__m128i *) out, p1); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; - _mm_storel_epi64((__m128i *) out, p3); out += out_stride; - _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); - } - -#undef dct_const -#undef dct_rot -#undef dct_widen -#undef dct_wadd -#undef dct_wsub -#undef dct_bfly32o -#undef dct_interleave8 -#undef dct_interleave16 -#undef dct_pass -} - -#endif // STBI_SSE2 - -#ifdef STBI_NEON - -// NEON integer IDCT. should produce bit-identical -// results to the generic C version. -static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) -{ - int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; - - int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); - int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); - int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); - int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); - int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); - int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); - int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); - int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); - int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); - int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); - int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); - int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); - -#define dct_long_mul(out, inq, coeff) \ - int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ - int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) - -#define dct_long_mac(out, acc, inq, coeff) \ - int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ - int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) - -#define dct_widen(out, inq) \ - int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ - int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) - -// wide add -#define dct_wadd(out, a, b) \ - int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ - int32x4_t out##_h = vaddq_s32(a##_h, b##_h) - -// wide sub -#define dct_wsub(out, a, b) \ - int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ - int32x4_t out##_h = vsubq_s32(a##_h, b##_h) - -// butterfly a/b, then shift using "shiftop" by "s" and pack -#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ - { \ - dct_wadd(sum, a, b); \ - dct_wsub(dif, a, b); \ - out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ - out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ - } - -#define dct_pass(shiftop, shift) \ - { \ - /* even part */ \ - int16x8_t sum26 = vaddq_s16(row2, row6); \ - dct_long_mul(p1e, sum26, rot0_0); \ - dct_long_mac(t2e, p1e, row6, rot0_1); \ - dct_long_mac(t3e, p1e, row2, rot0_2); \ - int16x8_t sum04 = vaddq_s16(row0, row4); \ - int16x8_t dif04 = vsubq_s16(row0, row4); \ - dct_widen(t0e, sum04); \ - dct_widen(t1e, dif04); \ - dct_wadd(x0, t0e, t3e); \ - dct_wsub(x3, t0e, t3e); \ - dct_wadd(x1, t1e, t2e); \ - dct_wsub(x2, t1e, t2e); \ - /* odd part */ \ - int16x8_t sum15 = vaddq_s16(row1, row5); \ - int16x8_t sum17 = vaddq_s16(row1, row7); \ - int16x8_t sum35 = vaddq_s16(row3, row5); \ - int16x8_t sum37 = vaddq_s16(row3, row7); \ - int16x8_t sumodd = vaddq_s16(sum17, sum35); \ - dct_long_mul(p5o, sumodd, rot1_0); \ - dct_long_mac(p1o, p5o, sum17, rot1_1); \ - dct_long_mac(p2o, p5o, sum35, rot1_2); \ - dct_long_mul(p3o, sum37, rot2_0); \ - dct_long_mul(p4o, sum15, rot2_1); \ - dct_wadd(sump13o, p1o, p3o); \ - dct_wadd(sump24o, p2o, p4o); \ - dct_wadd(sump23o, p2o, p3o); \ - dct_wadd(sump14o, p1o, p4o); \ - dct_long_mac(x4, sump13o, row7, rot3_0); \ - dct_long_mac(x5, sump24o, row5, rot3_1); \ - dct_long_mac(x6, sump23o, row3, rot3_2); \ - dct_long_mac(x7, sump14o, row1, rot3_3); \ - dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ - dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ - dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ - dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ - } - - // load - row0 = vld1q_s16(data + 0*8); - row1 = vld1q_s16(data + 1*8); - row2 = vld1q_s16(data + 2*8); - row3 = vld1q_s16(data + 3*8); - row4 = vld1q_s16(data + 4*8); - row5 = vld1q_s16(data + 5*8); - row6 = vld1q_s16(data + 6*8); - row7 = vld1q_s16(data + 7*8); - - // add DC bias - row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); - - // column pass - dct_pass(vrshrn_n_s32, 10); - - // 16bit 8x8 transpose - { -// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. -// whether compilers actually get this is another story, sadly. -#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } -#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } -#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } - - // pass 1 - dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 - dct_trn16(row2, row3); - dct_trn16(row4, row5); - dct_trn16(row6, row7); - - // pass 2 - dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 - dct_trn32(row1, row3); - dct_trn32(row4, row6); - dct_trn32(row5, row7); - - // pass 3 - dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 - dct_trn64(row1, row5); - dct_trn64(row2, row6); - dct_trn64(row3, row7); - -#undef dct_trn16 -#undef dct_trn32 -#undef dct_trn64 - } - - // row pass - // vrshrn_n_s32 only supports shifts up to 16, we need - // 17. so do a non-rounding shift of 16 first then follow - // up with a rounding shift by 1. - dct_pass(vshrn_n_s32, 16); - - { - // pack and round - uint8x8_t p0 = vqrshrun_n_s16(row0, 1); - uint8x8_t p1 = vqrshrun_n_s16(row1, 1); - uint8x8_t p2 = vqrshrun_n_s16(row2, 1); - uint8x8_t p3 = vqrshrun_n_s16(row3, 1); - uint8x8_t p4 = vqrshrun_n_s16(row4, 1); - uint8x8_t p5 = vqrshrun_n_s16(row5, 1); - uint8x8_t p6 = vqrshrun_n_s16(row6, 1); - uint8x8_t p7 = vqrshrun_n_s16(row7, 1); - - // again, these can translate into one instruction, but often don't. -#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } -#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } -#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } - - // sadly can't use interleaved stores here since we only write - // 8 bytes to each scan line! - - // 8x8 8-bit transpose pass 1 - dct_trn8_8(p0, p1); - dct_trn8_8(p2, p3); - dct_trn8_8(p4, p5); - dct_trn8_8(p6, p7); - - // pass 2 - dct_trn8_16(p0, p2); - dct_trn8_16(p1, p3); - dct_trn8_16(p4, p6); - dct_trn8_16(p5, p7); - - // pass 3 - dct_trn8_32(p0, p4); - dct_trn8_32(p1, p5); - dct_trn8_32(p2, p6); - dct_trn8_32(p3, p7); - - // store - vst1_u8(out, p0); out += out_stride; - vst1_u8(out, p1); out += out_stride; - vst1_u8(out, p2); out += out_stride; - vst1_u8(out, p3); out += out_stride; - vst1_u8(out, p4); out += out_stride; - vst1_u8(out, p5); out += out_stride; - vst1_u8(out, p6); out += out_stride; - vst1_u8(out, p7); - -#undef dct_trn8_8 -#undef dct_trn8_16 -#undef dct_trn8_32 - } - -#undef dct_long_mul -#undef dct_long_mac -#undef dct_widen -#undef dct_wadd -#undef dct_wsub -#undef dct_bfly32o -#undef dct_pass -} - -#endif // STBI_NEON - -#define STBI__MARKER_none 0xff -// if there's a pending marker from the entropy stream, return that -// otherwise, fetch from the stream and get a marker. if there's no -// marker, return 0xff, which is never a valid marker value -static stbi_uc stbi__get_marker(stbi__jpeg *j) -{ - stbi_uc x; - if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } - x = stbi__get8(j->s); - if (x != 0xff) return STBI__MARKER_none; - while (x == 0xff) - x = stbi__get8(j->s); // consume repeated 0xff fill bytes - return x; -} - -// in each scan, we'll have scan_n components, and the order -// of the components is specified by order[] -#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) - -// after a restart interval, stbi__jpeg_reset the entropy decoder and -// the dc prediction -static void stbi__jpeg_reset(stbi__jpeg *j) -{ - j->code_bits = 0; - j->code_buffer = 0; - j->nomore = 0; - j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; - j->marker = STBI__MARKER_none; - j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; - j->eob_run = 0; - // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, - // since we don't even allow 1<<30 pixels -} - -static int stbi__parse_entropy_coded_data(stbi__jpeg *z) -{ - stbi__jpeg_reset(z); - if (!z->progressive) { - if (z->scan_n == 1) { - int i,j; - STBI_SIMD_ALIGN(short, data[64]); - int n = z->order[0]; - // non-interleaved data, we just need to process one block at a time, - // in trivial scanline order - // number of blocks to do just depends on how many actual "pixels" this - // component has, independent of interleaved MCU blocking and such - int w = (z->img_comp[n].x+7) >> 3; - int h = (z->img_comp[n].y+7) >> 3; - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) { - int ha = z->img_comp[n].ha; - if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; - z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); - // every data block is an MCU, so countdown the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - // if it's NOT a restart, then just bail, so we get corrupt data - // rather than no data - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } else { // interleaved - int i,j,k,x,y; - STBI_SIMD_ALIGN(short, data[64]); - for (j=0; j < z->img_mcu_y; ++j) { - for (i=0; i < z->img_mcu_x; ++i) { - // scan an interleaved mcu... process scan_n components in order - for (k=0; k < z->scan_n; ++k) { - int n = z->order[k]; - // scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component - for (y=0; y < z->img_comp[n].v; ++y) { - for (x=0; x < z->img_comp[n].h; ++x) { - int x2 = (i*z->img_comp[n].h + x)*8; - int y2 = (j*z->img_comp[n].v + y)*8; - int ha = z->img_comp[n].ha; - if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; - z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); - } - } - } - // after all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } - } else { - if (z->scan_n == 1) { - int i,j; - int n = z->order[0]; - // non-interleaved data, we just need to process one block at a time, - // in trivial scanline order - // number of blocks to do just depends on how many actual "pixels" this - // component has, independent of interleaved MCU blocking and such - int w = (z->img_comp[n].x+7) >> 3; - int h = (z->img_comp[n].y+7) >> 3; - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) { - short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); - if (z->spec_start == 0) { - if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) - return 0; - } else { - int ha = z->img_comp[n].ha; - if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) - return 0; - } - // every data block is an MCU, so countdown the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } else { // interleaved - int i,j,k,x,y; - for (j=0; j < z->img_mcu_y; ++j) { - for (i=0; i < z->img_mcu_x; ++i) { - // scan an interleaved mcu... process scan_n components in order - for (k=0; k < z->scan_n; ++k) { - int n = z->order[k]; - // scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component - for (y=0; y < z->img_comp[n].v; ++y) { - for (x=0; x < z->img_comp[n].h; ++x) { - int x2 = (i*z->img_comp[n].h + x); - int y2 = (j*z->img_comp[n].v + y); - short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); - if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) - return 0; - } - } - } - // after all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); - } - } - } - return 1; - } - } -} - -static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) -{ - int i; - for (i=0; i < 64; ++i) - data[i] *= dequant[i]; -} - -static void stbi__jpeg_finish(stbi__jpeg *z) -{ - if (z->progressive) { - // dequantize and idct the data - int i,j,n; - for (n=0; n < z->s->img_n; ++n) { - int w = (z->img_comp[n].x+7) >> 3; - int h = (z->img_comp[n].y+7) >> 3; - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) { - short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); - stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); - z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); - } - } - } - } -} - -static int stbi__process_marker(stbi__jpeg *z, int m) -{ - int L; - switch (m) { - case STBI__MARKER_none: // no marker found - return stbi__err("expected marker","Corrupt JPEG"); - - case 0xDD: // DRI - specify restart interval - if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); - z->restart_interval = stbi__get16be(z->s); - return 1; - - case 0xDB: // DQT - define quantization table - L = stbi__get16be(z->s)-2; - while (L > 0) { - int q = stbi__get8(z->s); - int p = q >> 4, sixteen = (p != 0); - int t = q & 15,i; - if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); - if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); - - for (i=0; i < 64; ++i) - z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); - L -= (sixteen ? 129 : 65); - } - return L==0; - - case 0xC4: // DHT - define huffman table - L = stbi__get16be(z->s)-2; - while (L > 0) { - stbi_uc *v; - int sizes[16],i,n=0; - int q = stbi__get8(z->s); - int tc = q >> 4; - int th = q & 15; - if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); - for (i=0; i < 16; ++i) { - sizes[i] = stbi__get8(z->s); - n += sizes[i]; - } - L -= 17; - if (tc == 0) { - if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; - v = z->huff_dc[th].values; - } else { - if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; - v = z->huff_ac[th].values; - } - for (i=0; i < n; ++i) - v[i] = stbi__get8(z->s); - if (tc != 0) - stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); - L -= n; - } - return L==0; - } - - // check for comment block or APP blocks - if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { - L = stbi__get16be(z->s); - if (L < 2) { - if (m == 0xFE) - return stbi__err("bad COM len","Corrupt JPEG"); - else - return stbi__err("bad APP len","Corrupt JPEG"); - } - L -= 2; - - if (m == 0xE0 && L >= 5) { // JFIF APP0 segment - static const unsigned char tag[5] = {'J','F','I','F','\0'}; - int ok = 1; - int i; - for (i=0; i < 5; ++i) - if (stbi__get8(z->s) != tag[i]) - ok = 0; - L -= 5; - if (ok) - z->jfif = 1; - } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment - static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; - int ok = 1; - int i; - for (i=0; i < 6; ++i) - if (stbi__get8(z->s) != tag[i]) - ok = 0; - L -= 6; - if (ok) { - stbi__get8(z->s); // version - stbi__get16be(z->s); // flags0 - stbi__get16be(z->s); // flags1 - z->app14_color_transform = stbi__get8(z->s); // color transform - L -= 6; - } - } - - stbi__skip(z->s, L); - return 1; - } - - return stbi__err("unknown marker","Corrupt JPEG"); -} - -// after we see SOS -static int stbi__process_scan_header(stbi__jpeg *z) -{ - int i; - int Ls = stbi__get16be(z->s); - z->scan_n = stbi__get8(z->s); - if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); - if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); - for (i=0; i < z->scan_n; ++i) { - int id = stbi__get8(z->s), which; - int q = stbi__get8(z->s); - for (which = 0; which < z->s->img_n; ++which) - if (z->img_comp[which].id == id) - break; - if (which == z->s->img_n) return 0; // no match - z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); - z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); - z->order[i] = which; - } - - { - int aa; - z->spec_start = stbi__get8(z->s); - z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 - aa = stbi__get8(z->s); - z->succ_high = (aa >> 4); - z->succ_low = (aa & 15); - if (z->progressive) { - if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) - return stbi__err("bad SOS", "Corrupt JPEG"); - } else { - if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); - if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); - z->spec_end = 63; - } - } - - return 1; -} - -static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) -{ - int i; - for (i=0; i < ncomp; ++i) { - if (z->img_comp[i].raw_data) { - STBI_FREE(z->img_comp[i].raw_data); - z->img_comp[i].raw_data = NULL; - z->img_comp[i].data = NULL; - } - if (z->img_comp[i].raw_coeff) { - STBI_FREE(z->img_comp[i].raw_coeff); - z->img_comp[i].raw_coeff = 0; - z->img_comp[i].coeff = 0; - } - if (z->img_comp[i].linebuf) { - STBI_FREE(z->img_comp[i].linebuf); - z->img_comp[i].linebuf = NULL; - } - } - return why; -} - -static int stbi__process_frame_header(stbi__jpeg *z, int scan) -{ - stbi__context *s = z->s; - int Lf,p,i,q, h_max=1,v_max=1,c; - Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG - p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline - s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG - s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires - c = stbi__get8(s); - if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); - s->img_n = c; - for (i=0; i < c; ++i) { - z->img_comp[i].data = NULL; - z->img_comp[i].linebuf = NULL; - } - - if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); - - z->rgb = 0; - for (i=0; i < s->img_n; ++i) { - static const unsigned char rgb[3] = { 'R', 'G', 'B' }; - z->img_comp[i].id = stbi__get8(s); - if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) - ++z->rgb; - q = stbi__get8(s); - z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); - z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); - z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); - } - - if (scan != STBI__SCAN_load) return 1; - - if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); - - for (i=0; i < s->img_n; ++i) { - if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; - if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; - } - - // compute interleaved mcu info - z->img_h_max = h_max; - z->img_v_max = v_max; - z->img_mcu_w = h_max * 8; - z->img_mcu_h = v_max * 8; - // these sizes can't be more than 17 bits - z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; - z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; - - for (i=0; i < s->img_n; ++i) { - // number of effective pixels (e.g. for non-interleaved MCU) - z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; - z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; - // to simplify generation, we'll allocate enough memory to decode - // the bogus oversized data from using interleaved MCUs and their - // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't - // discard the extra data until colorspace conversion - // - // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) - // so these muls can't overflow with 32-bit ints (which we require) - z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; - z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; - z->img_comp[i].coeff = 0; - z->img_comp[i].raw_coeff = 0; - z->img_comp[i].linebuf = NULL; - z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); - if (z->img_comp[i].raw_data == NULL) - return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); - // align blocks for idct using mmx/sse - z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); - if (z->progressive) { - // w2, h2 are multiples of 8 (see above) - z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; - z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; - z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); - if (z->img_comp[i].raw_coeff == NULL) - return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); - z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); - } - } - - return 1; -} - -// use comparisons since in some cases we handle more than one case (e.g. SOF) -#define stbi__DNL(x) ((x) == 0xdc) -#define stbi__SOI(x) ((x) == 0xd8) -#define stbi__EOI(x) ((x) == 0xd9) -#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) -#define stbi__SOS(x) ((x) == 0xda) - -#define stbi__SOF_progressive(x) ((x) == 0xc2) - -static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) -{ - int m; - z->jfif = 0; - z->app14_color_transform = -1; // valid values are 0,1,2 - z->marker = STBI__MARKER_none; // initialize cached marker to empty - m = stbi__get_marker(z); - if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); - if (scan == STBI__SCAN_type) return 1; - m = stbi__get_marker(z); - while (!stbi__SOF(m)) { - if (!stbi__process_marker(z,m)) return 0; - m = stbi__get_marker(z); - while (m == STBI__MARKER_none) { - // some files have extra padding after their blocks, so ok, we'll scan - if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); - m = stbi__get_marker(z); - } - } - z->progressive = stbi__SOF_progressive(m); - if (!stbi__process_frame_header(z, scan)) return 0; - return 1; -} - -// decode image to YCbCr format -static int stbi__decode_jpeg_image(stbi__jpeg *j) -{ - int m; - for (m = 0; m < 4; m++) { - j->img_comp[m].raw_data = NULL; - j->img_comp[m].raw_coeff = NULL; - } - j->restart_interval = 0; - if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; - m = stbi__get_marker(j); - while (!stbi__EOI(m)) { - if (stbi__SOS(m)) { - if (!stbi__process_scan_header(j)) return 0; - if (!stbi__parse_entropy_coded_data(j)) return 0; - if (j->marker == STBI__MARKER_none ) { - // handle 0s at the end of image data from IP Kamera 9060 - while (!stbi__at_eof(j->s)) { - int x = stbi__get8(j->s); - if (x == 255) { - j->marker = stbi__get8(j->s); - break; - } - } - // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 - } - } else if (stbi__DNL(m)) { - int Ld = stbi__get16be(j->s); - stbi__uint32 NL = stbi__get16be(j->s); - if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); - if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); - } else { - if (!stbi__process_marker(j, m)) return 0; - } - m = stbi__get_marker(j); - } - if (j->progressive) - stbi__jpeg_finish(j); - return 1; -} - -// static jfif-centered resampling (across block boundaries) - -typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, - int w, int hs); - -#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) - -static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - STBI_NOTUSED(out); - STBI_NOTUSED(in_far); - STBI_NOTUSED(w); - STBI_NOTUSED(hs); - return in_near; -} - -static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate two samples vertically for every one in input - int i; - STBI_NOTUSED(hs); - for (i=0; i < w; ++i) - out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); - return out; -} - -static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate two samples horizontally for every one in input - int i; - stbi_uc *input = in_near; - - if (w == 1) { - // if only one sample, can't do any interpolation - out[0] = out[1] = input[0]; - return out; - } - - out[0] = input[0]; - out[1] = stbi__div4(input[0]*3 + input[1] + 2); - for (i=1; i < w-1; ++i) { - int n = 3*input[i]+2; - out[i*2+0] = stbi__div4(n+input[i-1]); - out[i*2+1] = stbi__div4(n+input[i+1]); - } - out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); - out[i*2+1] = input[w-1]; - - STBI_NOTUSED(in_far); - STBI_NOTUSED(hs); - - return out; -} - -#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) - -static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate 2x2 samples for every one in input - int i,t0,t1; - if (w == 1) { - out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); - return out; - } - - t1 = 3*in_near[0] + in_far[0]; - out[0] = stbi__div4(t1+2); - for (i=1; i < w; ++i) { - t0 = t1; - t1 = 3*in_near[i]+in_far[i]; - out[i*2-1] = stbi__div16(3*t0 + t1 + 8); - out[i*2 ] = stbi__div16(3*t1 + t0 + 8); - } - out[w*2-1] = stbi__div4(t1+2); - - STBI_NOTUSED(hs); - - return out; -} - -#if defined(STBI_SSE2) || defined(STBI_NEON) -static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // need to generate 2x2 samples for every one in input - int i=0,t0,t1; - - if (w == 1) { - out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); - return out; - } - - t1 = 3*in_near[0] + in_far[0]; - // process groups of 8 pixels for as long as we can. - // note we can't handle the last pixel in a row in this loop - // because we need to handle the filter boundary conditions. - for (; i < ((w-1) & ~7); i += 8) { -#if defined(STBI_SSE2) - // load and perform the vertical filtering pass - // this uses 3*x + y = 4*x + (y - x) - __m128i zero = _mm_setzero_si128(); - __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); - __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); - __m128i farw = _mm_unpacklo_epi8(farb, zero); - __m128i nearw = _mm_unpacklo_epi8(nearb, zero); - __m128i diff = _mm_sub_epi16(farw, nearw); - __m128i nears = _mm_slli_epi16(nearw, 2); - __m128i curr = _mm_add_epi16(nears, diff); // current row - - // horizontal filter works the same based on shifted vers of current - // row. "prev" is current row shifted right by 1 pixel; we need to - // insert the previous pixel value (from t1). - // "next" is current row shifted left by 1 pixel, with first pixel - // of next block of 8 pixels added in. - __m128i prv0 = _mm_slli_si128(curr, 2); - __m128i nxt0 = _mm_srli_si128(curr, 2); - __m128i prev = _mm_insert_epi16(prv0, t1, 0); - __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); - - // horizontal filter, polyphase implementation since it's convenient: - // even pixels = 3*cur + prev = cur*4 + (prev - cur) - // odd pixels = 3*cur + next = cur*4 + (next - cur) - // note the shared term. - __m128i bias = _mm_set1_epi16(8); - __m128i curs = _mm_slli_epi16(curr, 2); - __m128i prvd = _mm_sub_epi16(prev, curr); - __m128i nxtd = _mm_sub_epi16(next, curr); - __m128i curb = _mm_add_epi16(curs, bias); - __m128i even = _mm_add_epi16(prvd, curb); - __m128i odd = _mm_add_epi16(nxtd, curb); - - // interleave even and odd pixels, then undo scaling. - __m128i int0 = _mm_unpacklo_epi16(even, odd); - __m128i int1 = _mm_unpackhi_epi16(even, odd); - __m128i de0 = _mm_srli_epi16(int0, 4); - __m128i de1 = _mm_srli_epi16(int1, 4); - - // pack and write output - __m128i outv = _mm_packus_epi16(de0, de1); - _mm_storeu_si128((__m128i *) (out + i*2), outv); -#elif defined(STBI_NEON) - // load and perform the vertical filtering pass - // this uses 3*x + y = 4*x + (y - x) - uint8x8_t farb = vld1_u8(in_far + i); - uint8x8_t nearb = vld1_u8(in_near + i); - int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); - int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); - int16x8_t curr = vaddq_s16(nears, diff); // current row - - // horizontal filter works the same based on shifted vers of current - // row. "prev" is current row shifted right by 1 pixel; we need to - // insert the previous pixel value (from t1). - // "next" is current row shifted left by 1 pixel, with first pixel - // of next block of 8 pixels added in. - int16x8_t prv0 = vextq_s16(curr, curr, 7); - int16x8_t nxt0 = vextq_s16(curr, curr, 1); - int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); - int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); - - // horizontal filter, polyphase implementation since it's convenient: - // even pixels = 3*cur + prev = cur*4 + (prev - cur) - // odd pixels = 3*cur + next = cur*4 + (next - cur) - // note the shared term. - int16x8_t curs = vshlq_n_s16(curr, 2); - int16x8_t prvd = vsubq_s16(prev, curr); - int16x8_t nxtd = vsubq_s16(next, curr); - int16x8_t even = vaddq_s16(curs, prvd); - int16x8_t odd = vaddq_s16(curs, nxtd); - - // undo scaling and round, then store with even/odd phases interleaved - uint8x8x2_t o; - o.val[0] = vqrshrun_n_s16(even, 4); - o.val[1] = vqrshrun_n_s16(odd, 4); - vst2_u8(out + i*2, o); -#endif - - // "previous" value for next iter - t1 = 3*in_near[i+7] + in_far[i+7]; - } - - t0 = t1; - t1 = 3*in_near[i] + in_far[i]; - out[i*2] = stbi__div16(3*t1 + t0 + 8); - - for (++i; i < w; ++i) { - t0 = t1; - t1 = 3*in_near[i]+in_far[i]; - out[i*2-1] = stbi__div16(3*t0 + t1 + 8); - out[i*2 ] = stbi__div16(3*t1 + t0 + 8); - } - out[w*2-1] = stbi__div4(t1+2); - - STBI_NOTUSED(hs); - - return out; -} -#endif - -static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) -{ - // resample with nearest-neighbor - int i,j; - STBI_NOTUSED(in_far); - for (i=0; i < w; ++i) - for (j=0; j < hs; ++j) - out[i*hs+j] = in_near[i]; - return out; -} - -// this is a reduced-precision calculation of YCbCr-to-RGB introduced -// to make sure the code produces the same results in both SIMD and scalar -#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) -static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) -{ - int i; - for (i=0; i < count; ++i) { - int y_fixed = (y[i] << 20) + (1<<19); // rounding - int r,g,b; - int cr = pcr[i] - 128; - int cb = pcb[i] - 128; - r = y_fixed + cr* stbi__float2fixed(1.40200f); - g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); - b = y_fixed + cb* stbi__float2fixed(1.77200f); - r >>= 20; - g >>= 20; - b >>= 20; - if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } - if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } - if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } - out[0] = (stbi_uc)r; - out[1] = (stbi_uc)g; - out[2] = (stbi_uc)b; - out[3] = 255; - out += step; - } -} - -#if defined(STBI_SSE2) || defined(STBI_NEON) -static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) -{ - int i = 0; - -#ifdef STBI_SSE2 - // step == 3 is pretty ugly on the final interleave, and i'm not convinced - // it's useful in practice (you wouldn't use it for textures, for example). - // so just accelerate step == 4 case. - if (step == 4) { - // this is a fairly straightforward implementation and not super-optimized. - __m128i signflip = _mm_set1_epi8(-0x80); - __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); - __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); - __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); - __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); - __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); - __m128i xw = _mm_set1_epi16(255); // alpha channel - - for (; i+7 < count; i += 8) { - // load - __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); - __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); - __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); - __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 - __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 - - // unpack to short (and left-shift cr, cb by 8) - __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); - __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); - __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); - - // color transform - __m128i yws = _mm_srli_epi16(yw, 4); - __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); - __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); - __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); - __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); - __m128i rws = _mm_add_epi16(cr0, yws); - __m128i gwt = _mm_add_epi16(cb0, yws); - __m128i bws = _mm_add_epi16(yws, cb1); - __m128i gws = _mm_add_epi16(gwt, cr1); - - // descale - __m128i rw = _mm_srai_epi16(rws, 4); - __m128i bw = _mm_srai_epi16(bws, 4); - __m128i gw = _mm_srai_epi16(gws, 4); - - // back to byte, set up for transpose - __m128i brb = _mm_packus_epi16(rw, bw); - __m128i gxb = _mm_packus_epi16(gw, xw); - - // transpose to interleave channels - __m128i t0 = _mm_unpacklo_epi8(brb, gxb); - __m128i t1 = _mm_unpackhi_epi8(brb, gxb); - __m128i o0 = _mm_unpacklo_epi16(t0, t1); - __m128i o1 = _mm_unpackhi_epi16(t0, t1); - - // store - _mm_storeu_si128((__m128i *) (out + 0), o0); - _mm_storeu_si128((__m128i *) (out + 16), o1); - out += 32; - } - } -#endif - -#ifdef STBI_NEON - // in this version, step=3 support would be easy to add. but is there demand? - if (step == 4) { - // this is a fairly straightforward implementation and not super-optimized. - uint8x8_t signflip = vdup_n_u8(0x80); - int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); - int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); - int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); - int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); - - for (; i+7 < count; i += 8) { - // load - uint8x8_t y_bytes = vld1_u8(y + i); - uint8x8_t cr_bytes = vld1_u8(pcr + i); - uint8x8_t cb_bytes = vld1_u8(pcb + i); - int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); - int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); - - // expand to s16 - int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); - int16x8_t crw = vshll_n_s8(cr_biased, 7); - int16x8_t cbw = vshll_n_s8(cb_biased, 7); - - // color transform - int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); - int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); - int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); - int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); - int16x8_t rws = vaddq_s16(yws, cr0); - int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); - int16x8_t bws = vaddq_s16(yws, cb1); - - // undo scaling, round, convert to byte - uint8x8x4_t o; - o.val[0] = vqrshrun_n_s16(rws, 4); - o.val[1] = vqrshrun_n_s16(gws, 4); - o.val[2] = vqrshrun_n_s16(bws, 4); - o.val[3] = vdup_n_u8(255); - - // store, interleaving r/g/b/a - vst4_u8(out, o); - out += 8*4; - } - } -#endif - - for (; i < count; ++i) { - int y_fixed = (y[i] << 20) + (1<<19); // rounding - int r,g,b; - int cr = pcr[i] - 128; - int cb = pcb[i] - 128; - r = y_fixed + cr* stbi__float2fixed(1.40200f); - g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); - b = y_fixed + cb* stbi__float2fixed(1.77200f); - r >>= 20; - g >>= 20; - b >>= 20; - if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } - if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } - if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } - out[0] = (stbi_uc)r; - out[1] = (stbi_uc)g; - out[2] = (stbi_uc)b; - out[3] = 255; - out += step; - } -} -#endif - -// set up the kernels -static void stbi__setup_jpeg(stbi__jpeg *j) -{ - j->idct_block_kernel = stbi__idct_block; - j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; - j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; - -#ifdef STBI_SSE2 - if (stbi__sse2_available()) { - j->idct_block_kernel = stbi__idct_simd; - j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; - j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; - } -#endif - -#ifdef STBI_NEON - j->idct_block_kernel = stbi__idct_simd; - j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; - j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; -#endif -} - -// clean up the temporary component buffers -static void stbi__cleanup_jpeg(stbi__jpeg *j) -{ - stbi__free_jpeg_components(j, j->s->img_n, 0); -} - -typedef struct -{ - resample_row_func resample; - stbi_uc *line0,*line1; - int hs,vs; // expansion factor in each axis - int w_lores; // horizontal pixels pre-expansion - int ystep; // how far through vertical expansion we are - int ypos; // which pre-expansion row we're on -} stbi__resample; - -// fast 0..255 * 0..255 => 0..255 rounded multiplication -static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) -{ - unsigned int t = x*y + 128; - return (stbi_uc) ((t + (t >>8)) >> 8); -} - -static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) -{ - int n, decode_n, is_rgb; - z->s->img_n = 0; // make stbi__cleanup_jpeg safe - - // validate req_comp - if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); - - // load a jpeg image from whichever source, but leave in YCbCr format - if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } - - // determine actual number of components to generate - n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; - - is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); - - if (z->s->img_n == 3 && n < 3 && !is_rgb) - decode_n = 1; - else - decode_n = z->s->img_n; - - // resample and color-convert - { - int k; - unsigned int i,j; - stbi_uc *output; - stbi_uc *coutput[4]; - - stbi__resample res_comp[4]; - - for (k=0; k < decode_n; ++k) { - stbi__resample *r = &res_comp[k]; - - // allocate line buffer big enough for upsampling off the edges - // with upsample factor of 4 - z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); - if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } - - r->hs = z->img_h_max / z->img_comp[k].h; - r->vs = z->img_v_max / z->img_comp[k].v; - r->ystep = r->vs >> 1; - r->w_lores = (z->s->img_x + r->hs-1) / r->hs; - r->ypos = 0; - r->line0 = r->line1 = z->img_comp[k].data; - - if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; - else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; - else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; - else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; - else r->resample = stbi__resample_row_generic; - } - - // can't error after this so, this is safe - output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); - if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } - - // now go ahead and resample - for (j=0; j < z->s->img_y; ++j) { - stbi_uc *out = output + n * z->s->img_x * j; - for (k=0; k < decode_n; ++k) { - stbi__resample *r = &res_comp[k]; - int y_bot = r->ystep >= (r->vs >> 1); - coutput[k] = r->resample(z->img_comp[k].linebuf, - y_bot ? r->line1 : r->line0, - y_bot ? r->line0 : r->line1, - r->w_lores, r->hs); - if (++r->ystep >= r->vs) { - r->ystep = 0; - r->line0 = r->line1; - if (++r->ypos < z->img_comp[k].y) - r->line1 += z->img_comp[k].w2; - } - } - if (n >= 3) { - stbi_uc *y = coutput[0]; - if (z->s->img_n == 3) { - if (is_rgb) { - for (i=0; i < z->s->img_x; ++i) { - out[0] = y[i]; - out[1] = coutput[1][i]; - out[2] = coutput[2][i]; - out[3] = 255; - out += n; - } - } else { - z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); - } - } else if (z->s->img_n == 4) { - if (z->app14_color_transform == 0) { // CMYK - for (i=0; i < z->s->img_x; ++i) { - stbi_uc m = coutput[3][i]; - out[0] = stbi__blinn_8x8(coutput[0][i], m); - out[1] = stbi__blinn_8x8(coutput[1][i], m); - out[2] = stbi__blinn_8x8(coutput[2][i], m); - out[3] = 255; - out += n; - } - } else if (z->app14_color_transform == 2) { // YCCK - z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); - for (i=0; i < z->s->img_x; ++i) { - stbi_uc m = coutput[3][i]; - out[0] = stbi__blinn_8x8(255 - out[0], m); - out[1] = stbi__blinn_8x8(255 - out[1], m); - out[2] = stbi__blinn_8x8(255 - out[2], m); - out += n; - } - } else { // YCbCr + alpha? Ignore the fourth channel for now - z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); - } - } else - for (i=0; i < z->s->img_x; ++i) { - out[0] = out[1] = out[2] = y[i]; - out[3] = 255; // not used if n==3 - out += n; - } - } else { - if (is_rgb) { - if (n == 1) - for (i=0; i < z->s->img_x; ++i) - *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); - else { - for (i=0; i < z->s->img_x; ++i, out += 2) { - out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); - out[1] = 255; - } - } - } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { - for (i=0; i < z->s->img_x; ++i) { - stbi_uc m = coutput[3][i]; - stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); - stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); - stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); - out[0] = stbi__compute_y(r, g, b); - out[1] = 255; - out += n; - } - } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { - for (i=0; i < z->s->img_x; ++i) { - out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); - out[1] = 255; - out += n; - } - } else { - stbi_uc *y = coutput[0]; - if (n == 1) - for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; - else - for (i=0; i < z->s->img_x; ++i) *out++ = y[i], *out++ = 255; - } - } - } - stbi__cleanup_jpeg(z); - *out_x = z->s->img_x; - *out_y = z->s->img_y; - if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output - return output; - } -} - -static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - unsigned char* result; - stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); - if (!j) { - stbi__errpuc("outofmem", "Out of memory"); - return nullptr; - } - STBI_NOTUSED(ri); - j->s = s; - stbi__setup_jpeg(j); - result = load_jpeg_image(j, x,y,comp,req_comp); - STBI_FREE(j); - return result; -} - -static int stbi__jpeg_test(stbi__context *s) -{ - int r; - stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); - if (!j) { - stbi__errpuc("outofmem", "Out of memory"); - return 0; - } - j->s = s; - stbi__setup_jpeg(j); - r = stbi__decode_jpeg_header(j, STBI__SCAN_type); - stbi__rewind(s); - STBI_FREE(j); - return r; -} - -static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) -{ - if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { - stbi__rewind( j->s ); - return 0; - } - if (x) *x = j->s->img_x; - if (y) *y = j->s->img_y; - if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; - return 1; -} - -static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) -{ - int result; - stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); - if (!j) { - stbi__errpuc("outofmem", "Out of memory"); - return 0; - } - j->s = s; - result = stbi__jpeg_info_raw(j, x, y, comp); - STBI_FREE(j); - return result; -} -#endif - -// public domain zlib decode v0.2 Sean Barrett 2006-11-18 -// simple implementation -// - all input must be provided in an upfront buffer -// - all output is written to a single output buffer (can malloc/realloc) -// performance -// - fast huffman - -#ifndef STBI_NO_ZLIB - -// fast-way is faster to check than jpeg huffman, but slow way is slower -#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables -#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) - -// zlib-style huffman encoding -// (jpegs packs from left, zlib from right, so can't share code) -typedef struct -{ - stbi__uint16 fast[1 << STBI__ZFAST_BITS]; - stbi__uint16 firstcode[16]; - int maxcode[17]; - stbi__uint16 firstsymbol[16]; - stbi_uc size[288]; - stbi__uint16 value[288]; -} stbi__zhuffman; - -stbi_inline static int stbi__bitreverse16(int n) -{ - n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); - n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); - n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); - n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); - return n; -} - -stbi_inline static int stbi__bit_reverse(int v, int bits) -{ - STBI_ASSERT(bits <= 16); - // to bit reverse n bits, reverse 16 and shift - // e.g. 11 bits, bit reverse and shift away 5 - return stbi__bitreverse16(v) >> (16-bits); -} - -static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) -{ - int i,k=0; - int code, next_code[16], sizes[17]; - - // DEFLATE spec for generating codes - memset(sizes, 0, sizeof(sizes)); - memset(z->fast, 0, sizeof(z->fast)); - for (i=0; i < num; ++i) - ++sizes[sizelist[i]]; - sizes[0] = 0; - for (i=1; i < 16; ++i) - if (sizes[i] > (1 << i)) - return stbi__err("bad sizes", "Corrupt PNG"); - code = 0; - for (i=1; i < 16; ++i) { - next_code[i] = code; - z->firstcode[i] = (stbi__uint16) code; - z->firstsymbol[i] = (stbi__uint16) k; - code = (code + sizes[i]); - if (sizes[i]) - if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); - z->maxcode[i] = code << (16-i); // preshift for inner loop - code <<= 1; - k += sizes[i]; - } - z->maxcode[16] = 0x10000; // sentinel - for (i=0; i < num; ++i) { - int s = sizelist[i]; - if (s) { - int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; - stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); - z->size [c] = (stbi_uc ) s; - z->value[c] = (stbi__uint16) i; - if (s <= STBI__ZFAST_BITS) { - int j = stbi__bit_reverse(next_code[s],s); - while (j < (1 << STBI__ZFAST_BITS)) { - z->fast[j] = fastv; - j += (1 << s); - } - } - ++next_code[s]; - } - } - return 1; -} - -// zlib-from-memory implementation for PNG reading -// because PNG allows splitting the zlib stream arbitrarily, -// and it's annoying structurally to have PNG call ZLIB call PNG, -// we require PNG read all the IDATs and combine them into a single -// memory buffer - -typedef struct -{ - stbi_uc *zbuffer, *zbuffer_end; - int num_bits; - stbi__uint32 code_buffer; - - char *zout; - char *zout_start; - char *zout_end; - int z_expandable; - - stbi__zhuffman z_length, z_distance; -} stbi__zbuf; - -stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) -{ - if (z->zbuffer >= z->zbuffer_end) return 0; - return *z->zbuffer++; -} - -static void stbi__fill_bits(stbi__zbuf *z) -{ - do { - STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); - z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; - z->num_bits += 8; - } while (z->num_bits <= 24); -} - -stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) -{ - unsigned int k; - if (z->num_bits < n) stbi__fill_bits(z); - k = z->code_buffer & ((1 << n) - 1); - z->code_buffer >>= n; - z->num_bits -= n; - return k; -} - -static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) -{ - int b,s,k; - // not resolved by fast table, so compute it the slow way - // use jpeg approach, which requires MSbits at top - k = stbi__bit_reverse(a->code_buffer, 16); - for (s=STBI__ZFAST_BITS+1; ; ++s) - if (k < z->maxcode[s]) - break; - if (s == 16) return -1; // invalid code! - // code size is s, so: - b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; - STBI_ASSERT(z->size[b] == s); - a->code_buffer >>= s; - a->num_bits -= s; - return z->value[b]; -} - -stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) -{ - int b,s; - if (a->num_bits < 16) stbi__fill_bits(a); - b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; - if (b) { - s = b >> 9; - a->code_buffer >>= s; - a->num_bits -= s; - return b & 511; - } - return stbi__zhuffman_decode_slowpath(a, z); -} - -static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes -{ - char *q; - int cur, limit, old_limit; - z->zout = zout; - if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); - cur = (int) (z->zout - z->zout_start); - limit = old_limit = (int) (z->zout_end - z->zout_start); - while (cur + n > limit) - limit *= 2; - q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); - STBI_NOTUSED(old_limit); - if (q == NULL) return stbi__err("outofmem", "Out of memory"); - z->zout_start = q; - z->zout = q + cur; - z->zout_end = q + limit; - return 1; -} - -static const int stbi__zlength_base[31] = { - 3,4,5,6,7,8,9,10,11,13, - 15,17,19,23,27,31,35,43,51,59, - 67,83,99,115,131,163,195,227,258,0,0 }; - -static const int stbi__zlength_extra[31]= -{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; - -static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, -257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; - -static const int stbi__zdist_extra[32] = -{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; - -static int stbi__parse_huffman_block(stbi__zbuf *a) -{ - char *zout = a->zout; - for(;;) { - int z = stbi__zhuffman_decode(a, &a->z_length); - if (z < 256) { - if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes - if (zout >= a->zout_end) { - if (!stbi__zexpand(a, zout, 1)) return 0; - zout = a->zout; - } - *zout++ = (char) z; - } else { - stbi_uc *p; - int len,dist; - if (z == 256) { - a->zout = zout; - return 1; - } - z -= 257; - len = stbi__zlength_base[z]; - if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); - z = stbi__zhuffman_decode(a, &a->z_distance); - if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); - dist = stbi__zdist_base[z]; - if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); - if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); - if (zout + len > a->zout_end) { - if (!stbi__zexpand(a, zout, len)) return 0; - zout = a->zout; - } - p = (stbi_uc *) (zout - dist); - if (dist == 1) { // run of one byte; common in images. - stbi_uc v = *p; - if (len) { do *zout++ = v; while (--len); } - } else { - if (len) { do *zout++ = *p++; while (--len); } - } - } - } -} - -static int stbi__compute_huffman_codes(stbi__zbuf *a) -{ - static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; - stbi__zhuffman z_codelength; - stbi_uc lencodes[286+32+137];//padding for maximum single op - stbi_uc codelength_sizes[19]; - int i,n; - - int hlit = stbi__zreceive(a,5) + 257; - int hdist = stbi__zreceive(a,5) + 1; - int hclen = stbi__zreceive(a,4) + 4; - int ntot = hlit + hdist; - - memset(codelength_sizes, 0, sizeof(codelength_sizes)); - for (i=0; i < hclen; ++i) { - int s = stbi__zreceive(a,3); - codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; - } - if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; - - n = 0; - while (n < ntot) { - int c = stbi__zhuffman_decode(a, &z_codelength); - if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); - if (c < 16) - lencodes[n++] = (stbi_uc) c; - else { - stbi_uc fill = 0; - if (c == 16) { - c = stbi__zreceive(a,2)+3; - if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); - fill = lencodes[n-1]; - } else if (c == 17) - c = stbi__zreceive(a,3)+3; - else { - STBI_ASSERT(c == 18); - c = stbi__zreceive(a,7)+11; - } - if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); - memset(lencodes+n, fill, c); - n += c; - } - } - if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); - if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; - if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; - return 1; -} - -static int stbi__parse_uncompressed_block(stbi__zbuf *a) -{ - stbi_uc header[4]; - int len,nlen,k; - if (a->num_bits & 7) - stbi__zreceive(a, a->num_bits & 7); // discard - // drain the bit-packed data into header - k = 0; - while (a->num_bits > 0) { - header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check - a->code_buffer >>= 8; - a->num_bits -= 8; - } - STBI_ASSERT(a->num_bits == 0); - // now fill header the normal way - while (k < 4) - header[k++] = stbi__zget8(a); - len = header[1] * 256 + header[0]; - nlen = header[3] * 256 + header[2]; - if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); - if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); - if (a->zout + len > a->zout_end) - if (!stbi__zexpand(a, a->zout, len)) return 0; - memcpy(a->zout, a->zbuffer, len); - a->zbuffer += len; - a->zout += len; - return 1; -} - -static int stbi__parse_zlib_header(stbi__zbuf *a) -{ - int cmf = stbi__zget8(a); - int cm = cmf & 15; - /* int cinfo = cmf >> 4; */ - int flg = stbi__zget8(a); - if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec - if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png - if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png - // window = 1 << (8 + cinfo)... but who cares, we fully buffer output - return 1; -} - -static const stbi_uc stbi__zdefault_length[288] = -{ - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 -}; -static const stbi_uc stbi__zdefault_distance[32] = -{ - 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 -}; -/* -Init algorithm: -{ - int i; // use <= to match clearly with spec - for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; - for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; - for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; - for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; - - for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; -} -*/ - -static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) -{ - int final, type; - if (parse_header) - if (!stbi__parse_zlib_header(a)) return 0; - a->num_bits = 0; - a->code_buffer = 0; - do { - final = stbi__zreceive(a,1); - type = stbi__zreceive(a,2); - if (type == 0) { - if (!stbi__parse_uncompressed_block(a)) return 0; - } else if (type == 3) { - return 0; - } else { - if (type == 1) { - // use fixed code lengths - if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) return 0; - if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; - } else { - if (!stbi__compute_huffman_codes(a)) return 0; - } - if (!stbi__parse_huffman_block(a)) return 0; - } - } while (!final); - return 1; -} - -static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) -{ - a->zout_start = obuf; - a->zout = obuf; - a->zout_end = obuf + olen; - a->z_expandable = exp; - - return stbi__parse_zlib(a, parse_header); -} - -STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) -{ - stbi__zbuf a; - char *p = (char *) stbi__malloc(initial_size); - if (p == NULL) return NULL; - a.zbuffer = (stbi_uc *) buffer; - a.zbuffer_end = (stbi_uc *) buffer + len; - if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - STBI_FREE(a.zout_start); - return NULL; - } -} - -STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) -{ - return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); -} - -STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) -{ - stbi__zbuf a; - char *p = (char *) stbi__malloc(initial_size); - if (p == NULL) return NULL; - a.zbuffer = (stbi_uc *) buffer; - a.zbuffer_end = (stbi_uc *) buffer + len; - if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - STBI_FREE(a.zout_start); - return NULL; - } -} - -STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) -{ - stbi__zbuf a; - a.zbuffer = (stbi_uc *) ibuffer; - a.zbuffer_end = (stbi_uc *) ibuffer + ilen; - if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) - return (int) (a.zout - a.zout_start); - else - return -1; -} - -STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) -{ - stbi__zbuf a; - char *p = (char *) stbi__malloc(16384); - if (p == NULL) return NULL; - a.zbuffer = (stbi_uc *) buffer; - a.zbuffer_end = (stbi_uc *) buffer+len; - if (stbi__do_zlib(&a, p, 16384, 1, 0)) { - if (outlen) *outlen = (int) (a.zout - a.zout_start); - return a.zout_start; - } else { - STBI_FREE(a.zout_start); - return NULL; - } -} - -STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) -{ - stbi__zbuf a; - a.zbuffer = (stbi_uc *) ibuffer; - a.zbuffer_end = (stbi_uc *) ibuffer + ilen; - if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) - return (int) (a.zout - a.zout_start); - else - return -1; -} -#endif - -// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 -// simple implementation -// - only 8-bit samples -// - no CRC checking -// - allocates lots of intermediate memory -// - avoids problem of streaming data between subsystems -// - avoids explicit window management -// performance -// - uses stb_zlib, a PD zlib implementation with fast huffman decoding - -#ifndef STBI_NO_PNG -typedef struct -{ - stbi__uint32 length; - stbi__uint32 type; -} stbi__pngchunk; - -static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) -{ - stbi__pngchunk c; - c.length = stbi__get32be(s); - c.type = stbi__get32be(s); - return c; -} - -static int stbi__check_png_header(stbi__context *s) -{ - static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; - int i; - for (i=0; i < 8; ++i) - if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); - return 1; -} - -typedef struct -{ - stbi__context *s; - stbi_uc *idata, *expanded, *out; - int depth; -} stbi__png; - - -enum { - STBI__F_none=0, - STBI__F_sub=1, - STBI__F_up=2, - STBI__F_avg=3, - STBI__F_paeth=4, - // synthetic filters used for first scanline to avoid needing a dummy row of 0s - STBI__F_avg_first, - STBI__F_paeth_first -}; - -static stbi_uc first_row_filter[5] = -{ - STBI__F_none, - STBI__F_sub, - STBI__F_none, - STBI__F_avg_first, - STBI__F_paeth_first -}; - -static int stbi__paeth(int a, int b, int c) -{ - int p = a + b - c; - int pa = abs(p-a); - int pb = abs(p-b); - int pc = abs(p-c); - if (pa <= pb && pa <= pc) return a; - if (pb <= pc) return b; - return c; -} - -static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; - -// create the png data from post-deflated data -static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) -{ - int bytes = (depth == 16? 2 : 1); - stbi__context *s = a->s; - stbi__uint32 i,j,stride = x*out_n*bytes; - stbi__uint32 img_len, img_width_bytes; - int k; - int img_n = s->img_n; // copy it into a local for later - - int output_bytes = out_n*bytes; - int filter_bytes = img_n*bytes; - int width = x; - - STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); - a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into - if (!a->out) return stbi__err("outofmem", "Out of memory"); - - if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); - img_width_bytes = (((img_n * x * depth) + 7) >> 3); - img_len = (img_width_bytes + 1) * y; - - // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, - // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), - // so just check for raw_len < img_len always. - if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); - - for (j=0; j < y; ++j) { - stbi_uc *cur = a->out + stride*j; - stbi_uc *prior; - int filter = *raw++; - - if (filter > 4) - return stbi__err("invalid filter","Corrupt PNG"); - - if (depth < 8) { - STBI_ASSERT(img_width_bytes <= x); - cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place - filter_bytes = 1; - width = img_width_bytes; - } - prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above - - // if first row, use special filter that doesn't sample previous row - if (j == 0) filter = first_row_filter[filter]; - - // handle first byte explicitly - for (k=0; k < filter_bytes; ++k) { - switch (filter) { - case STBI__F_none : cur[k] = raw[k]; break; - case STBI__F_sub : cur[k] = raw[k]; break; - case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; - case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; - case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; - case STBI__F_avg_first : cur[k] = raw[k]; break; - case STBI__F_paeth_first: cur[k] = raw[k]; break; - } - } - - if (depth == 8) { - if (img_n != out_n) - cur[img_n] = 255; // first pixel - raw += img_n; - cur += out_n; - prior += out_n; - } else if (depth == 16) { - if (img_n != out_n) { - cur[filter_bytes] = 255; // first pixel top byte - cur[filter_bytes+1] = 255; // first pixel bottom byte - } - raw += filter_bytes; - cur += output_bytes; - prior += output_bytes; - } else { - raw += 1; - cur += 1; - prior += 1; - } - - // this is a little gross, so that we don't switch per-pixel or per-component - if (depth < 8 || img_n == out_n) { - int nk = (width - 1)*filter_bytes; - #define STBI__CASE(f) \ - case f: \ - for (k=0; k < nk; ++k) - switch (filter) { - // "none" filter turns into a memcpy here; make that explicit. - case STBI__F_none: memcpy(cur, raw, nk); break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; - } - #undef STBI__CASE - raw += nk; - } else { - STBI_ASSERT(img_n+1 == out_n); - #define STBI__CASE(f) \ - case f: \ - for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ - for (k=0; k < filter_bytes; ++k) - switch (filter) { - STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; - } - #undef STBI__CASE - - // the loop above sets the high byte of the pixels' alpha, but for - // 16 bit png files we also need the low byte set. we'll do that here. - if (depth == 16) { - cur = a->out + stride*j; // start at the beginning of the row again - for (i=0; i < x; ++i,cur+=output_bytes) { - cur[filter_bytes+1] = 255; - } - } - } - } - - // we make a separate pass to expand bits to pixels; for performance, - // this could run two scanlines behind the above code, so it won't - // intefere with filtering but will still be in the cache. - if (depth < 8) { - for (j=0; j < y; ++j) { - stbi_uc *cur = a->out + stride*j; - stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; - // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit - // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop - stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range - - // note that the final byte might overshoot and write more data than desired. - // we can allocate enough data that this never writes out of memory, but it - // could also overwrite the next scanline. can it overwrite non-empty data - // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. - // so we need to explicitly clamp the final ones - - if (depth == 4) { - for (k=x*img_n; k >= 2; k-=2, ++in) { - *cur++ = scale * ((*in >> 4) ); - *cur++ = scale * ((*in ) & 0x0f); - } - if (k > 0) *cur++ = scale * ((*in >> 4) ); - } else if (depth == 2) { - for (k=x*img_n; k >= 4; k-=4, ++in) { - *cur++ = scale * ((*in >> 6) ); - *cur++ = scale * ((*in >> 4) & 0x03); - *cur++ = scale * ((*in >> 2) & 0x03); - *cur++ = scale * ((*in ) & 0x03); - } - if (k > 0) *cur++ = scale * ((*in >> 6) ); - if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); - if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); - } else if (depth == 1) { - for (k=x*img_n; k >= 8; k-=8, ++in) { - *cur++ = scale * ((*in >> 7) ); - *cur++ = scale * ((*in >> 6) & 0x01); - *cur++ = scale * ((*in >> 5) & 0x01); - *cur++ = scale * ((*in >> 4) & 0x01); - *cur++ = scale * ((*in >> 3) & 0x01); - *cur++ = scale * ((*in >> 2) & 0x01); - *cur++ = scale * ((*in >> 1) & 0x01); - *cur++ = scale * ((*in ) & 0x01); - } - if (k > 0) *cur++ = scale * ((*in >> 7) ); - if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); - if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); - if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); - if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); - if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); - if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); - } - if (img_n != out_n) { - int q; - // insert alpha = 255 - cur = a->out + stride*j; - if (img_n == 1) { - for (q=x-1; q >= 0; --q) { - cur[q*2+1] = 255; - cur[q*2+0] = cur[q]; - } - } else { - STBI_ASSERT(img_n == 3); - for (q=x-1; q >= 0; --q) { - cur[q*4+3] = 255; - cur[q*4+2] = cur[q*3+2]; - cur[q*4+1] = cur[q*3+1]; - cur[q*4+0] = cur[q*3+0]; - } - } - } - } - } else if (depth == 16) { - // force the image data from big-endian to platform-native. - // this is done in a separate pass due to the decoding relying - // on the data being untouched, but could probably be done - // per-line during decode if care is taken. - stbi_uc *cur = a->out; - stbi__uint16 *cur16 = (stbi__uint16*)cur; - - for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { - *cur16 = (cur[0] << 8) | cur[1]; - } - } - - return 1; -} - -static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) -{ - int bytes = (depth == 16 ? 2 : 1); - int out_bytes = out_n * bytes; - stbi_uc *final; - int p; - if (!interlaced) - return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); - - // de-interlacing - final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); - for (p=0; p < 7; ++p) { - int xorig[] = { 0,4,0,2,0,1,0 }; - int yorig[] = { 0,0,4,0,2,0,1 }; - int xspc[] = { 8,8,4,4,2,2,1 }; - int yspc[] = { 8,8,8,4,4,2,2 }; - int i,j,x,y; - // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 - x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; - y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; - if (x && y) { - stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; - if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { - STBI_FREE(final); - return 0; - } - for (j=0; j < y; ++j) { - for (i=0; i < x; ++i) { - int out_y = j*yspc[p]+yorig[p]; - int out_x = i*xspc[p]+xorig[p]; - memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, - a->out + (j*x+i)*out_bytes, out_bytes); - } - } - STBI_FREE(a->out); - a->out = NULL; - image_data += img_len; - image_data_len -= img_len; - } - } - a->out = final; - - return 1; -} - -static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) -{ - stbi__context *s = z->s; - stbi__uint32 i, pixel_count = s->img_x * s->img_y; - stbi_uc *p = z->out; - - // compute color-based transparency, assuming we've - // already got 255 as the alpha value in the output - STBI_ASSERT(out_n == 2 || out_n == 4); - - if (out_n == 2) { - for (i=0; i < pixel_count; ++i) { - p[1] = (p[0] == tc[0] ? 0 : 255); - p += 2; - } - } else { - for (i=0; i < pixel_count; ++i) { - if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) - p[3] = 0; - p += 4; - } - } - return 1; -} - -static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) -{ - stbi__context *s = z->s; - stbi__uint32 i, pixel_count = s->img_x * s->img_y; - stbi__uint16 *p = (stbi__uint16*) z->out; - - // compute color-based transparency, assuming we've - // already got 65535 as the alpha value in the output - STBI_ASSERT(out_n == 2 || out_n == 4); - - if (out_n == 2) { - for (i = 0; i < pixel_count; ++i) { - p[1] = (p[0] == tc[0] ? 0 : 65535); - p += 2; - } - } else { - for (i = 0; i < pixel_count; ++i) { - if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) - p[3] = 0; - p += 4; - } - } - return 1; -} - -static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) -{ - stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; - stbi_uc *p, *temp_out, *orig = a->out; - - p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); - if (p == NULL) return stbi__err("outofmem", "Out of memory"); - - // between here and free(out) below, exitting would leak - temp_out = p; - - if (pal_img_n == 3) { - for (i=0; i < pixel_count; ++i) { - int n = orig[i]*4; - p[0] = palette[n ]; - p[1] = palette[n+1]; - p[2] = palette[n+2]; - p += 3; - } - } else { - for (i=0; i < pixel_count; ++i) { - int n = orig[i]*4; - p[0] = palette[n ]; - p[1] = palette[n+1]; - p[2] = palette[n+2]; - p[3] = palette[n+3]; - p += 4; - } - } - STBI_FREE(a->out); - a->out = temp_out; - - STBI_NOTUSED(len); - - return 1; -} - -static int stbi__unpremultiply_on_load = 0; -static int stbi__de_iphone_flag = 0; - -STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) -{ - stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply; -} - -STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) -{ - stbi__de_iphone_flag = flag_true_if_should_convert; -} - -static void stbi__de_iphone(stbi__png *z) -{ - stbi__context *s = z->s; - stbi__uint32 i, pixel_count = s->img_x * s->img_y; - stbi_uc *p = z->out; - - if (s->img_out_n == 3) { // convert bgr to rgb - for (i=0; i < pixel_count; ++i) { - stbi_uc t = p[0]; - p[0] = p[2]; - p[2] = t; - p += 3; - } - } else { - STBI_ASSERT(s->img_out_n == 4); - if (stbi__unpremultiply_on_load) { - // convert bgr to rgb and unpremultiply - for (i=0; i < pixel_count; ++i) { - stbi_uc a = p[3]; - stbi_uc t = p[0]; - if (a) { - stbi_uc half = a / 2; - p[0] = (p[2] * 255 + half) / a; - p[1] = (p[1] * 255 + half) / a; - p[2] = ( t * 255 + half) / a; - } else { - p[0] = p[2]; - p[2] = t; - } - p += 4; - } - } else { - // convert bgr to rgb - for (i=0; i < pixel_count; ++i) { - stbi_uc t = p[0]; - p[0] = p[2]; - p[2] = t; - p += 4; - } - } - } -} - -#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) - -static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) -{ - stbi_uc palette[1024], pal_img_n=0; - stbi_uc has_trans=0, tc[3]; - stbi__uint16 tc16[3]; - stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; - int first=1,k,interlace=0, color=0, is_iphone=0; - stbi__context *s = z->s; - - z->expanded = NULL; - z->idata = NULL; - z->out = NULL; - - if (!stbi__check_png_header(s)) return 0; - - if (scan == STBI__SCAN_type) return 1; - - for (;;) { - stbi__pngchunk c = stbi__get_chunk_header(s); - switch (c.type) { - case STBI__PNG_TYPE('C','g','B','I'): - is_iphone = 1; - stbi__skip(s, c.length); - break; - case STBI__PNG_TYPE('I','H','D','R'): { - int comp,filter; - if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); - first = 0; - if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); - s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); - s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); - z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); - color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); - if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); - if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); - comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); - filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); - interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); - if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); - if (!pal_img_n) { - s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); - if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); - if (scan == STBI__SCAN_header) return 1; - } else { - // if paletted, then pal_n is our final components, and - // img_n is # components to decompress/filter. - s->img_n = 1; - if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); - // if SCAN_header, have to scan to see if we have a tRNS - } - break; - } - - case STBI__PNG_TYPE('P','L','T','E'): { - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); - pal_len = c.length / 3; - if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); - for (i=0; i < pal_len; ++i) { - palette[i*4+0] = stbi__get8(s); - palette[i*4+1] = stbi__get8(s); - palette[i*4+2] = stbi__get8(s); - palette[i*4+3] = 255; - } - break; - } - - case STBI__PNG_TYPE('t','R','N','S'): { - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); - if (pal_img_n) { - if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } - if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); - if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); - pal_img_n = 4; - for (i=0; i < c.length; ++i) - palette[i*4+3] = stbi__get8(s); - } else { - if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); - if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); - has_trans = 1; - if (z->depth == 16) { - for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is - } else { - for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger - } - } - break; - } - - case STBI__PNG_TYPE('I','D','A','T'): { - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); - if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } - if ((int)(ioff + c.length) < (int)ioff) return 0; - if (ioff + c.length > idata_limit) { - stbi__uint32 idata_limit_old = idata_limit; - stbi_uc *p; - if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; - while (ioff + c.length > idata_limit) - idata_limit *= 2; - STBI_NOTUSED(idata_limit_old); - p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); - z->idata = p; - } - if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); - ioff += c.length; - break; - } - - case STBI__PNG_TYPE('I','E','N','D'): { - stbi__uint32 raw_len, bpl; - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (scan != STBI__SCAN_load) return 1; - if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); - // initial guess for decoded data size to avoid unnecessary reallocs - bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component - raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; - z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); - if (z->expanded == NULL) return 0; // zlib should set error - STBI_FREE(z->idata); z->idata = NULL; - if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) - s->img_out_n = s->img_n+1; - else - s->img_out_n = s->img_n; - if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; - if (has_trans) { - if (z->depth == 16) { - if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; - } else { - if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; - } - } - if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) - stbi__de_iphone(z); - if (pal_img_n) { - // pal_img_n == 3 or 4 - s->img_n = pal_img_n; // record the actual colors we had - s->img_out_n = pal_img_n; - if (req_comp >= 3) s->img_out_n = req_comp; - if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) - return 0; - } else if (has_trans) { - // non-paletted image with tRNS -> source image has (constant) alpha - ++s->img_n; - } - STBI_FREE(z->expanded); z->expanded = NULL; - return 1; - } - - default: - // if critical, fail - if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if ((c.type & (1 << 29)) == 0) { - #ifndef STBI_NO_FAILURE_STRINGS - // not threadsafe - static char invalid_chunk[] = "XXXX PNG chunk not known"; - invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); - invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); - invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); - invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); - #endif - return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); - } - stbi__skip(s, c.length); - break; - } - // end of PNG chunk, read and skip CRC - stbi__get32be(s); - } -} - -static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) -{ - void *result=NULL; - if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); - if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { - if (p->depth < 8) - ri->bits_per_channel = 8; - else - ri->bits_per_channel = p->depth; - result = p->out; - p->out = NULL; - if (req_comp && req_comp != p->s->img_out_n) { - if (ri->bits_per_channel == 8) - result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); - else - result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); - p->s->img_out_n = req_comp; - if (result == NULL) return result; - } - *x = p->s->img_x; - *y = p->s->img_y; - if (n) *n = p->s->img_n; - } - STBI_FREE(p->out); p->out = NULL; - STBI_FREE(p->expanded); p->expanded = NULL; - STBI_FREE(p->idata); p->idata = NULL; - - return result; -} - -static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - stbi__png p; - p.s = s; - return stbi__do_png(&p, x,y,comp,req_comp, ri); -} - -static int stbi__png_test(stbi__context *s) -{ - int r; - r = stbi__check_png_header(s); - stbi__rewind(s); - return r; -} - -static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) -{ - if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { - stbi__rewind( p->s ); - return 0; - } - if (x) *x = p->s->img_x; - if (y) *y = p->s->img_y; - if (comp) *comp = p->s->img_n; - return 1; -} - -static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) -{ - stbi__png p; - p.s = s; - return stbi__png_info_raw(&p, x, y, comp); -} - -static int stbi__png_is16(stbi__context *s) -{ - stbi__png p; - p.s = s; - if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) - return 0; - if (p.depth != 16) { - stbi__rewind(p.s); - return 0; - } - return 1; -} -#endif - -// Microsoft/Windows BMP image - -#ifndef STBI_NO_BMP -static int stbi__bmp_test_raw(stbi__context *s) -{ - int r; - int sz; - if (stbi__get8(s) != 'B') return 0; - if (stbi__get8(s) != 'M') return 0; - stbi__get32le(s); // discard filesize - stbi__get16le(s); // discard reserved - stbi__get16le(s); // discard reserved - stbi__get32le(s); // discard data offset - sz = stbi__get32le(s); - r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); - return r; -} - -static int stbi__bmp_test(stbi__context *s) -{ - int r = stbi__bmp_test_raw(s); - stbi__rewind(s); - return r; -} - - -// returns 0..31 for the highest set bit -static int stbi__high_bit(unsigned int z) -{ - int n=0; - if (z == 0) return -1; - if (z >= 0x10000) n += 16, z >>= 16; - if (z >= 0x00100) n += 8, z >>= 8; - if (z >= 0x00010) n += 4, z >>= 4; - if (z >= 0x00004) n += 2, z >>= 2; - if (z >= 0x00002) n += 1, z >>= 1; - return n; -} - -static int stbi__bitcount(unsigned int a) -{ - a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 - a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 - a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits - a = (a + (a >> 8)); // max 16 per 8 bits - a = (a + (a >> 16)); // max 32 per 8 bits - return a & 0xff; -} - -// extract an arbitrarily-aligned N-bit value (N=bits) -// from v, and then make it 8-bits long and fractionally -// extend it to full full range. -static int stbi__shiftsigned(int v, int shift, int bits) -{ - static unsigned int mul_table[9] = { - 0, - 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, - 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, - }; - static unsigned int shift_table[9] = { - 0, 0,0,1,0,2,4,6,0, - }; - if (shift < 0) - v <<= -shift; - else - v >>= shift; - STBI_ASSERT(v >= 0 && v < 256); - v >>= (8-bits); - STBI_ASSERT(bits >= 0 && bits <= 8); - return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; -} - -typedef struct -{ - int bpp, offset, hsz; - unsigned int mr,mg,mb,ma, all_a; -} stbi__bmp_data; - -static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) -{ - int hsz; - if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); - stbi__get32le(s); // discard filesize - stbi__get16le(s); // discard reserved - stbi__get16le(s); // discard reserved - info->offset = stbi__get32le(s); - info->hsz = hsz = stbi__get32le(s); - info->mr = info->mg = info->mb = info->ma = 0; - - if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); - if (hsz == 12) { - s->img_x = stbi__get16le(s); - s->img_y = stbi__get16le(s); - } else { - s->img_x = stbi__get32le(s); - s->img_y = stbi__get32le(s); - } - if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); - info->bpp = stbi__get16le(s); - if (hsz != 12) { - int compress = stbi__get32le(s); - if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); - stbi__get32le(s); // discard sizeof - stbi__get32le(s); // discard hres - stbi__get32le(s); // discard vres - stbi__get32le(s); // discard colorsused - stbi__get32le(s); // discard max important - if (hsz == 40 || hsz == 56) { - if (hsz == 56) { - stbi__get32le(s); - stbi__get32le(s); - stbi__get32le(s); - stbi__get32le(s); - } - if (info->bpp == 16 || info->bpp == 32) { - if (compress == 0) { - if (info->bpp == 32) { - info->mr = 0xffu << 16; - info->mg = 0xffu << 8; - info->mb = 0xffu << 0; - info->ma = 0xffu << 24; - info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 - } else { - info->mr = 31u << 10; - info->mg = 31u << 5; - info->mb = 31u << 0; - } - } else if (compress == 3) { - info->mr = stbi__get32le(s); - info->mg = stbi__get32le(s); - info->mb = stbi__get32le(s); - // not documented, but generated by photoshop and handled by mspaint - if (info->mr == info->mg && info->mg == info->mb) { - // ?!?!? - return stbi__errpuc("bad BMP", "bad BMP"); - } - } else - return stbi__errpuc("bad BMP", "bad BMP"); - } - } else { - int i; - if (hsz != 108 && hsz != 124) - return stbi__errpuc("bad BMP", "bad BMP"); - info->mr = stbi__get32le(s); - info->mg = stbi__get32le(s); - info->mb = stbi__get32le(s); - info->ma = stbi__get32le(s); - stbi__get32le(s); // discard color space - for (i=0; i < 12; ++i) - stbi__get32le(s); // discard color space parameters - if (hsz == 124) { - stbi__get32le(s); // discard rendering intent - stbi__get32le(s); // discard offset of profile data - stbi__get32le(s); // discard size of profile data - stbi__get32le(s); // discard reserved - } - } - } - return (void *) 1; -} - - -static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - stbi_uc *out; - unsigned int mr=0,mg=0,mb=0,ma=0, all_a; - stbi_uc pal[256][4]; - int psize=0,i,j,width; - int flip_vertically, pad, target; - stbi__bmp_data info; - STBI_NOTUSED(ri); - - info.all_a = 255; - if (stbi__bmp_parse_header(s, &info) == NULL) - return NULL; // error code already set - - flip_vertically = ((int) s->img_y) > 0; - s->img_y = abs((int) s->img_y); - - mr = info.mr; - mg = info.mg; - mb = info.mb; - ma = info.ma; - all_a = info.all_a; - - if (info.hsz == 12) { - if (info.bpp < 24) - psize = (info.offset - 14 - 24) / 3; - } else { - if (info.bpp < 16) - psize = (info.offset - 14 - info.hsz) >> 2; - } - - s->img_n = ma ? 4 : 3; - if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 - target = req_comp; - else - target = s->img_n; // if they want monochrome, we'll post-convert - - // sanity-check size - if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) - return stbi__errpuc("too large", "Corrupt BMP"); - - out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); - if (!out) return stbi__errpuc("outofmem", "Out of memory"); - if (info.bpp < 16) { - int z=0; - if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } - for (i=0; i < psize; ++i) { - pal[i][2] = stbi__get8(s); - pal[i][1] = stbi__get8(s); - pal[i][0] = stbi__get8(s); - if (info.hsz != 12) stbi__get8(s); - pal[i][3] = 255; - } - stbi__skip(s, info.offset - 14 - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); - if (info.bpp == 1) width = (s->img_x + 7) >> 3; - else if (info.bpp == 4) width = (s->img_x + 1) >> 1; - else if (info.bpp == 8) width = s->img_x; - else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } - pad = (-width)&3; - if (info.bpp == 1) { - for (j=0; j < (int) s->img_y; ++j) { - int bit_offset = 7, v = stbi__get8(s); - for (i=0; i < (int) s->img_x; ++i) { - int color = (v>>bit_offset)&0x1; - out[z++] = pal[color][0]; - out[z++] = pal[color][1]; - out[z++] = pal[color][2]; - if((--bit_offset) < 0) { - bit_offset = 7; - v = stbi__get8(s); - } - } - stbi__skip(s, pad); - } - } else { - for (j=0; j < (int) s->img_y; ++j) { - for (i=0; i < (int) s->img_x; i += 2) { - int v=stbi__get8(s),v2=0; - if (info.bpp == 4) { - v2 = v & 15; - v >>= 4; - } - out[z++] = pal[v][0]; - out[z++] = pal[v][1]; - out[z++] = pal[v][2]; - if (target == 4) out[z++] = 255; - if (i+1 == (int) s->img_x) break; - v = (info.bpp == 8) ? stbi__get8(s) : v2; - out[z++] = pal[v][0]; - out[z++] = pal[v][1]; - out[z++] = pal[v][2]; - if (target == 4) out[z++] = 255; - } - stbi__skip(s, pad); - } - } - } else { - int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; - int z = 0; - int easy=0; - stbi__skip(s, info.offset - 14 - info.hsz); - if (info.bpp == 24) width = 3 * s->img_x; - else if (info.bpp == 16) width = 2*s->img_x; - else /* bpp = 32 and pad = 0 */ width=0; - pad = (-width) & 3; - if (info.bpp == 24) { - easy = 1; - } else if (info.bpp == 32) { - if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) - easy = 2; - } - if (!easy) { - if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } - // right shift amt to put high bit in position #7 - rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); - gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); - bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); - ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); - } - for (j=0; j < (int) s->img_y; ++j) { - if (easy) { - for (i=0; i < (int) s->img_x; ++i) { - unsigned char a; - out[z+2] = stbi__get8(s); - out[z+1] = stbi__get8(s); - out[z+0] = stbi__get8(s); - z += 3; - a = (easy == 2 ? stbi__get8(s) : 255); - all_a |= a; - if (target == 4) out[z++] = a; - } - } else { - int bpp = info.bpp; - for (i=0; i < (int) s->img_x; ++i) { - stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); - unsigned int a; - out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); - out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); - out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); - a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); - all_a |= a; - if (target == 4) out[z++] = STBI__BYTECAST(a); - } - } - stbi__skip(s, pad); - } - } - - // if alpha channel is all 0s, replace with all 255s - if (target == 4 && all_a == 0) - for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) - out[i] = 255; - - if (flip_vertically) { - stbi_uc t; - for (j=0; j < (int) s->img_y>>1; ++j) { - stbi_uc *p1 = out + j *s->img_x*target; - stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; - for (i=0; i < (int) s->img_x*target; ++i) { - t = p1[i], p1[i] = p2[i], p2[i] = t; - } - } - } - - if (req_comp && req_comp != target) { - out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); - if (out == NULL) return out; // stbi__convert_format frees input on failure - } - - *x = s->img_x; - *y = s->img_y; - if (comp) *comp = s->img_n; - return out; -} -#endif - -// Targa Truevision - TGA -// by Jonathan Dummer -#ifndef STBI_NO_TGA -// returns STBI_rgb or whatever, 0 on error -static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) -{ - // only RGB or RGBA (incl. 16bit) or grey allowed - if (is_rgb16) *is_rgb16 = 0; - switch(bits_per_pixel) { - case 8: return STBI_grey; - case 16: if(is_grey) return STBI_grey_alpha; - // fallthrough - case 15: if(is_rgb16) *is_rgb16 = 1; - return STBI_rgb; - case 24: // fallthrough - case 32: return bits_per_pixel/8; - default: return 0; - } -} - -static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) -{ - int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; - int sz, tga_colormap_type; - stbi__get8(s); // discard Offset - tga_colormap_type = stbi__get8(s); // colormap type - if( tga_colormap_type > 1 ) { - stbi__rewind(s); - return 0; // only RGB or indexed allowed - } - tga_image_type = stbi__get8(s); // image type - if ( tga_colormap_type == 1 ) { // colormapped (paletted) image - if (tga_image_type != 1 && tga_image_type != 9) { - stbi__rewind(s); - return 0; - } - stbi__skip(s,4); // skip index of first colormap entry and number of entries - sz = stbi__get8(s); // check bits per palette color entry - if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { - stbi__rewind(s); - return 0; - } - stbi__skip(s,4); // skip image x and y origin - tga_colormap_bpp = sz; - } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE - if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { - stbi__rewind(s); - return 0; // only RGB or grey allowed, +/- RLE - } - stbi__skip(s,9); // skip colormap specification and image x/y origin - tga_colormap_bpp = 0; - } - tga_w = stbi__get16le(s); - if( tga_w < 1 ) { - stbi__rewind(s); - return 0; // test width - } - tga_h = stbi__get16le(s); - if( tga_h < 1 ) { - stbi__rewind(s); - return 0; // test height - } - tga_bits_per_pixel = stbi__get8(s); // bits per pixel - stbi__get8(s); // ignore alpha bits - if (tga_colormap_bpp != 0) { - if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { - // when using a colormap, tga_bits_per_pixel is the size of the indexes - // I don't think anything but 8 or 16bit indexes makes sense - stbi__rewind(s); - return 0; - } - tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); - } else { - tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); - } - if(!tga_comp) { - stbi__rewind(s); - return 0; - } - if (x) *x = tga_w; - if (y) *y = tga_h; - if (comp) *comp = tga_comp; - return 1; // seems to have passed everything -} - -static int stbi__tga_test(stbi__context *s) -{ - int res = 0; - int sz, tga_color_type; - stbi__get8(s); // discard Offset - tga_color_type = stbi__get8(s); // color type - if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed - sz = stbi__get8(s); // image type - if ( tga_color_type == 1 ) { // colormapped (paletted) image - if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 - stbi__skip(s,4); // skip index of first colormap entry and number of entries - sz = stbi__get8(s); // check bits per palette color entry - if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; - stbi__skip(s,4); // skip image x and y origin - } else { // "normal" image w/o colormap - if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE - stbi__skip(s,9); // skip colormap specification and image x/y origin - } - if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width - if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height - sz = stbi__get8(s); // bits per pixel - if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index - if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; - - res = 1; // if we got this far, everything's good and we can return 1 instead of 0 - -errorEnd: - stbi__rewind(s); - return res; -} - -// read 16bit value and convert to 24bit RGB -static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) -{ - stbi__uint16 px = (stbi__uint16)stbi__get16le(s); - stbi__uint16 fiveBitMask = 31; - // we have 3 channels with 5bits each - int r = (px >> 10) & fiveBitMask; - int g = (px >> 5) & fiveBitMask; - int b = px & fiveBitMask; - // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later - out[0] = (stbi_uc)((r * 255)/31); - out[1] = (stbi_uc)((g * 255)/31); - out[2] = (stbi_uc)((b * 255)/31); - - // some people claim that the most significant bit might be used for alpha - // (possibly if an alpha-bit is set in the "image descriptor byte") - // but that only made 16bit test images completely translucent.. - // so let's treat all 15 and 16bit TGAs as RGB with no alpha. -} - -static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - // read in the TGA header stuff - int tga_offset = stbi__get8(s); - int tga_indexed = stbi__get8(s); - int tga_image_type = stbi__get8(s); - int tga_is_RLE = 0; - int tga_palette_start = stbi__get16le(s); - int tga_palette_len = stbi__get16le(s); - int tga_palette_bits = stbi__get8(s); - int tga_x_origin = stbi__get16le(s); - int tga_y_origin = stbi__get16le(s); - int tga_width = stbi__get16le(s); - int tga_height = stbi__get16le(s); - int tga_bits_per_pixel = stbi__get8(s); - int tga_comp, tga_rgb16=0; - int tga_inverted = stbi__get8(s); - // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) - // image data - unsigned char *tga_data; - unsigned char *tga_palette = NULL; - int i, j; - unsigned char raw_data[4] = {0}; - int RLE_count = 0; - int RLE_repeating = 0; - int read_next_pixel = 1; - STBI_NOTUSED(ri); - - // do a tiny bit of precessing - if ( tga_image_type >= 8 ) - { - tga_image_type -= 8; - tga_is_RLE = 1; - } - tga_inverted = 1 - ((tga_inverted >> 5) & 1); - - // If I'm paletted, then I'll use the number of bits from the palette - if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); - else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); - - if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency - return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); - - // tga info - *x = tga_width; - *y = tga_height; - if (comp) *comp = tga_comp; - - if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) - return stbi__errpuc("too large", "Corrupt TGA"); - - tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); - if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); - - // skip to the data's starting position (offset usually = 0) - stbi__skip(s, tga_offset ); - - if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { - for (i=0; i < tga_height; ++i) { - int row = tga_inverted ? tga_height -i - 1 : i; - stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; - stbi__getn(s, tga_row, tga_width * tga_comp); - } - } else { - // do I need to load a palette? - if ( tga_indexed) - { - // any data to skip? (offset usually = 0) - stbi__skip(s, tga_palette_start ); - // load the palette - tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); - if (!tga_palette) { - STBI_FREE(tga_data); - return stbi__errpuc("outofmem", "Out of memory"); - } - if (tga_rgb16) { - stbi_uc *pal_entry = tga_palette; - STBI_ASSERT(tga_comp == STBI_rgb); - for (i=0; i < tga_palette_len; ++i) { - stbi__tga_read_rgb16(s, pal_entry); - pal_entry += tga_comp; - } - } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { - STBI_FREE(tga_data); - STBI_FREE(tga_palette); - return stbi__errpuc("bad palette", "Corrupt TGA"); - } - } - // load the data - for (i=0; i < tga_width * tga_height; ++i) - { - // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? - if ( tga_is_RLE ) - { - if ( RLE_count == 0 ) - { - // yep, get the next byte as a RLE command - int RLE_cmd = stbi__get8(s); - RLE_count = 1 + (RLE_cmd & 127); - RLE_repeating = RLE_cmd >> 7; - read_next_pixel = 1; - } else if ( !RLE_repeating ) - { - read_next_pixel = 1; - } - } else - { - read_next_pixel = 1; - } - // OK, if I need to read a pixel, do it now - if ( read_next_pixel ) - { - // load however much data we did have - if ( tga_indexed ) - { - // read in index, then perform the lookup - int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); - if ( pal_idx >= tga_palette_len ) { - // invalid index - pal_idx = 0; - } - pal_idx *= tga_comp; - for (j = 0; j < tga_comp; ++j) { - raw_data[j] = tga_palette[pal_idx+j]; - } - } else if(tga_rgb16) { - STBI_ASSERT(tga_comp == STBI_rgb); - stbi__tga_read_rgb16(s, raw_data); - } else { - // read in the data raw - for (j = 0; j < tga_comp; ++j) { - raw_data[j] = stbi__get8(s); - } - } - // clear the reading flag for the next pixel - read_next_pixel = 0; - } // end of reading a pixel - - // copy data - for (j = 0; j < tga_comp; ++j) - tga_data[i*tga_comp+j] = raw_data[j]; - - // in case we're in RLE mode, keep counting down - --RLE_count; - } - // do I need to invert the image? - if ( tga_inverted ) - { - for (j = 0; j*2 < tga_height; ++j) - { - int index1 = j * tga_width * tga_comp; - int index2 = (tga_height - 1 - j) * tga_width * tga_comp; - for (i = tga_width * tga_comp; i > 0; --i) - { - unsigned char temp = tga_data[index1]; - tga_data[index1] = tga_data[index2]; - tga_data[index2] = temp; - ++index1; - ++index2; - } - } - } - // clear my palette, if I had one - if ( tga_palette != NULL ) - { - STBI_FREE( tga_palette ); - } - } - - // swap RGB - if the source data was RGB16, it already is in the right order - if (tga_comp >= 3 && !tga_rgb16) - { - unsigned char* tga_pixel = tga_data; - for (i=0; i < tga_width * tga_height; ++i) - { - unsigned char temp = tga_pixel[0]; - tga_pixel[0] = tga_pixel[2]; - tga_pixel[2] = temp; - tga_pixel += tga_comp; - } - } - - // convert to target component count - if (req_comp && req_comp != tga_comp) - tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); - - // the things I do to get rid of an error message, and yet keep - // Microsoft's C compilers happy... [8^( - tga_palette_start = tga_palette_len = tga_palette_bits = - tga_x_origin = tga_y_origin = 0; - // OK, done - return tga_data; -} -#endif - -// ************************************************************************************************* -// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB - -#ifndef STBI_NO_PSD -static int stbi__psd_test(stbi__context *s) -{ - int r = (stbi__get32be(s) == 0x38425053); - stbi__rewind(s); - return r; -} - -static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) -{ - int count, nleft, len; - - count = 0; - while ((nleft = pixelCount - count) > 0) { - len = stbi__get8(s); - if (len == 128) { - // No-op. - } else if (len < 128) { - // Copy next len+1 bytes literally. - len++; - if (len > nleft) return 0; // corrupt data - count += len; - while (len) { - *p = stbi__get8(s); - p += 4; - len--; - } - } else if (len > 128) { - stbi_uc val; - // Next -len+1 bytes in the dest are replicated from next source byte. - // (Interpret len as a negative 8-bit int.) - len = 257 - len; - if (len > nleft) return 0; // corrupt data - val = stbi__get8(s); - count += len; - while (len) { - *p = val; - p += 4; - len--; - } - } - } - - return 1; -} - -static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) -{ - int pixelCount; - int channelCount, compression; - int channel, i; - int bitdepth; - int w,h; - stbi_uc *out; - STBI_NOTUSED(ri); - - // Check identifier - if (stbi__get32be(s) != 0x38425053) // "8BPS" - return stbi__errpuc("not PSD", "Corrupt PSD image"); - - // Check file type version. - if (stbi__get16be(s) != 1) - return stbi__errpuc("wrong version", "Unsupported version of PSD image"); - - // Skip 6 reserved bytes. - stbi__skip(s, 6 ); - - // Read the number of channels (R, G, B, A, etc). - channelCount = stbi__get16be(s); - if (channelCount < 0 || channelCount > 16) - return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); - - // Read the rows and columns of the image. - h = stbi__get32be(s); - w = stbi__get32be(s); - - // Make sure the depth is 8 bits. - bitdepth = stbi__get16be(s); - if (bitdepth != 8 && bitdepth != 16) - return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); - - // Make sure the color mode is RGB. - // Valid options are: - // 0: Bitmap - // 1: Grayscale - // 2: Indexed color - // 3: RGB color - // 4: CMYK color - // 7: Multichannel - // 8: Duotone - // 9: Lab color - if (stbi__get16be(s) != 3) - return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); - - // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) - stbi__skip(s,stbi__get32be(s) ); - - // Skip the image resources. (resolution, pen tool paths, etc) - stbi__skip(s, stbi__get32be(s) ); - - // Skip the reserved data. - stbi__skip(s, stbi__get32be(s) ); - - // Find out if the data is compressed. - // Known values: - // 0: no compression - // 1: RLE compressed - compression = stbi__get16be(s); - if (compression > 1) - return stbi__errpuc("bad compression", "PSD has an unknown compression format"); - - // Check size - if (!stbi__mad3sizes_valid(4, w, h, 0)) - return stbi__errpuc("too large", "Corrupt PSD"); - - // Create the destination image. - - if (!compression && bitdepth == 16 && bpc == 16) { - out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); - ri->bits_per_channel = 16; - } else - out = (stbi_uc *) stbi__malloc(4 * w*h); - - if (!out) return stbi__errpuc("outofmem", "Out of memory"); - pixelCount = w*h; - - // Initialize the data to zero. - //memset( out, 0, pixelCount * 4 ); - - // Finally, the image data. - if (compression) { - // RLE as used by .PSD and .TIFF - // Loop until you get the number of unpacked bytes you are expecting: - // Read the next source byte into n. - // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. - // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. - // Else if n is 128, noop. - // Endloop - - // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data, - // which we're going to just skip. - stbi__skip(s, h * channelCount * 2 ); - - // Read the RLE data by channel. - for (channel = 0; channel < 4; channel++) { - stbi_uc *p; - - p = out+channel; - if (channel >= channelCount) { - // Fill this channel with default data. - for (i = 0; i < pixelCount; i++, p += 4) - *p = (channel == 3 ? 255 : 0); - } else { - // Read the RLE data. - if (!stbi__psd_decode_rle(s, p, pixelCount)) { - STBI_FREE(out); - return stbi__errpuc("corrupt", "bad RLE data"); - } - } - } - - } else { - // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) - // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. - - // Read the data by channel. - for (channel = 0; channel < 4; channel++) { - if (channel >= channelCount) { - // Fill this channel with default data. - if (bitdepth == 16 && bpc == 16) { - stbi__uint16 *q = ((stbi__uint16 *) out) + channel; - stbi__uint16 val = channel == 3 ? 65535 : 0; - for (i = 0; i < pixelCount; i++, q += 4) - *q = val; - } else { - stbi_uc *p = out+channel; - stbi_uc val = channel == 3 ? 255 : 0; - for (i = 0; i < pixelCount; i++, p += 4) - *p = val; - } - } else { - if (ri->bits_per_channel == 16) { // output bpc - stbi__uint16 *q = ((stbi__uint16 *) out) + channel; - for (i = 0; i < pixelCount; i++, q += 4) - *q = (stbi__uint16) stbi__get16be(s); - } else { - stbi_uc *p = out+channel; - if (bitdepth == 16) { // input bpc - for (i = 0; i < pixelCount; i++, p += 4) - *p = (stbi_uc) (stbi__get16be(s) >> 8); - } else { - for (i = 0; i < pixelCount; i++, p += 4) - *p = stbi__get8(s); - } - } - } - } - } - - // remove weird white matte from PSD - if (channelCount >= 4) { - if (ri->bits_per_channel == 16) { - for (i=0; i < w*h; ++i) { - stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; - if (pixel[3] != 0 && pixel[3] != 65535) { - float a = pixel[3] / 65535.0f; - float ra = 1.0f / a; - float inv_a = 65535.0f * (1 - ra); - pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); - pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); - pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); - } - } - } else { - for (i=0; i < w*h; ++i) { - unsigned char *pixel = out + 4*i; - if (pixel[3] != 0 && pixel[3] != 255) { - float a = pixel[3] / 255.0f; - float ra = 1.0f / a; - float inv_a = 255.0f * (1 - ra); - pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); - pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); - pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); - } - } - } - } - - // convert to desired output format - if (req_comp && req_comp != 4) { - if (ri->bits_per_channel == 16) - out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); - else - out = stbi__convert_format(out, 4, req_comp, w, h); - if (out == NULL) return out; // stbi__convert_format frees input on failure - } - - if (comp) *comp = 4; - *y = h; - *x = w; - - return out; -} -#endif - -// ************************************************************************************************* -// Softimage PIC loader -// by Tom Seddon -// -// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format -// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ - -#ifndef STBI_NO_PIC -static int stbi__pic_is4(stbi__context *s,const char *str) -{ - int i; - for (i=0; i<4; ++i) - if (stbi__get8(s) != (stbi_uc)str[i]) - return 0; - - return 1; -} - -static int stbi__pic_test_core(stbi__context *s) -{ - int i; - - if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) - return 0; - - for(i=0;i<84;++i) - stbi__get8(s); - - if (!stbi__pic_is4(s,"PICT")) - return 0; - - return 1; -} - -typedef struct -{ - stbi_uc size,type,channel; -} stbi__pic_packet; - -static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) -{ - int mask=0x80, i; - - for (i=0; i<4; ++i, mask>>=1) { - if (channel & mask) { - if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); - dest[i]=stbi__get8(s); - } - } - - return dest; -} - -static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) -{ - int mask=0x80,i; - - for (i=0;i<4; ++i, mask>>=1) - if (channel&mask) - dest[i]=src[i]; -} - -static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) -{ - int act_comp=0,num_packets=0,y,chained; - stbi__pic_packet packets[10]; - - // this will (should...) cater for even some bizarre stuff like having data - // for the same channel in multiple packets. - do { - stbi__pic_packet *packet; - - if (num_packets==sizeof(packets)/sizeof(packets[0])) - return stbi__errpuc("bad format","too many packets"); - - packet = &packets[num_packets++]; - - chained = stbi__get8(s); - packet->size = stbi__get8(s); - packet->type = stbi__get8(s); - packet->channel = stbi__get8(s); - - act_comp |= packet->channel; - - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); - if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); - } while (chained); - - *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? - - for(y=0; ytype) { - default: - return stbi__errpuc("bad format","packet has bad compression type"); - - case 0: {//uncompressed - int x; - - for(x=0;xchannel,dest)) - return 0; - break; - } - - case 1://Pure RLE - { - int left=width, i; - - while (left>0) { - stbi_uc count,value[4]; - - count=stbi__get8(s); - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); - - if (count > left) - count = (stbi_uc) left; - - if (!stbi__readval(s,packet->channel,value)) return 0; - - for(i=0; ichannel,dest,value); - left -= count; - } - } - break; - - case 2: {//Mixed RLE - int left=width; - while (left>0) { - int count = stbi__get8(s), i; - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); - - if (count >= 128) { // Repeated - stbi_uc value[4]; - - if (count==128) - count = stbi__get16be(s); - else - count -= 127; - if (count > left) - return stbi__errpuc("bad file","scanline overrun"); - - if (!stbi__readval(s,packet->channel,value)) - return 0; - - for(i=0;ichannel,dest,value); - } else { // Raw - ++count; - if (count>left) return stbi__errpuc("bad file","scanline overrun"); - - for(i=0;ichannel,dest)) - return 0; - } - left-=count; - } - break; - } - } - } - } - - return result; -} - -static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) -{ - stbi_uc *result; - int i, x,y, internal_comp; - STBI_NOTUSED(ri); - - if (!comp) comp = &internal_comp; - - for (i=0; i<92; ++i) - stbi__get8(s); - - x = stbi__get16be(s); - y = stbi__get16be(s); - if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); - if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); - - stbi__get32be(s); //skip `ratio' - stbi__get16be(s); //skip `fields' - stbi__get16be(s); //skip `pad' - - // intermediate buffer is RGBA - result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); - memset(result, 0xff, x*y*4); - - if (!stbi__pic_load_core(s,x,y,comp, result)) { - STBI_FREE(result); - result=0; - } - *px = x; - *py = y; - if (req_comp == 0) req_comp = *comp; - result=stbi__convert_format(result,4,req_comp,x,y); - - return result; -} - -static int stbi__pic_test(stbi__context *s) -{ - int r = stbi__pic_test_core(s); - stbi__rewind(s); - return r; -} -#endif - -// ************************************************************************************************* -// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb - -#ifndef STBI_NO_GIF -typedef struct -{ - stbi__int16 prefix; - stbi_uc first; - stbi_uc suffix; -} stbi__gif_lzw; - -typedef struct -{ - int w,h; - stbi_uc *out; // output buffer (always 4 components) - stbi_uc *background; // The current "background" as far as a gif is concerned - stbi_uc *history; - int flags, bgindex, ratio, transparent, eflags; - stbi_uc pal[256][4]; - stbi_uc lpal[256][4]; - stbi__gif_lzw codes[8192]; - stbi_uc *color_table; - int parse, step; - int lflags; - int start_x, start_y; - int max_x, max_y; - int cur_x, cur_y; - int line_size; - int delay; -} stbi__gif; - -static int stbi__gif_test_raw(stbi__context *s) -{ - int sz; - if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; - sz = stbi__get8(s); - if (sz != '9' && sz != '7') return 0; - if (stbi__get8(s) != 'a') return 0; - return 1; -} - -static int stbi__gif_test(stbi__context *s) -{ - int r = stbi__gif_test_raw(s); - stbi__rewind(s); - return r; -} - -static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) -{ - int i; - for (i=0; i < num_entries; ++i) { - pal[i][2] = stbi__get8(s); - pal[i][1] = stbi__get8(s); - pal[i][0] = stbi__get8(s); - pal[i][3] = transp == i ? 0 : 255; - } -} - -static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) -{ - stbi_uc version; - if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') - return stbi__err("not GIF", "Corrupt GIF"); - - version = stbi__get8(s); - if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); - if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); - - stbi__g_failure_reason = ""; - g->w = stbi__get16le(s); - g->h = stbi__get16le(s); - g->flags = stbi__get8(s); - g->bgindex = stbi__get8(s); - g->ratio = stbi__get8(s); - g->transparent = -1; - - if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments - - if (is_info) return 1; - - if (g->flags & 0x80) - stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); - - return 1; -} - -static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) -{ - stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); - if (!stbi__gif_header(s, g, comp, 1)) { - STBI_FREE(g); - stbi__rewind( s ); - return 0; - } - if (x) *x = g->w; - if (y) *y = g->h; - STBI_FREE(g); - return 1; -} - -static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) -{ - stbi_uc *p, *c; - int idx; - - // recurse to decode the prefixes, since the linked-list is backwards, - // and working backwards through an interleaved image would be nasty - if (g->codes[code].prefix >= 0) - stbi__out_gif_code(g, g->codes[code].prefix); - - if (g->cur_y >= g->max_y) return; - - idx = g->cur_x + g->cur_y; - p = &g->out[idx]; - g->history[idx / 4] = 1; - - c = &g->color_table[g->codes[code].suffix * 4]; - if (c[3] > 128) { // don't render transparent pixels; - p[0] = c[2]; - p[1] = c[1]; - p[2] = c[0]; - p[3] = c[3]; - } - g->cur_x += 4; - - if (g->cur_x >= g->max_x) { - g->cur_x = g->start_x; - g->cur_y += g->step; - - while (g->cur_y >= g->max_y && g->parse > 0) { - g->step = (1 << g->parse) * g->line_size; - g->cur_y = g->start_y + (g->step >> 1); - --g->parse; - } - } -} - -static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) -{ - stbi_uc lzw_cs; - stbi__int32 len, init_code; - stbi__uint32 first; - stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; - stbi__gif_lzw *p; - - lzw_cs = stbi__get8(s); - if (lzw_cs > 12) return NULL; - clear = 1 << lzw_cs; - first = 1; - codesize = lzw_cs + 1; - codemask = (1 << codesize) - 1; - bits = 0; - valid_bits = 0; - for (init_code = 0; init_code < clear; init_code++) { - g->codes[init_code].prefix = -1; - g->codes[init_code].first = (stbi_uc) init_code; - g->codes[init_code].suffix = (stbi_uc) init_code; - } - - // support no starting clear code - avail = clear+2; - oldcode = -1; - - len = 0; - for(;;) { - if (valid_bits < codesize) { - if (len == 0) { - len = stbi__get8(s); // start new block - if (len == 0) - return g->out; - } - --len; - bits |= (stbi__int32) stbi__get8(s) << valid_bits; - valid_bits += 8; - } else { - stbi__int32 code = bits & codemask; - bits >>= codesize; - valid_bits -= codesize; - // @OPTIMIZE: is there some way we can accelerate the non-clear path? - if (code == clear) { // clear code - codesize = lzw_cs + 1; - codemask = (1 << codesize) - 1; - avail = clear + 2; - oldcode = -1; - first = 0; - } else if (code == clear + 1) { // end of stream code - stbi__skip(s, len); - while ((len = stbi__get8(s)) > 0) - stbi__skip(s,len); - return g->out; - } else if (code <= avail) { - if (first) { - return stbi__errpuc("no clear code", "Corrupt GIF"); - } - - if (oldcode >= 0) { - p = &g->codes[avail++]; - if (avail > 8192) { - return stbi__errpuc("too many codes", "Corrupt GIF"); - } - - p->prefix = (stbi__int16) oldcode; - p->first = g->codes[oldcode].first; - p->suffix = (code == avail) ? p->first : g->codes[code].first; - } else if (code == avail) - return stbi__errpuc("illegal code in raster", "Corrupt GIF"); - - stbi__out_gif_code(g, (stbi__uint16) code); - - if ((avail & codemask) == 0 && avail <= 0x0FFF) { - codesize++; - codemask = (1 << codesize) - 1; - } - - oldcode = code; - } else { - return stbi__errpuc("illegal code in raster", "Corrupt GIF"); - } - } - } -} - -// this function is designed to support animated gifs, although stb_image doesn't support it -// two back is the image from two frames ago, used for a very specific disposal format -static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) -{ - int dispose; - int first_frame; - int pi; - int pcount; - - // on first frame, any non-written pixels get the background colour (non-transparent) - first_frame = 0; - if (g->out == 0) { - if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header - g->out = (stbi_uc *) stbi__malloc(4 * g->w * g->h); - g->background = (stbi_uc *) stbi__malloc(4 * g->w * g->h); - g->history = (stbi_uc *) stbi__malloc(g->w * g->h); - if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); - - // image is treated as "tranparent" at the start - ie, nothing overwrites the current background; - // background colour is only used for pixels that are not rendered first frame, after that "background" - // color refers to teh color that was there the previous frame. - memset( g->out, 0x00, 4 * g->w * g->h ); - memset( g->background, 0x00, 4 * g->w * g->h ); // state of the background (starts transparent) - memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame - first_frame = 1; - } else { - // second frame - how do we dispoase of the previous one? - dispose = (g->eflags & 0x1C) >> 2; - pcount = g->w * g->h; - - if ((dispose == 3) && (two_back == 0)) { - dispose = 2; // if I don't have an image to revert back to, default to the old background - } - - if (dispose == 3) { // use previous graphic - for (pi = 0; pi < pcount; ++pi) { - if (g->history[pi]) { - memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); - } - } - } else if (dispose == 2) { - // restore what was changed last frame to background before that frame; - for (pi = 0; pi < pcount; ++pi) { - if (g->history[pi]) { - memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); - } - } - } else { - // This is a non-disposal case eithe way, so just - // leave the pixels as is, and they will become the new background - // 1: do not dispose - // 0: not specified. - } - - // background is what out is after the undoing of the previou frame; - memcpy( g->background, g->out, 4 * g->w * g->h ); - } - - // clear my history; - memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame - - for (;;) { - int tag = stbi__get8(s); - switch (tag) { - case 0x2C: /* Image Descriptor */ - { - stbi__int32 x, y, w, h; - stbi_uc *o; - - x = stbi__get16le(s); - y = stbi__get16le(s); - w = stbi__get16le(s); - h = stbi__get16le(s); - if (((x + w) > (g->w)) || ((y + h) > (g->h))) - return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); - - g->line_size = g->w * 4; - g->start_x = x * 4; - g->start_y = y * g->line_size; - g->max_x = g->start_x + w * 4; - g->max_y = g->start_y + h * g->line_size; - g->cur_x = g->start_x; - g->cur_y = g->start_y; - - g->lflags = stbi__get8(s); - - if (g->lflags & 0x40) { - g->step = 8 * g->line_size; // first interlaced spacing - g->parse = 3; - } else { - g->step = g->line_size; - g->parse = 0; - } - - if (g->lflags & 0x80) { - stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); - g->color_table = (stbi_uc *) g->lpal; - } else if (g->flags & 0x80) { - g->color_table = (stbi_uc *) g->pal; - } else - return stbi__errpuc("missing color table", "Corrupt GIF"); - - o = stbi__process_gif_raster(s, g); - if (o == NULL) return NULL; - - // if this was the first frame, - pcount = g->w * g->h; - if (first_frame && (g->bgindex > 0)) { - // if first frame, any pixel not drawn to gets the background color - for (pi = 0; pi < pcount; ++pi) { - if (g->history[pi] == 0) { - g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; - memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); - } - } - } - - return o; - } - - case 0x21: // Comment Extension. - { - int len; - int ext = stbi__get8(s); - if (ext == 0xF9) { // Graphic Control Extension. - len = stbi__get8(s); - if (len == 4) { - g->eflags = stbi__get8(s); - g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. - - // unset old transparent - if (g->transparent >= 0) { - g->pal[g->transparent][3] = 255; - } - if (g->eflags & 0x01) { - g->transparent = stbi__get8(s); - if (g->transparent >= 0) { - g->pal[g->transparent][3] = 0; - } - } else { - // don't need transparent - stbi__skip(s, 1); - g->transparent = -1; - } - } else { - stbi__skip(s, len); - break; - } - } - while ((len = stbi__get8(s)) != 0) { - stbi__skip(s, len); - } - break; - } - - case 0x3B: // gif stream termination code - return (stbi_uc *) s; // using '1' causes warning on some compilers - - default: - return stbi__errpuc("unknown code", "Corrupt GIF"); - } - } -} - -static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) -{ - if (stbi__gif_test(s)) { - int layers = 0; - stbi_uc *u = 0; - stbi_uc *out = 0; - stbi_uc *two_back = 0; - stbi__gif g; - int stride; - memset(&g, 0, sizeof(g)); - if (delays) { - *delays = 0; - } - - do { - u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); - if (u == (stbi_uc *) s) u = 0; // end of animated gif marker - - if (u) { - *x = g.w; - *y = g.h; - ++layers; - stride = g.w * g.h * 4; - - if (out) { - out = (stbi_uc*) STBI_REALLOC( out, layers * stride ); - if (delays) { - *delays = (int*) STBI_REALLOC( *delays, sizeof(int) * layers ); - } - } else { - out = (stbi_uc*)stbi__malloc( layers * stride ); - if (delays) { - *delays = (int*) stbi__malloc( layers * sizeof(int) ); - } - } - memcpy( out + ((layers - 1) * stride), u, stride ); - if (layers >= 2) { - two_back = out - 2 * stride; - } - - if (delays) { - (*delays)[layers - 1U] = g.delay; - } - } - } while (u != 0); - - // free temp buffer; - STBI_FREE(g.out); - STBI_FREE(g.history); - STBI_FREE(g.background); - - // do the final conversion after loading everything; - if (req_comp && req_comp != 4) - out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); - - *z = layers; - return out; - } else { - return stbi__errpuc("not GIF", "Image was not as a gif type."); - } -} - -static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - stbi_uc *u = 0; - stbi__gif g; - memset(&g, 0, sizeof(g)); - - u = stbi__gif_load_next(s, &g, comp, req_comp, 0); - if (u == (stbi_uc *) s) u = 0; // end of animated gif marker - if (u) { - *x = g.w; - *y = g.h; - - // moved conversion to after successful load so that the same - // can be done for multiple frames. - if (req_comp && req_comp != 4) - u = stbi__convert_format(u, 4, req_comp, g.w, g.h); - } - - // free buffers needed for multiple frame loading; - STBI_FREE(g.history); - STBI_FREE(g.background); - - return u; -} - -static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) -{ - return stbi__gif_info_raw(s,x,y,comp); -} -#endif - -// ************************************************************************************************* -// Radiance RGBE HDR loader -// originally by Nicolas Schulz -#ifndef STBI_NO_HDR -static int stbi__hdr_test_core(stbi__context *s, const char *signature) -{ - int i; - for (i=0; signature[i]; ++i) - if (stbi__get8(s) != signature[i]) - return 0; - stbi__rewind(s); - return 1; -} - -static int stbi__hdr_test(stbi__context* s) -{ - int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); - stbi__rewind(s); - if(!r) { - r = stbi__hdr_test_core(s, "#?RGBE\n"); - stbi__rewind(s); - } - return r; -} - -#define STBI__HDR_BUFLEN 1024 -static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) -{ - int len=0; - char c = '\0'; - - c = (char) stbi__get8(z); - - while (!stbi__at_eof(z) && c != '\n') { - buffer[len++] = c; - if (len == STBI__HDR_BUFLEN-1) { - // flush to end of line - while (!stbi__at_eof(z) && stbi__get8(z) != '\n') - ; - break; - } - c = (char) stbi__get8(z); - } - - buffer[len] = 0; - return buffer; -} - -static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) -{ - if ( input[3] != 0 ) { - float f1; - // Exponent - f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); - if (req_comp <= 2) - output[0] = (input[0] + input[1] + input[2]) * f1 / 3; - else { - output[0] = input[0] * f1; - output[1] = input[1] * f1; - output[2] = input[2] * f1; - } - if (req_comp == 2) output[1] = 1; - if (req_comp == 4) output[3] = 1; - } else { - switch (req_comp) { - case 4: output[3] = 1; /* fallthrough */ - case 3: output[0] = output[1] = output[2] = 0; - break; - case 2: output[1] = 1; /* fallthrough */ - case 1: output[0] = 0; - break; - } - } -} - -static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - char buffer[STBI__HDR_BUFLEN]; - char *token; - int valid = 0; - int width, height; - stbi_uc *scanline; - float *hdr_data; - int len; - unsigned char count, value; - int i, j, k, c1,c2, z; - const char *headerToken; - STBI_NOTUSED(ri); - - // Check identifier - headerToken = stbi__hdr_gettoken(s,buffer); - if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) - return stbi__errpf("not HDR", "Corrupt HDR image"); - - // Parse header - for(;;) { - token = stbi__hdr_gettoken(s,buffer); - if (token[0] == 0) break; - if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; - } - - if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); - - // Parse width and height - // can't use sscanf() if we're not using stdio! - token = stbi__hdr_gettoken(s,buffer); - if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); - token += 3; - height = (int) strtol(token, &token, 10); - while (*token == ' ') ++token; - if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); - token += 3; - width = (int) strtol(token, NULL, 10); - - *x = width; - *y = height; - - if (comp) *comp = 3; - if (req_comp == 0) req_comp = 3; - - if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) - return stbi__errpf("too large", "HDR image is too large"); - - // Read data - hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); - if (!hdr_data) - return stbi__errpf("outofmem", "Out of memory"); - - // Load image data - // image data is stored as some number of sca - if ( width < 8 || width >= 32768) { - // Read flat data - for (j=0; j < height; ++j) { - for (i=0; i < width; ++i) { - stbi_uc rgbe[4]; - main_decode_loop: - stbi__getn(s, rgbe, 4); - stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); - } - } - } else { - // Read RLE-encoded data - scanline = NULL; - - for (j = 0; j < height; ++j) { - c1 = stbi__get8(s); - c2 = stbi__get8(s); - len = stbi__get8(s); - if (c1 != 2 || c2 != 2 || (len & 0x80)) { - // not run-length encoded, so we have to actually use THIS data as a decoded - // pixel (note this can't be a valid pixel--one of RGB must be >= 128) - stbi_uc rgbe[4]; - rgbe[0] = (stbi_uc) c1; - rgbe[1] = (stbi_uc) c2; - rgbe[2] = (stbi_uc) len; - rgbe[3] = (stbi_uc) stbi__get8(s); - stbi__hdr_convert(hdr_data, rgbe, req_comp); - i = 1; - j = 0; - STBI_FREE(scanline); - goto main_decode_loop; // yes, this makes no sense - } - len <<= 8; - len |= stbi__get8(s); - if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } - if (scanline == NULL) { - scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); - if (!scanline) { - STBI_FREE(hdr_data); - return stbi__errpf("outofmem", "Out of memory"); - } - } - - for (k = 0; k < 4; ++k) { - int nleft; - i = 0; - while ((nleft = width - i) > 0) { - count = stbi__get8(s); - if (count > 128) { - // Run - value = stbi__get8(s); - count -= 128; - if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } - for (z = 0; z < count; ++z) - scanline[i++ * 4 + k] = value; - } else { - // Dump - if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } - for (z = 0; z < count; ++z) - scanline[i++ * 4 + k] = stbi__get8(s); - } - } - } - for (i=0; i < width; ++i) - stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); - } - if (scanline) - STBI_FREE(scanline); - } - - return hdr_data; -} - -static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) -{ - char buffer[STBI__HDR_BUFLEN]; - char *token; - int valid = 0; - int dummy; - - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - - if (stbi__hdr_test(s) == 0) { - stbi__rewind( s ); - return 0; - } - - for(;;) { - token = stbi__hdr_gettoken(s,buffer); - if (token[0] == 0) break; - if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; - } - - if (!valid) { - stbi__rewind( s ); - return 0; - } - token = stbi__hdr_gettoken(s,buffer); - if (strncmp(token, "-Y ", 3)) { - stbi__rewind( s ); - return 0; - } - token += 3; - *y = (int) strtol(token, &token, 10); - while (*token == ' ') ++token; - if (strncmp(token, "+X ", 3)) { - stbi__rewind( s ); - return 0; - } - token += 3; - *x = (int) strtol(token, NULL, 10); - *comp = 3; - return 1; -} -#endif // STBI_NO_HDR - -#ifndef STBI_NO_BMP -static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) -{ - void *p; - stbi__bmp_data info; - - info.all_a = 255; - p = stbi__bmp_parse_header(s, &info); - stbi__rewind( s ); - if (p == NULL) - return 0; - if (x) *x = s->img_x; - if (y) *y = s->img_y; - if (comp) *comp = info.ma ? 4 : 3; - return 1; -} -#endif - -#ifndef STBI_NO_PSD -static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) -{ - int channelCount, dummy, depth; - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - if (stbi__get32be(s) != 0x38425053) { - stbi__rewind( s ); - return 0; - } - if (stbi__get16be(s) != 1) { - stbi__rewind( s ); - return 0; - } - stbi__skip(s, 6); - channelCount = stbi__get16be(s); - if (channelCount < 0 || channelCount > 16) { - stbi__rewind( s ); - return 0; - } - *y = stbi__get32be(s); - *x = stbi__get32be(s); - depth = stbi__get16be(s); - if (depth != 8 && depth != 16) { - stbi__rewind( s ); - return 0; - } - if (stbi__get16be(s) != 3) { - stbi__rewind( s ); - return 0; - } - *comp = 4; - return 1; -} - -static int stbi__psd_is16(stbi__context *s) -{ - int channelCount, depth; - if (stbi__get32be(s) != 0x38425053) { - stbi__rewind( s ); - return 0; - } - if (stbi__get16be(s) != 1) { - stbi__rewind( s ); - return 0; - } - stbi__skip(s, 6); - channelCount = stbi__get16be(s); - if (channelCount < 0 || channelCount > 16) { - stbi__rewind( s ); - return 0; - } - (void) stbi__get32be(s); - (void) stbi__get32be(s); - depth = stbi__get16be(s); - if (depth != 16) { - stbi__rewind( s ); - return 0; - } - return 1; -} -#endif - -#ifndef STBI_NO_PIC -static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) -{ - int act_comp=0,num_packets=0,chained,dummy; - stbi__pic_packet packets[10]; - - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - - if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { - stbi__rewind(s); - return 0; - } - - stbi__skip(s, 88); - - *x = stbi__get16be(s); - *y = stbi__get16be(s); - if (stbi__at_eof(s)) { - stbi__rewind( s); - return 0; - } - if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { - stbi__rewind( s ); - return 0; - } - - stbi__skip(s, 8); - - do { - stbi__pic_packet *packet; - - if (num_packets==sizeof(packets)/sizeof(packets[0])) - return 0; - - packet = &packets[num_packets++]; - chained = stbi__get8(s); - packet->size = stbi__get8(s); - packet->type = stbi__get8(s); - packet->channel = stbi__get8(s); - act_comp |= packet->channel; - - if (stbi__at_eof(s)) { - stbi__rewind( s ); - return 0; - } - if (packet->size != 8) { - stbi__rewind( s ); - return 0; - } - } while (chained); - - *comp = (act_comp & 0x10 ? 4 : 3); - - return 1; -} -#endif - -// ************************************************************************************************* -// Portable Gray Map and Portable Pixel Map loader -// by Ken Miller -// -// PGM: http://netpbm.sourceforge.net/doc/pgm.html -// PPM: http://netpbm.sourceforge.net/doc/ppm.html -// -// Known limitations: -// Does not support comments in the header section -// Does not support ASCII image data (formats P2 and P3) -// Does not support 16-bit-per-channel - -#ifndef STBI_NO_PNM - -static int stbi__pnm_test(stbi__context *s) -{ - char p, t; - p = (char) stbi__get8(s); - t = (char) stbi__get8(s); - if (p != 'P' || (t != '5' && t != '6')) { - stbi__rewind( s ); - return 0; - } - return 1; -} - -static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) -{ - stbi_uc *out; - STBI_NOTUSED(ri); - - if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n)) - return 0; - - *x = s->img_x; - *y = s->img_y; - if (comp) *comp = s->img_n; - - if (!stbi__mad3sizes_valid(s->img_n, s->img_x, s->img_y, 0)) - return stbi__errpuc("too large", "PNM too large"); - - out = (stbi_uc *) stbi__malloc_mad3(s->img_n, s->img_x, s->img_y, 0); - if (!out) return stbi__errpuc("outofmem", "Out of memory"); - stbi__getn(s, out, s->img_n * s->img_x * s->img_y); - - if (req_comp && req_comp != s->img_n) { - out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); - if (out == NULL) return out; // stbi__convert_format frees input on failure - } - return out; -} - -static int stbi__pnm_isspace(char c) -{ - return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; -} - -static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) -{ - for (;;) { - while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) - *c = (char) stbi__get8(s); - - if (stbi__at_eof(s) || *c != '#') - break; - - while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) - *c = (char) stbi__get8(s); - } -} - -static int stbi__pnm_isdigit(char c) -{ - return c >= '0' && c <= '9'; -} - -static int stbi__pnm_getinteger(stbi__context *s, char *c) -{ - int value = 0; - - while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { - value = value*10 + (*c - '0'); - *c = (char) stbi__get8(s); - } - - return value; -} - -static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) -{ - int maxv, dummy; - char c, p, t; - - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - - stbi__rewind(s); - - // Get identifier - p = (char) stbi__get8(s); - t = (char) stbi__get8(s); - if (p != 'P' || (t != '5' && t != '6')) { - stbi__rewind(s); - return 0; - } - - *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm - - c = (char) stbi__get8(s); - stbi__pnm_skip_whitespace(s, &c); - - *x = stbi__pnm_getinteger(s, &c); // read width - stbi__pnm_skip_whitespace(s, &c); - - *y = stbi__pnm_getinteger(s, &c); // read height - stbi__pnm_skip_whitespace(s, &c); - - maxv = stbi__pnm_getinteger(s, &c); // read max value - - if (maxv > 255) - return stbi__err("max value > 255", "PPM image not 8-bit"); - else - return 1; -} -#endif - -static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) -{ - #ifndef STBI_NO_JPEG - if (stbi__jpeg_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PNG - if (stbi__png_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_GIF - if (stbi__gif_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_BMP - if (stbi__bmp_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PSD - if (stbi__psd_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PIC - if (stbi__pic_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_PNM - if (stbi__pnm_info(s, x, y, comp)) return 1; - #endif - - #ifndef STBI_NO_HDR - if (stbi__hdr_info(s, x, y, comp)) return 1; - #endif - - // test tga last because it's a crappy test! - #ifndef STBI_NO_TGA - if (stbi__tga_info(s, x, y, comp)) - return 1; - #endif - return stbi__err("unknown image type", "Image not of any known type, or corrupt"); -} - -static int stbi__is_16_main(stbi__context *s) -{ - #ifndef STBI_NO_PNG - if (stbi__png_is16(s)) return 1; - #endif - - #ifndef STBI_NO_PSD - if (stbi__psd_is16(s)) return 1; - #endif - - return 0; -} - -#ifndef STBI_NO_STDIO -STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) -{ - FILE *f = stbi__fopen(filename, "rb"); - int result; - if (!f) return stbi__err("can't fopen", "Unable to open file"); - result = stbi_info_from_file(f, x, y, comp); - fclose(f); - return result; -} - -STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) -{ - int r; - stbi__context s; - long pos = ftell(f); - stbi__start_file(&s, f); - r = stbi__info_main(&s,x,y,comp); - if (pos >= 0) { - if (fseek(f,pos,SEEK_SET) == -1) return stbi__err("fseek() error", "File Seek Fail"); - } - return r; -} - -STBIDEF int stbi_is_16_bit(char const *filename) -{ - FILE *f = stbi__fopen(filename, "rb"); - int result; - if (!f) return stbi__err("can't fopen", "Unable to open file"); - result = stbi_is_16_bit_from_file(f); - fclose(f); - return result; -} - -STBIDEF int stbi_is_16_bit_from_file(FILE *f) -{ - int r; - stbi__context s; - long pos = ftell(f); - stbi__start_file(&s, f); - r = stbi__is_16_main(&s); - if (pos >= 0) { - if (fseek(f,pos,SEEK_SET) == -1) return stbi__err("fseek() error", "File Seek Fail"); - } - return r; -} -#endif // !STBI_NO_STDIO - -STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__info_main(&s,x,y,comp); -} - -STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); - return stbi__info_main(&s,x,y,comp); -} - -STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__is_16_main(&s); -} - -STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); - return stbi__is_16_main(&s); -} - -#endif // STB_IMAGE_IMPLEMENTATION - -/* - revision history: - 2.19 (2018-02-11) fix warning - 2.18 (2018-01-30) fix warnings - 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug - 1-bit BMP - *_is_16_bit api - avoid warnings - 2.16 (2017-07-23) all functions have 16-bit variants; - STBI_NO_STDIO works again; - compilation fixes; - fix rounding in unpremultiply; - optimize vertical flip; - disable raw_len validation; - documentation fixes - 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; - warning fixes; disable run-time SSE detection on gcc; - uniform handling of optional "return" values; - thread-safe initialization of zlib tables - 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs - 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now - 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes - 2.11 (2016-04-02) allocate large structures on the stack - remove white matting for transparent PSD - fix reported channel count for PNG & BMP - re-enable SSE2 in non-gcc 64-bit - support RGB-formatted JPEG - read 16-bit PNGs (only as 8-bit) - 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED - 2.09 (2016-01-16) allow comments in PNM files - 16-bit-per-pixel TGA (not bit-per-component) - info() for TGA could break due to .hdr handling - info() for BMP to shares code instead of sloppy parse - can use STBI_REALLOC_SIZED if allocator doesn't support realloc - code cleanup - 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA - 2.07 (2015-09-13) fix compiler warnings - partial animated GIF support - limited 16-bpc PSD support - #ifdef unused functions - bug with < 92 byte PIC,PNM,HDR,TGA - 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value - 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning - 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit - 2.03 (2015-04-12) extra corruption checking (mmozeiko) - stbi_set_flip_vertically_on_load (nguillemot) - fix NEON support; fix mingw support - 2.02 (2015-01-19) fix incorrect assert, fix warning - 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 - 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG - 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) - progressive JPEG (stb) - PGM/PPM support (Ken Miller) - STBI_MALLOC,STBI_REALLOC,STBI_FREE - GIF bugfix -- seemingly never worked - STBI_NO_*, STBI_ONLY_* - 1.48 (2014-12-14) fix incorrectly-named assert() - 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) - optimize PNG (ryg) - fix bug in interlaced PNG with user-specified channel count (stb) - 1.46 (2014-08-26) - fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG - 1.45 (2014-08-16) - fix MSVC-ARM internal compiler error by wrapping malloc - 1.44 (2014-08-07) - various warning fixes from Ronny Chevalier - 1.43 (2014-07-15) - fix MSVC-only compiler problem in code changed in 1.42 - 1.42 (2014-07-09) - don't define _CRT_SECURE_NO_WARNINGS (affects user code) - fixes to stbi__cleanup_jpeg path - added STBI_ASSERT to avoid requiring assert.h - 1.41 (2014-06-25) - fix search&replace from 1.36 that messed up comments/error messages - 1.40 (2014-06-22) - fix gcc struct-initialization warning - 1.39 (2014-06-15) - fix to TGA optimization when req_comp != number of components in TGA; - fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) - add support for BMP version 5 (more ignored fields) - 1.38 (2014-06-06) - suppress MSVC warnings on integer casts truncating values - fix accidental rename of 'skip' field of I/O - 1.37 (2014-06-04) - remove duplicate typedef - 1.36 (2014-06-03) - convert to header file single-file library - if de-iphone isn't set, load iphone images color-swapped instead of returning NULL - 1.35 (2014-05-27) - various warnings - fix broken STBI_SIMD path - fix bug where stbi_load_from_file no longer left file pointer in correct place - fix broken non-easy path for 32-bit BMP (possibly never used) - TGA optimization by Arseny Kapoulkine - 1.34 (unknown) - use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case - 1.33 (2011-07-14) - make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements - 1.32 (2011-07-13) - support for "info" function for all supported filetypes (SpartanJ) - 1.31 (2011-06-20) - a few more leak fixes, bug in PNG handling (SpartanJ) - 1.30 (2011-06-11) - added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) - removed deprecated format-specific test/load functions - removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway - error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) - fix inefficiency in decoding 32-bit BMP (David Woo) - 1.29 (2010-08-16) - various warning fixes from Aurelien Pocheville - 1.28 (2010-08-01) - fix bug in GIF palette transparency (SpartanJ) - 1.27 (2010-08-01) - cast-to-stbi_uc to fix warnings - 1.26 (2010-07-24) - fix bug in file buffering for PNG reported by SpartanJ - 1.25 (2010-07-17) - refix trans_data warning (Won Chun) - 1.24 (2010-07-12) - perf improvements reading from files on platforms with lock-heavy fgetc() - minor perf improvements for jpeg - deprecated type-specific functions so we'll get feedback if they're needed - attempt to fix trans_data warning (Won Chun) - 1.23 fixed bug in iPhone support - 1.22 (2010-07-10) - removed image *writing* support - stbi_info support from Jetro Lauha - GIF support from Jean-Marc Lienher - iPhone PNG-extensions from James Brown - warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) - 1.21 fix use of 'stbi_uc' in header (reported by jon blow) - 1.20 added support for Softimage PIC, by Tom Seddon - 1.19 bug in interlaced PNG corruption check (found by ryg) - 1.18 (2008-08-02) - fix a threading bug (local mutable static) - 1.17 support interlaced PNG - 1.16 major bugfix - stbi__convert_format converted one too many pixels - 1.15 initialize some fields for thread safety - 1.14 fix threadsafe conversion bug - header-file-only version (#define STBI_HEADER_FILE_ONLY before including) - 1.13 threadsafe - 1.12 const qualifiers in the API - 1.11 Support installable IDCT, colorspace conversion routines - 1.10 Fixes for 64-bit (don't use "unsigned long") - optimized upsampling by Fabian "ryg" Giesen - 1.09 Fix format-conversion for PSD code (bad global variables!) - 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz - 1.07 attempt to fix C++ warning/errors again - 1.06 attempt to fix C++ warning/errors again - 1.05 fix TGA loading to return correct *comp and use good luminance calc - 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free - 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR - 1.02 support for (subset of) HDR files, float interface for preferred access to them - 1.01 fix bug: possible bug in handling right-side up bmps... not sure - fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all - 1.00 interface to zlib that skips zlib header - 0.99 correct handling of alpha in palette - 0.98 TGA loader by lonesock; dynamically add loaders (untested) - 0.97 jpeg errors on too large a file; also catch another malloc failure - 0.96 fix detection of invalid v value - particleman@mollyrocket forum - 0.95 during header scan, seek to markers in case of padding - 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same - 0.93 handle jpegtran output; verbose errors - 0.92 read 4,8,16,24,32-bit BMP files of several formats - 0.91 output 24-bit Windows 3.0 BMP files - 0.90 fix a few more warnings; bump version number to approach 1.0 - 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd - 0.60 fix compiling as c++ - 0.59 fix warnings: merge Dave Moore's -Wall fixes - 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian - 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available - 0.56 fix bug: zlib uncompressed mode len vs. nlen - 0.55 fix bug: restart_interval not initialized to 0 - 0.54 allow NULL for 'int *comp' - 0.53 fix bug in png 3->4; speedup png decoding - 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments - 0.51 obey req_comp requests, 1-component jpegs return as 1-component, - on 'test' only check type, not whether we support this variant - 0.50 (2006-11-19) - first released version -*/ - - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/varenaalloc.cpp b/libfenrir/src/main/jni/animation/rlottie/src/vector/varenaalloc.cpp deleted file mode 100644 index a31351432..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/varenaalloc.cpp +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright 2016 Google Inc. - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -#include "varenaalloc.h" -#include -#include - -static char* end_chain(char*) { return nullptr; } - -static uint32_t first_allocated_block(uint32_t blockSize, uint32_t firstHeapAllocation) { - return firstHeapAllocation > 0 ? firstHeapAllocation : - blockSize > 0 ? blockSize : 1024; -} - -VArenaAlloc::VArenaAlloc(char* block, size_t size, size_t firstHeapAllocation) - : fDtorCursor {block} - , fCursor {block} - , fEnd {block + ToU32(size)} - , fFirstBlock {block} - , fFirstSize {ToU32(size)} - , fFirstHeapAllocationSize {first_allocated_block(ToU32(size), ToU32(firstHeapAllocation))} -{ - if (size < sizeof(Footer)) { - fEnd = fCursor = fDtorCursor = nullptr; - } - - if (fCursor != nullptr) { - this->installFooter(end_chain, 0); - } -} - -VArenaAlloc::~VArenaAlloc() { - RunDtorsOnBlock(fDtorCursor); -} - -void VArenaAlloc::reset() { - this->~VArenaAlloc(); - new (this) VArenaAlloc{fFirstBlock, fFirstSize, fFirstHeapAllocationSize}; -} - -void VArenaAlloc::installFooter(FooterAction* action, uint32_t padding) { - assert(padding < 64); - auto actionInt = (int64_t)(intptr_t)action; - - // The top 14 bits should be either all 0s or all 1s. Check this. - assert((actionInt << 6) >> 6 == actionInt); - Footer encodedFooter = (actionInt << 6) | padding; - memmove(fCursor, &encodedFooter, sizeof(Footer)); - fCursor += sizeof(Footer); - fDtorCursor = fCursor; -} - -void VArenaAlloc::installPtrFooter(FooterAction* action, char* ptr, uint32_t padding) { - memmove(fCursor, &ptr, sizeof(char*)); - fCursor += sizeof(char*); - this->installFooter(action, padding); -} - -char* VArenaAlloc::SkipPod(char* footerEnd) { - char* objEnd = footerEnd - (sizeof(Footer) + sizeof(int32_t)); - int32_t skip; - memmove(&skip, objEnd, sizeof(int32_t)); - return objEnd - skip; -} - -void VArenaAlloc::RunDtorsOnBlock(char* footerEnd) { - while (footerEnd != nullptr) { - Footer footer; - memcpy(&footer, footerEnd - sizeof(Footer), sizeof(Footer)); - - auto* action = (FooterAction*)(footer >> 6); - ptrdiff_t padding = footer & 63; - - footerEnd = action(footerEnd) - padding; - } -} - -char* VArenaAlloc::NextBlock(char* footerEnd) { - char* objEnd = footerEnd - (sizeof(Footer) + sizeof(char*)); - char* next; - memmove(&next, objEnd, sizeof(char*)); - RunDtorsOnBlock(next); - delete [] objEnd; - return nullptr; -} - -void VArenaAlloc::installUint32Footer(FooterAction* action, uint32_t value, uint32_t padding) { - memmove(fCursor, &value, sizeof(uint32_t)); - fCursor += sizeof(uint32_t); - this->installFooter(action, padding); -} - -void VArenaAlloc::ensureSpace(uint32_t size, uint32_t alignment) { - constexpr uint32_t headerSize = sizeof(Footer) + sizeof(ptrdiff_t); - // The chrome c++ library we use does not define std::max_align_t. - // This must be conservative to add the right amount of extra memory to handle the alignment - // padding. - constexpr uint32_t alignof_max_align_t = 8; - constexpr uint32_t maxSize = std::numeric_limits::max(); - constexpr uint32_t overhead = headerSize + sizeof(Footer); - AssertRelease(size <= maxSize - overhead); - uint32_t objSizeAndOverhead = size + overhead; - if (alignment > alignof_max_align_t) { - uint32_t alignmentOverhead = alignment - 1; - AssertRelease(objSizeAndOverhead <= maxSize - alignmentOverhead); - objSizeAndOverhead += alignmentOverhead; - } - - uint32_t minAllocationSize; - if (fFirstHeapAllocationSize <= maxSize / fFib0) { - minAllocationSize = fFirstHeapAllocationSize * fFib0; - fFib0 += fFib1; - std::swap(fFib0, fFib1); - } else { - minAllocationSize = maxSize; - } - uint32_t allocationSize = std::max(objSizeAndOverhead, minAllocationSize); - - // Round up to a nice size. If > 32K align to 4K boundary else up to max_align_t. The > 32K - // heuristic is from the JEMalloc behavior. - { - uint32_t mask = allocationSize > (1 << 15) ? (1 << 12) - 1 : 16 - 1; - AssertRelease(allocationSize <= maxSize - mask); - allocationSize = (allocationSize + mask) & ~mask; - } - - char* newBlock = new char[allocationSize]; - - auto previousDtor = fDtorCursor; - fCursor = newBlock; - fDtorCursor = newBlock; - fEnd = fCursor + allocationSize; - this->installPtrFooter(NextBlock, previousDtor, 0); -} - -char* VArenaAlloc::allocObjectWithFooter(uint32_t sizeIncludingFooter, uint32_t alignment) { - uintptr_t mask = alignment - 1; - -restart: - uint32_t skipOverhead = 0; - bool needsSkipFooter = fCursor != fDtorCursor; - if (needsSkipFooter) { - skipOverhead = sizeof(Footer) + sizeof(uint32_t); - } - char* objStart = (char*)((uintptr_t)(fCursor + skipOverhead + mask) & ~mask); - uint32_t totalSize = sizeIncludingFooter + skipOverhead; - //std::cout<<"non POD object size = "< fEnd - objStart) { - this->ensureSpace(totalSize, alignment); - goto restart; - } - - AssertRelease((ptrdiff_t)totalSize <= fEnd - objStart); - - // Install a skip footer if needed, thus terminating a run of POD data. The calling code is - // responsible for installing the footer after the object. - if (needsSkipFooter) { - this->installUint32Footer(SkipPod, ToU32(fCursor - fDtorCursor), 0); - } - - return objStart; -} diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/varenaalloc.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/varenaalloc.h deleted file mode 100644 index ed03b53f2..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/varenaalloc.h +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright 2016 Google Inc. - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -#ifndef VARENAALLOC_H -#define VARENAALLOC_H -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// SkArenaAlloc allocates object and destroys the allocated objects when destroyed. It's designed -// to minimize the number of underlying block allocations. SkArenaAlloc allocates first out of an -// (optional) user-provided block of memory, and when that's exhausted it allocates on the heap, -// starting with an allocation of firstHeapAllocation bytes. If your data (plus a small overhead) -// fits in the user-provided block, SkArenaAlloc never uses the heap, and if it fits in -// firstHeapAllocation bytes, it'll use the heap only once. If 0 is specified for -// firstHeapAllocation, then blockSize is used unless that too is 0, then 1024 is used. -// -// Examples: -// -// char block[mostCasesSize]; -// SkArenaAlloc arena(block, mostCasesSize); -// -// If mostCasesSize is too large for the stack, you can use the following pattern. -// -// std::unique_ptr block{new char[mostCasesSize]}; -// SkArenaAlloc arena(block.get(), mostCasesSize, almostAllCasesSize); -// -// If the program only sometimes allocates memory, use the following pattern. -// -// SkArenaAlloc arena(nullptr, 0, almostAllCasesSize); -// -// The storage does not necessarily need to be on the stack. Embedding the storage in a class also -// works. -// -// class Foo { -// char storage[mostCasesSize]; -// SkArenaAlloc arena (storage, mostCasesSize); -// }; -// -// In addition, the system is optimized to handle POD data including arrays of PODs (where -// POD is really data with no destructors). For POD data it has zero overhead per item, and a -// typical per block overhead of 8 bytes. For non-POD objects there is a per item overhead of 4 -// bytes. For arrays of non-POD objects there is a per array overhead of typically 8 bytes. There -// is an addition overhead when switching from POD data to non-POD data of typically 8 bytes. -// -// If additional blocks are needed they are increased exponentially. This strategy bounds the -// recursion of the RunDtorsOnBlock to be limited to O(log size-of-memory). Block size grow using -// the Fibonacci sequence which means that for 2^32 memory there are 48 allocations, and for 2^48 -// there are 71 allocations. -class VArenaAlloc { -public: - VArenaAlloc(char* block, size_t blockSize, size_t firstHeapAllocation); - - explicit VArenaAlloc(size_t firstHeapAllocation) - : VArenaAlloc(nullptr, 0, firstHeapAllocation) - {} - - ~VArenaAlloc(); - - template - T* make(Args&&... args) { - uint32_t size = ToU32(sizeof(T)); - uint32_t alignment = ToU32(alignof(T)); - char* objStart; - if (std::is_trivially_destructible::value) { - objStart = this->allocObject(size, alignment); - fCursor = objStart + size; - } else { - objStart = this->allocObjectWithFooter(size + sizeof(Footer), alignment); - // Can never be UB because max value is alignof(T). - uint32_t padding = ToU32(objStart - fCursor); - - // Advance to end of object to install footer. - fCursor = objStart + size; - FooterAction* releaser = [](char* objEnd) { - char* objStart = objEnd - (sizeof(T) + sizeof(Footer)); - ((T*)objStart)->~T(); - return objStart; - }; - this->installFooter(releaser, padding); - } - - // This must be last to make objects with nested use of this allocator work. - return new(objStart) T(std::forward(args)...); - } - - template - T* makeArrayDefault(size_t count) { - uint32_t safeCount = ToU32(count); - T* array = (T*)this->commonArrayAlloc(safeCount); - - // If T is primitive then no initialization takes place. - for (size_t i = 0; i < safeCount; i++) { - new (&array[i]) T; - } - return array; - } - - template - T* makeArray(size_t count) { - uint32_t safeCount = ToU32(count); - T* array = (T*)this->commonArrayAlloc(safeCount); - - // If T is primitive then the memory is initialized. For example, an array of chars will - // be zeroed. - for (size_t i = 0; i < safeCount; i++) { - new (&array[i]) T(); - } - return array; - } - - // Only use makeBytesAlignedTo if none of the typed variants are impractical to use. - void* makeBytesAlignedTo(size_t size, size_t align) { - auto objStart = this->allocObject(ToU32(size), ToU32(align)); - fCursor = objStart + size; - return objStart; - } - - // Destroy all allocated objects, free any heap allocations. - void reset(); - -private: - static void AssertRelease(bool cond) { if (!cond) { ::abort(); } } - static uint32_t ToU32(size_t v) { - return (uint32_t)v; - } - - using Footer = int64_t; - using FooterAction = char* (char*); - - static char* SkipPod(char* footerEnd); - static void RunDtorsOnBlock(char* footerEnd); - static char* NextBlock(char* footerEnd); - - void installFooter(FooterAction* releaser, uint32_t padding); - void installUint32Footer(FooterAction* action, uint32_t value, uint32_t padding); - void installPtrFooter(FooterAction* action, char* ptr, uint32_t padding); - - void ensureSpace(uint32_t size, uint32_t alignment); - - char* allocObject(uint32_t size, uint32_t alignment) { - uintptr_t mask = alignment - 1; - uintptr_t alignedOffset = (~reinterpret_cast(fCursor) + 1) & mask; - uintptr_t totalSize = size + alignedOffset; - AssertRelease(totalSize >= size); - - if (totalSize > static_cast(fEnd - fCursor)) { - this->ensureSpace(size, alignment); - alignedOffset = (~reinterpret_cast(fCursor) + 1) & mask; - } - return fCursor + alignedOffset; - } - - char* allocObjectWithFooter(uint32_t sizeIncludingFooter, uint32_t alignment); - - template - char* commonArrayAlloc(uint32_t count) { - char* objStart; - AssertRelease(count <= std::numeric_limits::max() / sizeof(T)); - uint32_t arraySize = ToU32(count * sizeof(T)); - uint32_t alignment = ToU32(alignof(T)); - - if (std::is_trivially_destructible::value) { - objStart = this->allocObject(arraySize, alignment); - fCursor = objStart + arraySize; - } else { - constexpr uint32_t overhead = sizeof(Footer) + sizeof(uint32_t); - AssertRelease(arraySize <= std::numeric_limits::max() - overhead); - uint32_t totalSize = arraySize + overhead; - objStart = this->allocObjectWithFooter(totalSize, alignment); - - // Can never be UB because max value is alignof(T). - uint32_t padding = ToU32(objStart - fCursor); - - // Advance to end of array to install footer.? - fCursor = objStart + arraySize; - this->installUint32Footer( - [](char* footerEnd) { - char* objEnd = footerEnd - (sizeof(Footer) + sizeof(uint32_t)); - uint32_t count; - memmove(&count, objEnd, sizeof(uint32_t)); - char* objStart = objEnd - count * sizeof(T); - T* array = (T*) objStart; - for (uint32_t i = 0; i < count; i++) { - array[i].~T(); - } - return objStart; - }, - ToU32(count), - padding); - } - - return objStart; - } - - char* fDtorCursor; - char* fCursor; - char* fEnd; - char* const fFirstBlock; - const uint32_t fFirstSize; - const uint32_t fFirstHeapAllocationSize; - - // Use the Fibonacci sequence as the growth factor for block size. The size of the block - // allocated is fFib0 * fFirstHeapAllocationSize. Using 2 ^ n * fFirstHeapAllocationSize - // had too much slop for Android. - uint32_t fFib0 {1}, fFib1 {1}; -}; - -// Helper for defining allocators with inline/reserved storage. -// For argument declarations, stick to the base type (SkArenaAlloc). -template -class VSTArenaAlloc : public VArenaAlloc { -public: - explicit VSTArenaAlloc(size_t firstHeapAllocation = InlineStorageSize) - : VArenaAlloc(fInlineStorage, InlineStorageSize, firstHeapAllocation) {} - -private: - char fInlineStorage[InlineStorageSize]; -}; - -#endif // VARENAALLOC_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vbezier.cpp b/libfenrir/src/main/jni/animation/rlottie/src/vector/vbezier.cpp deleted file mode 100644 index 15889299d..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vbezier.cpp +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "vbezier.h" -#include -#include "vline.h" - -V_BEGIN_NAMESPACE - -VBezier VBezier::fromPoints(const VPointF &p1, const VPointF &p2, - const VPointF &p3, const VPointF &p4) -{ - VBezier b; - b.x1 = p1.x(); - b.y1 = p1.y(); - b.x2 = p2.x(); - b.y2 = p2.y(); - b.x3 = p3.x(); - b.y3 = p3.y(); - b.x4 = p4.x(); - b.y4 = p4.y(); - return b; -} - -float VBezier::length() const -{ - const auto len = VLine::length(x1, y1, x2, y2) + - VLine::length(x2, y2, x3, y3) + - VLine::length(x3, y3, x4, y4); - - const auto chord = VLine::length(x1, y1, x4, y4); - - if ((len - chord) > 0.01) { - VBezier left, right; - split(&left, &right); - return left.length() + right.length(); - } - - return len; -} - -VBezier VBezier::onInterval(float t0, float t1) const -{ - if (t0 == 0 && t1 == 1) return *this; - - VBezier bezier = *this; - - VBezier result; - bezier.parameterSplitLeft(t0, &result); - float trueT = (t1 - t0) / (1 - t0); - bezier.parameterSplitLeft(trueT, &result); - - return result; -} - -float VBezier::tAtLength(float l, float totalLength) const -{ - float t = 1.0; - const float error = 0.01f; - if (l > totalLength || vCompare(l, totalLength)) return t; - - t *= 0.5; - - float lastBigger = 1.0; - for (int num = 0; num < 100500; num++) { - VBezier right = *this; - VBezier left; - right.parameterSplitLeft(t, &left); - float lLen = left.length(); - if (fabs(lLen - l) < error) return t; - - if (lLen < l) { - t += (lastBigger - t) * 0.5f; - } else { - lastBigger = t; - t -= t * 0.5f; - } - } - vWarning << "no convergence"; - return t; -} - -void VBezier::splitAtLength(float len, VBezier *left, VBezier *right) -{ - float t; - - *right = *this; - t = right->tAtLength(len); - right->parameterSplitLeft(t, left); -} - -VPointF VBezier::derivative(float t) const -{ - // p'(t) = 3 * (-(1-2t+t^2) * p0 + (1 - 4 * t + 3 * t^2) * p1 + (2 * t - 3 * - // t^2) * p2 + t^2 * p3) - - float m_t = 1.0f - t; - - float d = t * t; - float a = -m_t * m_t; - float b = 1 - 4 * t + 3 * d; - float c = 2 * t - 3 * d; - - return 3 * VPointF(a * x1 + b * x2 + c * x3 + d * x4, - a * y1 + b * y2 + c * y3 + d * y4); -} - -float VBezier::angleAt(float t) const -{ - if (t < 0 || t > 1) { - return 0; - } - return VLine({}, derivative(t)).angle(); -} - -V_END_NAMESPACE diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vbezier.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/vbezier.h deleted file mode 100644 index 18b7c596d..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vbezier.h +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef VBEZIER_H -#define VBEZIER_H - -#include - -V_BEGIN_NAMESPACE - -class VBezier { -public: - VBezier() = default; - VPointF pointAt(float t) const; - float angleAt(float t) const; - VBezier onInterval(float t0, float t1) const; - float length() const; - static void coefficients(float t, float &a, float &b, float &c, float &d); - static VBezier fromPoints(const VPointF &start, const VPointF &cp1, - const VPointF &cp2, const VPointF &end); - inline void parameterSplitLeft(float t, VBezier *left); - inline void split(VBezier *firstHalf, VBezier *secondHalf) const; - float tAtLength(float len) const { return tAtLength(len , length());} - float tAtLength(float len, float totalLength) const; - void splitAtLength(float len, VBezier *left, VBezier *right); - VPointF pt1() const { return {x1, y1}; } - VPointF pt2() const { return {x2, y2}; } - VPointF pt3() const { return {x3, y3}; } - VPointF pt4() const { return {x4, y4}; } - -private: - VPointF derivative(float t) const; - float x1, y1, x2, y2, x3, y3, x4, y4; -}; - -inline void VBezier::coefficients(float t, float &a, float &b, float &c, - float &d) -{ - float m_t = 1.0f - t; - b = m_t * m_t; - c = t * t; - d = c * t; - a = b * m_t; - b *= 3.0f * t; - c *= 3.0f * m_t; -} - -inline VPointF VBezier::pointAt(float t) const -{ - // numerically more stable: - float x, y; - - float m_t = 1.0f - t; - { - float a = x1 * m_t + x2 * t; - float b = x2 * m_t + x3 * t; - float c = x3 * m_t + x4 * t; - a = a * m_t + b * t; - b = b * m_t + c * t; - x = a * m_t + b * t; - } - { - float a = y1 * m_t + y2 * t; - float b = y2 * m_t + y3 * t; - float c = y3 * m_t + y4 * t; - a = a * m_t + b * t; - b = b * m_t + c * t; - y = a * m_t + b * t; - } - return {x, y}; -} - -inline void VBezier::parameterSplitLeft(float t, VBezier *left) -{ - left->x1 = x1; - left->y1 = y1; - - left->x2 = x1 + t * (x2 - x1); - left->y2 = y1 + t * (y2 - y1); - - left->x3 = x2 + t * (x3 - x2); // temporary holding spot - left->y3 = y2 + t * (y3 - y2); // temporary holding spot - - x3 = x3 + t * (x4 - x3); - y3 = y3 + t * (y4 - y3); - - x2 = left->x3 + t * (x3 - left->x3); - y2 = left->y3 + t * (y3 - left->y3); - - left->x3 = left->x2 + t * (left->x3 - left->x2); - left->y3 = left->y2 + t * (left->y3 - left->y2); - - left->x4 = x1 = left->x3 + t * (x2 - left->x3); - left->y4 = y1 = left->y3 + t * (y2 - left->y3); -} - -inline void VBezier::split(VBezier *firstHalf, VBezier *secondHalf) const -{ - float c = (x2 + x3) * 0.5f; - firstHalf->x2 = (x1 + x2) * 0.5f; - secondHalf->x3 = (x3 + x4) * 0.5f; - firstHalf->x1 = x1; - secondHalf->x4 = x4; - firstHalf->x3 = (firstHalf->x2 + c) * 0.5f; - secondHalf->x2 = (secondHalf->x3 + c) * 0.5f; - firstHalf->x4 = secondHalf->x1 = (firstHalf->x3 + secondHalf->x2) * 0.5f; - - c = (y2 + y3) / 2; - firstHalf->y2 = (y1 + y2) * 0.5f; - secondHalf->y3 = (y3 + y4) * 0.5f; - firstHalf->y1 = y1; - secondHalf->y4 = y4; - firstHalf->y3 = (firstHalf->y2 + c) * 0.5f; - secondHalf->y2 = (secondHalf->y3 + c) * 0.5f; - firstHalf->y4 = secondHalf->y1 = (firstHalf->y3 + secondHalf->y2) * 0.5f; -} - -V_END_NAMESPACE - -#endif // VBEZIER_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vbitmap.cpp b/libfenrir/src/main/jni/animation/rlottie/src/vector/vbitmap.cpp deleted file mode 100644 index fb881d780..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vbitmap.cpp +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "vbitmap.h" -#include -#include -#include "vdrawhelper.h" -#include "vglobal.h" - -V_BEGIN_NAMESPACE - -void VBitmap::Impl::reset(size_t width, size_t height, VBitmap::Format format) -{ - mRoData = nullptr; - mWidth = uint32_t(width); - mHeight = uint32_t(height); - mFormat = format; - - mDepth = depth(format); - mStride = ((mWidth * mDepth + 31) >> 5) - << 2; // bytes per scanline (must be multiple of 4) - mOwnData = std::make_unique(mStride * mHeight); -} - -void VBitmap::Impl::reset(uint8_t *data, size_t width, size_t height, - size_t bytesPerLine, VBitmap::Format format) -{ - mRoData = data; - mWidth = uint32_t(width); - mHeight = uint32_t(height); - mStride = uint32_t(bytesPerLine); - mFormat = format; - mDepth = depth(format); - mOwnData = nullptr; -} - -uint8_t VBitmap::Impl::depth(VBitmap::Format format) -{ - uint8_t depth = 1; - switch (format) { - case VBitmap::Format::Alpha8: - depth = 8; - break; - case VBitmap::Format::ARGB32: - case VBitmap::Format::ARGB32_Premultiplied: - depth = 32; - break; - default: - break; - } - return depth; -} - -void VBitmap::Impl::fill(uint32_t /*pixel*/) -{ - //@TODO -} - -void VBitmap::Impl::updateLuma() -{ - if (mFormat != VBitmap::Format::ARGB32_Premultiplied) return; - auto dataPtr = data(); - for (uint32_t col = 0; col < mHeight; col++) { - auto *pixel = (uint32_t *)(dataPtr + mStride * col); - for (uint32_t row = 0; row < mWidth; row++) { - int alpha = vAlpha(*pixel); - if (alpha == 0) { - pixel++; - continue; - } - - int red = vRed(*pixel); - int green = vGreen(*pixel); - int blue = vBlue(*pixel); - - if (alpha != 255) { - // un multiply - red = (red * 255) / alpha; - green = (green * 255) / alpha; - blue = (blue * 255) / alpha; - } - int luminosity = int(0.299f * red + 0.587f * green + 0.114f * blue); - *pixel = luminosity << 24; - pixel++; - } - } -} - -VBitmap::VBitmap(size_t width, size_t height, VBitmap::Format format) -{ - if (width <= 0 || height <= 0 || format == Format::Invalid) return; - - mImpl = rc_ptr(width, height, format); -} - -VBitmap::VBitmap(uint8_t *data, size_t width, size_t height, - size_t bytesPerLine, VBitmap::Format format) -{ - if (!data || width <= 0 || height <= 0 || bytesPerLine <= 0 || - format == Format::Invalid) - return; - - mImpl = rc_ptr(data, width, height, bytesPerLine, format); -} - -void VBitmap::reset(uint8_t *data, size_t w, size_t h, size_t bytesPerLine, - VBitmap::Format format) -{ - if (mImpl) { - mImpl->reset(data, w, h, bytesPerLine, format); - } else { - mImpl = rc_ptr(data, w, h, bytesPerLine, format); - } -} - -void VBitmap::reset(size_t w, size_t h, VBitmap::Format format) -{ - if (mImpl) { - if (w == mImpl->width() && h == mImpl->height() && - format == mImpl->format()) { - return; - } - mImpl->reset(w, h, format); - } else { - mImpl = rc_ptr(w, h, format); - } -} - -size_t VBitmap::stride() const -{ - return mImpl ? mImpl->stride() : 0; -} - -size_t VBitmap::width() const -{ - return mImpl ? mImpl->width() : 0; -} - -size_t VBitmap::height() const -{ - return mImpl ? mImpl->height() : 0; -} - -size_t VBitmap::depth() const -{ - return mImpl ? mImpl->mDepth : 0; -} - -uint8_t *VBitmap::data() -{ - return mImpl ? mImpl->data() : nullptr; -} - -uint8_t *VBitmap::data() const -{ - return mImpl ? mImpl->data() : nullptr; -} - -VRect VBitmap::rect() const -{ - return mImpl ? mImpl->rect() : VRect(); -} - -VSize VBitmap::size() const -{ - return mImpl ? mImpl->size() : VSize(); -} - -bool VBitmap::valid() const -{ - return mImpl; -} - -VBitmap::Format VBitmap::format() const -{ - return mImpl ? mImpl->format() : VBitmap::Format::Invalid; -} - -void VBitmap::fill(uint32_t pixel) -{ - if (mImpl) mImpl->fill(pixel); -} - -/* - * This is special function which converts - * RGB value to Luminosity and stores it in - * the Alpha component of the pixel. - * After this conversion the bitmap data is no more - * in RGB space. but the Alpha component contains the - * Luminosity value of the pixel in HSL color space. - * NOTE: this api has its own special usecase - * make sure you know what you are doing before using - * this api. - */ -void VBitmap::updateLuma() -{ - if (mImpl) mImpl->updateLuma(); -} - -V_END_NAMESPACE diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vbitmap.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/vbitmap.h deleted file mode 100644 index bc5059a7f..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vbitmap.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef VBITMAP_H -#define VBITMAP_H - -#include "vrect.h" -#include - -V_BEGIN_NAMESPACE - -class VBitmap { -public: - enum class Format : uint8_t { - Invalid, - Alpha8, - ARGB32, - ARGB32_Premultiplied - }; - - VBitmap() = default; - VBitmap(size_t w, size_t h, VBitmap::Format format); - VBitmap(uint8_t *data, size_t w, size_t h, size_t bytesPerLine, - VBitmap::Format format); - void reset(uint8_t *data, size_t w, size_t h, size_t stride, - VBitmap::Format format); - void reset(size_t w, size_t h, VBitmap::Format format=Format::ARGB32_Premultiplied); - size_t stride() const; - size_t width() const; - size_t height() const; - size_t depth() const; - VBitmap::Format format() const; - bool valid() const; - uint8_t * data(); - uint8_t * data() const; - VRect rect() const; - VSize size() const; - void fill(uint32_t pixel); - void updateLuma(); -private: - struct Impl { - std::unique_ptr mOwnData{nullptr}; - uint8_t * mRoData{nullptr}; - uint32_t mWidth{0}; - uint32_t mHeight{0}; - uint32_t mStride{0}; - uint8_t mDepth{0}; - VBitmap::Format mFormat{VBitmap::Format::Invalid}; - - explicit Impl(size_t width, size_t height, VBitmap::Format format) - { - reset(width, height, format); - } - explicit Impl(uint8_t *data, size_t w, size_t h, size_t bytesPerLine, - VBitmap::Format format) - { - reset(data, w, h, bytesPerLine, format); - } - VRect rect() const { return VRect(0, 0, mWidth, mHeight);} - VSize size() const { return VSize(mWidth, mHeight); } - size_t stride() const { return mStride; } - size_t width() const { return mWidth; } - size_t height() const { return mHeight; } - uint8_t * data() { return mRoData ? mRoData : mOwnData.get(); } - VBitmap::Format format() const { return mFormat; } - void reset(uint8_t *, size_t, size_t, size_t, VBitmap::Format); - void reset(size_t, size_t, VBitmap::Format); - static uint8_t depth(VBitmap::Format format); - void fill(uint32_t); - void updateLuma(); - }; - - rc_ptr mImpl; -}; - -V_END_NAMESPACE - -#endif // VBITMAP_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vbrush.cpp b/libfenrir/src/main/jni/animation/rlottie/src/vector/vbrush.cpp deleted file mode 100644 index f2ab3387c..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vbrush.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "vbrush.h" - -V_BEGIN_NAMESPACE - -VGradient::VGradient(VGradient::Type type) - : mType(type) -{ - if (mType == Type::Linear) - linear.x1 = linear.y1 = linear.x2 = linear.y2 = 0.0f; - else - radial.cx = radial.cy = radial.fx = - radial.fy = radial.cradius = radial.fradius = 0.0f; -} - -void VGradient::setStops(const VGradientStops &stops) -{ - mStops = stops; -} - -VBrush::VBrush(const VColor &color) : mType(VBrush::Type::Solid), mColor(color) -{ -} - -VBrush::VBrush(uint8_t r, uint8_t g, uint8_t b, uint8_t a) - : mType(VBrush::Type::Solid), mColor(r, g, b, a) - -{ -} - -VBrush::VBrush(const VGradient *gradient) -{ - if (!gradient) return; - - mGradient = gradient; - - if (gradient->mType == VGradient::Type::Linear) { - mType = VBrush::Type::LinearGradient; - } else if (gradient->mType == VGradient::Type::Radial) { - mType = VBrush::Type::RadialGradient; - } -} - -VBrush::VBrush(const VTexture *texture):mType(VBrush::Type::Texture), mTexture(texture) -{ -} - -V_END_NAMESPACE diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vbrush.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/vbrush.h deleted file mode 100644 index a1abbd34e..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vbrush.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef VBRUSH_H -#define VBRUSH_H - -#include -#include "vglobal.h" -#include "vmatrix.h" -#include "vpoint.h" -#include "vbitmap.h" - -V_BEGIN_NAMESPACE - -using VGradientStop = std::pair; -using VGradientStops = std::vector; -class VGradient { -public: - enum class Mode { Absolute, Relative }; - enum class Spread { Pad, Repeat, Reflect }; - enum class Type { Linear, Radial }; - explicit VGradient(VGradient::Type type); - void setStops(const VGradientStops &stops); - void setAlpha(float alpha) {mAlpha = alpha;} - float alpha() const {return mAlpha;} - -public: - static constexpr int colorTableSize = 1024; - VGradient::Type mType{Type::Linear}; - VGradient::Spread mSpread{Spread::Pad}; - VGradient::Mode mMode{Mode::Absolute}; - VGradientStops mStops; - float mAlpha{1.0}; - struct Linear{ - float x1{0}, y1{0}, x2{0}, y2{0}; - }; - struct Radial{ - float cx{0}, cy{0}, fx{0}, fy{0}, cradius{0}, fradius{0}; - }; - union { - Linear linear; - Radial radial; - }; - VMatrix mMatrix; -}; - -struct VTexture { - VBitmap mBitmap; - VMatrix mMatrix; - int mAlpha{255}; -}; - -class VBrush { -public: - enum class Type { NoBrush, Solid, LinearGradient, RadialGradient, Texture }; - VBrush():mType(Type::NoBrush),mColor(){}; - explicit VBrush(const VColor &color); - explicit VBrush(const VGradient *gradient); - explicit VBrush(uint8_t r, uint8_t g, uint8_t b, uint8_t a); - explicit VBrush(const VTexture *texture); - inline VBrush::Type type() const { return mType; } -public: - VBrush::Type mType{Type::NoBrush}; - union { - VColor mColor{}; - const VGradient *mGradient; - const VTexture *mTexture; - }; -}; - -V_END_NAMESPACE - -#endif // VBRUSH_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vcowptr.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/vcowptr.h deleted file mode 100644 index af2c7f248..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vcowptr.h +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef VCOWPTR_H -#define VCOWPTR_H - -#include -#include - -template -class vcow_ptr { - struct model { - std::atomic mRef{1}; - - model() = default; - - template - explicit model(Args&&... args) : mValue(std::forward(args)...){} - explicit model(const T& other) : mValue(other){} - - T mValue; - }; - model* mModel; - -public: - using element_type = T; - - vcow_ptr() - { - static model default_s; - mModel = &default_s; - ++mModel->mRef; - } - - ~vcow_ptr() - { - if (mModel && (--mModel->mRef == 0)) delete mModel; - } - - template - explicit vcow_ptr(Args&&... args) : mModel(new model(std::forward(args)...)) - { - } - - vcow_ptr(const vcow_ptr& x) noexcept : mModel(x.mModel) - { - assert(mModel); - ++mModel->mRef; - } - vcow_ptr(vcow_ptr&& x) noexcept : mModel(x.mModel) - { - assert(mModel); - x.mModel = nullptr; - } - - auto operator=(const vcow_ptr& x) noexcept -> vcow_ptr& - { - *this = vcow_ptr(x); - return *this; - } - - auto operator=(vcow_ptr&& x) noexcept -> vcow_ptr& - { - auto tmp = std::move(x); - swap(*this, tmp); - return *this; - } - - auto operator*() const noexcept -> const element_type& { return read(); } - - auto operator-> () const noexcept -> const element_type* { return &read(); } - - std::size_t refCount() const noexcept - { - assert(mModel); - - return mModel->mRef; - } - - bool unique() const noexcept - { - assert(mModel); - - return mModel->mRef == 1; - } - - auto write() -> element_type& - { - if (!unique()) *this = vcow_ptr(read()); - - return mModel->mValue; - } - - auto read() const noexcept -> const element_type& - { - assert(mModel); - - return mModel->mValue; - } - - friend inline void swap(vcow_ptr& x, vcow_ptr& y) noexcept - { - std::swap(x.mModel, y.mModel); - } -}; - -#endif // VCOWPTR_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vdasher.cpp b/libfenrir/src/main/jni/animation/rlottie/src/vector/vdasher.cpp deleted file mode 100644 index 759e30ac2..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vdasher.cpp +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#include "vbezier.h" - -#include - -#include "vdasher.h" -#include "vline.h" - -V_BEGIN_NAMESPACE - -static constexpr float tolerance = 0.1f; -VDasher::VDasher(const float *dashArray, size_t size) -{ - mDashArray = reinterpret_cast(dashArray); - mArraySize = size / 2; - if (size % 2) mDashOffset = dashArray[size - 1]; - mIndex = 0; - mCurrentLength = 0; - mDiscard = false; - //if the dash array contains ZERO length - // segments or ZERO lengths gaps we could - // optimize those usecase. - for (size_t i = 0; i < mArraySize; i++) { - if (!vCompare(mDashArray[i].length, 0.0f)) - mNoLength = false; - if (!vCompare(mDashArray[i].gap, 0.0f)) - mNoGap = false; - } -} - -void VDasher::moveTo(const VPointF &p) -{ - mDiscard = false; - mStartNewSegment = true; - mCurPt = p; - mIndex = 0; - - if (!vCompare(mDashOffset, 0.0f)) { - float totalLength = 0.0; - for (size_t i = 0; i < mArraySize; i++) { - totalLength = mDashArray[i].length + mDashArray[i].gap; - } - float normalizeLen = std::fmod(mDashOffset, totalLength); - if (normalizeLen < 0.0f) { - normalizeLen = totalLength + normalizeLen; - } - // now the length is less than total length and +ve - // findout the current dash index , dashlength and gap. - for (size_t i = 0; i < mArraySize; i++) { - if (normalizeLen < mDashArray[i].length) { - mIndex = i; - mCurrentLength = mDashArray[i].length - normalizeLen; - mDiscard = false; - break; - } - normalizeLen -= mDashArray[i].length; - if (normalizeLen < mDashArray[i].gap) { - mIndex = i; - mCurrentLength = mDashArray[i].gap - normalizeLen; - mDiscard = true; - break; - } - normalizeLen -= mDashArray[i].gap; - } - } else { - mCurrentLength = mDashArray[mIndex].length; - } - if (vIsZero(mCurrentLength)) updateActiveSegment(); -} - -void VDasher::addLine(const VPointF &p) -{ - if (mDiscard) return; - - if (mStartNewSegment) { - mResult->moveTo(mCurPt); - mStartNewSegment = false; - } - mResult->lineTo(p); -} - -void VDasher::updateActiveSegment() -{ - mStartNewSegment = true; - - if (mDiscard) { - mDiscard = false; - mIndex = (mIndex + 1) % mArraySize; - mCurrentLength = mDashArray[mIndex].length; - } else { - mDiscard = true; - mCurrentLength = mDashArray[mIndex].gap; - } - if (vIsZero(mCurrentLength)) updateActiveSegment(); -} - -void VDasher::lineTo(const VPointF &p) -{ - VLine left, right; - VLine line(mCurPt, p); - float length = line.length(); - - if (length <= mCurrentLength) { - mCurrentLength -= length; - addLine(p); - } else { - while (length > mCurrentLength) { - length -= mCurrentLength; - line.splitAtLength(mCurrentLength, left, right); - - addLine(left.p2()); - updateActiveSegment(); - - line = right; - mCurPt = line.p1(); - } - // handle remainder - if (length > tolerance) { - mCurrentLength -= length; - addLine(line.p2()); - } - } - - if (mCurrentLength < tolerance) updateActiveSegment(); - - mCurPt = p; -} - -void VDasher::addCubic(const VPointF &cp1, const VPointF &cp2, const VPointF &e) -{ - if (mDiscard) return; - - if (mStartNewSegment) { - mResult->moveTo(mCurPt); - mStartNewSegment = false; - } - mResult->cubicTo(cp1, cp2, e); -} - -void VDasher::cubicTo(const VPointF &cp1, const VPointF &cp2, const VPointF &e) -{ - VBezier left, right; - VBezier b = VBezier::fromPoints(mCurPt, cp1, cp2, e); - float bezLen = b.length(); - - if (bezLen <= mCurrentLength) { - mCurrentLength -= bezLen; - addCubic(cp1, cp2, e); - } else { - while (bezLen > mCurrentLength) { - bezLen -= mCurrentLength; - b.splitAtLength(mCurrentLength, &left, &right); - - addCubic(left.pt2(), left.pt3(), left.pt4()); - updateActiveSegment(); - - b = right; - mCurPt = b.pt1(); - } - // handle remainder - if (bezLen > tolerance) { - mCurrentLength -= bezLen; - addCubic(b.pt2(), b.pt3(), b.pt4()); - } - } - - if (mCurrentLength < tolerance) updateActiveSegment(); - - mCurPt = e; -} - -void VDasher::dashHelper(const VPath &path, VPath &result) -{ - mResult = &result; - mResult->reserve(path.points().size(), path.elements().size()); - mIndex = 0; - const std::vector &elms = path.elements(); - const std::vector & pts = path.points(); - const VPointF * ptPtr = pts.data(); - - for (auto &i : elms) { - switch (i) { - case VPath::Element::MoveTo: { - moveTo(*ptPtr++); - break; - } - case VPath::Element::LineTo: { - lineTo(*ptPtr++); - break; - } - case VPath::Element::CubicTo: { - cubicTo(*ptPtr, *(ptPtr + 1), *(ptPtr + 2)); - ptPtr += 3; - break; - } - case VPath::Element::Close: { - // The point is already joined to start point in VPath - // no need to do anything here. - break; - } - } - } - mResult = nullptr; -} - -void VDasher::dashed(const VPath &path, VPath &result) -{ - if (mNoLength && mNoGap) return result.reset(); - - if (path.empty() || mNoLength) return result.reset(); - - if (mNoGap) return result.clone(path); - - result.reset(); - - dashHelper(path, result); -} - -VPath VDasher::dashed(const VPath &path) -{ - if (mNoLength && mNoGap) return path; - - if (path.empty() || mNoLength) return VPath(); - - if (mNoGap) return path; - - VPath result; - - dashHelper(path, result); - - return result; -} - -V_END_NAMESPACE diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vdasher.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/vdasher.h deleted file mode 100644 index ec579d9fe..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vdasher.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef VDASHER_H -#define VDASHER_H -#include "vpath.h" - -V_BEGIN_NAMESPACE - -class VDasher { -public: - VDasher(const float *dashArray, size_t size); - VPath dashed(const VPath &path); - void dashed(const VPath &path, VPath &result); - -private: - void moveTo(const VPointF &p); - void lineTo(const VPointF &p); - void cubicTo(const VPointF &cp1, const VPointF &cp2, const VPointF &e); - void addLine(const VPointF &p); - void addCubic(const VPointF &cp1, const VPointF &cp2, const VPointF &e); - void updateActiveSegment(); - -private: - void dashHelper(const VPath &path, VPath &result); - struct Dash { - float length; - float gap; - }; - const VDasher::Dash *mDashArray; - size_t mArraySize{0}; - VPointF mCurPt; - size_t mIndex{0}; /* index to the dash Array */ - float mCurrentLength; - float mDashOffset{0}; - VPath *mResult{nullptr}; - bool mDiscard{false}; - bool mStartNewSegment{true}; - bool mNoLength{true}; - bool mNoGap{true}; -}; - -V_END_NAMESPACE - -#endif // VDASHER_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vdebug.cpp b/libfenrir/src/main/jni/animation/rlottie/src/vector/vdebug.cpp deleted file mode 100644 index 03196ef37..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vdebug.cpp +++ /dev/null @@ -1,758 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "vdebug.h" - -#ifdef LOTTIE_LOGGING_SUPPORT - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace { - -/* Returns microseconds since epoch */ -uint64_t timestamp_now() -{ - return std::chrono::duration_cast( - std::chrono::high_resolution_clock::now().time_since_epoch()) - .count(); -} - -/* I want [2016-10-13 00:01:23.528514] */ -void format_timestamp(std::ostream& os, uint64_t timestamp) -{ - // The next 3 lines do not work on MSVC! - // auto duration = std::chrono::microseconds(timestamp); - // std::chrono::high_resolution_clock::time_point time_point(duration); - // std::time_t time_t = - // std::chrono::high_resolution_clock::to_time_t(time_point); - std::time_t time_t = timestamp / 1000000; - auto gmtime = std::gmtime(&time_t); - char buffer[32]; - strftime(buffer, 32, "%Y-%m-%d %T.", gmtime); - char microseconds[7]; - snprintf(microseconds, 7, "%06llu", - (long long unsigned int)timestamp % 1000000); - os << '[' << buffer << microseconds << ']'; -} - -std::thread::id this_thread_id() -{ - static thread_local const std::thread::id id = std::this_thread::get_id(); - return id; -} - -template -struct TupleIndex; - -template -struct TupleIndex > { - static constexpr const std::size_t value = 0; -}; - -template -struct TupleIndex > { - static constexpr const std::size_t value = - 1 + TupleIndex >::value; -}; - -} // anonymous namespace - -typedef std::tuple - SupportedTypes; - -char const* to_string(LogLevel loglevel) -{ - switch (loglevel) { - case LogLevel::OFF: - return "OFF"; - case LogLevel::INFO: - return "INFO"; - case LogLevel::WARN: - return "WARN"; - case LogLevel::CRIT: - return "CRIT"; - } - return "XXXX"; -} - -template -void VDebug::encode(Arg arg) -{ - *reinterpret_cast(buffer()) = arg; - m_bytes_used += sizeof(Arg); -} - -template -void VDebug::encode(Arg arg, uint8_t type_id) -{ - resize_buffer_if_needed(sizeof(Arg) + sizeof(uint8_t)); - encode(type_id); - encode(arg); -} - -VDebug::VDebug(LogLevel level, char const* file, char const* function, - uint32_t line) - : m_bytes_used(0), m_buffer_size(sizeof(m_stack_buffer)) -{ - encode(timestamp_now()); - encode(this_thread_id()); - encode(string_literal_t(file)); - encode(string_literal_t(function)); - encode(line); - encode(level); - if (level == LogLevel::INFO) { - m_logAll = false; - } else { - m_logAll = true; - } -} - -VDebug::~VDebug() = default; - -void VDebug::stringify(std::ostream& os) -{ - char* b = !m_heap_buffer ? m_stack_buffer : m_heap_buffer.get(); - char const* const end = b + m_bytes_used; - uint64_t timestamp = *reinterpret_cast(b); - b += sizeof(uint64_t); - std::thread::id threadid = *reinterpret_cast(b); - b += sizeof(std::thread::id); - string_literal_t file = *reinterpret_cast(b); - b += sizeof(string_literal_t); - string_literal_t function = *reinterpret_cast(b); - b += sizeof(string_literal_t); - uint32_t line = *reinterpret_cast(b); - b += sizeof(uint32_t); - LogLevel loglevel = *reinterpret_cast(b); - b += sizeof(LogLevel); - if (m_logAll) { - format_timestamp(os, timestamp); - - os << '[' << to_string(loglevel) << ']' << '[' << threadid << ']' << '[' - << file.m_s << ':' << function.m_s << ':' << line << "] "; - } - - stringify(os, b, end); - os << std::endl; - - if (loglevel >= LogLevel::CRIT) os.flush(); -} - -template -char* decode(std::ostream& os, char* b, Arg* /*dummy*/) -{ - Arg arg = *reinterpret_cast(b); - os << arg; - return b + sizeof(Arg); -} - -template <> -char* decode(std::ostream& os, char* b, VDebug::string_literal_t* /*dummy*/) -{ - VDebug::string_literal_t s = - *reinterpret_cast(b); - os << s.m_s; - return b + sizeof(VDebug::string_literal_t); -} - -template <> -char* decode(std::ostream& os, char* b, char** /*dummy*/) -{ - while (*b != '\0') { - os << *b; - ++b; - } - return ++b; -} - -void VDebug::stringify(std::ostream& os, char* start, char const* const end) -{ - if (start == end) return; - - int type_id = static_cast(*start); - start++; - - switch (type_id) { - case 0: - stringify( - os, - decode(os, start, - static_cast::type*>( - nullptr)), - end); - return; - case 1: - stringify( - os, - decode(os, start, - static_cast::type*>( - nullptr)), - end); - return; - case 2: - stringify( - os, - decode(os, start, - static_cast::type*>( - nullptr)), - end); - return; - case 3: - stringify( - os, - decode(os, start, - static_cast::type*>( - nullptr)), - end); - return; - case 4: - stringify( - os, - decode(os, start, - static_cast::type*>( - nullptr)), - end); - return; - case 5: - stringify( - os, - decode(os, start, - static_cast::type*>( - nullptr)), - end); - return; - case 6: - stringify( - os, - decode(os, start, - static_cast::type*>( - nullptr)), - end); - return; - case 7: - stringify( - os, - decode(os, start, - static_cast::type*>( - nullptr)), - end); - return; - } -} - -char* VDebug::buffer() -{ - return !m_heap_buffer ? &m_stack_buffer[m_bytes_used] - : &(m_heap_buffer.get())[m_bytes_used]; -} - -void VDebug::resize_buffer_if_needed(size_t additional_bytes) -{ - size_t const required_size = m_bytes_used + additional_bytes; - - if (required_size <= m_buffer_size) return; - - if (!m_heap_buffer) { - m_buffer_size = std::max(static_cast(512), required_size); - m_heap_buffer = std::make_unique(m_buffer_size); - memcpy(m_heap_buffer.get(), m_stack_buffer, m_bytes_used); - return; - } else { - m_buffer_size = - std::max(static_cast(2 * m_buffer_size), required_size); - std::unique_ptr new_heap_buffer(new char[m_buffer_size]); - memcpy(new_heap_buffer.get(), m_heap_buffer.get(), m_bytes_used); - m_heap_buffer.swap(new_heap_buffer); - } -} - -void VDebug::encode(char const* arg) -{ - if (arg != nullptr) encode_c_string(arg, strlen(arg)); -} - -void VDebug::encode(char* arg) -{ - if (arg != nullptr) encode_c_string(arg, strlen(arg)); -} - -void VDebug::encode_c_string(char const* arg, size_t length) -{ - if (length == 0) return; - - resize_buffer_if_needed(1 + length + 1); - char* b = buffer(); - auto type_id = TupleIndex::value; - *reinterpret_cast(b++) = static_cast(type_id); - memcpy(b, arg, length + 1); - m_bytes_used += 1 + length + 1; -} - -void VDebug::encode(string_literal_t arg) -{ - encode( - arg, TupleIndex::value); -} - -VDebug& VDebug::operator<<(std::string const& arg) -{ - encode_c_string(arg.c_str(), arg.length()); - return *this; -} - -VDebug& VDebug::operator<<(int32_t arg) -{ - encode(arg, TupleIndex::value); - return *this; -} - -VDebug& VDebug::operator<<(uint32_t arg) -{ - encode(arg, TupleIndex::value); - return *this; -} - -// VDebug& VDebug::operator<<(int64_t arg) -// { -// encode < int64_t >(arg, TupleIndex < int64_t, SupportedTypes >::value); -// return *this; -// } - -// VDebug& VDebug::operator<<(uint64_t arg) -// { -// encode < uint64_t >(arg, TupleIndex < uint64_t, SupportedTypes >::value); -// return *this; -// } -VDebug& VDebug::operator<<(unsigned long arg) -{ - encode(arg, TupleIndex::value); - return *this; -} - -VDebug& VDebug::operator<<(long arg) -{ - encode(arg, TupleIndex::value); - return *this; -} - -VDebug& VDebug::operator<<(double arg) -{ - encode(arg, TupleIndex::value); - return *this; -} - -VDebug& VDebug::operator<<(char arg) -{ - encode(arg, TupleIndex::value); - return *this; -} - -struct BufferBase { - virtual ~BufferBase() = default; - virtual void push(VDebug&& logline) = 0; - virtual bool try_pop(VDebug& logline) = 0; -}; - -struct SpinLock { - SpinLock(std::atomic_flag& flag) : m_flag(flag) - { - while (m_flag.test_and_set(std::memory_order_acquire)) - ; - } - - ~SpinLock() { m_flag.clear(std::memory_order_release); } - -private: - std::atomic_flag& m_flag; -}; - -/* Multi Producer Single Consumer Ring Buffer */ -class RingBuffer : public BufferBase { -public: - struct alignas(64) Item { - Item() - : flag(), written(0), logline(LogLevel::INFO, nullptr, nullptr, 0) - { - } - - std::atomic_flag flag; - char written; - char padding[256 - sizeof(std::atomic_flag) - sizeof(char) - - sizeof(VDebug)]; - VDebug logline; - }; - - RingBuffer(size_t const size) - : m_size(size), - m_ring(static_cast(std::malloc(size * sizeof(Item)))), - m_write_index(0), - m_read_index(0) - { - for (size_t i = 0; i < m_size; ++i) { - new (&m_ring[i]) Item(); - } - static_assert(sizeof(Item) == 256, "Unexpected size != 256"); - } - - ~RingBuffer() override - { - for (size_t i = 0; i < m_size; ++i) { - m_ring[i].~Item(); - } - std::free(m_ring); - } - - void push(VDebug&& logline) override - { - unsigned int write_index = - m_write_index.fetch_add(1, std::memory_order_relaxed) % m_size; - Item& item = m_ring[write_index]; - SpinLock spinlock(item.flag); - item.logline = std::move(logline); - item.written = 1; - } - - bool try_pop(VDebug& logline) override - { - Item& item = m_ring[m_read_index % m_size]; - SpinLock spinlock(item.flag); - if (item.written == 1) { - logline = std::move(item.logline); - item.written = 0; - ++m_read_index; - return true; - } - return false; - } - - RingBuffer(RingBuffer const&) = delete; - RingBuffer& operator=(RingBuffer const&) = delete; - -private: - size_t const m_size; - Item* m_ring; - std::atomic m_write_index; - -public: - char pad[64]; - -private: - unsigned int m_read_index; -}; - -class Buffer { -public: - struct Item { - Item(VDebug&& logline) : logline(std::move(logline)) {} - char padding[256 - sizeof(VDebug)]; - VDebug logline; - }; - - static constexpr const size_t size = - 32768; // 8MB. Helps reduce memory fragmentation - - Buffer() : m_buffer(static_cast(std::malloc(size * sizeof(Item)))) - { - for (size_t i = 0; i <= size; ++i) { - m_write_state[i].store(0, std::memory_order_relaxed); - } - static_assert(sizeof(Item) == 256, "Unexpected size != 256"); - } - - ~Buffer() - { - unsigned int write_count = m_write_state[size].load(); - for (size_t i = 0; i < write_count; ++i) { - m_buffer[i].~Item(); - } - std::free(m_buffer); - } - - // Returns true if we need to switch to next buffer - bool push(VDebug&& logline, unsigned int const write_index) - { - new (&m_buffer[write_index]) Item(std::move(logline)); - m_write_state[write_index].store(1, std::memory_order_release); - return m_write_state[size].fetch_add(1, std::memory_order_acquire) + - 1 == - size; - } - - bool try_pop(VDebug& logline, unsigned int const read_index) - { - if (m_write_state[read_index].load(std::memory_order_acquire)) { - Item& item = m_buffer[read_index]; - logline = std::move(item.logline); - return true; - } - return false; - } - - Buffer(Buffer const&) = delete; - Buffer& operator=(Buffer const&) = delete; - -private: - Item* m_buffer; - std::atomic m_write_state[size + 1]; -}; - -class QueueBuffer : public BufferBase { -public: - QueueBuffer(QueueBuffer const&) = delete; - QueueBuffer& operator=(QueueBuffer const&) = delete; - - QueueBuffer() - : m_current_read_buffer{nullptr}, - m_write_index(0), - m_flag(), - m_read_index(0) - { - setup_next_write_buffer(); - } - - void push(VDebug&& logline) override - { - unsigned int write_index = - m_write_index.fetch_add(1, std::memory_order_relaxed); - if (write_index < Buffer::size) { - if (m_current_write_buffer.load(std::memory_order_acquire) - ->push(std::move(logline), write_index)) { - setup_next_write_buffer(); - } - } else { - while (m_write_index.load(std::memory_order_acquire) >= - Buffer::size) - ; - push(std::move(logline)); - } - } - - bool try_pop(VDebug& logline) override - { - if (m_current_read_buffer == nullptr) - m_current_read_buffer = get_next_read_buffer(); - - Buffer* read_buffer = m_current_read_buffer; - - if (read_buffer == nullptr) return false; - - if (read_buffer->try_pop(logline, m_read_index)) { - m_read_index++; - if (m_read_index == Buffer::size) { - m_read_index = 0; - m_current_read_buffer = nullptr; - SpinLock spinlock(m_flag); - m_buffers.pop(); - } - return true; - } - - return false; - } - -private: - void setup_next_write_buffer() - { - std::unique_ptr next_write_buffer(new Buffer()); - m_current_write_buffer.store(next_write_buffer.get(), - std::memory_order_release); - SpinLock spinlock(m_flag); - m_buffers.push(std::move(next_write_buffer)); - m_write_index.store(0, std::memory_order_relaxed); - } - - Buffer* get_next_read_buffer() - { - SpinLock spinlock(m_flag); - return m_buffers.empty() ? nullptr : m_buffers.front().get(); - } - -private: - std::queue > m_buffers; - std::atomic m_current_write_buffer; - Buffer* m_current_read_buffer; - std::atomic m_write_index; - std::atomic_flag m_flag; - unsigned int m_read_index; -}; - -class FileWriter { -public: - FileWriter(std::string const& log_directory, - std::string const& log_file_name, uint32_t log_file_roll_size_mb) - : m_log_file_roll_size_bytes(log_file_roll_size_mb * 1024 * 1024), - m_name(log_directory + log_file_name) - { - roll_file(); - } - - void write(VDebug& logline) - { - auto pos = m_os->tellp(); - logline.stringify(*m_os); - m_bytes_written += m_os->tellp() - pos; - if (m_bytes_written > m_log_file_roll_size_bytes) { - roll_file(); - } - } - -private: - void roll_file() - { - if (m_os) { - m_os->flush(); - m_os->close(); - } - - m_bytes_written = 0; - m_os = std::make_unique(); - // TODO Optimize this part. Does it even matter ? - std::string log_file_name = m_name; - log_file_name.append("."); - log_file_name.append(std::to_string(++m_file_number)); - log_file_name.append(".txt"); - m_os->open(log_file_name, std::ofstream::out | std::ofstream::trunc); - } - -private: - uint32_t m_file_number = 0; - std::streamoff m_bytes_written = 0; - uint32_t const m_log_file_roll_size_bytes; - std::string const m_name; - std::unique_ptr m_os; -}; - -class NanoLogger { -public: - NanoLogger(NonGuaranteedLogger ngl, std::string const& log_directory, - std::string const& log_file_name, uint32_t log_file_roll_size_mb) - : m_state(State::INIT), - m_buffer_base( - new RingBuffer(std::max(1u, ngl.ring_buffer_size_mb) * 1024 * 4)), - m_file_writer(log_directory, log_file_name, - std::max(1u, log_file_roll_size_mb)), - m_thread(&NanoLogger::pop, this) - { - m_state.store(State::READY, std::memory_order_release); - } - - NanoLogger(GuaranteedLogger /*gl*/, std::string const& log_directory, - std::string const& log_file_name, uint32_t log_file_roll_size_mb) - : m_state(State::INIT), - m_buffer_base(new QueueBuffer()), - m_file_writer(log_directory, log_file_name, - std::max(1u, log_file_roll_size_mb)), - m_thread(&NanoLogger::pop, this) - { - m_state.store(State::READY, std::memory_order_release); - } - - ~NanoLogger() - { - m_state.store(State::SHUTDOWN); - m_thread.join(); - } - - void add(VDebug&& logline) { m_buffer_base->push(std::move(logline)); } - - void pop() - { - // Wait for constructor to complete and pull all stores done there to - // this thread / core. - while (m_state.load(std::memory_order_acquire) == State::INIT) - std::this_thread::sleep_for(std::chrono::microseconds(50)); - - VDebug logline(LogLevel::INFO, nullptr, nullptr, 0); - - while (m_state.load() == State::READY) { - if (m_buffer_base->try_pop(logline)) - m_file_writer.write(logline); - else - std::this_thread::sleep_for(std::chrono::microseconds(50)); - } - - // Pop and log all remaining entries - while (m_buffer_base->try_pop(logline)) { - m_file_writer.write(logline); - } - } - -private: - enum class State { INIT, READY, SHUTDOWN }; - - std::atomic m_state; - std::unique_ptr m_buffer_base; - FileWriter m_file_writer; - std::thread m_thread; -}; - -std::unique_ptr nanologger; -std::atomic atomic_nanologger; - -bool VDebugServer::operator==(VDebug& logline) -{ - atomic_nanologger.load(std::memory_order_acquire)->add(std::move(logline)); - return true; -} - -void initialize(NonGuaranteedLogger ngl, std::string const& log_directory, - std::string const& log_file_name, - uint32_t log_file_roll_size_mb) -{ - nanologger = std::make_unique(ngl, log_directory, log_file_name, - log_file_roll_size_mb); - atomic_nanologger.store(nanologger.get(), std::memory_order_seq_cst); -} - -void initialize(GuaranteedLogger gl, std::string const& log_directory, - std::string const& log_file_name, - uint32_t log_file_roll_size_mb) -{ - nanologger = std::make_unique(gl, log_directory, log_file_name, - log_file_roll_size_mb); - atomic_nanologger.store(nanologger.get(), std::memory_order_seq_cst); -} - -std::atomic loglevel = {0}; - -void set_log_level(LogLevel level) -{ - loglevel.store(static_cast(level), std::memory_order_release); -} - -bool is_logged(LogLevel level) -{ - return static_cast(level) >= - loglevel.load(std::memory_order_relaxed); -} - -#endif // LOTTIE_LOGGING_SUPPORT diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vdebug.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/vdebug.h deleted file mode 100644 index 5b6bef5b1..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vdebug.h +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef VDEBUG_H -#define VDEBUG_H - -#include "config.h" - -#ifdef LOTTIE_LOGGING_SUPPORT - -#include -#include -#include -#include -#include - -enum class LogLevel : uint8_t { INFO, WARN, CRIT, OFF }; - -class VDebug { -public: - VDebug(); - VDebug& debug() { return *this; } - VDebug(LogLevel level, char const* file, char const* function, - uint32_t line); - ~VDebug(); - - VDebug(VDebug&&) = default; - VDebug& operator=(VDebug&&) = default; - - void stringify(std::ostream& os); - - VDebug& operator<<(char arg); - VDebug& operator<<(int32_t arg); - VDebug& operator<<(uint32_t arg); - // VDebug& operator<<(int64_t arg); - // VDebug& operator<<(uint64_t arg); - - VDebug& operator<<(long arg); - VDebug& operator<<(unsigned long arg); - VDebug& operator<<(double arg); - VDebug& operator<<(std::string const& arg); - - template - VDebug& operator<<(const char (&arg)[N]) - { - encode(string_literal_t(arg)); - return *this; - } - - template - typename std::enable_if::value, - VDebug&>::type - operator<<(Arg const& arg) - { - encode(arg); - return *this; - } - - template - typename std::enable_if::value, VDebug&>::type - operator<<(Arg const& arg) - { - encode(arg); - return *this; - } - - struct string_literal_t { - explicit string_literal_t(char const* s) : m_s(s) {} - char const* m_s; - }; - -private: - char* buffer(); - - template - void encode(Arg arg); - - template - void encode(Arg arg, uint8_t type_id); - - void encode(char* arg); - void encode(char const* arg); - void encode(string_literal_t arg); - void encode_c_string(char const* arg, size_t length); - void resize_buffer_if_needed(size_t additional_bytes); - void stringify(std::ostream& os, char* start, char const* const end); - -private: - size_t m_bytes_used{0}; - size_t m_buffer_size{0}; - std::unique_ptr m_heap_buffer; - bool m_logAll; - char m_stack_buffer[256 - sizeof(bool) - 2 * sizeof(size_t) - - sizeof(decltype(m_heap_buffer)) - 8 /* Reserved */]; -}; - -struct VDebugServer { - /* - * Ideally this should have been operator+= - * Could not get that to compile, so here we are... - */ - bool operator==(VDebug&); -}; - -void set_log_level(LogLevel level); - -bool is_logged(LogLevel level); - -/* - * Non guaranteed logging. Uses a ring buffer to hold log lines. - * When the ring gets full, the previous log line in the slot will be dropped. - * Does not block producer even if the ring buffer is full. - * ring_buffer_size_mb - LogLines are pushed into a mpsc ring buffer whose size - * is determined by this parameter. Since each LogLine is 256 bytes, - * ring_buffer_size = ring_buffer_size_mb * 1024 * 1024 / 256 - */ -struct NonGuaranteedLogger { - NonGuaranteedLogger(uint32_t ring_buffer_size_mb_) - : ring_buffer_size_mb(ring_buffer_size_mb_) - { - } - uint32_t ring_buffer_size_mb; -}; - -/* - * Provides a guarantee log lines will not be dropped. - */ -struct GuaranteedLogger { -}; - -/* - * Ensure initialize() is called prior to any log statements. - * log_directory - where to create the logs. For example - "/tmp/" - * log_file_name - root of the file name. For example - "nanolog" - * This will create log files of the form - - * /tmp/nanolog.1.txt - * /tmp/nanolog.2.txt - * etc. - * log_file_roll_size_mb - mega bytes after which we roll to next log file. - */ -void initialize(GuaranteedLogger gl, std::string const& log_directory, - std::string const& log_file_name, - uint32_t log_file_roll_size_mb); -void initialize(NonGuaranteedLogger ngl, std::string const& log_directory, - std::string const& log_file_name, - uint32_t log_file_roll_size_mb); - -#define VDEBUG_LOG(LEVEL) \ - VDebugServer() == VDebug(LEVEL, __FILE__, __func__, __LINE__).debug() -#define vDebug is_logged(LogLevel::INFO) && VDEBUG_LOG(LogLevel::INFO) -#define vWarning is_logged(LogLevel::WARN) && VDEBUG_LOG(LogLevel::WARN) -#define vCritical is_logged(LogLevel::CRIT) && VDEBUG_LOG(LogLevel::CRIT) - -#else - -struct VDebug -{ - template - VDebug& operator<<(const Args &){return *this;} -}; - -#define vDebug VDebug() -#define vWarning VDebug() -#define vCritical VDebug() - -#endif //LOTTIE_LOGGING_SUPPORT - -#endif // VDEBUG_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawable.cpp b/libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawable.cpp deleted file mode 100644 index d5731456a..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawable.cpp +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "vdrawable.h" -#include "vdasher.h" -#include "vraster.h" - -VDrawable::VDrawable(VDrawable::Type type) -{ - setType(type); -} - -VDrawable::~VDrawable() noexcept -{ - if (mStrokeInfo) { - if (mType == Type::StrokeWithDash) { - delete static_cast(mStrokeInfo); - } else { - delete mStrokeInfo; - } - } -} - -void VDrawable::setType(VDrawable::Type type) -{ - mType = type; - if (mType == VDrawable::Type::Stroke) { - mStrokeInfo = new StrokeInfo(); - } else if (mType == VDrawable::Type::StrokeWithDash) { - mStrokeInfo = new StrokeWithDashInfo(); - } -} - -void VDrawable::applyDashOp() -{ - if (mStrokeInfo && (mType == Type::StrokeWithDash)) { - auto obj = static_cast(mStrokeInfo); - if (!obj->mDash.empty()) { - VDasher dasher(obj->mDash.data(), obj->mDash.size()); - mPath.clone(dasher.dashed(mPath)); - } - } -} - -void VDrawable::preprocess(const VRect &clip) -{ - if (mFlag & (DirtyState::Path)) { - if (mType == Type::Fill) { - mRasterizer.rasterize(std::move(mPath), mFillRule, clip); - } else { - applyDashOp(); - mRasterizer.rasterize(std::move(mPath), mStrokeInfo->cap, mStrokeInfo->join, - mStrokeInfo->width, mStrokeInfo->miterLimit, clip); - } - mPath = {}; - mFlag &= ~DirtyFlag(DirtyState::Path); - } -} - -VRle VDrawable::rle() -{ - return mRasterizer.rle(); -} - -void VDrawable::setStrokeInfo(CapStyle cap, JoinStyle join, float miterLimit, - float strokeWidth) -{ - assert(mStrokeInfo); - if ((mStrokeInfo->cap == cap) && (mStrokeInfo->join == join) && - vCompare(mStrokeInfo->miterLimit, miterLimit) && - vCompare(mStrokeInfo->width, strokeWidth)) - return; - - mStrokeInfo->cap = cap; - mStrokeInfo->join = join; - mStrokeInfo->miterLimit = miterLimit; - mStrokeInfo->width = strokeWidth; - mFlag |= DirtyState::Path; -} - -void VDrawable::setDashInfo(std::vector &dashInfo) -{ - assert(mStrokeInfo); - assert(mType == VDrawable::Type::StrokeWithDash); - - auto obj = static_cast(mStrokeInfo); - bool hasChanged = false; - - if (obj->mDash.size() == dashInfo.size()) { - for (uint32_t i = 0; i < dashInfo.size(); ++i) { - if (!vCompare(obj->mDash[i], dashInfo[i])) { - hasChanged = true; - break; - } - } - } else { - hasChanged = true; - } - - if (!hasChanged) return; - - obj->mDash = dashInfo; - if (obj->mDash.size() == 1) { - obj->mDash.push_back(20); - } - - mFlag |= DirtyState::Path; -} - -void VDrawable::setPath(const VPath &path) -{ - mPath = path; - mFlag |= DirtyState::Path; -} diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawable.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawable.h deleted file mode 100644 index 357a69fdb..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawable.h +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef VDRAWABLE_H -#define VDRAWABLE_H -#include -#include -#include "vbrush.h" -#include "vpath.h" -#include "vrle.h" -#include "vraster.h" - -class VDrawable { -public: - enum class DirtyState : unsigned char { - None = 1<<0, - Path = 1<<1, - Stroke = 1<<2, - Brush = 1<<3, - All = (Path | Stroke | Brush) - }; - - enum class Type : unsigned char{ - Fill, - Stroke, - StrokeWithDash - }; - - explicit VDrawable(VDrawable::Type type = Type::Fill); - void setType(VDrawable::Type type); - ~VDrawable() noexcept; - - typedef vFlag DirtyFlag; - void setPath(const VPath &path); - void setFillRule(FillRule rule) { mFillRule = rule; } - void setBrush(const VBrush &brush) { mBrush = brush; } - void setStrokeInfo(CapStyle cap, JoinStyle join, float miterLimit, - float strokeWidth); - void setDashInfo(std::vector &dashInfo); - void preprocess(const VRect &clip); - void applyDashOp(); - VRle rle(); - void setName(const char *name) - { - mName = name; - } - const char* name() const { return mName; } - -public: - struct StrokeInfo { - float width{0.0}; - float miterLimit{10}; - CapStyle cap{CapStyle::Flat}; - JoinStyle join{JoinStyle::Bevel}; - }; - - struct StrokeWithDashInfo : public StrokeInfo{ - std::vector mDash; - }; - -public: - VPath mPath; - VBrush mBrush; - VRasterizer mRasterizer; - StrokeInfo *mStrokeInfo{nullptr}; - - DirtyFlag mFlag{DirtyState::All}; - FillRule mFillRule{FillRule::Winding}; - VDrawable::Type mType{Type::Fill}; - - const char *mName{nullptr}; -}; - -#endif // VDRAWABLE_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawhelper.cpp b/libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawhelper.cpp deleted file mode 100644 index cbfd298e8..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawhelper.cpp +++ /dev/null @@ -1,769 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "vdrawhelper.h" -#include -#include -#include -#include -#include -#include - -static RenderFuncTable RenderTable; - -void VTextureData::setClip(const VRect &clip) -{ - left = clip.left(); - top = clip.top(); - right = std::min(clip.right(), int(width())) - 1; - bottom = std::min(clip.bottom(), int(height())) - 1; -} - -class VGradientCache { -public: - struct CacheInfo : public VColorTable { - inline explicit CacheInfo(VGradientStops s) : stops(std::move(s)) {} - VGradientStops stops; - }; - using VCacheData = std::shared_ptr; - using VCacheKey = int64_t; - using VGradientColorTableHash = - std::unordered_multimap; - - static bool generateGradientColorTable(const VGradientStops &stops, float alpha, - uint32_t *colorTable, int size); - VCacheData getBuffer(const VGradient &gradient) - { - VCacheKey hash_val = 0; - VCacheData info; - const VGradientStops &stops = gradient.mStops; - for (uint32_t i = 0; i < stops.size() && i <= 2; i++) - hash_val += - VCacheKey(stops[i].second.premulARGB() * gradient.alpha()); - - { - std::lock_guard guard(mMutex); - - size_t count = mCache.count(hash_val); - if (!count) { - // key is not present in the hash - info = addCacheElement(hash_val, gradient); - } else if (count == 1) { - auto search = mCache.find(hash_val); - if (search->second->stops == stops) { - info = search->second; - } else { - // didn't find an exact match - info = addCacheElement(hash_val, gradient); - } - } else { - // we have a multiple data with same key - auto range = mCache.equal_range(hash_val); - for (auto it = range.first; it != range.second; ++it) { - if (it->second->stops == stops) { - info = it->second; - break; - } - } - if (!info) { - // didn't find an exact match - info = addCacheElement(hash_val, gradient); - } - } - } - return info; - } - - static VGradientCache &instance() - { - static VGradientCache CACHE; - return CACHE; - } - -protected: - uint32_t maxCacheSize() const { return 60; } - VCacheData addCacheElement(VCacheKey hash_val, const VGradient &gradient) - { - if (mCache.size() == maxCacheSize()) { - uint32_t count = maxCacheSize() / 10; - while (count--) { - mCache.erase(mCache.begin()); - } - } - auto cache_entry = std::make_shared(gradient.mStops); - cache_entry->alpha = generateGradientColorTable( - gradient.mStops, gradient.alpha(), cache_entry->buffer32, - VGradient::colorTableSize); - mCache.insert(std::make_pair(hash_val, cache_entry)); - return cache_entry; - } - -private: - VGradientCache() = default; - - VGradientColorTableHash mCache; - std::mutex mMutex; -}; - -bool VGradientCache::generateGradientColorTable(const VGradientStops &stops, - float opacity, - uint32_t *colorTable, int size) -{ - if (stops.empty()) { - return false; - } - int dist, idist, pos = 0; - size_t i; - bool alpha = false; - size_t stopCount = stops.size(); - const VGradientStop *curr, *next, *start; - uint32_t curColor, nextColor; - float delta, t, incr, fpos; - - if (!vCompare(opacity, 1.0f)) alpha = true; - - start = stops.data(); - curr = start; - if (!curr->second.isOpaque()) alpha = true; - curColor = curr->second.premulARGB(opacity); - incr = 1.0f / (float)size; - fpos = 1.5f * incr; - - colorTable[pos++] = curColor; - - while (fpos <= curr->first && pos < size) { - colorTable[pos] = colorTable[pos - 1]; - pos++; - fpos += incr; - } - - for (i = 0; i < stopCount - 1; ++i) { - curr = (start + i); - next = (start + i + 1); - delta = 1 / (next->first - curr->first); - if (!next->second.isOpaque()) alpha = true; - nextColor = next->second.premulARGB(opacity); - while (fpos < next->first && pos < size) { - t = (fpos - curr->first) * delta; - dist = (int)(255 * t); - idist = 255 - dist; - colorTable[pos] = - interpolate_pixel(curColor, idist, nextColor, dist); - ++pos; - fpos += incr; - } - curColor = nextColor; - } - - for (; pos < size; ++pos) colorTable[pos] = curColor; - - // Make sure the last color stop is represented at the end of the table - colorTable[size - 1] = curColor; - return alpha; -} - -void VRasterBuffer::clear() -{ - memset(mBuffer, 0, mHeight * mBytesPerLine); -} - -VBitmap::Format VRasterBuffer::prepare(const VBitmap *image) -{ - mBuffer = image->data(); - mWidth = image->width(); - mHeight = image->height(); - mBytesPerPixel = 4; - mBytesPerLine = image->stride(); - - mFormat = image->format(); - return mFormat; -} - -void VSpanData::init(VRasterBuffer *image) -{ - mRasterBuffer = image; - setDrawRegion(VRect(0, 0, int(image->width()), int(image->height()))); - mType = VSpanData::Type::None; - mBlendFunc = nullptr; - mUnclippedBlendFunc = nullptr; -} - -/* - * Gradient Draw routines - * - */ - -#define FIXPT_BITS 8 -#define FIXPT_SIZE (1 << FIXPT_BITS) -static inline void getLinearGradientValues(LinearGradientValues *v, - const VSpanData * data) -{ - const VGradientData *grad = &data->mGradient; - v->dx = grad->linear.x2 - grad->linear.x1; - v->dy = grad->linear.y2 - grad->linear.y1; - v->l = v->dx * v->dx + v->dy * v->dy; - v->off = 0; - if (v->l != 0) { - v->dx /= v->l; - v->dy /= v->l; - v->off = -v->dx * grad->linear.x1 - v->dy * grad->linear.y1; - } -} - -static inline void getRadialGradientValues(RadialGradientValues *v, - const VSpanData * data) -{ - const VGradientData &gradient = data->mGradient; - v->dx = gradient.radial.cx - gradient.radial.fx; - v->dy = gradient.radial.cy - gradient.radial.fy; - - v->dr = gradient.radial.cradius - gradient.radial.fradius; - v->sqrfr = gradient.radial.fradius * gradient.radial.fradius; - - v->a = v->dr * v->dr - v->dx * v->dx - v->dy * v->dy; - v->inv2a = 1 / (2 * v->a); - - v->extended = !vIsZero(gradient.radial.fradius) || v->a <= 0; -} - -static inline int gradientClamp(const VGradientData *grad, int ipos) -{ - int limit; - - if (grad->mSpread == VGradient::Spread::Repeat) { - ipos = ipos % VGradient::colorTableSize; - ipos = ipos < 0 ? VGradient::colorTableSize + ipos : ipos; - } else if (grad->mSpread == VGradient::Spread::Reflect) { - limit = VGradient::colorTableSize * 2; - ipos = ipos % limit; - ipos = ipos < 0 ? limit + ipos : ipos; - ipos = ipos >= VGradient::colorTableSize ? limit - 1 - ipos : ipos; - } else { - if (ipos < 0) - ipos = 0; - else if (ipos >= VGradient::colorTableSize) - ipos = VGradient::colorTableSize - 1; - } - return ipos; -} - -static uint32_t gradientPixelFixed(const VGradientData *grad, int fixed_pos) -{ - int ipos = (fixed_pos + (FIXPT_SIZE / 2)) >> FIXPT_BITS; - - return grad->mColorTable[gradientClamp(grad, ipos)]; -} - -static inline uint32_t gradientPixel(const VGradientData *grad, float pos) -{ - int ipos = (int)(pos * (VGradient::colorTableSize - 1) + (float)(0.5)); - - return grad->mColorTable[gradientClamp(grad, ipos)]; -} - -void fetch_linear_gradient(uint32_t *buffer, const Operator *op, - const VSpanData *data, int y, int x, int length) -{ - float t, inc; - const VGradientData *gradient = &data->mGradient; - - bool affine = true; - float rx = 0, ry = 0; - if (op->linear.l == 0) { - t = inc = 0; - } else { - rx = data->m21 * (y + float(0.5)) + data->m11 * (x + float(0.5)) + - data->dx; - ry = data->m22 * (y + float(0.5)) + data->m12 * (x + float(0.5)) + - data->dy; - t = op->linear.dx * rx + op->linear.dy * ry + op->linear.off; - inc = op->linear.dx * data->m11 + op->linear.dy * data->m12; - affine = !data->m13 && !data->m23; - - if (affine) { - t *= (VGradient::colorTableSize - 1); - inc *= (VGradient::colorTableSize - 1); - } - } - - const uint32_t *end = buffer + length; - if (affine) { - if (inc > float(-1e-5) && inc < float(1e-5)) { - memfill32(buffer, gradientPixelFixed(gradient, int(t * FIXPT_SIZE)), - length); - } else { - if (t + inc * length < float(INT_MAX >> (FIXPT_BITS + 1)) && - t + inc * length > float(INT_MIN >> (FIXPT_BITS + 1))) { - // we can use fixed point math - int t_fixed = int(t * FIXPT_SIZE); - int inc_fixed = int(inc * FIXPT_SIZE); - while (buffer < end) { - *buffer = gradientPixelFixed(gradient, t_fixed); - t_fixed += inc_fixed; - ++buffer; - } - } else { - // we have to fall back to float math - while (buffer < end) { - *buffer = - gradientPixel(gradient, t / VGradient::colorTableSize); - t += inc; - ++buffer; - } - } - } - } else { // fall back to float math here as well - float rw = data->m23 * (y + float(0.5)) + data->m13 * (x + float(0.5)) + - data->m33; - while (buffer < end) { - float xt = rx / rw; - float yt = ry / rw; - t = (op->linear.dx * xt + op->linear.dy * yt) + op->linear.off; - - *buffer = gradientPixel(gradient, t); - rx += data->m11; - ry += data->m12; - rw += data->m13; - if (!rw) { - rw += data->m13; - } - ++buffer; - } - } -} - -static inline float radialDeterminant(float a, float b, float c) -{ - return (b * b) - (4 * a * c); -} - -static void fetch(uint32_t *buffer, const uint32_t *end, const Operator *op, - const VSpanData *data, float det, float delta_det, - float delta_delta_det, float b, float delta_b) -{ - if (op->radial.extended) { - while (buffer < end) { - uint32_t result = 0; - if (det >= 0) { - float w = std::sqrt(det) - b; - if (data->mGradient.radial.fradius + op->radial.dr * w >= 0) - result = gradientPixel(&data->mGradient, w); - } - - *buffer = result; - - det += delta_det; - delta_det += delta_delta_det; - b += delta_b; - - ++buffer; - } - } else { - while (buffer < end) { - *buffer++ = gradientPixel(&data->mGradient, std::sqrt(det) - b); - - det += delta_det; - delta_det += delta_delta_det; - b += delta_b; - } - } -} - -void fetch_radial_gradient(uint32_t *buffer, const Operator *op, - const VSpanData *data, int y, int x, int length) -{ - // avoid division by zero - if (vIsZero(op->radial.a)) { - memfill32(buffer, 0, length); - return; - } - - float rx = - data->m21 * (y + float(0.5)) + data->dx + data->m11 * (x + float(0.5)); - float ry = - data->m22 * (y + float(0.5)) + data->dy + data->m12 * (x + float(0.5)); - bool affine = !data->m13 && !data->m23; - - uint32_t *end = buffer + length; - if (affine) { - rx -= data->mGradient.radial.fx; - ry -= data->mGradient.radial.fy; - - float inv_a = 1 / float(2 * op->radial.a); - - const float delta_rx = data->m11; - const float delta_ry = data->m12; - - float b = 2 * (op->radial.dr * data->mGradient.radial.fradius + - rx * op->radial.dx + ry * op->radial.dy); - float delta_b = - 2 * (delta_rx * op->radial.dx + delta_ry * op->radial.dy); - const float b_delta_b = 2 * b * delta_b; - const float delta_b_delta_b = 2 * delta_b * delta_b; - - const float bb = b * b; - const float delta_bb = delta_b * delta_b; - - b *= inv_a; - delta_b *= inv_a; - - const float rxrxryry = rx * rx + ry * ry; - const float delta_rxrxryry = delta_rx * delta_rx + delta_ry * delta_ry; - const float rx_plus_ry = 2 * (rx * delta_rx + ry * delta_ry); - const float delta_rx_plus_ry = 2 * delta_rxrxryry; - - inv_a *= inv_a; - - float det = - (bb - 4 * op->radial.a * (op->radial.sqrfr - rxrxryry)) * inv_a; - float delta_det = (b_delta_b + delta_bb + - 4 * op->radial.a * (rx_plus_ry + delta_rxrxryry)) * - inv_a; - const float delta_delta_det = - (delta_b_delta_b + 4 * op->radial.a * delta_rx_plus_ry) * inv_a; - - fetch(buffer, end, op, data, det, delta_det, delta_delta_det, b, - delta_b); - } else { - float rw = data->m23 * (y + float(0.5)) + data->m33 + - data->m13 * (x + float(0.5)); - - while (buffer < end) { - if (rw == 0) { - *buffer = 0; - } else { - float invRw = 1 / rw; - float gx = rx * invRw - data->mGradient.radial.fx; - float gy = ry * invRw - data->mGradient.radial.fy; - float b = 2 * (op->radial.dr * data->mGradient.radial.fradius + - gx * op->radial.dx + gy * op->radial.dy); - float det = radialDeterminant( - op->radial.a, b, op->radial.sqrfr - (gx * gx + gy * gy)); - - uint32_t result = 0; - if (det >= 0) { - float detSqrt = std::sqrt(det); - - float s0 = (-b - detSqrt) * op->radial.inv2a; - float s1 = (-b + detSqrt) * op->radial.inv2a; - - float s = vMax(s0, s1); - - if (data->mGradient.radial.fradius + op->radial.dr * s >= 0) - result = gradientPixel(&data->mGradient, s); - } - - *buffer = result; - } - - rx += data->m11; - ry += data->m12; - rw += data->m13; - - ++buffer; - } - } -} - -static inline Operator getOperator(const VSpanData *data) -{ - Operator op; - bool solidSource = false; - - switch (data->mType) { - case VSpanData::Type::Solid: - solidSource = (vAlpha(data->mSolid) == 255); - op.srcFetch = nullptr; - break; - case VSpanData::Type::LinearGradient: - solidSource = false; - getLinearGradientValues(&op.linear, data); - op.srcFetch = &fetch_linear_gradient; - break; - case VSpanData::Type::RadialGradient: - solidSource = false; - getRadialGradientValues(&op.radial, data); - op.srcFetch = &fetch_radial_gradient; - break; - default: - op.srcFetch = nullptr; - break; - } - - op.mode = data->mBlendMode; - if (op.mode == BlendMode::SrcOver && solidSource) op.mode = BlendMode::Src; - - op.funcSolid = RenderTable.color(op.mode); - op.func = RenderTable.src(op.mode); - - return op; -} - -static void blend_color(size_t size, const VRle::Span *array, void *userData) -{ - VSpanData *data = (VSpanData *)(userData); - Operator op = getOperator(data); - const uint32_t color = data->mSolid; - - for (size_t i = 0 ; i < size; ++i) { - const auto &span = array[i]; - op.funcSolid(data->buffer(span.x, span.y), span.len, color, span.coverage); - } -} - -// Signature of Process Object -// void Pocess(uint* scratchBuffer, size_t x, size_t y, uint8_t cov) -template -static inline void process_in_chunk(const VRle::Span *array, size_t size, - Process process) -{ - std::array buf; - for (size_t i = 0; i < size; i++) { - const auto &span = array[i]; - size_t len = span.len; - auto x = span.x; - while (len) { - auto l = std::min(len, buf.size()); - process(buf.data(), x, span.y, l, span.coverage); - x += l; - len -= l; - } - } -} - -static void blend_gradient(size_t size, const VRle::Span *array, - void *userData) -{ - auto *data = (VSpanData *)(userData); - Operator op = getOperator(data); - - if (!op.srcFetch) return; - - process_in_chunk( - array, size, - [&](uint32_t *scratch, size_t x, size_t y, size_t len, uint8_t cov) { - op.srcFetch(scratch, &op, data, (int)y, (int)x, (int)len); - op.func(data->buffer((int)x, (int)y), (int)len, scratch, cov); - }); -} - -template -constexpr const T &clamp(const T &v, const T &lo, const T &hi) -{ - return v < lo ? lo : hi < v ? hi : v; -} - -static constexpr inline uint8_t alpha_mul(uint8_t a, uint8_t b) -{ - return ((a * b) >> 8); -} - -static void blend_image_xform(size_t size, const VRle::Span *array, - void *userData) -{ - const auto data = reinterpret_cast(userData); - const auto &src = data->texture(); - - if (src.format() != VBitmap::Format::ARGB32_Premultiplied && - src.format() != VBitmap::Format::ARGB32) { - //@TODO other formats not yet handled. - return; - } - - Operator op = getOperator(data); - - process_in_chunk( - array, size, - [&](uint32_t *scratch, size_t x, size_t y, size_t len, uint8_t cov) { - const auto coverage = (cov * src.alpha()) >> 8; - const float xfactor = y * data->m21 + data->dx + data->m11; - const float yfactor = y * data->m22 + data->dy + data->m12; - for (size_t i = 0; i < len; i++) { - const float fx = (x + i) * data->m11 + xfactor; - const float fy = (x + i) * data->m12 + yfactor; - const int px = clamp(int(fx), src.left, src.right); - const int py = clamp(int(fy), src.top, src.bottom); - scratch[i] = src.pixel(px, py); - } - op.func(data->buffer((int)x, (int)y), (int)len, scratch, coverage); - }); -} - -static void blend_image(size_t size, const VRle::Span *array, void *userData) -{ - const auto data = reinterpret_cast(userData); - const auto &src = data->texture(); - - if (src.format() != VBitmap::Format::ARGB32_Premultiplied && - src.format() != VBitmap::Format::ARGB32) { - //@TODO other formats not yet handled. - return; - } - - Operator op = getOperator(data); - - for (size_t i = 0; i < size; i++) { - const auto &span = array[i]; - int x = span.x; - int length = span.len; - int sx = x + int(data->dx); - int sy = span.y + int(data->dy); - - // notyhing to copy. - if (sy < 0 || sy >= int(src.height()) || sx >= int(src.width()) || - (sx + length) <= 0) - continue; - - // intersecting left edge of image - if (sx < 0) { - x -= sx; - length += sx; - sx = 0; - } - // intersecting right edge of image - if (sx + length > int(src.width())) length = (int)src.width() - sx; - - op.func(data->buffer(x, span.y), length, src.pixelRef(sx, sy), - alpha_mul(span.coverage, src.alpha())); - } -} - -void VSpanData::setup(const VBrush &brush, BlendMode /*mode*/, int /*alpha*/) -{ - transformType = VMatrix::MatrixType::None; - - switch (brush.type()) { - case VBrush::Type::NoBrush: - mType = VSpanData::Type::None; - break; - case VBrush::Type::Solid: - mType = VSpanData::Type::Solid; - mSolid = brush.mColor.premulARGB(); - break; - case VBrush::Type::LinearGradient: { - mType = VSpanData::Type::LinearGradient; - mColorTable = VGradientCache::instance().getBuffer(*brush.mGradient); - mGradient.mColorTable = mColorTable->buffer32; - mGradient.mColorTableAlpha = mColorTable->alpha; - mGradient.linear.x1 = brush.mGradient->linear.x1; - mGradient.linear.y1 = brush.mGradient->linear.y1; - mGradient.linear.x2 = brush.mGradient->linear.x2; - mGradient.linear.y2 = brush.mGradient->linear.y2; - mGradient.mSpread = brush.mGradient->mSpread; - setupMatrix(brush.mGradient->mMatrix); - break; - } - case VBrush::Type::RadialGradient: { - mType = VSpanData::Type::RadialGradient; - mColorTable = VGradientCache::instance().getBuffer(*brush.mGradient); - mGradient.mColorTable = mColorTable->buffer32; - mGradient.mColorTableAlpha = mColorTable->alpha; - mGradient.radial.cx = brush.mGradient->radial.cx; - mGradient.radial.cy = brush.mGradient->radial.cy; - mGradient.radial.fx = brush.mGradient->radial.fx; - mGradient.radial.fy = brush.mGradient->radial.fy; - mGradient.radial.cradius = brush.mGradient->radial.cradius; - mGradient.radial.fradius = brush.mGradient->radial.fradius; - mGradient.mSpread = brush.mGradient->mSpread; - setupMatrix(brush.mGradient->mMatrix); - break; - } - case VBrush::Type::Texture: { - mType = VSpanData::Type::Texture; - initTexture(&brush.mTexture->mBitmap, brush.mTexture->mAlpha, - brush.mTexture->mBitmap.rect()); - setupMatrix(brush.mTexture->mMatrix); - break; - } - default: - break; - } - updateSpanFunc(); -} - -void VSpanData::setupMatrix(const VMatrix &matrix) -{ - VMatrix inv = matrix.inverted(); - m11 = inv.m11; - m12 = inv.m12; - m13 = inv.m13; - m21 = inv.m21; - m22 = inv.m22; - m23 = inv.m23; - m33 = inv.m33; - dx = inv.mtx; - dy = inv.mty; - transformType = inv.type(); - - const bool affine = inv.isAffine(); - const float f1 = m11 * m11 + m21 * m21; - const float f2 = m12 * m12 + m22 * m22; - fast_matrix = affine && f1 < 1e4 && f2 < 1e4 && f1 > (1.0 / 65536) && - f2 > (1.0 / 65536) && fabs(dx) < 1e4 && fabs(dy) < 1e4; -} - -void VSpanData::initTexture(const VBitmap *bitmap, int alpha, - const VRect &sourceRect) -{ - mType = VSpanData::Type::Texture; - mTexture.prepare(bitmap); - mTexture.setClip(sourceRect); - mTexture.setAlpha(alpha); - updateSpanFunc(); -} - -void VSpanData::updateSpanFunc() -{ - switch (mType) { - case VSpanData::Type::None: - mUnclippedBlendFunc = nullptr; - break; - case VSpanData::Type::Solid: - mUnclippedBlendFunc = &blend_color; - break; - case VSpanData::Type::LinearGradient: - case VSpanData::Type::RadialGradient: { - mUnclippedBlendFunc = &blend_gradient; - break; - } - case VSpanData::Type::Texture: { - //@TODO update proper image function. - if (transformType <= VMatrix::MatrixType::Translate) { - mUnclippedBlendFunc = &blend_image; - } else { - mUnclippedBlendFunc = &blend_image_xform; - } - break; - } - } -} - -#if !defined(__SSE2__) && !defined(__ARM_NEON__) && !defined(__aarch64__) -void memfill32(uint32_t *dest, uint32_t value, int length) -{ - // let compiler do the auto vectorization. - for (int i = 0 ; i < length; i++) { - *dest++ = value; - } -} -#endif - diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawhelper.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawhelper.h deleted file mode 100644 index dae7b24b4..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawhelper.h +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef VDRAWHELPER_H -#define VDRAWHELPER_H - -#include -#include -#include "assert.h" -#include "vbitmap.h" -#include "vbrush.h" -#include "vrect.h" -#include "vrle.h" - -V_USE_NAMESPACE - -struct VSpanData; -struct Operator; - -struct RenderFunc -{ - using Color = void (*)(uint32_t *dest, int length, uint32_t color, uint32_t alpha); - using Src = void (*)(uint32_t *dest, int length, const uint32_t *src, uint32_t alpha); - enum class Type { - Invalid, - Color, - Src, - }; - RenderFunc() = default; - RenderFunc(Type t, Color f):type_(t), color_(f){assert(t == Type::Color);} - RenderFunc(Type t, Src f):type_(t), src_(f){ assert(t == Type::Src);} - - Type type_{Type::Invalid}; - union { - Color color_; - Src src_; - }; -}; - -class RenderFuncTable -{ -public: - RenderFuncTable(); - RenderFunc::Color color(BlendMode mode) const - { - return colorTable[uint32_t(mode)].color_; - } - RenderFunc::Src src(BlendMode mode) const - { - return srcTable[uint32_t(mode)].src_; - } -private: - void neon(); - void sse(); - void updateColor(BlendMode mode, RenderFunc::Color f) - { - colorTable[uint32_t(mode)] = {RenderFunc::Type::Color, f}; - } - void updateSrc(BlendMode mode, RenderFunc::Src f) - { - srcTable[uint32_t(mode)] = {RenderFunc::Type::Src, f}; - } -private: - std::array colorTable; - std::array srcTable; -}; - -typedef void (*SourceFetchProc)(uint32_t *buffer, const Operator *o, - const VSpanData *data, int y, int x, - int length); -typedef void (*ProcessRleSpan)(size_t count, const VRle::Span *spans, - void *userData); - -extern void memfill32(uint32_t *dest, uint32_t value, int count); - -struct LinearGradientValues { - float dx; - float dy; - float l; - float off; -}; - -struct RadialGradientValues { - float dx; - float dy; - float dr; - float sqrfr; - float a; - float inv2a; - bool extended; -}; - -struct Operator { - BlendMode mode; - SourceFetchProc srcFetch; - RenderFunc::Color funcSolid; - RenderFunc::Src func; - union { - LinearGradientValues linear; - RadialGradientValues radial; - }; -}; - -class VRasterBuffer { -public: - VBitmap::Format prepare(const VBitmap *image); - void clear(); - - inline uint8_t *scanLine(int y) - { - assert(y >= 0); - assert(size_t(y) < mHeight); - return mBuffer + y * mBytesPerLine; - } - uint32_t *pixelRef(int x, int y) const - { - return (uint32_t *)(mBuffer + y * mBytesPerLine + x * mBytesPerPixel); - } - - size_t width() const { return mWidth; } - size_t height() const { return mHeight; } - size_t bytesPerLine() const { return mBytesPerLine; } - size_t bytesPerPixel() const { return mBytesPerPixel; } - VBitmap::Format format() const { return mFormat; } - -private: - VBitmap::Format mFormat{VBitmap::Format::ARGB32_Premultiplied}; - size_t mWidth{0}; - size_t mHeight{0}; - size_t mBytesPerLine{0}; - size_t mBytesPerPixel{0}; - mutable uint8_t *mBuffer{nullptr}; -}; - -struct VGradientData { - VGradient::Spread mSpread; - struct Linear { - float x1, y1, x2, y2; - }; - struct Radial { - float cx, cy, fx, fy, cradius, fradius; - }; - union { - Linear linear; - Radial radial; - }; - const uint32_t *mColorTable; - bool mColorTableAlpha; -}; - -struct VTextureData : public VRasterBuffer { - uint32_t pixel(int x, int y) const { return *pixelRef(x, y); }; - uint8_t alpha() const { return mAlpha; } - void setAlpha(uint8_t alpha) { mAlpha = alpha; } - void setClip(const VRect &clip); - // clip rect - int left; - int right; - int top; - int bottom; - bool hasAlpha; - uint8_t mAlpha; -}; - -struct VColorTable { - uint32_t buffer32[VGradient::colorTableSize]; - bool alpha{true}; -}; - -struct VSpanData { - enum class Type { None, Solid, LinearGradient, RadialGradient, Texture }; - - void updateSpanFunc(); - void init(VRasterBuffer *image); - void setup(const VBrush &brush, BlendMode mode = BlendMode::SrcOver, - int alpha = 255); - void setupMatrix(const VMatrix &matrix); - - VRect clipRect() const - { - return VRect(0, 0, mDrawableSize.width(), mDrawableSize.height()); - } - - void setDrawRegion(const VRect ®ion) - { - mOffset = VPoint(region.left(), region.top()); - mDrawableSize = VSize(region.width(), region.height()); - } - - uint32_t *buffer(int x, int y) const - { - return mRasterBuffer->pixelRef(x + mOffset.x(), y + mOffset.y()); - } - void initTexture(const VBitmap *image, int alpha, const VRect &sourceRect); - const VTextureData &texture() const { return mTexture; } - - BlendMode mBlendMode{BlendMode::SrcOver}; - VRasterBuffer * mRasterBuffer; - ProcessRleSpan mBlendFunc; - ProcessRleSpan mUnclippedBlendFunc; - VSpanData::Type mType; - std::shared_ptr mColorTable{nullptr}; - VPoint mOffset; // offset to the subsurface - VSize mDrawableSize; // suburface size - uint32_t mSolid; - VGradientData mGradient; - VTextureData mTexture; - - float m11, m12, m13, m21, m22, m23, m33, dx, dy; // inverse xform matrix - bool fast_matrix{true}; - VMatrix::MatrixType transformType{VMatrix::MatrixType::None}; -}; - -#define BYTE_MUL(c, a) \ - ((((((c) >> 8) & 0x00ff00ff) * (a)) & 0xff00ff00) + \ - (((((c)&0x00ff00ff) * (a)) >> 8) & 0x00ff00ff)) - -inline constexpr int vRed(uint32_t c) -{ - return ((c >> 16) & 0xff); -} - -inline constexpr int vGreen(uint32_t c) -{ - return ((c >> 8) & 0xff); -} - -inline constexpr int vBlue(uint32_t c) -{ - return (c & 0xff); -} - -inline constexpr int vAlpha(uint32_t c) -{ - return c >> 24; -} - -static inline uint32_t interpolate_pixel(uint32_t x, uint32_t a, uint32_t y, - uint32_t b) -{ - uint32_t t = (x & 0xff00ff) * a + (y & 0xff00ff) * b; - t >>= 8; - t &= 0xff00ff; - x = ((x >> 8) & 0xff00ff) * a + ((y >> 8) & 0xff00ff) * b; - x &= 0xff00ff00; - x |= t; - return x; -} - -#endif // QDRAWHELPER_P_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawhelper_common.cpp b/libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawhelper_common.cpp deleted file mode 100644 index 454d80924..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawhelper_common.cpp +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include -#include "vdrawhelper.h" - -/* -result = s -dest = s * ca + d * cia -*/ -static void color_Source(uint32_t *dest, int length, uint32_t color, - uint32_t alpha) -{ - int ialpha, i; - - if (alpha == 255) { - memfill32(dest, color, length); - } else { - ialpha = 255 - alpha; - color = BYTE_MUL(color, alpha); - for (i = 0; i < length; ++i) - dest[i] = color + BYTE_MUL(dest[i], ialpha); - } -} - -/* - r = s + d * sia - dest = r * ca + d * cia - = (s + d * sia) * ca + d * cia - = s * ca + d * (sia * ca + cia) - = s * ca + d * (1 - sa*ca) - = s' + d ( 1 - s'a) -*/ -static void color_SourceOver(uint32_t *dest, int length, uint32_t color, - uint32_t alpha) -{ - int ialpha, i; - - if (alpha != 255) color = BYTE_MUL(color, alpha); - ialpha = 255 - vAlpha(color); - for (i = 0; i < length; ++i) dest[i] = color + BYTE_MUL(dest[i], ialpha); -} - -/* - result = d * sa - dest = d * sa * ca + d * cia - = d * (sa * ca + cia) -*/ -static void color_DestinationIn(uint32_t *dest, int length, uint32_t color, - uint32_t alpha) -{ - uint32_t a = vAlpha(color); - if (alpha != 255) { - a = BYTE_MUL(a, alpha) + 255 - alpha; - } - for (int i = 0; i < length; ++i) { - dest[i] = BYTE_MUL(dest[i], a); - } -} - -/* - result = d * sia - dest = d * sia * ca + d * cia - = d * (sia * ca + cia) -*/ -static void color_DestinationOut(uint32_t *dest, int length, uint32_t color, - uint32_t alpha) -{ - uint32_t a = vAlpha(~color); - if (alpha != 255) a = BYTE_MUL(a, alpha) + 255 - alpha; - for (int i = 0; i < length; ++i) { - dest[i] = BYTE_MUL(dest[i], a); - } -} - -static void src_Source(uint32_t *dest, int length, const uint32_t *src, - uint32_t alpha) -{ - if (alpha == 255) { - memcpy(dest, src, size_t(length) * sizeof(uint32_t)); - } else { - uint32_t ialpha = 255 - alpha; - for (int i = 0; i < length; ++i) { - dest[i] = - interpolate_pixel(src[i], alpha, dest[i], ialpha); - } - } -} - -/* s' = s * ca - * d' = s' + d (1 - s'a) - */ -static void src_SourceOver(uint32_t *dest, int length, const uint32_t *src, - uint32_t alpha) -{ - uint32_t s, sia; - - if (alpha == 255) { - for (int i = 0; i < length; ++i) { - s = src[i]; - if (s >= 0xff000000) - dest[i] = s; - else if (s != 0) { - sia = vAlpha(~s); - dest[i] = s + BYTE_MUL(dest[i], sia); - } - } - } else { - /* source' = source * const_alpha - * dest = source' + dest ( 1- source'a) - */ - for (int i = 0; i < length; ++i) { - s = BYTE_MUL(src[i], alpha); - sia = vAlpha(~s); - dest[i] = s + BYTE_MUL(dest[i], sia); - } - } -} - -static void src_DestinationIn(uint32_t *dest, int length, const uint32_t *src, - uint32_t alpha) -{ - if (alpha == 255) { - for (int i = 0; i < length; ++i) { - dest[i] = BYTE_MUL(dest[i], vAlpha(src[i])); - } - } else { - uint32_t cia = 255 - alpha; - for (int i = 0; i < length; ++i) { - uint32_t a = BYTE_MUL(vAlpha(src[i]), alpha) + cia; - dest[i] = BYTE_MUL(dest[i], a); - } - } -} - -static void src_DestinationOut(uint32_t *dest, int length, const uint32_t *src, - uint32_t alpha) -{ - if (alpha == 255) { - for (int i = 0; i < length; ++i) { - dest[i] = BYTE_MUL(dest[i], vAlpha(~src[i])); - } - } else { - uint32_t cia = 255 - alpha; - for (int i = 0; i < length; ++i) { - uint32_t sia = BYTE_MUL(vAlpha(~src[i]), alpha) + cia; - dest[i] = BYTE_MUL(dest[i], sia); - } - } -} - -RenderFuncTable::RenderFuncTable() -{ - updateColor(BlendMode::Src, color_Source); - updateColor(BlendMode::SrcOver, color_SourceOver); - updateColor(BlendMode::DestIn, color_DestinationIn); - updateColor(BlendMode::DestOut, color_DestinationOut); - - updateSrc(BlendMode::Src, src_Source); - updateSrc(BlendMode::SrcOver, src_SourceOver); - updateSrc(BlendMode::DestIn, src_DestinationIn); - updateSrc(BlendMode::DestOut, src_DestinationOut); - -#if defined(__ARM_NEON__) || defined(__aarch64__) - neon(); -#endif -#if defined(__SSE2__) - sse(); -#endif -} diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawhelper_neon.cpp b/libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawhelper_neon.cpp deleted file mode 100644 index 3e099f647..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawhelper_neon.cpp +++ /dev/null @@ -1,290 +0,0 @@ -#if defined(__ARM_NEON__) || defined(__aarch64__) - -#include -#include "vdrawhelper.h" - -#define SIMD_EPILOGUE(i, length, max) \ - for (int _i = 0; _i < (max) && (i) < (length); ++(i), ++_i) - -static inline uint16x8_t qvdiv_255_u16(uint16x8_t x, uint16x8_t half) { - // result = (x + (x >> 8) + 0x80) >> 8 - - const uint16x8_t temp = vshrq_n_u16(x, 8); // x >> 8 - const uint16x8_t sum_part = vaddq_u16(x, half); // x + 0x80 - const uint16x8_t sum = vaddq_u16(temp, sum_part); - - return vshrq_n_u16(sum, 8); -} - - -static inline uint16x8_t qvbyte_mul_u16(uint16x8_t x, uint16x8_t alpha, uint16x8_t half) { - // t = qRound(x * alpha / 255.0) - - const uint16x8_t t = vmulq_u16(x, alpha); // t - return qvdiv_255_u16(t, half); -} - -static const uint32_t AMASK = 0xff000000; -static const uint32_t RMASK = 0x00ff0000; -static const uint32_t GMASK = 0x0000ff00; -static const uint32_t BMASK = 0x000000ff; - -#if __aarch64__ // 64-bit versions -#define AMIX(mask) (vMin(((uint64_t(s)&(mask)) + (uint64_t(d)&(mask))), uint64_t(mask))) -#define MIX(mask) (vMin(((uint64_t(s)&(mask)) + (uint64_t(d)&(mask))), uint64_t(mask))) -#else // 32 bits -// The mask for alpha can overflow over 32 bits -#define AMIX(mask) uint32_t(vMin(((uint64_t(s)&(mask)) + (uint64_t(d)&(mask))), uint64_t(mask))) -#define MIX(mask) (vMin(((uint32_t(s)&(mask)) + (uint32_t(d)&(mask))), uint32_t(mask))) -#endif - -static inline uint32_t -comp_func_Plus_one_pixel_const_alpha(uint32_t d, const uint32_t s, const uint32_t const_alpha, - const uint32_t one_minus_const_alpha) { - const uint32_t result = uint32_t(AMIX(AMASK) | MIX(RMASK) | MIX(GMASK) | MIX(BMASK)); - return interpolate_pixel(result, const_alpha, d, one_minus_const_alpha); -} - -static inline uint32_t comp_func_Plus_one_pixel(uint32_t d, const uint32_t s) { - const uint32_t result = uint32_t(AMIX(AMASK) | MIX(RMASK) | MIX(GMASK) | MIX(BMASK)); - return result; -} - -static inline uint16x8_t -qvinterpolate_pixel_255(uint16x8_t x, uint16x8_t a, uint16x8_t y, uint16x8_t b, uint16x8_t half) { - // t = x * a + y * b - - const uint16x8_t ta = vmulq_u16(x, a); - const uint16x8_t tb = vmulq_u16(y, b); - - return qvdiv_255_u16(vaddq_u16(ta, tb), half); -} - -///////////////////////////////////////////////////////////////////////////////////////////////// - -void memfill32(uint32_t *dest, uint32_t value, int count) { - const int epilogueSize = count % 16; -#if defined(__aarch64__) - if (count >= 16) { - uint32_t *const neonEnd = dest + count - epilogueSize; - register uint32x4_t valueVector1 asm ("v0") = vdupq_n_u32(value); - register uint32x4_t valueVector2 asm ("v1") = valueVector1; - while (dest != neonEnd) { - asm volatile ( - "st2 { v0.4s, v1.4s }, [%[DST]], #32 \n\t" - "st2 { v0.4s, v1.4s }, [%[DST]], #32 \n\t" - : [DST]"+r"(dest) - : [VALUE1]"w"(valueVector1), [VALUE2]"w"(valueVector2) - : "memory" - ); - } - } -#else - if (count >= 16) { - uint32_t *const neonEnd = dest + count - epilogueSize; - register uint32x4_t valueVector1 asm ("q0") = vdupq_n_u32(value); - register uint32x4_t valueVector2 asm ("q1") = valueVector1; - while (dest != neonEnd) { - asm volatile ( - "vst2.32 { d0, d1, d2, d3 }, [%[DST]] !\n\t" - "vst2.32 { d0, d1, d2, d3 }, [%[DST]] !\n\t" - : [DST]"+r" (dest) - : [VALUE1]"w"(valueVector1), [VALUE2]"w"(valueVector2) - : "memory" - ); - } - } -#endif - - switch (epilogueSize) { - case 15: - *dest++ = value; - case 14: - *dest++ = value; - case 13: - *dest++ = value; - case 12: - *dest++ = value; - case 11: - *dest++ = value; - case 10: - *dest++ = value; - case 9: - *dest++ = value; - case 8: - *dest++ = value; - case 7: - *dest++ = value; - case 6: - *dest++ = value; - case 5: - *dest++ = value; - case 4: - *dest++ = value; - case 3: - *dest++ = value; - case 2: - *dest++ = value; - case 1: - *dest = value; - default: - break; - } -} - -//comp_func_solid_Source_neon -static void color_Source(uint32_t *destPixels, int length, uint32_t color, uint32_t const_alpha) { - if (const_alpha == 255) { - memfill32(destPixels, color, length); - } else { - const uint32_t minusAlphaOfColor = 255 - const_alpha; - color = BYTE_MUL(color, const_alpha); - int x = 0; - - auto *dst = (uint32_t *) destPixels; - const uint32x4_t colorVector = vdupq_n_u32(color); - uint16x8_t half = vdupq_n_u16(0x80); - const uint16x8_t minusAlphaOfColorVector = vdupq_n_u16(minusAlphaOfColor); - - for (; x < length - 3; x += 4) { - uint32x4_t dstVector = vld1q_u32(&dst[x]); - - const uint8x16_t dst8 = vreinterpretq_u8_u32(dstVector); - - const uint8x8_t dst8_low = vget_low_u8(dst8); - const uint8x8_t dst8_high = vget_high_u8(dst8); - - const uint16x8_t dst16_low = vmovl_u8(dst8_low); - const uint16x8_t dst16_high = vmovl_u8(dst8_high); - - const uint16x8_t result16_low = qvbyte_mul_u16(dst16_low, minusAlphaOfColorVector, - half); - const uint16x8_t result16_high = qvbyte_mul_u16(dst16_high, minusAlphaOfColorVector, - half); - - const uint32x2_t result32_low = vreinterpret_u32_u8(vmovn_u16(result16_low)); - const uint32x2_t result32_high = vreinterpret_u32_u8(vmovn_u16(result16_high)); - - uint32x4_t blendedPixels = vcombine_u32(result32_low, result32_high); - uint32x4_t colorPlusBlendedPixels = vaddq_u32(colorVector, blendedPixels); - vst1q_u32(&dst[x], colorPlusBlendedPixels); - } - - SIMD_EPILOGUE(x, length, 3) { - destPixels[x] = color + BYTE_MUL(destPixels[x], minusAlphaOfColor); - } - } -} - -//comp_func_solid_SourceOver_neon -static void -color_SourceOver(uint32_t *destPixels, int length, uint32_t color, uint32_t const_alpha) { - if ((const_alpha & vAlpha(color)) == 255) { - memfill32(destPixels, color, length); - } else { - if (const_alpha != 255) { - color = BYTE_MUL(color, const_alpha); - } - - const uint32_t minusAlphaOfColor = vAlpha(~color); - int x = 0; - - auto *dst = (uint32_t *) destPixels; - const uint32x4_t colorVector = vdupq_n_u32(color); - uint16x8_t half = vdupq_n_u16(0x80); - const uint16x8_t minusAlphaOfColorVector = vdupq_n_u16(minusAlphaOfColor); - - for (; x < length - 3; x += 4) { - uint32x4_t dstVector = vld1q_u32(&dst[x]); - - const uint8x16_t dst8 = vreinterpretq_u8_u32(dstVector); - - const uint8x8_t dst8_low = vget_low_u8(dst8); - const uint8x8_t dst8_high = vget_high_u8(dst8); - - const uint16x8_t dst16_low = vmovl_u8(dst8_low); - const uint16x8_t dst16_high = vmovl_u8(dst8_high); - - const uint16x8_t result16_low = qvbyte_mul_u16(dst16_low, minusAlphaOfColorVector, - half); - const uint16x8_t result16_high = qvbyte_mul_u16(dst16_high, minusAlphaOfColorVector, - half); - - const uint32x2_t result32_low = vreinterpret_u32_u8(vmovn_u16(result16_low)); - const uint32x2_t result32_high = vreinterpret_u32_u8(vmovn_u16(result16_high)); - - uint32x4_t blendedPixels = vcombine_u32(result32_low, result32_high); - uint32x4_t colorPlusBlendedPixels = vaddq_u32(colorVector, blendedPixels); - vst1q_u32(&dst[x], colorPlusBlendedPixels); - } - - SIMD_EPILOGUE(x, length, 3) { - destPixels[x] = color + BYTE_MUL(destPixels[x], minusAlphaOfColor); - } - } -} - -//comp_func_Plus_neon -static void src_Source(uint32_t *dst, int length, const uint32_t *src, uint32_t const_alpha) { - if (const_alpha == 255) { - uint32_t *const end = dst + length; - uint32_t *const neonEnd = end - 3; - - while (dst < neonEnd) { - uint8x16_t vs = vld1q_u8((const uint8_t *) src); - const uint8x16_t vd = vld1q_u8((uint8_t *) dst); - vs = vqaddq_u8(vs, vd); - vst1q_u8((uint8_t *) dst, vs); - src += 4; - dst += 4; - } - - while (dst != end) { - *dst = comp_func_Plus_one_pixel(*dst, *src); - ++dst; - ++src; - } - } else { - int x = 0; - const int32_t one_minus_const_alpha = 255 - (int32_t) const_alpha; - const uint16x8_t constAlphaVector = vdupq_n_u16(const_alpha); - const uint16x8_t oneMinusconstAlphaVector = vdupq_n_u16(one_minus_const_alpha); - - const uint16x8_t half = vdupq_n_u16(0x80); - for (; x < length - 3; x += 4) { - const uint32x4_t src32 = vld1q_u32((uint32_t *) &src[x]); - const uint8x16_t src8 = vreinterpretq_u8_u32(src32); - uint8x16_t dst8 = vld1q_u8((uint8_t *) &dst[x]); - uint8x16_t result = vqaddq_u8(dst8, src8); - - uint16x8_t result_low = vmovl_u8(vget_low_u8(result)); - uint16x8_t result_high = vmovl_u8(vget_high_u8(result)); - - uint16x8_t dst_low = vmovl_u8(vget_low_u8(dst8)); - uint16x8_t dst_high = vmovl_u8(vget_high_u8(dst8)); - - result_low = qvinterpolate_pixel_255(result_low, constAlphaVector, dst_low, - oneMinusconstAlphaVector, half); - result_high = qvinterpolate_pixel_255(result_high, constAlphaVector, dst_high, - oneMinusconstAlphaVector, half); - - const uint32x2_t result32_low = vreinterpret_u32_u8(vmovn_u16(result_low)); - const uint32x2_t result32_high = vreinterpret_u32_u8(vmovn_u16(result_high)); - vst1q_u32((uint32_t *) &dst[x], vcombine_u32(result32_low, result32_high)); - } - - SIMD_EPILOGUE(x, length, 3) { - dst[x] = comp_func_Plus_one_pixel_const_alpha(dst[x], src[x], const_alpha, - one_minus_const_alpha); - } - } -} - -void RenderFuncTable::neon() { - updateColor(BlendMode::Src, color_Source); - updateColor(BlendMode::SrcOver, color_SourceOver); - - updateSrc(BlendMode::Src, src_Source); -} - -#endif diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawhelper_sse2.cpp b/libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawhelper_sse2.cpp deleted file mode 100644 index fd9b711e5..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawhelper_sse2.cpp +++ /dev/null @@ -1,261 +0,0 @@ -#if defined(__SSE2__) - -#include -#include /* for SSE2 intrinsics */ -#include /* for _mm_shuffle_pi16 and _MM_SHUFFLE */ - -#include "vdrawhelper.h" -// Each 32bits components of alphaChannel must be in the form 0x00AA00AA -inline static __m128i v4_byte_mul_sse2(__m128i c, __m128i a) -{ - const __m128i ag_mask = _mm_set1_epi32(0xFF00FF00); - const __m128i rb_mask = _mm_set1_epi32(0x00FF00FF); - - /* for AG */ - __m128i v_ag = _mm_and_si128(ag_mask, c); - v_ag = _mm_srli_epi32(v_ag, 8); - v_ag = _mm_mullo_epi16(a, v_ag); - v_ag = _mm_and_si128(ag_mask, v_ag); - - /* for RB */ - __m128i v_rb = _mm_and_si128(rb_mask, c); - v_rb = _mm_mullo_epi16(a, v_rb); - v_rb = _mm_srli_epi32(v_rb, 8); - v_rb = _mm_and_si128(rb_mask, v_rb); - - /* combine */ - return _mm_add_epi32(v_ag, v_rb); -} - -static inline __m128i v4_interpolate_color_sse2(__m128i a, __m128i c0, - __m128i c1) -{ - const __m128i rb_mask = _mm_set1_epi32(0xFF00FF00); - const __m128i zero = _mm_setzero_si128(); - - __m128i a_l = a; - __m128i a_h = a; - a_l = _mm_unpacklo_epi16(a_l, a_l); - a_h = _mm_unpackhi_epi16(a_h, a_h); - - __m128i a_t = _mm_slli_epi64(a_l, 32); - __m128i a_t0 = _mm_slli_epi64(a_h, 32); - - a_l = _mm_add_epi32(a_l, a_t); - a_h = _mm_add_epi32(a_h, a_t0); - - __m128i c0_l = c0; - __m128i c0_h = c0; - - c0_l = _mm_unpacklo_epi8(c0_l, zero); - c0_h = _mm_unpackhi_epi8(c0_h, zero); - - __m128i c1_l = c1; - __m128i c1_h = c1; - - c1_l = _mm_unpacklo_epi8(c1_l, zero); - c1_h = _mm_unpackhi_epi8(c1_h, zero); - - __m128i cl_sub = _mm_sub_epi16(c0_l, c1_l); - __m128i ch_sub = _mm_sub_epi16(c0_h, c1_h); - - cl_sub = _mm_mullo_epi16(cl_sub, a_l); - ch_sub = _mm_mullo_epi16(ch_sub, a_h); - - __m128i c1ls = _mm_slli_epi16(c1_l, 8); - __m128i c1hs = _mm_slli_epi16(c1_h, 8); - - cl_sub = _mm_add_epi16(cl_sub, c1ls); - ch_sub = _mm_add_epi16(ch_sub, c1hs); - - cl_sub = _mm_and_si128(cl_sub, rb_mask); - ch_sub = _mm_and_si128(ch_sub, rb_mask); - - cl_sub = _mm_srli_epi64(cl_sub, 8); - ch_sub = _mm_srli_epi64(ch_sub, 8); - - cl_sub = _mm_packus_epi16(cl_sub, cl_sub); - ch_sub = _mm_packus_epi16(ch_sub, ch_sub); - - return (__m128i)_mm_shuffle_ps((__m128)cl_sub, (__m128)ch_sub, 0x44); -} - -// Load src and dest vector -#define V4_FETCH_SRC_DEST \ - __m128i v_src = _mm_loadu_si128((__m128i*)src); \ - __m128i v_dest = _mm_load_si128((__m128i*)dest); - -#define V4_FETCH_SRC __m128i v_src = _mm_loadu_si128((__m128i*)src); - -#define V4_STORE_DEST _mm_store_si128((__m128i*)dest, v_src); - -#define V4_SRC_DEST_LEN_INC \ - dest += 4; \ - src += 4; \ - length -= 4; - -// Multiply src color with const_alpha -#define V4_ALPHA_MULTIPLY v_src = v4_byte_mul_sse2(v_src, v_alpha); - - -// dest = src + dest * sia -#define V4_COMP_OP_SRC \ - v_src = v4_interpolate_color_sse2(v_alpha, v_src, v_dest); - -#define LOOP_ALIGNED_U1_A4(DEST, LENGTH, UOP, A4OP) \ - { \ - while ((uintptr_t)DEST & 0xF && LENGTH) \ - UOP \ - \ - while (LENGTH) \ - { \ - switch (LENGTH) { \ - case 3: \ - case 2: \ - case 1: \ - UOP break; \ - default: \ - A4OP break; \ - } \ - } \ - } - -void memfill32(uint32_t* dest, uint32_t value, int length) -{ - __m128i vector_data = _mm_set_epi32(value, value, value, value); - - // run till memory alligned to 16byte memory - while (length && ((uintptr_t)dest & 0xf)) { - *dest++ = value; - length--; - } - - while (length >= 32) { - _mm_store_si128((__m128i*)(dest), vector_data); - _mm_store_si128((__m128i*)(dest + 4), vector_data); - _mm_store_si128((__m128i*)(dest + 8), vector_data); - _mm_store_si128((__m128i*)(dest + 12), vector_data); - _mm_store_si128((__m128i*)(dest + 16), vector_data); - _mm_store_si128((__m128i*)(dest + 20), vector_data); - _mm_store_si128((__m128i*)(dest + 24), vector_data); - _mm_store_si128((__m128i*)(dest + 28), vector_data); - - dest += 32; - length -= 32; - } - - if (length >= 16) { - _mm_store_si128((__m128i*)(dest), vector_data); - _mm_store_si128((__m128i*)(dest + 4), vector_data); - _mm_store_si128((__m128i*)(dest + 8), vector_data); - _mm_store_si128((__m128i*)(dest + 12), vector_data); - - dest += 16; - length -= 16; - } - - if (length >= 8) { - _mm_store_si128((__m128i*)(dest), vector_data); - _mm_store_si128((__m128i*)(dest + 4), vector_data); - - dest += 8; - length -= 8; - } - - if (length >= 4) { - _mm_store_si128((__m128i*)(dest), vector_data); - - dest += 4; - length -= 4; - } - - while (length) { - *dest++ = value; - length--; - } -} - -// dest = color + (dest * alpha) -inline static void copy_helper_sse2(uint32_t* dest, int length, - uint32_t color, uint32_t alpha) -{ - const __m128i v_color = _mm_set1_epi32(color); - const __m128i v_a = _mm_set1_epi16(alpha); - - LOOP_ALIGNED_U1_A4(dest, length, - { /* UOP */ - *dest = color + BYTE_MUL(*dest, alpha); - dest++; - length--; - }, - { /* A4OP */ - __m128i v_dest = _mm_load_si128((__m128i*)dest); - - v_dest = v4_byte_mul_sse2(v_dest, v_a); - v_dest = _mm_add_epi32(v_dest, v_color); - - _mm_store_si128((__m128i*)dest, v_dest); - - dest += 4; - length -= 4; - }) -} - -static void color_Source(uint32_t* dest, int length, uint32_t color, - uint32_t const_alpha) -{ - if (const_alpha == 255) { - memfill32(dest, color, length); - } else { - int ialpha; - - ialpha = 255 - const_alpha; - color = BYTE_MUL(color, const_alpha); - copy_helper_sse2(dest, length, color, ialpha); - } -} - -static void color_SourceOver(uint32_t* dest, int length, - uint32_t color, - uint32_t const_alpha) -{ - int ialpha; - - if (const_alpha != 255) color = BYTE_MUL(color, const_alpha); - ialpha = 255 - vAlpha(color); - copy_helper_sse2(dest, length, color, ialpha); -} - -static void src_Source(uint32_t* dest, int length, const uint32_t* src, - uint32_t const_alpha) -{ - int ialpha; - if (const_alpha == 255) { - memcpy(dest, src, length * sizeof(uint32_t)); - } else { - ialpha = 255 - const_alpha; - __m128i v_alpha = _mm_set1_epi32(const_alpha); - - LOOP_ALIGNED_U1_A4(dest, length, - { /* UOP */ - *dest = interpolate_pixel(*src, const_alpha, - *dest, ialpha); - dest++; - src++; - length--; - }, - {/* A4OP */ - V4_FETCH_SRC_DEST V4_COMP_OP_SRC V4_STORE_DEST - V4_SRC_DEST_LEN_INC}) - } -} - -void RenderFuncTable::sse() -{ - updateColor(BlendMode::Src , color_Source); - updateColor(BlendMode::SrcOver , color_SourceOver); - - updateSrc(BlendMode::Src , src_Source); -} - -#endif diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/velapsedtimer.cpp b/libfenrir/src/main/jni/animation/rlottie/src/vector/velapsedtimer.cpp deleted file mode 100644 index 7e054b1ae..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/velapsedtimer.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#include "velapsedtimer.h" - -void VElapsedTimer::start() -{ - clock = std::chrono::high_resolution_clock::now(); - m_valid = true; -} - -double VElapsedTimer::restart() -{ - double elapsedTime = elapsed(); - start(); - return elapsedTime; -} - -double VElapsedTimer::elapsed() const -{ - if (!isValid()) return 0; - return std::chrono::duration( - std::chrono::high_resolution_clock::now() - clock) - .count(); -} - -bool VElapsedTimer::hasExpired(double time) const -{ - double elapsedTime = elapsed(); - if (elapsedTime > time) return true; - return false; -} diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/velapsedtimer.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/velapsedtimer.h deleted file mode 100644 index 4b0f21380..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/velapsedtimer.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef VELAPSEDTIMER_H -#define VELAPSEDTIMER_H - -#include -#include "vglobal.h" - -class VElapsedTimer { -public: - double elapsed() const; - bool hasExpired(double millsec) const; - void start(); - double restart(); - inline bool isValid() const { return m_valid; } - -private: - std::chrono::high_resolution_clock::time_point clock; - bool m_valid{false}; -}; -#endif // VELAPSEDTIMER_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vglobal.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/vglobal.h deleted file mode 100644 index f4be3157d..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vglobal.h +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef VGLOBAL_H -#define VGLOBAL_H - -#include -#include -#include -#include -#include - - -#if !defined(V_NAMESPACE) - -#define V_USE_NAMESPACE -#define V_BEGIN_NAMESPACE -#define V_END_NAMESPACE - -#else /* user namespace */ - -#define V_USE_NAMESPACE using namespace ::V_NAMESPACE; -#define V_BEGIN_NAMESPACE namespace V_NAMESPACE { -#define V_END_NAMESPACE } - -#endif - -#ifndef __has_attribute -# define __has_attribute(x) 0 -#endif /* !__has_attribute */ - -#if __has_attribute(unused) -# define V_UNUSED __attribute__((__unused__)) -#else -# define V_UNUSED -#endif /* V_UNUSED */ - -#if __has_attribute(warn_unused_result) -# define V_REQUIRED_RESULT __attribute__((__warn_unused_result__)) -#else -# define V_REQUIRED_RESULT -#endif /* V_REQUIRED_RESULT */ - -#define V_CONSTEXPR constexpr -#define V_NOTHROW noexcept - -#include "vdebug.h" - -#if __GNUC__ >= 7 -#define VECTOR_FALLTHROUGH __attribute__ ((fallthrough)); -#else -#define VECTOR_FALLTHROUGH -#endif - -#if defined(LOTTIE_THREAD_SUPPORT) || defined(LOTTIE_THREAD_SAFE) -#define vthread_local thread_local -#else -#define vthread_local -#endif - -#if defined(_MSC_VER) - #define V_ALWAYS_INLINE __forceinline -#else - #define V_ALWAYS_INLINE __attribute__((always_inline)) -#endif - -template -V_CONSTEXPR inline const T &vMin(const T &a, const T &b) -{ - return (a < b) ? a : b; -} -template -V_CONSTEXPR inline const T &vMax(const T &a, const T &b) -{ - return (a < b) ? b : a; -} - -static const double EPSILON_DOUBLE = 0.000000000001f; -static const float EPSILON_FLOAT = 0.000001f; - -static inline bool vCompare(float p1, float p2) -{ - return (std::abs(p1 - p2) < EPSILON_FLOAT); -} - -static inline bool vIsZero(float f) -{ - return (std::abs(f) <= EPSILON_FLOAT); -} - -static inline bool vIsZero(double f) -{ - return (std::abs(f) <= EPSILON_DOUBLE); -} - -class vFlagHelper { - int i; - -public: - explicit constexpr inline vFlagHelper(int ai) noexcept : i(ai) {} - constexpr inline operator int() const noexcept { return i; } - - explicit constexpr inline vFlagHelper(uint32_t ai) noexcept : i(int(ai)) {} - explicit constexpr inline vFlagHelper(short ai) noexcept : i(int(ai)) {} - explicit constexpr inline vFlagHelper(uint16_t ai) noexcept - : i(int(uint32_t(ai))) - { - } - constexpr inline operator uint32_t() const noexcept { return uint32_t(i); } -}; - -template -class vFlag { -public: - static_assert( - (sizeof(Enum) <= sizeof(int)), - "vFlag only supports int as storage so bigger type will overflow"); - static_assert((std::is_enum::value), - "vFlag is only usable on enumeration types."); - - using Int = typename std::conditional< - std::is_unsigned::type>::value, - uint32_t, signed int>::type; - - using enum_type = Enum; - // compiler-generated copy/move ctor/assignment operators are fine! - - vFlag() = default; - constexpr vFlag(Enum f) noexcept : i(Int(f)) {} - explicit constexpr vFlag(vFlagHelper f) noexcept : i(f) {} - - inline vFlag &operator&=(int mask) noexcept - { - i &= mask; - return *this; - } - inline vFlag &operator&=(uint32_t mask) noexcept - { - i &= mask; - return *this; - } - inline vFlag &operator&=(Enum mask) noexcept - { - i &= Int(mask); - return *this; - } - inline vFlag &operator|=(vFlag f) noexcept - { - i |= f.i; - return *this; - } - inline vFlag &operator|=(Enum f) noexcept - { - i |= Int(f); - return *this; - } - inline vFlag &operator^=(vFlag f) noexcept - { - i ^= f.i; - return *this; - } - inline vFlag &operator^=(Enum f) noexcept - { - i ^= Int(f); - return *this; - } - - constexpr inline operator Int() const noexcept { return i; } - - constexpr inline vFlag operator|(vFlag f) const - { - return vFlag(vFlagHelper(i | f.i)); - } - constexpr inline vFlag operator|(Enum f) const noexcept - { - return vFlag(vFlagHelper(i | Int(f))); - } - constexpr inline vFlag operator^(vFlag f) const noexcept - { - return vFlag(vFlagHelper(i ^ f.i)); - } - constexpr inline vFlag operator^(Enum f) const noexcept - { - return vFlag(vFlagHelper(i ^ Int(f))); - } - constexpr inline vFlag operator&(int mask) const noexcept - { - return vFlag(vFlagHelper(i & mask)); - } - constexpr inline vFlag operator&(uint32_t mask) const noexcept - { - return vFlag(vFlagHelper(i & mask)); - } - constexpr inline vFlag operator&(Enum f) const noexcept - { - return vFlag(vFlagHelper(i & Int(f))); - } - constexpr inline vFlag operator~() const noexcept - { - return vFlag(vFlagHelper(~i)); - } - - constexpr inline bool operator!() const noexcept { return !i; } - - constexpr inline bool testFlag(Enum f) const noexcept - { - return (i & Int(f)) == Int(f) && (Int(f) != 0 || i == Int(f)); - } - inline vFlag &setFlag(Enum f, bool on = true) noexcept - { - return on ? (*this |= f) : (*this &= ~f); - } - - Int i{0}; -}; - -class VColor { -public: - VColor() = default; - explicit VColor(uint8_t red, uint8_t green, uint8_t blue, - uint8_t alpha = 255) noexcept - : a(alpha), r(red), g(green), b(blue) - { - } - inline uint8_t red() const noexcept { return r; } - inline uint8_t green() const noexcept { return g; } - inline uint8_t blue() const noexcept { return b; } - inline uint8_t alpha() const noexcept { return a; } - inline void setRed(uint8_t red) noexcept { r = red; } - inline void setGreen(uint8_t green) noexcept { g = green; } - inline void setBlue(uint8_t blue) noexcept { b = blue; } - inline void setAlpha(uint8_t alpha) noexcept { a = alpha; } - inline bool isOpaque() const { return a == 255; } - inline bool isTransparent() const { return a == 0; } - inline bool operator==(const VColor &o) const - { - return ((a == o.a) && (r == o.r) && (g == o.g) && (b == o.b)); - } - uint32_t premulARGB() const - { - int pr = (r * a) / 255; - int pg = (g * a) / 255; - int pb = (b * a) / 255; - return uint32_t((a << 24) | (pr << 16) | (pg << 8) | (pb)); - } - - uint32_t premulARGB(float opacity) const - { - int alpha = int(a * opacity); - int pr = (r * alpha) / 255; - int pg = (g * alpha) / 255; - int pb = (b * alpha) / 255; - return uint32_t((alpha << 24) | (pr << 16) | (pg << 8) | (pb)); - } - -public: - uint8_t a{0}; - uint8_t r{0}; - uint8_t g{0}; - uint8_t b{0}; -}; - -enum class FillRule: unsigned char { EvenOdd, Winding }; -enum class JoinStyle: unsigned char { Miter, Bevel, Round }; -enum class CapStyle: unsigned char { Flat, Square, Round }; - -enum class BlendMode { - Src, - SrcOver, - DestIn, - DestOut, - Last, -}; - -#ifndef V_CONSTRUCTOR_FUNCTION -#define V_CONSTRUCTOR_FUNCTION0(AFUNC) \ - namespace { \ - static const struct AFUNC##_ctor_class_ { \ - inline AFUNC##_ctor_class_() { AFUNC(); } \ - } AFUNC##_ctor_instance_; \ - } - -#define V_CONSTRUCTOR_FUNCTION(AFUNC) V_CONSTRUCTOR_FUNCTION0(AFUNC) -#endif - -#endif // VGLOBAL_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vimageloader.cpp b/libfenrir/src/main/jni/animation/rlottie/src/vector/vimageloader.cpp deleted file mode 100644 index ac34a34ef..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vimageloader.cpp +++ /dev/null @@ -1,233 +0,0 @@ -#include "vimageloader.h" -#include "config.h" -#include "vdebug.h" -#include - -#ifdef _WIN32 -# include -#else -# include -#endif // _WIN32 - -using lottie_image_load_f = unsigned char *(*)(const char *filename, int *x, - int *y, int *comp, int req_comp); -using lottie_image_load_data_f = unsigned char *(*)(const char *data, int len, - int *x, int *y, int *comp, - int req_comp); -using lottie_image_free_f = void (*)(unsigned char *); - -#ifdef __cplusplus -extern "C" { -#endif - -extern unsigned char *lottie_image_load(char const *filename, int *x, int *y, - int *comp, int req_comp); -extern unsigned char *lottie_image_load_from_data(const char *imageData, - int len, int *x, int *y, - int *comp, int req_comp); -extern void lottie_image_free(unsigned char *data); - -#ifdef __cplusplus -} -#endif - -struct VImageLoader::Impl { - lottie_image_load_f imageLoad{nullptr}; - lottie_image_free_f imageFree{nullptr}; - lottie_image_load_data_f imageFromData{nullptr}; - -#ifdef LOTTIE_IMAGE_MODULE_SUPPORT -# ifdef _WIN32 - HMODULE dl_handle{nullptr}; - bool moduleLoad() - { - dl_handle = LoadLibraryA(LOTTIE_IMAGE_MODULE_PLUGIN); - return (dl_handle == nullptr); - } - void moduleFree() - { - if (dl_handle) FreeLibrary(dl_handle); - } - void init() - { - imageLoad = reinterpret_cast( - GetProcAddress(dl_handle, "lottie_image_load")); - imageFree = reinterpret_cast( - GetProcAddress(dl_handle, "lottie_image_free")); - imageFromData = reinterpret_cast( - GetProcAddress(dl_handle, "lottie_image_load_from_data")); - } -# else // _WIN32 - void *dl_handle{nullptr}; - void init() - { - imageLoad = reinterpret_cast( - dlsym(dl_handle, "lottie_image_load")); - imageFree = reinterpret_cast( - dlsym(dl_handle, "lottie_image_free")); - imageFromData = reinterpret_cast( - dlsym(dl_handle, "lottie_image_load_from_data")); - } - - void moduleFree() - { - if (dl_handle) dlclose(dl_handle); - } - bool moduleLoad() - { - dl_handle = dlopen(LOTTIE_IMAGE_MODULE_PLUGIN, RTLD_LAZY); - return (dl_handle == nullptr); - } -# endif // _WIN32 -#else // LOTTIE_IMAGE_MODULE_SUPPORT - void init() - { - imageLoad = lottie_image_load; - imageFree = lottie_image_free; - imageFromData = lottie_image_load_from_data; - } - void moduleFree() {} - static bool moduleLoad() { return false; } -#endif // LOTTIE_IMAGE_MODULE_SUPPORT - - Impl() - { - if (moduleLoad()) { - vWarning << "Failed to dlopen librlottie-image-loader library"; - return; - } - - init(); - - if (!imageLoad) - vWarning << "Failed to find symbol lottie_image_load in " - "librlottie-image-loader library"; - - if (!imageFree) - vWarning << "Failed to find symbol lottie_image_free in " - "librlottie-image-loader library"; - - if (!imageFromData) - vWarning << "Failed to find symbol lottie_image_load_data in " - "librlottie-image-loader library"; - } - - ~Impl() { moduleFree(); } - - VBitmap createBitmap(unsigned char *data, int width, int height, - int channel) const - { - // premultiply alpha - if (channel == 4) - convertToBGRAPremul(data, width, height); - else - convertToBGRA(data, width, height); - - // create a bitmap of same size. - VBitmap result = - VBitmap(width, height, VBitmap::Format::ARGB32_Premultiplied); - - // copy the data to bitmap buffer - memcpy(result.data(), data, width * height * 4); - - // free the image data - imageFree(data); - - return result; - } - - VBitmap load(const char *fileName) const - { - if (!imageLoad) return VBitmap(); - - int width, height, n; - unsigned char *data = imageLoad(fileName, &width, &height, &n, 4); - - if (!data) { - return VBitmap(); - } - - return createBitmap(data, width, height, n); - } - - VBitmap load(const char *imageData, size_t len) const - { - if (!imageFromData) return VBitmap(); - - int width, height, n; - unsigned char *data = - imageFromData(imageData, static_cast(len), &width, &height, &n, 4); - - if (!data) { - return VBitmap(); - } - - return createBitmap(data, width, height, n); - } - /* - * convert from RGBA to BGRA and premultiply - */ - static void convertToBGRAPremul(unsigned char *bits, int width, int height) - { - int pixelCount = width * height; - unsigned char *pix = bits; - for (int i = 0; i < pixelCount; i++) { - unsigned char r = pix[0]; - unsigned char g = pix[1]; - unsigned char b = pix[2]; - unsigned char a = pix[3]; - - r = (r * a) / 255; - g = (g * a) / 255; - b = (b * a) / 255; - -#ifdef BIG_ENDIAN_COLORS - pix[0] = r; - pix[1] = g; - pix[2] = b; - pix[3] = a; -#else - pix[0] = b; - pix[1] = g; - pix[2] = r; - pix[3] = a; -#endif - - pix += 4; - } - } - /* - * convert from RGBA to BGRA - */ - static void convertToBGRA(unsigned char *bits, int width, int height) - { - int pixelCount = width * height; - unsigned char *pix = bits; - for (int i = 0; i < pixelCount; i++) { - unsigned char r = pix[0]; - unsigned char b = pix[2]; -#ifdef BIG_ENDIAN_COLORS - pix[0] = r; - pix[2] = b; -#else - pix[0] = b; - pix[2] = r; -#endif - pix += 4; - } - } -}; - -VImageLoader::VImageLoader() : mImpl(std::make_unique()) {} - -VImageLoader::~VImageLoader() = default; - -VBitmap VImageLoader::load(const char *fileName) -{ - return mImpl->load(fileName); -} - -VBitmap VImageLoader::load(const char *data, size_t len) -{ - return mImpl->load(data, int(len)); -} diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vimageloader.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/vimageloader.h deleted file mode 100644 index 4d96a7dc0..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vimageloader.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef VIMAGELOADER_H -#define VIMAGELOADER_H - -#include - -#include "vbitmap.h" - -class VImageLoader -{ -public: - static VImageLoader& instance() - { - static VImageLoader singleton; - return singleton; - } - - VBitmap load(const char *fileName); - VBitmap load(const char *data, size_t len); - ~VImageLoader(); -private: - VImageLoader(); - struct Impl; - std::unique_ptr mImpl; -}; - -#endif // VIMAGELOADER_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vinterpolator.cpp b/libfenrir/src/main/jni/animation/rlottie/src/vector/vinterpolator.cpp deleted file mode 100644 index fca078467..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vinterpolator.cpp +++ /dev/null @@ -1,124 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "vinterpolator.h" -#include - -V_BEGIN_NAMESPACE - -#define NEWTON_ITERATIONS 4 -#define NEWTON_MIN_SLOPE 0.02 -#define SUBDIVISION_PRECISION 0.0000001 -#define SUBDIVISION_MAX_ITERATIONS 10 - -const float VInterpolator::kSampleStepSize = - 1.0f / float(VInterpolator::kSplineTableSize - 1); - -void VInterpolator::init(float aX1, float aY1, float aX2, float aY2) -{ - mX1 = aX1; - mY1 = aY1; - mX2 = aX2; - mY2 = aY2; - - if (mX1 != mY1 || mX2 != mY2) CalcSampleValues(); -} - -/*static*/ float VInterpolator::CalcBezier(float aT, float aA1, float aA2) -{ - // use Horner's scheme to evaluate the Bezier polynomial - return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; -} - -void VInterpolator::CalcSampleValues() -{ - for (int i = 0; i < kSplineTableSize; ++i) { - mSampleValues[i] = CalcBezier(float(i) * kSampleStepSize, mX1, mX2); - } -} - -float VInterpolator::GetSlope(float aT, float aA1, float aA2) -{ - return 3.0f * A(aA1, aA2) * aT * aT + 2.0f * B(aA1, aA2) * aT + C(aA1); -} - -float VInterpolator::value(float aX) const -{ - if (mX1 == mY1 && mX2 == mY2) return aX; - - return CalcBezier(GetTForX(aX), mY1, mY2); -} - -float VInterpolator::GetTForX(float aX) const -{ - // Find interval where t lies - float intervalStart = 0.0; - const float* currentSample = &mSampleValues[1]; - const float* const lastSample = &mSampleValues[kSplineTableSize - 1]; - for (; currentSample != lastSample && *currentSample <= aX; - ++currentSample) { - intervalStart += kSampleStepSize; - } - --currentSample; // t now lies between *currentSample and *currentSample+1 - - // Interpolate to provide an initial guess for t - float dist = - (aX - *currentSample) / (*(currentSample + 1) - *currentSample); - float guessForT = intervalStart + dist * kSampleStepSize; - - // Check the slope to see what strategy to use. If the slope is too small - // Newton-Raphson iteration won't converge on a root so we use bisection - // instead. - float initialSlope = GetSlope(guessForT, mX1, mX2); - if (initialSlope >= NEWTON_MIN_SLOPE) { - return NewtonRaphsonIterate(aX, guessForT); - } else if (initialSlope == 0.0) { - return guessForT; - } else { - return BinarySubdivide(aX, intervalStart, - intervalStart + kSampleStepSize); - } -} - -float VInterpolator::NewtonRaphsonIterate(float aX, float aGuessT) const -{ - // Refine guess with Newton-Raphson iteration - for (int i = 0; i < NEWTON_ITERATIONS; ++i) { - // We're trying to find where f(t) = aX, - // so we're actually looking for a root for: CalcBezier(t) - aX - float currentX = CalcBezier(aGuessT, mX1, mX2) - aX; - float currentSlope = GetSlope(aGuessT, mX1, mX2); - - if (currentSlope == 0.0) return aGuessT; - - aGuessT -= currentX / currentSlope; - } - - return aGuessT; -} - -float VInterpolator::BinarySubdivide(float aX, float aA, float aB) const -{ - float currentX; - float currentT; - int i = 0; - - do { - currentT = aA + (aB - aA) / 2.0f; - currentX = CalcBezier(currentT, mX1, mX2) - aX; - - if (currentX > 0.0) { - aB = currentT; - } else { - aA = currentT; - } - } while (fabs(currentX) > SUBDIVISION_PRECISION && - ++i < SUBDIVISION_MAX_ITERATIONS); - - return currentT; -} - -V_END_NAMESPACE diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vinterpolator.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/vinterpolator.h deleted file mode 100644 index beee34a64..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vinterpolator.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef VINTERPOLATOR_H -#define VINTERPOLATOR_H - -#include "vpoint.h" - -V_BEGIN_NAMESPACE - -class VInterpolator { -public: - VInterpolator() - { /* caller must call Init later */ - } - - VInterpolator(float aX1, float aY1, float aX2, float aY2) - { - init(aX1, aY1, aX2, aY2); - } - - VInterpolator(VPointF pt1, VPointF pt2) - { - init(pt1.x(), pt1.y(), pt2.x(), pt2.y()); - } - - void init(float aX1, float aY1, float aX2, float aY2); - - float value(float aX) const; - -private: - void CalcSampleValues(); - - /** - * Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2. - */ - static float CalcBezier(float aT, float aA1, float aA2); - - /** - * Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2. - */ - static float GetSlope(float aT, float aA1, float aA2); - - float GetTForX(float aX) const; - - float NewtonRaphsonIterate(float aX, float aGuessT) const; - - float BinarySubdivide(float aX, float aA, float aB) const; - - static float A(float aA1, float aA2) { return 1.0f - 3.0f * aA2 + 3.0f * aA1; } - - static float B(float aA1, float aA2) { return 3.0f * aA2 - 6.0f * aA1; } - - static float C(float aA1) { return 3.0f * aA1; } - - float mX1; - float mY1; - float mX2; - float mY2; - enum { kSplineTableSize = 11 }; - float mSampleValues[kSplineTableSize]; - static const float kSampleStepSize; -}; - -V_END_NAMESPACE - -#endif // VINTERPOLATOR_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vline.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/vline.h deleted file mode 100644 index ee8b4a567..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vline.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef VLINE_H -#define VLINE_H - -#include "vglobal.h" -#include "vpoint.h" - -V_BEGIN_NAMESPACE - -class VLine { -public: - VLine() = default; - VLine(float x1, float y1, float x2, float y2) - : mX1(x1), mY1(y1), mX2(x2), mY2(y2) - { - } - VLine(const VPointF &p1, const VPointF &p2) - : mX1(p1.x()), mY1(p1.y()), mX2(p2.x()), mY2(p2.y()) - { - } - float length() const { return length(mX1, mY1, mX2, mY2);} - void splitAtLength(float length, VLine &left, VLine &right) const; - VPointF p1() const { return {mX1, mY1}; } - VPointF p2() const { return {mX2, mY2}; } - float angle() const; - static float length(float x1, float y1, float x2, float y2); - -private: - float mX1{0}; - float mY1{0}; - float mX2{0}; - float mY2{0}; -}; - -inline float VLine::angle() const -{ - static constexpr float K_PI = 3.141592f; - const float dx = mX2 - mX1; - const float dy = mY2 - mY1; - - const float theta = std::atan2(dy, dx) * 180.0f / K_PI; - return theta; -} - -// approximate sqrt(x*x + y*y) using alpha max plus beta min algorithm. -// With alpha = 1, beta = 3/8, giving results with the largest error less -// than 7% compared to the exact value. -inline V_ALWAYS_INLINE float VLine::length(float x1, float y1, float x2, float y2) -{ - float x = x2 - x1; - float y = y2 - y1; - - x = x < 0 ? -x : x; - y = y < 0 ? -y : y; - - return (x > y ? x + 0.375f * y : y + 0.375f * x); -} - -inline void VLine::splitAtLength(float lengthAt, VLine &left, VLine &right) const -{ - float len = length(); - float dx = ((mX2 - mX1) / len) * lengthAt; - float dy = ((mY2 - mY1) / len) * lengthAt; - - left.mX1 = mX1; - left.mY1 = mY1; - left.mX2 = left.mX1 + dx; - left.mY2 = left.mY1 + dy; - - right.mX1 = left.mX2; - right.mY1 = left.mY2; - right.mX2 = mX2; - right.mY2 = mY2; -} - -#endif //VLINE_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vmatrix.cpp b/libfenrir/src/main/jni/animation/rlottie/src/vector/vmatrix.cpp deleted file mode 100644 index 7efed61bb..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vmatrix.cpp +++ /dev/null @@ -1,684 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "vmatrix.h" -#include -#include -#include - -V_BEGIN_NAMESPACE - -/* m11 m21 mtx - * m12 m22 mty - * m13 m23 m33 - */ - -inline float VMatrix::determinant() const -{ - return m11 * (m33 * m22 - mty * m23) - m21 * (m33 * m12 - mty * m13) + - mtx * (m23 * m12 - m22 * m13); -} - -bool VMatrix::isAffine() const -{ - return type() < MatrixType::Project; -} - -bool VMatrix::isIdentity() const -{ - return type() == MatrixType::None; -} - -bool VMatrix::isInvertible() const -{ - return !vIsZero(determinant()); -} - -bool VMatrix::isScaling() const -{ - return type() >= MatrixType::Scale; -} -bool VMatrix::isRotating() const -{ - return type() >= MatrixType::Rotate; -} - -bool VMatrix::isTranslating() const -{ - return type() >= MatrixType::Translate; -} - -VMatrix &VMatrix::operator*=(float num) -{ - if (num == 1.) return *this; - - m11 *= num; - m12 *= num; - m13 *= num; - m21 *= num; - m22 *= num; - m23 *= num; - mtx *= num; - mty *= num; - m33 *= num; - if (dirty < MatrixType::Scale) dirty = MatrixType::Scale; - - return *this; -} - -VMatrix &VMatrix::operator/=(float div) -{ - if (div == 0) return *this; - - div = 1 / div; - return operator*=(div); -} - -VMatrix::MatrixType VMatrix::type() const -{ - if (dirty == MatrixType::None || dirty < mType) return mType; - - switch (dirty) { - case MatrixType::Project: - if (!vIsZero(m13) || !vIsZero(m23) || !vIsZero(m33 - 1)) { - mType = MatrixType::Project; - break; - } - VECTOR_FALLTHROUGH - case MatrixType::Shear: - case MatrixType::Rotate: - if (!vIsZero(m12) || !vIsZero(m21)) { - const float dot = m11 * m12 + m21 * m22; - if (vIsZero(dot)) - mType = MatrixType::Rotate; - else - mType = MatrixType::Shear; - break; - } - VECTOR_FALLTHROUGH - case MatrixType::Scale: - if (!vIsZero(m11 - 1) || !vIsZero(m22 - 1)) { - mType = MatrixType::Scale; - break; - } - VECTOR_FALLTHROUGH - case MatrixType::Translate: - if (!vIsZero(mtx) || !vIsZero(mty)) { - mType = MatrixType::Translate; - break; - } - VECTOR_FALLTHROUGH - case MatrixType::None: - mType = MatrixType::None; - break; - } - - dirty = MatrixType::None; - return mType; -} - -VMatrix &VMatrix::translate(float dx, float dy) -{ - if (dx == 0 && dy == 0) return *this; - - switch (type()) { - case MatrixType::None: - mtx = dx; - mty = dy; - break; - case MatrixType::Translate: - mtx += dx; - mty += dy; - break; - case MatrixType::Scale: - mtx += dx * m11; - mty += dy * m22; - break; - case MatrixType::Project: - m33 += dx * m13 + dy * m23; - VECTOR_FALLTHROUGH - case MatrixType::Shear: - case MatrixType::Rotate: - mtx += dx * m11 + dy * m21; - mty += dy * m22 + dx * m12; - break; - } - if (dirty < MatrixType::Translate) dirty = MatrixType::Translate; - return *this; -} - -VMatrix &VMatrix::scale(float sx, float sy) -{ - if (sx == 1 && sy == 1) return *this; - - switch (type()) { - case MatrixType::None: - case MatrixType::Translate: - m11 = sx; - m22 = sy; - break; - case MatrixType::Project: - m13 *= sx; - m23 *= sy; - VECTOR_FALLTHROUGH - case MatrixType::Rotate: - case MatrixType::Shear: - m12 *= sx; - m21 *= sy; - VECTOR_FALLTHROUGH - case MatrixType::Scale: - m11 *= sx; - m22 *= sy; - break; - } - if (dirty < MatrixType::Scale) dirty = MatrixType::Scale; - return *this; -} - -VMatrix &VMatrix::shear(float sh, float sv) -{ - if (sh == 0 && sv == 0) return *this; - - switch (type()) { - case MatrixType::None: - case MatrixType::Translate: - m12 = sv; - m21 = sh; - break; - case MatrixType::Scale: - m12 = sv * m22; - m21 = sh * m11; - break; - case MatrixType::Project: { - float tm13 = sv * m23; - float tm23 = sh * m13; - m13 += tm13; - m23 += tm23; - VECTOR_FALLTHROUGH - } - case MatrixType::Rotate: - case MatrixType::Shear: { - float tm11 = sv * m21; - float tm22 = sh * m12; - float tm12 = sv * m22; - float tm21 = sh * m11; - m11 += tm11; - m12 += tm12; - m21 += tm21; - m22 += tm22; - break; - } - } - if (dirty < MatrixType::Shear) dirty = MatrixType::Shear; - return *this; -} - -static const float deg2rad = float(0.017453292519943295769); // pi/180 -static const float inv_dist_to_plane = 1. / 1024.; - -VMatrix &VMatrix::rotate(float a, Axis axis) -{ - if (a == 0) return *this; - - float sina = 0; - float cosa = 0; - if (a == 90. || a == -270.) - sina = 1.; - else if (a == 270. || a == -90.) - sina = -1.; - else if (a == 180.) - cosa = -1.; - else { - float b = deg2rad * a; // convert to radians - sina = std::sin(b); // fast and convenient - cosa = std::cos(b); - } - - if (axis == Axis::Z) { - switch (type()) { - case MatrixType::None: - case MatrixType::Translate: - m11 = cosa; - m12 = sina; - m21 = -sina; - m22 = cosa; - break; - case MatrixType::Scale: { - float tm11 = cosa * m11; - float tm12 = sina * m22; - float tm21 = -sina * m11; - float tm22 = cosa * m22; - m11 = tm11; - m12 = tm12; - m21 = tm21; - m22 = tm22; - break; - } - case MatrixType::Project: { - float tm13 = cosa * m13 + sina * m23; - float tm23 = -sina * m13 + cosa * m23; - m13 = tm13; - m23 = tm23; - VECTOR_FALLTHROUGH - } - case MatrixType::Rotate: - case MatrixType::Shear: { - float tm11 = cosa * m11 + sina * m21; - float tm12 = cosa * m12 + sina * m22; - float tm21 = -sina * m11 + cosa * m21; - float tm22 = -sina * m12 + cosa * m22; - m11 = tm11; - m12 = tm12; - m21 = tm21; - m22 = tm22; - break; - } - } - if (dirty < MatrixType::Rotate) dirty = MatrixType::Rotate; - } else { - VMatrix result; - if (axis == Axis::Y) { - result.m11 = cosa; - result.m13 = -sina * inv_dist_to_plane; - } else { - result.m22 = cosa; - result.m23 = -sina * inv_dist_to_plane; - } - result.mType = MatrixType::Project; - *this = result * *this; - } - - return *this; -} - -VMatrix VMatrix::operator*(const VMatrix &m) const -{ - const MatrixType otherType = m.type(); - if (otherType == MatrixType::None) return *this; - - const MatrixType thisType = type(); - if (thisType == MatrixType::None) return m; - - VMatrix t; - MatrixType type = vMax(thisType, otherType); - switch (type) { - case MatrixType::None: - break; - case MatrixType::Translate: - t.mtx = mtx + m.mtx; - t.mty += mty + m.mty; - break; - case MatrixType::Scale: { - float m11v = m11 * m.m11; - float m22v = m22 * m.m22; - - float m31v = mtx * m.m11 + m.mtx; - float m32v = mty * m.m22 + m.mty; - - t.m11 = m11v; - t.m22 = m22v; - t.mtx = m31v; - t.mty = m32v; - break; - } - case MatrixType::Rotate: - case MatrixType::Shear: { - float m11v = m11 * m.m11 + m12 * m.m21; - float m12v = m11 * m.m12 + m12 * m.m22; - - float m21v = m21 * m.m11 + m22 * m.m21; - float m22v = m21 * m.m12 + m22 * m.m22; - - float m31v = mtx * m.m11 + mty * m.m21 + m.mtx; - float m32v = mtx * m.m12 + mty * m.m22 + m.mty; - - t.m11 = m11v; - t.m12 = m12v; - t.m21 = m21v; - t.m22 = m22v; - t.mtx = m31v; - t.mty = m32v; - break; - } - case MatrixType::Project: { - float m11v = m11 * m.m11 + m12 * m.m21 + m13 * m.mtx; - float m12v = m11 * m.m12 + m12 * m.m22 + m13 * m.mty; - float m13v = m11 * m.m13 + m12 * m.m23 + m13 * m.m33; - - float m21v = m21 * m.m11 + m22 * m.m21 + m23 * m.mtx; - float m22v = m21 * m.m12 + m22 * m.m22 + m23 * m.mty; - float m23v = m21 * m.m13 + m22 * m.m23 + m23 * m.m33; - - float m31v = mtx * m.m11 + mty * m.m21 + m33 * m.mtx; - float m32v = mtx * m.m12 + mty * m.m22 + m33 * m.mty; - float m33v = mtx * m.m13 + mty * m.m23 + m33 * m.m33; - - t.m11 = m11v; - t.m12 = m12v; - t.m13 = m13v; - t.m21 = m21v; - t.m22 = m22v; - t.m23 = m23v; - t.mtx = m31v; - t.mty = m32v; - t.m33 = m33v; - } - } - - t.dirty = type; - t.mType = type; - - return t; -} - -VMatrix &VMatrix::operator*=(const VMatrix &o) -{ - const MatrixType otherType = o.type(); - if (otherType == MatrixType::None) return *this; - - const MatrixType thisType = type(); - if (thisType == MatrixType::None) return operator=(o); - - MatrixType t = vMax(thisType, otherType); - switch (t) { - case MatrixType::None: - break; - case MatrixType::Translate: - mtx += o.mtx; - mty += o.mty; - break; - case MatrixType::Scale: { - float m11v = m11 * o.m11; - float m22v = m22 * o.m22; - - float m31v = mtx * o.m11 + o.mtx; - float m32v = mty * o.m22 + o.mty; - - m11 = m11v; - m22 = m22v; - mtx = m31v; - mty = m32v; - break; - } - case MatrixType::Rotate: - case MatrixType::Shear: { - float m11v = m11 * o.m11 + m12 * o.m21; - float m12v = m11 * o.m12 + m12 * o.m22; - - float m21v = m21 * o.m11 + m22 * o.m21; - float m22v = m21 * o.m12 + m22 * o.m22; - - float m31v = mtx * o.m11 + mty * o.m21 + o.mtx; - float m32v = mtx * o.m12 + mty * o.m22 + o.mty; - - m11 = m11v; - m12 = m12v; - m21 = m21v; - m22 = m22v; - mtx = m31v; - mty = m32v; - break; - } - case MatrixType::Project: { - float m11v = m11 * o.m11 + m12 * o.m21 + m13 * o.mtx; - float m12v = m11 * o.m12 + m12 * o.m22 + m13 * o.mty; - float m13v = m11 * o.m13 + m12 * o.m23 + m13 * o.m33; - - float m21v = m21 * o.m11 + m22 * o.m21 + m23 * o.mtx; - float m22v = m21 * o.m12 + m22 * o.m22 + m23 * o.mty; - float m23v = m21 * o.m13 + m22 * o.m23 + m23 * o.m33; - - float m31v = mtx * o.m11 + mty * o.m21 + m33 * o.mtx; - float m32v = mtx * o.m12 + mty * o.m22 + m33 * o.mty; - float m33v = mtx * o.m13 + mty * o.m23 + m33 * o.m33; - - m11 = m11v; - m12 = m12v; - m13 = m13v; - m21 = m21v; - m22 = m22v; - m23 = m23v; - mtx = m31v; - mty = m32v; - m33 = m33v; - } - } - - dirty = t; - mType = t; - - return *this; -} - -VMatrix VMatrix::adjoint() const -{ - float h11, h12, h13, h21, h22, h23, h31, h32, h33; - h11 = m22 * m33 - m23 * mty; - h21 = m23 * mtx - m21 * m33; - h31 = m21 * mty - m22 * mtx; - h12 = m13 * mty - m12 * m33; - h22 = m11 * m33 - m13 * mtx; - h32 = m12 * mtx - m11 * mty; - h13 = m12 * m23 - m13 * m22; - h23 = m13 * m21 - m11 * m23; - h33 = m11 * m22 - m12 * m21; - - VMatrix res; - res.m11 = h11; - res.m12 = h12; - res.m13 = h13; - res.m21 = h21; - res.m22 = h22; - res.m23 = h23; - res.mtx = h31; - res.mty = h32; - res.m33 = h33; - res.mType = MatrixType::None; - res.dirty = MatrixType::Project; - - return res; -} - -VMatrix VMatrix::inverted(bool *invertible) const -{ - VMatrix invert; - bool inv = true; - - switch (type()) { - case MatrixType::None: - break; - case MatrixType::Translate: - invert.mtx = -mtx; - invert.mty = -mty; - break; - case MatrixType::Scale: - inv = !vIsZero(m11); - inv &= !vIsZero(m22); - if (inv) { - invert.m11 = 1.0f / m11; - invert.m22 = 1.0f / m22; - invert.mtx = -mtx * invert.m11; - invert.mty = -mty * invert.m22; - } - break; - default: - // general case - float det = determinant(); - inv = !vIsZero(det); - if (inv) invert = (adjoint() /= det); - // TODO Test above line - break; - } - - if (invertible) *invertible = inv; - - if (inv) { - // inverting doesn't change the type - invert.mType = mType; - invert.dirty = dirty; - } - - return invert; -} - -bool VMatrix::operator==(const VMatrix &o) const -{ - return fuzzyCompare(o); -} - -bool VMatrix::operator!=(const VMatrix &o) const -{ - return !operator==(o); -} - -bool VMatrix::fuzzyCompare(const VMatrix &o) const -{ - return vCompare(m11, o.m11) && vCompare(m12, o.m12) && - vCompare(m21, o.m21) && vCompare(m22, o.m22) && - vCompare(mtx, o.mtx) && vCompare(mty, o.mty); -} - -#define V_NEAR_CLIP 0.000001f -#ifdef MAP -#undef MAP -#endif -#define MAP(x, y, nx, ny) \ - do { \ - float FX_ = x; \ - float FY_ = y; \ - switch (t) { \ - case MatrixType::None: \ - nx = FX_; \ - ny = FY_; \ - break; \ - case MatrixType::Translate: \ - nx = FX_ + mtx; \ - ny = FY_ + mty; \ - break; \ - case MatrixType::Scale: \ - nx = m11 * FX_ + mtx; \ - ny = m22 * FY_ + mty; \ - break; \ - case MatrixType::Rotate: \ - case MatrixType::Shear: \ - case MatrixType::Project: \ - nx = m11 * FX_ + m21 * FY_ + mtx; \ - ny = m12 * FX_ + m22 * FY_ + mty; \ - if (t == MatrixType::Project) { \ - float w = (m13 * FX_ + m23 * FY_ + m33); \ - if (w < V_NEAR_CLIP) w = V_NEAR_CLIP; \ - w = 1. / w; \ - nx *= w; \ - ny *= w; \ - } \ - } \ - } while (0) - -VRect VMatrix::map(const VRect &rect) const -{ - VMatrix::MatrixType t = type(); - if (t <= MatrixType::Translate) - return rect.translated(std::lround(mtx), std::lround(mty)); - - if (t <= MatrixType::Scale) { - int x = std::lround(m11 * rect.x() + mtx); - int y = std::lround(m22 * rect.y() + mty); - int w = std::lround(m11 * rect.width()); - int h = std::lround(m22 * rect.height()); - if (w < 0) { - w = -w; - x -= w; - } - if (h < 0) { - h = -h; - y -= h; - } - return {x, y, w, h}; - } else if (t < MatrixType::Project) { - // see mapToPolygon for explanations of the algorithm. - float x = 0, y = 0; - MAP(rect.left(), rect.top(), x, y); - float xmin = x; - float ymin = y; - float xmax = x; - float ymax = y; - MAP(rect.right() + 1, rect.top(), x, y); - xmin = vMin(xmin, x); - ymin = vMin(ymin, y); - xmax = vMax(xmax, x); - ymax = vMax(ymax, y); - MAP(rect.right() + 1, rect.bottom() + 1, x, y); - xmin = vMin(xmin, x); - ymin = vMin(ymin, y); - xmax = vMax(xmax, x); - ymax = vMax(ymax, y); - MAP(rect.left(), rect.bottom() + 1, x, y); - xmin = vMin(xmin, x); - ymin = vMin(ymin, y); - xmax = vMax(xmax, x); - ymax = vMax(ymax, y); - return VRect(std::lround(xmin), std::lround(ymin), - std::lround(xmax) - std::lround(xmin), - std::lround(ymax) - std::lround(ymin)); - } else { - // Not supported - assert(0); - return {}; - } -} - -VPointF VMatrix::map(const VPointF &p) const -{ - float fx = p.x(); - float fy = p.y(); - - float x = 0, y = 0; - - VMatrix::MatrixType t = type(); - switch (t) { - case MatrixType::None: - x = fx; - y = fy; - break; - case MatrixType::Translate: - x = fx + mtx; - y = fy + mty; - break; - case MatrixType::Scale: - x = m11 * fx + mtx; - y = m22 * fy + mty; - break; - case MatrixType::Rotate: - case MatrixType::Shear: - case MatrixType::Project: - x = m11 * fx + m21 * fy + mtx; - y = m12 * fx + m22 * fy + mty; - if (t == MatrixType::Project) { - float w = 1.0f / (m13 * fx + m23 * fy + m33); - x *= w; - y *= w; - } - } - return {x, y}; -} - -V_END_NAMESPACE diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vmatrix.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/vmatrix.h deleted file mode 100644 index 2de0069a3..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vmatrix.h +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef VMATRIX_H -#define VMATRIX_H -#include "vglobal.h" -#include "vpoint.h" -#include "vrect.h" - -V_BEGIN_NAMESPACE - -struct VMatrixData; -class VMatrix { -public: - enum class Axis { X, Y, Z }; - enum class MatrixType: unsigned char { - None = 0x00, - Translate = 0x01, - Scale = 0x02, - Rotate = 0x04, - Shear = 0x08, - Project = 0x10 - }; - VMatrix() = default; - bool isAffine() const; - bool isIdentity() const; - bool isInvertible() const; - bool isScaling() const; - bool isRotating() const; - bool isTranslating() const; - MatrixType type() const; - inline float determinant() const; - - float m_11() const { return m11;} - float m_12() const { return m12;} - float m_13() const { return m13;} - - float m_21() const { return m21;} - float m_22() const { return m22;} - float m_23() const { return m23;} - - float m_tx() const { return mtx;} - float m_ty() const { return mty;} - float m_33() const { return m33;} - - VMatrix &translate(VPointF pos) { return translate(pos.x(), pos.y()); } - VMatrix &translate(float dx, float dy); - VMatrix &scale(VPointF s) { return scale(s.x(), s.y()); } - VMatrix &scale(float sx, float sy); - VMatrix &shear(float sh, float sv); - VMatrix &rotate(float a, Axis axis = VMatrix::Axis::Z); - - VPointF map(const VPointF &p) const; - inline VPointF map(float x, float y) const; - VRect map(const VRect &r) const; - - V_REQUIRED_RESULT VMatrix inverted(bool *invertible = nullptr) const; - V_REQUIRED_RESULT VMatrix adjoint() const; - - VMatrix operator*(const VMatrix &o) const; - VMatrix & operator*=(const VMatrix &); - VMatrix & operator*=(float mul); - VMatrix & operator/=(float div); - bool operator==(const VMatrix &) const; - bool operator!=(const VMatrix &) const; - bool fuzzyCompare(const VMatrix &) const; - float scale() const; -private: - friend struct VSpanData; - float m11{1}, m12{0}, m13{0}; - float m21{0}, m22{1}, m23{0}; - float mtx{0}, mty{0}, m33{1}; - mutable MatrixType mType{MatrixType::None}; - mutable MatrixType dirty{MatrixType::None}; -}; - -inline float VMatrix::scale() const -{ - constexpr float SQRT_2 = 1.41421f; - VPointF p1(0, 0); - VPointF p2(SQRT_2, SQRT_2); - p1 = map(p1); - p2 = map(p2); - VPointF final = p2 - p1; - - return std::sqrt(final.x() * final.x() + final.y() * final.y()) / 2.0f; -} - -inline VPointF VMatrix::map(float x, float y) const -{ - return map(VPointF(x, y)); -} - -V_END_NAMESPACE - -#endif // VMATRIX_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vpainter.cpp b/libfenrir/src/main/jni/animation/rlottie/src/vector/vpainter.cpp deleted file mode 100644 index 948f26ae5..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vpainter.cpp +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "vpainter.h" -#include - - -V_BEGIN_NAMESPACE - - -void VPainter::drawRle(const VPoint &, const VRle &rle) -{ - if (rle.empty()) return; - // mSpanData.updateSpanFunc(); - - if (!mSpanData.mUnclippedBlendFunc) return; - - // do draw after applying clip. - rle.intersect(mSpanData.clipRect(), mSpanData.mUnclippedBlendFunc, - &mSpanData); -} - -void VPainter::drawRle(const VRle &rle, const VRle &clip) -{ - if (rle.empty() || clip.empty()) return; - - if (!mSpanData.mUnclippedBlendFunc) return; - - rle.intersect(clip, mSpanData.mUnclippedBlendFunc, &mSpanData); -} - -static void fillRect(const VRect &r, VSpanData *data) -{ - auto x1 = std::max(r.x(), 0); - auto x2 = std::min(r.x() + r.width(), data->mDrawableSize.width()); - auto y1 = std::max(r.y(), 0); - auto y2 = std::min(r.y() + r.height(), data->mDrawableSize.height()); - - if (x2 <= x1 || y2 <= y1) return; - - const int nspans = 256; - VRle::Span spans[nspans]; - - int y = y1; - while (y < y2) { - int n = std::min(nspans, y2 - y); - int i = 0; - while (i < n) { - spans[i].x = short(x1); - spans[i].len = uint16_t(x2 - x1); - spans[i].y = short(y + i); - spans[i].coverage = 255; - ++i; - } - - data->mUnclippedBlendFunc(n, spans, data); - y += n; - } -} - -void VPainter::drawBitmapUntransform(const VRect & target, - const VBitmap &bitmap, - const VRect & source, - uint8_t const_alpha) -{ - mSpanData.initTexture(&bitmap, const_alpha, source); - if (!mSpanData.mUnclippedBlendFunc) return; - - // update translation matrix for source texture. - mSpanData.dx = float(target.x() - source.x()); - mSpanData.dy = float(target.y() - source.y()); - - fillRect(target, &mSpanData); -} - -VPainter::VPainter(VBitmap *buffer, bool clear) -{ - begin(buffer, clear); -} -bool VPainter::begin(VBitmap *buffer, bool clear) -{ - mBuffer.prepare(buffer); - mSpanData.init(&mBuffer); - // TODO find a better api to clear the surface - if (clear) { - mBuffer.clear(); - } - return true; -} -void VPainter::end() {} - -void VPainter::setDrawRegion(const VRect ®ion) -{ - mSpanData.setDrawRegion(region); -} - -void VPainter::setBrush(const VBrush &brush) -{ - mSpanData.setup(brush); -} - -void VPainter::setBlendMode(BlendMode mode) -{ - mSpanData.mBlendMode = mode; -} - -VRect VPainter::clipBoundingRect() const -{ - return mSpanData.clipRect(); -} - -void VPainter::drawBitmap(const VPoint &point, const VBitmap &bitmap, - const VRect &source, uint8_t const_alpha) -{ - if (!bitmap.valid()) return; - - drawBitmap(VRect(point, bitmap.size()), - bitmap, source, const_alpha); -} - -void VPainter::drawBitmap(const VRect &target, const VBitmap &bitmap, - const VRect &source, uint8_t const_alpha) -{ - if (!bitmap.valid()) return; - - // clear any existing brush data. - setBrush(VBrush()); - - if (target.size() == source.size()) { - drawBitmapUntransform(target, bitmap, source, const_alpha); - } else { - // @TODO scaling - } -} - -void VPainter::drawBitmap(const VPoint &point, const VBitmap &bitmap, - uint8_t const_alpha) -{ - if (!bitmap.valid()) return; - - drawBitmap(VRect(point, bitmap.size()), - bitmap, bitmap.rect(), - const_alpha); -} - -void VPainter::drawBitmap(const VRect &rect, const VBitmap &bitmap, - uint8_t const_alpha) -{ - if (!bitmap.valid()) return; - - drawBitmap(rect, bitmap, bitmap.rect(), - const_alpha); -} - -V_END_NAMESPACE diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vpainter.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/vpainter.h deleted file mode 100644 index 64a253d10..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vpainter.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef VPAINTER_H -#define VPAINTER_H - -#include "vbrush.h" -#include "vpoint.h" -#include "vrle.h" -#include "vdrawhelper.h" - -V_BEGIN_NAMESPACE - -class VBitmap; -class VPainter { -public: - VPainter() = default; - explicit VPainter(VBitmap *buffer, bool clear); - bool begin(VBitmap *buffer, bool clear); - void end(); - void setDrawRegion(const VRect ®ion); // sub surface rendering area. - void setBrush(const VBrush &brush); - void setBlendMode(BlendMode mode); - void drawRle(const VPoint &pos, const VRle &rle); - void drawRle(const VRle &rle, const VRle &clip); - VRect clipBoundingRect() const; - - void drawBitmap(const VPoint &point, const VBitmap &bitmap, const VRect &source, uint8_t const_alpha = 255); - void drawBitmap(const VRect &target, const VBitmap &bitmap, const VRect &source, uint8_t const_alpha = 255); - void drawBitmap(const VPoint &point, const VBitmap &bitmap, uint8_t const_alpha = 255); - void drawBitmap(const VRect &rect, const VBitmap &bitmap, uint8_t const_alpha = 255); -private: - void drawBitmapUntransform(const VRect &target, const VBitmap &bitmap, - const VRect &source, uint8_t const_alpha); - VRasterBuffer mBuffer; - VSpanData mSpanData; -}; - -V_END_NAMESPACE - -#endif // VPAINTER_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vpath.cpp b/libfenrir/src/main/jni/animation/rlottie/src/vector/vpath.cpp deleted file mode 100644 index 8555b8fa4..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vpath.cpp +++ /dev/null @@ -1,709 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#include "vpath.h" -#include -#include -#include -#include "vbezier.h" -#include "vdebug.h" -#include "vline.h" -#include "vrect.h" - -V_BEGIN_NAMESPACE - -void VPath::VPathData::transform(const VMatrix &m) -{ - for (auto &i : m_points) { - i = m.map(i); - } - mLengthDirty = true; -} - -float VPath::VPathData::length() const -{ - if (!mLengthDirty) return mLength; - - mLengthDirty = false; - mLength = 0.0; - - size_t i = 0; - for (auto e : m_elements) { - switch (e) { - case VPath::Element::MoveTo: - i++; - break; - case VPath::Element::LineTo: { - mLength += VLine(m_points[i - 1], m_points[i]).length(); - i++; - break; - } - case VPath::Element::CubicTo: { - mLength += VBezier::fromPoints(m_points[i - 1], m_points[i], - m_points[i + 1], m_points[i + 2]) - .length(); - i += 3; - break; - } - case VPath::Element::Close: - break; - } - } - - return mLength; -} - -void VPath::VPathData::checkNewSegment() -{ - if (mNewSegment) { - moveTo(0, 0); - mNewSegment = false; - } -} - -void VPath::VPathData::moveTo(float x, float y) -{ - mStartPoint = {x, y}; - mNewSegment = false; - m_elements.emplace_back(VPath::Element::MoveTo); - m_points.emplace_back(x, y); - m_segments++; - mLengthDirty = true; -} - -void VPath::VPathData::lineTo(float x, float y) -{ - checkNewSegment(); - m_elements.emplace_back(VPath::Element::LineTo); - m_points.emplace_back(x, y); - mLengthDirty = true; -} - -void VPath::VPathData::cubicTo(float cx1, float cy1, float cx2, float cy2, - float ex, float ey) -{ - checkNewSegment(); - m_elements.emplace_back(VPath::Element::CubicTo); - m_points.emplace_back(cx1, cy1); - m_points.emplace_back(cx2, cy2); - m_points.emplace_back(ex, ey); - mLengthDirty = true; -} - -void VPath::VPathData::close() -{ - if (empty()) return; - - const VPointF &lastPt = m_points.back(); - if (!fuzzyCompare(mStartPoint, lastPt)) { - lineTo(mStartPoint.x(), mStartPoint.y()); - } - m_elements.push_back(VPath::Element::Close); - mNewSegment = true; - mLengthDirty = true; -} - -void VPath::VPathData::reset() -{ - if (empty()) return; - - m_elements.clear(); - m_points.clear(); - m_segments = 0; - mLength = 0; - mLengthDirty = false; -} - -size_t VPath::VPathData::segments() const -{ - return m_segments; -} - -void VPath::VPathData::reserve(size_t pts, size_t elms) -{ - if (m_points.capacity() < m_points.size() + pts) - m_points.reserve(m_points.size() + pts); - if (m_elements.capacity() < m_elements.size() + elms) - m_elements.reserve(m_elements.size() + elms); -} - -static VPointF curvesForArc(const VRectF &, float, float, VPointF *, size_t *); -static constexpr float PATH_KAPPA = 0.5522847498f; -static constexpr float K_PI = 3.141592f; - -void VPath::VPathData::arcTo(const VRectF &rect, float startAngle, - float sweepLength, bool forceMoveTo) -{ - size_t point_count = 0; - VPointF pts[15]; - VPointF curve_start = - curvesForArc(rect, startAngle, sweepLength, pts, &point_count); - - reserve(point_count + 1, point_count / 3 + 1); - if (empty() || forceMoveTo) { - moveTo(curve_start.x(), curve_start.y()); - } else { - lineTo(curve_start.x(), curve_start.y()); - } - for (size_t i = 0; i < point_count; i += 3) { - cubicTo(pts[i].x(), pts[i].y(), pts[i + 1].x(), pts[i + 1].y(), - pts[i + 2].x(), pts[i + 2].y()); - } -} - -void VPath::VPathData::addCircle(float cx, float cy, float radius, - VPath::Direction dir) -{ - addOval(VRectF(cx - radius, cy - radius, 2 * radius, 2 * radius), dir); -} - -void VPath::VPathData::addOval(const VRectF &rect, VPath::Direction dir) -{ - if (rect.empty()) return; - - float x = rect.x(); - float y = rect.y(); - - float w = rect.width(); - float w2 = rect.width() / 2; - float w2k = w2 * PATH_KAPPA; - - float h = rect.height(); - float h2 = rect.height() / 2; - float h2k = h2 * PATH_KAPPA; - - reserve(13, 6); // 1Move + 4Cubic + 1Close - if (dir == VPath::Direction::CW) { - // moveto 12 o'clock. - moveTo(x + w2, y); - // 12 -> 3 o'clock - cubicTo(x + w2 + w2k, y, x + w, y + h2 - h2k, x + w, y + h2); - // 3 -> 6 o'clock - cubicTo(x + w, y + h2 + h2k, x + w2 + w2k, y + h, x + w2, y + h); - // 6 -> 9 o'clock - cubicTo(x + w2 - w2k, y + h, x, y + h2 + h2k, x, y + h2); - // 9 -> 12 o'clock - cubicTo(x, y + h2 - h2k, x + w2 - w2k, y, x + w2, y); - } else { - // moveto 12 o'clock. - moveTo(x + w2, y); - // 12 -> 9 o'clock - cubicTo(x + w2 - w2k, y, x, y + h2 - h2k, x, y + h2); - // 9 -> 6 o'clock - cubicTo(x, y + h2 + h2k, x + w2 - w2k, y + h, x + w2, y + h); - // 6 -> 3 o'clock - cubicTo(x + w2 + w2k, y + h, x + w, y + h2 + h2k, x + w, y + h2); - // 3 -> 12 o'clock - cubicTo(x + w, y + h2 - h2k, x + w2 + w2k, y, x + w2, y); - } - close(); -} - -void VPath::VPathData::addRect(const VRectF &rect, VPath::Direction dir) -{ - float x = rect.x(); - float y = rect.y(); - float w = rect.width(); - float h = rect.height(); - - if (vCompare(w, 0.f) && vCompare(h, 0.f)) return; - - reserve(5, 6); // 1Move + 4Line + 1Close - if (dir == VPath::Direction::CW) { - moveTo(x + w, y); - lineTo(x + w, y + h); - lineTo(x, y + h); - lineTo(x, y); - close(); - } else { - moveTo(x + w, y); - lineTo(x, y); - lineTo(x, y + h); - lineTo(x + w, y + h); - close(); - } -} - -void VPath::VPathData::addRoundRect(const VRectF &rect, float roundness, - VPath::Direction dir) -{ - if (2 * roundness > rect.width()) roundness = rect.width() / 2.0f; - if (2 * roundness > rect.height()) roundness = rect.height() / 2.0f; - addRoundRect(rect, roundness, roundness, dir); -} - -void VPath::VPathData::addRoundRect(const VRectF &rect, float rx, float ry, - VPath::Direction dir) -{ - if (vCompare(rx, 0.f) || vCompare(ry, 0.f)) { - addRect(rect, dir); - return; - } - - float x = rect.x(); - float y = rect.y(); - float w = rect.width(); - float h = rect.height(); - // clamp the rx and ry radius value. - rx = 2 * rx; - ry = 2 * ry; - if (rx > w) rx = w; - if (ry > h) ry = h; - - reserve(17, 10); // 1Move + 4Cubic + 1Close - if (dir == VPath::Direction::CW) { - moveTo(x + w, y + ry / 2.f); - arcTo(VRectF(x + w - rx, y + h - ry, rx, ry), 0, -90, false); - arcTo(VRectF(x, y + h - ry, rx, ry), -90, -90, false); - arcTo(VRectF(x, y, rx, ry), -180, -90, false); - arcTo(VRectF(x + w - rx, y, rx, ry), -270, -90, false); - close(); - } else { - moveTo(x + w, y + ry / 2.f); - arcTo(VRectF(x + w - rx, y, rx, ry), 0, 90, false); - arcTo(VRectF(x, y, rx, ry), 90, 90, false); - arcTo(VRectF(x, y + h - ry, rx, ry), 180, 90, false); - arcTo(VRectF(x + w - rx, y + h - ry, rx, ry), 270, 90, false); - close(); - } -} - -static float tForArcAngle(float angle); -void findEllipseCoords(const VRectF &r, float angle, float length, - VPointF *startPoint, VPointF *endPoint) -{ - if (r.empty()) { - if (startPoint) *startPoint = VPointF(); - if (endPoint) *endPoint = VPointF(); - return; - } - - float w2 = r.width() / 2; - float h2 = r.height() / 2; - - float angles[2] = {angle, angle + length}; - VPointF *points[2] = {startPoint, endPoint}; - - for (int i = 0; i < 2; ++i) { - if (!points[i]) continue; - - float theta = angles[i] - 360 * floorf(angles[i] / 360); - float t = theta / 90; - // truncate - int quadrant = int(t); - t -= quadrant; - - t = tForArcAngle(90 * t); - - // swap x and y? - if (quadrant & 1) t = 1 - t; - - float a, b, c, d; - VBezier::coefficients(t, a, b, c, d); - VPointF p(a + b + c * PATH_KAPPA, d + c + b * PATH_KAPPA); - - // left quadrants - if (quadrant == 1 || quadrant == 2) p.rx() = -p.x(); - - // top quadrants - if (quadrant == 0 || quadrant == 1) p.ry() = -p.y(); - - *points[i] = r.center() + VPointF(w2 * p.x(), h2 * p.y()); - } -} - -static float tForArcAngle(float angle) -{ - float radians, cos_angle, sin_angle, tc, ts, t; - - if (vCompare(angle, 0.f)) return 0; - if (vCompare(angle, 90.0f)) return 1; - - radians = (angle / 180) * K_PI; - - cos_angle = cosf(radians); - sin_angle = sinf(radians); - - // initial guess - tc = angle / 90; - - // do some iterations of newton's method to approximate cos_angle - // finds the zero of the function b.pointAt(tc).x() - cos_angle - tc -= ((((2 - 3 * PATH_KAPPA) * tc + 3 * (PATH_KAPPA - 1)) * tc) * tc + 1 - - cos_angle) // value - / (((6 - 9 * PATH_KAPPA) * tc + 6 * (PATH_KAPPA - 1)) * - tc); // derivative - tc -= ((((2 - 3 * PATH_KAPPA) * tc + 3 * (PATH_KAPPA - 1)) * tc) * tc + 1 - - cos_angle) // value - / (((6 - 9 * PATH_KAPPA) * tc + 6 * (PATH_KAPPA - 1)) * - tc); // derivative - - // initial guess - ts = tc; - // do some iterations of newton's method to approximate sin_angle - // finds the zero of the function b.pointAt(tc).y() - sin_angle - ts -= ((((3 * PATH_KAPPA - 2) * ts - 6 * PATH_KAPPA + 3) * ts + - 3 * PATH_KAPPA) * - ts - - sin_angle) / - (((9 * PATH_KAPPA - 6) * ts + 12 * PATH_KAPPA - 6) * ts + - 3 * PATH_KAPPA); - ts -= ((((3 * PATH_KAPPA - 2) * ts - 6 * PATH_KAPPA + 3) * ts + - 3 * PATH_KAPPA) * - ts - - sin_angle) / - (((9 * PATH_KAPPA - 6) * ts + 12 * PATH_KAPPA - 6) * ts + - 3 * PATH_KAPPA); - - // use the average of the t that best approximates cos_angle - // and the t that best approximates sin_angle - t = 0.5f * (tc + ts); - return t; -} - -// The return value is the starting point of the arc -static VPointF curvesForArc(const VRectF &rect, float startAngle, - float sweepLength, VPointF *curves, - size_t *point_count) -{ - if (rect.empty()) { - return {}; - } - - float x = rect.x(); - float y = rect.y(); - - float w = rect.width(); - float w2 = rect.width() / 2; - float w2k = w2 * PATH_KAPPA; - - float h = rect.height(); - float h2 = rect.height() / 2; - float h2k = h2 * PATH_KAPPA; - - VPointF points[16] = { - // start point - VPointF(x + w, y + h2), - - // 0 -> 270 degrees - VPointF(x + w, y + h2 + h2k), VPointF(x + w2 + w2k, y + h), - VPointF(x + w2, y + h), - - // 270 -> 180 degrees - VPointF(x + w2 - w2k, y + h), VPointF(x, y + h2 + h2k), - VPointF(x, y + h2), - - // 180 -> 90 degrees - VPointF(x, y + h2 - h2k), VPointF(x + w2 - w2k, y), VPointF(x + w2, y), - - // 90 -> 0 degrees - VPointF(x + w2 + w2k, y), VPointF(x + w, y + h2 - h2k), - VPointF(x + w, y + h2)}; - - if (sweepLength > 360) - sweepLength = 360; - else if (sweepLength < -360) - sweepLength = -360; - - // Special case fast paths - if (startAngle == 0.0f) { - if (vCompare(sweepLength, 360)) { - for (int i = 11; i >= 0; --i) curves[(*point_count)++] = points[i]; - return points[12]; - } else if (vCompare(sweepLength, -360)) { - for (int i = 1; i <= 12; ++i) curves[(*point_count)++] = points[i]; - return points[0]; - } - } - - int startSegment = int(floorf(startAngle / 90.0f)); - int endSegment = int(floorf((startAngle + sweepLength) / 90.0f)); - - float startT = (startAngle - startSegment * 90) / 90; - float endT = (startAngle + sweepLength - endSegment * 90) / 90; - - int delta = sweepLength > 0 ? 1 : -1; - if (delta < 0) { - startT = 1 - startT; - endT = 1 - endT; - } - - // avoid empty start segment - if (vIsZero(startT - float(1))) { - startT = 0; - startSegment += delta; - } - - // avoid empty end segment - if (vIsZero(endT)) { - endT = 1; - endSegment -= delta; - } - - startT = tForArcAngle(startT * 90); - endT = tForArcAngle(endT * 90); - - const bool splitAtStart = !vIsZero(startT); - const bool splitAtEnd = !vIsZero(endT - float(1)); - - const int end = endSegment + delta; - - // empty arc? - if (startSegment == end) { - const int quadrant = 3 - ((startSegment % 4) + 4) % 4; - const int j = 3 * quadrant; - return delta > 0 ? points[j + 3] : points[j]; - } - - VPointF startPoint, endPoint; - findEllipseCoords(rect, startAngle, sweepLength, &startPoint, &endPoint); - - for (int i = startSegment; i != end; i += delta) { - const int quadrant = 3 - ((i % 4) + 4) % 4; - const int j = 3 * quadrant; - - VBezier b; - if (delta > 0) - b = VBezier::fromPoints(points[j + 3], points[j + 2], points[j + 1], - points[j]); - else - b = VBezier::fromPoints(points[j], points[j + 1], points[j + 2], - points[j + 3]); - - // empty arc? - if (startSegment == endSegment && vCompare(startT, endT)) - return startPoint; - - if (i == startSegment) { - if (i == endSegment && splitAtEnd) - b = b.onInterval(startT, endT); - else if (splitAtStart) - b = b.onInterval(startT, 1); - } else if (i == endSegment && splitAtEnd) { - b = b.onInterval(0, endT); - } - - // push control points - curves[(*point_count)++] = b.pt2(); - curves[(*point_count)++] = b.pt3(); - curves[(*point_count)++] = b.pt4(); - } - - curves[*(point_count)-1] = endPoint; - - return startPoint; -} - -void VPath::VPathData::addPolystar(float points, float innerRadius, - float outerRadius, float innerRoundness, - float outerRoundness, float startAngle, - float cx, float cy, VPath::Direction dir) -{ - const static float POLYSTAR_MAGIC_NUMBER = 0.47829f / 0.28f; - float currentAngle = (startAngle - 90.0f) * K_PI / 180.0f; - float x; - float y; - float partialPointRadius = 0; - float anglePerPoint = (2.0f * K_PI / points); - float halfAnglePerPoint = anglePerPoint / 2.0f; - float partialPointAmount = points - floorf(points); - bool longSegment = false; - auto numPoints = size_t(ceilf(points) * 2); - float angleDir = ((dir == VPath::Direction::CW) ? 1.0f : -1.0f); - bool hasRoundness = false; - - innerRoundness /= 100.0f; - outerRoundness /= 100.0f; - - if (!vCompare(partialPointAmount, 0)) { - currentAngle += - halfAnglePerPoint * (1.0f - partialPointAmount) * angleDir; - } - - if (!vCompare(partialPointAmount, 0)) { - partialPointRadius = - innerRadius + partialPointAmount * (outerRadius - innerRadius); - x = partialPointRadius * cosf(currentAngle); - y = partialPointRadius * sinf(currentAngle); - currentAngle += anglePerPoint * partialPointAmount / 2.0f * angleDir; - } else { - x = outerRadius * cosf(currentAngle); - y = outerRadius * sinf(currentAngle); - currentAngle += halfAnglePerPoint * angleDir; - } - - if (vIsZero(innerRoundness) && vIsZero(outerRoundness)) { - reserve(numPoints + 2, numPoints + 3); - } else { - reserve(numPoints * 3 + 2, numPoints + 3); - hasRoundness = true; - } - - moveTo(x + cx, y + cy); - - for (size_t i = 0; i < numPoints; i++) { - float radius = longSegment ? outerRadius : innerRadius; - float dTheta = halfAnglePerPoint; - if (!vIsZero(partialPointRadius) && i == numPoints - 2) { - dTheta = anglePerPoint * partialPointAmount / 2.0f; - } - if (!vIsZero(partialPointRadius) && i == numPoints - 1) { - radius = partialPointRadius; - } - float previousX = x; - float previousY = y; - x = radius * cosf(currentAngle); - y = radius * sinf(currentAngle); - - if (hasRoundness) { - float cp1Theta = - (atan2f(previousY, previousX) - K_PI / 2.0f * angleDir); - float cp1Dx = cosf(cp1Theta); - float cp1Dy = sinf(cp1Theta); - float cp2Theta = (atan2f(y, x) - K_PI / 2.0f * angleDir); - float cp2Dx = cosf(cp2Theta); - float cp2Dy = sinf(cp2Theta); - - float cp1Roundness = longSegment ? innerRoundness : outerRoundness; - float cp2Roundness = longSegment ? outerRoundness : innerRoundness; - float cp1Radius = longSegment ? innerRadius : outerRadius; - float cp2Radius = longSegment ? outerRadius : innerRadius; - - float cp1x = cp1Radius * cp1Roundness * POLYSTAR_MAGIC_NUMBER * - cp1Dx / points; - float cp1y = cp1Radius * cp1Roundness * POLYSTAR_MAGIC_NUMBER * - cp1Dy / points; - float cp2x = cp2Radius * cp2Roundness * POLYSTAR_MAGIC_NUMBER * - cp2Dx / points; - float cp2y = cp2Radius * cp2Roundness * POLYSTAR_MAGIC_NUMBER * - cp2Dy / points; - - if (!vIsZero(partialPointAmount) && - ((i == 0) || (i == numPoints - 1))) { - cp1x *= partialPointAmount; - cp1y *= partialPointAmount; - cp2x *= partialPointAmount; - cp2y *= partialPointAmount; - } - - cubicTo(previousX - cp1x + cx, previousY - cp1y + cy, x + cp2x + cx, - y + cp2y + cy, x + cx, y + cy); - } else { - lineTo(x + cx, y + cy); - } - - currentAngle += dTheta * angleDir; - longSegment = !longSegment; - } - - close(); -} - -void VPath::VPathData::addPolygon(float points, float radius, float roundness, - float startAngle, float cx, float cy, - VPath::Direction dir) -{ - // TODO: Need to support floating point number for number of points - const static float POLYGON_MAGIC_NUMBER = 0.25; - float currentAngle = (startAngle - 90.0f) * K_PI / 180.0f; - float x; - float y; - float anglePerPoint = 2.0f * K_PI / floorf(points); - auto numPoints = size_t(floorf(points)); - float angleDir = ((dir == VPath::Direction::CW) ? 1.0f : -1.0f); - bool hasRoundness = false; - - roundness /= 100.0f; - - currentAngle = (currentAngle - 90.0f) * K_PI / 180.0f; - x = radius * cosf(currentAngle); - y = radius * sinf(currentAngle); - currentAngle += anglePerPoint * angleDir; - - if (vIsZero(roundness)) { - reserve(numPoints + 2, numPoints + 3); - } else { - reserve(numPoints * 3 + 2, numPoints + 3); - hasRoundness = true; - } - - moveTo(x + cx, y + cy); - - for (size_t i = 0; i < numPoints; i++) { - float previousX = x; - float previousY = y; - x = (radius * cosf(currentAngle)); - y = (radius * sinf(currentAngle)); - - if (hasRoundness) { - float cp1Theta = - (atan2f(previousY, previousX) - K_PI / 2.0f * angleDir); - float cp1Dx = cosf(cp1Theta); - float cp1Dy = sinf(cp1Theta); - float cp2Theta = atan2f(y, x) - K_PI / 2.0f * angleDir; - float cp2Dx = cosf(cp2Theta); - float cp2Dy = sinf(cp2Theta); - - float cp1x = radius * roundness * POLYGON_MAGIC_NUMBER * cp1Dx; - float cp1y = radius * roundness * POLYGON_MAGIC_NUMBER * cp1Dy; - float cp2x = radius * roundness * POLYGON_MAGIC_NUMBER * cp2Dx; - float cp2y = radius * roundness * POLYGON_MAGIC_NUMBER * cp2Dy; - - cubicTo(previousX - cp1x + cx, previousY - cp1y + cy, x + cp2x + cx, - y + cp2y + cy, x, y); - } else { - lineTo(x + cx, y + cy); - } - - currentAngle += anglePerPoint * angleDir; - } - - close(); -} - -void VPath::VPathData::addPath(const VPathData &path, const VMatrix *m) -{ - size_t segment = path.segments(); - - // make sure enough memory available - if (m_points.capacity() < m_points.size() + path.m_points.size()) - m_points.reserve(m_points.size() + path.m_points.size()); - - if (m_elements.capacity() < m_elements.size() + path.m_elements.size()) - m_elements.reserve(m_elements.size() + path.m_elements.size()); - - if (m) { - for (const auto &i : path.m_points) { - m_points.push_back(m->map(i)); - } - } else { - std::copy(path.m_points.begin(), path.m_points.end(), - back_inserter(m_points)); - } - - std::copy(path.m_elements.begin(), path.m_elements.end(), - back_inserter(m_elements)); - - m_segments += segment; - mLengthDirty = true; -} - -V_END_NAMESPACE diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vpath.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/vpath.h deleted file mode 100644 index 9273cb61b..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vpath.h +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef VPATH_H -#define VPATH_H -#include -#include "vcowptr.h" -#include "vmatrix.h" -#include "vpoint.h" -#include "vrect.h" - -V_BEGIN_NAMESPACE - -struct VPathData; -class VPath { -public: - enum class Direction { CCW, CW }; - - enum class Element : uint8_t { MoveTo, LineTo, CubicTo, Close }; - bool empty() const; - bool null() const; - void moveTo(const VPointF &p); - void moveTo(float x, float y); - void lineTo(const VPointF &p); - void lineTo(float x, float y); - void cubicTo(const VPointF &c1, const VPointF &c2, const VPointF &e); - void cubicTo(float c1x, float c1y, float c2x, float c2y, float ex, - float ey); - void arcTo(const VRectF &rect, float startAngle, float sweepLength, - bool forceMoveTo); - void close(); - void reset(); - void reserve(size_t pts, size_t elms); - size_t segments() const; - void addCircle(float cx, float cy, float radius, - VPath::Direction dir = Direction::CW); - void addOval(const VRectF &rect, VPath::Direction dir = Direction::CW); - void addRoundRect(const VRectF &rect, float rx, float ry, - VPath::Direction dir = Direction::CW); - void addRoundRect(const VRectF &rect, float roundness, - VPath::Direction dir = Direction::CW); - void addRect(const VRectF &rect, VPath::Direction dir = Direction::CW); - void addPolystar(float points, float innerRadius, float outerRadius, - float innerRoundness, float outerRoundness, - float startAngle, float cx, float cy, - VPath::Direction dir = Direction::CW); - void addPolygon(float points, float radius, float roundness, - float startAngle, float cx, float cy, - VPath::Direction dir = Direction::CW); - void addPath(const VPath &path); - void addPath(const VPath &path, const VMatrix &m); - void transform(const VMatrix &m); - float length() const; - const std::vector &elements() const; - const std::vector & points() const; - void clone(const VPath &srcPath); - bool unique() const { return d.unique();} - size_t refCount() const { return d.refCount();} - -private: - struct VPathData { - bool empty() const { return m_elements.empty(); } - bool null() const { return empty() && !m_elements.capacity();} - void moveTo(float x, float y); - void lineTo(float x, float y); - void cubicTo(float cx1, float cy1, float cx2, float cy2, float ex, float ey); - void close(); - void reset(); - void reserve(size_t, size_t); - void checkNewSegment(); - size_t segments() const; - void transform(const VMatrix &m); - float length() const; - void addRoundRect(const VRectF &, float, float, VPath::Direction); - void addRoundRect(const VRectF &, float, VPath::Direction); - void addRect(const VRectF &, VPath::Direction); - void arcTo(const VRectF &, float, float, bool); - void addCircle(float, float, float, VPath::Direction); - void addOval(const VRectF &, VPath::Direction); - void addPolystar(float points, float innerRadius, float outerRadius, - float innerRoundness, float outerRoundness, - float startAngle, float cx, float cy, - VPath::Direction dir = Direction::CW); - void addPolygon(float points, float radius, float roundness, - float startAngle, float cx, float cy, - VPath::Direction dir = Direction::CW); - void addPath(const VPathData &path, const VMatrix *m = nullptr); - void clone(const VPath::VPathData &o) { *this = o;} - const std::vector &elements() const - { - return m_elements; - } - const std::vector &points() const { return m_points; } - std::vector m_points; - std::vector m_elements; - size_t m_segments; - VPointF mStartPoint; - mutable float mLength{0}; - mutable bool mLengthDirty{true}; - bool mNewSegment; - }; - - vcow_ptr d; -}; - -inline bool VPath::empty() const -{ - return d->empty(); -} - -/* - * path is empty as well as null(no memory for data allocated yet). - */ -inline bool VPath::null() const -{ - return d->null(); -} - -inline void VPath::moveTo(const VPointF &p) -{ - d.write().moveTo(p.x(), p.y()); -} - -inline void VPath::lineTo(const VPointF &p) -{ - d.write().lineTo(p.x(), p.y()); -} - -inline void VPath::close() -{ - d.write().close(); -} - -inline void VPath::reset() -{ - d.write().reset(); -} - -inline void VPath::reserve(size_t pts, size_t elms) -{ - d.write().reserve(pts, elms); -} - -inline size_t VPath::segments() const -{ - return d->segments(); -} - -inline float VPath::length() const -{ - return d->length(); -} - -inline void VPath::cubicTo(const VPointF &c1, const VPointF &c2, - const VPointF &e) -{ - d.write().cubicTo(c1.x(), c1.y(), c2.x(), c2.y(), e.x(), e.y()); -} - -inline void VPath::lineTo(float x, float y) -{ - d.write().lineTo(x, y); -} - -inline void VPath::moveTo(float x, float y) -{ - d.write().moveTo(x, y); -} - -inline void VPath::cubicTo(float c1x, float c1y, float c2x, float c2y, float ex, - float ey) -{ - d.write().cubicTo(c1x, c1y, c2x, c2y, ex, ey); -} - -inline void VPath::transform(const VMatrix &m) -{ - d.write().transform(m); -} - -inline void VPath::arcTo(const VRectF &rect, float startAngle, - float sweepLength, bool forceMoveTo) -{ - d.write().arcTo(rect, startAngle, sweepLength, forceMoveTo); -} - -inline void VPath::addRect(const VRectF &rect, VPath::Direction dir) -{ - d.write().addRect(rect, dir); -} - -inline void VPath::addRoundRect(const VRectF &rect, float rx, float ry, - VPath::Direction dir) -{ - d.write().addRoundRect(rect, rx, ry, dir); -} - -inline void VPath::addRoundRect(const VRectF &rect, float roundness, - VPath::Direction dir) -{ - d.write().addRoundRect(rect, roundness, dir); -} - -inline void VPath::addCircle(float cx, float cy, float radius, - VPath::Direction dir) -{ - d.write().addCircle(cx, cy, radius, dir); -} - -inline void VPath::addOval(const VRectF &rect, VPath::Direction dir) -{ - d.write().addOval(rect, dir); -} - -inline void VPath::addPolystar(float points, float innerRadius, - float outerRadius, float innerRoundness, - float outerRoundness, float startAngle, float cx, - float cy, VPath::Direction dir) -{ - d.write().addPolystar(points, innerRadius, outerRadius, innerRoundness, - outerRoundness, startAngle, cx, cy, dir); -} - -inline void VPath::addPolygon(float points, float radius, float roundness, - float startAngle, float cx, float cy, - VPath::Direction dir) -{ - d.write().addPolygon(points, radius, roundness, startAngle, cx, cy, dir); -} - -inline void VPath::addPath(const VPath &path) -{ - if (path.empty()) return; - - if (null()) { - *this = path; - } else { - d.write().addPath(path.d.read()); - } -} - -inline void VPath::addPath(const VPath &path, const VMatrix &m) -{ - if (path.empty()) return; - - d.write().addPath(path.d.read(), &m); -} - -inline const std::vector &VPath::elements() const -{ - return d->elements(); -} - -inline const std::vector &VPath::points() const -{ - return d->points(); -} - -inline void VPath::clone(const VPath &o) -{ - d.write().clone(o.d.read()); -} - -V_END_NAMESPACE - -#endif // VPATH_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vpathmesure.cpp b/libfenrir/src/main/jni/animation/rlottie/src/vector/vpathmesure.cpp deleted file mode 100644 index 5d44987ca..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vpathmesure.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "vpathmesure.h" -#include -#include "vbezier.h" -#include "vdasher.h" - -V_BEGIN_NAMESPACE - -/* - * start and end value must be normalized to [0 - 1] - * Path mesure trims the path from [start --> end] - * if start > end it treates as a loop and trims as two segment - * [0-->end] and [start --> 1] - */ -VPath VPathMesure::trim(const VPath &path) -{ - if (vCompare(mStart, mEnd)) return VPath(); - - if ((vCompare(mStart, 0.0f) && (vCompare(mEnd, 1.0f))) || - (vCompare(mStart, 1.0f) && (vCompare(mEnd, 0.0f)))) - return path; - - float length = path.length(); - - if (mStart < mEnd) { - float array[4] = { - 0.0f, length * mStart, // 1st segment - (mEnd - mStart) * length, - std::numeric_limits::max(), // 2nd segment - }; - VDasher dasher(array, 4); - dasher.dashed(path, mScratchObject); - return mScratchObject; - } else { - float array[4] = { - length * mEnd, (mStart - mEnd) * length, // 1st segment - (1 - mStart) * length, - std::numeric_limits::max(), // 2nd segment - }; - VDasher dasher(array, 4); - dasher.dashed(path, mScratchObject); - return mScratchObject; - } -} - -V_END_NAMESPACE diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vpoint.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/vpoint.h deleted file mode 100644 index da1bb2f55..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vpoint.h +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef VPOINT_H -#define VPOINT_H - -#include "vglobal.h" - -V_BEGIN_NAMESPACE - -class VPointF { -public: - VPointF() = default; - constexpr inline VPointF(float x, float y) noexcept : mx(x), my(y) {} - constexpr inline float x() const noexcept { return mx; } - constexpr inline float y() const noexcept { return my; } - inline float & rx() noexcept { return mx; } - inline float & ry() noexcept { return my; } - inline void setX(float x) { mx = x; } - inline void setY(float y) { my = y; } - inline VPointF operator-() noexcept { return {-mx, -my}; } - inline VPointF & operator+=(const VPointF &p) noexcept; - inline VPointF & operator-=(const VPointF &p) noexcept; - friend const VPointF operator+(const VPointF &p1, const VPointF &p2) - { - return VPointF(p1.mx + p2.mx, p1.my + p2.my); - } - inline friend bool fuzzyCompare(const VPointF &p1, const VPointF &p2); - inline friend VDebug & operator<<(VDebug &os, const VPointF &o); - - friend inline VPointF operator-(const VPointF &p1, const VPointF &p2); - friend inline const VPointF operator*(const VPointF &, float); - friend inline const VPointF operator*(float, const VPointF &); - friend inline const VPointF operator/(const VPointF &, float); - friend inline const VPointF operator/(float, const VPointF &); - -private: - float mx{0}; - float my{0}; -}; - -inline bool fuzzyCompare(const VPointF &p1, const VPointF &p2) -{ - return (vCompare(p1.mx, p2.mx) && vCompare(p1.my, p2.my)); -} - -inline VPointF operator-(const VPointF &p1, const VPointF &p2) -{ - return {p1.mx - p2.mx, p1.my - p2.my}; -} - -inline const VPointF operator*(const VPointF &p, float c) -{ - return VPointF(p.mx * c, p.my * c); -} - -inline const VPointF operator*(float c, const VPointF &p) -{ - return VPointF(p.mx * c, p.my * c); -} - -inline const VPointF operator/(const VPointF &p, float c) -{ - return VPointF(p.mx / c, p.my / c); -} - -inline const VPointF operator/(float c, const VPointF &p) -{ - return VPointF(p.mx / c, p.my / c); -} - -inline VDebug &operator<<(VDebug &os, const VPointF &o) -{ - os << "{P " << o.x() << "," << o.y() << "}"; - return os; -} - -inline VPointF &VPointF::operator+=(const VPointF &p) noexcept -{ - mx += p.mx; - my += p.my; - return *this; -} - -inline VPointF &VPointF::operator-=(const VPointF &p) noexcept -{ - mx -= p.mx; - my -= p.my; - return *this; -} - -class VPoint { -public: - VPoint() = default; - constexpr inline VPoint(int x, int y) noexcept : mx(x), my(y) {} - constexpr inline int x() const noexcept { return mx; } - constexpr inline int y() const noexcept { return my; } - inline void setX(int x) { mx = x; } - inline void setY(int y) { my = y; } - inline VPoint & operator+=(const VPoint &p) noexcept; - inline VPoint & operator-=(const VPoint &p) noexcept; - constexpr inline bool operator==(const VPoint &o) const; - constexpr inline bool operator!=(const VPoint &o) const - { - return !(operator==(o)); - } - friend inline VPoint operator-(const VPoint &p1, const VPoint &p2); - inline friend VDebug &operator<<(VDebug &os, const VPoint &o); - -private: - int mx{0}; - int my{0}; -}; -inline VDebug &operator<<(VDebug &os, const VPoint &o) -{ - os << "{P " << o.x() << "," << o.y() << "}"; - return os; -} - -inline VPoint operator-(const VPoint &p1, const VPoint &p2) -{ - return {p1.mx - p2.mx, p1.my - p2.my}; -} - -constexpr inline bool VPoint::operator==(const VPoint &o) const -{ - return (mx == o.x() && my == o.y()); -} - -inline VPoint &VPoint::operator+=(const VPoint &p) noexcept -{ - mx += p.mx; - my += p.my; - return *this; -} - -inline VPoint &VPoint::operator-=(const VPoint &p) noexcept -{ - mx -= p.mx; - my -= p.my; - return *this; -} - -class VSize { -public: - VSize() = default; - constexpr inline VSize(int w, int h) noexcept : mw(w), mh(h) {} - bool empty() const {return (mw <= 0 || mh <= 0);} - constexpr inline int width() const noexcept { return mw; } - constexpr inline int height() const noexcept { return mh; } - inline void setWidth(int w) { mw = w; } - inline void setHeight(int h) { mh = h; } - inline VSize & operator+=(const VSize &p) noexcept; - inline VSize & operator-=(const VSize &p) noexcept; - constexpr inline bool operator==(const VSize &o) const; - constexpr inline bool operator!=(const VSize &o) const - { - return !(operator==(o)); - } - inline friend VDebug &operator<<(VDebug &os, const VSize &o); - -private: - int mw{0}; - int mh{0}; -}; -inline VDebug &operator<<(VDebug &os, const VSize &o) -{ - os << "{P " << o.width() << "," << o.height() << "}"; - return os; -} -constexpr inline bool VSize::operator==(const VSize &o) const -{ - return (mw == o.width() && mh == o.height()); -} - -inline VSize &VSize::operator+=(const VSize &p) noexcept -{ - mw += p.mw; - mh += p.mh; - return *this; -} - -inline VSize &VSize::operator-=(const VSize &p) noexcept -{ - mw -= p.mw; - mh -= p.mh; - return *this; -} - -V_END_NAMESPACE - -#endif // VPOINT_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vraster.cpp b/libfenrir/src/main/jni/animation/rlottie/src/vector/vraster.cpp deleted file mode 100644 index 7c915a737..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vraster.cpp +++ /dev/null @@ -1,605 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#include "vraster.h" -#include -#include -#include -#include "config.h" -#include "v_ft_raster.h" -#include "v_ft_stroker.h" -#include "vdebug.h" -#include "vmatrix.h" -#include "vpath.h" -#include "vrle.h" - -V_BEGIN_NAMESPACE - -template -class dyn_array { -public: - explicit dyn_array(size_t size) - : mCapacity(size), mData(std::make_unique(mCapacity)) - { - } - void reserve(size_t size) - { - if (mCapacity > size) return; - mCapacity = size; - mData = std::make_unique(mCapacity); - } - T * data() const { return mData.get(); } - dyn_array &operator=(dyn_array &&) noexcept = delete; - -private: - size_t mCapacity{0}; - std::unique_ptr mData{nullptr}; -}; - -struct FTOutline { -public: - void reset(); - void grow(size_t, size_t); - void convert(const VPath &path); - void convert(CapStyle, JoinStyle, float, float); - void moveTo(const VPointF &pt); - void lineTo(const VPointF &pt); - void cubicTo(const VPointF &cp1, const VPointF &cp2, const VPointF &ep); - void close(); - void end(); - static SW_FT_Pos TO_FT_COORD(float x) - { - return SW_FT_Pos(x * 64); - } // to freetype 26.6 coordinate. - SW_FT_Outline ft; - bool closed{false}; - SW_FT_Stroker_LineCap ftCap; - SW_FT_Stroker_LineJoin ftJoin; - SW_FT_Fixed ftWidth; - SW_FT_Fixed ftMiterLimit; - dyn_array mPointMemory{100}; - dyn_array mTagMemory{100}; - dyn_array mContourMemory{10}; - dyn_array mContourFlagMemory{10}; -}; - -void FTOutline::reset() -{ - ft.n_points = ft.n_contours = 0; - ft.flags = 0x0; -} - -void FTOutline::grow(size_t points, size_t segments) -{ - reset(); - mPointMemory.reserve(points + segments); - mTagMemory.reserve(points + segments); - mContourMemory.reserve(segments); - mContourFlagMemory.reserve(segments); - - ft.points = mPointMemory.data(); - ft.tags = mTagMemory.data(); - ft.contours = mContourMemory.data(); - ft.contours_flag = mContourFlagMemory.data(); -} - -void FTOutline::convert(const VPath &path) -{ - const std::vector &elements = path.elements(); - const std::vector & points = path.points(); - - grow(points.size(), path.segments()); - - size_t index = 0; - for (auto element : elements) { - switch (element) { - case VPath::Element::MoveTo: - moveTo(points[index]); - index++; - break; - case VPath::Element::LineTo: - lineTo(points[index]); - index++; - break; - case VPath::Element::CubicTo: - cubicTo(points[index], points[index + 1], points[index + 2]); - index = index + 3; - break; - case VPath::Element::Close: - close(); - break; - } - } - end(); -} - -void FTOutline::convert(CapStyle cap, JoinStyle join, float width, - float miterLimit) -{ - // map strokeWidth to freetype. It uses as the radius of the pen not the - // diameter - width = width / 2.0f; - // convert to freetype co-ordinate - // IMP: stroker takes radius in 26.6 co-ordinate - ftWidth = SW_FT_Fixed(width * (1 << 6)); - // IMP: stroker takes meterlimit in 16.16 co-ordinate - ftMiterLimit = SW_FT_Fixed(miterLimit * (1 << 16)); - - // map to freetype capstyle - switch (cap) { - case CapStyle::Square: - ftCap = SW_FT_STROKER_LINECAP_SQUARE; - break; - case CapStyle::Round: - ftCap = SW_FT_STROKER_LINECAP_ROUND; - break; - default: - ftCap = SW_FT_STROKER_LINECAP_BUTT; - break; - } - switch (join) { - case JoinStyle::Bevel: - ftJoin = SW_FT_STROKER_LINEJOIN_BEVEL; - break; - case JoinStyle::Round: - ftJoin = SW_FT_STROKER_LINEJOIN_ROUND; - break; - default: - ftJoin = SW_FT_STROKER_LINEJOIN_MITER_FIXED; - break; - } -} - -void FTOutline::moveTo(const VPointF &pt) -{ - assert(ft.n_points <= SHRT_MAX - 1); - - ft.points[ft.n_points].x = TO_FT_COORD(pt.x()); - ft.points[ft.n_points].y = TO_FT_COORD(pt.y()); - ft.tags[ft.n_points] = SW_FT_CURVE_TAG_ON; - if (ft.n_points) { - ft.contours[ft.n_contours] = ft.n_points - 1; - ft.n_contours++; - } - // mark the current contour as open - // will be updated if ther is a close tag at the end. - ft.contours_flag[ft.n_contours] = 1; - - ft.n_points++; -} - -void FTOutline::lineTo(const VPointF &pt) -{ - assert(ft.n_points <= SHRT_MAX - 1); - - ft.points[ft.n_points].x = TO_FT_COORD(pt.x()); - ft.points[ft.n_points].y = TO_FT_COORD(pt.y()); - ft.tags[ft.n_points] = SW_FT_CURVE_TAG_ON; - ft.n_points++; -} - -void FTOutline::cubicTo(const VPointF &cp1, const VPointF &cp2, - const VPointF &ep) -{ - assert(ft.n_points <= SHRT_MAX - 3); - - ft.points[ft.n_points].x = TO_FT_COORD(cp1.x()); - ft.points[ft.n_points].y = TO_FT_COORD(cp1.y()); - ft.tags[ft.n_points] = SW_FT_CURVE_TAG_CUBIC; - ft.n_points++; - - ft.points[ft.n_points].x = TO_FT_COORD(cp2.x()); - ft.points[ft.n_points].y = TO_FT_COORD(cp2.y()); - ft.tags[ft.n_points] = SW_FT_CURVE_TAG_CUBIC; - ft.n_points++; - - ft.points[ft.n_points].x = TO_FT_COORD(ep.x()); - ft.points[ft.n_points].y = TO_FT_COORD(ep.y()); - ft.tags[ft.n_points] = SW_FT_CURVE_TAG_ON; - ft.n_points++; -} -void FTOutline::close() -{ - assert(ft.n_points <= SHRT_MAX - 1); - - // mark the contour as a close path. - ft.contours_flag[ft.n_contours] = 0; - - int index; - if (ft.n_contours) { - index = ft.contours[ft.n_contours - 1] + 1; - } else { - index = 0; - } - - // make sure atleast 1 point exists in the segment. - if (ft.n_points == index) { - closed = false; - return; - } - - ft.points[ft.n_points].x = ft.points[index].x; - ft.points[ft.n_points].y = ft.points[index].y; - ft.tags[ft.n_points] = SW_FT_CURVE_TAG_ON; - ft.n_points++; -} - -void FTOutline::end() -{ - assert(ft.n_contours <= SHRT_MAX - 1); - - if (ft.n_points) { - ft.contours[ft.n_contours] = ft.n_points - 1; - ft.n_contours++; - } -} - -static void rleGenerationCb(int count, const SW_FT_Span *spans, void *user) -{ - VRle *rle = static_cast(user); - auto *rleSpan = reinterpret_cast(spans); - rle->addSpan(rleSpan, count); -} - -static void bboxCb(int x, int y, int w, int h, void *user) -{ - VRle *rle = static_cast(user); - rle->setBoundingRect({x, y, w, h}); -} - -class SharedRle { -public: - SharedRle() = default; - VRle &unsafe() { return _rle; } - void notify() - { - { - std::lock_guard lock(_mutex); - _ready = true; - } - _cv.notify_one(); - } - void wait() - { - if (!_pending) return; - - { - std::unique_lock lock(_mutex); - while (!_ready) _cv.wait(lock); - } - - _pending = false; - } - - VRle &get() - { - wait(); - return _rle; - } - - void reset() - { - wait(); - _ready = false; - _pending = true; - } - -private: - VRle _rle; - std::mutex _mutex; - std::condition_variable _cv; - bool _ready{true}; - bool _pending{false}; -}; - -struct VRleTask { - SharedRle mRle; - VPath mPath; - float mStrokeWidth; - float mMiterLimit; - VRect mClip; - FillRule mFillRule; - CapStyle mCap; - JoinStyle mJoin; - bool mGenerateStroke; - - VRle &rle() { return mRle.get(); } - - void update(VPath path, FillRule fillRule, const VRect &clip) - { - mRle.reset(); - mPath = std::move(path); - mFillRule = fillRule; - mClip = clip; - mGenerateStroke = false; - } - - void update(VPath path, CapStyle cap, JoinStyle join, float width, - float miterLimit, const VRect &clip) - { - mRle.reset(); - mPath = std::move(path); - mCap = cap; - mJoin = join; - mStrokeWidth = width; - mMiterLimit = miterLimit; - mClip = clip; - mGenerateStroke = true; - } - void render(FTOutline &outRef) - { - SW_FT_Raster_Params params; - - mRle.unsafe().reset(); - - params.flags = SW_FT_RASTER_FLAG_DIRECT | SW_FT_RASTER_FLAG_AA; - params.gray_spans = &rleGenerationCb; - params.bbox_cb = &bboxCb; - params.user = &mRle.unsafe(); - params.source = &outRef.ft; - - if (!mClip.empty()) { - params.flags |= SW_FT_RASTER_FLAG_CLIP; - - params.clip_box.xMin = mClip.left(); - params.clip_box.yMin = mClip.top(); - params.clip_box.xMax = mClip.right(); - params.clip_box.yMax = mClip.bottom(); - } - // compute rle - sw_ft_grays_raster.raster_render(nullptr, ¶ms); - } - - void operator()(FTOutline &outRef, SW_FT_Stroker &stroker) - { - if (mPath.points().size() > SHRT_MAX || - mPath.points().size() + mPath.segments() > SHRT_MAX) { - return; - } - - if (mGenerateStroke) { // Stroke Task - outRef.convert(mPath); - outRef.convert(mCap, mJoin, mStrokeWidth, mMiterLimit); - - uint32_t points, contors; - - SW_FT_Stroker_Set(stroker, outRef.ftWidth, outRef.ftCap, - outRef.ftJoin, outRef.ftMiterLimit); - SW_FT_Stroker_ParseOutline(stroker, &outRef.ft); - SW_FT_Stroker_GetCounts(stroker, &points, &contors); - - outRef.grow(points, contors); - - SW_FT_Stroker_Export(stroker, &outRef.ft); - - } else { // Fill Task - outRef.convert(mPath); - int fillRuleFlag = SW_FT_OUTLINE_NONE; - switch (mFillRule) { - case FillRule::EvenOdd: - fillRuleFlag = SW_FT_OUTLINE_EVEN_ODD_FILL; - break; - default: - fillRuleFlag = SW_FT_OUTLINE_NONE; - break; - } - outRef.ft.flags = fillRuleFlag; - } - - render(outRef); - - mPath = VPath(); - - mRle.notify(); - } -}; - -using VTask = std::shared_ptr; - -#ifdef LOTTIE_THREAD_SUPPORT - -#include -#include "vtaskqueue.h" - -class RleTaskScheduler { - const unsigned _count{std::thread::hardware_concurrency()}; - std::vector _threads; - std::vector> _q{_count}; - std::atomic _index{0}; - - void run(unsigned i) - { - /* - * initalize per thread objects. - */ - FTOutline outlineRef; - SW_FT_Stroker stroker; - SW_FT_Stroker_New(&stroker); - - // Task Loop - VTask task; - while (true) { - bool success = false; - - for (unsigned n = 0; n != _count * 2; ++n) { - if (_q[(i + n) % _count].try_pop(task)) { - success = true; - break; - } - } - - if (!success && !_q[i].pop(task)) break; - - (*task)(outlineRef, stroker); - } - - // cleanup - SW_FT_Stroker_Done(stroker); - } - - RleTaskScheduler() - { - for (unsigned n = 0; n != _count; ++n) { - _threads.emplace_back([&, n] { run(n); }); - } - - IsRunning = true; - } - -public: - static bool IsRunning; - - static RleTaskScheduler &instance() - { - static RleTaskScheduler singleton; - return singleton; - } - - ~RleTaskScheduler() { stop(); } - - void stop() - { - if (IsRunning) { - IsRunning = false; - - for (auto &e : _q) e.done(); - for (auto &e : _threads) e.join(); - } - } - - void process(VTask task) - { - auto i = _index++; - - for (unsigned n = 0; n != _count; ++n) { - if (_q[(i + n) % _count].try_push(std::move(task))) return; - } - - if (_count > 0) { - _q[i % _count].push(std::move(task)); - } - } - void lottieShutdownRasterTaskScheduler() - { - if (RleTaskScheduler::IsRunning) { - RleTaskScheduler::instance().stop(); - } - } -}; - -#else - -class RleTaskScheduler { -public: -#ifndef LOTTIE_THREAD_SAFE - FTOutline outlineRef{}; - SW_FT_Stroker stroker; -#endif - -public: - static bool IsRunning; - - static RleTaskScheduler &instance() - { - static RleTaskScheduler singleton; - return singleton; - } - -#ifndef LOTTIE_THREAD_SAFE - RleTaskScheduler() { - SW_FT_Stroker_New(&stroker); - } - - ~RleTaskScheduler() { - SW_FT_Stroker_Done(stroker); - } -#endif - -#ifdef LOTTIE_THREAD_SAFE - static void process(const VTask& task) { - FTOutline outlineRef{}; - SW_FT_Stroker stroker; - SW_FT_Stroker_New(&stroker); -#else - void process(VTask task) { -#endif - (*task)(outlineRef, stroker); -#ifdef LOTTIE_THREAD_SAFE - SW_FT_Stroker_Done(stroker); -#endif - } -}; -#endif - -bool RleTaskScheduler::IsRunning{false}; - -struct VRasterizer::VRasterizerImpl { - VRleTask mTask; - - VRle & rle() { return mTask.rle(); } - VRleTask &task() { return mTask; } -}; - -VRle VRasterizer::rle() -{ - if (!d) return VRle(); - return d->rle(); -} - -void VRasterizer::init() -{ - if (!d) d = std::make_shared(); -} - -void VRasterizer::updateRequest() -{ - VTask taskObj = VTask(d, &d->task()); -#ifdef LOTTIE_THREAD_SAFE - RleTaskScheduler::process(taskObj); -#else - RleTaskScheduler::instance().process(std::move(taskObj)); -#endif -} - -void VRasterizer::rasterize(VPath path, FillRule fillRule, const VRect &clip) -{ - init(); - if (path.empty()) { - d->rle().reset(); - return; - } - d->task().update(std::move(path), fillRule, clip); - updateRequest(); -} - -void VRasterizer::rasterize(VPath path, CapStyle cap, JoinStyle join, - float width, float miterLimit, const VRect &clip) -{ - init(); - if (path.empty() || vIsZero(width)) { - d->rle().reset(); - return; - } - d->task().update(std::move(path), cap, join, width, miterLimit, clip); - updateRequest(); -} - -V_END_NAMESPACE diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vrect.cpp b/libfenrir/src/main/jni/animation/rlottie/src/vector/vrect.cpp deleted file mode 100644 index ff219c67d..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vrect.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "vrect.h" -#include - -VRect VRect::operator&(const VRect &r) const -{ - if (empty()) return VRect(); - - int l1 = x1; - int r1 = x1; - if (x2 - x1 + 1 < 0) - l1 = x2; - else - r1 = x2; - - int l2 = r.x1; - int r2 = r.x1; - if (r.x2 - r.x1 + 1 < 0) - l2 = r.x2; - else - r2 = r.x2; - - if (l1 > r2 || l2 > r1) return VRect(); - - int t1 = y1; - int b1 = y1; - if (y2 - y1 + 1 < 0) - t1 = y2; - else - b1 = y2; - - int t2 = r.y1; - int b2 = r.y1; - if (r.y2 - r.y1 + 1 < 0) - t2 = r.y2; - else - b2 = r.y2; - - if (t1 > b2 || t2 > b1) return VRect(); - - VRect tmp; - tmp.x1 = std::max(l1, l2); - tmp.x2 = std::min(r1, r2); - tmp.y1 = std::max(t1, t2); - tmp.y2 = std::min(b1, b2); - return tmp; -} diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vrect.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/vrect.h deleted file mode 100644 index f1c673e1a..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vrect.h +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef VRECT_H -#define VRECT_H -#include "vglobal.h" -#include "vpoint.h" - -V_BEGIN_NAMESPACE -class VRectF; - -class VRect { -public: - VRect() = default; - VRect(int x, int y, int w, int h):x1(x),y1(y),x2(x+w),y2(y+h){} - explicit VRect(VPoint pt, VSize sz):VRect(pt.x(), pt.y(), sz.width(), sz.height()){} - operator VRectF() const; - V_CONSTEXPR bool empty() const {return x1 >= x2 || y1 >= y2;} - V_CONSTEXPR int left() const {return x1;} - V_CONSTEXPR int top() const {return y1;} - V_CONSTEXPR int right() const {return x2;} - V_CONSTEXPR int bottom() const {return y2;} - V_CONSTEXPR int width() const {return x2-x1;} - V_CONSTEXPR int height() const {return y2-y1;} - V_CONSTEXPR int x() const {return x1;} - V_CONSTEXPR int y() const {return y1;} - VSize size() const {return {width(), height()};} - void setLeft(int l) { x1 = l; } - void setTop(int t) { y1 = t; } - void setRight(int r) { x2 = r; } - void setBottom(int b) { y2 = b; } - void setWidth(int w) { x2 = x1 + w; } - void setHeight(int h) { y2 = y1 + h; } - VRect translated(int dx, int dy) const; - void translate(int dx, int dy); - bool contains(const VRect &r, bool proper = false) const; - bool intersects(const VRect &r); - friend V_CONSTEXPR inline bool operator==(const VRect &, - const VRect &) noexcept; - friend V_CONSTEXPR inline bool operator!=(const VRect &, - const VRect &) noexcept; - friend VDebug & operator<<(VDebug &os, const VRect &o); - - VRect intersected(const VRect &r) const; - VRect operator&(const VRect &r) const; - -private: - int x1{0}; - int y1{0}; - int x2{0}; - int y2{0}; -}; - -inline VRect VRect::intersected(const VRect &r) const -{ - return *this & r; -} - -inline bool VRect::intersects(const VRect &r) -{ - return (right() > r.left() && left() < r.right() && bottom() > r.top() && - top() < r.bottom()); -} - -inline VDebug &operator<<(VDebug &os, const VRect &o) -{ - os << "{R " << o.x() << "," << o.y() << "," << o.width() << "," - << o.height() << "}"; - return os; -} -V_CONSTEXPR inline bool operator==(const VRect &r1, const VRect &r2) noexcept -{ - return r1.x1 == r2.x1 && r1.x2 == r2.x2 && r1.y1 == r2.y1 && r1.y2 == r2.y2; -} - -V_CONSTEXPR inline bool operator!=(const VRect &r1, const VRect &r2) noexcept -{ - return r1.x1 != r2.x1 || r1.x2 != r2.x2 || r1.y1 != r2.y1 || r1.y2 != r2.y2; -} - -inline VRect VRect::translated(int dx, int dy) const -{ - return {x1 + dx, y1 + dy, x2 - x1, y2 - y1}; -} - -inline void VRect::translate(int dx, int dy) -{ - x1 += dx; - y1 += dy; - x2 += dx; - y2 += dy; -} - -inline bool VRect::contains(const VRect &r, bool proper) const -{ - return proper ? - ((x1 < r.x1) && (x2 > r.x2) && (y1 < r.y1) && (y2 > r.y2)) : - ((x1 <= r.x1) && (x2 >= r.x2) && (y1 <= r.y1) && (y2 >= r.y2)); -} - -class VRectF { -public: - VRectF() = default; - - VRectF(double x, double y, double w, double h): - x1(float(x)),y1(float(y)), - x2(float(x+w)),y2(float(y+h)){} - operator VRect() const { - return {int(left()), int(right()), int(width()), int(height())}; - } - - V_CONSTEXPR bool empty() const {return x1 >= x2 || y1 >= y2;} - V_CONSTEXPR float left() const {return x1;} - V_CONSTEXPR float top() const {return y1;} - V_CONSTEXPR float right() const {return x2;} - V_CONSTEXPR float bottom() const {return y2;} - V_CONSTEXPR float width() const {return x2-x1;} - V_CONSTEXPR float height() const {return y2-y1;} - V_CONSTEXPR float x() const {return x1;} - V_CONSTEXPR float y() const {return y1;} - V_CONSTEXPR inline VPointF center() const - { - return {x1 + (x2 - x1) / 2.f, y1 + (y2 - y1) / 2.f}; - } - void setLeft(float l) { x1 = l; } - void setTop(float t) { y1 = t; } - void setRight(float r) { x2 = r; } - void setBottom(float b) { y2 = b; } - void setWidth(float w) { x2 = x1 + w; } - void setHeight(float h) { y2 = y1 + h; } - void translate(float dx, float dy) - { - x1 += dx; - y1 += dy; - x2 += dx; - y2 += dy; - } - -private: - float x1{0}; - float y1{0}; - float x2{0}; - float y2{0}; -}; - -inline VRect::operator VRectF() const -{ - return {double(left()), double(right()), double(width()), double(height())}; -} - -V_END_NAMESPACE - -#endif // VRECT_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vrle.cpp b/libfenrir/src/main/jni/animation/rlottie/src/vector/vrle.cpp deleted file mode 100644 index 3382e86b9..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vrle.cpp +++ /dev/null @@ -1,763 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in - all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "vrle.h" -#include -#include -#include -#include -#include -#include -#include -#include "vdebug.h" -#include "vglobal.h" - -V_BEGIN_NAMESPACE - -using Result = std::array; -using rle_view = VRle::View; -static size_t _opGeneric(rle_view &a, rle_view &b, Result &result, - VRle::Data::Op op); -static size_t _opIntersect(const VRect &, rle_view &, Result &); -static size_t _opIntersect(rle_view &, rle_view &, Result &); - -static inline uint8_t divBy255(int x) -{ - return (x + (x >> 8) + 0x80) >> 8; -} - -inline static void copy(const VRle::Span *span, size_t count, - std::vector &v) -{ - // make sure enough memory available - if (v.capacity() < v.size() + count) v.reserve(v.size() + count); - std::copy(span, span + count, back_inserter(v)); -} - -void VRle::Data::addSpan(const VRle::Span *span, size_t count) -{ - copy(span, count, mSpans); - mBboxDirty = true; -} - -VRect VRle::Data::bbox() const -{ - updateBbox(); - return mBbox; -} - -void VRle::Data::setBbox(const VRect &bbox) const -{ - mBboxDirty = false; - mBbox = bbox; -} - -void VRle::Data::reset() -{ - mSpans.clear(); - mBbox = VRect(); - mOffset = VPoint(); - mBboxDirty = false; -} - -void VRle::Data::clone(const VRle::Data &o) -{ - *this = o; -} - -void VRle::Data::translate(const VPoint &p) -{ - // take care of last offset if applied - mOffset = p - mOffset; - int x = mOffset.x(); - int y = mOffset.y(); - for (auto &i : mSpans) { - i.x = i.x + x; - i.y = i.y + y; - } - updateBbox(); - mBbox.translate(mOffset.x(), mOffset.y()); -} - -void VRle::Data::addRect(const VRect &rect) -{ - int x = rect.left(); - int y = rect.top(); - int width = rect.width(); - int height = rect.height(); - - mSpans.reserve(size_t(height)); - - VRle::Span span; - for (int i = 0; i < height; i++) { - span.x = x; - span.y = y + i; - span.len = width; - span.coverage = 255; - mSpans.push_back(span); - } - mBbox = rect; -} - -void VRle::Data::updateBbox() const -{ - if (!mBboxDirty) return; - - mBboxDirty = false; - - int l = std::numeric_limits::max(); - const VRle::Span *span = mSpans.data(); - - mBbox = VRect(); - size_t sz = mSpans.size(); - if (sz) { - int t = span[0].y; - int b = span[sz - 1].y; - int r = 0; - for (size_t i = 0; i < sz; i++) { - if (span[i].x < l) l = span[i].x; - if (span[i].x + span[i].len > r) r = span[i].x + span[i].len; - } - mBbox = VRect(l, t, r - l, b - t + 1); - } -} - -void VRle::Data::operator*=(uint8_t alpha) -{ - for (auto &i : mSpans) { - i.coverage = divBy255(i.coverage * alpha); - } -} - -void VRle::Data::opIntersect(const VRect &r, VRle::VRleSpanCb cb, - void *userData) const -{ - if (empty()) return; - - if (r.contains(bbox())) { - cb(mSpans.size(), mSpans.data(), userData); - return; - } - - auto obj = view(); - Result result; - // run till all the spans are processed - while (obj.size()) { - auto count = _opIntersect(r, obj, result); - if (count) cb(count, result.data(), userData); - } -} - -// res = a - b; -void VRle::Data::opSubstract(const VRle::Data &aObj, const VRle::Data &bObj) -{ - // if two rle are disjoint - if (!aObj.bbox().intersects(bObj.bbox())) { - mSpans = aObj.mSpans; - } else { - auto a = aObj.view(); - auto b = bObj.view(); - - auto aPtr = a.data(); - auto aEnd = a.data() + a.size(); - auto bPtr = b.data(); - auto bEnd = b.data() + b.size(); - - // 1. forward a till it intersects with b - while ((aPtr != aEnd) && (aPtr->y < bPtr->y)) aPtr++; - auto count = aPtr - a.data(); - if (count) copy(a.data(), count, mSpans); - - // 2. forward b till it intersects with a - if (aPtr != aEnd) - while ((bPtr != bEnd) && (bPtr->y < aPtr->y)) bPtr++; - - // update a and b object - a = {aPtr, size_t(aEnd - aPtr)}; - b = {bPtr, size_t(bEnd - bPtr)}; - - // 3. calculate the intersect region - Result result; - - // run till all the spans are processed - while (a.size() && b.size()) { - auto count = _opGeneric(a, b, result, Op::Substract); - if (count) copy(result.data(), count, mSpans); - } - - // 4. copy the rest of a - if (a.size()) copy(a.data(), a.size(), mSpans); - } - - mBboxDirty = true; -} - -void VRle::Data::opGeneric(const VRle::Data &aObj, const VRle::Data &bObj, - Op op) -{ - // This routine assumes, obj1(span_y) < obj2(span_y). - - auto a = aObj.view(); - auto b = bObj.view(); - - // reserve some space for the result vector. - mSpans.reserve(a.size() + b.size()); - - // if two rle are disjoint - if (!aObj.bbox().intersects(bObj.bbox())) { - if (a.data()[0].y < b.data()[0].y) { - copy(a.data(), a.size(), mSpans); - copy(b.data(), b.size(), mSpans); - } else { - copy(b.data(), b.size(), mSpans); - copy(a.data(), a.size(), mSpans); - } - } else { - auto aPtr = a.data(); - auto aEnd = a.data() + a.size(); - auto bPtr = b.data(); - auto bEnd = b.data() + b.size(); - - // 1. forward a till it intersects with b - while ((aPtr != aEnd) && (aPtr->y < bPtr->y)) aPtr++; - - auto count = aPtr - a.data(); - if (count) copy(a.data(), count, mSpans); - - // 2. forward b till it intersects with a - if (aPtr != aEnd) - while ((bPtr != bEnd) && (bPtr->y < aPtr->y)) bPtr++; - - count = bPtr - b.data(); - if (count) copy(b.data(), count, mSpans); - - // update a and b object - a = {aPtr, size_t(aEnd - aPtr)}; - b = {bPtr, size_t(bEnd - bPtr)}; - - // 3. calculate the intersect region - Result result; - - // run till all the spans are processed - while (a.size() && b.size()) { - auto count = _opGeneric(a, b, result, op); - if (count) copy(result.data(), count, mSpans); - } - // 3. copy the rest - if (b.size()) copy(b.data(), b.size(), mSpans); - if (a.size()) copy(a.data(), a.size(), mSpans); - } - - mBboxDirty = true; -} - -static inline V_ALWAYS_INLINE void _opIntersectPrepare(VRle::View &a, - VRle::View &b) -{ - auto aPtr = a.data(); - auto aEnd = a.data() + a.size(); - auto bPtr = b.data(); - auto bEnd = b.data() + b.size(); - - // 1. advance a till it intersects with b - while ((aPtr != aEnd) && (aPtr->y < bPtr->y)) aPtr++; - - // 2. advance b till it intersects with a - if (aPtr != aEnd) - while ((bPtr != bEnd) && (bPtr->y < aPtr->y)) bPtr++; - - // update a and b object - a = {aPtr, size_t(aEnd - aPtr)}; - b = {bPtr, size_t(bEnd - bPtr)}; -} - -void VRle::Data::opIntersect(VRle::View a, VRle::View b) -{ - _opIntersectPrepare(a, b); - Result result; - while (a.size()) { - auto count = _opIntersect(a, b, result); - if (count) copy(result.data(), count, mSpans); - } - - updateBbox(); -} - -static void _opIntersect(rle_view a, rle_view b, VRle::VRleSpanCb cb, - void *userData) -{ - if (!cb) return; - - _opIntersectPrepare(a, b); - Result result; - while (a.size()) { - auto count = _opIntersect(a, b, result); - if (count) cb(count, result.data(), userData); - } -} - -/* - * This function will clip a rle list with another rle object - * tmp_clip : The rle list that will be use to clip the rle - * tmp_obj : holds the list of spans that has to be clipped - * result : will hold the result after the processing - * NOTE: if the algorithm runs out of the result buffer list - * it will stop and update the tmp_obj with the span list - * that are yet to be processed as well as the tpm_clip object - * with the unprocessed clip spans. - */ - -static size_t _opIntersect(rle_view &obj, rle_view &clip, Result &result) -{ - auto out = result.data(); - auto available = result.max_size(); - auto spans = obj.data(); - auto end = obj.data() + obj.size(); - auto clipSpans = clip.data(); - auto clipEnd = clip.data() + clip.size(); - int sx1, sx2, cx1, cx2, x, len; - - while (available && spans < end) { - if (clipSpans >= clipEnd) { - spans = end; - break; - } - if (clipSpans->y > spans->y) { - ++spans; - continue; - } - if (spans->y != clipSpans->y) { - ++clipSpans; - continue; - } - // assert(spans->y == (clipSpans->y + clip_offset_y)); - sx1 = spans->x; - sx2 = sx1 + spans->len; - cx1 = clipSpans->x; - cx2 = cx1 + clipSpans->len; - - if (cx1 < sx1 && cx2 < sx1) { - ++clipSpans; - continue; - } else if (sx1 < cx1 && sx2 < cx1) { - ++spans; - continue; - } - x = std::max(sx1, cx1); - len = std::min(sx2, cx2) - x; - if (len) { - out->x = std::max(sx1, cx1); - out->len = (std::min(sx2, cx2) - out->x); - out->y = spans->y; - out->coverage = divBy255(spans->coverage * clipSpans->coverage); - ++out; - --available; - } - if (sx2 < cx2) { - ++spans; - } else { - ++clipSpans; - } - } - - // update the obj view yet to be processed - obj = {spans, size_t(end - spans)}; - - // update the clip view yet to be processed - clip = {clipSpans, size_t(clipEnd - clipSpans)}; - - return result.max_size() - available; -} - -/* - * This function will clip a rle list with a given rect - * clip : The clip rect that will be use to clip the rle - * tmp_obj : holds the list of spans that has to be clipped - * result : will hold the result after the processing - * NOTE: if the algorithm runs out of the result buffer list - * it will stop and update the tmp_obj with the span list - * that are yet to be processed - */ -static size_t _opIntersect(const VRect &clip, rle_view &obj, Result &result) -{ - auto out = result.data(); - auto available = result.max_size(); - auto ptr = obj.data(); - auto end = obj.data() + obj.size(); - - const auto minx = clip.left(); - const auto miny = clip.top(); - const auto maxx = clip.right() - 1; - const auto maxy = clip.bottom() - 1; - - while (available && ptr < end) { - const auto &span = *ptr; - if (span.y > maxy) { - ptr = end; // update spans so that we can breakout - break; - } - if (span.y < miny || span.x > maxx || span.x + span.len <= minx) { - ++ptr; - continue; - } - if (span.x < minx) { - out->len = std::min(span.len - (minx - span.x), maxx - minx + 1); - out->x = minx; - } else { - out->x = span.x; - out->len = std::min(span.len, uint16_t(maxx - span.x + 1)); - } - if (out->len != 0) { - out->y = span.y; - out->coverage = span.coverage; - ++out; - --available; - } - ++ptr; - } - - // update the span list that yet to be processed - obj = {ptr, size_t(end - ptr)}; - - return result.max_size() - available; -} - -static void blitXor(VRle::Span *spans, int count, uint8_t *buffer, int len, int offsetX) -{ - while (count--) { - int x = spans->x + offsetX; - int l = spans->len; - if (x + l >= len) { - return; - } - uint8_t *ptr = buffer + x; - while (l--) { - int da = *ptr; - *ptr = divBy255((255 - spans->coverage) * (da) + - spans->coverage * (255 - da)); - ptr++; - } - spans++; - } -} - -static void blitDestinationOut(VRle::Span *spans, int count, uint8_t *buffer, int len, - int offsetX) -{ - while (count--) { - int x = spans->x + offsetX; - int l = spans->len; - if (x + l >= len) { - return; - } - uint8_t *ptr = buffer + x; - while (l--) { - *ptr = divBy255((255 - spans->coverage) * (*ptr)); - ptr++; - } - spans++; - } -} - -static void blitSrcOver(VRle::Span *spans, int count, uint8_t *buffer, int len, - int offsetX) -{ - while (count--) { - int x = spans->x + offsetX; - int l = spans->len; - if (x + l >= len) { - return; - } - uint8_t *ptr = buffer + x; - while (l--) { - *ptr = spans->coverage + divBy255((255 - spans->coverage) * (*ptr)); - ptr++; - } - spans++; - } -} - -void blitSrc(VRle::Span *spans, int count, uint8_t *buffer, int len, int offsetX) -{ - while (count--) { - int x = spans->x + offsetX; - int l = spans->len; - if (x + l < 0 || x + l > len) { - return; - } - uint8_t *ptr = buffer + x; - while (l--) { - *ptr = std::max(spans->coverage, *ptr); - ptr++; - } - spans++; - } -} - -size_t bufferToRle(uint8_t *buffer, int len, int size, int offsetX, int y, VRle::Span *out) -{ - size_t count = 0; - uint8_t value = buffer[0]; - int curIndex = 0; - - // size = offsetX < 0 ? size + offsetX : size; - if (size > len) { - return count; - } - for (int i = 0; i < size; i++) { - uint8_t curValue = buffer[0]; - if (value != curValue) { - if (value) { - out->y = y; - out->x = offsetX + curIndex; - out->len = i - curIndex; - out->coverage = value; - out++; - count++; - } - curIndex = i; - value = curValue; - } - buffer++; - } - if (value) { - out->y = y; - out->x = offsetX + curIndex; - out->len = size - curIndex; - out->coverage = value; - count++; - } - return count; -} - -struct SpanMerger { - explicit SpanMerger(VRle::Data::Op op) - { - switch (op) { - case VRle::Data::Op::Add: - _blitter = &blitSrcOver; - break; - case VRle::Data::Op::Xor: - _blitter = &blitXor; - break; - case VRle::Data::Op::Substract: - _blitter = &blitDestinationOut; - break; - } - } - using blitter = void (*)(VRle::Span *, int, uint8_t *, int, int); - blitter _blitter; - std::array _result; - std::array _buffer; - VRle::Span * _aStart{nullptr}; - VRle::Span * _bStart{nullptr}; - - void revert(VRle::Span *&aPtr, VRle::Span *&bPtr) const - { - aPtr = _aStart; - bPtr = _bStart; - } - VRle::Span *data() { return _result.data(); } - size_t merge(VRle::Span *&aPtr, const VRle::Span *aEnd, VRle::Span *&bPtr, - const VRle::Span *bEnd); -}; - -size_t SpanMerger::merge(VRle::Span *&aPtr, const VRle::Span *aEnd, - VRle::Span *&bPtr, const VRle::Span *bEnd) -{ - assert(aPtr->y == bPtr->y); - - _aStart = aPtr; - _bStart = bPtr; - int lb = std::min(aPtr->x, bPtr->x); - int y = aPtr->y; - - while (aPtr < aEnd && aPtr->y == y) aPtr++; - while (bPtr < bEnd && bPtr->y == y) bPtr++; - - int ub = std::max((aPtr - 1)->x + (aPtr - 1)->len, - (bPtr - 1)->x + (bPtr - 1)->len); - int length = (lb < 0) ? ub + lb : ub - lb; - - if (length <= 0 || size_t(length) >= _buffer.max_size()) { - // can't handle merge . skip - return 0; - } - - // clear buffer - memset(_buffer.data(), 0, length); - - // blit a to buffer - blitSrc(_aStart, aPtr - _aStart, _buffer.data(), 1024, -lb); - - // blit b to buffer - _blitter(_bStart, bPtr - _bStart, _buffer.data(), 1024, -lb); - - // convert buffer to span - return bufferToRle(_buffer.data(), 1024, length, lb, y, _result.data()); -} - -static size_t _opGeneric(rle_view &a, rle_view &b, Result &result, - VRle::Data::Op op) -{ - SpanMerger merger{op}; - - auto out = result.data(); - size_t available = result.max_size(); - auto aPtr = a.data(); - auto aEnd = a.data() + a.size(); - auto bPtr = b.data(); - auto bEnd = b.data() + b.size(); - - // only logic change for substract operation. - const bool keep = op != (VRle::Data::Op::Substract); - - while (available && aPtr < aEnd && bPtr < bEnd) { - if (aPtr->y < bPtr->y) { - *out++ = *aPtr++; - available--; - } else if (bPtr->y < aPtr->y) { - if (keep) { - *out++ = *bPtr; - available--; - } - bPtr++; - } else { // same y - auto count = merger.merge(aPtr, aEnd, bPtr, bEnd); - if (available >= count) { - if (count) { - memcpy(out, merger.data(), count * sizeof(VRle::Span)); - out += count; - available -= count; - } - } else { - // not enough space try next time. - merger.revert(aPtr, bPtr); - break; - } - } - } - // update the span list that yet to be processed - a = {aPtr, size_t(aEnd - aPtr)}; - b = {bPtr, size_t(bEnd - bPtr)}; - - return result.max_size() - available; -} - -/* - * this api makes use of thread_local temporary - * buffer to avoid creating intermediate temporary rle buffer - * the scratch buffer object will grow its size on demand - * so that future call won't need any more memory allocation. - * this function is thread safe as it uses thread_local variable - * which is unique per thread. - */ -static vthread_local VRle::Data Scratch_Object; - -VRle VRle::opGeneric(const VRle &o, Data::Op op) const -{ - if (empty()) return o; - if (o.empty()) return *this; - - Scratch_Object.reset(); - Scratch_Object.opGeneric(d.read(), o.d.read(), op); - - VRle result; - result.d.write() = Scratch_Object; - - return result; -} - -VRle VRle::operator-(const VRle &o) const -{ - if (empty()) return {}; - if (o.empty()) return *this; - - Scratch_Object.reset(); - Scratch_Object.opSubstract(d.read(), o.d.read()); - - VRle result; - result.d.write() = Scratch_Object; - - return result; -} - -VRle VRle::operator&(const VRle &o) const -{ - if (empty() || o.empty()) return {}; - - Scratch_Object.reset(); - Scratch_Object.opIntersect(d.read().view(), o.d.read().view()); - - VRle result; - result.d.write() = Scratch_Object; - - return result; -} - -void VRle::operator&=(const VRle &o) -{ - if (empty()) return; - if (o.empty()) { - reset(); - return; - } - Scratch_Object.reset(); - Scratch_Object.opIntersect(d.read().view(), o.d.read().view()); - d.write() = Scratch_Object; -} - -VRle operator-(const VRect &rect, const VRle &o) -{ - if (rect.empty()) return {}; - - Scratch_Object.reset(); - Scratch_Object.addRect(rect); - - VRle result; - result.d.write().opSubstract(Scratch_Object, o.d.read()); - - return result; -} - -VRle operator&(const VRect &rect, const VRle &o) -{ - if (rect.empty() || o.empty()) return {}; - - Scratch_Object.reset(); - Scratch_Object.addRect(rect); - - VRle result; - result.d.write().opIntersect(Scratch_Object.view(), o.d.read().view()); - - return result; -} - -void VRle::intersect(const VRle &clip, VRleSpanCb cb, void *userData) const -{ - if (empty() || clip.empty()) return; - - _opIntersect(d.read().view(), clip.d.read().view(), cb, userData); -} - -V_END_NAMESPACE diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vrle.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/vrle.h deleted file mode 100644 index 1bbb8365b..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vrle.h +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef VRLE_H -#define VRLE_H - -#include -#include "vcowptr.h" -#include "vglobal.h" -#include "vpoint.h" -#include "vrect.h" - -V_BEGIN_NAMESPACE - -class VRle { -public: - struct Span { - short x{0}; - short y{0}; - uint16_t len{0}; - uint8_t coverage{0}; - }; - using VRleSpanCb = void (*)(size_t count, const VRle::Span *spans, - void *userData); - bool empty() const { return d->empty(); } - VRect boundingRect() const { return d->bbox(); } - void setBoundingRect(const VRect &bbox) { d->setBbox(bbox); } - void addSpan(const VRle::Span *span, size_t count) - { - d.write().addSpan(span, count); - } - - void reset() { d.write().reset(); } - void translate(const VPoint &p) { d.write().translate(p); } - - void operator*=(uint8_t alpha) { d.write() *= alpha; } - - void intersect(const VRect &r, VRleSpanCb cb, void *userData) const; - void intersect(const VRle &rle, VRleSpanCb cb, void *userData) const; - - void operator&=(const VRle &o); - VRle operator&(const VRle &o) const; - VRle operator-(const VRle &o) const; - VRle operator+(const VRle &o) const { return opGeneric(o, Data::Op::Add); } - VRle operator^(const VRle &o) const { return opGeneric(o, Data::Op::Xor); } - - friend VRle operator-(const VRect &rect, const VRle &o); - friend VRle operator&(const VRect &rect, const VRle &o); - - bool unique() const { return d.unique(); } - size_t refCount() const { return d.refCount(); } - void clone(const VRle &o) { d.write().clone(o.d.read()); } - -public: - struct View { - Span * _data; - size_t _size; - View(const Span *data, size_t sz) : _data((Span *)data), _size(sz) {} - Span * data() { return _data; } - size_t size() { return _size; } - }; - struct Data { - enum class Op { Add, Xor, Substract }; - VRle::View view() const - { - return VRle::View(mSpans.data(), mSpans.size()); - } - bool empty() const { return mSpans.empty(); } - void addSpan(const VRle::Span *span, size_t count); - void updateBbox() const; - VRect bbox() const; - void setBbox(const VRect &bbox) const; - void reset(); - void translate(const VPoint &p); - void operator*=(uint8_t alpha); - void opGeneric(const VRle::Data &, const VRle::Data &, Op code); - void opSubstract(const VRle::Data &, const VRle::Data &); - void opIntersect(VRle::View a, VRle::View b); - void opIntersect(const VRect &, VRle::VRleSpanCb, void *) const; - void addRect(const VRect &rect); - void clone(const VRle::Data &); - - std::vector mSpans; - VPoint mOffset; - mutable VRect mBbox; - mutable bool mBboxDirty = true; - }; - -private: - VRle opGeneric(const VRle &o, Data::Op opcode) const; - - vcow_ptr d; -}; - -inline void VRle::intersect(const VRect &r, VRleSpanCb cb, void *userData) const -{ - d->opIntersect(r, cb, userData); -} - -V_END_NAMESPACE - -#endif // VRLE_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vsharedptr.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/vsharedptr.h deleted file mode 100644 index fc0c419c9..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vsharedptr.h +++ /dev/null @@ -1,123 +0,0 @@ -#ifndef VSHAREDPTR_H -#define VSHAREDPTR_H - -#include -#include -#include - -template -class vshared_ptr { - struct model { - Rc mRef{1}; - - model() = default; - - template - explicit model(Args&&... args) : mValue(std::forward(args)...){} - explicit model(const T& other) : mValue(other){} - - T mValue; - }; - model* mModel{nullptr}; - -public: - using element_type = T; - - vshared_ptr() = default; - - ~vshared_ptr() - { - unref(); - } - - template - explicit vshared_ptr(Args&&... args) : mModel(new model(std::forward(args)...)) - { - } - - vshared_ptr(const vshared_ptr& x) noexcept : vshared_ptr() - { - if (x.mModel) { - mModel = x.mModel; - ++mModel->mRef; - } - } - - vshared_ptr(vshared_ptr&& x) noexcept : vshared_ptr() - { - if (x.mModel) { - mModel = x.mModel; - x.mModel = nullptr; - } - } - - auto operator=(const vshared_ptr& x) noexcept -> vshared_ptr& - { - unref(); - mModel = x.mModel; - ref(); - return *this; - } - - auto operator=(vshared_ptr&& x) noexcept -> vshared_ptr& - { - unref(); - mModel = x.mModel; - x.mModel = nullptr; - return *this; - } - - operator bool() const noexcept { - return mModel != nullptr; - } - - auto operator*() const noexcept -> element_type& { return read(); } - - auto operator-> () const noexcept -> element_type* { return &read(); } - - std::size_t refCount() const noexcept - { - assert(mModel); - - return mModel->mRef; - } - - bool unique() const noexcept - { - assert(mModel); - - return mModel->mRef == 1; - } - -private: - - auto read() const noexcept -> element_type& - { - assert(mModel); - - return mModel->mValue; - } - - void ref() - { - if (mModel) ++mModel->mRef; - } - - void unref() - { - if (mModel && (--mModel->mRef == 0)) { - delete mModel; - mModel = nullptr; - } - } -}; - -// atomic ref counted pointer implementation. -template < typename T> -using arc_ptr = vshared_ptr>; - -// ref counter pointer implementation. -template < typename T> -using rc_ptr = vshared_ptr; - -#endif // VSHAREDPTR_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vstackallocator.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/vstackallocator.h deleted file mode 100644 index a305b7393..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vstackallocator.h +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef VSTACK_ALLOCATOR_H -#define VSTACK_ALLOCATOR_H - -#include -#include - -template -class arena -{ - alignas(alignment) char buf_[N]; - char* ptr_; - -public: - ~arena() {ptr_ = nullptr;} - arena() noexcept : ptr_(buf_) {} - arena(const arena&) = delete; - arena& operator=(const arena&) = delete; - - template char* allocate(std::size_t n); - void deallocate(char* p, std::size_t n) noexcept; - - static constexpr std::size_t size() noexcept {return N;} - std::size_t used() const noexcept {return static_cast(ptr_ - buf_);} - void reset() noexcept {ptr_ = buf_;} - -private: - static - std::size_t - align_up(std::size_t n) noexcept - {return (n + (alignment-1)) & ~(alignment-1);} - - bool - pointer_in_buffer(char* p) noexcept - {return buf_ <= p && p <= buf_ + N;} -}; - -template -template -char* -arena::allocate(std::size_t n) -{ - static_assert(ReqAlign <= alignment, "alignment is too small for this arena"); - assert(pointer_in_buffer(ptr_) && "stack_alloc has outlived arena"); - auto const aligned_n = align_up(n); - if (static_cast(buf_ + N - ptr_) >= aligned_n) - { - char* r = ptr_; - ptr_ += aligned_n; - return r; - } - - static_assert(alignment <= alignof(std::max_align_t), "you've chosen an " - "alignment that is larger than alignof(std::max_align_t), and " - "cannot be guaranteed by normal operator new"); - return static_cast(::operator new(n)); -} - -template -void -arena::deallocate(char* p, std::size_t n) noexcept -{ - assert(pointer_in_buffer(ptr_) && "stack_alloc has outlived arena"); - if (pointer_in_buffer(p)) - { - n = align_up(n); - if (p + n == ptr_) - ptr_ = p; - } - else - ::operator delete(p); -} - -template -class stack_alloc -{ -public: - using value_type = T; - static auto constexpr alignment = Align; - static auto constexpr size = N; - using arena_type = arena; - -private: - arena_type& a_; - -public: - stack_alloc(const stack_alloc&) = default; - stack_alloc& operator=(const stack_alloc&) = delete; - - stack_alloc(arena_type& a) noexcept : a_(a) - { - static_assert(size % alignment == 0, - "size N needs to be a multiple of alignment Align"); - } - template - stack_alloc(const stack_alloc& a) noexcept - : a_(a.a_) {} - - template struct rebind {using other = stack_alloc<_Up, N, alignment>;}; - - T* allocate(std::size_t n) - { - return reinterpret_cast(a_.template allocate(n*sizeof(T))); - } - void deallocate(T* p, std::size_t n) noexcept - { - a_.deallocate(reinterpret_cast(p), n*sizeof(T)); - } - - template - friend - bool - operator==(const stack_alloc& x, const stack_alloc& y) noexcept; - - template friend class stack_alloc; -}; - -template -inline -bool -operator==(const stack_alloc& x, const stack_alloc& y) noexcept -{ - return N == M && A1 == A2 && &x.a_ == &y.a_; -} - -template -inline -bool -operator!=(const stack_alloc& x, const stack_alloc& y) noexcept -{ - return !(x == y); -} - -#endif // VSTACK_ALLOCATOR_H diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vtaskqueue.h b/libfenrir/src/main/jni/animation/rlottie/src/vector/vtaskqueue.h deleted file mode 100644 index e505c2f4c..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vtaskqueue.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef VTASKQUEUE_H -#define VTASKQUEUE_H - -#include - -template -class TaskQueue { - using lock_t = std::unique_lock; - std::deque _q; - bool _done{false}; - std::mutex _mutex; - std::condition_variable _ready; - -public: - bool try_pop(Task &task) - { - lock_t lock{_mutex, std::try_to_lock}; - if (!lock || _q.empty()) return false; - task = std::move(_q.front()); - _q.pop_front(); - return true; - } - - bool try_push(Task &&task) - { - { - lock_t lock{_mutex, std::try_to_lock}; - if (!lock) return false; - _q.push_back(std::move(task)); - } - _ready.notify_one(); - return true; - } - - void done() - { - { - lock_t lock{_mutex}; - _done = true; - } - _ready.notify_all(); - } - - bool pop(Task &task) - { - lock_t lock{_mutex}; - while (_q.empty() && !_done) _ready.wait(lock); - if (_q.empty()) return false; - task = std::move(_q.front()); - _q.pop_front(); - return true; - } - - void push(Task &&task) - { - { - lock_t lock{_mutex}; - _q.push_back(std::move(task)); - } - _ready.notify_one(); - } - -}; - -#endif // VTASKQUEUE_H diff --git a/libfenrir/src/main/jni/animation/thorvg/inc/colorreplace.h b/libfenrir/src/main/jni/animation/thorvg/inc/colorreplace.h new file mode 100644 index 000000000..4033898f0 --- /dev/null +++ b/libfenrir/src/main/jni/animation/thorvg/inc/colorreplace.h @@ -0,0 +1,213 @@ +#ifndef _ColorReplace_H_ +#define _ColorReplace_H_ +#include +#include + +#ifndef TVG_API +#define TVG_API +#endif + +namespace tvg { + class TVG_API hsv_model { + public: + hsv_model() { + h = 0.f; + s = 0.f; + v = 0.f; + } + float h; + float s; + float v; + }; + + class TVG_API rgb_model { + public: + rgb_model() { + r = 0; + g = 0; + b = 0; + } + rgb_model(int32_t color) { +#ifdef BIG_ENDIAN_COLORS + r = ((color >> 16) & 0xff); + g = ((color >> 8) & 0xff); + b = (color & 0xff); +#else + r = (color & 0xff); + g = ((color >> 8) & 0xff); + b = ((color >> 16) & 0xff); +#endif + } + rgb_model(uint8_t iR, uint8_t iG, uint8_t iB) { + r = iR; + g = iG; + b = iB; + } + rgb_model(float fR, float fG, float fB) { + r = (uint8_t)(fR * 255) & 0xff; + g = (uint8_t)(fG * 255) & 0xff; + b = (uint8_t)(fB * 255) & 0xff; + } + void toFloat(float& fR, float& fG, float& fB) const { + fR = (float)r / 255.0f; + fG = (float)g / 255.0f; + fB = (float)b / 255.0f; + } + void toColors(uint8_t& iR, uint8_t& iG, uint8_t& iB) const { + iR = r; + iG = g; + iB = b; + } + int32_t toInt32() const { +#ifdef BIG_ENDIAN_COLORS + return (int32_t)((r << 16) | (g << 8) | b); +#else + return (int32_t)(r | (g << 8) | (b << 16)); +#endif + } + uint8_t r; + uint8_t g; + uint8_t b; + }; + static inline uint8_t clamp(float v) + { + if (v < 0.f) + return 0; + if (v > 255.f) + return 255; + return (uint8_t)v; + } + + static inline float clampf(float v) + { + if (v < 0.f) + return 0.f; + if (v > 1.f) + return 1.f; + return v; + } + static hsv_model rgb2hsv(const rgb_model& color) + { + hsv_model out; + float min, max, delta; + + min = color.r < color.g ? color.r : color.g; + min = min < color.b ? min : color.b; + + max = color.r > color.g ? color.r : color.g; + max = max > color.b ? max : color.b; + + out.v = max / 255.0f; + delta = max - min; + if (delta < 0.00001f) + { + out.s = 0; + out.h = 0; + return out; + } + if (max > 0.0f) { + out.s = (delta / max); + } + else { + out.s = 0.0f; + out.h = NAN; + return out; + } + if (color.r >= max) + out.h = (color.g - color.b) / delta; + else + if (color.g >= max) + out.h = 2.0f + (color.b - color.r) / delta; + else + out.h = 4.0f + (color.r - color.g) / delta; + + out.h *= 60.0f; + + if (out.h < 0.0) + out.h += 360.0f; + + return out; + } + + static rgb_model change_hsv_c(const rgb_model& color, const float fHue, const float fSat, const float fVal) + { + rgb_model out; + const float cosA = fSat * cos(fHue * 3.14159265f / 180); + const float sinA = fSat * sin(fHue * 3.14159265f / 180); + + const float aThird = 1.0f / 3.0f; + const float rootThird = sqrtf(aThird); + const float oneMinusCosA = (1.0f - cosA); + const float aThirdOfOneMinusCosA = aThird * oneMinusCosA; + const float rootThirdTimesSinA = rootThird * sinA; + const float plus = aThirdOfOneMinusCosA + rootThirdTimesSinA; + const float minus = aThirdOfOneMinusCosA - rootThirdTimesSinA; + + float matrix[3][3] = { + { cosA + oneMinusCosA / 3.0f , minus , plus }, + { plus , cosA + aThirdOfOneMinusCosA , minus }, + { minus , plus , cosA + aThirdOfOneMinusCosA } + }; + out.r = clamp((color.r * matrix[0][0] + color.g * matrix[0][1] + color.b * matrix[0][2]) * fVal); + out.g = clamp((color.r * matrix[1][0] + color.g * matrix[1][1] + color.b * matrix[1][2]) * fVal); + out.b = clamp((color.r * matrix[2][0] + color.g * matrix[2][1] + color.b * matrix[2][2]) * fVal); + return out; + } + + class TVG_API ColorReplace { + public: + ColorReplace() { + useCustomColorsLottieOffset = false; + } + ColorReplace& registerCustomColorLottie(uint32_t color, uint32_t replace) { + customColorsTableLottie[color] = replace; + return *this; + } + ColorReplace& setUseCustomColorsLottieOffset() { + useCustomColorsLottieOffset = true; + return *this; + } + + void getCustomColorLottie32(int32_t& r, int32_t& g, int32_t& b) { + uint8_t sr = (uint8_t)r; + uint8_t sg = (uint8_t)g; + uint8_t sb = (uint8_t)b; + getCustomColorLottie(sr, sg, sb); + r = sr; + g = sg; + b = sb; + } + + void getCustomColorLottie(uint8_t& r, uint8_t& g, uint8_t& b) { + if (!customColorsTableLottie.empty()) { + if (useCustomColorsLottieOffset) { + switchColorModel(r, g, b); + return; + } + auto it = customColorsTableLottie.find(rgb_model(r, g, b).toInt32()); + if (it != customColorsTableLottie.end()) { + rgb_model(it->second).toColors(r, g, b); + return; + } + } + } + + std::unique_ptr _ptr() { + return std::make_unique(*this); + } + + std::mapcustomColorsTableLottie; + bool useCustomColorsLottieOffset; + private: + inline void switchColorModel(uint8_t& r, uint8_t& g, uint8_t& b) { + int32_t from = customColorsTableLottie.begin()->first; + int32_t to = customColorsTableLottie.begin()->second; + + hsv_model frm = rgb2hsv(rgb_model(from)); + hsv_model tom = rgb2hsv(rgb_model(to)); + hsv_model clrr = rgb2hsv(rgb_model(r, g, b)); + change_hsv_c(rgb_model(r, g, b), tom.h - frm.h, clampf(clrr.s + (tom.s - frm.s)), clampf(clrr.v + (tom.v - frm.v))).toColors(r, g, b); + } + }; +} +#endif diff --git a/libfenrir/src/main/jni/animation/thorvg/inc/thorvg.h b/libfenrir/src/main/jni/animation/thorvg/inc/thorvg.h index 28bd7602d..7bdd9f72d 100644 --- a/libfenrir/src/main/jni/animation/thorvg/inc/thorvg.h +++ b/libfenrir/src/main/jni/animation/thorvg/inc/thorvg.h @@ -3,7 +3,6 @@ #include #include -#include #include #ifdef TVG_API @@ -57,6 +56,8 @@ protected: \ #define _TVG_DECLARE_ACCESSOR(A) \ friend A +#include "colorreplace.h" + namespace tvg { @@ -85,17 +86,28 @@ enum class Result FailedAllocation, ///< The value returned in case of unsuccessful memory allocation. MemoryCorruption, ///< The value returned in the event of bad memory handling - e.g. failing in pointer releasing or casting NonSupport, ///< The value returned in case of choosing unsupported engine features(options). - Unknown ///< The value returned in all other cases. + Unknown = 255 ///< The value returned in all other cases. }; /** - * @brief Enumeration specifying the values of the path commands accepted by TVG. - * - * Not to be confused with the path commands from the svg path element (like M, L, Q, H and many others). - * TVG interprets all of them and translates to the ones from the PathCommand values. + * @brief Enumeration specifying the methods of combining the 8-bit color channels into 32-bit color. + */ +enum class ColorSpace : uint8_t +{ + ABGR8888 = 0, ///< The channels are joined in the order: alpha, blue, green, red. Colors are alpha-premultiplied. + ARGB8888, ///< The channels are joined in the order: alpha, red, green, blue. Colors are alpha-premultiplied. + ABGR8888S, ///< The channels are joined in the order: alpha, blue, green, red. Colors are un-alpha-premultiplied. @since 0.12 + ARGB8888S, ///< The channels are joined in the order: alpha, red, green, blue. Colors are un-alpha-premultiplied. @since 0.12 + Grayscale8, ///< One single channel data. + Unknown = 255 ///< Unknown channel data. This is reserved for an initial ColorSpace value. @since 1.0 +}; + + +/** + * @brief Enumeration specifying the values of the path commands accepted by ThorVG. */ -enum class PathCommand +enum class PathCommand : uint8_t { Close = 0, ///< Ends the current sub-path and connects it with its initial point. This command doesn't expect any points. MoveTo, ///< Sets a new initial point of the sub-path and a new current point. This command expects 1 point: the starting position. @@ -107,7 +119,7 @@ enum class PathCommand /** * @brief Enumeration determining the ending type of a stroke in the open sub-paths. */ -enum class StrokeCap +enum class StrokeCap : uint8_t { Square = 0, ///< The stroke is extended in both end-points of a sub-path by a rectangle, with the width equal to the stroke width and the length equal to the half of the stroke width. For zero length sub-paths the square is rendered with the size of the stroke width. Round, ///< The stroke is extended in both end-points of a sub-path by a half circle, with a radius equal to the half of a stroke width. For zero length sub-paths a full circle is rendered. @@ -118,7 +130,7 @@ enum class StrokeCap /** * @brief Enumeration determining the style used at the corners of joined stroked path segments. */ -enum class StrokeJoin +enum class StrokeJoin : uint8_t { Bevel = 0, ///< The outer corner of the joined path segments is bevelled at the join point. The triangular region of the corner is enclosed by a straight line between the outer corners of each stroke. Round, ///< The outer corner of the joined path segments is rounded. The circular region is centered at the join point. @@ -129,7 +141,7 @@ enum class StrokeJoin /** * @brief Enumeration specifying how to fill the area outside the gradient bounds. */ -enum class FillSpread +enum class FillSpread : uint8_t { Pad = 0, ///< The remaining area is filled with the closest stop color. Reflect, ///< The gradient pattern is reflected outside the gradient area until the expected region is filled. @@ -140,7 +152,7 @@ enum class FillSpread /** * @brief Enumeration specifying the algorithm used to establish which parts of the shape are treated as the inside of the shape. */ -enum class FillRule +enum class FillRule : uint8_t { Winding = 0, ///< A line from the point to a location outside the shape is drawn. The intersections of the line with the path segment of the shape are counted. Starting from zero, if the path segment of the shape crosses the line clockwise, one is added, otherwise one is subtracted. If the resulting sum is non zero, the point is inside the shape. EvenOdd ///< A line from the point to a location outside the shape is drawn and its intersections with the path segments of the shape are counted. If the number of intersections is an odd number, the point is inside the shape. @@ -148,26 +160,25 @@ enum class FillRule /** - * @brief Enumeration indicating the method used in the composition of two objects - the target and the source. + * @brief Enumeration indicating the method used in the mask of two objects - the target and the source. * * Notation: S(Source), T(Target), SA(Source Alpha), TA(Target Alpha) * - * @see Paint::composite() + * @see Paint::mask() */ -enum class CompositeMethod +enum class MaskMethod : uint8_t { - None = 0, ///< No composition is applied. - ClipPath, ///< The intersection of the source and the target is determined and only the resulting pixels from the source are rendered. Note that ClipPath only supports the Shape type. @deprecated Use Paint::clip() instead. - AlphaMask, ///< Alpha Masking using the compositing target's pixels as an alpha value. - InvAlphaMask, ///< Alpha Masking using the complement to the compositing target's pixels as an alpha value. - LumaMask, ///< Alpha Masking using the grayscale (0.2125R + 0.7154G + 0.0721*B) of the compositing target's pixels. @since 0.9 - InvLumaMask, ///< Alpha Masking using the grayscale (0.2125R + 0.7154G + 0.0721*B) of the complement to the compositing target's pixels. @since 0.11 - AddMask, ///< Combines the target and source objects pixels using target alpha. (T * TA) + (S * (255 - TA)) (Experimental API) - SubtractMask, ///< Subtracts the source color from the target color while considering their respective target alpha. (T * TA) - (S * (255 - TA)) (Experimental API) - IntersectMask, ///< Computes the result by taking the minimum value between the target alpha and the source alpha and multiplies it with the target color. (T * min(TA, SA)) (Experimental API) - DifferenceMask, ///< Calculates the absolute difference between the target color and the source color multiplied by the complement of the target alpha. abs(T - S * (255 - TA)) (Experimental API) - LightenMask, ///< Where multiple masks intersect, the highest transparency value is used. (Experimental API) - DarkenMask ///< Where multiple masks intersect, the lowest transparency value is used. (Experimental API) + None = 0, ///< No Masking is applied. + Alpha, ///< Alpha Masking using the masking target's pixels as an alpha value. + InvAlpha, ///< Alpha Masking using the complement to the masking target's pixels as an alpha value. + Luma, ///< Alpha Masking using the grayscale (0.2125R + 0.7154G + 0.0721*B) of the masking target's pixels. @since 0.9 + InvLuma, ///< Alpha Masking using the grayscale (0.2125R + 0.7154G + 0.0721*B) of the complement to the masking target's pixels. @since 0.11 + Add, ///< Combines the target and source objects pixels using target alpha. (T * TA) + (S * (255 - TA)) (Experimental API) + Subtract, ///< Subtracts the source color from the target color while considering their respective target alpha. (T * TA) - (S * (255 - TA)) (Experimental API) + Intersect, ///< Computes the result by taking the minimum value between the target alpha and the source alpha and multiplies it with the target color. (T * min(TA, SA)) (Experimental API) + Difference, ///< Calculates the absolute difference between the target color and the source color multiplied by the complement of the target alpha. abs(T - S * (255 - TA)) (Experimental API) + Lighten, ///< Where multiple masks intersect, the highest transparency value is used. (Experimental API) + Darken ///< Where multiple masks intersect, the lowest transparency value is used. (Experimental API) }; @@ -216,15 +227,17 @@ enum class BlendMethod : uint8_t enum class SceneEffect : uint8_t { ClearAll = 0, ///< Reset all previously applied scene effects, restoring the scene to its original state. - GaussianBlur ///< Apply a blur effect with a Gaussian filter. Param(3) = {sigma(float)[> 0], direction(int)[both: 0 / horizontal: 1 / vertical: 2], border(int)[duplicate: 0 / wrap: 1], quality(int)[0 - 100]} + GaussianBlur, ///< Apply a blur effect with a Gaussian filter. Param(3) = {sigma(float)[> 0], direction(int)[both: 0 / horizontal: 1 / vertical: 2], border(int)[duplicate: 0 / wrap: 1], quality(int)[0 - 100]} + DropShadow ///< Apply a drop shadow effect with a Gaussian Blur filter. Param(8) = {color_R(int)[0 - 255], color_G(int)[0 - 255], color_B(int)[0 - 255], opacity(int)[0 - 255], angle(float)[0 - 360], distance(float), blur_sigma(float)[> 0], quality(int)[0 - 100]} }; /** * @brief Enumeration specifying the engine type used for the graphics backend. For multiple backends bitwise operation is allowed. */ -enum class CanvasEngine +enum class CanvasEngine : uint8_t { + All = 0, ///< All feasible rasterizers. @since 1.0 Sw = (1 << 1), ///< CPU rasterizer. Gl = (1 << 2), ///< OpenGL rasterizer. Wg = (1 << 3), ///< WebGPU rasterizer. @since 0.15 @@ -347,7 +360,7 @@ class TVG_API Paint * * @since 0.4 */ - Matrix transform() noexcept; + Matrix& transform() noexcept; /** * @brief Sets the opacity of the object. @@ -359,12 +372,12 @@ class TVG_API Paint Result opacity(uint8_t o) noexcept; /** - * @brief Sets the composition target object and the composition method. + * @brief Sets the masking target object and the masking method. * * @param[in] target The paint of the target object. - * @param[in] method The method used to composite the source object with the target. + * @param[in] method The method used to mask the source object with the target. */ - Result composite(std::unique_ptr target, CompositeMethod method) noexcept; + Result mask(std::unique_ptr target, MaskMethod method) noexcept; /** * @brief Clip the drawing region of the paint object. @@ -393,11 +406,6 @@ class TVG_API Paint */ Result blend(BlendMethod method) noexcept; - /** - * @deprecated Use bounds(float* x, float* y, float* w, float* h, bool transformed) instead - */ - TVG_DEPRECATED Result bounds(float* x, float* y, float* w, float* h) const noexcept; - /** * @brief Gets the axis-aligned bounding box of the paint object. * @@ -412,7 +420,7 @@ class TVG_API Paint * @note If @p transformed is @c true, the paint needs to be pushed into a canvas and updated before this api is called. * @see Canvas::update() */ - Result bounds(float* x, float* y, float* w, float* h, bool transformed) const noexcept; + Result bounds(float* x, float* y, float* w, float* h, bool transformed = false) const noexcept; /** * @brief Duplicates the object. @@ -431,15 +439,15 @@ class TVG_API Paint uint8_t opacity() const noexcept; /** - * @brief Gets the composition target object and the composition method. + * @brief Gets the masking target object and the masking method. * * @param[out] target The paint of the target object. * - * @return The method used to composite the source object with the target. + * @return The method used to mask the source object with the target. * * @since 0.5 */ - CompositeMethod composite(const Paint** target) const noexcept; + MaskMethod mask(const Paint** target) const noexcept; /** * @brief Returns the ID value of this class. @@ -461,11 +469,6 @@ class TVG_API Paint */ uint32_t id = 0; - /** - * @see Paint::type() - */ - TVG_DEPRECATED uint32_t identifier() const noexcept; - _TVG_DECLARE_PRIVATE(Paint); }; @@ -545,7 +548,7 @@ class TVG_API Fill * * @return The augmented transformation matrix. */ - Matrix transform() const noexcept; + Matrix& transform() const noexcept; /** * @brief Creates a copy of the Fill object. @@ -567,11 +570,6 @@ class TVG_API Fill */ virtual Type type() const noexcept = 0; - /** - * @see Fill::type() - */ - TVG_DEPRECATED uint32_t identifier() const noexcept; - _TVG_DECLARE_PRIVATE(Fill); }; @@ -592,8 +590,6 @@ class TVG_API Canvas Canvas(RenderMethod*); virtual ~Canvas(); - TVG_DEPRECATED Result reserve(uint32_t n) noexcept; - /** * @brief Returns the list of the paints that currently held by the Canvas. * @@ -626,16 +622,16 @@ class TVG_API Canvas * @brief Clear the internal canvas resources that used for the drawing. * * This API sets the total number of paints pushed into the canvas to zero. - * Depending on the value of the @p free argument, the paints are either freed or retained. - * So if you need to update paint properties while maintaining the existing scene structure, you can set @p free = false. - * - * @param[in] free If @c true, the memory occupied by paints is deallocated, otherwise it is not. + * Depending on the value of the @p paints argument, the paints are either freed or retained. + * So if you need to update paint properties while maintaining the existing scene structure, you can set @p paints = false. * + * @param[in] paints If @c true, the memory occupied by paints is deallocated; otherwise, the paints will be retained on the canvas. + * @param[in] buffer If @c true, the canvas target buffer is cleared with a zero value. * * @see Canvas::push() * @see Canvas::paints() */ - virtual Result clear(bool free = true) noexcept; + virtual Result clear(bool paints = true, bool buffer = true) noexcept; /** * @brief Request the canvas to update the paint objects. @@ -757,11 +753,6 @@ class TVG_API LinearGradient final : public Fill */ Type type() const noexcept override; - /** - * @see LinearGradient::type() - */ - TVG_DEPRECATED static uint32_t identifier() noexcept; - _TVG_DECLARE_PRIVATE(LinearGradient); }; @@ -778,31 +769,43 @@ class TVG_API RadialGradient final : public Fill ~RadialGradient(); /** - * @brief Sets the radial gradient bounds. + * @brief Sets the radial gradient attributes. * - * The radial gradient bounds are defined as a circle centered in a given point (@p cx, @p cy) of a given radius. + * The radial gradient is defined by the end circle with a center (@p cx, @p cy) and a radius @p r and + * the start circle with a center/focal point (@p fx, @p fy) and a radius @p fr. + * The gradient will be rendered such that the gradient stop at an offset of 100% aligns with the edge of the end circle + * and the stop at an offset of 0% aligns with the edge of the start circle. * - * @param[in] cx The horizontal coordinate of the center of the bounding circle. - * @param[in] cy The vertical coordinate of the center of the bounding circle. - * @param[in] radius The radius of the bounding circle. + * @param[in] cx The horizontal coordinate of the center of the end circle. + * @param[in] cy The vertical coordinate of the center of the end circle. + * @param[in] r The radius of the end circle. + * @param[in] fx The horizontal coordinate of the center of the start circle. + * @param[in] fy The vertical coordinate of the center of the start circle. + * @param[in] fr The radius of the start circle. * - * @retval Result::InvalidArguments in case the @p radius value is zero or less. + * @retval Result::InvalidArguments in case the radius @p r or @p fr value is negative. * - * @note In case the @p radius is zero, an object is filled with a single color using the last color specified in the colorStops(). + * @note In case the radius @p r is zero, an object is filled with a single color using the last color specified in the colorStops(). + * @note By manipulating the position and size of the focal point, a wide range of visual effects can be achieved, such as directing + * the gradient focus towards a specific edge or enhancing the depth and complexity of shading patterns. + * If a focal effect is not desired, simply align the focal point (@p fx and @p fy) with the center of the end circle (@p cx and @p cy) + * and set the radius (@p fr) to zero. This will result in a uniform gradient without any focal variations. */ - Result radial(float cx, float cy, float radius) noexcept; + Result radial(float cx, float cy, float r, float fx, float fy, float fr) noexcept; /** - * @brief Gets the radial gradient bounds. - * - * The radial gradient bounds are defined as a circle centered in a given point (@p cx, @p cy) of a given radius. + * @brief Gets the radial gradient attributes. * - * @param[out] cx The horizontal coordinate of the center of the bounding circle. - * @param[out] cy The vertical coordinate of the center of the bounding circle. - * @param[out] radius The radius of the bounding circle. + * @param[out] cx The horizontal coordinate of the center of the end circle. + * @param[out] cy The vertical coordinate of the center of the end circle. + * @param[out] r The radius of the end circle. + * @param[out] fx The horizontal coordinate of the center of the start circle. + * @param[out] fy The vertical coordinate of the center of the start circle. + * @param[out] fr The radius of the start circle. * + * @see RadialGradient::radial() */ - Result radial(float* cx, float* cy, float* radius) const noexcept; + Result radial(float* cx, float* cy, float* r, float* fx = nullptr, float* fy = nullptr, float* fr = nullptr) const noexcept; /** * @brief Creates a new RadialGradient object. @@ -822,11 +825,6 @@ class TVG_API RadialGradient final : public Fill */ Type type() const noexcept override; - /** - * @see RadialGradient::type() - */ - TVG_DEPRECATED static uint32_t identifier() noexcept; - _TVG_DECLARE_PRIVATE(RadialGradient); }; @@ -946,23 +944,6 @@ class TVG_API Shape final : public Paint */ Result appendCircle(float cx, float cy, float rx, float ry) noexcept; - /** - * @brief Appends a circular arc to the path. - * - * The arc is treated as a new sub-path - it is not connected with the previous sub-path. - * The current point value is set to the end-point of the arc in case @p pie is @c false, and to the center of the arc otherwise. - * - * @param[in] cx The horizontal coordinate of the center of the arc. - * @param[in] cy The vertical coordinate of the center of the arc. - * @param[in] radius The radius of the arc. - * @param[in] startAngle The start angle of the arc given in degrees, measured counter-clockwise from the horizontal line. - * @param[in] sweep The central angle of the arc given in degrees, measured counter-clockwise from @p startAngle. - * @param[in] pie Specifies whether to draw radii from the arc's center to both of its end-point - drawn if @c true. - * - * @note Setting @p sweep value greater than 360 degrees, is equivalent to calling appendCircle(cx, cy, radius, radius). - */ - TVG_DEPRECATED Result appendArc(float cx, float cy, float radius, float startAngle, float sweep, bool pie) noexcept; - /** * @brief Appends a given sub-path to the path. * @@ -985,7 +966,7 @@ class TVG_API Shape final : public Paint * @param[in] width The width of the stroke. The default value is 0. * */ - Result stroke(float width) noexcept; + Result strokeWidth(float width) noexcept; /** * @brief Sets the color of the stroke for all of the figures from the path. @@ -996,7 +977,7 @@ class TVG_API Shape final : public Paint * @param[in] a The alpha channel value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. The default value is 0. * */ - Result stroke(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) noexcept; + Result strokeFill(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) noexcept; /** * @brief Sets the gradient fill of the stroke for all of the figures from the path. @@ -1005,20 +986,23 @@ class TVG_API Shape final : public Paint * * @retval Result::MemoryCorruption In case a @c nullptr is passed as the argument. */ - Result stroke(std::unique_ptr f) noexcept; + Result strokeFill(std::unique_ptr f) noexcept; /** * @brief Sets the dash pattern of the stroke. * * @param[in] dashPattern The array of consecutive pair values of the dash length and the gap length. * @param[in] cnt The length of the @p dashPattern array. + * @param[in] offset The shift of the starting point within the repeating dash pattern from which the path's dashing begins. * * @retval Result::InvalidArguments In case @p dashPattern is @c nullptr and @p cnt > 0, @p cnt is zero, any of the dash pattern values is zero or less. * * @note To reset the stroke dash pattern, pass @c nullptr to @p dashPattern and zero to @p cnt. * @warning @p cnt must be greater than 1 if the dash pattern is valid. + * + * @since 1.0 */ - Result stroke(const float* dashPattern, uint32_t cnt) noexcept; + Result strokeDash(const float* dashPattern, uint32_t cnt, float offset = 0.0f) noexcept; /** * @brief Sets the cap style of the stroke in the open sub-paths. @@ -1026,7 +1010,7 @@ class TVG_API Shape final : public Paint * @param[in] cap The cap style value. The default value is @c StrokeCap::Square. * */ - Result stroke(StrokeCap cap) noexcept; + Result strokeCap(StrokeCap cap) noexcept; /** * @brief Sets the join style for stroked path segments. @@ -1036,7 +1020,7 @@ class TVG_API Shape final : public Paint * @param[in] join The join style value. The default value is @c StrokeJoin::Bevel. * */ - Result stroke(StrokeJoin join) noexcept; + Result strokeJoin(StrokeJoin join) noexcept; /** * @brief Sets the stroke miterlimit. @@ -1163,7 +1147,7 @@ class TVG_API Shape final : public Paint * @param[out] a The alpha channel value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. * */ - Result strokeColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a = nullptr) const noexcept; + Result strokeFill(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a = nullptr) const noexcept; /** * @brief Gets the pointer to the gradient fill of the stroke. @@ -1176,10 +1160,13 @@ class TVG_API Shape final : public Paint * @brief Gets the dash pattern of the stroke. * * @param[out] dashPattern The pointer to the memory, where the dash pattern array is stored. + * @param[out] offset The shift of the starting point within the repeating dash pattern. * * @return The length of the @p dashPattern array. + * + * @since 1.0 */ - uint32_t strokeDash(const float** dashPattern) const noexcept; + uint32_t strokeDash(const float** dashPattern, float* offset = nullptr) const noexcept; /** * @brief Gets the cap style used for stroking the path. @@ -1222,11 +1209,6 @@ class TVG_API Shape final : public Paint */ Type type() const noexcept override; - /** - * @see Shape::type() - */ - TVG_DEPRECATED static uint32_t identifier() noexcept; - _TVG_DECLARE_PRIVATE(Shape); }; @@ -1252,7 +1234,7 @@ class TVG_API Picture final : public Paint * This means that loading the same file again will not result in duplicate operations; * instead, ThorVG will reuse the previously loaded picture data. * - * @param[in] path A path to the picture file. + * @param[in] filename A file name, including the path, for the picture file. * * @retval Result::InvalidArguments In case the @p path is invalid. * @retval Result::NonSupport When trying to load a file with an unknown extension. @@ -1260,12 +1242,7 @@ class TVG_API Picture final : public Paint * @note The Load behavior can be asynchronous if the assigned thread number is greater than zero. * @see Initializer::init() */ - Result load(const std::string& path) noexcept; - - /** - * @deprecated Use load(const char* data, uint32_t size, const std::string& mimeType, bool copy) instead. - */ - TVG_DEPRECATED Result load(const char* data, uint32_t size, bool copy = false) noexcept; + Result load(const char* filename, std::unique_ptr colorReplacement = nullptr) noexcept; /** * @brief Loads a picture data from a memory block of a given size. @@ -1277,6 +1254,7 @@ class TVG_API Picture final : public Paint * @param[in] data A pointer to a memory location where the content of the picture file is stored. A null-terminated string is expected for non-binary data if @p copy is @c false. * @param[in] size The size in bytes of the memory occupied by the @p data. * @param[in] mimeType Mimetype or extension of data such as "jpg", "jpeg", "lottie", "svg", "svg+xml", "png", etc. In case an empty string or an unknown type is provided, the loaders will be tried one by one. + * @param[in] rpath A resource directory path, if the @p data needs to access any external resources. * @param[in] copy If @c true the data are copied into the engine local buffer, otherwise they are not. * * @retval Result::InvalidArguments In case no data are provided or the @p size is zero or less. @@ -1287,7 +1265,7 @@ class TVG_API Picture final : public Paint * @note If you are unsure about the MIME type, you can provide an empty value like @c "", and thorvg will attempt to figure it out. * @since 0.5 */ - Result load(const char* data, uint32_t size, const std::string& mimeType, bool copy = false) noexcept; + Result load(const char* data, uint32_t size, const char* mimeType, const char* rpath = "", bool copy = false, std::unique_ptr colorReplacement = nullptr) noexcept; /** * @brief Resizes the picture content to the given width and height. @@ -1311,21 +1289,22 @@ class TVG_API Picture final : public Paint Result size(float* w, float* h) const noexcept; /** - * @brief Loads raw data in ARGB8888 format from a memory block of the given size. + * @brief Loads raw image data in a specific format from a memory block of the given size. * - * ThorVG efficiently caches the loaded data using the specified @p data address as a key - * when the @p copy has @c false. This means that loading the same data again will not result in duplicate operations - * for the sharable @p data. Instead, ThorVG will reuse the previously loaded picture data. + * ThorVG efficiently caches the loaded data, using the provided @p data address as a key + * when @p copy is set to @c false. This allows ThorVG to avoid redundant operations + * by reusing the previously loaded picture data for the same sharable @p data, + * rather than duplicating the load process. * - * @param[in] data A pointer to a memory location where the content of the picture raw data is stored. - * @param[in] w The width of the image @p data in pixels. - * @param[in] h The height of the image @p data in pixels. - * @param[in] premultiplied If @c true, the given image data is alpha-premultiplied. - * @param[in] copy If @c true the data are copied into the engine local buffer, otherwise they are not. + * @param[in] data A pointer to the memory block where the raw image data is stored. + * @param[in] w The width of the image in pixels. + * @param[in] h The height of the image in pixels. + * @param[in] cs Specifies how the 32-bit color values should be interpreted. + * @param[in] copy If @c true, the data is copied into the engine's local buffer. If @c false, the data is not copied. * * @since 0.9 */ - Result load(uint32_t* data, uint32_t w, uint32_t h, bool copy) noexcept; + Result load(uint32_t* data, uint32_t w, uint32_t h, ColorSpace cs, bool copy = false) noexcept; /** * @brief Retrieve a paint object from the Picture scene by its Unique ID. @@ -1360,11 +1339,6 @@ class TVG_API Picture final : public Paint */ Type type() const noexcept override; - /** - * @see Picture::type() - */ - TVG_DEPRECATED static uint32_t identifier() noexcept; - _TVG_DECLARE_ACCESSOR(Animation); _TVG_DECLARE_PRIVATE(Picture); }; @@ -1400,8 +1374,6 @@ class TVG_API Scene final : public Paint */ Result push(std::unique_ptr paint) noexcept; - TVG_DEPRECATED Result reserve(uint32_t size) noexcept; - /** * @brief Returns the list of the paints that currently held by the Scene. * @@ -1460,11 +1432,6 @@ class TVG_API Scene final : public Paint */ Type type() const noexcept override; - /** - * @see Scene::type() - */ - TVG_DEPRECATED static uint32_t identifier() noexcept; - _TVG_DECLARE_PRIVATE(Scene); }; @@ -1544,16 +1511,16 @@ class TVG_API Text final : public Paint * This means that loading the same file again will not result in duplicate operations; * instead, ThorVG will reuse the previously loaded font data. * - * @param[in] path The path to the font file. + * @param[in] filename A file name, including the path, for the font file. * * @retval Result::InvalidArguments In case the @p path is invalid. * @retval Result::NonSupport When trying to load a file with an unknown extension. * - * @see Text::unload(const std::string& path) + * @see Text::unload(const char* filename) * * @since 0.15 */ - static Result load(const std::string& path) noexcept; + static Result load(const char* filename) noexcept; /** * @brief Loads a scalable font data (ttf) from a memory block of a given size. @@ -1580,23 +1547,23 @@ class TVG_API Text final : public Paint * * @note 0.15 */ - static Result load(const char* name, const char* data, uint32_t size, const std::string& mimeType = "ttf", bool copy = false) noexcept; + static Result load(const char* name, const char* data, uint32_t size, const char* mimeType = "ttf", bool copy = false) noexcept; /** * @brief Unloads the specified scalable font data (TTF) that was previously loaded. * * This function is used to release resources associated with a font file that has been loaded into memory. * - * @param[in] path The file path of the loaded font. + * @param[in] filename The file name of the loaded font, including the path. * * @retval Result::InsufficientCondition Fails if the loader is not initialized. * * @note If the font data is currently in use, it will not be immediately unloaded. - * @see Text::load(const std::string& path) + * @see Text::load(const char* filename) * * @since 0.15 */ - static Result unload(const std::string& path) noexcept; + static Result unload(const char* filename) noexcept; /** * @brief Creates a new Text object. @@ -1632,22 +1599,11 @@ class TVG_API SwCanvas final : public Canvas public: ~SwCanvas(); - /** - * @brief Enumeration specifying the methods of combining the 8-bit color channels into 32-bit color. - */ - enum Colorspace - { - ABGR8888 = 0, ///< The channels are joined in the order: alpha, blue, green, red. Colors are alpha-premultiplied. (a << 24 | b << 16 | g << 8 | r) - ARGB8888, ///< The channels are joined in the order: alpha, red, green, blue. Colors are alpha-premultiplied. (a << 24 | r << 16 | g << 8 | b) - ABGR8888S, ///< The channels are joined in the order: alpha, blue, green, red. Colors are un-alpha-premultiplied. @since 0.12 - ARGB8888S, ///< The channels are joined in the order: alpha, red, green, blue. Colors are un-alpha-premultiplied. @since 0.12 - }; - /** * @brief Enumeration specifying the methods of Memory Pool behavior policy. * @since 0.4 */ - enum MempoolPolicy + enum MempoolPolicy : uint8_t { Default = 0, ///< Default behavior that ThorVG is designed to. Shareable, ///< Memory Pool is shared among the SwCanvases. @@ -1674,7 +1630,7 @@ class TVG_API SwCanvas final : public Canvas * @see Canvas::viewport() * @see Canvas::sync() */ - Result target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, Colorspace cs) noexcept; + Result target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, ColorSpace cs) noexcept; /** * @brief Set sw engine memory pool behavior policy. @@ -1818,15 +1774,15 @@ class TVG_API Initializer final * You can indicate the number of threads, the count of which is designated @p threads. * In the initialization step, TVG will generate/spawn the threads as set by @p threads count. * - * @param[in] engine The engine types to initialize. This is relative to the Canvas types, in which it will be used. For multiple backends bitwise operation is allowed. * @param[in] threads The number of additional threads. Zero indicates only the main thread is to be used. + * @param[in] engine The engine types to initialize. This is relative to the Canvas types, in which it will be used. For multiple backends bitwise operation is allowed. * * @retval Result::NonSupport In case the engine type is not supported on the system. * * @note The Initializer keeps track of the number of times it was called. Threads count is fixed at the first init() call. * @see Initializer::term() */ - static Result init(CanvasEngine engine, uint32_t threads) noexcept; + static Result init(uint32_t threads, CanvasEngine engine = tvg::CanvasEngine::All) noexcept; /** * @brief Terminates TVG engines. @@ -1839,7 +1795,7 @@ class TVG_API Initializer final * @note Initializer does own reference counting for multiple calls. * @see Initializer::init() */ - static Result term(CanvasEngine engine) noexcept; + static Result term(CanvasEngine engine = tvg::CanvasEngine::All) noexcept; /** * @brief Retrieves the version of the TVG engine. @@ -2024,8 +1980,8 @@ class TVG_API Saver final * if you wish to optimize for speed. * * @param[in] paint The paint to be saved with all its associated properties. - * @param[in] path A path to the file, in which the paint data is to be saved. - * @param[in] compress If @c true then compress data if possible. + * @param[in] filename A file name, including the path, where the paint data will be saved. + * @param[in] quality The encoded quality level. @c 0 is the minimum, @c 100 is the maximum value(recommended). * * @retval Result::InsufficientCondition If currently saving other resources. * @retval Result::NonSupport When trying to save a file with an unknown extension or in an unsupported format. @@ -2036,7 +1992,7 @@ class TVG_API Saver final * * @since 0.5 */ - Result save(std::unique_ptr paint, const std::string& path, bool compress = true) noexcept; + Result save(std::unique_ptr paint, const char* filename, uint32_t quality = 100) noexcept; /** * @brief Export the provided animation data to the specified file path. @@ -2044,7 +2000,7 @@ class TVG_API Saver final * This function exports the given animation data to the provided file path. You can also specify the desired frame rate in frames per second (FPS) by providing the fps parameter. * * @param[in] animation The animation to be saved, including all associated properties. - * @param[in] path The path to the file where the animation will be saved. + * @param[in] filename A file name, including the path, where the animation will be saved. * @param[in] quality The encoded quality level. @c 0 is the minimum, @c 100 is the maximum value(recommended). * @param[in] fps The desired frames per second (FPS). For example, to encode data at 60 FPS, pass 60. Pass 0 to keep the original frame data. * @@ -2059,7 +2015,7 @@ class TVG_API Saver final * * @note Experimental API */ - Result save(std::unique_ptr animation, const std::string& path, uint32_t quality = 100, uint32_t fps = 0) noexcept; + Result save(std::unique_ptr animation, const char* filename, uint32_t quality = 100, uint32_t fps = 0) noexcept; /** * @brief Guarantees that the saving task is finished. @@ -2104,8 +2060,6 @@ class TVG_API Accessor final public: ~Accessor(); - TVG_DEPRECATED std::unique_ptr set(std::unique_ptr picture, std::function func) noexcept; - /** * @brief Set the access function for traversing the Picture scene tree nodes. * diff --git a/libfenrir/src/main/jni/animation/thorvg/src/common/tvgCompressor.cpp b/libfenrir/src/main/jni/animation/thorvg/src/common/tvgCompressor.cpp index a068d91b7..f03d5a926 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/common/tvgCompressor.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/common/tvgCompressor.cpp @@ -21,7 +21,7 @@ */ /* - * Lempel–Ziv–Welch (LZW) encoder/decoder by Guilherme R. Lampert(guilherme.ronaldo.lampert@gmail.com) + * Lempel–Ziv–Welch (LZW) decoder by Guilherme R. Lampert(guilherme.ronaldo.lampert@gmail.com) * This is the compression scheme used by the GIF image format and the Unix 'compress' tool. * Main differences from this implementation is that End Of Input (EOI) and Clear Codes (CC) @@ -55,8 +55,6 @@ */ #include "config.h" - - #include #include #include "tvgCompressor.h" @@ -68,7 +66,6 @@ namespace tvg { /* LZW Implementation */ /************************************************************************/ - //LZW Dictionary helper: constexpr int Nil = -1; constexpr int MaxDictBits = 12; @@ -76,128 +73,6 @@ constexpr int StartBits = 9; constexpr int FirstCode = (1 << (StartBits - 1)); // 256 constexpr int MaxDictEntries = (1 << MaxDictBits); // 4096 - -//Round up to the next power-of-two number, e.g. 37 => 64 -static int nextPowerOfTwo(int num) -{ - --num; - for (size_t i = 1; i < sizeof(num) * 8; i <<= 1) { - num = num | num >> i; - } - return ++num; -} - - -struct BitStreamWriter -{ - uint8_t* stream; //Growable buffer to store our bits. Heap allocated & owned by the class instance. - int bytesAllocated; //Current size of heap-allocated stream buffer *in bytes*. - int granularity; //Amount bytesAllocated multiplies by when auto-resizing in appendBit(). - int currBytePos; //Current byte being written to, from 0 to bytesAllocated-1. - int nextBitPos; //Bit position within the current byte to access next. 0 to 7. - int numBitsWritten; //Number of bits in use from the stream buffer, not including byte-rounding padding. - - void internalInit() - { - stream = nullptr; - bytesAllocated = 0; - granularity = 2; - currBytePos = 0; - nextBitPos = 0; - numBitsWritten = 0; - } - - uint8_t* allocBytes(const int bytesWanted, uint8_t * oldPtr, const int oldSize) - { - auto newMemory = static_cast(malloc(bytesWanted)); - memset(newMemory, 0, bytesWanted); - - if (oldPtr) { - memcpy(newMemory, oldPtr, oldSize); - free(oldPtr); - } - return newMemory; - } - - BitStreamWriter() - { - /* 8192 bits for a start (1024 bytes). It will resize if needed. - Default granularity is 2. */ - internalInit(); - allocate(8192); - } - - BitStreamWriter(const int initialSizeInBits, const int growthGranularity = 2) - { - internalInit(); - setGranularity(growthGranularity); - allocate(initialSizeInBits); - } - - ~BitStreamWriter() - { - free(stream); - } - - void allocate(int bitsWanted) - { - //Require at least a byte. - if (bitsWanted <= 0) bitsWanted = 8; - - //Round upwards if needed: - if ((bitsWanted % 8) != 0) bitsWanted = nextPowerOfTwo(bitsWanted); - - //We might already have the required count. - const int sizeInBytes = bitsWanted / 8; - if (sizeInBytes <= bytesAllocated) return; - - stream = allocBytes(sizeInBytes, stream, bytesAllocated); - bytesAllocated = sizeInBytes; - } - - void appendBit(const int bit) - { - const uint32_t mask = uint32_t(1) << nextBitPos; - stream[currBytePos] = (stream[currBytePos] & ~mask) | (-bit & mask); - ++numBitsWritten; - - if (++nextBitPos == 8) { - nextBitPos = 0; - if (++currBytePos == bytesAllocated) allocate(bytesAllocated * granularity * 8); - } - } - - void appendBitsU64(const uint64_t num, const int bitCount) - { - for (int b = 0; b < bitCount; ++b) { - const uint64_t mask = uint64_t(1) << b; - const int bit = !!(num & mask); - appendBit(bit); - } - } - - uint8_t* release() - { - auto oldPtr = stream; - internalInit(); - return oldPtr; - } - - void setGranularity(const int growthGranularity) - { - granularity = (growthGranularity >= 2) ? growthGranularity : 2; - } - - int getByteCount() const - { - int usedBytes = numBitsWritten / 8; - int leftovers = numBitsWritten % 8; - if (leftovers != 0) ++usedBytes; - return usedBytes; - } -}; - - struct BitStreamReader { const uint8_t* stream; // Pointer to the external bit stream. Not owned by the reader. @@ -272,18 +147,6 @@ struct Dictionary } } - int findIndex(const int code, const int value) const - { - if (code == Nil) return value; - - //Linear search for now. - //TODO: Worth optimizing with a proper hash-table? - for (int i = 0; i < size; ++i) { - if (entries[i].code == code && entries[i].value == value) return i; - } - return Nil; - } - bool add(const int code, const int value) { if (size == MaxDictEntries) return false; @@ -379,52 +242,10 @@ uint8_t* lzwDecode(const uint8_t* compressed, uint32_t compressedSizeBytes, uint } -uint8_t* lzwEncode(const uint8_t* uncompressed, uint32_t uncompressedSizeBytes, uint32_t* compressedSizeBytes, uint32_t* compressedSizeBits) -{ - //LZW encoding context: - int code = Nil; - int codeBitsWidth = StartBits; - Dictionary dictionary; - - //Output bit stream we write to. This will allocate memory as needed to accommodate the encoded data. - BitStreamWriter bitStream; - - for (; uncompressedSizeBytes > 0; --uncompressedSizeBytes, ++uncompressed) { - const int value = *uncompressed; - const int index = dictionary.findIndex(code, value); - - if (index != Nil) { - code = index; - continue; - } - - //Write the dictionary code using the minimum bit-with: - bitStream.appendBitsU64(code, codeBitsWidth); - - //Flush it when full so we can restart the sequences. - if (!dictionary.flush(codeBitsWidth)) { - //There's still space for this sequence. - dictionary.add(code, value); - } - code = value; - } - - //Residual code at the end: - if (code != Nil) bitStream.appendBitsU64(code, codeBitsWidth); - - //Pass ownership of the compressed data buffer to the user pointer: - *compressedSizeBytes = bitStream.getByteCount(); - *compressedSizeBits = bitStream.numBitsWritten; - - return bitStream.release(); -} - - /************************************************************************/ /* B64 Implementation */ /************************************************************************/ - size_t b64Decode(const char* encoded, const size_t len, char** decoded) { static constexpr const char B64_INDEX[256] = @@ -473,7 +294,7 @@ size_t b64Decode(const char* encoded, const size_t len, char** decoded) /************************************************************************/ -/* DJB2 Implementation */ +/* DJB2 Implementation */ /************************************************************************/ unsigned long djb2Encode(const char* str) diff --git a/libfenrir/src/main/jni/animation/thorvg/src/common/tvgCompressor.h b/libfenrir/src/main/jni/animation/thorvg/src/common/tvgCompressor.h index b043cc77d..9c09aa6a8 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/common/tvgCompressor.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/common/tvgCompressor.h @@ -27,7 +27,6 @@ namespace tvg { - uint8_t* lzwEncode(const uint8_t* uncompressed, uint32_t uncompressedSizeBytes, uint32_t* compressedSizeBytes, uint32_t* compressedSizeBits); uint8_t* lzwDecode(const uint8_t* compressed, uint32_t compressedSizeBytes, uint32_t compressedSizeBits, uint32_t uncompressedSizeBytes); size_t b64Decode(const char* encoded, const size_t len, char** decoded); unsigned long djb2Encode(const char* str); diff --git a/libfenrir/src/main/jni/animation/thorvg/src/common/tvgMath.cpp b/libfenrir/src/main/jni/animation/thorvg/src/common/tvgMath.cpp index cb7f24ff4..0fac9eec4 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/common/tvgMath.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/common/tvgMath.cpp @@ -222,6 +222,15 @@ Point normal(const Point& p1, const Point& p2) } +void normalize(Point& pt) +{ + if (zero(pt)) return; //prevent zero division + auto ilength = 1.0f / sqrtf((pt.x * pt.x) + (pt.y * pt.y)); + pt.x *= ilength; + pt.y *= ilength; +} + + float Line::length() const { return _lineLength(pt1, pt2); diff --git a/libfenrir/src/main/jni/animation/thorvg/src/common/tvgMath.h b/libfenrir/src/main/jni/animation/thorvg/src/common/tvgMath.h index 353cccd0f..e2912a153 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/common/tvgMath.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/common/tvgMath.h @@ -173,6 +173,32 @@ static inline void log(const Matrix& m) void operator*=(Point& pt, const Matrix& m); Point operator*(const Point& pt, const Matrix& m); Point normal(const Point& p1, const Point& p2); +void normalize(Point& pt); + + +static inline Point min(const Point& lhs, const Point& rhs) +{ + return {std::min(lhs.x, rhs.x), std::min(lhs.y, rhs.y)}; +} + + +static inline Point max(const Point& lhs, const Point& rhs) +{ + return {std::max(lhs.x, rhs.x), std::max(lhs.y, rhs.y)}; +} + + +static inline float dot(const Point& lhs, const Point& rhs) +{ + return lhs.x * rhs.x + lhs.y * rhs.y; +} + + +static inline float cross(const Point& lhs, const Point& rhs) +{ + return lhs.x * rhs.y - rhs.x * lhs.y; +} + static inline bool zero(const Point& p) { @@ -198,6 +224,12 @@ static inline float length(const Point& a) } +static inline float length2(const Point& a) +{ + return a.x * a.x + a.y * a.y; +}; + + static inline bool operator==(const Point& lhs, const Point& rhs) { return tvg::equal(lhs.x, rhs.x) && tvg::equal(lhs.y, rhs.y); @@ -216,13 +248,31 @@ static inline Point operator-(const Point& lhs, const Point& rhs) } +static inline Point operator-(const Point& lhs, const float rhs) +{ + return {lhs.x - rhs, lhs.y - rhs}; +} + + static inline Point operator+(const Point& lhs, const Point& rhs) { return {lhs.x + rhs.x, lhs.y + rhs.y}; } -static inline Point operator*(const Point& lhs, float rhs) +static inline Point operator+(const Point& lhs, const float rhs) +{ + return {lhs.x + rhs, lhs.y + rhs}; +} + + +static inline Point operator*(const Point& lhs, const Point& rhs) +{ + return {lhs.x * rhs.x, lhs.y * rhs.y}; +} + + +static inline Point operator*(const Point& lhs, const float rhs) { return {lhs.x * rhs, lhs.y * rhs}; } @@ -234,12 +284,24 @@ static inline Point operator*(const float& lhs, const Point& rhs) } +static inline Point operator/(const Point& lhs, const Point& rhs) +{ + return {lhs.x / rhs.x, lhs.y / rhs.y}; +} + + static inline Point operator/(const Point& lhs, const float rhs) { return {lhs.x / rhs, lhs.y / rhs}; } +static inline Point operator-(const Point& a) +{ + return {-a.x, -a.y}; +} + + static inline void log(const Point& pt) { TVGLOG("COMMON", "Point: [%f %f]", pt.x, pt.y); diff --git a/libfenrir/src/main/jni/animation/thorvg/src/common/tvgStr.cpp b/libfenrir/src/main/jni/animation/thorvg/src/common/tvgStr.cpp index 1ebdd41c5..ea2062fc6 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/common/tvgStr.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/common/tvgStr.cpp @@ -237,4 +237,16 @@ char* strDirname(const char* path) return strDuplicate(path, len); } + +const char* strExtension(const char* filename) +{ + auto ext = filename; + while (ext) { + auto p = strchr(ext, '.'); + if (!p) break; + ext = p + 1; + } + return ext; +} + } diff --git a/libfenrir/src/main/jni/animation/thorvg/src/common/tvgStr.h b/libfenrir/src/main/jni/animation/thorvg/src/common/tvgStr.h index 6b16b4c76..6b1a2be22 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/common/tvgStr.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/common/tvgStr.h @@ -28,10 +28,11 @@ namespace tvg { -float strToFloat(const char *nPtr, char **endPtr); //convert to float -char* strDuplicate(const char *str, size_t n); //copy the string +float strToFloat(const char *nPtr, char **endPtr); //convert to float +char* strDuplicate(const char *str, size_t n); //copy the string char* strAppend(char* lhs, const char* rhs, size_t n); //append the rhs to the lhs -char* strDirname(const char* path); //return the full directory name +char* strDirname(const char* path); //return the full directory name +const char* strExtension(const char* filename); //return the file extension name } #endif //_TVG_STR_H_ diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/allocators.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/allocators.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/allocators.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/allocators.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/cursorstreamwrapper.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/cursorstreamwrapper.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/cursorstreamwrapper.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/cursorstreamwrapper.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/document.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/document.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/document.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/document.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/encodedstream.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/encodedstream.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/encodedstream.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/encodedstream.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/encodings.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/encodings.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/encodings.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/encodings.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/error/en.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/error/en.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/error/en.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/error/en.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/error/error.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/error/error.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/error/error.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/error/error.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/filereadstream.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/filereadstream.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/filereadstream.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/filereadstream.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/filewritestream.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/filewritestream.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/filewritestream.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/filewritestream.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/fwd.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/fwd.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/fwd.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/fwd.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/biginteger.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/biginteger.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/biginteger.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/biginteger.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/clzll.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/clzll.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/clzll.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/clzll.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/diyfp.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/diyfp.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/diyfp.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/diyfp.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/dtoa.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/dtoa.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/dtoa.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/dtoa.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/ieee754.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/ieee754.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/ieee754.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/ieee754.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/itoa.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/itoa.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/itoa.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/itoa.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/meta.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/meta.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/meta.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/meta.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/pow10.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/pow10.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/pow10.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/pow10.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/regex.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/regex.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/regex.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/regex.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/stack.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/stack.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/stack.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/stack.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/strfunc.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/strfunc.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/strfunc.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/strfunc.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/strtod.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/strtod.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/strtod.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/strtod.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/swap.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/swap.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/internal/swap.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/internal/swap.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/istreamwrapper.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/istreamwrapper.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/istreamwrapper.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/istreamwrapper.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/memorybuffer.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/memorybuffer.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/memorybuffer.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/memorybuffer.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/memorystream.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/memorystream.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/memorystream.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/memorystream.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/msinttypes/inttypes.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/msinttypes/inttypes.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/msinttypes/inttypes.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/msinttypes/inttypes.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/msinttypes/stdint.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/msinttypes/stdint.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/msinttypes/stdint.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/msinttypes/stdint.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/ostreamwrapper.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/ostreamwrapper.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/ostreamwrapper.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/ostreamwrapper.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/pointer.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/pointer.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/pointer.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/pointer.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/prettywriter.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/prettywriter.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/prettywriter.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/prettywriter.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/rapidjson.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/rapidjson.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/rapidjson.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/rapidjson.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/reader.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/reader.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/reader.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/reader.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/schema.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/schema.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/schema.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/schema.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/stream.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/stream.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/stream.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/stream.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/stringbuffer.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/stringbuffer.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/stringbuffer.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/stringbuffer.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/uri.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/uri.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/uri.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/uri.h diff --git a/libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/writer.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/writer.h similarity index 100% rename from libfenrir/src/main/jni/animation/rlottie/src/lottie/rapidjson/writer.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/rapidjson/writer.h diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/thorvg_lottie.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/thorvg_lottie.h new file mode 100644 index 000000000..2fe88f012 --- /dev/null +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/thorvg_lottie.h @@ -0,0 +1,94 @@ +#ifndef _THORVG_LOTTIE_H_ +#define _THORVG_LOTTIE_H_ + +#include + +namespace tvg +{ + +/** + * @class LottieAnimation + * + * @brief The LottieAnimation class enables control of advanced Lottie features. + * + * This class extends the Animation and has additional interfaces. + * + * @see Animation + * + * @since 0.15 + */ +class TVG_API LottieAnimation final : public Animation +{ +public: + ~LottieAnimation(); + + /** + * @brief Override Lottie properties using slot data. + * + * @param[in] slot The Lottie slot data in JSON format to override, or @c nullptr to reset. + * + * @retval Result::Success When succeed. + * @retval Result::InsufficientCondition In case the animation is not loaded. + * @retval Result::InvalidArguments When the given parameter is invalid. + * + * @note Experimental API + */ + Result override(const char* slot) noexcept; + + /** + * @brief Specifies a segment by marker. + * + * Markers are used to control animation playback by specifying start and end points, + * eliminating the need to know the exact frame numbers. + * Generally, markers are designated at the design level, + * meaning the callers must know the marker name in advance to use it. + * + * @param[in] marker The name of the segment marker. + * + * @retval Result::Success When successful. + * @retval Result::InsufficientCondition If the animation is not loaded. + * @retval Result::InvalidArguments When the given parameter is invalid. + * @retval Result::NonSupport When it's not animatable. + * + * @note If a @c marker is specified, the previously set segment will be disregarded. + * @note Set @c nullptr to reset the specified segment. + * @see Animation::segment(float begin, float end) + * @note Experimental API + */ + Result segment(const char* marker) noexcept; + + /** + * @brief Gets the marker count of the animation. + * + * @retval The count of the markers, zero if there is no marker. + * + * @see LottieAnimation::marker() + * @note Experimental API + */ + uint32_t markersCnt() noexcept; + + /** + * @brief Gets the marker name by a given index. + * + * @param[in] idx The index of the animation marker, starts from 0. + * + * @retval The name of marker when succeed, @c nullptr otherwise. + * + * @see LottieAnimation::markersCnt() + * @note Experimental API + */ + const char* marker(uint32_t idx) noexcept; + + /** + * @brief Creates a new LottieAnimation object. + * + * @return A new LottieAnimation object. + * + * @since 0.15 + */ + static std::unique_ptr gen() noexcept; +}; + +} //namespace + +#endif //_THORVG_LOTTIE_H_ diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieAnimation.cpp b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieAnimation.cpp new file mode 100644 index 000000000..4ae6396e5 --- /dev/null +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieAnimation.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgCommon.h" +#include "thorvg_lottie.h" +#include "tvgLottieLoader.h" +#include "tvgAnimation.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +LottieAnimation::~LottieAnimation() +{ +} + + +Result LottieAnimation::override(const char* slot) noexcept +{ + if (!pImpl->picture->pImpl->loader) return Result::InsufficientCondition; + + if (static_cast(pImpl->picture->pImpl->loader)->override(slot)) return Result::Success; + + return Result::InvalidArguments; +} + + +Result LottieAnimation::segment(const char* marker) noexcept +{ + auto loader = pImpl->picture->pImpl->loader; + if (!loader) return Result::InsufficientCondition; + + if (!marker) { + static_cast(loader)->segment(0.0f, 1.0f); + return Result::Success; + } + + float begin, end; + if (!static_cast(loader)->segment(marker, begin, end)) return Result::InvalidArguments; + + return static_cast(this)->segment(begin, end); +} + + +uint32_t LottieAnimation::markersCnt() noexcept +{ + auto loader = pImpl->picture->pImpl->loader; + if (!loader) return 0; + return static_cast(loader)->markersCnt(); +} + + +const char* LottieAnimation::marker(uint32_t idx) noexcept +{ + auto loader = pImpl->picture->pImpl->loader; + if (!loader) return nullptr; + return static_cast(loader)->markers(idx); +} + + +unique_ptr LottieAnimation::gen() noexcept +{ + return unique_ptr(new LottieAnimation); +} diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieBuilder.cpp b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieBuilder.cpp new file mode 100644 index 000000000..e56bea292 --- /dev/null +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieBuilder.cpp @@ -0,0 +1,1498 @@ +/* + * Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include + +#include "tvgCommon.h" +#include "tvgMath.h" +#include "tvgLottieModel.h" +#include "tvgLottieBuilder.h" +#include "tvgLottieExpressions.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static bool _buildComposition(LottieComposition* comp, LottieLayer* parent); +static bool _draw(LottieGroup* parent, LottieShape* shape, RenderContext* ctx); + + +static void _rotationXYZ(Matrix* m, float degreeX, float degreeY, float degreeZ) +{ + auto radianX = deg2rad(degreeX); + auto radianY = deg2rad(degreeY); + auto radianZ = deg2rad(degreeZ); + + auto cx = cosf(radianX), sx = sinf(radianX); + auto cy = cosf(radianY), sy = sinf(radianY);; + auto cz = cosf(radianZ), sz = sinf(radianZ);; + m->e11 = cy * cz; + m->e12 = -cy * sz; + m->e21 = sx * sy * cz + cx * sz; + m->e22 = -sx * sy * sz + cx * cz; +} + + +static void _rotationZ(Matrix* m, float degree) +{ + if (degree == 0.0f) return; + auto radian = deg2rad(degree); + m->e11 = cosf(radian); + m->e12 = -sinf(radian); + m->e21 = sinf(radian); + m->e22 = cosf(radian); +} + + +static void _skew(Matrix* m, float angleDeg, float axisDeg) +{ + auto angle = -deg2rad(angleDeg); + float tanVal = tanf(angle); + + axisDeg = fmod(axisDeg, 180.0f); + if (fabsf(axisDeg) < 0.01f || fabsf(axisDeg - 180.0f) < 0.01f || fabsf(axisDeg + 180.0f) < 0.01f) { + float cosVal = cosf(deg2rad(axisDeg)); + auto B = cosVal * cosVal * tanVal; + m->e12 += B * m->e11; + m->e22 += B * m->e21; + return; + } else if (fabsf(axisDeg - 90.0f) < 0.01f || fabsf(axisDeg + 90.0f) < 0.01f) { + float sinVal = -sinf(deg2rad(axisDeg)); + auto C = sinVal * sinVal * tanVal; + m->e11 -= C * m->e12; + m->e21 -= C * m->e22; + return; + } + + auto axis = -deg2rad(axisDeg); + float cosVal = cosf(axis); + float sinVal = sinf(axis); + auto A = sinVal * cosVal * tanVal; + auto B = cosVal * cosVal * tanVal; + auto C = sinVal * sinVal * tanVal; + + auto e11 = m->e11; + auto e21 = m->e21; + m->e11 = (1.0f - A) * e11 - C * m->e12; + m->e12 = B * e11 + (1.0f + A) * m->e12; + m->e21 = (1.0f - A) * e21 - C * m->e22; + m->e22 = B * e21 + (1.0f + A) * m->e22; +} + + +static bool _updateTransform(LottieTransform* transform, float frameNo, bool autoOrient, Matrix& matrix, uint8_t& opacity, LottieExpressions* exps) +{ + tvg::identity(&matrix); + + if (!transform) { + opacity = 255; + return false; + } + + if (transform->coords) { + translate(&matrix, transform->coords->x(frameNo, exps), transform->coords->y(frameNo, exps)); + } else { + auto position = transform->position(frameNo, exps); + translate(&matrix, position.x, position.y); + } + + auto angle = 0.0f; + if (autoOrient) angle = transform->position.angle(frameNo); + if (transform->rotationEx) _rotationXYZ(&matrix, transform->rotationEx->x(frameNo, exps), transform->rotationEx->y(frameNo, exps), transform->rotation(frameNo, exps) + angle); + else _rotationZ(&matrix, transform->rotation(frameNo, exps) + angle); + + + auto skewAngle = transform->skewAngle(frameNo, exps); + if (skewAngle != 0.0f) { + // For angles where tangent explodes, the shape degenerates into an infinitely thin line. + // This is handled by zeroing out the matrix due to finite numerical precision. + skewAngle = fmod(skewAngle, 180.0f); + if (fabsf(skewAngle - 90.0f) < 0.01f || fabsf(skewAngle + 90.0f) < 0.01f) return false; + _skew(&matrix, skewAngle, transform->skewAxis(frameNo, exps)); + } + + auto scale = transform->scale(frameNo, exps); + scaleR(&matrix, scale.x * 0.01f, scale.y * 0.01f); + + //Lottie specific anchor transform. + auto anchor = transform->anchor(frameNo, exps); + translateR(&matrix, -anchor.x, -anchor.y); + + //invisible just in case. + if (scale.x == 0.0f || scale.y == 0.0f) opacity = 0; + else opacity = transform->opacity(frameNo, exps); + + return true; +} + + +void LottieBuilder::updateTransform(LottieLayer* layer, float frameNo) +{ + if (!layer || tvg::equal(layer->cache.frameNo, frameNo)) return; + + auto transform = layer->transform; + auto parent = layer->parent; + + if (parent) updateTransform(parent, frameNo); + + auto& matrix = layer->cache.matrix; + + _updateTransform(transform, frameNo, layer->autoOrient, matrix, layer->cache.opacity, exps); + + if (parent) { + if (!tvg::identity((const Matrix*) &parent->cache.matrix)) { + if (tvg::identity((const Matrix*) &matrix)) layer->cache.matrix = parent->cache.matrix; + else layer->cache.matrix = parent->cache.matrix * matrix; + } + } + layer->cache.frameNo = frameNo; +} + + +void LottieBuilder::updateTransform(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, RenderContext* ctx) +{ + auto transform = static_cast(*child); + if (!transform) return; + + uint8_t opacity; + + if (parent->mergeable()) { + if (!ctx->transform) ctx->transform = (Matrix*)malloc(sizeof(Matrix)); + _updateTransform(transform, frameNo, false, *ctx->transform, opacity, exps); + return; + } + + ctx->merging = nullptr; + + Matrix matrix; + if (!_updateTransform(transform, frameNo, false, matrix, opacity, exps)) return; + + ctx->propagator->transform(ctx->propagator->transform() * matrix); + ctx->propagator->opacity(MULTIPLY(opacity, PP(ctx->propagator)->opacity)); + + //FIXME: preserve the stroke width. too workaround, need a better design. + if (P(ctx->propagator)->rs.strokeWidth() > 0.0f) { + auto denominator = sqrtf(matrix.e11 * matrix.e11 + matrix.e12 * matrix.e12); + if (denominator > 1.0f) ctx->propagator->strokeWidth(ctx->propagator->strokeWidth() / denominator); + } +} + + +void LottieBuilder::updateGroup(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& pcontexts, RenderContext* ctx) +{ + auto group = static_cast(*child); + + if (!group->visible) return; + + //Prepare render data + group->scene = parent->scene; + group->reqFragment |= ctx->reqFragment; + + //generate a merging shape to consolidate partial shapes into a single entity + if (group->mergeable()) _draw(parent, nullptr, ctx); + + Inlist contexts; + auto propagator = group->mergeable() ? ctx->propagator : static_cast(PP(ctx->propagator)->duplicate(group->pooling())); + contexts.back(new RenderContext(*ctx, propagator, group->mergeable())); + + updateChildren(group, frameNo, contexts); + + contexts.free(); +} + + +static void _updateStroke(LottieStroke* stroke, float frameNo, RenderContext* ctx, LottieExpressions* exps) +{ + ctx->propagator->strokeWidth(stroke->width(frameNo, exps)); + ctx->propagator->strokeCap(stroke->cap); + ctx->propagator->strokeJoin(stroke->join); + ctx->propagator->strokeMiterlimit(stroke->miterLimit); + + if (stroke->dashattr) { + float dashes[2]; + dashes[0] = stroke->dashSize(frameNo, exps); + dashes[1] = dashes[0] + stroke->dashGap(frameNo, exps); + ctx->propagator->strokeDash(dashes, 2, stroke->dashOffset(frameNo, exps)); + } else { + ctx->propagator->strokeDash(nullptr, 0); + } +} + + +static bool _fragmented(LottieGroup* parent, LottieObject** child, Inlist& contexts, RenderContext* ctx) +{ + if (!ctx->reqFragment) return false; + if (ctx->fragmenting) return true; + + contexts.back(new RenderContext(*ctx, static_cast(PP(ctx->propagator)->duplicate(parent->pooling())))); + auto fragment = contexts.tail; + fragment->begin = child - 1; + ctx->fragmenting = true; + + return false; +} + + +void LottieBuilder::updateSolidStroke(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx) +{ + if (_fragmented(parent, child, contexts, ctx)) return; + + auto stroke = static_cast(*child); + + ctx->merging = nullptr; + auto color = stroke->color(frameNo, exps); + ctx->propagator->strokeFill(color.rgb[0], color.rgb[1], color.rgb[2], stroke->opacity(frameNo, exps)); + _updateStroke(static_cast(stroke), frameNo, ctx, exps); +} + + +void LottieBuilder::updateGradientStroke(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx) +{ + if (_fragmented(parent, child, contexts, ctx)) return; + + auto stroke = static_cast(*child); + + ctx->merging = nullptr; + ctx->propagator->strokeFill(unique_ptr(stroke->fill(frameNo, exps))); + _updateStroke(static_cast(stroke), frameNo, ctx, exps); +} + + +void LottieBuilder::updateSolidFill(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx) +{ + if (_fragmented(parent, child, contexts, ctx)) return; + + auto fill = static_cast(*child); + + ctx->merging = nullptr; + auto color = fill->color(frameNo, exps); + ctx->propagator->fill(color.rgb[0], color.rgb[1], color.rgb[2], fill->opacity(frameNo, exps)); + ctx->propagator->fill(fill->rule); + + if (ctx->propagator->strokeWidth() > 0) ctx->propagator->order(true); +} + + +void LottieBuilder::updateGradientFill(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx) +{ + if (_fragmented(parent, child, contexts, ctx)) return; + + auto fill = static_cast(*child); + + ctx->merging = nullptr; + //TODO: reuse the fill instance? + ctx->propagator->fill(unique_ptr(fill->fill(frameNo, exps))); + ctx->propagator->fill(fill->rule); + + if (ctx->propagator->strokeWidth() > 0) ctx->propagator->order(true); +} + + +static bool _draw(LottieGroup* parent, LottieShape* shape, RenderContext* ctx) +{ + if (ctx->merging) return false; + + if (shape) { + ctx->merging = shape->pooling(); + PP(ctx->propagator)->duplicate(ctx->merging); + } else { + ctx->merging = static_cast(ctx->propagator->duplicate()); + } + + parent->scene->push(cast(ctx->merging)); + + return true; +} + + +static void _repeat(LottieGroup* parent, Shape* path, RenderContext* ctx) +{ + Array propagators; + propagators.push(ctx->propagator); + Array shapes; + + for (auto repeater = ctx->repeaters.end() - 1; repeater >= ctx->repeaters.begin(); --repeater) { + shapes.reserve(repeater->cnt); + + for (int i = 0; i < repeater->cnt; ++i) { + auto multiplier = repeater->offset + static_cast(i); + + for (auto propagator = propagators.begin(); propagator < propagators.end(); ++propagator) { + auto shape = static_cast((*propagator)->duplicate()); + P(shape)->rs.path = P(path)->rs.path; + + auto opacity = repeater->interpOpacity ? lerp(repeater->startOpacity, repeater->endOpacity, static_cast(i + 1) / repeater->cnt) : repeater->startOpacity; + shape->opacity(opacity); + + Matrix m; + tvg::identity(&m); + translate(&m, repeater->position.x * multiplier + repeater->anchor.x, repeater->position.y * multiplier + repeater->anchor.y); + scale(&m, powf(repeater->scale.x * 0.01f, multiplier), powf(repeater->scale.y * 0.01f, multiplier)); + rotate(&m, repeater->rotation * multiplier); + translateR(&m, -repeater->anchor.x, -repeater->anchor.y); + m = repeater->transform * m; + + Matrix inv; + inverse(&repeater->transform, &inv); + shape->transform(m * (inv * shape->transform())); + shapes.push(shape); + } + } + + propagators.clear(); + propagators.reserve(shapes.count); + + //push repeat shapes in order. + if (repeater->inorder) { + for (auto shape = shapes.begin(); shape < shapes.end(); ++shape) { + parent->scene->push(cast(*shape)); + propagators.push(*shape); + } + } else if (!shapes.empty()) { + for (auto shape = shapes.end() - 1; shape >= shapes.begin(); --shape) { + parent->scene->push(cast(*shape)); + propagators.push(*shape); + } + } + shapes.clear(); + } +} + + +static void _appendRect(Shape* shape, float x, float y, float w, float h, float r, const LottieOffsetModifier* offsetPath, Matrix* transform, bool clockwise) +{ + //sharp rect + if (tvg::zero(r)) { + PathCommand commands[] = { + PathCommand::MoveTo, PathCommand::LineTo, PathCommand::LineTo, + PathCommand::LineTo, PathCommand::Close + }; + + Point points[4]; + if (clockwise) { + points[0] = {x + w, y}; + points[1] = {x + w, y + h}; + points[2] = {x, y + h}; + points[3] = {x, y}; + } else { + points[0] = {x + w, y}; + points[1] = {x, y}; + points[2] = {x, y + h}; + points[3] = {x + w, y + h}; + } + if (transform) { + for (int i = 0; i < 4; i++) { + points[i] *= *transform; + } + } + + if (offsetPath) offsetPath->modifyRect(commands, 5, points, 4, P(shape)->rs.path.cmds, P(shape)->rs.path.pts); + else shape->appendPath(commands, 5, points, 4); + //round rect + } else { + constexpr int cmdCnt = 10; + PathCommand commands[cmdCnt]; + + auto halfW = w * 0.5f; + auto halfH = h * 0.5f; + auto rx = r > halfW ? halfW : r; + auto ry = r > halfH ? halfH : r; + auto hrx = rx * PATH_KAPPA; + auto hry = ry * PATH_KAPPA; + + constexpr int ptsCnt = 17; + Point points[ptsCnt]; + if (clockwise) { + commands[0] = PathCommand::MoveTo; commands[1] = PathCommand::LineTo; commands[2] = PathCommand::CubicTo; + commands[3] = PathCommand::LineTo; commands[4] = PathCommand::CubicTo;commands[5] = PathCommand::LineTo; + commands[6] = PathCommand::CubicTo; commands[7] = PathCommand::LineTo; commands[8] = PathCommand::CubicTo; + commands[9] = PathCommand::Close; + + points[0] = {x + w, y + ry}; //moveTo + points[1] = {x + w, y + h - ry}; //lineTo + points[2] = {x + w, y + h - ry + hry}; points[3] = {x + w - rx + hrx, y + h}; points[4] = {x + w - rx, y + h}; //cubicTo + points[5] = {x + rx, y + h}, //lineTo + points[6] = {x + rx - hrx, y + h}; points[7] = {x, y + h - ry + hry}; points[8] = {x, y + h - ry}; //cubicTo + points[9] = {x, y + ry}, //lineTo + points[10] = {x, y + ry - hry}; points[11] = {x + rx - hrx, y}; points[12] = {x + rx, y}; //cubicTo + points[13] = {x + w - rx, y}; //lineTo + points[14] = {x + w - rx + hrx, y}; points[15] = {x + w, y + ry - hry}; points[16] = {x + w, y + ry}; //cubicTo + } else { + commands[0] = PathCommand::MoveTo; commands[1] = PathCommand::CubicTo; commands[2] = PathCommand::LineTo; + commands[3] = PathCommand::CubicTo; commands[4] = PathCommand::LineTo; commands[5] = PathCommand::CubicTo; + commands[6] = PathCommand::LineTo; commands[7] = PathCommand::CubicTo; commands[8] = PathCommand::LineTo; + commands[9] = PathCommand::Close; + + points[0] = {x + w, y + ry}; //moveTo + points[1] = {x + w, y + ry - hry}; points[2] = {x + w - rx + hrx, y}; points[3] = {x + w - rx, y}; //cubicTo + points[4] = {x + rx, y}, //lineTo + points[5] = {x + rx - hrx, y}; points[6] = {x, y + ry - hry}; points[7] = {x, y + ry}; //cubicTo + points[8] = {x, y + h - ry}; //lineTo + points[9] = {x, y + h - ry + hry}; points[10] = {x + rx - hrx, y + h}; points[11] = {x + rx, y + h}; //cubicTo + points[12] = {x + w - rx, y + h}; //lineTo + points[13] = {x + w - rx + hrx, y + h}; points[14] = {x + w, y + h - ry + hry}; points[15] = {x + w, y + h - ry}; //cubicTo + points[16] = {x + w, y + ry}; //lineTo + } + if (transform) { + for (int i = 0; i < ptsCnt; i++) { + points[i] *= *transform; + } + } + + if (offsetPath) offsetPath->modifyRect(commands, cmdCnt, points, ptsCnt, P(shape)->rs.path.cmds, P(shape)->rs.path.pts); + else shape->appendPath(commands, cmdCnt, points, ptsCnt); + } +} + + +void LottieBuilder::updateRect(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, RenderContext* ctx) +{ + auto rect = static_cast(*child); + + auto position = rect->position(frameNo, exps); + auto size = rect->size(frameNo, exps); + auto r = rect->radius(frameNo, exps); + if (r == 0.0f) { + if (ctx->roundness) ctx->roundness->modifyRect(size, r); + } else { + r = std::min({r, size.x * 0.5f, size.y * 0.5f}); + } + + if (!ctx->repeaters.empty()) { + auto shape = rect->pooling(); + shape->reset(); + _appendRect(shape, position.x - size.x * 0.5f, position.y - size.y * 0.5f, size.x, size.y, r, ctx->offsetPath, ctx->transform, rect->clockwise); + _repeat(parent, shape, ctx); + } else { + _draw(parent, rect, ctx); + _appendRect(ctx->merging, position.x - size.x * 0.5f, position.y - size.y * 0.5f, size.x, size.y, r, ctx->offsetPath, ctx->transform, rect->clockwise); + } +} + + +static void _appendCircle(Shape* shape, float cx, float cy, float rx, float ry, const LottieOffsetModifier* offsetPath, Matrix* transform, bool clockwise) +{ + if (offsetPath) offsetPath->modifyEllipse(rx, ry); + + if (rx == 0.0f || ry == 0.0f) return; + + auto rxKappa = rx * PATH_KAPPA; + auto ryKappa = ry * PATH_KAPPA; + + constexpr int cmdsCnt = 6; + PathCommand commands[cmdsCnt] = { + PathCommand::MoveTo, PathCommand::CubicTo, PathCommand::CubicTo, + PathCommand::CubicTo, PathCommand::CubicTo, PathCommand::Close + }; + + constexpr int ptsCnt = 13; + Point points[ptsCnt]; + + if (clockwise) { + points[0] = {cx, cy - ry}; //moveTo + points[1] = {cx + rxKappa, cy - ry}; points[2] = {cx + rx, cy - ryKappa}; points[3] = {cx + rx, cy}; //cubicTo + points[4] = {cx + rx, cy + ryKappa}; points[5] = {cx + rxKappa, cy + ry}; points[6] = {cx, cy + ry}; //cubicTo + points[7] = {cx - rxKappa, cy + ry}; points[8] = {cx - rx, cy + ryKappa}; points[9] = {cx - rx, cy}; //cubicTo + points[10] = {cx - rx, cy - ryKappa}; points[11] = {cx - rxKappa, cy - ry}; points[12] = {cx, cy - ry}; //cubicTo + } else { + points[0] = {cx, cy - ry}; //moveTo + points[1] = {cx - rxKappa, cy - ry}; points[2] = {cx - rx, cy - ryKappa}; points[3] = {cx - rx, cy}; //cubicTo + points[4] = {cx - rx, cy + ryKappa}; points[5] = {cx - rxKappa, cy + ry}; points[6] = {cx, cy + ry}; //cubicTo + points[7] = {cx + rxKappa, cy + ry}; points[8] = {cx + rx, cy + ryKappa}; points[9] = {cx + rx, cy}; //cubicTo + points[10] = {cx + rx, cy - ryKappa}; points[11] = {cx + rxKappa, cy - ry}; points[12] = {cx, cy - ry}; //cubicTo + } + + if (transform) { + for (int i = 0; i < ptsCnt; ++i) { + points[i] *= *transform; + } + } + + shape->appendPath(commands, cmdsCnt, points, ptsCnt); +} + + +void LottieBuilder::updateEllipse(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, RenderContext* ctx) +{ + auto ellipse = static_cast(*child); + + auto position = ellipse->position(frameNo, exps); + auto size = ellipse->size(frameNo, exps); + + if (!ctx->repeaters.empty()) { + auto shape = ellipse->pooling(); + shape->reset(); + _appendCircle(shape, position.x, position.y, size.x * 0.5f, size.y * 0.5f, ctx->offsetPath, ctx->transform, ellipse->clockwise); + _repeat(parent, shape, ctx); + } else { + _draw(parent, ellipse, ctx); + _appendCircle(ctx->merging, position.x, position.y, size.x * 0.5f, size.y * 0.5f, ctx->offsetPath, ctx->transform, ellipse->clockwise); + } +} + + +void LottieBuilder::updatePath(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, RenderContext* ctx) +{ + auto path = static_cast(*child); + + if (!ctx->repeaters.empty()) { + auto shape = path->pooling(); + shape->reset(); + path->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, ctx->transform, ctx->roundness, ctx->offsetPath, exps); + _repeat(parent, shape, ctx); + } else { + _draw(parent, path, ctx); + if (path->pathset(frameNo, P(ctx->merging)->rs.path.cmds, P(ctx->merging)->rs.path.pts, ctx->transform, ctx->roundness, ctx->offsetPath, exps)) { + P(ctx->merging)->update(RenderUpdateFlag::Path); + } + } +} + + +static void _updateStar(TVG_UNUSED LottieGroup* parent, LottiePolyStar* star, Matrix* transform, const LottieRoundnessModifier* roundness, const LottieOffsetModifier* offsetPath, float frameNo, Shape* merging, LottieExpressions* exps) +{ + static constexpr auto POLYSTAR_MAGIC_NUMBER = 0.47829f / 0.28f; + + auto ptsCnt = star->ptsCnt(frameNo, exps); + auto innerRadius = star->innerRadius(frameNo, exps); + auto outerRadius = star->outerRadius(frameNo, exps); + auto innerRoundness = star->innerRoundness(frameNo, exps) * 0.01f; + auto outerRoundness = star->outerRoundness(frameNo, exps) * 0.01f; + + auto angle = deg2rad(-90.0f); + auto partialPointRadius = 0.0f; + auto anglePerPoint = (2.0f * MATH_PI / ptsCnt); + auto halfAnglePerPoint = anglePerPoint * 0.5f; + auto partialPointAmount = ptsCnt - floorf(ptsCnt); + auto longSegment = false; + auto numPoints = size_t(ceilf(ptsCnt) * 2); + auto direction = star->clockwise ? 1.0f : -1.0f; + auto hasRoundness = false; + bool roundedCorner = roundness && (tvg::zero(innerRoundness) || tvg::zero(outerRoundness)); + + Shape* shape; + if (roundedCorner || offsetPath) { + shape = star->pooling(); + shape->reset(); + } else { + shape = merging; + } + + float x, y; + + if (!tvg::zero(partialPointAmount)) { + angle += halfAnglePerPoint * (1.0f - partialPointAmount) * direction; + } + + if (!tvg::zero(partialPointAmount)) { + partialPointRadius = innerRadius + partialPointAmount * (outerRadius - innerRadius); + x = partialPointRadius * cosf(angle); + y = partialPointRadius * sinf(angle); + angle += anglePerPoint * partialPointAmount * 0.5f * direction; + } else { + x = outerRadius * cosf(angle); + y = outerRadius * sinf(angle); + angle += halfAnglePerPoint * direction; + } + + if (tvg::zero(innerRoundness) && tvg::zero(outerRoundness)) { + P(shape)->rs.path.pts.reserve(numPoints + 2); + P(shape)->rs.path.cmds.reserve(numPoints + 3); + } else { + P(shape)->rs.path.pts.reserve(numPoints * 3 + 2); + P(shape)->rs.path.cmds.reserve(numPoints + 3); + hasRoundness = true; + } + + Point in = {x, y}; + if (transform) in *= *transform; + shape->moveTo(in.x, in.y); + + for (size_t i = 0; i < numPoints; i++) { + auto radius = longSegment ? outerRadius : innerRadius; + auto dTheta = halfAnglePerPoint; + if (!tvg::zero(partialPointRadius) && i == numPoints - 2) { + dTheta = anglePerPoint * partialPointAmount * 0.5f; + } + if (!tvg::zero(partialPointRadius) && i == numPoints - 1) { + radius = partialPointRadius; + } + auto previousX = x; + auto previousY = y; + x = radius * cosf(angle); + y = radius * sinf(angle); + + if (hasRoundness) { + auto cp1Theta = (tvg::atan2(previousY, previousX) - MATH_PI2 * direction); + auto cp1Dx = cosf(cp1Theta); + auto cp1Dy = sinf(cp1Theta); + auto cp2Theta = (tvg::atan2(y, x) - MATH_PI2 * direction); + auto cp2Dx = cosf(cp2Theta); + auto cp2Dy = sinf(cp2Theta); + + auto cp1Roundness = longSegment ? innerRoundness : outerRoundness; + auto cp2Roundness = longSegment ? outerRoundness : innerRoundness; + auto cp1Radius = longSegment ? innerRadius : outerRadius; + auto cp2Radius = longSegment ? outerRadius : innerRadius; + + auto cp1x = cp1Radius * cp1Roundness * POLYSTAR_MAGIC_NUMBER * cp1Dx / ptsCnt; + auto cp1y = cp1Radius * cp1Roundness * POLYSTAR_MAGIC_NUMBER * cp1Dy / ptsCnt; + auto cp2x = cp2Radius * cp2Roundness * POLYSTAR_MAGIC_NUMBER * cp2Dx / ptsCnt; + auto cp2y = cp2Radius * cp2Roundness * POLYSTAR_MAGIC_NUMBER * cp2Dy / ptsCnt; + + if (!tvg::zero(partialPointAmount) && ((i == 0) || (i == numPoints - 1))) { + cp1x *= partialPointAmount; + cp1y *= partialPointAmount; + cp2x *= partialPointAmount; + cp2y *= partialPointAmount; + } + Point in2 = {previousX - cp1x, previousY - cp1y}; + Point in3 = {x + cp2x, y + cp2y}; + Point in4 = {x, y}; + if (transform) { + in2 *= *transform; + in3 *= *transform; + in4 *= *transform; + } + shape->cubicTo(in2.x, in2.y, in3.x, in3.y, in4.x, in4.y); + } else { + Point in = {x, y}; + if (transform) in *= *transform; + shape->lineTo(in.x, in.y); + } + angle += dTheta * direction; + longSegment = !longSegment; + } + shape->close(); + + if (roundedCorner) { + if (offsetPath) { + auto intermediate = Shape::gen(); + roundness->modifyPolystar(P(shape)->rs.path.cmds, P(shape)->rs.path.pts, P(intermediate)->rs.path.cmds, P(intermediate)->rs.path.pts, outerRoundness, hasRoundness); + offsetPath->modifyPolystar(P(intermediate)->rs.path.cmds, P(intermediate)->rs.path.pts, P(merging)->rs.path.cmds, P(merging)->rs.path.pts); + } else { + roundness->modifyPolystar(P(shape)->rs.path.cmds, P(shape)->rs.path.pts, P(merging)->rs.path.cmds, P(merging)->rs.path.pts, outerRoundness, hasRoundness); + } + } else if (offsetPath) offsetPath->modifyPolystar(P(shape)->rs.path.cmds, P(shape)->rs.path.pts, P(merging)->rs.path.cmds, P(merging)->rs.path.pts); +} + + +static void _updatePolygon(LottieGroup* parent, LottiePolyStar* star, Matrix* transform, const LottieRoundnessModifier* roundness, const LottieOffsetModifier* offsetPath, float frameNo, Shape* merging, LottieExpressions* exps) +{ + static constexpr auto POLYGON_MAGIC_NUMBER = 0.25f; + + auto ptsCnt = size_t(floor(star->ptsCnt(frameNo, exps))); + auto radius = star->outerRadius(frameNo, exps); + auto outerRoundness = star->outerRoundness(frameNo, exps) * 0.01f; + + auto angle = deg2rad(-90.0f); + auto anglePerPoint = 2.0f * MATH_PI / float(ptsCnt); + auto direction = star->clockwise ? 1.0f : -1.0f; + auto hasRoundness = !tvg::zero(outerRoundness); + bool roundedCorner = roundness && !hasRoundness; + auto x = radius * cosf(angle); + auto y = radius * sinf(angle); + + angle += anglePerPoint * direction; + + Shape* shape; + if (roundedCorner || offsetPath) { + shape = star->pooling(); + shape->reset(); + } else { + shape = merging; + if (hasRoundness) { + P(shape)->rs.path.pts.reserve(ptsCnt * 3 + 2); + P(shape)->rs.path.cmds.reserve(ptsCnt + 3); + } else { + P(shape)->rs.path.pts.reserve(ptsCnt + 2); + P(shape)->rs.path.cmds.reserve(ptsCnt + 3); + } + } + + Point in = {x, y}; + if (transform) in *= *transform; + shape->moveTo(in.x, in.y); + + for (size_t i = 0; i < ptsCnt; i++) { + auto previousX = x; + auto previousY = y; + x = (radius * cosf(angle)); + y = (radius * sinf(angle)); + + if (hasRoundness) { + auto cp1Theta = tvg::atan2(previousY, previousX) - MATH_PI2 * direction; + auto cp1Dx = cosf(cp1Theta); + auto cp1Dy = sinf(cp1Theta); + auto cp2Theta = tvg::atan2(y, x) - MATH_PI2 * direction; + auto cp2Dx = cosf(cp2Theta); + auto cp2Dy = sinf(cp2Theta); + + auto cp1x = radius * outerRoundness * POLYGON_MAGIC_NUMBER * cp1Dx; + auto cp1y = radius * outerRoundness * POLYGON_MAGIC_NUMBER * cp1Dy; + auto cp2x = radius * outerRoundness * POLYGON_MAGIC_NUMBER * cp2Dx; + auto cp2y = radius * outerRoundness * POLYGON_MAGIC_NUMBER * cp2Dy; + + Point in2 = {previousX - cp1x, previousY - cp1y}; + Point in3 = {x + cp2x, y + cp2y}; + Point in4 = {x, y}; + if (transform) { + in2 *= *transform; + in3 *= *transform; + in4 *= *transform; + } + shape->cubicTo(in2.x, in2.y, in3.x, in3.y, in4.x, in4.y); + } else { + Point in = {x, y}; + if (transform) in *= *transform; + shape->lineTo(in.x, in.y); + } + angle += anglePerPoint * direction; + } + shape->close(); + + if (roundedCorner) { + if (offsetPath) { + auto intermediate = Shape::gen(); + roundness->modifyPolystar(P(shape)->rs.path.cmds, P(shape)->rs.path.pts, P(intermediate)->rs.path.cmds, P(intermediate)->rs.path.pts, 0.0f, false); + offsetPath->modifyPolystar(P(intermediate)->rs.path.cmds, P(intermediate)->rs.path.pts, P(merging)->rs.path.cmds, P(merging)->rs.path.pts); + } else { + roundness->modifyPolystar(P(shape)->rs.path.cmds, P(shape)->rs.path.pts, P(merging)->rs.path.cmds, P(merging)->rs.path.pts, 0.0f, false); + } + } else if (offsetPath) offsetPath->modifyPolystar(P(shape)->rs.path.cmds, P(shape)->rs.path.pts, P(merging)->rs.path.cmds, P(merging)->rs.path.pts); +} + + +void LottieBuilder::updatePolystar(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, RenderContext* ctx) +{ + auto star = static_cast(*child); + + //Optimize: Can we skip the individual coords transform? + Matrix matrix; + tvg::identity(&matrix); + auto position = star->position(frameNo, exps); + translate(&matrix, position.x, position.y); + rotate(&matrix, star->rotation(frameNo, exps)); + + if (ctx->transform) matrix = *ctx->transform * matrix; + + auto identity = tvg::identity((const Matrix*)&matrix); + + if (!ctx->repeaters.empty()) { + auto shape = star->pooling(); + shape->reset(); + if (star->type == LottiePolyStar::Star) _updateStar(parent, star, identity ? nullptr : &matrix, ctx->roundness, ctx->offsetPath, frameNo, shape, exps); + else _updatePolygon(parent, star, identity ? nullptr : &matrix, ctx->roundness, ctx->offsetPath, frameNo, shape, exps); + _repeat(parent, shape, ctx); + } else { + _draw(parent, star, ctx); + if (star->type == LottiePolyStar::Star) _updateStar(parent, star, identity ? nullptr : &matrix, ctx->roundness, ctx->offsetPath, frameNo, ctx->merging, exps); + else _updatePolygon(parent, star, identity ? nullptr : &matrix, ctx->roundness, ctx->offsetPath, frameNo, ctx->merging, exps); + P(ctx->merging)->update(RenderUpdateFlag::Path); + } +} + + +void LottieBuilder::updateRoundedCorner(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, RenderContext* ctx) +{ + auto roundedCorner = static_cast(*child); + auto r = roundedCorner->radius(frameNo, exps); + if (r < LottieRoundnessModifier::ROUNDNESS_EPSILON) return; + + if (!ctx->roundness) ctx->roundness = new LottieRoundnessModifier(r); + else if (ctx->roundness->r < r) ctx->roundness->r = r; +} + + +void LottieBuilder::updateOffsetPath(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, RenderContext* ctx) +{ + auto offsetPath = static_cast(*child); + if (!ctx->offsetPath) ctx->offsetPath = new LottieOffsetModifier(offsetPath->offset(frameNo, exps), offsetPath->miterLimit(frameNo, exps), offsetPath->join); +} + + +void LottieBuilder::updateRepeater(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, RenderContext* ctx) +{ + auto repeater = static_cast(*child); + + RenderRepeater r; + r.cnt = static_cast(repeater->copies(frameNo, exps)); + r.transform = ctx->propagator->transform(); + r.offset = repeater->offset(frameNo, exps); + r.position = repeater->position(frameNo, exps); + r.anchor = repeater->anchor(frameNo, exps); + r.scale = repeater->scale(frameNo, exps); + r.rotation = repeater->rotation(frameNo, exps); + r.startOpacity = repeater->startOpacity(frameNo, exps); + r.endOpacity = repeater->endOpacity(frameNo, exps); + r.inorder = repeater->inorder; + r.interpOpacity = (r.startOpacity == r.endOpacity) ? false : true; + ctx->repeaters.push(r); + + ctx->merging = nullptr; +} + + +void LottieBuilder::updateTrimpath(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, RenderContext* ctx) +{ + auto trimpath = static_cast(*child); + + float begin, end; + trimpath->segment(frameNo, begin, end, exps); + + if (P(ctx->propagator)->rs.stroke) { + auto pbegin = P(ctx->propagator)->rs.stroke->trim.begin; + auto pend = P(ctx->propagator)->rs.stroke->trim.end; + auto length = fabsf(pend - pbegin); + begin = (length * begin) + pbegin; + end = (length * end) + pbegin; + } + + P(ctx->propagator)->strokeTrim(begin, end, trimpath->type == LottieTrimpath::Type::Simultaneous); + ctx->merging = nullptr; +} + + +void LottieBuilder::updateChildren(LottieGroup* parent, float frameNo, Inlist& contexts) +{ + contexts.head->begin = parent->children.end() - 1; + + while (!contexts.empty()) { + auto ctx = contexts.front(); + ctx->reqFragment = parent->reqFragment; + for (auto child = ctx->begin; child >= parent->children.data; --child) { + //Here switch-case statements are more performant than virtual methods. + switch ((*child)->type) { + case LottieObject::Group: { + updateGroup(parent, child, frameNo, contexts, ctx); + break; + } + case LottieObject::Transform: { + updateTransform(parent, child, frameNo, contexts, ctx); + break; + } + case LottieObject::SolidFill: { + updateSolidFill(parent, child, frameNo, contexts, ctx); + break; + } + case LottieObject::SolidStroke: { + updateSolidStroke(parent, child, frameNo, contexts, ctx); + break; + } + case LottieObject::GradientFill: { + updateGradientFill(parent, child, frameNo, contexts, ctx); + break; + } + case LottieObject::GradientStroke: { + updateGradientStroke(parent, child, frameNo, contexts, ctx); + break; + } + case LottieObject::Rect: { + updateRect(parent, child, frameNo, contexts, ctx); + break; + } + case LottieObject::Ellipse: { + updateEllipse(parent, child, frameNo, contexts, ctx); + break; + } + case LottieObject::Path: { + updatePath(parent, child, frameNo, contexts, ctx); + break; + } + case LottieObject::Polystar: { + updatePolystar(parent, child, frameNo, contexts, ctx); + break; + } + case LottieObject::Trimpath: { + updateTrimpath(parent, child, frameNo, contexts, ctx); + break; + } + case LottieObject::Repeater: { + updateRepeater(parent, child, frameNo, contexts, ctx); + break; + } + case LottieObject::RoundedCorner: { + updateRoundedCorner(parent, child, frameNo, contexts, ctx); + break; + } + case LottieObject::OffsetPath: { + updateOffsetPath(parent, child, frameNo, contexts, ctx); + break; + } + default: break; + } + if (ctx->propagator->opacity() == 0) break; + } + delete(ctx); + } +} + + +void LottieBuilder::updatePrecomp(LottieComposition* comp, LottieLayer* precomp, float frameNo) +{ + if (precomp->children.empty()) return; + + frameNo = precomp->remap(comp, frameNo, exps); + + for (auto c = precomp->children.end() - 1; c >= precomp->children.begin(); --c) { + auto child = static_cast(*c); + if (!child->matteSrc) updateLayer(comp, precomp->scene, child, frameNo); + } + + //clip the layer viewport + auto clipper = precomp->statical.pooling(true); + clipper->transform(precomp->cache.matrix); + precomp->scene->clip(cast(clipper)); +} + + +void LottieBuilder::updateSolid(LottieLayer* layer) +{ + auto solidFill = layer->statical.pooling(true); + solidFill->opacity(layer->cache.opacity); + layer->scene->push(cast(solidFill)); +} + + +void LottieBuilder::updateImage(LottieGroup* layer) +{ + auto image = static_cast(layer->children.first()); + layer->scene->push(tvg::cast(image->pooling(true))); +} + + +void LottieBuilder::updateText(LottieLayer* layer, float frameNo) +{ + auto text = static_cast(layer->children.first()); + auto textGrouping = text->alignOption.grouping; + auto& doc = text->doc(frameNo); + auto p = doc.text; + + if (!p || !text->font) return; + + auto scale = doc.size; + Point cursor = {0.0f, 0.0f}; + auto scene = Scene::gen(); + auto textGroup = Scene::gen(); + int line = 0; + int space = 0; + auto lineSpacing = 0.0f; + auto totalLineSpacing = 0.0f; + + //text string + int idx = 0; + auto totalChars = strlen(p); + while (true) { + //TODO: remove nested scenes. + //end of text, new line of the cursor position + if (*p == 13 || *p == 3 || *p == '\0') { + //text layout position + auto ascent = text->font->ascent * scale; + if (ascent > doc.bbox.size.y) ascent = doc.bbox.size.y; + Point layout = {doc.bbox.pos.x, doc.bbox.pos.y + ascent - doc.shift}; + + //adjust the layout + if (doc.justify == 1) layout.x += doc.bbox.size.x - (cursor.x * scale); //right aligned + else if (doc.justify == 2) layout.x += (doc.bbox.size.x * 0.5f) - (cursor.x * 0.5f * scale); //center aligned + + //new text group, single scene based on text-grouping + scene->push(std::move(textGroup)); + textGroup = Scene::gen(); + textGroup->translate(cursor.x, cursor.y); + + scene->translate(layout.x, layout.y); + scene->scale(scale); + + layer->scene->push(std::move(scene)); + + if (*p == '\0') break; + ++p; + + totalLineSpacing += lineSpacing; + lineSpacing = 0.0f; + + //new text group, single scene for each line + scene = Scene::gen(); + cursor.x = 0.0f; + cursor.y = (++line * doc.height + totalLineSpacing) / scale; + continue; + } + + if (*p == ' ') { + ++space; + if (textGrouping == LottieText::AlignOption::Group::Word) { + //new text group, single scene for each word + scene->push(std::move(textGroup)); + textGroup = Scene::gen(); + textGroup->translate(cursor.x, cursor.y); + } + } + + //find the glyph + bool found = false; + for (auto g = text->font->chars.begin(); g < text->font->chars.end(); ++g) { + auto glyph = *g; + //draw matched glyphs + if (!strncmp(glyph->code, p, glyph->len)) { + if (textGrouping == LottieText::AlignOption::Group::Chars || textGrouping == LottieText::AlignOption::Group::All) { + //new text group, single scene for each characters + scene->push(std::move(textGroup)); + textGroup = Scene::gen(); + textGroup->translate(cursor.x, cursor.y); + } + + auto& textGroupMatrix = textGroup->transform(); + auto shape = text->pooling(); + shape->reset(); + for (auto g = glyph->children.begin(); g < glyph->children.end(); ++g) { + auto group = static_cast(*g); + for (auto p = group->children.begin(); p < group->children.end(); ++p) { + if (static_cast(*p)->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, nullptr, nullptr, nullptr)) { + P(shape)->update(RenderUpdateFlag::Path); + } + } + } + shape->fill(doc.color.rgb[0], doc.color.rgb[1], doc.color.rgb[2]); + shape->translate(cursor.x - textGroupMatrix.e13, cursor.y - textGroupMatrix.e23); + shape->opacity(255); + + if (doc.stroke.render) { + shape->strokeJoin(StrokeJoin::Round); + shape->strokeWidth(doc.stroke.width / scale); + shape->strokeFill(doc.stroke.color.rgb[0], doc.stroke.color.rgb[1], doc.stroke.color.rgb[2]); + } + + bool needGroup = false; + if (!text->ranges.empty()) { + Point scaling = {1.0f, 1.0f}; + auto rotation = 0.0f; + Point translation = {0.0f, 0.0f}; + + //text range process + for (auto s = text->ranges.begin(); s < text->ranges.end(); ++s) { + float start, end; + (*s)->range(frameNo, float(totalChars), start, end); + + auto basedIdx = idx; + if ((*s)->based == LottieTextRange::Based::CharsExcludingSpaces) basedIdx = idx - space; + else if ((*s)->based == LottieTextRange::Based::Words) basedIdx = line + space; + else if ((*s)->based == LottieTextRange::Based::Lines) basedIdx = line; + + if (basedIdx < start || basedIdx >= end) continue; + needGroup = true; + + translation = translation + (*s)->style.position(frameNo); + scaling = scaling * (*s)->style.scale(frameNo) * 0.01f; + rotation += (*s)->style.rotation(frameNo); + + shape->opacity((*s)->style.opacity(frameNo)); + + auto color = (*s)->style.fillColor(frameNo); + shape->fill(color.rgb[0], color.rgb[1], color.rgb[2], (*s)->style.fillOpacity(frameNo)); + + if (doc.stroke.render) { + auto strokeColor = (*s)->style.strokeColor(frameNo); + shape->strokeWidth((*s)->style.strokeWidth(frameNo) / scale); + shape->strokeFill(strokeColor.rgb[0], strokeColor.rgb[1], strokeColor.rgb[2], (*s)->style.strokeOpacity(frameNo)); + } + cursor.x += (*s)->style.letterSpacing(frameNo); + + auto spacing = (*s)->style.lineSpacing(frameNo); + if (spacing > lineSpacing) lineSpacing = spacing; + } + + // TextGroup transformation is performed once + if (textGroup->paints().size() == 0 && needGroup) { + tvg::identity(&textGroupMatrix); + translate(&textGroupMatrix, cursor.x, cursor.y); + + auto alignment = text->alignOption.anchor(frameNo); + + // center pivoting + textGroupMatrix.e13 += alignment.x; + textGroupMatrix.e23 += alignment.y; + + rotate(&textGroupMatrix, rotation); + + auto pivotX = alignment.x * -1; + auto pivotY = alignment.y * -1; + + //center pivoting + textGroupMatrix.e13 += (pivotX * textGroupMatrix.e11 + pivotX * textGroupMatrix.e12); + textGroupMatrix.e23 += (pivotY * textGroupMatrix.e21 + pivotY * textGroupMatrix.e22); + + textGroup->transform(textGroupMatrix); + } + + auto& matrix = shape->transform(); + tvg::identity(&matrix); + translate(&matrix, (translation.x / scale + cursor.x) - textGroupMatrix.e13, (translation.y / scale + cursor.y) - textGroupMatrix.e23); + tvg::scale(&matrix, scaling.x, scaling.y); + shape->transform(matrix); + } + + if (needGroup) { + textGroup->push(cast(shape)); + } else { + // When text isn't selected, exclude the shape from the text group + auto& matrix = shape->transform(); + matrix.e13 = cursor.x; + matrix.e23 = cursor.y; + shape->transform(matrix); + scene->push(cast(shape)); + } + + p += glyph->len; + idx += glyph->len; + + //advance the cursor position horizontally + cursor.x += glyph->width + doc.tracking; + + found = true; + break; + } + } + + if (!found) { + ++p; + ++idx; + } + } +} + + +void LottieBuilder::updateMaskings(LottieLayer* layer, float frameNo) +{ + if (layer->masks.count == 0) return; + + //Apply the base mask + auto pMask = static_cast(layer->masks[0]); + auto pMethod = pMask->method; + auto opacity = pMask->opacity(frameNo); + auto expand = pMask->expand(frameNo); + + auto pShape = layer->pooling(); + pShape->reset(); + pShape->fill(255, 255, 255, opacity); + pShape->transform(layer->cache.matrix); + + //Apply Masking Expansion (Offset) + if (expand == 0.0f) { + pMask->pathset(frameNo, P(pShape)->rs.path.cmds, P(pShape)->rs.path.pts, nullptr, nullptr, nullptr, exps); + } else { + //TODO: Once path direction support is implemented, ensure that the direction is ignored here + auto offset = LottieOffsetModifier(pMask->expand(frameNo)); + pMask->pathset(frameNo, P(pShape)->rs.path.cmds, P(pShape)->rs.path.pts, nullptr, nullptr, &offset, exps); + } + + auto compMethod = (pMethod == MaskMethod::Subtract || pMethod == MaskMethod::InvAlpha) ? MaskMethod::InvAlpha : MaskMethod::Alpha; + + //Cheaper. Replace the masking with a clipper + if (layer->masks.count == 1 && compMethod == MaskMethod::Alpha && opacity == 255) { + layer->scene->clip(tvg::cast(pShape)); + return; + } + + //Introduce an intermediate scene for embracing the matte + masking + if (layer->matteTarget) { + auto scene = Scene::gen().release(); + scene->push(cast(layer->scene)); + layer->scene = scene; + } + + layer->scene->mask(tvg::cast(pShape), compMethod); + + //Apply the subsquent masks + for (auto m = layer->masks.begin() + 1; m < layer->masks.end(); ++m) { + auto mask = static_cast(*m); + auto method = mask->method; + if (method == MaskMethod::None) continue; + + //Append the mask shape + if (pMethod == method && (method == MaskMethod::Subtract || method == MaskMethod::Difference)) { + mask->pathset(frameNo, P(pShape)->rs.path.cmds, P(pShape)->rs.path.pts, nullptr, nullptr, nullptr, exps); + //Chain composition + } else { + auto shape = layer->pooling(); + shape->reset(); + shape->fill(255, 255, 255, mask->opacity(frameNo)); + shape->transform(layer->cache.matrix); + mask->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, nullptr, nullptr, nullptr, exps); + pShape->mask(tvg::cast(shape), method); + pShape = shape; + pMethod = method; + } + } +} + + +bool LottieBuilder::updateMatte(LottieComposition* comp, float frameNo, Scene* scene, LottieLayer* layer) +{ + auto target = layer->matteTarget; + if (!target) return true; + + updateLayer(comp, scene, target, frameNo); + + if (target->scene) { + layer->scene->mask(cast(target->scene), layer->matteType); + } else if (layer->matteType == MaskMethod::Alpha || layer->matteType == MaskMethod::Luma) { + //matte target is not exist. alpha blending definitely bring an invisible result + delete(layer->scene); + layer->scene = nullptr; + return false; + } + return true; +} + + +void LottieBuilder::updateEffect(LottieLayer* layer, float frameNo) +{ + constexpr int QUALITY = 25; + constexpr float BLUR_TO_SIGMA = 0.3f; + + if (layer->effects.count == 0) return; + + for (auto ef = layer->effects.begin(); ef < layer->effects.end(); ++ef) { + if (!(*ef)->enable) continue; + switch ((*ef)->type) { + case LottieEffect::DropShadow: { + auto effect = static_cast(*ef); + auto color = effect->color(frameNo); + layer->scene->push(SceneEffect::DropShadow, color.rgb[0], color.rgb[1], color.rgb[2], (int)effect->opacity(frameNo), effect->angle(frameNo), effect->distance(frameNo), effect->blurness(frameNo) * BLUR_TO_SIGMA, QUALITY); + break; + } + case LottieEffect::GaussianBlur: { + auto effect = static_cast(*ef); + layer->scene->push(SceneEffect::GaussianBlur, effect->blurness(frameNo) * BLUR_TO_SIGMA, effect->direction(frameNo) - 1, effect->wrap(frameNo), QUALITY); + break; + } + default: break; + } + } +} + + +void LottieBuilder::updateLayer(LottieComposition* comp, Scene* scene, LottieLayer* layer, float frameNo) +{ + layer->scene = nullptr; + + //visibility + if (frameNo < layer->inFrame || frameNo >= layer->outFrame) return; + + updateTransform(layer, frameNo); + + //full transparent scene. no need to perform + if (layer->type != LottieLayer::Null && layer->cache.opacity == 0) return; + + //Prepare render data + layer->scene = Scene::gen().release(); + layer->scene->id = layer->id; + + //ignore opacity when Null layer? + if (layer->type != LottieLayer::Null) layer->scene->opacity(layer->cache.opacity); + + layer->scene->transform(layer->cache.matrix); + + if (!updateMatte(comp, frameNo, scene, layer)) return; + + switch (layer->type) { + case LottieLayer::Precomp: { + updatePrecomp(comp, layer, frameNo); + break; + } + case LottieLayer::Solid: { + updateSolid(layer); + break; + } + case LottieLayer::Image: { + updateImage(layer); + break; + } + case LottieLayer::Text: { + updateText(layer, frameNo); + break; + } + default: { + if (!layer->children.empty()) { + Inlist contexts; + contexts.back(new RenderContext(layer->pooling())); + updateChildren(layer, frameNo, contexts); + contexts.free(); + } + break; + } + } + + updateMaskings(layer, frameNo); + + layer->scene->blend(layer->blendMethod); + + updateEffect(layer, frameNo); + + //the given matte source was composited by the target earlier. + if (!layer->matteSrc) scene->push(cast(layer->scene)); +} + + +static void _buildReference(LottieComposition* comp, LottieLayer* layer) +{ + for (auto asset = comp->assets.begin(); asset < comp->assets.end(); ++asset) { + if (layer->rid != (*asset)->id) continue; + if (layer->type == LottieLayer::Precomp) { + auto assetLayer = static_cast(*asset); + if (_buildComposition(comp, assetLayer)) { + layer->children = assetLayer->children; + layer->reqFragment = assetLayer->reqFragment; + } + } else if (layer->type == LottieLayer::Image) { + layer->children.push(*asset); + } + break; + } +} + + +static void _buildHierarchy(LottieGroup* parent, LottieLayer* child) +{ + if (child->pidx == -1) return; + + if (child->matteTarget && child->pidx == child->matteTarget->idx) { + child->parent = child->matteTarget; + return; + } + + for (auto p = parent->children.begin(); p < parent->children.end(); ++p) { + auto parent = static_cast(*p); + if (child == parent) continue; + if (child->pidx == parent->idx) { + child->parent = parent; + break; + } + if (parent->matteTarget && parent->matteTarget->idx == child->pidx) { + child->parent = parent->matteTarget; + break; + } + } +} + + +static void _attachFont(LottieComposition* comp, LottieLayer* parent) +{ + //TODO: Consider to migrate this attachment to the frame update time. + for (auto c = parent->children.begin(); c < parent->children.end(); ++c) { + auto text = static_cast(*c); + auto& doc = text->doc(0); + if (!doc.name) continue; + auto len = strlen(doc.name); + for (uint32_t i = 0; i < comp->fonts.count; ++i) { + auto font = comp->fonts[i]; + auto len2 = strlen(font->name); + if (len == len2 && !strcmp(font->name, doc.name)) { + text->font = font; + break; + } + } + } +} + + +static bool _buildComposition(LottieComposition* comp, LottieLayer* parent) +{ + if (parent->children.count == 0) return false; + if (parent->buildDone) return true; + parent->buildDone = true; + + for (auto c = parent->children.begin(); c < parent->children.end(); ++c) { + auto child = static_cast(*c); + + //attach the precomp layer. + if (child->rid) _buildReference(comp, child); + + if (child->matteType != MaskMethod::None) { + //no index of the matte layer is provided: the layer above is used as the matte source + if (child->mid == -1) { + if (c > parent->children.begin()) { + child->matteTarget = static_cast(*(c - 1)); + } + //matte layer is specified by an index. + } else child->matteTarget = parent->layerByIdx(child->mid); + } + + if (child->matteTarget) { + //parenting + _buildHierarchy(parent, child->matteTarget); + //precomp referencing + if (child->matteTarget->rid) _buildReference(comp, child->matteTarget); + } + _buildHierarchy(parent, child); + + //attach the necessary font data + if (child->type == LottieLayer::Text) _attachFont(comp, child); + } + return true; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +bool LottieBuilder::update(LottieComposition* comp, float frameNo) +{ + if (comp->root->children.empty()) return false; + + frameNo += comp->root->inFrame; + if (frameNo root->inFrame) frameNo = comp->root->inFrame; + if (frameNo >= comp->root->outFrame) frameNo = (comp->root->outFrame - 1); + + //update children layers + auto root = comp->root; + root->scene->clear(); + + if (exps && comp->expressions) exps->update(comp->timeAtFrame(frameNo)); + + for (auto child = root->children.end() - 1; child >= root->children.begin(); --child) { + auto layer = static_cast(*child); + if (!layer->matteSrc) updateLayer(comp, root->scene, layer, frameNo); + } + + return true; +} + + +void LottieBuilder::build(LottieComposition* comp) +{ + if (!comp) return; + + comp->root->scene = Scene::gen().release(); + + _buildComposition(comp, comp->root); + + if (!update(comp, 0)) return; + + //viewport clip + auto clip = Shape::gen(); + clip->appendRect(0, 0, comp->w, comp->h); + comp->root->scene->clip(std::move(clip)); +} diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieBuilder.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieBuilder.h new file mode 100644 index 000000000..cb628e866 --- /dev/null +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieBuilder.h @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_LOTTIE_BUILDER_H_ +#define _TVG_LOTTIE_BUILDER_H_ + +#include "tvgCommon.h" +#include "tvgInlist.h" +#include "tvgPaint.h" +#include "tvgShape.h" +#include "tvgLottieExpressions.h" +#include "tvgLottieModifier.h" + +struct LottieComposition; + +struct RenderRepeater +{ + int cnt; + Matrix transform; + float offset; + Point position; + Point anchor; + Point scale; + float rotation; + uint8_t startOpacity; + uint8_t endOpacity; + bool interpOpacity; + bool inorder; +}; + +struct RenderContext +{ + INLIST_ITEM(RenderContext); + + Shape* propagator = nullptr; //for propagating the shape properties excluding paths + Shape* merging = nullptr; //merging shapes if possible (if shapes have same properties) + LottieObject** begin = nullptr; //iteration entry point + Array repeaters; + Matrix* transform = nullptr; + LottieRoundnessModifier* roundness = nullptr; + LottieOffsetModifier* offsetPath = nullptr; + bool fragmenting = false; //render context has been fragmented by filling + bool reqFragment = false; //requirement to fragment the render context + + RenderContext(Shape* propagator) + { + P(propagator)->reset(); + PP(propagator)->ref(); + this->propagator = propagator; + } + + ~RenderContext() + { + PP(propagator)->unref(); + free(transform); + delete(roundness); + delete(offsetPath); + } + + RenderContext(const RenderContext& rhs, Shape* propagator, bool mergeable = false) + { + if (mergeable) merging = rhs.merging; + PP(propagator)->ref(); + this->propagator = propagator; + this->repeaters = rhs.repeaters; + if (rhs.roundness) this->roundness = new LottieRoundnessModifier(rhs.roundness->r); + if (rhs.offsetPath) this->offsetPath = new LottieOffsetModifier(rhs.offsetPath->offset, rhs.offsetPath->miterLimit, rhs.offsetPath->join); + } +}; + +struct LottieBuilder +{ + LottieBuilder() + { + exps = LottieExpressions::instance(); + } + + ~LottieBuilder() + { + LottieExpressions::retrieve(exps); + } + + bool update(LottieComposition* comp, float progress); + void build(LottieComposition* comp); + +private: + void updateEffect(LottieLayer* layer, float frameNo); + void updateLayer(LottieComposition* comp, Scene* scene, LottieLayer* layer, float frameNo); + bool updateMatte(LottieComposition* comp, float frameNo, Scene* scene, LottieLayer* layer); + void updatePrecomp(LottieComposition* comp, LottieLayer* precomp, float frameNo); + void updateSolid(LottieLayer* layer); + void updateImage(LottieGroup* layer); + void updateText(LottieLayer* layer, float frameNo); + void updateMaskings(LottieLayer* layer, float frameNo); + void updateTransform(LottieLayer* layer, float frameNo); + void updateChildren(LottieGroup* parent, float frameNo, Inlist& contexts); + void updateGroup(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& pcontexts, RenderContext* ctx); + void updateTransform(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx); + void updateSolidFill(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx); + void updateSolidStroke(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx); + void updateGradientFill(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx); + void updateGradientStroke(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx); + void updateRect(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx); + void updateEllipse(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx); + void updatePath(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx); + void updatePolystar(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx); + void updateTrimpath(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx); + void updateRepeater(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx); + void updateRoundedCorner(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx); + void updateOffsetPath(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx); + + LottieExpressions* exps; +}; + +#endif //_TVG_LOTTIE_BUILDER_H \ No newline at end of file diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieCommon.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieCommon.h new file mode 100644 index 000000000..5cdbdc3e6 --- /dev/null +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieCommon.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_LOTTIE_COMMON_ +#define _TVG_LOTTIE_COMMON_ + +#include +#include "tvgCommon.h" +#include "tvgArray.h" + +struct PathSet +{ + Point* pts = nullptr; + PathCommand* cmds = nullptr; + uint16_t ptsCnt = 0; + uint16_t cmdsCnt = 0; +}; + + +struct RGB24 +{ + int32_t rgb[3]; +}; + + +struct ColorStop +{ + Fill::ColorStop* data = nullptr; + Array* input = nullptr; +}; + + +struct TextDocument +{ + char* text = nullptr; + float height; + float shift; + RGB24 color; + struct { + Point pos; + Point size; + } bbox; + struct { + RGB24 color; + float width; + bool render = false; + } stroke; + char* name = nullptr; + float size; + float tracking = 0.0f; + uint8_t justify; +}; + + +static inline int32_t REMAP255(float val) +{ + return (int32_t)nearbyintf(val * 255.0f); +} + + +static inline RGB24 operator-(const RGB24& lhs, const RGB24& rhs) +{ + return {lhs.rgb[0] - rhs.rgb[0], lhs.rgb[1] - rhs.rgb[1], lhs.rgb[2] - rhs.rgb[2]}; +} + + +static inline RGB24 operator+(const RGB24& lhs, const RGB24& rhs) +{ + return {lhs.rgb[0] + rhs.rgb[0], lhs.rgb[1] + rhs.rgb[1], lhs.rgb[2] + rhs.rgb[2]}; +} + + +static inline RGB24 operator*(const RGB24& lhs, float rhs) +{ + return {(int32_t)nearbyintf(lhs.rgb[0] * rhs), (int32_t)nearbyintf(lhs.rgb[1] * rhs), (int32_t)nearbyintf(lhs.rgb[2] * rhs)}; +} + + +#endif //_TVG_LOTTIE_COMMON_ diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieExpressions.cpp b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieExpressions.cpp new file mode 100644 index 000000000..cb752c452 --- /dev/null +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieExpressions.cpp @@ -0,0 +1,1423 @@ +/* + * Copyright (c) 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +#include "tvgMath.h" +#include "tvgCompressor.h" +#include "tvgLottieModel.h" +#include "tvgLottieExpressions.h" + +#ifdef THORVG_LOTTIE_EXPRESSIONS_SUPPORT + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct ExpContent +{ + LottieExpression* exp; + LottieObject* obj; + float frameNo; +}; + +static jerry_value_t _content(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt); + +//reserved expressions specifiers +static const char* EXP_NAME = "name"; +static const char* EXP_CONTENT = "content"; +static const char* EXP_WIDTH = "width"; +static const char* EXP_HEIGHT = "height"; +static const char* EXP_CYCLE = "cycle"; +static const char* EXP_PINGPONG = "pingpong"; +static const char* EXP_OFFSET = "offset"; +static const char* EXP_CONTINUE = "continue"; +static const char* EXP_TIME = "time"; +static const char* EXP_VALUE = "value"; +static const char* EXP_INDEX = "index"; +static const char* EXP_EFFECT= "effect"; + +static LottieExpressions* exps = nullptr; //singleton instance engine + + +static ExpContent* _expcontent(LottieExpression* exp, float frameNo, LottieObject* obj) +{ + auto data = (ExpContent*)malloc(sizeof(ExpContent)); + data->exp = exp; + data->frameNo = frameNo; + data->obj = obj; + return data; +} + + +static void contentFree(void *native_p, struct jerry_object_native_info_t *info_p) +{ + free(native_p); +} + +static jerry_object_native_info_t freeCb {contentFree, 0, 0}; +static uint32_t engineRefCnt = 0; //Expressions Engine reference count + + +static char* _name(jerry_value_t args) +{ + auto arg0 = jerry_value_to_string(args); + auto len = jerry_string_length(arg0); + auto name = (jerry_char_t*)malloc(len * sizeof(jerry_char_t) + 1); + jerry_string_to_buffer(arg0, JERRY_ENCODING_UTF8, name, len); + name[len] = '\0'; + jerry_value_free(arg0); + return (char*) name; +} + + +static unsigned long _idByName(jerry_value_t args) +{ + auto name = _name(args); + auto id = djb2Encode(name); + free(name); + return id; +} + + +static jerry_value_t _toComp(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + TVGLOG("LOTTIE", "toComp is not supported in expressions!"); + + return jerry_undefined(); +} + + +static jerry_value_t _value(float frameNo, LottieProperty* property) +{ + switch (property->type) { + case LottieProperty::Type::Point: { + auto value = jerry_object(); + auto pos = (*static_cast(property))(frameNo); + auto val1 = jerry_number(pos.x); + auto val2 = jerry_number(pos.y); + jerry_object_set_index(value, 0, val1); + jerry_object_set_index(value, 1, val2); + jerry_value_free(val1); + jerry_value_free(val2); + return value; + } + case LottieProperty::Type::Float: { + return jerry_number((*static_cast(property))(frameNo)); + } + case LottieProperty::Type::Opacity: { + return jerry_number((*static_cast(property))(frameNo)); + } + case LottieProperty::Type::PathSet: { + auto value = jerry_object(); + jerry_object_set_native_ptr(value, nullptr, property); + return value; + } + case LottieProperty::Type::Position: { + auto value = jerry_object(); + auto pos = (*static_cast(property))(frameNo); + auto val1 = jerry_number(pos.x); + auto val2 = jerry_number(pos.y); + jerry_object_set_index(value, 0, val1); + jerry_object_set_index(value, 1, val2); + jerry_value_free(val1); + jerry_value_free(val2); + return value; + } + default: { + TVGERR("LOTTIE", "Non supported type for value? = %d", (int) property->type); + } + } + return jerry_undefined(); +} + + +static void _buildTransform(jerry_value_t context, float frameNo, LottieTransform* transform) +{ + if (!transform) return; + + auto obj = jerry_object(); + jerry_object_set_sz(context, "transform", obj); + + auto anchorPoint = _value(frameNo, &transform->anchor); + jerry_object_set_sz(obj, "anchorPoint", anchorPoint); + jerry_value_free(anchorPoint); + + auto position = _value(frameNo, &transform->position); + jerry_object_set_sz(obj, "position", position); + jerry_value_free(position); + + auto scale = _value(frameNo, &transform->scale); + jerry_object_set_sz(obj, "scale", scale); + jerry_value_free(scale); + + auto rotation = _value(frameNo, &transform->rotation); + jerry_object_set_sz(obj, "rotation", rotation); + jerry_value_free(rotation); + + auto opacity = _value(frameNo, &transform->opacity); + jerry_object_set_sz(obj, "opacity", opacity); + jerry_value_free(opacity); + + jerry_value_free(obj); +} + + +static jerry_value_t _buildGroup(LottieGroup* group, float frameNo) +{ + auto obj = jerry_function_external(_content); + + //attach a transform + for (auto c = group->children.begin(); c < group->children.end(); ++c) { + if ((*c)->type == LottieObject::Type::Transform) { + _buildTransform(obj, frameNo, static_cast(*c)); + break; + } + } + jerry_object_set_native_ptr(obj, &freeCb, _expcontent(nullptr, frameNo, group)); + jerry_object_set_sz(obj, EXP_CONTENT, obj); + return obj; +} + + +static jerry_value_t _buildPolystar(LottiePolyStar* polystar, float frameNo) +{ + auto obj = jerry_object(); + auto position = jerry_object(); + jerry_object_set_native_ptr(position, nullptr, &polystar->position); + jerry_object_set_sz(obj, "position", position); + jerry_value_free(position); + auto innerRadius = jerry_number(polystar->innerRadius(frameNo)); + jerry_object_set_sz(obj, "innerRadius", innerRadius); + jerry_value_free(innerRadius); + auto outerRadius = jerry_number(polystar->outerRadius(frameNo)); + jerry_object_set_sz(obj, "outerRadius", outerRadius); + jerry_value_free(outerRadius); + auto innerRoundness = jerry_number(polystar->innerRoundness(frameNo)); + jerry_object_set_sz(obj, "innerRoundness", innerRoundness); + jerry_value_free(innerRoundness); + auto outerRoundness = jerry_number(polystar->outerRoundness(frameNo)); + jerry_object_set_sz(obj, "outerRoundness", outerRoundness); + jerry_value_free(outerRoundness); + auto rotation = jerry_number(polystar->rotation(frameNo)); + jerry_object_set_sz(obj, "rotation", rotation); + jerry_value_free(rotation); + auto ptsCnt = jerry_number(polystar->ptsCnt(frameNo)); + jerry_object_set_sz(obj, "points", ptsCnt); + jerry_value_free(ptsCnt); + + return obj; +} + + +static jerry_value_t _buildTrimpath(LottieTrimpath* trimpath, float frameNo) +{ + jerry_value_t obj = jerry_object(); + auto start = jerry_number(trimpath->start(frameNo)); + jerry_object_set_sz(obj, "start", start); + jerry_value_free(start); + auto end = jerry_number(trimpath->end(frameNo)); + jerry_object_set_sz(obj, "end", end); + jerry_value_free(end); + auto offset = jerry_number(trimpath->offset(frameNo)); + jerry_object_set_sz(obj, "offset", end); + jerry_value_free(offset); + + return obj; +} + + +static void _buildLayer(jerry_value_t context, float frameNo, LottieLayer* layer, LottieLayer* comp, LottieExpression* exp) +{ + auto width = jerry_number(layer->w); + jerry_object_set_sz(context, EXP_WIDTH, width); + jerry_value_free(width); + + auto height = jerry_number(layer->h); + jerry_object_set_sz(context, EXP_HEIGHT, height); + jerry_value_free(height); + + auto index = jerry_number(layer->idx); + jerry_object_set_sz(context, EXP_INDEX, index); + jerry_value_free(index); + + auto parent = jerry_object(); + jerry_object_set_native_ptr(parent, nullptr, layer->parent); + jerry_object_set_sz(context, "parent", parent); + jerry_value_free(parent); + + auto hasParent = jerry_boolean(layer->parent ? true : false); + jerry_object_set_sz(context, "hasParent", hasParent); + jerry_value_free(hasParent); + + auto inPoint = jerry_number(layer->inFrame); + jerry_object_set_sz(context, "inPoint", inPoint); + jerry_value_free(inPoint); + + auto outPoint = jerry_number(layer->outFrame); + jerry_object_set_sz(context, "outPoint", outPoint); + jerry_value_free(outPoint); + + //TODO: Confirm exp->layer->comp->timeAtFrame() ? + auto startTime = jerry_number(exp->comp->timeAtFrame(layer->startFrame)); + jerry_object_set_sz(context, "startTime", startTime); + jerry_value_free(startTime); + + auto hasVideo = jerry_boolean(false); + jerry_object_set_sz(context, "hasVideo", hasVideo); + jerry_value_free(hasVideo); + + auto hasAudio = jerry_boolean(false); + jerry_object_set_sz(context, "hasAudio", hasAudio); + jerry_value_free(hasAudio); + + //active, #current in the animation range? + + auto enabled = jerry_boolean(!layer->hidden); + jerry_object_set_sz(context, "enabled", enabled); + jerry_value_free(enabled); + + auto audioActive = jerry_boolean(false); + jerry_object_set_sz(context, "audioActive", audioActive); + jerry_value_free(audioActive); + + //sampleImage(point, radius = [.5, .5], postEffect=true, t=time) + + _buildTransform(context, frameNo, layer->transform); + + //audioLevels, #the value of the Audio Levels property of the layer in decibels + + auto timeRemap = jerry_object(); + jerry_object_set_native_ptr(timeRemap, nullptr, &layer->timeRemap); + jerry_object_set_sz(context, "timeRemap", timeRemap); + jerry_value_free(timeRemap); + + //marker.key(index) + //marker.key(name) + //marker.nearestKey(t) + //marker.numKeys + + auto name = jerry_string_sz(layer->name); + jerry_object_set_sz(context, EXP_NAME, name); + jerry_value_free(name); + + auto toComp = jerry_function_external(_toComp); + jerry_object_set_sz(context, "toComp", toComp); + jerry_object_set_native_ptr(toComp, nullptr, comp); + jerry_value_free(toComp); + + //content("name"), #look for the named property from a layer + auto content = jerry_function_external(_content); + jerry_object_set_sz(context, EXP_CONTENT, content); + jerry_object_set_native_ptr(content, &freeCb, _expcontent(exp, frameNo, layer)); + jerry_value_free(content); +} + + +static jerry_value_t _addsub(const jerry_value_t args[], float addsub) +{ + auto n1 = jerry_value_is_number(args[0]); + auto n2 = jerry_value_is_number(args[1]); + + //1d + 1d + if (n1 && n2) return jerry_number(jerry_value_as_number(args[0]) + addsub * jerry_value_as_number(args[1])); + + auto val1 = jerry_object_get_index(args[n1 ? 1 : 0], 0); + auto val2 = jerry_object_get_index(args[n1 ? 1 : 0], 1); + auto x = jerry_value_as_number(val1); + auto y = jerry_value_as_number(val2); + jerry_value_free(val1); + jerry_value_free(val2); + + //2d + 1d + if (n1 || n2) { + auto secondary = n1 ? 0 : 1; + auto val3 = jerry_value_as_number(args[secondary]); + if (secondary == 0) x = (x * addsub) + val3; + else x += (addsub * val3); + //2d + 2d + } else { + auto val3 = jerry_object_get_index(args[1], 0); + auto val4 = jerry_object_get_index(args[1], 1); + x += (addsub * jerry_value_as_number(val3)); + y += (addsub * jerry_value_as_number(val4)); + jerry_value_free(val3); + jerry_value_free(val4); + } + + auto obj = jerry_object(); + val1 = jerry_number(x); + val2 = jerry_number(y); + jerry_object_set_index(obj, 0, val1); + jerry_object_set_index(obj, 1, val2); + jerry_value_free(val1); + jerry_value_free(val2); + + return obj; +} + + +static jerry_value_t _muldiv(const jerry_value_t arg1, float arg2) +{ + //1d + if (jerry_value_is_number(arg1)) return jerry_number(jerry_value_as_number(arg1) * arg2); + + //2d + auto val1 = jerry_object_get_index(arg1, 0); + auto val2 = jerry_object_get_index(arg1, 1); + auto x = jerry_value_as_number(val1) * arg2; + auto y = jerry_value_as_number(val2) * arg2; + + jerry_value_free(val1); + jerry_value_free(val2); + + auto obj = jerry_object(); + val1 = jerry_number(x); + val2 = jerry_number(y); + jerry_object_set_index(obj, 0, val1); + jerry_object_set_index(obj, 1, val2); + jerry_value_free(val1); + jerry_value_free(val2); + + return obj; +} + + +static jerry_value_t _add(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + return _addsub(args, 1.0f); +} + + +static jerry_value_t _sub(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + return _addsub(args, -1.0f); +} + + +static jerry_value_t _mul(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + return _muldiv(args[0], jerry_value_as_number(args[1])); +} + + +static jerry_value_t _div(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + return _muldiv(args[0], 1.0f / jerry_value_as_number(args[1])); +} + + +static jerry_value_t _interp(float t, const jerry_value_t args[], int argsCnt) +{ + auto tMin = 0.0f; + auto tMax = 1.0f; + int idx = 0; + + tMin = jerry_value_as_number(args[1]); + tMax = jerry_value_as_number(args[2]); + idx += 2; + + t = (t - tMin) / (tMax - tMin); + if (t < 0) t = 0.0f; + else if (t > 1) t = 1.0f; + + //2d + if (jerry_value_is_object(args[idx + 1]) && jerry_value_is_object(args[idx + 2])) { + auto val1 = jerry_object_get_index(args[idx + 1], 0); + auto val2 = jerry_object_get_index(args[idx + 1], 1); + auto val3 = jerry_object_get_index(args[idx + 2], 0); + auto val4 = jerry_object_get_index(args[idx + 2], 1); + + Point pt1 = {(float)jerry_value_as_number(val1), (float)jerry_value_as_number(val2)}; + Point pt2 = {(float)jerry_value_as_number(val3), (float)jerry_value_as_number(val4)}; + Point ret; + ret = lerp(pt1, pt2, t); + + jerry_value_free(val1); + jerry_value_free(val2); + jerry_value_free(val3); + jerry_value_free(val4); + + auto obj = jerry_object(); + val1 = jerry_number(ret.x); + val2 = jerry_number(ret.y); + jerry_object_set_index(obj, 0, val1); + jerry_object_set_index(obj, 1, val2); + jerry_value_free(val1); + jerry_value_free(val2); + + return obj; + } + + //1d + auto val1 = (float) jerry_value_as_number(args[idx + 1]); + auto val2 = (float) jerry_value_as_number(args[idx + 2]); + return jerry_number(lerp(val1, val2, t)); +} + + +static jerry_value_t _linear(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto t = (float) jerry_value_as_number(args[0]); + return _interp(t, args, jerry_value_as_uint32(argsCnt)); +} + + +static jerry_value_t _ease(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto t = (float) jerry_value_as_number(args[0]); + t = (t < 0.5) ? (4 * t * t * t) : (1.0f - pow(-2.0f * t + 2.0f, 3) * 0.5f); + return _interp(t, args, jerry_value_as_uint32(argsCnt)); +} + + + +static jerry_value_t _easeIn(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto t = (float) jerry_value_as_number(args[0]); + t = t * t * t; + return _interp(t, args, jerry_value_as_uint32(argsCnt)); +} + + +static jerry_value_t _easeOut(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto t = (float) jerry_value_as_number(args[0]); + t = 1.0f - pow(1.0f - t, 3); + return _interp(t, args, jerry_value_as_uint32(argsCnt)); +} + + +static jerry_value_t _clamp(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto num = jerry_value_as_number(args[0]); + auto limit1 = jerry_value_as_number(args[1]); + auto limit2 = jerry_value_as_number(args[2]); + + //clamping + if (num < limit1) num = limit1; + if (num > limit2) num = limit2; + + return jerry_number(num); +} + + +static jerry_value_t _dot(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto val1 = jerry_object_get_index(args[0], 0); + auto val2 = jerry_object_get_index(args[0], 1); + auto val3 = jerry_object_get_index(args[1], 0); + auto val4 = jerry_object_get_index(args[1], 1); + + auto x = jerry_value_as_number(val1) * jerry_value_as_number(val3); + auto y = jerry_value_as_number(val2) * jerry_value_as_number(val4); + + jerry_value_free(val1); + jerry_value_free(val2); + jerry_value_free(val3); + jerry_value_free(val4); + + return jerry_number(x + y); +} + + +static jerry_value_t _cross(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto val1 = jerry_object_get_index(args[0], 0); + auto val2 = jerry_object_get_index(args[0], 1); + auto val3 = jerry_object_get_index(args[1], 0); + auto val4 = jerry_object_get_index(args[1], 1); + + auto x = jerry_value_as_number(val1) * jerry_value_as_number(val4); + auto y = jerry_value_as_number(val2) * jerry_value_as_number(val3); + + jerry_value_free(val1); + jerry_value_free(val2); + jerry_value_free(val3); + jerry_value_free(val4); + + return jerry_number(x - y); +} + + +static jerry_value_t _normalize(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto val1 = jerry_object_get_index(args[0], 0); + auto val2 = jerry_object_get_index(args[0], 1); + auto x = jerry_value_as_number(val1); + auto y = jerry_value_as_number(val2); + + jerry_value_free(val1); + jerry_value_free(val2); + + auto length = sqrtf(x * x + y * y); + + x /= length; + y /= length; + + auto obj = jerry_object(); + val1 = jerry_number(x); + val2 = jerry_number(y); + jerry_object_set_index(obj, 0, val1); + jerry_object_set_index(obj, 0, val2); + jerry_value_free(val1); + jerry_value_free(val2); + + return obj; +} + + +static jerry_value_t _length(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto val1 = jerry_object_get_index(args[0], 0); + auto val2 = jerry_object_get_index(args[0], 1); + auto x = jerry_value_as_number(val1); + auto y = jerry_value_as_number(val2); + + jerry_value_free(val1); + jerry_value_free(val2); + + return jerry_number(sqrtf(x * x + y * y)); +} + + +static jerry_value_t _random(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto val = (float)(rand() % 10000001); + return jerry_number(val * 0.0000001f); +} + + +static jerry_value_t _deg2rad(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + return jerry_number(deg2rad((float)jerry_value_as_number(args[0]))); +} + + +static jerry_value_t _rad2deg(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + return jerry_number(rad2deg((float)jerry_value_as_number(args[0]))); +} + + +static jerry_value_t _effect(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + TVGLOG("LOTTIE", "effect is not supported in expressions!"); + + return jerry_undefined(); +} + + +static jerry_value_t _fromCompToSurface(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + TVGLOG("LOTTIE", "fromCompToSurface is not supported in expressions!"); + + return jerry_undefined(); +} + + +static jerry_value_t _content(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto data = static_cast(jerry_object_get_native_ptr(info->function, &freeCb)); + auto group = static_cast(data->obj); + auto target = group->content(_idByName(args[0])); + if (!target) return jerry_undefined(); + + //find the a path property(sh) in the group layer? + switch (target->type) { + case LottieObject::Group: return _buildGroup(static_cast(target), data->frameNo); + case LottieObject::Path: { + jerry_value_t obj = jerry_object(); + jerry_object_set_native_ptr(obj, nullptr, &static_cast(target)->pathset); + jerry_object_set_sz(obj, "path", obj); + return obj; + } + case LottieObject::Polystar: return _buildPolystar(static_cast(target), data->frameNo); + case LottieObject::Trimpath: return _buildTrimpath(static_cast(target), data->frameNo); + default: break; + } + return jerry_undefined(); +} + + +static jerry_value_t _layer(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto data = static_cast(jerry_object_get_native_ptr(info->function, &freeCb)); + auto comp = static_cast(data->obj); + LottieLayer* layer; + + //layer index + if (jerry_value_is_number(args[0])) { + auto idx = (uint16_t)jerry_value_as_int32(args[0]); + layer = comp->layerByIdx(idx); + jerry_value_free(idx); + //layer name + } else { + layer = comp->layerById(_idByName(args[0])); + } + + if (!layer) return jerry_undefined(); + + auto obj = jerry_object(); + jerry_object_set_native_ptr(obj, nullptr, layer); + _buildLayer(obj, data->frameNo, layer, comp, data->exp); + + return obj; +} + + +static jerry_value_t _nearestKey(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); + auto time = jerry_value_as_number(args[0]); + auto frameNo = exp->comp->frameAtTime(time); + auto index = jerry_number(exp->property->nearest(frameNo)); + + auto obj = jerry_object(); + jerry_object_set_sz(obj, EXP_INDEX, index); + jerry_value_free(index); + + return obj; +} + +static jerry_value_t _property(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto data = static_cast(jerry_object_get_native_ptr(info->function, &freeCb)); + auto property = data->obj->property(jerry_value_as_int32(args[0])); + if (!property) return jerry_undefined(); + return _value(data->frameNo, property); +} + + +static jerry_value_t _propertyGroup(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto data = static_cast(jerry_object_get_native_ptr(info->function, &freeCb)); + auto level = jerry_value_as_int32(args[0]); + + //intermediate group + if (level == 1) { + auto group = jerry_function_external(_property); + jerry_object_set_native_ptr(group, &freeCb, _expcontent(data->exp, data->frameNo, data->obj)); + jerry_object_set_sz(group, "", group); + return group; + } + + TVGLOG("LOTTIE", "propertyGroup(%d)?", level); + + return jerry_undefined(); +} + + +static jerry_value_t _valueAtTime(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); + auto time = jerry_value_as_number(args[0]); + auto frameNo = exp->comp->frameAtTime(time); + return _value(frameNo, exp->property); +} + + +static jerry_value_t _velocity(float px, float cx, float py, float cy, float elapsed) +{ + float velocity[] = {(cx - px) / elapsed, (cy - py) / elapsed}; + auto obj = jerry_object(); + auto val1 = jerry_number(velocity[0]); + auto val2 = jerry_number(velocity[1]); + jerry_object_set_index(obj, 0, val1); + jerry_object_set_index(obj, 1, val2); + jerry_value_free(val1); + jerry_value_free(val2); + return obj; +} + + +static jerry_value_t _velocityAtTime(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); + auto time = jerry_value_as_number(args[0]); + auto frameNo = exp->comp->frameAtTime(time); + auto key = exp->property->nearest(frameNo); + auto pframe = exp->property->frameNo(key - 1); + auto cframe = exp->property->frameNo(key); + auto elapsed = (cframe - pframe) / (exp->comp->frameRate); + + //compute the velocity + switch (exp->property->type) { + case LottieProperty::Type::Point: { + auto prv = (*static_cast(exp->property))(pframe); + auto cur = (*static_cast(exp->property))(cframe); + return _velocity(prv.x, cur.x, prv.y, cur.y, elapsed); + } + case LottieProperty::Type::Position: { + auto prv = (*static_cast(exp->property))(pframe); + auto cur = (*static_cast(exp->property))(cframe); + return _velocity(prv.x, cur.x, prv.y, cur.y, elapsed); + } + case LottieProperty::Type::Float: { + auto prv = (*static_cast(exp->property))(pframe); + auto cur = (*static_cast(exp->property))(cframe); + auto velocity = (cur - prv) / elapsed; + return jerry_number(velocity); + } + default: TVGLOG("LOTTIE", "Non supported type for velocityAtTime?"); + } + return jerry_undefined(); +} + + +static jerry_value_t _speedAtTime(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); + auto time = jerry_value_as_number(args[0]); + auto frameNo = exp->comp->frameAtTime(time); + auto key = exp->property->nearest(frameNo); + auto pframe = exp->property->frameNo(key - 1); + auto cframe = exp->property->frameNo(key); + auto elapsed = (cframe - pframe) / (exp->comp->frameRate); + + Point cur, prv; + + //compute the velocity + switch (exp->property->type) { + case LottieProperty::Type::Point: { + prv = (*static_cast(exp->property))(pframe); + cur = (*static_cast(exp->property))(cframe); + break; + } + case LottieProperty::Type::Position: { + prv = (*static_cast(exp->property))(pframe); + cur = (*static_cast(exp->property))(cframe); + break; + } + default: { + TVGLOG("LOTTIE", "Non supported type for speedAtTime?"); + return jerry_undefined(); + } + } + + auto speed = sqrtf(pow(cur.x - prv.x, 2) + pow(cur.y - prv.y, 2)) / elapsed; + auto obj = jerry_number(speed); + return obj; +} + + +static bool _loopOutCommon(LottieExpression* exp, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + exp->loop.mode = LottieExpression::LoopMode::OutCycle; + + if (argsCnt > 0) { + auto name = _name(args[0]); + if (!strcmp(name, EXP_CYCLE)) exp->loop.mode = LottieExpression::LoopMode::OutCycle; + else if (!strcmp(name, EXP_PINGPONG)) exp->loop.mode = LottieExpression::LoopMode::OutPingPong; + else if (!strcmp(name, EXP_OFFSET)) exp->loop.mode = LottieExpression::LoopMode::OutOffset; + else if (!strcmp(name, EXP_CONTINUE)) exp->loop.mode = LottieExpression::LoopMode::OutContinue; + free(name); + } + + if (exp->loop.mode != LottieExpression::LoopMode::OutCycle && exp->loop.mode != LottieExpression::LoopMode::OutPingPong) { + TVGLOG("LOTTIE", "Not supported loopOut type = %d", exp->loop.mode); + return false; + } + + return true; +} + + +static jerry_value_t _loopOut(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); + + if (!_loopOutCommon(exp, args, argsCnt)) return jerry_undefined(); + + if (argsCnt > 1) exp->loop.key = jerry_value_as_int32(args[1]); + + auto obj = jerry_object(); + jerry_object_set_native_ptr(obj, nullptr, exp->property); + return obj; +} + + +static jerry_value_t _loopOutDuration(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); + + if (!_loopOutCommon(exp, args, argsCnt)) return jerry_undefined(); + + if (argsCnt > 1) { + exp->loop.in = exp->comp->frameAtTime((float)jerry_value_as_int32(args[1])); + } + + auto obj = jerry_object(); + jerry_object_set_native_ptr(obj, nullptr, exp->property); + return obj; +} + + +static bool _loopInCommon(LottieExpression* exp, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + exp->loop.mode = LottieExpression::LoopMode::InCycle; + + if (argsCnt > 0) { + auto name = _name(args[0]); + if (!strcmp(name, EXP_CYCLE)) exp->loop.mode = LottieExpression::LoopMode::InCycle; + else if (!strcmp(name, EXP_PINGPONG)) exp->loop.mode = LottieExpression::LoopMode::InPingPong; + else if (!strcmp(name, EXP_OFFSET)) exp->loop.mode = LottieExpression::LoopMode::InOffset; + else if (!strcmp(name, EXP_CONTINUE)) exp->loop.mode = LottieExpression::LoopMode::InContinue; + free(name); + } + + if (exp->loop.mode != LottieExpression::LoopMode::InCycle && exp->loop.mode != LottieExpression::LoopMode::InPingPong) { + TVGLOG("LOTTIE", "Not supported loopIn type = %d", exp->loop.mode); + return false; + } + + return true; +} + +static jerry_value_t _loopIn(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); + + if (!_loopInCommon(exp, args, argsCnt)) return jerry_undefined(); + + if (argsCnt > 1) exp->loop.key = jerry_value_as_int32(args[1]); + + auto obj = jerry_object(); + jerry_object_set_native_ptr(obj, nullptr, exp->property); + return obj; +} + + +static jerry_value_t _loopInDuration(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); + + if (argsCnt > 1) { + exp->loop.in = exp->comp->frameAtTime((float)jerry_value_as_int32(args[1])); + } + + if (!_loopInCommon(exp, args, argsCnt)) return jerry_undefined(); + + auto obj = jerry_object(); + jerry_object_set_native_ptr(obj, nullptr, exp->property); + return obj; +} + + +static jerry_value_t _key(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); + auto key = jerry_value_as_int32(args[0]); + auto frameNo = exp->property->frameNo(key); + auto time = jerry_number(exp->comp->timeAtFrame(frameNo)); + auto value = _value(frameNo, exp->property); + + auto obj = jerry_object(); + jerry_object_set_sz(obj, EXP_TIME, time); + jerry_object_set_sz(obj, EXP_INDEX, args[0]); + jerry_object_set_sz(obj, EXP_VALUE, value); + + //direct access, key[0], key[1] + if (exp->property->type == LottieProperty::Type::Float) { + jerry_object_set_index(obj, 0, value); + } else if (exp->property->type == LottieProperty::Type::Point || exp->property->type == LottieProperty::Type::Position) { + jerry_object_set_index(obj, 0, jerry_object_get_index(value, 0)); + jerry_object_set_index(obj, 1, jerry_object_get_index(value, 1)); + } + + jerry_value_free(time); + jerry_value_free(value); + + return obj; +} + + +static jerry_value_t _createPath(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + //TODO: arg1: points, arg2: inTangents, arg3: outTangents, arg4: isClosed + auto arg1 = jerry_value_to_object(args[0]); + auto pathset = jerry_object_get_native_ptr(arg1, nullptr); + if (!pathset) { + TVGERR("LOTTIE", "failed createPath()"); + return jerry_undefined(); + } + + jerry_value_free(arg1); + + auto obj = jerry_object(); + jerry_object_set_native_ptr(obj, nullptr, pathset); + return obj; +} + + +static jerry_value_t _uniformPath(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto pathset = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); + + /* TODO: ThorVG prebuilds the path data for performance. + It actually need to constructs the Array for points, inTangents, outTangents and then return here... */ + auto obj = jerry_object(); + jerry_object_set_native_ptr(obj, nullptr, pathset); + return obj; +} + + +static jerry_value_t _isClosed(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + //TODO: Not used + return jerry_boolean(true); +} + + +static void _buildPath(jerry_value_t context, LottieExpression* exp) +{ + //Trick for fast building path. + auto points = jerry_function_external(_uniformPath); + jerry_object_set_native_ptr(points, nullptr, exp->property); + jerry_object_set_sz(context, "points", points); + jerry_value_free(points); + + auto inTangents = jerry_function_external(_uniformPath); + jerry_object_set_native_ptr(inTangents, nullptr, exp->property); + jerry_object_set_sz(context, "inTangents", inTangents); + jerry_value_free(inTangents); + + auto outTangents = jerry_function_external(_uniformPath); + jerry_object_set_native_ptr(outTangents, nullptr, exp->property); + jerry_object_set_sz(context, "outTangents", outTangents); + jerry_value_free(outTangents); + + auto isClosed = jerry_function_external(_isClosed); + jerry_object_set_native_ptr(isClosed, nullptr, exp->property); + jerry_object_set_sz(context, "isClosed", isClosed); + jerry_value_free(isClosed); + +} + + +static void _buildProperty(float frameNo, jerry_value_t context, LottieExpression* exp) +{ + auto value = _value(frameNo, exp->property); + jerry_object_set_sz(context, EXP_VALUE, value); + jerry_value_free(value); + + auto valueAtTime = jerry_function_external(_valueAtTime); + jerry_object_set_sz(context, "valueAtTime", valueAtTime); + jerry_object_set_native_ptr(valueAtTime, nullptr, exp); + jerry_value_free(valueAtTime); + + auto velocity = jerry_number(0.0f); + jerry_object_set_sz(context, "velocity", velocity); + jerry_value_free(velocity); + + auto velocityAtTime = jerry_function_external(_velocityAtTime); + jerry_object_set_sz(context, "velocityAtTime", velocityAtTime); + jerry_object_set_native_ptr(velocityAtTime, nullptr, exp); + jerry_value_free(velocityAtTime); + + auto speed = jerry_number(0.0f); + jerry_object_set_sz(context, "speed", speed); + jerry_value_free(speed); + + auto speedAtTime = jerry_function_external(_speedAtTime); + jerry_object_set_sz(context, "speedAtTime", speedAtTime); + jerry_object_set_native_ptr(speedAtTime, nullptr, exp); + jerry_value_free(speedAtTime); + + //wiggle(freq, amp, octaves=1, amp_mult=.5, t=time) + //temporalWiggle(freq, amp, octaves=1, amp_mult=.5, t=time) + //smooth(width=.2, samples=5, t=time) + + auto loopIn = jerry_function_external(_loopIn); + jerry_object_set_sz(context, "loopIn", loopIn); + jerry_object_set_native_ptr(loopIn, nullptr, exp); + jerry_value_free(loopIn); + + auto loopOut = jerry_function_external(_loopOut); + jerry_object_set_sz(context, "loopOut", loopOut); + jerry_object_set_native_ptr(loopOut, nullptr, exp); + jerry_value_free(loopOut); + + auto loopInDuration = jerry_function_external(_loopInDuration); + jerry_object_set_sz(context, "loopInDuration", loopInDuration); + jerry_object_set_native_ptr(loopInDuration, nullptr, exp); + jerry_value_free(loopInDuration); + + auto loopOutDuration = jerry_function_external(_loopOutDuration); + jerry_object_set_sz(context, "loopOutDuration", loopOutDuration); + jerry_object_set_native_ptr(loopOutDuration, nullptr, exp); + jerry_value_free(loopOutDuration); + + auto key = jerry_function_external(_key); + jerry_object_set_sz(context, "key", key); + jerry_object_set_native_ptr(key, nullptr, exp); + jerry_value_free(key); + + //key(markerName) + + auto nearestKey = jerry_function_external(_nearestKey); + jerry_object_set_native_ptr(nearestKey, nullptr, exp); + jerry_object_set_sz(context, "nearestKey", nearestKey); + jerry_value_free(nearestKey); + + auto numKeys = jerry_number(exp->property->frameCnt()); + jerry_object_set_sz(context, "numKeys", numKeys); + jerry_value_free(numKeys); + + auto propertyGroup = jerry_function_external(_propertyGroup); + jerry_object_set_native_ptr(propertyGroup, &freeCb, _expcontent(exp, frameNo, exp->object)); + jerry_object_set_sz(context, "propertyGroup", propertyGroup); + jerry_value_free(propertyGroup); + + //propertyIndex + + //name + + //content("name"), #look for the named property from a layer + auto content = jerry_function_external(_content); + jerry_object_set_sz(context, EXP_CONTENT, content); + jerry_object_set_native_ptr(content, &freeCb, _expcontent(exp, frameNo, exp->layer)); + jerry_value_free(content); + + //expansions per types + if (exp->property->type == LottieProperty::Type::PathSet) _buildPath(context, exp); +} + + +static jerry_value_t _comp(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto data = static_cast(jerry_object_get_native_ptr(info->function, &freeCb)); + auto comp = static_cast(data->obj); + auto layer = comp->layerById(_idByName(args[0])); + + if (!layer) return jerry_undefined(); + + auto obj = jerry_object(); + jerry_object_set_native_ptr(obj, nullptr, layer); + _buildLayer(obj, data->frameNo, layer, comp, data->exp); + + return obj; +} + + +static void _buildMath(jerry_value_t context) +{ + auto bm_mul = jerry_function_external(_mul); + jerry_object_set_sz(context, "$bm_mul", bm_mul); + jerry_value_free(bm_mul); + + auto bm_sum = jerry_function_external(_add); + jerry_object_set_sz(context, "$bm_sum", bm_sum); + jerry_value_free(bm_sum); + + auto bm_add = jerry_function_external(_add); + jerry_object_set_sz(context, "$bm_add", bm_add); + jerry_value_free(bm_add); + + auto bm_sub = jerry_function_external(_sub); + jerry_object_set_sz(context, "$bm_sub", bm_sub); + jerry_value_free(bm_sub); + + auto bm_div = jerry_function_external(_div); + jerry_object_set_sz(context, "$bm_div", bm_div); + jerry_value_free(bm_div); + + auto mul = jerry_function_external(_mul); + jerry_object_set_sz(context, "mul", mul); + jerry_value_free(mul); + + auto sum = jerry_function_external(_add); + jerry_object_set_sz(context, "sum", sum); + jerry_value_free(sum); + + auto add = jerry_function_external(_add); + jerry_object_set_sz(context, "add", add); + jerry_value_free(add); + + auto sub = jerry_function_external(_sub); + jerry_object_set_sz(context, "sub", sub); + jerry_value_free(sub); + + auto div = jerry_function_external(_div); + jerry_object_set_sz(context, "div", div); + jerry_value_free(div); + + auto clamp = jerry_function_external(_clamp); + jerry_object_set_sz(context, "clamp", clamp); + jerry_value_free(clamp); + + auto dot = jerry_function_external(_dot); + jerry_object_set_sz(context, "dot", dot); + jerry_value_free(dot); + + auto cross = jerry_function_external(_cross); + jerry_object_set_sz(context, "cross", cross); + jerry_value_free(cross); + + auto normalize = jerry_function_external(_normalize); + jerry_object_set_sz(context, "normalize", normalize); + jerry_value_free(normalize); + + auto length = jerry_function_external(_length); + jerry_object_set_sz(context, "length", length); + jerry_value_free(length); + + auto random = jerry_function_external(_random); + jerry_object_set_sz(context, "random", random); + jerry_value_free(random); + + auto deg2rad = jerry_function_external(_deg2rad); + jerry_object_set_sz(context, "degreesToRadians", deg2rad); + jerry_value_free(deg2rad); + + auto rad2deg = jerry_function_external(_rad2deg); + jerry_object_set_sz(context, "radiansToDegrees", rad2deg); + jerry_value_free(rad2deg); + + auto linear = jerry_function_external(_linear); + jerry_object_set_sz(context, "linear", linear); + jerry_value_free(linear); + + auto ease = jerry_function_external(_ease); + jerry_object_set_sz(context, "ease", ease); + jerry_value_free(ease); + + auto easeIn = jerry_function_external(_easeIn); + jerry_object_set_sz(context, "easeIn", easeIn); + jerry_value_free(easeIn); + + auto easeOut = jerry_function_external(_easeOut); + jerry_object_set_sz(context, "easeOut", easeOut); + jerry_value_free(easeOut); + + //lookAt +} + + +void LottieExpressions::buildGlobal(LottieExpression* exp) +{ + auto index = jerry_number(exp->layer->idx); + jerry_object_set_sz(global, EXP_INDEX, index); + jerry_value_free(index); +} + + +void LottieExpressions::buildComp(jerry_value_t context, float frameNo, LottieLayer* comp, LottieExpression* exp) +{ + auto data = static_cast(jerry_object_get_native_ptr(context, &freeCb)); + data->exp = exp; + data->frameNo = frameNo; + data->obj = comp; + + //layer(index) / layer(name) / layer(otherLayer, reIndex) + auto layer = jerry_function_external(_layer); + jerry_object_set_sz(context, "layer", layer); + + jerry_object_set_native_ptr(layer, &freeCb, _expcontent(exp, frameNo, comp)); + jerry_value_free(layer); + + auto numLayers = jerry_number(comp->children.count); + jerry_object_set_sz(context, "numLayers", numLayers); + jerry_value_free(numLayers); +} + + +void LottieExpressions::buildComp(LottieComposition* comp, float frameNo, LottieExpression* exp) +{ + buildComp(this->comp, frameNo, comp->root, exp); + + //marker + //marker.key(index) + //marker.key(name) + //marker.nearestKey(t) + //marker.numKeys + + //activeCamera + + auto width = jerry_number(comp->w); + jerry_object_set_sz(thisComp, EXP_WIDTH, width); + jerry_value_free(width); + + auto height = jerry_number(comp->h); + jerry_object_set_sz(thisComp, EXP_HEIGHT, height); + jerry_value_free(height); + + auto duration = jerry_number(comp->duration()); + jerry_object_set_sz(thisComp, "duration", duration); + jerry_value_free(duration); + + //ntscDropFrame + //displayStartTime + + auto frameDuration = jerry_number(1.0f / comp->frameRate); + jerry_object_set_sz(thisComp, "frameDuration", frameDuration); + jerry_value_free(frameDuration); + + //shutterAngle + //shutterPhase + //bgColor + //pixelAspect + + auto name = jerry_string((jerry_char_t*)comp->name, strlen(comp->name), JERRY_ENCODING_UTF8); + jerry_object_set_sz(thisComp, EXP_NAME, name); + jerry_value_free(name); +} + + +jerry_value_t LottieExpressions::buildGlobal() +{ + global = jerry_current_realm(); + + //comp(name) + comp = jerry_function_external(_comp); + jerry_object_set_native_ptr(comp, &freeCb, _expcontent(nullptr, 0.0f, nullptr)); + jerry_object_set_sz(global, "comp", comp); + + //footage(name) + + thisComp = jerry_object(); + jerry_object_set_native_ptr(thisComp, &freeCb, _expcontent(nullptr, 0.0f, nullptr)); + jerry_object_set_sz(global, "thisComp", thisComp); + + thisLayer = jerry_object(); + jerry_object_set_sz(global, "thisLayer", thisLayer); + + thisProperty = jerry_object(); + jerry_object_set_sz(global, "thisProperty", thisProperty); + + auto effect = jerry_function_external(_effect); + jerry_object_set_sz(global, EXP_EFFECT, effect); + jerry_value_free(effect); + + auto fromCompToSurface = jerry_function_external(_fromCompToSurface); + jerry_object_set_sz(global, "fromCompToSurface", fromCompToSurface); + jerry_value_free(fromCompToSurface); + + auto createPath = jerry_function_external(_createPath); + jerry_object_set_sz(global, "createPath", createPath); + jerry_value_free(createPath); + + //posterizeTime(framesPerSecond) + //value + + return global; +} + + +jerry_value_t LottieExpressions::evaluate(float frameNo, LottieExpression* exp) +{ + if (exp->disabled) return jerry_undefined(); + + buildGlobal(exp); + + //main composition + buildComp(exp->comp, frameNo, exp); + + //this composition + buildComp(thisComp, frameNo, exp->layer->comp, exp); + + //update global context values + _buildProperty(frameNo, global, exp); + + //this layer + jerry_object_set_native_ptr(thisLayer, nullptr, exp->layer); + _buildLayer(thisLayer, frameNo, exp->layer, exp->comp->root, exp); + + //this property + jerry_object_set_native_ptr(thisProperty, nullptr, exp->property); + _buildProperty(frameNo, thisProperty, exp); + + //expansions per object type + if (exp->object->type == LottieObject::Transform) _buildTransform(global, frameNo, static_cast(exp->object)); + + //evaluate the code + auto eval = jerry_eval((jerry_char_t *) exp->code, strlen(exp->code), JERRY_PARSE_NO_OPTS); + + if (jerry_value_is_exception(eval) || jerry_value_is_undefined(eval)) { + TVGERR("LOTTIE", "Failed to dispatch the expressions!"); + exp->disabled = true; + return jerry_undefined(); + } + + jerry_value_free(eval); + + return jerry_object_get_sz(global, "$bm_rt"); +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +LottieExpressions::~LottieExpressions() +{ + jerry_value_free(thisProperty); + jerry_value_free(thisLayer); + jerry_value_free(thisComp); + jerry_value_free(comp); + jerry_value_free(global); + jerry_cleanup(); +} + + +LottieExpressions::LottieExpressions() +{ + jerry_init(JERRY_INIT_EMPTY); + _buildMath(buildGlobal()); +} + + +void LottieExpressions::update(float curTime) +{ + //time, #current time in seconds + auto time = jerry_number(curTime); + jerry_object_set_sz(global, EXP_TIME, time); + jerry_value_free(time); +} + + +//FIXME: Threads support +#include "tvgTaskScheduler.h" + +LottieExpressions* LottieExpressions::instance() +{ + //FIXME: Threads support + if (TaskScheduler::threads() > 1) { + TVGLOG("LOTTIE", "Lottie Expressions are not supported with tvg threads"); + return nullptr; + } + + if (!exps) exps = new LottieExpressions; + ++engineRefCnt; + return exps; +} + + +void LottieExpressions::retrieve(LottieExpressions* instance) +{ + if (--engineRefCnt == 0) { + delete(instance); + exps = nullptr; + } +} + + +#endif //THORVG_LOTTIE_EXPRESSIONS_SUPPORT diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieExpressions.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieExpressions.h new file mode 100644 index 000000000..4a3c2dcfd --- /dev/null +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieExpressions.h @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_LOTTIE_EXPRESSIONS_H_ +#define _TVG_LOTTIE_EXPRESSIONS_H_ + +#include "tvgCommon.h" +#include "tvgLottieCommon.h" + +struct LottieExpression; +struct LottieComposition; +struct LottieLayer; +struct LottieRoundnessModifier; +struct LottieOffsetModifier; + +#ifdef THORVG_LOTTIE_EXPRESSIONS_SUPPORT + +#include "jerryscript.h" + + +struct LottieExpressions +{ +public: + template + bool result(float frameNo, NumType& out, LottieExpression* exp) + { + auto bm_rt = evaluate(frameNo, exp); + if (jerry_value_is_undefined(bm_rt)) return false; + + if (jerry_value_is_number(bm_rt)) { + out = (NumType) jerry_value_as_number(bm_rt); + } else if (auto prop = static_cast(jerry_object_get_native_ptr(bm_rt, nullptr))) { + out = (*prop)(frameNo); + } + jerry_value_free(bm_rt); + return true; + } + + template + bool result(float frameNo, Point& out, LottieExpression* exp) + { + auto bm_rt = evaluate(frameNo, exp); + if (jerry_value_is_undefined(bm_rt)) return false; + + if (auto prop = static_cast(jerry_object_get_native_ptr(bm_rt, nullptr))) { + out = (*prop)(frameNo); + } else { + auto x = jerry_object_get_index(bm_rt, 0); + auto y = jerry_object_get_index(bm_rt, 1); + out.x = jerry_value_as_number(x); + out.y = jerry_value_as_number(y); + jerry_value_free(x); + jerry_value_free(y); + } + jerry_value_free(bm_rt); + return true; + } + + template + bool result(float frameNo, RGB24& out, LottieExpression* exp) + { + auto bm_rt = evaluate(frameNo, exp); + if (jerry_value_is_undefined(bm_rt)) return false; + + if (auto color = static_cast(jerry_object_get_native_ptr(bm_rt, nullptr))) { + out = (*color)(frameNo); + } else { + auto r = jerry_object_get_index(bm_rt, 0); + auto g = jerry_object_get_index(bm_rt, 1); + auto b = jerry_object_get_index(bm_rt, 2); + out.rgb[0] = REMAP255(jerry_value_as_number(r)); + out.rgb[1] = REMAP255(jerry_value_as_number(g)); + out.rgb[2] = REMAP255(jerry_value_as_number(b)); + jerry_value_free(r); + jerry_value_free(g); + jerry_value_free(b); + } + jerry_value_free(bm_rt); + return true; + } + + template + bool result(float frameNo, Fill* fill, LottieExpression* exp) + { + auto bm_rt = evaluate(frameNo, exp); + if (jerry_value_is_undefined(bm_rt)) return false; + + if (auto colorStop = static_cast(jerry_object_get_native_ptr(bm_rt, nullptr))) { + (*colorStop)(frameNo, fill, this); + } + jerry_value_free(bm_rt); + return true; + } + + template + bool result(float frameNo, Array& cmds, Array& pts, Matrix* transform, const LottieRoundnessModifier* roundness, const LottieOffsetModifier* offsetPath, LottieExpression* exp) + { + auto bm_rt = evaluate(frameNo, exp); + if (jerry_value_is_undefined(bm_rt)) return false; + + if (auto pathset = static_cast(jerry_object_get_native_ptr(bm_rt, nullptr))) { + (*pathset)(frameNo, cmds, pts, transform, roundness, offsetPath); + } + jerry_value_free(bm_rt); + return true; + } + + void update(float curTime); + + //singleton (no thread safety) + static LottieExpressions* instance(); + static void retrieve(LottieExpressions* instance); + +private: + LottieExpressions(); + ~LottieExpressions(); + + jerry_value_t evaluate(float frameNo, LottieExpression* exp); + jerry_value_t buildGlobal(); + + void buildComp(LottieComposition* comp, float frameNo, LottieExpression* exp); + void buildComp(jerry_value_t context, float frameNo, LottieLayer* comp, LottieExpression* exp); + void buildGlobal(LottieExpression* exp); + + //global object, attributes, methods + jerry_value_t global; + jerry_value_t comp; + jerry_value_t thisComp; + jerry_value_t thisLayer; + jerry_value_t thisProperty; +}; + +#else + +struct LottieExpressions +{ + template bool result(TVG_UNUSED float, TVG_UNUSED NumType&, TVG_UNUSED LottieExpression*) { return false; } + template bool result(TVG_UNUSED float, TVG_UNUSED Point&, LottieExpression*) { return false; } + template bool result(TVG_UNUSED float, TVG_UNUSED RGB24&, TVG_UNUSED LottieExpression*) { return false; } + template bool result(TVG_UNUSED float, TVG_UNUSED Fill*, TVG_UNUSED LottieExpression*) { return false; } + template bool result(TVG_UNUSED float, TVG_UNUSED Array&, TVG_UNUSED Array&, TVG_UNUSED Matrix* transform, TVG_UNUSED const LottieRoundnessModifier*, TVG_UNUSED const LottieOffsetModifier*, TVG_UNUSED LottieExpression*) { return false; } + void update(TVG_UNUSED float) {} + static LottieExpressions* instance() { return nullptr; } + static void retrieve(TVG_UNUSED LottieExpressions* instance) {} +}; + +#endif //THORVG_LOTTIE_EXPRESSIONS_SUPPORT + +#endif //_TVG_LOTTIE_EXPRESSIONS_H_ \ No newline at end of file diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieInterpolator.cpp b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieInterpolator.cpp new file mode 100644 index 000000000..19ad502e0 --- /dev/null +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieInterpolator.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include "tvgCommon.h" +#include "tvgMath.h" +#include "tvgLottieInterpolator.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +#define NEWTON_MIN_SLOPE 0.02f +#define NEWTON_ITERATIONS 4 +#define SUBDIVISION_PRECISION 0.0000001f +#define SUBDIVISION_MAX_ITERATIONS 10 + + +static inline float _constA(float aA1, float aA2) { return 1.0f - 3.0f * aA2 + 3.0f * aA1; } +static inline float _constB(float aA1, float aA2) { return 3.0f * aA2 - 6.0f * aA1; } +static inline float _constC(float aA1) { return 3.0f * aA1; } + + +static inline float _getSlope(float t, float aA1, float aA2) +{ + return 3.0f * _constA(aA1, aA2) * t * t + 2.0f * _constB(aA1, aA2) * t + _constC(aA1); +} + + +static inline float _calcBezier(float t, float aA1, float aA2) +{ + return ((_constA(aA1, aA2) * t + _constB(aA1, aA2)) * t + _constC(aA1)) * t; +} + + +float LottieInterpolator::getTForX(float aX) +{ + //Find interval where t lies + auto intervalStart = 0.0f; + auto currentSample = &samples[1]; + auto lastSample = &samples[SPLINE_TABLE_SIZE - 1]; + + for (; currentSample != lastSample && *currentSample <= aX; ++currentSample) { + intervalStart += SAMPLE_STEP_SIZE; + } + + --currentSample; // t now lies between *currentSample and *currentSample+1 + + // Interpolate to provide an initial guess for t + auto dist = (aX - *currentSample) / (*(currentSample + 1) - *currentSample); + auto guessForT = intervalStart + dist * SAMPLE_STEP_SIZE; + + // Check the slope to see what strategy to use. If the slope is too small + // Newton-Raphson iteration won't converge on a root so we use bisection + // instead. + auto initialSlope = _getSlope(guessForT, outTangent.x, inTangent.x); + if (initialSlope >= NEWTON_MIN_SLOPE) return NewtonRaphsonIterate(aX, guessForT); + else if (initialSlope == 0.0) return guessForT; + else return binarySubdivide(aX, intervalStart, intervalStart + SAMPLE_STEP_SIZE); +} + + +float LottieInterpolator::binarySubdivide(float aX, float aA, float aB) +{ + float x, t; + int i = 0; + + do { + t = aA + (aB - aA) / 2.0f; + x = _calcBezier(t, outTangent.x, inTangent.x) - aX; + if (x > 0.0f) aB = t; + else aA = t; + } while (fabsf(x) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS); + return t; +} + + +float LottieInterpolator::NewtonRaphsonIterate(float aX, float aGuessT) +{ + // Refine guess with Newton-Raphson iteration + for (int i = 0; i < NEWTON_ITERATIONS; ++i) { + // We're trying to find where f(t) = aX, + // so we're actually looking for a root for: CalcBezier(t) - aX + auto currentX = _calcBezier(aGuessT, outTangent.x, inTangent.x) - aX; + auto currentSlope = _getSlope(aGuessT, outTangent.x, inTangent.x); + if (currentSlope == 0.0f) return aGuessT; + aGuessT -= currentX / currentSlope; + } + return aGuessT; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +float LottieInterpolator::progress(float t) +{ + if (outTangent.x == outTangent.y && inTangent.x == inTangent.y) return t; + return _calcBezier(getTForX(t), outTangent.y, inTangent.y); +} + + +void LottieInterpolator::set(const char* key, Point& inTangent, Point& outTangent) +{ + this->key = strdup(key); + this->inTangent = inTangent; + this->outTangent = outTangent; + + if (outTangent.x == outTangent.y && inTangent.x == inTangent.y) return; + + //calculates sample values + for (int i = 0; i < SPLINE_TABLE_SIZE; ++i) { + samples[i] = _calcBezier(float(i) * SAMPLE_STEP_SIZE, outTangent.x, inTangent.x); + } +} \ No newline at end of file diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vpathmesure.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieInterpolator.h similarity index 62% rename from libfenrir/src/main/jni/animation/rlottie/src/vector/vpathmesure.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieInterpolator.h index 2a4adb976..0e1789fdf 100644 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vpathmesure.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieInterpolator.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + * Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved. * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,25 +20,26 @@ * SOFTWARE. */ -#ifndef VPATHMESURE_H -#define VPATHMESURE_H +#ifndef _TVG_LOTTIE_INTERPOLATOR_H_ +#define _TVG_LOTTIE_INTERPOLATOR_H_ -#include "vpath.h" +#define SPLINE_TABLE_SIZE 11 -V_BEGIN_NAMESPACE +struct LottieInterpolator +{ + char* key; + Point outTangent, inTangent; + + float progress(float t); + void set(const char* key, Point& inTangent, Point& outTangent); -class VPathMesure { -public: - void setRange(float start, float end) {mStart = start; mEnd = end;} - void setStart(float start){mStart = start;} - void setEnd(float end){mEnd = end;} - VPath trim(const VPath &path); private: - float mStart{0.0f}; - float mEnd{1.0f}; - VPath mScratchObject; -}; + static constexpr float SAMPLE_STEP_SIZE = 1.0f / float(SPLINE_TABLE_SIZE - 1); + float samples[SPLINE_TABLE_SIZE]; -V_END_NAMESPACE + float getTForX(float aX); + float binarySubdivide(float aX, float aA, float aB); + float NewtonRaphsonIterate(float aX, float aGuessT); +}; -#endif // VPATHMESURE_H +#endif //_TVG_LOTTIE_INTERPOLATOR_H_ \ No newline at end of file diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieLoader.cpp b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieLoader.cpp new file mode 100644 index 000000000..aa05b9695 --- /dev/null +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieLoader.cpp @@ -0,0 +1,425 @@ +/* + * Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgLottieLoader.h" +#include "tvgLottieModel.h" +#include "tvgLottieParser.h" +#include "tvgLottieBuilder.h" +#include "tvgStr.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +void LottieLoader::run(unsigned tid) +{ + //update frame + if (comp) { + builder->update(comp, frameNo); + //initial loading + } else { + LottieParser parser(content, dirName, colorReplaceInternal); + if (!parser.parse()) return; + { + ScopedLock lock(key); + comp = parser.comp; + } + builder->build(comp); + + release(); + } + rebuild = false; +} + + +void LottieLoader::release() +{ + if (copy) { + free((char*)content); + content = nullptr; + } + free(dirName); + dirName = nullptr; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +LottieLoader::LottieLoader() : FrameModule(FileType::Lottie), builder(new LottieBuilder) +{ + +} + + +LottieLoader::~LottieLoader() +{ + done(); + + release(); + + //TODO: correct position? + delete(comp); + delete(builder); +} + + +bool LottieLoader::header() +{ + //A single thread doesn't need to perform intensive tasks. + if (TaskScheduler::threads() == 0) { + LoadModule::read(); + run(0); + if (comp) { + w = static_cast(comp->w); + h = static_cast(comp->h); + frameDuration = comp->duration(); + frameCnt = comp->frameCnt(); + frameRate = comp->frameRate; + return true; + } else { + return false; + } + } + + //Quickly validate the given Lottie file without parsing in order to get the animation info. + auto startFrame = 0.0f; + auto endFrame = 0.0f; + uint32_t depth = 0; + + auto p = content; + + while (*p != '\0') { + if (*p == '{') { + ++depth; + ++p; + continue; + } + if (*p == '}') { + --depth; + ++p; + continue; + } + if (depth != 1) { + ++p; + continue; + } + //version. + if (!strncmp(p, "\"v\":", 4)) { + p += 4; + continue; + } + + //framerate + if (!strncmp(p, "\"fr\":", 5)) { + p += 5; + auto e = strstr(p, ","); + if (!e) e = strstr(p, "}"); + frameRate = strToFloat(p, nullptr); + p = e; + continue; + } + + //start frame + if (!strncmp(p, "\"ip\":", 5)) { + p += 5; + auto e = strstr(p, ","); + if (!e) e = strstr(p, "}"); + startFrame = strToFloat(p, nullptr); + p = e; + continue; + } + + //end frame + if (!strncmp(p, "\"op\":", 5)) { + p += 5; + auto e = strstr(p, ","); + if (!e) e = strstr(p, "}"); + endFrame = strToFloat(p, nullptr); + p = e; + continue; + } + + //width + if (!strncmp(p, "\"w\":", 4)) { + p += 4; + auto e = strstr(p, ","); + if (!e) e = strstr(p, "}"); + w = strToFloat(p, nullptr); + p = e; + continue; + } + //height + if (!strncmp(p, "\"h\":", 4)) { + p += 4; + auto e = strstr(p, ","); + if (!e) e = strstr(p, "}"); + h = strToFloat(p, nullptr); + p = e; + continue; + } + ++p; + } + + if (frameRate < FLOAT_EPSILON) { + TVGLOG("LOTTIE", "Not a Lottie file? Frame rate is 0!"); + return false; + } + + frameCnt = (endFrame - startFrame); + frameDuration = frameCnt / frameRate; + + TVGLOG("LOTTIE", "info: frame rate = %f, duration = %f size = %f x %f", frameRate, frameDuration, w, h); + + return true; +} + + +bool LottieLoader::open(const char* data, uint32_t size, const std::string& rpath, bool copy, const ColorReplace& colorReplacement) +{ + colorReplaceInternal = colorReplacement; + if (copy) { + content = (char*)malloc(size + 1); + if (!content) return false; + memcpy((char*)content, data, size); + const_cast(content)[size] = '\0'; + } else content = data; + + this->size = size; + this->copy = copy; + + if (rpath.empty()) this->dirName = strdup("."); + else this->dirName = strdup(rpath.c_str()); + + return header(); +} + + +bool LottieLoader::open(const string& path, const ColorReplace& colorReplacement) +{ + colorReplaceInternal = colorReplacement; + auto f = fopen(path.c_str(), "r"); + if (!f) return false; + + fseek(f, 0, SEEK_END); + + size = ftell(f); + if (size == 0) { + fclose(f); + return false; + } + + auto content = (char*)(malloc(sizeof(char) * size + 1)); + fseek(f, 0, SEEK_SET); + auto ret = fread(content, sizeof(char), size, f); + if (ret < size) { + fclose(f); + return false; + } + content[size] = '\0'; + + fclose(f); + + this->dirName = strDirname(path.c_str()); + this->content = content; + this->copy = true; + + return header(); +} + + +bool LottieLoader::resize(Paint* paint, float w, float h) +{ + if (!paint) return false; + + auto sx = w / this->w; + auto sy = h / this->h; + Matrix m = {sx, 0, 0, 0, sy, 0, 0, 0, 1}; + paint->transform(m); + + //apply the scale to the base clipper + const Paint* clipper; + paint->mask(&clipper); + if (clipper) const_cast(clipper)->transform(m); + + return true; +} + + +bool LottieLoader::read() +{ + //the loading has been already completed + if (!LoadModule::read()) return true; + + if (!content || size == 0) return false; + + TaskScheduler::request(this); + + return true; +} + + +Paint* LottieLoader::paint() +{ + done(); + + if (!comp) return nullptr; + comp->initiated = true; + return comp->root->scene; +} + + +bool LottieLoader::override(const char* slot) +{ + if (!ready() || comp->slots.count == 0) return false; + + auto success = true; + + //override slots + if (slot) { + //Copy the input data because the JSON parser will encode the data immediately. + auto temp = strdup(slot); + + //parsing slot json + LottieParser parser(temp, dirName, colorReplaceInternal); + parser.comp = comp; + + auto idx = 0; + while (auto sid = parser.sid(idx == 0)) { + for (auto s = comp->slots.begin(); s < comp->slots.end(); ++s) { + if (strcmp((*s)->sid, sid)) continue; + if (!parser.apply(*s)) success = false; + break; + } + ++idx; + } + + if (idx < 1) success = false; + free(temp); + rebuild = overridden = success; + //reset slots + } else if (overridden) { + for (auto s = comp->slots.begin(); s < comp->slots.end(); ++s) { + (*s)->reset(); + } + overridden = false; + rebuild = true; + } + return success; +} + + +bool LottieLoader::frame(float no) +{ + auto frameNo = no + startFrame(); + + //This ensures that the target frame number is reached. + frameNo *= 10000.0f; + frameNo = nearbyintf(frameNo); + frameNo *= 0.0001f; + + //Skip update if frame diff is too small. + if (fabsf(this->frameNo - frameNo) <= 0.0009f) return false; + + this->done(); + + this->frameNo = frameNo; + + TaskScheduler::request(this); + + return true; +} + + +float LottieLoader::startFrame() +{ + return frameCnt * segmentBegin; +} + + +float LottieLoader::totalFrame() +{ + return (segmentEnd - segmentBegin) * frameCnt; +} + + +float LottieLoader::curFrame() +{ + return frameNo - startFrame(); +} + + +float LottieLoader::duration() +{ + if (segmentBegin == 0.0f && segmentEnd == 1.0f) return frameDuration; + return frameCnt * (segmentEnd - segmentBegin) / frameRate; +} + + +void LottieLoader::sync() +{ + done(); + + if (rebuild) run(0); +} + + +uint32_t LottieLoader::markersCnt() +{ + return ready() ? comp->markers.count : 0; +} + + +const char* LottieLoader::markers(uint32_t index) +{ + if (!ready() || index >= comp->markers.count) return nullptr; + auto marker = comp->markers.begin() + index; + return (*marker)->name; +} + + +bool LottieLoader::segment(const char* marker, float& begin, float& end) +{ + if (!ready() || comp->markers.count == 0) return false; + + for (auto m = comp->markers.begin(); m < comp->markers.end(); ++m) { + if (!strcmp(marker, (*m)->name)) { + begin = (*m)->time / frameCnt; + end = ((*m)->time + (*m)->duration) / frameCnt; + return true; + } + } + return false; +} + + +bool LottieLoader::ready() +{ + { + ScopedLock lock(key); + if (comp) return true; + } + done(); + if (comp) return true; + return false; +} \ No newline at end of file diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieLoader.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieLoader.h new file mode 100644 index 000000000..70988b1b8 --- /dev/null +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieLoader.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_LOTTIE_LOADER_H_ +#define _TVG_LOTTIE_LOADER_H_ + +#include "tvgCommon.h" +#include "tvgFrameModule.h" +#include "tvgTaskScheduler.h" + +struct LottieComposition; +struct LottieBuilder; + +class LottieLoader : public FrameModule, public Task +{ +public: + const char* content = nullptr; //lottie file data + ColorReplace colorReplaceInternal; + uint32_t size = 0; //lottie data size + float frameNo = 0.0f; //current frame number + float frameCnt = 0.0f; + float frameDuration = 0.0f; + float frameRate = 0.0f; + + LottieBuilder* builder; + LottieComposition* comp = nullptr; + + Key key; + char* dirName = nullptr; //base resource directory + bool copy = false; //"content" is owned by this loader + bool overridden = false; //overridden properties with slots + bool rebuild = false; //require building the lottie scene + + LottieLoader(); + ~LottieLoader(); + + bool open(const string& path, const ColorReplace& colorReplacement) override; + bool open(const char* data, uint32_t size, const std::string& rpath, bool copy, const ColorReplace& colorReplacement) override; + bool resize(Paint* paint, float w, float h) override; + bool read() override; + Paint* paint() override; + bool override(const char* slot); + + //Frame Controls + bool frame(float no) override; + float totalFrame() override; + float curFrame() override; + float duration() override; + void sync() override; + + //Marker Supports + uint32_t markersCnt(); + const char* markers(uint32_t index); + bool segment(const char* marker, float& begin, float& end); + +private: + bool ready(); + bool header(); + void clear(); + float startFrame(); + void run(unsigned tid) override; + void release(); +}; + + +#endif //_TVG_LOTTIELOADER_H_ diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieModel.cpp b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieModel.cpp new file mode 100644 index 000000000..4f97802aa --- /dev/null +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieModel.cpp @@ -0,0 +1,505 @@ +/* + * Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgMath.h" +#include "tvgPaint.h" +#include "tvgFill.h" +#include "tvgTaskScheduler.h" +#include "tvgLottieModel.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +void LottieSlot::reset() +{ + if (!overridden) return; + + for (auto pair = pairs.begin(); pair < pairs.end(); ++pair) { + switch (type) { + case LottieProperty::Type::ColorStop: { + static_cast(pair->obj)->colorStops.release(); + static_cast(pair->obj)->colorStops = *static_cast(pair->prop); + static_cast(pair->prop)->frames = nullptr; + break; + } + case LottieProperty::Type::Color: { + static_cast(pair->obj)->color.release(); + static_cast(pair->obj)->color = *static_cast(pair->prop); + static_cast(pair->prop)->frames = nullptr; + break; + } + case LottieProperty::Type::TextDoc: { + static_cast(pair->obj)->doc.release(); + static_cast(pair->obj)->doc = *static_cast(pair->prop); + static_cast(pair->prop)->frames = nullptr; + break; + } + default: break; + } + delete(pair->prop); + pair->prop = nullptr; + } + overridden = false; +} + + +void LottieSlot::assign(LottieObject* target, ColorReplace* colorReplacement) +{ + //apply slot object to all targets + for (auto pair = pairs.begin(); pair < pairs.end(); ++pair) { + //backup the original properties before overwriting + switch (type) { + case LottieProperty::Type::ColorStop: { + if (!overridden) { + pair->prop = new LottieColorStop; + *static_cast(pair->prop) = static_cast(pair->obj)->colorStops; + } + + pair->obj->override(&static_cast(target)->colorStops); + break; + } + case LottieProperty::Type::Color: { + auto color = static_cast(pair->obj)->color; + colorReplacement->getCustomColorLottie32(color.value.rgb[0], color.value.rgb[1], color.value.rgb[2]); + if (!overridden) { + pair->prop = new LottieColor; + *static_cast(pair->prop) = color; + } + + pair->obj->override(&color); + break; + } + case LottieProperty::Type::TextDoc: { + if (!overridden) { + pair->prop = new LottieTextDoc; + *static_cast(pair->prop) = static_cast(pair->obj)->doc; + } + + pair->obj->override(&static_cast(target)->doc); + break; + } + default: break; + } + } + overridden = true; +} + + +void LottieTextRange::range(float frameNo, float totalLen, float& start, float& end) +{ + auto divisor = (rangeUnit == Unit::Percent) ? (100.0f / totalLen) : 1.0f; + auto offset = this->offset(frameNo) / divisor; + start = nearbyintf(this->start(frameNo) / divisor) + offset; + end = nearbyintf(this->end(frameNo) / divisor) + offset; + + if (start > end) std::swap(start, end); + + if (random == 0) return; + + auto range = end - start; + auto len = (rangeUnit == Unit::Percent) ? 100.0f : totalLen; + start = static_cast(random % int(len - range)); + end = start + range; +} + + +LottieImage::~LottieImage() +{ + free(b64Data); + free(mimeType); +} + + +void LottieImage::prepare() +{ + LottieObject::type = LottieObject::Image; + + auto picture = Picture::gen().release(); + + //force to load a picture on the same thread + TaskScheduler::async(false); + + if (size > 0) picture->load((const char*)b64Data, size, mimeType); + else picture->load(path); + + TaskScheduler::async(true); + + picture->size(width, height); + PP(picture)->ref(); + + pooler.push(picture); +} + + +void LottieTrimpath::segment(float frameNo, float& start, float& end, LottieExpressions* exps) +{ + start = this->start(frameNo, exps) * 0.01f; + tvg::clamp(start, 0.0f, 1.0f); + end = this->end(frameNo, exps) * 0.01f; + tvg::clamp(end, 0.0f, 1.0f); + + auto o = fmodf(this->offset(frameNo, exps), 360.0f) / 360.0f; //0 ~ 1 + + auto diff = fabs(start - end); + if (tvg::zero(diff)) { + start = 0.0f; + end = 0.0f; + return; + } + if (tvg::equal(diff, 1.0f) || tvg::equal(diff, 2.0f)) { + start = 0.0f; + end = 1.0f; + return; + } + + if (start > end) std::swap(start, end); + start += o; + end += o; +} + + +uint32_t LottieGradient::populate(ColorStop& color, size_t count) +{ + if (!color.input) return 0; + + uint32_t alphaCnt = (color.input->count - (count * 4)) / 2; + Array output(count + alphaCnt); + uint32_t cidx = 0; //color count + uint32_t clast = count * 4; + if (clast > color.input->count) clast = color.input->count; + uint32_t aidx = clast; //alpha count + Fill::ColorStop cs; + + //merge color stops. + for (uint32_t i = 0; i < color.input->count; ++i) { + if (cidx == clast || aidx == color.input->count) break; + if ((*color.input)[cidx] == (*color.input)[aidx]) { + cs.offset = (*color.input)[cidx]; + cs.r = (uint8_t)nearbyint((*color.input)[cidx + 1] * 255.0f); + cs.g = (uint8_t)nearbyint((*color.input)[cidx + 2] * 255.0f); + cs.b = (uint8_t)nearbyint((*color.input)[cidx + 3] * 255.0f); + cs.a = (uint8_t)nearbyint((*color.input)[aidx + 1] * 255.0f); + cidx += 4; + aidx += 2; + } else if ((*color.input)[cidx] < (*color.input)[aidx]) { + cs.offset = (*color.input)[cidx]; + cs.r = (uint8_t)nearbyint((*color.input)[cidx + 1] * 255.0f); + cs.g = (uint8_t)nearbyint((*color.input)[cidx + 2] * 255.0f); + cs.b = (uint8_t)nearbyint((*color.input)[cidx + 3] * 255.0f); + //generate alpha value + if (output.count > 0) { + auto p = ((*color.input)[cidx] - output.last().offset) / ((*color.input)[aidx] - output.last().offset); + cs.a = lerp(output.last().a, (uint8_t)nearbyint((*color.input)[aidx + 1] * 255.0f), p); + } else cs.a = (uint8_t)nearbyint((*color.input)[aidx + 1] * 255.0f); + cidx += 4; + } else { + cs.offset = (*color.input)[aidx]; + cs.a = (uint8_t)nearbyint((*color.input)[aidx + 1] * 255.0f); + //generate color value + if (output.count > 0) { + auto p = ((*color.input)[aidx] - output.last().offset) / ((*color.input)[cidx] - output.last().offset); + cs.r = lerp(output.last().r, (uint8_t)nearbyint((*color.input)[cidx + 1] * 255.0f), p); + cs.g = lerp(output.last().g, (uint8_t)nearbyint((*color.input)[cidx + 2] * 255.0f), p); + cs.b = lerp(output.last().b, (uint8_t)nearbyint((*color.input)[cidx + 3] * 255.0f), p); + } else { + cs.r = (uint8_t)nearbyint((*color.input)[cidx + 1] * 255.0f); + cs.g = (uint8_t)nearbyint((*color.input)[cidx + 2] * 255.0f); + cs.b = (uint8_t)nearbyint((*color.input)[cidx + 3] * 255.0f); + } + aidx += 2; + } + colorReplacement->getCustomColorLottie(cs.r, cs.g, cs.b); + output.push(cs); + } + + //color remains + while (cidx + 3 < clast) { + cs.offset = (*color.input)[cidx]; + cs.r = (uint8_t)nearbyint((*color.input)[cidx + 1] * 255.0f); + cs.g = (uint8_t)nearbyint((*color.input)[cidx + 2] * 255.0f); + cs.b = (uint8_t)nearbyint((*color.input)[cidx + 3] * 255.0f); + cs.a = (output.count > 0) ? output.last().a : 255; + colorReplacement->getCustomColorLottie(cs.r, cs.g, cs.b); + output.push(cs); + cidx += 4; + } + + //alpha remains + while (aidx < color.input->count) { + cs.offset = (*color.input)[aidx]; + cs.a = (uint8_t)nearbyint((*color.input)[aidx + 1] * 255.0f); + if (output.count > 0) { + cs.r = output.last().r; + cs.g = output.last().g; + cs.b = output.last().b; + } else cs.r = cs.g = cs.b = 255; + colorReplacement->getCustomColorLottie(cs.r, cs.g, cs.b); + output.push(cs); + aidx += 2; + } + + color.data = output.data; + output.data = nullptr; + + color.input->reset(); + delete(color.input); + + return output.count; +} + + +Fill* LottieGradient::fill(float frameNo, LottieExpressions* exps) +{ + auto opacity = this->opacity(frameNo); + if (opacity == 0) return nullptr; + + Fill* fill = nullptr; + auto s = start(frameNo, exps); + auto e = end(frameNo, exps); + + //Linear Graident + if (id == 1) { + fill = LinearGradient::gen().release(); + static_cast(fill)->linear(s.x, s.y, e.x, e.y); + } + //Radial Gradient + if (id == 2) { + fill = RadialGradient::gen().release(); + + auto w = fabsf(e.x - s.x); + auto h = fabsf(e.y - s.y); + auto r = (w > h) ? (w + 0.375f * h) : (h + 0.375f * w); + auto progress = this->height(frameNo, exps) * 0.01f; + + if (tvg::zero(progress)) { + static_cast(fill)->radial(s.x, s.y, r, s.x, s.y, 0.0f); + } else { + if (tvg::equal(progress, 1.0f)) progress = 0.99f; + auto startAngle = rad2deg(tvg::atan2(e.y - s.y, e.x - s.x)); + auto angle = deg2rad((startAngle + this->angle(frameNo, exps))); + auto fx = s.x + cos(angle) * progress * r; + auto fy = s.y + sin(angle) * progress * r; + // Lottie doesn't have any focal radius concept + static_cast(fill)->radial(s.x, s.y, r, fx, fy, 0.0f); + } + } + + if (!fill) return nullptr; + + colorStops(frameNo, fill, exps); + + //multiply the current opacity with the fill + if (opacity < 255) { + const Fill::ColorStop* colorStops; + auto cnt = fill->colorStops(&colorStops); + for (uint32_t i = 0; i < cnt; ++i) { + const_cast(&colorStops[i])->a = MULTIPLY(colorStops[i].a, opacity); + } + } + + return fill; +} + + +LottieGroup::LottieGroup() +{ + reqFragment = false; + buildDone = false; + trimpath = false; + visible = false; + allowMerge = true; +} + + +void LottieGroup::prepare(LottieObject::Type type) +{ + LottieObject::type = type; + + if (children.count == 0) return; + + size_t strokeCnt = 0; + size_t fillCnt = 0; + + for (auto c = children.end() - 1; c >= children.begin(); --c) { + auto child = static_cast(*c); + + if (child->type == LottieObject::Type::Trimpath) trimpath = true; + + /* Figure out if this group is a simple path drawing. + In that case, the rendering context can be sharable with the parent's. */ + if (allowMerge && (child->type == LottieObject::Group || !child->mergeable())) allowMerge = false; + + //Figure out this group has visible contents + switch (child->type) { + case LottieObject::Group: { + visible |= static_cast(child)->visible; + break; + } + case LottieObject::Rect: + case LottieObject::Ellipse: + case LottieObject::Path: + case LottieObject::Polystar: + case LottieObject::Image: + case LottieObject::Text: { + visible = true; + break; + } + default: break; + } + + if (reqFragment) continue; + + /* Figure out if the rendering context should be fragmented. + Multiple stroking or grouping with a stroking would occur this. + This fragment resolves the overlapped stroke outlines. */ + if (child->type == LottieObject::Group && !child->mergeable()) { + if (strokeCnt > 0 || fillCnt > 0) reqFragment = true; + } else if (child->type == LottieObject::SolidStroke || child->type == LottieObject::GradientStroke) { + if (strokeCnt > 0) reqFragment = true; + else ++strokeCnt; + } else if (child->type == LottieObject::SolidFill || child->type == LottieObject::GradientFill) { + if (fillCnt > 0) reqFragment = true; + else ++fillCnt; + } + } + + //Reverse the drawing order if this group has a trimpath. + if (!trimpath) return; + + for (uint32_t i = 0; i < children.count - 1; ) { + auto child2 = children[i + 1]; + if (!child2->mergeable() || child2->type == LottieObject::Transform) { + i += 2; + continue; + } + auto child = children[i]; + if (!child->mergeable() || child->type == LottieObject::Transform) { + i++; + continue; + } + children[i] = child2; + children[i + 1] = child; + i++; + } +} + + +LottieLayer::~LottieLayer() +{ + //No need to free assets children because the Composition owns them. + if (rid) children.clear(); + + for (auto m = masks.begin(); m < masks.end(); ++m) { + delete(*m); + } + + for (auto e = effects.begin(); e < effects.end(); ++e) { + delete(*e); + } + + delete(transform); + free(name); +} + + +void LottieLayer::prepare(RGB24* color) +{ + /* if layer is hidden, only useful data is its transform matrix. + so force it to be a Null Layer and release all resource. */ + if (hidden) { + type = LottieLayer::Null; + for (auto p = children.begin(); p < children.end(); ++p) delete(*p); + children.reset(); + return; + } + + //prepare the viewport clipper + if (type == LottieLayer::Precomp) { + auto clipper = Shape::gen().release(); + clipper->appendRect(0.0f, 0.0f, w, h); + PP(clipper)->ref(); + statical.pooler.push(clipper); + //prepare solid fill in advance if it is a layer type. + } else if (color && type == LottieLayer::Solid) { + auto solidFill = Shape::gen().release(); + solidFill->appendRect(0, 0, static_cast(w), static_cast(h)); + solidFill->fill(color->rgb[0], color->rgb[1], color->rgb[2]); + PP(solidFill)->ref(); + statical.pooler.push(solidFill); + } + + LottieGroup::prepare(LottieObject::Layer); +} + + +float LottieLayer::remap(LottieComposition* comp, float frameNo, LottieExpressions* exp) +{ + if (timeRemap.frames || timeRemap.value) { + frameNo = comp->frameAtTime(timeRemap(frameNo, exp)); + } else { + frameNo -= startFrame; + } + return (frameNo / timeStretch); +} + + +LottieComposition::~LottieComposition() +{ + if (!initiated && root) delete(root->scene); + + delete(root); + free(version); + free(name); + + //delete interpolators + for (auto i = interpolators.begin(); i < interpolators.end(); ++i) { + free((*i)->key); + free(*i); + } + + //delete assets + for (auto a = assets.begin(); a < assets.end(); ++a) { + delete(*a); + } + + //delete fonts + for (auto f = fonts.begin(); f < fonts.end(); ++f) { + delete(*f); + } + + //delete slots + for (auto s = slots.begin(); s < slots.end(); ++s) { + delete(*s); + } + + for (auto m = markers.begin(); m < markers.end(); ++m) { + delete(*m); + } +} diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieModel.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieModel.h new file mode 100644 index 000000000..e924d5c5e --- /dev/null +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieModel.h @@ -0,0 +1,925 @@ +/* + * Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_LOTTIE_MODEL_H_ +#define _TVG_LOTTIE_MODEL_H_ + +#include + +#include "tvgCommon.h" +#include "tvgRender.h" +#include "tvgLottieProperty.h" +#include "tvgLottieRenderPooler.h" + + +struct LottieComposition; + +struct LottieStroke +{ + struct DashAttr + { + //0: offset, 1: dash, 2: gap + LottieFloat value[3] = {0.0f, 0.0f, 0.0f}; + }; + + virtual ~LottieStroke() + { + delete(dashattr); + } + + LottieFloat& dash(int no) + { + if (!dashattr) dashattr = new DashAttr; + return dashattr->value[no]; + } + + float dashOffset(float frameNo, LottieExpressions* exps) + { + return dash(0)(frameNo, exps); + } + + float dashGap(float frameNo, LottieExpressions* exps) + { + return dash(2)(frameNo, exps); + } + + float dashSize(float frameNo, LottieExpressions* exps) + { + auto d = dash(1)(frameNo, exps); + if (d == 0.0f) return 0.1f; + else return d; + } + + LottieFloat width = 0.0f; + DashAttr* dashattr = nullptr; + float miterLimit = 0; + StrokeCap cap = StrokeCap::Round; + StrokeJoin join = StrokeJoin::Round; +}; + + +struct LottieEffect +{ + enum Type : uint8_t + { + DropShadow = 0, + GaussianBlur = 1, + }; + + virtual ~LottieEffect() {} + + Type type; + bool enable = false; +}; + + +struct LottieDropShadow : LottieEffect +{ + LottieColor color; + LottieFloat opacity = 0; + LottieAngle angle = 0.0f; + LottieSlider distance = 0.0f; + LottieSlider blurness = 0.0f; + + LottieDropShadow() + { + type = DropShadow; + } +}; + + +struct LottieGaussianBlur : LottieEffect +{ + LottieSlider blurness = 0.0f; + LottieCheckbox direction = 0; + LottieCheckbox wrap = 0; + + LottieGaussianBlur() + { + type = GaussianBlur; + } +}; + + +struct LottieMask +{ + LottiePathSet pathset; + LottieFloat expand = 0.0f; + LottieOpacity opacity = 255; + MaskMethod method; + bool inverse = false; +}; + + +struct LottieObject +{ + enum Type : uint8_t + { + Composition = 0, + Layer, + Group, + Transform, + SolidFill, + SolidStroke, + GradientFill, + GradientStroke, + Rect, + Ellipse, + Path, + Polystar, + Image, + Trimpath, + Text, + Repeater, + RoundedCorner, + OffsetPath + }; + + virtual ~LottieObject() + { + } + + virtual void override(LottieProperty* prop) + { + TVGERR("LOTTIE", "Unsupported slot type"); + } + + virtual bool mergeable() { return false; } + virtual LottieProperty* property(uint16_t ix) { return nullptr; } + + unsigned long id = 0; + Type type; + bool hidden = false; //remove? +}; + + +struct LottieGlyph +{ + Array children; //glyph shapes. + float width; + char* code; + char* family = nullptr; + char* style = nullptr; + uint16_t size; + uint8_t len; + + void prepare() + { + len = strlen(code); + } + + ~LottieGlyph() + { + for (auto p = children.begin(); p < children.end(); ++p) delete(*p); + free(code); + } +}; + + +struct LottieTextRange +{ + enum Based : uint8_t { Chars = 1, CharsExcludingSpaces, Words, Lines }; + enum Shape : uint8_t { Square = 1, RampUp, RampDown, Triangle, Round, Smooth }; + enum Unit : uint8_t { Percent = 1, Index }; + + struct { + LottieColor fillColor = RGB24{255, 255, 255}; + LottieColor strokeColor = RGB24{255, 255, 255}; + LottiePosition position = Point{0, 0}; + LottiePoint scale = Point{100, 100}; + LottieFloat letterSpacing = 0.0f; + LottieFloat lineSpacing = 0.0f; + LottieFloat strokeWidth = 0.0f; + LottieFloat rotation = 0.0f; + LottieOpacity fillOpacity = 255; + LottieOpacity strokeOpacity = 255; + LottieOpacity opacity = 255; + } style; + + LottieFloat offset = 0.0f; + LottieFloat maxEase = 0.0f; + LottieFloat minEase = 0.0f; + LottieFloat maxAmount = 0.0f; + LottieFloat smoothness = 0.0f; + LottieFloat start = 0.0f; + LottieFloat end = FLT_MAX; + Based based = Chars; + Shape shape = Square; + Unit rangeUnit = Percent; + uint8_t random = 0; + bool expressible = false; + + void range(float frameNo, float totalLen, float& start, float& end); +}; + + +struct LottieFont +{ + enum Origin : uint8_t { Local = 0, CssURL, ScriptURL, FontURL, Embedded }; + + ~LottieFont() + { + for (auto c = chars.begin(); c < chars.end(); ++c) delete(*c); + free(style); + free(family); + free(name); + } + + Array chars; + char* name = nullptr; + char* family = nullptr; + char* style = nullptr; + float ascent = 0.0f; + Origin origin = Embedded; +}; + +struct LottieMarker +{ + char* name = nullptr; + float time = 0.0f; + float duration = 0.0f; + + ~LottieMarker() + { + free(name); + } +}; + +struct LottieText : LottieObject, LottieRenderPooler +{ + struct AlignOption + { + enum Group : uint8_t { Chars = 1, Word = 2, Line = 3, All = 4 }; + Group grouping = Chars; + LottiePoint anchor{}; + } alignOption; + + void prepare() + { + LottieObject::type = LottieObject::Text; + } + + void override(LottieProperty* prop) override + { + this->doc = *static_cast(prop); + this->prepare(); + } + + LottieProperty* property(uint16_t ix) override + { + if (doc.ix == ix) return &doc; + return nullptr; + } + + LottieTextDoc doc; + LottieFont* font; + Array ranges; + + ~LottieText() + { + for (auto r = ranges.begin(); r < ranges.end(); ++r) delete(*r); + } +}; + + +struct LottieTrimpath : LottieObject +{ + enum Type : uint8_t { Simultaneous = 1, Individual = 2 }; + + void prepare() + { + LottieObject::type = LottieObject::Trimpath; + } + + bool mergeable() override + { + if (!start.frames && start.value == 0.0f && !end.frames && end.value == 100.0f && !offset.frames && offset.value == 0.0f) return true; + return false; + } + + LottieProperty* property(uint16_t ix) override + { + if (start.ix == ix) return &start; + if (end.ix == ix) return &end; + if (offset.ix == ix) return &offset; + return nullptr; + } + + void segment(float frameNo, float& start, float& end, LottieExpressions* exps); + + LottieFloat start = 0.0f; + LottieFloat end = 100.0f; + LottieFloat offset = 0.0f; + Type type = Simultaneous; +}; + + +struct LottieShape : LottieObject, LottieRenderPooler +{ + bool clockwise = true; //clockwise or counter-clockwise + + virtual ~LottieShape() {} + + bool mergeable() override + { + return true; + } + + void prepare(LottieObject::Type type) + { + LottieObject::type = type; + } +}; + + +struct LottieRoundedCorner : LottieObject +{ + void prepare() + { + LottieObject::type = LottieObject::RoundedCorner; + } + + LottieProperty* property(uint16_t ix) override + { + if (radius.ix == ix) return &radius; + return nullptr; + } + + LottieFloat radius = 0.0f; +}; + + +struct LottiePath : LottieShape +{ + void prepare() + { + LottieShape::prepare(LottieObject::Path); + } + + LottieProperty* property(uint16_t ix) override + { + if (pathset.ix == ix) return &pathset; + return nullptr; + } + + LottiePathSet pathset; +}; + + +struct LottieRect : LottieShape +{ + void prepare() + { + LottieShape::prepare(LottieObject::Rect); + } + + LottieProperty* property(uint16_t ix) override + { + if (position.ix == ix) return &position; + if (size.ix == ix) return &size; + if (radius.ix == ix) return &radius; + return nullptr; + } + + LottiePosition position = Point{0.0f, 0.0f}; + LottiePoint size = Point{0.0f, 0.0f}; + LottieFloat radius = 0.0f; //rounded corner radius +}; + + +struct LottiePolyStar : LottieShape +{ + enum Type : uint8_t {Star = 1, Polygon}; + + void prepare() + { + LottieShape::prepare(LottieObject::Polystar); + } + + LottieProperty* property(uint16_t ix) override + { + if (position.ix == ix) return &position; + if (innerRadius.ix == ix) return &innerRadius; + if (outerRadius.ix == ix) return &outerRadius; + if (innerRoundness.ix == ix) return &innerRoundness; + if (outerRoundness.ix == ix) return &outerRoundness; + if (rotation.ix == ix) return &rotation; + if (ptsCnt.ix == ix) return &ptsCnt; + return nullptr; + } + + LottiePosition position = Point{0.0f, 0.0f}; + LottieFloat innerRadius = 0.0f; + LottieFloat outerRadius = 0.0f; + LottieFloat innerRoundness = 0.0f; + LottieFloat outerRoundness = 0.0f; + LottieFloat rotation = 0.0f; + LottieFloat ptsCnt = 0.0f; + Type type = Polygon; +}; + + +struct LottieEllipse : LottieShape +{ + void prepare() + { + LottieShape::prepare(LottieObject::Ellipse); + } + + LottieProperty* property(uint16_t ix) override + { + if (position.ix == ix) return &position; + if (size.ix == ix) return &size; + return nullptr; + } + + LottiePosition position = Point{0.0f, 0.0f}; + LottiePoint size = Point{0.0f, 0.0f}; +}; + + +struct LottieTransform : LottieObject +{ + struct SeparateCoord + { + LottieFloat x = 0.0f; + LottieFloat y = 0.0f; + }; + + struct RotationEx + { + LottieFloat x = 0.0f; + LottieFloat y = 0.0f; + }; + + ~LottieTransform() + { + delete(coords); + delete(rotationEx); + } + + void prepare() + { + LottieObject::type = LottieObject::Transform; + } + + bool mergeable() override + { + if (!opacity.frames && opacity.value == 255) return true; + return false; + } + + LottieProperty* property(uint16_t ix) override + { + if (position.ix == ix) return &position; + if (rotation.ix == ix) return &rotation; + if (scale.ix == ix) return &scale; + if (anchor.ix == ix) return &anchor; + if (opacity.ix == ix) return &opacity; + if (skewAngle.ix == ix) return &skewAngle; + if (skewAxis.ix == ix) return &skewAxis; + if (coords) { + if (coords->x.ix == ix) return &coords->x; + if (coords->y.ix == ix) return &coords->y; + } + return nullptr; + } + + LottiePosition position = Point{0.0f, 0.0f}; + LottieFloat rotation = 0.0f; //z rotation + LottiePoint scale = Point{100.0f, 100.0f}; + LottiePoint anchor = Point{0.0f, 0.0f}; + LottieOpacity opacity = 255; + LottieFloat skewAngle = 0.0f; + LottieFloat skewAxis = 0.0f; + + SeparateCoord* coords = nullptr; //either a position or separate coordinates + RotationEx* rotationEx = nullptr; //extension for 3d rotation +}; + + +struct LottieSolid : LottieObject +{ + LottieColor color = RGB24{255, 255, 255}; + LottieOpacity opacity = 255; + + LottieProperty* property(uint16_t ix) override + { + if (color.ix == ix) return &color; + if (opacity.ix == ix) return &opacity; + return nullptr; + } +}; + + +struct LottieSolidStroke : LottieSolid, LottieStroke +{ + void prepare() + { + LottieObject::type = LottieObject::SolidStroke; + } + + LottieProperty* property(uint16_t ix) override + { + if (width.ix == ix) return &width; + if (dashattr) { + if (dashattr->value[0].ix == ix) return &dashattr->value[0]; + if (dashattr->value[1].ix == ix) return &dashattr->value[1]; + if (dashattr->value[2].ix == ix) return &dashattr->value[2]; + } + return LottieSolid::property(ix); + } + + void override(LottieProperty* prop) override + { + this->color = *static_cast(prop); + this->prepare(); + } +}; + + +struct LottieSolidFill : LottieSolid +{ + void prepare() + { + LottieObject::type = LottieObject::SolidFill; + } + + void override(LottieProperty* prop) override + { + this->color = *static_cast(prop); + this->prepare(); + } + + FillRule rule = FillRule::Winding; +}; + + +struct LottieGradient : LottieObject +{ + LottieGradient(ColorReplace* colorReplacement) { + this->colorReplacement = colorReplacement; + } + bool prepare() + { + if (!colorStops.populated) { + auto count = colorStops.count; //colorstop count can be modified after population + if (colorStops.frames) { + for (auto v = colorStops.frames->begin(); v < colorStops.frames->end(); ++v) { + colorStops.count = populate(v->value, count); + } + } else { + colorStops.count = populate(colorStops.value, count); + } + colorStops.populated = true; + } + if (start.frames || end.frames || height.frames || angle.frames || opacity.frames || colorStops.frames) return true; + return false; + } + + LottieProperty* property(uint16_t ix) override + { + if (start.ix == ix) return &start; + if (end.ix == ix) return &end; + if (height.ix == ix) return &height; + if (angle.ix == ix) return ∠ + if (opacity.ix == ix) return &opacity; + if (colorStops.ix == ix) return &colorStops; + return nullptr; + } + + + uint32_t populate(ColorStop& color, size_t count); + Fill* fill(float frameNo, LottieExpressions* exps); + + LottiePoint start = Point{0.0f, 0.0f}; + LottiePoint end = Point{0.0f, 0.0f}; + LottieFloat height = 0.0f; + LottieFloat angle = 0.0f; + LottieOpacity opacity = 255; + LottieColorStop colorStops; + uint8_t id = 0; //1: linear, 2: radial + ColorReplace* colorReplacement; +}; + + +struct LottieGradientFill : LottieGradient +{ + LottieGradientFill(ColorReplace* colorReplacement): LottieGradient(colorReplacement){} + void prepare() + { + LottieObject::type = LottieObject::GradientFill; + LottieGradient::prepare(); + } + + void override(LottieProperty* prop) override + { + this->colorStops = *static_cast(prop); + this->prepare(); + } + + FillRule rule = FillRule::Winding; +}; + + +struct LottieGradientStroke : LottieGradient, LottieStroke +{ + LottieGradientStroke(ColorReplace* colorReplacement) : LottieGradient(colorReplacement) {} + void prepare() + { + LottieObject::type = LottieObject::GradientStroke; + LottieGradient::prepare(); + } + + LottieProperty* property(uint16_t ix) override + { + if (width.ix == ix) return &width; + if (dashattr) { + if (dashattr->value[0].ix == ix) return &dashattr->value[0]; + if (dashattr->value[1].ix == ix) return &dashattr->value[1]; + if (dashattr->value[2].ix == ix) return &dashattr->value[2]; + } + return LottieGradient::property(ix); + } + + void override(LottieProperty* prop) override + { + this->colorStops = *static_cast(prop); + this->prepare(); + } +}; + + +struct LottieImage : LottieObject, LottieRenderPooler +{ + union { + char* b64Data = nullptr; + char* path; + }; + char* mimeType = nullptr; + uint32_t size = 0; + float width = 0.0f; + float height = 0.0f; + + ~LottieImage(); + void prepare(); +}; + + +struct LottieRepeater : LottieObject +{ + void prepare() + { + LottieObject::type = LottieObject::Repeater; + } + + LottieProperty* property(uint16_t ix) override + { + if (copies.ix == ix) return &copies; + if (offset.ix == ix) return &offset; + if (position.ix == ix) return &position; + if (rotation.ix == ix) return &rotation; + if (scale.ix == ix) return &scale; + if (anchor.ix == ix) return &anchor; + if (startOpacity.ix == ix) return &startOpacity; + if (endOpacity.ix == ix) return &endOpacity; + return nullptr; + } + + LottieFloat copies = 0.0f; + LottieFloat offset = 0.0f; + + //Transform + LottiePosition position = Point{0.0f, 0.0f}; + LottieFloat rotation = 0.0f; + LottiePoint scale = Point{100.0f, 100.0f}; + LottiePoint anchor = Point{0.0f, 0.0f}; + LottieOpacity startOpacity = 255; + LottieOpacity endOpacity = 255; + bool inorder = true; //true: higher, false: lower +}; + + +struct LottieOffsetPath : LottieObject +{ + void prepare() + { + LottieObject::type = LottieObject::OffsetPath; + } + + LottieFloat offset = 0.0f; + LottieFloat miterLimit = 4.0f; + StrokeJoin join = StrokeJoin::Miter; +}; + + +struct LottieGroup : LottieObject, LottieRenderPooler +{ + LottieGroup(); + + virtual ~LottieGroup() + { + for (auto p = children.begin(); p < children.end(); ++p) delete(*p); + } + + void prepare(LottieObject::Type type = LottieObject::Group); + bool mergeable() override { return allowMerge; } + + LottieObject* content(unsigned long id) + { + if (this->id == id) return this; + + //source has children, find recursively. + for (auto c = children.begin(); c < children.end(); ++c) { + auto child = *c; + if (child->type == LottieObject::Type::Group || child->type == LottieObject::Type::Layer) { + if (auto ret = static_cast(child)->content(id)) return ret; + } else if (child->id == id) return child; + } + return nullptr; + } + + Scene* scene = nullptr; + Array children; + + bool reqFragment : 1; //requirement to fragment the render context + bool buildDone : 1; //completed in building the composition. + bool trimpath : 1; //this group has a trimpath. + bool visible : 1; //this group has visible contents. + bool allowMerge : 1; //if this group is consisted of simple (transformed) shapes. +}; + + +struct LottieLayer : LottieGroup +{ + enum Type : uint8_t {Precomp = 0, Solid, Image, Null, Shape, Text}; + + ~LottieLayer(); + + uint8_t opacity(float frameNo) + { + //return zero if the visibility is false. + if (type == Null) return 255; + return transform->opacity(frameNo); + } + + bool mergeable() override { return false; } + + void prepare(RGB24* color = nullptr); + float remap(LottieComposition* comp, float frameNo, LottieExpressions* exp); + + char* name = nullptr; + LottieLayer* parent = nullptr; + LottieFloat timeRemap = 0.0f; + LottieLayer* comp = nullptr; //Precompositor, current layer is belonges. + LottieTransform* transform = nullptr; + Array masks; + Array effects; + LottieLayer* matteTarget = nullptr; + + LottieRenderPooler statical; //static pooler for solid fill and clipper + + float timeStretch = 1.0f; + float w = 0.0f, h = 0.0f; + float inFrame = 0.0f; + float outFrame = 0.0f; + float startFrame = 0.0f; + unsigned long rid = 0; //pre-composition reference id. + int16_t mid = -1; //id of the matte layer. + int16_t pidx = -1; //index of the parent layer. + int16_t idx = -1; //index of the current layer. + + struct { + float frameNo = -1.0f; + Matrix matrix; + uint8_t opacity; + } cache; + + MaskMethod matteType = MaskMethod::None; + BlendMethod blendMethod = BlendMethod::Normal; + Type type = Null; + bool autoOrient = false; + bool matteSrc = false; + + LottieLayer* layerById(unsigned long id) + { + for (auto child = children.begin(); child < children.end(); ++child) { + if ((*child)->type != LottieObject::Type::Layer) continue; + auto layer = static_cast(*child); + if (layer->id == id) return layer; + } + return nullptr; + } + + LottieLayer* layerByIdx(int16_t idx) + { + for (auto child = children.begin(); child < children.end(); ++child) { + if ((*child)->type != LottieObject::Type::Layer) continue; + auto layer = static_cast(*child); + if (layer->idx == idx) return layer; + } + return nullptr; + } +}; + + +struct LottieSlot +{ + struct Pair { + LottieObject* obj; + LottieProperty* prop; + }; + + void assign(LottieObject* target, ColorReplace* colorReplacement); + void reset(); + + LottieSlot(char* sid, LottieObject* obj, LottieProperty::Type type) : sid(sid), type(type) + { + pairs.push({obj}); + } + + ~LottieSlot() + { + free(sid); + if (!overridden) return; + for (auto pair = pairs.begin(); pair < pairs.end(); ++pair) { + delete(pair->prop); + } + } + + char* sid; + Array pairs; + LottieProperty::Type type; + bool overridden = false; +}; + + +struct LottieComposition +{ + ~LottieComposition(); + + float duration() const + { + return frameCnt() / frameRate; // in second + } + + float frameAtTime(float timeInSec) const + { + auto p = timeInSec / duration(); + if (p < 0.0f) p = 0.0f; + return p * frameCnt(); + } + + float timeAtFrame(float frameNo) + { + return (frameNo - root->inFrame) / frameRate; + } + + float frameCnt() const + { + return root->outFrame - root->inFrame; + } + + LottieLayer* asset(unsigned long id) + { + for (auto asset = assets.begin(); asset < assets.end(); ++asset) { + auto layer = static_cast(*asset); + if (layer->id == id) return layer; + } + return nullptr; + } + + LottieLayer* root = nullptr; + char* version = nullptr; + char* name = nullptr; + float w, h; + float frameRate; + Array assets; + Array interpolators; + Array fonts; + Array slots; + Array markers; + bool expressions = false; + bool initiated = false; +}; + +#endif //_TVG_LOTTIE_MODEL_H_ diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieModifier.cpp b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieModifier.cpp new file mode 100644 index 000000000..33dab7869 --- /dev/null +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieModifier.cpp @@ -0,0 +1,395 @@ +/* +* Copyright (c) 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgLottieModifier.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static void _roundCorner(Array& cmds, Array& pts, const Point& prev, const Point& curr, const Point& next, float r) +{ + auto lenPrev = length(prev - curr); + auto rPrev = lenPrev > 0.0f ? 0.5f * std::min(lenPrev * 0.5f, r) / lenPrev : 0.0f; + auto lenNext = length(next - curr); + auto rNext = lenNext > 0.0f ? 0.5f * std::min(lenNext * 0.5f, r) / lenNext : 0.0f; + + auto dPrev = rPrev * (curr - prev); + auto dNext = rNext * (curr - next); + + pts.push(curr - 2.0f * dPrev); + pts.push(curr - dPrev); + pts.push(curr - dNext); + pts.push(curr - 2.0f * dNext); + cmds.push(PathCommand::LineTo); + cmds.push(PathCommand::CubicTo); +} + + +static bool _zero(const Point& p1, const Point& p2) +{ + constexpr float epsilon = 1e-3f; + return fabsf(p1.x / p2.x - 1.0f) < epsilon && fabsf(p1.y / p2.y - 1.0f) < epsilon; +} + + +static bool _intersect(const Line& line1, const Line& line2, Point& intersection, bool& inside) +{ + if (_zero(line1.pt2, line2.pt1)) { + intersection = line1.pt2; + inside = true; + return true; + } + + constexpr float epsilon = 1e-3f; + float denom = (line1.pt2.x - line1.pt1.x) * (line2.pt2.y - line2.pt1.y) - (line1.pt2.y - line1.pt1.y) * (line2.pt2.x - line2.pt1.x); + if (fabsf(denom) < epsilon) return false; + + float t = ((line2.pt1.x - line1.pt1.x) * (line2.pt2.y - line2.pt1.y) - (line2.pt1.y - line1.pt1.y) * (line2.pt2.x - line2.pt1.x)) / denom; + float u = ((line2.pt1.x - line1.pt1.x) * (line1.pt2.y - line1.pt1.y) - (line2.pt1.y - line1.pt1.y) * (line1.pt2.x - line1.pt1.x)) / denom; + + intersection.x = line1.pt1.x + t * (line1.pt2.x - line1.pt1.x); + intersection.y = line1.pt1.y + t * (line1.pt2.y - line1.pt1.y); + inside = t >= -epsilon && t <= 1.0f + epsilon && u >= -epsilon && u <= 1.0f + epsilon; + + return true; +} + + +static Line _offset(const Point& p1, const Point& p2, float offset) +{ + auto scaledNormal = normal(p1, p2) * offset; + return {p1 + scaledNormal, p2 + scaledNormal}; +} + + +static bool _clockwise(const Point* pts, uint32_t n) +{ + auto area = 0.0f; + + for (uint32_t i = 0; i < n - 1; i++) { + area += cross(pts[i], pts[i + 1]); + } + area += cross(pts[n - 1], pts[0]);; + + return area < 0.0f; +} + + +void LottieOffsetModifier::corner(const Line& line, const Line& nextLine, uint32_t movetoOutIndex, bool nextClose, Array& outCmds, Array& outPts) const +{ + bool inside{}; + Point intersect{}; + if (_intersect(line, nextLine, intersect, inside)) { + if (inside) { + if (nextClose) outPts[movetoOutIndex] = intersect; + outPts.push(intersect); + } else { + outPts.push(line.pt2); + if (join == StrokeJoin::Round) { + outCmds.push(PathCommand::CubicTo); + outPts.push((line.pt2 + intersect) * 0.5f); + outPts.push((nextLine.pt1 + intersect) * 0.5f); + outPts.push(nextLine.pt1); + } else if (join == StrokeJoin::Miter) { + auto norm = normal(line.pt1, line.pt2); + auto nextNorm = normal(nextLine.pt1, nextLine.pt2); + auto miterDirection = (norm + nextNorm) / length(norm + nextNorm); + outCmds.push(PathCommand::LineTo); + if (1.0f <= miterLimit * fabsf(miterDirection.x * norm.x + miterDirection.y * norm.y)) outPts.push(intersect); + else outPts.push(nextLine.pt1); + } else { + outCmds.push(PathCommand::LineTo); + outPts.push(nextLine.pt1); + } + } + } else outPts.push(line.pt2); +} + + +void LottieOffsetModifier::line(const PathCommand* inCmds, uint32_t inCmdsCnt, const Point* inPts, uint32_t& currentPt, uint32_t currentCmd, State& state, bool degenerated, Array& outCmds, Array& outPts, float offset) const +{ + if (tvg::zero(inPts[currentPt - 1] - inPts[currentPt])) { + ++currentPt; + return; + } + + if (inCmds[currentCmd - 1] != PathCommand::LineTo) state.line = _offset(inPts[currentPt - 1], inPts[currentPt], offset); + + if (state.moveto) { + outCmds.push(PathCommand::MoveTo); + state.movetoOutIndex = outPts.count; + outPts.push(state.line.pt1); + state.firstLine = state.line; + state.moveto = false; + } + + auto nonDegeneratedCubic = [&](uint32_t cmd, uint32_t pt) { + return inCmds[cmd] == PathCommand::CubicTo && !tvg::zero(inPts[pt] - inPts[pt + 1]) && !tvg::zero(inPts[pt + 2] - inPts[pt + 3]); + }; + + outCmds.push(PathCommand::LineTo); + + if (currentCmd + 1 == inCmdsCnt || inCmds[currentCmd + 1] == PathCommand::MoveTo || nonDegeneratedCubic(currentCmd + 1, currentPt + degenerated)) { + outPts.push(state.line.pt2); + ++currentPt; + return; + } + + Line nextLine = state.firstLine; + if (inCmds[currentCmd + 1] == PathCommand::LineTo) nextLine = _offset(inPts[currentPt + degenerated], inPts[currentPt + 1 + degenerated], offset); + else if (inCmds[currentCmd + 1] == PathCommand::CubicTo) nextLine = _offset(inPts[currentPt + 1 + degenerated], inPts[currentPt + 2 + degenerated], offset); + else if (inCmds[currentCmd + 1] == PathCommand::Close && !_zero(inPts[currentPt + degenerated], inPts[state.movetoInIndex + degenerated])) + nextLine = _offset(inPts[currentPt + degenerated], inPts[state.movetoInIndex + degenerated], offset); + + corner(state.line, nextLine, state.movetoOutIndex, inCmds[currentCmd + 1] == PathCommand::Close, outCmds, outPts); + + state.line = nextLine; + ++currentPt; +} + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +bool LottieRoundnessModifier::modifyPath(const PathCommand* inCmds, uint32_t inCmdsCnt, const Point* inPts, uint32_t inPtsCnt, Array& outCmds, Array& outPts, Matrix* transform) const +{ + outCmds.reserve(inCmdsCnt * 2); + outPts.reserve((uint32_t)(inPtsCnt * 1.5)); + auto ptsCnt = outPts.count; + + uint32_t startIndex = 0; + for (uint32_t iCmds = 0, iPts = 0; iCmds < inCmdsCnt; ++iCmds) { + switch (inCmds[iCmds]) { + case PathCommand::MoveTo: { + startIndex = outPts.count; + outCmds.push(PathCommand::MoveTo); + outPts.push(inPts[iPts++]); + break; + } + case PathCommand::CubicTo: { + auto& prev = inPts[iPts - 1]; + auto& curr = inPts[iPts + 2]; + if (iCmds < inCmdsCnt - 1 && + tvg::zero(inPts[iPts - 1] - inPts[iPts]) && + tvg::zero(inPts[iPts + 1] - inPts[iPts + 2])) { + if (inCmds[iCmds + 1] == PathCommand::CubicTo && + tvg::zero(inPts[iPts + 2] - inPts[iPts + 3]) && + tvg::zero(inPts[iPts + 4] - inPts[iPts + 5])) { + _roundCorner(outCmds, outPts, prev, curr, inPts[iPts + 5], r); + iPts += 3; + break; + } else if (inCmds[iCmds + 1] == PathCommand::Close) { + _roundCorner(outCmds, outPts, prev, curr, inPts[2], r); + outPts[startIndex] = outPts.last(); + iPts += 3; + break; + } + } + outCmds.push(PathCommand::CubicTo); + outPts.push(inPts[iPts++]); + outPts.push(inPts[iPts++]); + outPts.push(inPts[iPts++]); + break; + } + case PathCommand::Close: { + outCmds.push(PathCommand::Close); + break; + } + default: break; + } + } + if (transform) { + for (auto i = ptsCnt; i < outPts.count; ++i) { + outPts[i] *= *transform; + } + } + return true; +} + + +bool LottieRoundnessModifier::modifyPolystar(TVG_UNUSED const Array& inCmds, const Array& inPts, Array& outCmds, Array& outPts, float outerRoundness, bool hasRoundness) const +{ + static constexpr auto ROUNDED_POLYSTAR_MAGIC_NUMBER = 0.47829f; + + auto len = length(inPts[1] - inPts[2]); + auto r = len > 0.0f ? ROUNDED_POLYSTAR_MAGIC_NUMBER * std::min(len * 0.5f, this->r) / len : 0.0f; + + if (hasRoundness) { + outCmds.grow((uint32_t)(1.5 * inCmds.count)); + outPts.grow((uint32_t)(4.5 * inCmds.count)); + + int start = 3 * tvg::zero(outerRoundness); + outCmds.push(PathCommand::MoveTo); + outPts.push(inPts[start]); + + for (uint32_t i = 1 + start; i < inPts.count; i += 6) { + auto& prev = inPts[i]; + auto& curr = inPts[i + 2]; + auto& next = (i < inPts.count - start) ? inPts[i + 4] : inPts[2]; + auto& nextCtrl = (i < inPts.count - start) ? inPts[i + 5] : inPts[3]; + auto dNext = r * (curr - next); + auto dPrev = r * (curr - prev); + + auto p0 = curr - 2.0f * dPrev; + auto p1 = curr - dPrev; + auto p2 = curr - dNext; + auto p3 = curr - 2.0f * dNext; + + outCmds.push(PathCommand::CubicTo); + outPts.push(prev); outPts.push(p0); outPts.push(p0); + outCmds.push(PathCommand::CubicTo); + outPts.push(p1); outPts.push(p2); outPts.push(p3); + outCmds.push(PathCommand::CubicTo); + outPts.push(p3); outPts.push(next); outPts.push(nextCtrl); + } + } else { + outCmds.grow(2 * inCmds.count); + outPts.grow(4 * inCmds.count); + + auto dPrev = r * (inPts[1] - inPts[0]); + auto p = inPts[0] + 2.0f * dPrev; + outCmds.push(PathCommand::MoveTo); + outPts.push(p); + + for (uint32_t i = 1; i < inPts.count; ++i) { + auto& curr = inPts[i]; + auto& next = (i == inPts.count - 1) ? inPts[1] : inPts[i + 1]; + auto dNext = r * (curr - next); + + auto p0 = curr - 2.0f * dPrev; + auto p1 = curr - dPrev; + auto p2 = curr - dNext; + auto p3 = curr - 2.0f * dNext; + + outCmds.push(PathCommand::LineTo); + outPts.push(p0); + outCmds.push(PathCommand::CubicTo); + outPts.push(p1); outPts.push(p2); outPts.push(p3); + + dPrev = -1.0f * dNext; + } + } + outCmds.push(PathCommand::Close); + return true; +} + + +bool LottieRoundnessModifier::modifyRect(const Point& size, float& r) const +{ + r = std::min(this->r, std::max(size.x, size.y) * 0.5f); + return true; +} + + +bool LottieOffsetModifier::modifyPath(const PathCommand* inCmds, uint32_t inCmdsCnt, const Point* inPts, uint32_t inPtsCnt, Array& outCmds, Array& outPts) const +{ + outCmds.reserve(inCmdsCnt * 2); + outPts.reserve(inPtsCnt * (join == StrokeJoin::Round ? 4 : 2)); + + Array stack{5}; + State state; + auto offset = _clockwise(inPts, inPtsCnt) ? this->offset : -this->offset; + auto threshold = 1.0f / fabsf(offset) + 1.0f; + + for (uint32_t iCmd = 0, iPt = 0; iCmd < inCmdsCnt; ++iCmd) { + if (inCmds[iCmd] == PathCommand::MoveTo) { + state.moveto = true; + state.movetoInIndex = iPt++; + } else if (inCmds[iCmd] == PathCommand::LineTo) { + line(inCmds, inCmdsCnt, inPts, iPt, iCmd, state, false, outCmds, outPts, offset); + } else if (inCmds[iCmd] == PathCommand::CubicTo) { + //cubic degenerated to a line + if (tvg::zero(inPts[iPt - 1] - inPts[iPt]) || tvg::zero(inPts[iPt + 1] - inPts[iPt + 2])) { + ++iPt; + line(inCmds, inCmdsCnt, inPts, iPt, iCmd, state, true, outCmds, outPts, offset); + ++iPt; + continue; + } + + stack.push({inPts[iPt - 1], inPts[iPt], inPts[iPt + 1], inPts[iPt + 2]}); + while (!stack.empty()) { + auto& bezier = stack.last(); + auto len = tvg::length(bezier.start - bezier.ctrl1) + tvg::length(bezier.ctrl1 - bezier.ctrl2) + tvg::length(bezier.ctrl2 - bezier.end); + + if (len > threshold * bezier.length()) { + Bezier next; + bezier.split(0.5f, next); + stack.push(next); + continue; + } + stack.pop(); + + auto line1 = _offset(bezier.start, bezier.ctrl1, offset); + auto line2 = _offset(bezier.ctrl1, bezier.ctrl2, offset); + auto line3 = _offset(bezier.ctrl2, bezier.end, offset); + + if (state.moveto) { + outCmds.push(PathCommand::MoveTo); + state.movetoOutIndex = outPts.count; + outPts.push(line1.pt1); + state.firstLine = line1; + state.moveto = false; + } + + bool inside{}; + Point intersect{}; + _intersect(line1, line2, intersect, inside); + outPts.push(intersect); + _intersect(line2, line3, intersect, inside); + outPts.push(intersect); + outPts.push(line3.pt2); + outCmds.push(PathCommand::CubicTo); + } + + iPt += 3; + } + else { + if (!_zero(inPts[iPt - 1], inPts[state.movetoInIndex])) { + outCmds.push(PathCommand::LineTo); + corner(state.line, state.firstLine, state.movetoOutIndex, true, outCmds, outPts); + } + outCmds.push(PathCommand::Close); + } + } + return true; +} + + +bool LottieOffsetModifier::modifyPolystar(const Array& inCmds, const Array& inPts, Array& outCmds, Array& outPts) const { + return modifyPath(inCmds.data, inCmds.count, inPts.data, inPts.count, outCmds, outPts); +} + + +bool LottieOffsetModifier::modifyRect(const PathCommand* inCmds, uint32_t inCmdsCnt, const Point* inPts, uint32_t inPtsCnt, Array& outCmds, Array& outPts) const +{ + return modifyPath(inCmds, inCmdsCnt, inPts, inPtsCnt, outCmds, outPts); +} + + +bool LottieOffsetModifier::modifyEllipse(float& rx, float& ry) const +{ + rx += offset; + ry += offset; + return true; +} \ No newline at end of file diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieModifier.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieModifier.h new file mode 100644 index 000000000..cc3f77e62 --- /dev/null +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieModifier.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_LOTTIE_MODIFIER_H_ +#define _TVG_LOTTIE_MODIFIER_H_ + +#include "tvgCommon.h" +#include "tvgArray.h" +#include "tvgMath.h" + + +struct LottieRoundnessModifier +{ + static constexpr float ROUNDNESS_EPSILON = 1.0f; + float r; + + LottieRoundnessModifier(float r) : r(r) {}; + + bool modifyPath(const PathCommand* inCmds, uint32_t inCmdsCnt, const Point* inPts, uint32_t inPtsCnt, Array& outCmds, Array& outPts, Matrix* transform) const; + bool modifyPolystar(const Array& inCmds, const Array& inPts, Array& outCmds, Array& outPts, float outerRoundness, bool hasRoundness) const; + bool modifyRect(const Point& size, float& r) const; +}; + + +struct LottieOffsetModifier +{ + float offset; + float miterLimit; + StrokeJoin join; + + LottieOffsetModifier(float offset, float miter = 4.0f, StrokeJoin join = StrokeJoin::Round) : offset(offset), miterLimit(miter), join(join) {}; + + bool modifyPath(const PathCommand* inCmds, uint32_t inCmdsCnt, const Point* inPts, uint32_t inPtsCnt, Array& outCmds, Array& outPts) const; + bool modifyPolystar(const Array& inCmds, const Array& inPts, Array& outCmds, Array& outPts) const; + bool modifyRect(const PathCommand* inCmds, uint32_t inCmdsCnt, const Point* inPts, uint32_t inPtsCnt, Array& outCmds, Array& outPts) const; + bool modifyEllipse(float& rx, float& ry) const; + +private: + struct State + { + Line line{}; + Line firstLine{}; + bool moveto = false; + uint32_t movetoOutIndex = 0; + uint32_t movetoInIndex = 0; + }; + + void line(const PathCommand* inCmds, uint32_t inCmdsCnt, const Point* inPts, uint32_t& currentPt, uint32_t currentCmd, State& state, bool degenerated, Array& cmds, Array& pts, float offset) const; + void corner(const Line& line, const Line& nextLine, uint32_t movetoIndex, bool nextClose, Array& cmds, Array& pts) const; +}; + +#endif \ No newline at end of file diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieParser.cpp b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieParser.cpp new file mode 100644 index 000000000..dbff50c4f --- /dev/null +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieParser.cpp @@ -0,0 +1,1564 @@ +/* + * Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgStr.h" +#include "tvgCompressor.h" +#include "tvgLottieModel.h" +#include "tvgLottieParser.h" +#include "tvgLottieExpressions.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +#define KEY_AS(name) !strcmp(key, name) + + +static LottieExpression* _expression(char* code, LottieComposition* comp, LottieLayer* layer, LottieObject* object, LottieProperty* property) +{ + if (!comp->expressions) comp->expressions = true; + + auto inst = new LottieExpression; + inst->code = code; + inst->comp = comp; + inst->layer = layer; + inst->object = object; + inst->property = property; + + return inst; +} + + +static unsigned long _int2str(int num) +{ + char str[20]; + snprintf(str, 20, "%d", num); + return djb2Encode(str); +} + + +LottieEffect* LottieParser::getEffect(int type) +{ + switch (type) { + case 25: return new LottieDropShadow; + case 29: return new LottieGaussianBlur; + default: return nullptr; + } +} + + +MaskMethod LottieParser::getMaskMethod(bool inversed) +{ + auto mode = getString(); + if (!mode) return MaskMethod::None; + + switch (mode[0]) { + case 'a': { + if (inversed) return MaskMethod::InvAlpha; + else return MaskMethod::Add; + } + case 's': return MaskMethod::Subtract; + case 'i': return MaskMethod::Intersect; + case 'f': return MaskMethod::Difference; + case 'l': return MaskMethod::Lighten; + case 'd': return MaskMethod::Darken; + default: return MaskMethod::None; + } +} + + +RGB24 LottieParser::getColor(const char *str) +{ + RGB24 color = {0, 0, 0}; + + if (!str) return color; + + auto len = strlen(str); + + // some resource has empty color string, return a default color for those cases. + if (len != 7 || str[0] != '#') return color; + + char tmp[3] = {'\0', '\0', '\0'}; + tmp[0] = str[1]; + tmp[1] = str[2]; + color.rgb[0] = uint8_t(strtol(tmp, nullptr, 16)); + + tmp[0] = str[3]; + tmp[1] = str[4]; + color.rgb[1] = uint8_t(strtol(tmp, nullptr, 16)); + + tmp[0] = str[5]; + tmp[1] = str[6]; + color.rgb[2] = uint8_t(strtol(tmp, nullptr, 16)); + + colorReplaceInternal.getCustomColorLottie32(color.rgb[0], color.rgb[1], color.rgb[2]); + + return color; +} + + +FillRule LottieParser::getFillRule() +{ + switch (getInt()) { + case 1: return FillRule::Winding; + default: return FillRule::EvenOdd; + } +} + + +MaskMethod LottieParser::getMatteType() +{ + switch (getInt()) { + case 1: return MaskMethod::Alpha; + case 2: return MaskMethod::InvAlpha; + case 3: return MaskMethod::Luma; + case 4: return MaskMethod::InvLuma; + default: return MaskMethod::None; + } +} + + +StrokeCap LottieParser::getStrokeCap() +{ + switch (getInt()) { + case 1: return StrokeCap::Butt; + case 2: return StrokeCap::Round; + default: return StrokeCap::Square; + } +} + + +StrokeJoin LottieParser::getStrokeJoin() +{ + switch (getInt()) { + case 1: return StrokeJoin::Miter; + case 2: return StrokeJoin::Round; + default: return StrokeJoin::Bevel; + } +} + + +void LottieParser::getValue(TextDocument& doc) +{ + enterObject(); + while (auto key = nextObjectKey()) { + if (KEY_AS("s")) doc.size = getFloat() * 0.01f; + else if (KEY_AS("f")) doc.name = getStringCopy(); + else if (KEY_AS("t")) doc.text = getStringCopy(); + else if (KEY_AS("j")) doc.justify = getInt(); + else if (KEY_AS("tr")) doc.tracking = getFloat() * 0.1f; + else if (KEY_AS("lh")) doc.height = getFloat(); + else if (KEY_AS("ls")) doc.shift = getFloat(); + else if (KEY_AS("fc")) getValue(doc.color); + else if (KEY_AS("ps")) getValue(doc.bbox.pos); + else if (KEY_AS("sz")) getValue(doc.bbox.size); + else if (KEY_AS("sc")) getValue(doc.stroke.color); + else if (KEY_AS("sw")) doc.stroke.width = getFloat(); + else if (KEY_AS("of")) doc.stroke.render = getBool(); + else skip(key); + } +} + + +void LottieParser::getValue(PathSet& path) +{ + Array outs, ins, pts; + bool closed = false; + + /* The shape object could be wrapped by a array + if its part of the keyframe object */ + auto arrayWrapper = (peekType() == kArrayType) ? true : false; + if (arrayWrapper) enterArray(); + + enterObject(); + while (auto key = nextObjectKey()) { + if (KEY_AS("i")) getValue(ins); + else if (KEY_AS("o")) getValue(outs); + else if (KEY_AS("v")) getValue(pts); + else if (KEY_AS("c")) closed = getBool(); + else skip(key); + } + + //exit properly from the array + if (arrayWrapper) nextArrayValue(); + + //valid path data? + if (ins.empty() || outs.empty() || pts.empty()) return; + if (ins.count != outs.count || outs.count != pts.count) return; + + //convert path + auto out = outs.begin(); + auto in = ins.begin(); + auto pt = pts.begin(); + + //Store manipulated results + Array outPts; + Array outCmds; + + //Reuse the buffers + outPts.data = path.pts; + outPts.reserved = path.ptsCnt; + outCmds.data = path.cmds; + outCmds.reserved = path.cmdsCnt; + + size_t extra = closed ? 3 : 0; + outPts.reserve(pts.count * 3 + 1 + extra); + outCmds.reserve(pts.count + 2); + + outCmds.push(PathCommand::MoveTo); + outPts.push(*pt); + + for (++pt, ++out, ++in; pt < pts.end(); ++pt, ++out, ++in) { + outCmds.push(PathCommand::CubicTo); + outPts.push(*(pt - 1) + *(out - 1)); + outPts.push(*pt + *in); + outPts.push(*pt); + } + + if (closed) { + outPts.push(pts.last() + outs.last()); + outPts.push(pts.first() + ins.first()); + outPts.push(pts.first()); + outCmds.push(PathCommand::CubicTo); + outCmds.push(PathCommand::Close); + } + + path.pts = outPts.data; + path.cmds = outCmds.data; + path.ptsCnt = outPts.count; + path.cmdsCnt = outCmds.count; + + outPts.data = nullptr; + outCmds.data = nullptr; +} + + +void LottieParser::getValue(ColorStop& color) +{ + if (peekType() == kArrayType) enterArray(); + + if (!color.input) color.input = new Array(static_cast(context.parent)->colorStops.count * 6); + else color.input->clear(); + + while (nextArrayValue()) color.input->push(getFloat()); +} + + +void LottieParser::getValue(Array& pts) +{ + enterArray(); + while (nextArrayValue()) { + enterArray(); + Point pt; + getValue(pt); + pts.push(pt); + } +} + + +void LottieParser::getValue(int8_t& val) +{ + if (peekType() == kArrayType) { + enterArray(); + if (nextArrayValue()) val = getInt(); + //discard rest + while (nextArrayValue()) getInt(); + } else { + val = getFloat(); + } +} + + +void LottieParser::getValue(uint8_t& val) +{ + if (peekType() == kArrayType) { + enterArray(); + if (nextArrayValue()) val = (uint8_t)(getFloat() * 2.55f); + //discard rest + while (nextArrayValue()) getFloat(); + } else { + val = (uint8_t)(getFloat() * 2.55f); + } +} + + +void LottieParser::getValue(float& val) +{ + if (peekType() == kArrayType) { + enterArray(); + if (nextArrayValue()) val = getFloat(); + //discard rest + while (nextArrayValue()) getFloat(); + } else { + val = getFloat(); + } +} + + +bool LottieParser::getValue(Point& pt) +{ + auto type = peekType(); + if (type == kNullType) return false; + + int i = 0; + auto ptr = (float*)(&pt); + + if (type == kArrayType) enterArray(); + + while (nextArrayValue()) { + auto val = getFloat(); + if (i < 2) ptr[i++] = val; + } + + return true; +} + + +void LottieParser::getValue(RGB24& color) +{ + int i = 0; + + if (peekType() == kArrayType) enterArray(); + + while (nextArrayValue()) { + auto val = getFloat(); + if (i < 3) color.rgb[i++] = REMAP255(val); + } + colorReplaceInternal.getCustomColorLottie32(color.rgb[0], color.rgb[1], color.rgb[2]); + + //TODO: color filter? +} + + +void LottieParser::getInterpolatorPoint(Point& pt) +{ + enterObject(); + while (auto key = nextObjectKey()) { + if (KEY_AS("x")) getValue(pt.x); + else if (KEY_AS("y")) getValue(pt.y); + } +} + + +template +void LottieParser::parseSlotProperty(T& prop) +{ + while (auto key = nextObjectKey()) { + if (KEY_AS("p")) parseProperty(prop); + else skip(key); + } +} + + +template +bool LottieParser::parseTangent(const char *key, LottieVectorFrame& value) +{ + if (KEY_AS("ti") && getValue(value.inTangent)) ; + else if (KEY_AS("to") && getValue(value.outTangent)) ; + else return false; + + value.hasTangent = true; + return true; +} + + +template +bool LottieParser::parseTangent(const char *key, LottieScalarFrame& value) +{ + return false; +} + + +LottieInterpolator* LottieParser::getInterpolator(const char* key, Point& in, Point& out) +{ + char buf[20]; + + if (!key) { + snprintf(buf, sizeof(buf), "%.2f_%.2f_%.2f_%.2f", in.x, in.y, out.x, out.y); + key = buf; + } + + LottieInterpolator* interpolator = nullptr; + + //get a cached interpolator if it has any. + for (auto i = comp->interpolators.begin(); i < comp->interpolators.end(); ++i) { + if (!strncmp((*i)->key, key, sizeof(buf))) interpolator = *i; + } + + //new interpolator + if (!interpolator) { + interpolator = static_cast(malloc(sizeof(LottieInterpolator))); + interpolator->set(key, in, out); + comp->interpolators.push(interpolator); + } + + return interpolator; +} + + +template +void LottieParser::parseKeyFrame(T& prop) +{ + Point inTangent, outTangent; + const char* interpolatorKey = nullptr; + auto& frame = prop.newFrame(); + auto interpolator = false; + + enterObject(); + + while (auto key = nextObjectKey()) { + if (KEY_AS("i")) { + interpolator = true; + getInterpolatorPoint(inTangent); + } else if (KEY_AS("o")) { + getInterpolatorPoint(outTangent); + } else if (KEY_AS("n")) { + if (peekType() == kStringType) { + interpolatorKey = getString(); + } else { + enterArray(); + while (nextArrayValue()) { + if (!interpolatorKey) interpolatorKey = getString(); + else skip(nullptr); + } + } + } else if (KEY_AS("t")) { + frame.no = getFloat(); + } else if (KEY_AS("s")) { + getValue(frame.value); + } else if (KEY_AS("e")) { + //current end frame and the next start frame is duplicated, + //We propagate the end value to the next frame to avoid having duplicated values. + auto& frame2 = prop.nextFrame(); + getValue(frame2.value); + } else if (parseTangent(key, frame)) { + continue; + } else if (KEY_AS("h")) { + frame.hold = getInt(); + } else skip(key); + } + + if (interpolator) { + frame.interpolator = getInterpolator(interpolatorKey, inTangent, outTangent); + } +} + +template +void LottieParser::parsePropertyInternal(T& prop) +{ + //single value property + if (peekType() == kNumberType) { + getValue(prop.value); + //multi value property + } else { + //TODO: Here might be a single frame. + //Can we figure out the frame number in advance? + enterArray(); + while (nextArrayValue()) { + //keyframes value + if (peekType() == kObjectType) { + parseKeyFrame(prop); + //multi value property with no keyframes + } else { + getValue(prop.value); + break; + } + } + prop.prepare(); + } +} + + +template +void LottieParser::registerSlot(LottieObject* obj) +{ + auto sid = getStringCopy(); + + //append object if the slot already exists. + for (auto slot = comp->slots.begin(); slot < comp->slots.end(); ++slot) { + if (strcmp((*slot)->sid, sid)) continue; + (*slot)->pairs.push({obj}); + return; + } + comp->slots.push(new LottieSlot(sid, obj, type)); +} + + +template +void LottieParser::parseProperty(T& prop, LottieObject* obj) +{ + enterObject(); + while (auto key = nextObjectKey()) { + if (KEY_AS("k")) parsePropertyInternal(prop); + else if (obj && KEY_AS("sid")) registerSlot(obj); + else if (KEY_AS("x")) prop.exp = _expression(getStringCopy(), comp, context.layer, context.parent, &prop); + else if (KEY_AS("ix")) prop.ix = getInt(); + else skip(key); + } + prop.type = type; +} + + +bool LottieParser::parseCommon(LottieObject* obj, const char* key) +{ + if (KEY_AS("nm")) { + obj->id = djb2Encode(getString()); + return true; + } else if (KEY_AS("hd")) { + obj->hidden = getBool(); + return true; + } else return false; +} + + +bool LottieParser::parseDirection(LottieShape* shape, const char* key) +{ + if (KEY_AS("d")) { + if (getInt() == 3) { + shape->clockwise = false; //default is true + } + return true; + } + return false; +} + + +LottieRect* LottieParser::parseRect() +{ + auto rect = new LottieRect; + + context.parent = rect; + + while (auto key = nextObjectKey()) { + if (parseCommon(rect, key)) continue; + else if (KEY_AS("s")) parseProperty(rect->size); + else if (KEY_AS("p")) parseProperty(rect->position); + else if (KEY_AS("r")) parseProperty(rect->radius); + else if (parseDirection(rect, key)) continue; + else skip(key); + } + rect->prepare(); + return rect; +} + + +LottieEllipse* LottieParser::parseEllipse() +{ + auto ellipse = new LottieEllipse; + + context.parent = ellipse; + + while (auto key = nextObjectKey()) { + if (parseCommon(ellipse, key)) continue; + else if (KEY_AS("p")) parseProperty(ellipse->position); + else if (KEY_AS("s")) parseProperty(ellipse->size); + else if (parseDirection(ellipse, key)) continue; + else skip(key); + } + ellipse->prepare(); + return ellipse; +} + + +LottieTransform* LottieParser::parseTransform(bool ddd) +{ + auto transform = new LottieTransform; + + context.parent = transform; + + if (ddd) { + transform->rotationEx = new LottieTransform::RotationEx; + TVGLOG("LOTTIE", "3D transform(ddd) is not totally compatible."); + } + + while (auto key = nextObjectKey()) { + if (parseCommon(transform, key)) continue; + else if (KEY_AS("p")) + { + enterObject(); + while (auto key = nextObjectKey()) { + if (KEY_AS("k")) parsePropertyInternal(transform->position); + else if (KEY_AS("s") && getBool()) transform->coords = new LottieTransform::SeparateCoord; + //check separateCoord to figure out whether "x(expression)" / "x(coord)" + else if (transform->coords && KEY_AS("x")) parseProperty(transform->coords->x); + else if (transform->coords && KEY_AS("y")) parseProperty(transform->coords->y); + else if (KEY_AS("x")) transform->position.exp = _expression(getStringCopy(), comp, context.layer, context.parent, &transform->position); + else skip(key); + } + transform->position.type = LottieProperty::Type::Position; + } + else if (KEY_AS("a")) parseProperty(transform->anchor); + else if (KEY_AS("s")) parseProperty(transform->scale); + else if (KEY_AS("r")) parseProperty(transform->rotation); + else if (KEY_AS("o")) parseProperty(transform->opacity); + else if (transform->rotationEx && KEY_AS("rx")) parseProperty(transform->rotationEx->x); + else if (transform->rotationEx && KEY_AS("ry")) parseProperty(transform->rotationEx->y); + else if (transform->rotationEx && KEY_AS("rz")) parseProperty(transform->rotation); + else if (KEY_AS("sk")) parseProperty(transform->skewAngle); + else if (KEY_AS("sa")) parseProperty(transform->skewAxis); + else skip(key); + } + transform->prepare(); + return transform; +} + + +LottieSolidFill* LottieParser::parseSolidFill() +{ + auto fill = new LottieSolidFill; + + context.parent = fill; + + while (auto key = nextObjectKey()) { + if (parseCommon(fill, key)) continue; + else if (KEY_AS("c")) parseProperty(fill->color, fill); + else if (KEY_AS("o")) parseProperty(fill->opacity, fill); + else if (KEY_AS("fillEnabled")) fill->hidden |= !getBool(); + else if (KEY_AS("r")) fill->rule = getFillRule(); + else skip(key); + } + fill->prepare(); + return fill; +} + + +void LottieParser::parseStrokeDash(LottieStroke* stroke) +{ + enterArray(); + while (nextArrayValue()) { + enterObject(); + int idx = 0; + while (auto key = nextObjectKey()) { + if (KEY_AS("n")) { + auto style = getString(); + if (!strcmp("o", style)) idx = 0; //offset + else if (!strcmp("d", style)) idx = 1; //dash + else if (!strcmp("g", style)) idx = 2; //gap + } else if (KEY_AS("v")) { + parseProperty(stroke->dash(idx)); + } else skip(key); + } + } +} + + +LottieSolidStroke* LottieParser::parseSolidStroke() +{ + auto stroke = new LottieSolidStroke; + + context.parent = stroke; + + while (auto key = nextObjectKey()) { + if (parseCommon(stroke, key)) continue; + else if (KEY_AS("c")) parseProperty(stroke->color, stroke); + else if (KEY_AS("o")) parseProperty(stroke->opacity, stroke); + else if (KEY_AS("w")) parseProperty(stroke->width, stroke); + else if (KEY_AS("lc")) stroke->cap = getStrokeCap(); + else if (KEY_AS("lj")) stroke->join = getStrokeJoin(); + else if (KEY_AS("ml")) stroke->miterLimit = getFloat(); + else if (KEY_AS("fillEnabled")) stroke->hidden |= !getBool(); + else if (KEY_AS("d")) parseStrokeDash(stroke); + else skip(key); + } + stroke->prepare(); + return stroke; +} + + +void LottieParser::getPathSet(LottiePathSet& path) +{ + enterObject(); + while (auto key = nextObjectKey()) { + if (KEY_AS("k")) { + if (peekType() == kArrayType) { + enterArray(); + while (nextArrayValue()) parseKeyFrame(path); + } else { + getValue(path.value); + } + } else if (KEY_AS("x")) { + path.exp = _expression(getStringCopy(), comp, context.layer, context.parent, &path); + } else skip(key); + } + path.type = LottieProperty::Type::PathSet; +} + + +LottiePath* LottieParser::parsePath() +{ + auto path = new LottiePath; + + while (auto key = nextObjectKey()) { + if (parseCommon(path, key)) continue; + else if (KEY_AS("ks")) getPathSet(path->pathset); + else if (parseDirection(path, key)) continue; + else skip(key); + } + path->prepare(); + return path; +} + + +LottiePolyStar* LottieParser::parsePolyStar() +{ + auto star = new LottiePolyStar; + + context.parent = star; + + while (auto key = nextObjectKey()) { + if (parseCommon(star, key)) continue; + else if (KEY_AS("p")) parseProperty(star->position); + else if (KEY_AS("pt")) parseProperty(star->ptsCnt); + else if (KEY_AS("ir")) parseProperty(star->innerRadius); + else if (KEY_AS("is")) parseProperty(star->innerRoundness); + else if (KEY_AS("or")) parseProperty(star->outerRadius); + else if (KEY_AS("os")) parseProperty(star->outerRoundness); + else if (KEY_AS("r")) parseProperty(star->rotation); + else if (KEY_AS("sy")) star->type = (LottiePolyStar::Type) getInt(); + else if (parseDirection(star, key)) continue; + else skip(key); + } + star->prepare(); + return star; +} + + +LottieRoundedCorner* LottieParser::parseRoundedCorner() +{ + auto corner = new LottieRoundedCorner; + + context.parent = corner; + + while (auto key = nextObjectKey()) { + if (parseCommon(corner, key)) continue; + else if (KEY_AS("r")) parseProperty(corner->radius); + else skip(key); + } + corner->prepare(); + return corner; +} + + +void LottieParser::parseColorStop(LottieGradient* gradient) +{ + enterObject(); + while (auto key = nextObjectKey()) { + if (KEY_AS("p")) gradient->colorStops.count = getInt(); + else if (KEY_AS("k")) parseProperty(gradient->colorStops, gradient); + else if (KEY_AS("sid")) registerSlot(gradient); + else skip(key); + } +} + + +void LottieParser::parseGradient(LottieGradient* gradient, const char* key) +{ + if (KEY_AS("t")) gradient->id = getInt(); + else if (KEY_AS("o")) parseProperty(gradient->opacity, gradient); + else if (KEY_AS("g")) parseColorStop(gradient); + else if (KEY_AS("s")) parseProperty(gradient->start, gradient); + else if (KEY_AS("e")) parseProperty(gradient->end, gradient); + else if (KEY_AS("h")) parseProperty(gradient->height, gradient); + else if (KEY_AS("a")) parseProperty(gradient->angle, gradient); + else skip(key); +} + + +LottieGradientFill* LottieParser::parseGradientFill() +{ + auto fill = new LottieGradientFill(&colorReplaceInternal); + + context.parent = fill; + + while (auto key = nextObjectKey()) { + if (parseCommon(fill, key)) continue; + else if (KEY_AS("r")) fill->rule = getFillRule(); + else parseGradient(fill, key); + } + + fill->prepare(); + + return fill; +} + + +LottieGradientStroke* LottieParser::parseGradientStroke() +{ + auto stroke = new LottieGradientStroke(&colorReplaceInternal); + + context.parent = stroke; + + while (auto key = nextObjectKey()) { + if (parseCommon(stroke, key)) continue; + else if (KEY_AS("lc")) stroke->cap = getStrokeCap(); + else if (KEY_AS("lj")) stroke->join = getStrokeJoin(); + else if (KEY_AS("ml")) stroke->miterLimit = getFloat(); + else if (KEY_AS("w")) parseProperty(stroke->width); + else if (KEY_AS("d")) parseStrokeDash(stroke); + else parseGradient(stroke, key); + } + stroke->prepare(); + + return stroke; +} + + +LottieTrimpath* LottieParser::parseTrimpath() +{ + auto trim = new LottieTrimpath; + + context.parent = trim; + + while (auto key = nextObjectKey()) { + if (parseCommon(trim, key)) continue; + else if (KEY_AS("s")) parseProperty(trim->start); + else if (KEY_AS("e")) parseProperty(trim->end); + else if (KEY_AS("o")) parseProperty(trim->offset); + else if (KEY_AS("m")) trim->type = static_cast(getInt()); + else skip(key); + } + trim->prepare(); + + return trim; +} + + +LottieRepeater* LottieParser::parseRepeater() +{ + auto repeater = new LottieRepeater; + + context.parent = repeater; + + while (auto key = nextObjectKey()) { + if (parseCommon(repeater, key)) continue; + else if (KEY_AS("c")) parseProperty(repeater->copies); + else if (KEY_AS("o")) parseProperty(repeater->offset); + else if (KEY_AS("m")) repeater->inorder = getInt() == 2; + else if (KEY_AS("tr")) + { + enterObject(); + while (auto key = nextObjectKey()) { + if (KEY_AS("a")) parseProperty(repeater->anchor); + else if (KEY_AS("p")) parseProperty(repeater->position); + else if (KEY_AS("r")) parseProperty(repeater->rotation); + else if (KEY_AS("s")) parseProperty(repeater->scale); + else if (KEY_AS("so")) parseProperty(repeater->startOpacity); + else if (KEY_AS("eo")) parseProperty(repeater->endOpacity); + else skip(key); + } + } + else skip(key); + } + repeater->prepare(); + + return repeater; +} + + +LottieOffsetPath* LottieParser::parseOffsetPath() +{ + auto offsetPath = new LottieOffsetPath; + + context.parent = offsetPath; + + while (auto key = nextObjectKey()) { + if (parseCommon(offsetPath, key)) continue; + else if (KEY_AS("a")) parseProperty(offsetPath->offset); + else if (KEY_AS("lj")) offsetPath->join = getStrokeJoin(); + else if (KEY_AS("ml")) parseProperty(offsetPath->miterLimit); + else skip(key); + } + offsetPath->prepare(); + + return offsetPath; +} + + +LottieObject* LottieParser::parseObject() +{ + auto type = getString(); + if (!type) return nullptr; + + if (!strcmp(type, "gr")) return parseGroup(); + else if (!strcmp(type, "rc")) return parseRect(); + else if (!strcmp(type, "el")) return parseEllipse(); + else if (!strcmp(type, "tr")) return parseTransform(); + else if (!strcmp(type, "fl")) return parseSolidFill(); + else if (!strcmp(type, "st")) return parseSolidStroke(); + else if (!strcmp(type, "sh")) return parsePath(); + else if (!strcmp(type, "sr")) return parsePolyStar(); + else if (!strcmp(type, "rd")) return parseRoundedCorner(); + else if (!strcmp(type, "gf")) return parseGradientFill(); + else if (!strcmp(type, "gs")) return parseGradientStroke(); + else if (!strcmp(type, "tm")) return parseTrimpath(); + else if (!strcmp(type, "rp")) return parseRepeater(); + else if (!strcmp(type, "mm")) TVGLOG("LOTTIE", "MergePath(mm) is not supported yet"); + else if (!strcmp(type, "pb")) TVGLOG("LOTTIE", "Puker/Bloat(pb) is not supported yet"); + else if (!strcmp(type, "tw")) TVGLOG("LOTTIE", "Twist(tw) is not supported yet"); + else if (!strcmp(type, "op")) return parseOffsetPath(); + else if (!strcmp(type, "zz")) TVGLOG("LOTTIE", "ZigZag(zz) is not supported yet"); + return nullptr; +} + + +void LottieParser::parseObject(Array& parent) +{ + enterObject(); + while (auto key = nextObjectKey()) { + if (KEY_AS("ty")) { + if (auto child = parseObject()) { + if (child->hidden) delete(child); + else parent.push(child); + } + } else skip(key); + } +} + + +LottieImage* LottieParser::parseImage(const char* data, const char* subPath, bool embedded, float width, float height) +{ + //Used for Image Asset + auto image = new LottieImage; + + //embedded image resource. should start with "data:" + //header look like "data:image/png;base64," so need to skip till ','. + if (embedded && !strncmp(data, "data:", 5)) { + //figure out the mimetype + auto mimeType = data + 11; + auto needle = strstr(mimeType, ";"); + image->mimeType = strDuplicate(mimeType, needle - mimeType); + //b64 data + auto b64Data = strstr(data, ",") + 1; + size_t length = strlen(data) - (b64Data - data); + image->size = b64Decode(b64Data, length, &image->b64Data); + //external image resource + } else { + auto len = strlen(dirName) + strlen(subPath) + strlen(data) + 2; + image->path = static_cast(malloc(len)); + snprintf(image->path, len, "%s/%s%s", dirName, subPath, data); + } + + image->width = width; + image->height = height; + image->prepare(); + + return image; +} + + +LottieObject* LottieParser::parseAsset() +{ + enterObject(); + + LottieObject* obj = nullptr; + unsigned long id = 0; + + //Used for Image Asset + const char* data = nullptr; + const char* subPath = nullptr; + float width = 0.0f; + float height = 0.0f; + auto embedded = false; + + while (auto key = nextObjectKey()) { + if (KEY_AS("id")) + { + if (peekType() == kStringType) { + id = djb2Encode(getString()); + } else { + id = _int2str(getInt()); + } + } + else if (KEY_AS("layers")) obj = parseLayers(comp->root); + else if (KEY_AS("u")) subPath = getString(); + else if (KEY_AS("p")) data = getString(); + else if (KEY_AS("w")) width = getFloat(); + else if (KEY_AS("h")) height = getFloat(); + else if (KEY_AS("e")) embedded = getInt(); + else skip(key); + } + if (data) obj = parseImage(data, subPath, embedded, width, height); + if (obj) obj->id = id; + return obj; +} + + +LottieFont* LottieParser::parseFont() +{ + enterObject(); + + auto font = new LottieFont; + + while (auto key = nextObjectKey()) { + if (KEY_AS("fName")) font->name = getStringCopy(); + else if (KEY_AS("fFamily")) font->family = getStringCopy(); + else if (KEY_AS("fStyle")) font->style = getStringCopy(); + else if (KEY_AS("ascent")) font->ascent = getFloat(); + else if (KEY_AS("origin")) font->origin = (LottieFont::Origin) getInt(); + else skip(key); + } + return font; +} + + +void LottieParser::parseAssets() +{ + enterArray(); + while (nextArrayValue()) { + auto asset = parseAsset(); + if (asset) comp->assets.push(asset); + else TVGERR("LOTTIE", "Invalid Asset!"); + } +} + +LottieMarker* LottieParser::parseMarker() +{ + enterObject(); + + auto marker = new LottieMarker; + + while (auto key = nextObjectKey()) { + if (KEY_AS("cm")) marker->name = getStringCopy(); + else if (KEY_AS("tm")) marker->time = getFloat(); + else if (KEY_AS("dr")) marker->duration = getFloat(); + else skip(key); + } + + return marker; +} + +void LottieParser::parseMarkers() +{ + enterArray(); + while (nextArrayValue()) { + comp->markers.push(parseMarker()); + } +} + +void LottieParser::parseChars(Array& glyphs) +{ + enterArray(); + while (nextArrayValue()) { + enterObject(); + //a new glyph + auto glyph = new LottieGlyph; + while (auto key = nextObjectKey()) { + if (KEY_AS("ch")) glyph->code = getStringCopy(); + else if (KEY_AS("size")) glyph->size = static_cast(getFloat()); + else if (KEY_AS("style")) glyph->style = getStringCopy(); + else if (KEY_AS("w")) glyph->width = getFloat(); + else if (KEY_AS("fFamily")) glyph->family = getStringCopy(); + else if (KEY_AS("data")) + { //glyph shapes + enterObject(); + while (auto key = nextObjectKey()) { + if (KEY_AS("shapes")) parseShapes(glyph->children); + } + } else skip(key); + } + glyph->prepare(); + glyphs.push(glyph); + } +} + +void LottieParser::parseFonts() +{ + enterObject(); + while (auto key = nextObjectKey()) { + if (KEY_AS("list")) { + enterArray(); + while (nextArrayValue()) { + comp->fonts.push(parseFont()); + } + } else skip(key); + } +} + + +LottieObject* LottieParser::parseGroup() +{ + auto group = new LottieGroup; + + while (auto key = nextObjectKey()) { + if (parseCommon(group, key)) continue; + else if (KEY_AS("it")) { + enterArray(); + while (nextArrayValue()) parseObject(group->children); + } else skip(key); + } + if (group->children.empty()) { + delete(group); + return nullptr; + } + group->prepare(); + + return group; +} + + +void LottieParser::parseTimeRemap(LottieLayer* layer) +{ + parseProperty(layer->timeRemap); +} + + +void LottieParser::parseShapes(Array& parent) +{ + enterArray(); + while (nextArrayValue()) { + enterObject(); + while (auto key = nextObjectKey()) { + if (KEY_AS("it")) { + enterArray(); + while (nextArrayValue()) parseObject(parent); + } else if (KEY_AS("ty")) { + if (auto child = parseObject()) { + if (child->hidden) delete(child); + else parent.push(child); + } + } else skip(key); + } + } +} + + +void LottieParser::parseTextAlignmentOption(LottieText* text) +{ + enterObject(); + while (auto key = nextObjectKey()) { + if (KEY_AS("g")) text->alignOption.grouping = (LottieText::AlignOption::Group) getInt(); + else if (KEY_AS("a")) parseProperty(text->alignOption.anchor); + else skip(key); + } +} + + +void LottieParser::parseTextRange(LottieText* text) +{ + enterArray(); + while (nextArrayValue()) { + enterObject(); + + auto selector = new LottieTextRange; + + while (auto key = nextObjectKey()) { + if (KEY_AS("s")) { // text range selector + enterObject(); + while (auto key = nextObjectKey()) { + if (KEY_AS("t")) selector->expressible = (bool) getInt(); + else if (KEY_AS("xe")) parseProperty(selector->maxEase); + else if (KEY_AS("ne")) parseProperty(selector->minEase); + else if (KEY_AS("a")) parseProperty(selector->maxAmount); + else if (KEY_AS("b")) selector->based = (LottieTextRange::Based) getInt(); + else if (KEY_AS("rn")) selector->random = getInt() ? rand() : 0; + else if (KEY_AS("sh")) selector->shape = (LottieTextRange::Shape) getInt(); + else if (KEY_AS("o")) parseProperty(selector->offset); + else if (KEY_AS("r")) selector->rangeUnit = (LottieTextRange::Unit) getInt(); + else if (KEY_AS("sm")) parseProperty(selector->smoothness); + else if (KEY_AS("s")) parseProperty(selector->start); + else if (KEY_AS("e")) parseProperty(selector->end); + else skip(key); + } + } else if (KEY_AS("a")) { // text style + enterObject(); + while (auto key = nextObjectKey()) { + if (KEY_AS("t")) parseProperty(selector->style.letterSpacing); + else if (KEY_AS("ls")) parseProperty(selector->style.lineSpacing); + else if (KEY_AS("fc")) parseProperty(selector->style.fillColor); + else if (KEY_AS("fo")) parseProperty(selector->style.fillOpacity); + else if (KEY_AS("sw")) parseProperty(selector->style.strokeWidth); + else if (KEY_AS("sc")) parseProperty(selector->style.strokeColor); + else if (KEY_AS("so")) parseProperty(selector->style.strokeOpacity); + else if (KEY_AS("o")) parseProperty(selector->style.opacity); + else if (KEY_AS("p")) parseProperty(selector->style.position); + else if (KEY_AS("s")) parseProperty(selector->style.scale); + else if (KEY_AS("r")) parseProperty(selector->style.rotation); + else skip(key); + } + } else skip(key); + } + + text->ranges.push(selector); + } +} + + +void LottieParser::parseText(Array& parent) +{ + enterObject(); + + auto text = new LottieText; + + while (auto key = nextObjectKey()) { + if (KEY_AS("d")) parseProperty(text->doc, text); + else if (KEY_AS("a")) parseTextRange(text); + else if (KEY_AS("m")) parseTextAlignmentOption(text); + else if (KEY_AS("p")) + { + TVGLOG("LOTTIE", "Text Follow Path (p) is not supported"); + skip(key); + } + else skip(key); + } + + text->prepare(); + parent.push(text); +} + + +void LottieParser::getLayerSize(float& val) +{ + if (val == 0.0f) { + val = getFloat(); + } else { + //layer might have both w(width) & sw(solid color width) + //override one if the a new size is smaller. + auto w = getFloat(); + if (w < val) val = w; + } +} + +LottieMask* LottieParser::parseMask() +{ + auto mask = new LottieMask; + auto valid = true; //skip if the mask mode is none. + + enterObject(); + while (auto key = nextObjectKey()) { + if (KEY_AS("inv")) mask->inverse = getBool(); + else if (KEY_AS("mode")) + { + mask->method = getMaskMethod(mask->inverse); + if (mask->method == MaskMethod::None) valid = false; + } + else if (valid && KEY_AS("pt")) getPathSet(mask->pathset); + else if (valid && KEY_AS("o")) parseProperty(mask->opacity); + else if (valid && KEY_AS("x")) parseProperty(mask->expand); + else skip(key); + } + + if (!valid) { + delete(mask); + return nullptr; + } + + return mask; +} + + +void LottieParser::parseMasks(LottieLayer* layer) +{ + enterArray(); + while (nextArrayValue()) { + if (auto mask = parseMask()) { + layer->masks.push(mask); + } + } +} + + +void LottieParser::parseGaussianBlur(LottieGaussianBlur* effect) +{ + int idx = 0; //blurness -> direction -> wrap + enterArray(); + while (nextArrayValue()) { + enterObject(); + while (auto key = nextObjectKey()) { + if (KEY_AS("v")) { + enterObject(); + while (auto key = nextObjectKey()) { + if (KEY_AS("k")) { + if (idx == 0) parsePropertyInternal(effect->blurness); + else if (idx == 1) parsePropertyInternal(effect->direction); + else if (idx == 2) parsePropertyInternal(effect->wrap); + else skip(key); + ++idx; + } else skip(key); + } + } else skip(key); + } + } +} + + +void LottieParser::parseDropShadow(LottieDropShadow* effect) +{ + int idx = 0; //color -> opacity -> angle -> distance -> blur + enterArray(); + while (nextArrayValue()) { + enterObject(); + while (auto key = nextObjectKey()) { + if (KEY_AS("v")) { + enterObject(); + while (auto key = nextObjectKey()) { + if (KEY_AS("k")) { + if (idx == 0) parsePropertyInternal(effect->color); + else if (idx == 1) parsePropertyInternal(effect->opacity); + else if (idx == 2) parsePropertyInternal(effect->angle); + else if (idx == 3) parsePropertyInternal(effect->distance); + else if (idx == 4) parsePropertyInternal(effect->blurness); + else skip(key); + ++idx; + } else skip(key); + } + } else skip(key); + } + } +} + + +void LottieParser::parseEffect(LottieEffect* effect) +{ + switch (effect->type) { + case LottieEffect::GaussianBlur: { + parseGaussianBlur(static_cast(effect)); + break; + } + case LottieEffect::DropShadow: { + parseDropShadow(static_cast(effect)); + break; + } + default: break; + } +} + + +void LottieParser::parseEffects(LottieLayer* layer) +{ + auto invalid = true; + + enterArray(); + while (nextArrayValue()) { + LottieEffect* effect = nullptr; + enterObject(); + while (auto key = nextObjectKey()) { + //type must be priortized. + if (KEY_AS("ty")) + { + effect = getEffect(getInt()); + if (!effect) break; + else invalid = false; + } + else if (effect && KEY_AS("en")) effect->enable = getInt(); + else if (effect && KEY_AS("ef")) parseEffect(effect); + else skip(key); + } + //TODO: remove when all effects were guaranteed. + if (invalid) { + TVGLOG("LOTTIE", "Not supported Layer Effect = %d", effect ? (int)effect->type : -1); + while (auto key = nextObjectKey()) skip(key); + } else layer->effects.push(effect); + } +} + + +LottieLayer* LottieParser::parseLayer(LottieLayer* precomp) +{ + auto layer = new LottieLayer; + + layer->comp = precomp; + context.layer = layer; + + auto ddd = false; + RGB24 color; + + enterObject(); + + while (auto key = nextObjectKey()) { + if (KEY_AS("nm")) + { + layer->name = getStringCopy(); + layer->id = djb2Encode(layer->name); + } + else if (KEY_AS("ddd")) ddd = getInt(); //3d layer + else if (KEY_AS("ind")) layer->idx = getInt(); + else if (KEY_AS("ty")) layer->type = (LottieLayer::Type) getInt(); + else if (KEY_AS("sr")) layer->timeStretch = getFloat(); + else if (KEY_AS("ks")) + { + enterObject(); + layer->transform = parseTransform(ddd); + } + else if (KEY_AS("ao")) layer->autoOrient = getInt(); + else if (KEY_AS("shapes")) parseShapes(layer->children); + else if (KEY_AS("ip")) layer->inFrame = getFloat(); + else if (KEY_AS("op")) layer->outFrame = getFloat(); + else if (KEY_AS("st")) layer->startFrame = getFloat(); + else if (KEY_AS("bm")) layer->blendMethod = (BlendMethod) getInt(); + else if (KEY_AS("parent")) layer->pidx = getInt(); + else if (KEY_AS("tm")) parseTimeRemap(layer); + else if (KEY_AS("w") || KEY_AS("sw")) getLayerSize(layer->w); + else if (KEY_AS("h") || KEY_AS("sh")) getLayerSize(layer->h); + else if (KEY_AS("sc")) color = getColor(getString()); + else if (KEY_AS("tt")) layer->matteType = getMatteType(); + else if (KEY_AS("tp")) layer->mid = getInt(); + else if (KEY_AS("masksProperties")) parseMasks(layer); + else if (KEY_AS("hd")) layer->hidden = getBool(); + else if (KEY_AS("refId")) layer->rid = djb2Encode(getString()); + else if (KEY_AS("td")) layer->matteSrc = getInt(); //used for matte layer + else if (KEY_AS("t")) parseText(layer->children); + else if (KEY_AS("ef")) parseEffects(layer); + else skip(key); + } + + layer->prepare(&color); + + return layer; +} + + +LottieLayer* LottieParser::parseLayers(LottieLayer* root) +{ + auto precomp = new LottieLayer; + + precomp->type = LottieLayer::Precomp; + precomp->comp = root; + + enterArray(); + while (nextArrayValue()) { + precomp->children.push(parseLayer(precomp)); + } + + precomp->prepare(); + return precomp; +} + + +void LottieParser::postProcess(Array& glyphs) +{ + //aggregate font characters + for (uint32_t g = 0; g < glyphs.count; ++g) { + auto glyph = glyphs[g]; + for (uint32_t i = 0; i < comp->fonts.count; ++i) { + auto& font = comp->fonts[i]; + if (!strcmp(font->family, glyph->family) && !strcmp(font->style, glyph->style)) { + font->chars.push(glyph); + free(glyph->family); + free(glyph->style); + break; + } + } + } +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +const char* LottieParser::sid(bool first) +{ + if (first) { + //verify json + if (!parseNext()) return nullptr; + enterObject(); + } + return nextObjectKey(); +} + + +bool LottieParser::apply(LottieSlot* slot) +{ + enterObject(); + + //OPTIMIZE: we can create the property directly, without object + LottieObject* obj = nullptr; //slot object + + switch (slot->type) { + case LottieProperty::Type::ColorStop: { + obj = new LottieGradient(&colorReplaceInternal); + context.parent = obj; + while (auto key = nextObjectKey()) { + if (KEY_AS("p")) parseColorStop(static_cast(obj)); + else skip(key); + } + break; + } + case LottieProperty::Type::Color: { + obj = new LottieSolid; + context.parent = obj; + parseSlotProperty(static_cast(obj)->color); + break; + } + case LottieProperty::Type::TextDoc: { + obj = new LottieText; + context.parent = obj; + parseSlotProperty(static_cast(obj)->doc); + break; + } + default: break; + } + + if (!obj || Invalid()) return false; + + slot->assign(obj, &colorReplaceInternal); + + delete(obj); + + return true; +} + + +bool LottieParser::parse() +{ + //verify json. + if (!parseNext()) return false; + + enterObject(); + + if (comp) delete(comp); + comp = new LottieComposition; + + Array glyphs; + + auto startFrame = 0.0f; + auto endFrame = 0.0f; + + while (auto key = nextObjectKey()) { + if (KEY_AS("v")) comp->version = getStringCopy(); + else if (KEY_AS("fr")) comp->frameRate = getFloat(); + else if (KEY_AS("ip")) startFrame = getFloat(); + else if (KEY_AS("op")) endFrame = getFloat(); + else if (KEY_AS("w")) comp->w = getFloat(); + else if (KEY_AS("h")) comp->h = getFloat(); + else if (KEY_AS("nm")) comp->name = getStringCopy(); + else if (KEY_AS("assets")) parseAssets(); + else if (KEY_AS("layers")) comp->root = parseLayers(comp->root); + else if (KEY_AS("fonts")) parseFonts(); + else if (KEY_AS("chars")) parseChars(glyphs); + else if (KEY_AS("markers")) parseMarkers(); + else skip(key); + } + + if (Invalid() || !comp->root) { + delete(comp); + return false; + } + + comp->root->inFrame = startFrame; + comp->root->outFrame = endFrame; + + postProcess(glyphs); + + return true; +} diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieParser.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieParser.h new file mode 100644 index 000000000..c5c99b11c --- /dev/null +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieParser.h @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_LOTTIE_PARSER_H_ +#define _TVG_LOTTIE_PARSER_H_ + +#include "tvgCommon.h" +#include "tvgLottieParserHandler.h" +#include "tvgLottieProperty.h" + +struct LottieParser : LookaheadParserHandler +{ +public: + LottieParser(const char *str, const char* dirName, const ColorReplace &colorReplace) : LookaheadParserHandler(str) + { + this->dirName = dirName; + colorReplaceInternal = colorReplace; + } + + bool parse(); + bool apply(LottieSlot* slot); + const char* sid(bool first = false); + template void registerSlot(LottieObject* obj); + + LottieComposition* comp = nullptr; + const char* dirName = nullptr; //base resource directory + +private: + ColorReplace colorReplaceInternal; + RGB24 getColor(const char *str); + MaskMethod getMatteType(); + FillRule getFillRule(); + StrokeCap getStrokeCap(); + StrokeJoin getStrokeJoin(); + MaskMethod getMaskMethod(bool inversed); + LottieInterpolator* getInterpolator(const char* key, Point& in, Point& out); + LottieEffect* getEffect(int type); + + void getInterpolatorPoint(Point& pt); + void getPathSet(LottiePathSet& path); + void getLayerSize(float& val); + void getValue(TextDocument& doc); + void getValue(PathSet& path); + void getValue(Array& pts); + void getValue(ColorStop& color); + void getValue(float& val); + void getValue(uint8_t& val); + void getValue(int8_t& val); + void getValue(RGB24& color); + bool getValue(Point& pt); + + template bool parseTangent(const char *key, LottieVectorFrame& value); + template bool parseTangent(const char *key, LottieScalarFrame& value); + template void parseKeyFrame(T& prop); + template void parsePropertyInternal(T& prop); + template void parseProperty(T& prop, LottieObject* obj = nullptr); + template void parseSlotProperty(T& prop); + + LottieObject* parseObject(); + LottieObject* parseAsset(); + LottieImage* parseImage(const char* data, const char* subPath, bool embedded, float width, float height); + LottieLayer* parseLayer(LottieLayer* precomp); + LottieObject* parseGroup(); + LottieRect* parseRect(); + LottieEllipse* parseEllipse(); + LottieSolidFill* parseSolidFill(); + LottieTransform* parseTransform(bool ddd = false); + LottieSolidStroke* parseSolidStroke(); + LottieGradientStroke* parseGradientStroke(); + LottiePath* parsePath(); + LottiePolyStar* parsePolyStar(); + LottieRoundedCorner* parseRoundedCorner(); + LottieGradientFill* parseGradientFill(); + LottieLayer* parseLayers(LottieLayer* root); + LottieMask* parseMask(); + LottieTrimpath* parseTrimpath(); + LottieRepeater* parseRepeater(); + LottieOffsetPath* parseOffsetPath(); + LottieFont* parseFont(); + LottieMarker* parseMarker(); + + void parseGaussianBlur(LottieGaussianBlur* effect); + void parseDropShadow(LottieDropShadow* effect); + + bool parseDirection(LottieShape* shape, const char* key); + bool parseCommon(LottieObject* obj, const char* key); + void parseObject(Array& parent); + void parseShapes(Array& parent); + void parseText(Array& parent); + void parseMasks(LottieLayer* layer); + void parseEffects(LottieLayer* layer); + void parseTimeRemap(LottieLayer* layer); + void parseStrokeDash(LottieStroke* stroke); + void parseGradient(LottieGradient* gradient, const char* key); + void parseColorStop(LottieGradient* gradient); + void parseTextRange(LottieText* text); + void parseTextAlignmentOption(LottieText* text); + void parseAssets(); + void parseFonts(); + void parseChars(Array& glyphs); + void parseMarkers(); + void parseEffect(LottieEffect* effect); + void postProcess(Array& glyphs); + + //Current parsing context + struct Context { + LottieLayer* layer = nullptr; + LottieObject* parent = nullptr; + } context; +}; + +#endif //_TVG_LOTTIE_PARSER_H_ diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieParserHandler.cpp b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieParserHandler.cpp new file mode 100644 index 000000000..4c7b355ac --- /dev/null +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieParserHandler.cpp @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgLottieParserHandler.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static const int PARSE_FLAGS = kParseDefaultFlags | kParseInsituFlag; + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + + +bool LookaheadParserHandler::enterArray() +{ + if (state != kEnteringArray) { + Error(); + return false; + } + parseNext(); + return true; +} + + +bool LookaheadParserHandler::nextArrayValue() +{ + if (state == kExitingArray) { + parseNext(); + return false; + } + //SPECIAL CASE: same as nextObjectKey() + if (state == kExitingObject) return false; + if (state == kError || state == kHasKey) { + Error(); + return false; + } + return true; +} + + +int LookaheadParserHandler::getInt() +{ + if (state != kHasNumber) { + Error(); + return 0; + } + auto result = val.GetInt(); + parseNext(); + return result; +} + + +float LookaheadParserHandler::getFloat() +{ + if (state != kHasNumber) { + Error(); + return 0; + } + auto result = val.GetFloat(); + parseNext(); + return result; +} + + +const char* LookaheadParserHandler::getString() +{ + if (state != kHasString) { + Error(); + return nullptr; + } + auto result = val.GetString(); + parseNext(); + return result; +} + + +char* LookaheadParserHandler::getStringCopy() +{ + auto str = getString(); + if (str) return strdup(str); + return nullptr; +} + + +bool LookaheadParserHandler::getBool() +{ + if (state != kHasBool) { + Error(); + return false; + } + auto result = val.GetBool(); + parseNext(); + return result; +} + + +void LookaheadParserHandler::getNull() +{ + if (state != kHasNull) { + Error(); + return; + } + parseNext(); +} + + +bool LookaheadParserHandler::parseNext() +{ + if (reader.HasParseError()) { + Error(); + return false; + } + if (!reader.IterativeParseNext(iss, *this)) { + Error(); + return false; + } + return true; +} + + +bool LookaheadParserHandler::enterObject() +{ + if (state != kEnteringObject) { + Error(); + return false; + } + parseNext(); + return true; +} + + +int LookaheadParserHandler::peekType() +{ + if (state >= kHasNull && state <= kHasKey) return val.GetType(); + if (state == kEnteringArray) return kArrayType; + if (state == kEnteringObject) return kObjectType; + return -1; +} + + +void LookaheadParserHandler::skipOut(int depth) +{ + do { + if (state == kEnteringArray || state == kEnteringObject) ++depth; + else if (state == kExitingArray || state == kExitingObject) --depth; + else if (state == kError) return; + parseNext(); + } while (depth > 0); +} + + +const char* LookaheadParserHandler::nextObjectKey() +{ + if (state == kHasKey) { + auto result = val.GetString(); + parseNext(); + return result; + } + + /* SPECIAL CASE: The parser works with a predefined rule that it will be only + while (nextObjectKey()) for each object but in case of our nested group + object we can call multiple time nextObjectKey() while exiting the object + so ignore those and don't put parser in the error state. */ + if (state == kExitingArray || state == kEnteringObject) return nullptr; + + if (state != kExitingObject) { + Error(); + return nullptr; + } + + parseNext(); + return nullptr; +} + + +void LookaheadParserHandler::skip(const char* key) +{ + if (peekType() == kArrayType) { + enterArray(); + skipOut(1); + } else if (peekType() == kObjectType) { + enterObject(); + skipOut(1); + } else { + skipOut(0); + } +} \ No newline at end of file diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieParserHandler.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieParserHandler.h new file mode 100644 index 000000000..2a2d77931 --- /dev/null +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieParserHandler.h @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_LOTTIE_PARSER_HANDLER_H_ +#define _TVG_LOTTIE_PARSER_HANDLER_H_ + +#include "rapidjson/document.h" +#include "tvgCommon.h" + + +using namespace rapidjson; + + +struct LookaheadParserHandler +{ + enum LookaheadParsingState { + kInit = 0, + kError, + kHasNull, + kHasBool, + kHasNumber, + kHasString, + kHasKey, + kEnteringObject, + kExitingObject, + kEnteringArray, + kExitingArray + }; + + Value val; + LookaheadParsingState state = kInit; + Reader reader; + InsituStringStream iss; + + LookaheadParserHandler(const char *str) : iss((char*)str) + { + reader.IterativeParseInit(); + } + + bool Null() + { + state = kHasNull; + val.SetNull(); + return true; + } + + bool Bool(bool b) + { + state = kHasBool; + val.SetBool(b); + return true; + } + + bool Int(int i) + { + state = kHasNumber; + val.SetInt(i); + return true; + } + + bool Uint(unsigned u) + { + state = kHasNumber; + val.SetUint(u); + return true; + } + + bool Int64(int64_t i) + { + state = kHasNumber; + val.SetInt64(i); + return true; + } + + bool Uint64(int64_t u) + { + state = kHasNumber; + val.SetUint64(u); + return true; + } + + bool Double(double d) + { + state = kHasNumber; + val.SetDouble(d); + return true; + } + + bool RawNumber(const char *, SizeType, TVG_UNUSED bool) + { + return false; + } + + bool String(const char *str, SizeType length, TVG_UNUSED bool) + { + state = kHasString; + val.SetString(str, length); + return true; + } + + bool StartObject() + { + state = kEnteringObject; + return true; + } + + bool Key(const char *str, SizeType length, TVG_UNUSED bool) + { + state = kHasKey; + val.SetString(str, length); + return true; + } + + bool EndObject(SizeType) + { + state = kExitingObject; + return true; + } + + bool StartArray() + { + state = kEnteringArray; + return true; + } + + bool EndArray(SizeType) + { + state = kExitingArray; + return true; + } + + void Error() + { + TVGERR("LOTTIE", "Parsing Error!"); + state = kError; + } + + bool Invalid() + { + return state == kError; + } + + bool enterObject(); + bool enterArray(); + bool nextArrayValue(); + int getInt(); + float getFloat(); + const char* getString(); + char* getStringCopy(); + bool getBool(); + void getNull(); + bool parseNext(); + const char* nextObjectKey(); + void skip(const char* key); + void skipOut(int depth); + int peekType(); +}; + +#endif //_TVG_LOTTIE_PARSER_HANDLER_H_ \ No newline at end of file diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieProperty.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieProperty.h new file mode 100644 index 000000000..66acac8ff --- /dev/null +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieProperty.h @@ -0,0 +1,837 @@ +/* + * Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_LOTTIE_PROPERTY_H_ +#define _TVG_LOTTIE_PROPERTY_H_ + +#include +#include "tvgMath.h" +#include "tvgLottieCommon.h" +#include "tvgLottieInterpolator.h" +#include "tvgLottieExpressions.h" +#include "tvgLottieModifier.h" + + +struct LottieFont; +struct LottieLayer; +struct LottieObject; + + +template +struct LottieScalarFrame +{ + T value; //keyframe value + float no; //frame number + LottieInterpolator* interpolator; + bool hold = false; //do not interpolate. + + T interpolate(LottieScalarFrame* next, float frameNo) + { + auto t = (frameNo - no) / (next->no - no); + if (interpolator) t = interpolator->progress(t); + + if (hold) { + if (t < 1.0f) return value; + else return next->value; + } + return lerp(value, next->value, t); + } +}; + + +template +struct LottieVectorFrame +{ + T value; //keyframe value + float no; //frame number + LottieInterpolator* interpolator; + T outTangent, inTangent; + float length; + bool hasTangent = false; + bool hold = false; + + T interpolate(LottieVectorFrame* next, float frameNo) + { + auto t = (frameNo - no) / (next->no - no); + if (interpolator) t = interpolator->progress(t); + + if (hold) { + if (t < 1.0f) return value; + else return next->value; + } + + if (hasTangent) { + Bezier bz = {value, value + outTangent, next->value + inTangent, next->value}; + return bz.at(bz.atApprox(t * length, length)); + } else { + return lerp(value, next->value, t); + } + } + + float angle(LottieVectorFrame* next, float frameNo) + { + if (!hasTangent) { + Point dp = next->value - value; + return rad2deg(tvg::atan2(dp.y, dp.x)); + } + + auto t = (frameNo - no) / (next->no - no); + if (interpolator) t = interpolator->progress(t); + Bezier bz = {value, value + outTangent, next->value + inTangent, next->value}; + t = bz.atApprox(t * length, length); + return bz.angle(t >= 1.0f ? 0.99f : (t <= 0.0f ? 0.01f : t)); + } + + void prepare(LottieVectorFrame* next) + { + Bezier bz = {value, value + outTangent, next->value + inTangent, next->value}; + length = bz.lengthApprox(); + } +}; + + +//Property would have an either keyframes or single value. +struct LottieProperty +{ + enum class Type : uint8_t { Point = 0, Float, Opacity, Color, PathSet, ColorStop, Position, TextDoc, Invalid }; + + LottieExpression* exp = nullptr; + Type type; + uint8_t ix; //property index + + //TODO: Apply common bodies? + virtual ~LottieProperty() {} + virtual uint32_t frameCnt() = 0; + virtual uint32_t nearest(float time) = 0; + virtual float frameNo(int32_t key) = 0; +}; + + +struct LottieExpression +{ + enum LoopMode : uint8_t { None = 0, InCycle = 1, InPingPong, InOffset, InContinue, OutCycle, OutPingPong, OutOffset, OutContinue }; + + char* code; + LottieComposition* comp; + LottieLayer* layer; + LottieObject* object; + LottieProperty* property; + bool disabled = false; + + struct { + uint32_t key = 0; //the keyframe number repeating to + float in = FLT_MAX; //looping duration in frame number + LoopMode mode = None; + } loop; + + ~LottieExpression() + { + free(code); + } +}; + + +static void _copy(PathSet* pathset, Array& outPts, Matrix* transform) +{ + Array inPts; + + if (transform) { + for (int i = 0; i < pathset->ptsCnt; ++i) { + Point pt = pathset->pts[i]; + pt *= *transform; + outPts.push(pt); + } + } else { + inPts.data = pathset->pts; + inPts.count = pathset->ptsCnt; + outPts.push(inPts); + inPts.data = nullptr; + } +} + + +static void _copy(PathSet* pathset, Array& outCmds) +{ + Array inCmds; + inCmds.data = pathset->cmds; + inCmds.count = pathset->cmdsCnt; + outCmds.push(inCmds); + inCmds.data = nullptr; +} + + +template +uint32_t _bsearch(T* frames, float frameNo) +{ + int32_t low = 0; + int32_t high = int32_t(frames->count) - 1; + + while (low <= high) { + auto mid = low + (high - low) / 2; + auto frame = frames->data + mid; + if (frameNo < frame->no) high = mid - 1; + else low = mid + 1; + } + if (high < low) low = high; + if (low < 0) low = 0; + return low; +} + + +template +uint32_t _nearest(T* frames, float frameNo) +{ + if (frames) { + auto key = _bsearch(frames, frameNo); + if (key == frames->count - 1) return key; + return (fabsf(frames->data[key].no - frameNo) < fabsf(frames->data[key + 1].no - frameNo)) ? key : (key + 1); + } + return 0; +} + + +template +float _frameNo(T* frames, int32_t key) +{ + if (!frames) return 0.0f; + if (key < 0) key = 0; + if (key >= (int32_t) frames->count) key = (int32_t)(frames->count - 1); + return (*frames)[key].no; +} + + +template +float _loop(T* frames, float frameNo, LottieExpression* exp) +{ + if (frameNo >= exp->loop.in || frameNo < frames->first().no || frameNo < frames->last().no) return frameNo; + + frameNo -= frames->first().no; + + switch (exp->loop.mode) { + case LottieExpression::LoopMode::InCycle: { + return fmodf(frameNo, frames->last().no - frames->first().no) + (*frames)[exp->loop.key].no; + } + case LottieExpression::LoopMode::InPingPong: { + auto range = frames->last().no - (*frames)[exp->loop.key].no; + auto forward = (static_cast(frameNo / range) % 2) == 0 ? true : false; + frameNo = fmodf(frameNo, range); + return (forward ? frameNo : (range - frameNo)) + (*frames)[exp->loop.key].no; + } + case LottieExpression::LoopMode::OutCycle: { + return fmodf(frameNo, (*frames)[frames->count - 1 - exp->loop.key].no - frames->first().no) + frames->first().no; + } + case LottieExpression::LoopMode::OutPingPong: { + auto range = (*frames)[frames->count - 1 - exp->loop.key].no - frames->first().no; + auto forward = (static_cast(frameNo / range) % 2) == 0 ? true : false; + frameNo = fmodf(frameNo, range); + return (forward ? frameNo : (range - frameNo)) + frames->first().no; + } + default: break; + } + return frameNo; +} + + +template +struct LottieGenericProperty : LottieProperty +{ + //Property has an either keyframes or single value. + Array>* frames = nullptr; + T value; + + LottieGenericProperty(T v) : value(v) {} + LottieGenericProperty() {} + + ~LottieGenericProperty() + { + release(); + } + + void release() + { + delete(frames); + frames = nullptr; + if (exp) { + delete(exp); + exp = nullptr; + } + } + + uint32_t nearest(float frameNo) override + { + return _nearest(frames, frameNo); + } + + uint32_t frameCnt() override + { + return frames ? frames->count : 1; + } + + float frameNo(int32_t key) override + { + return _frameNo(frames, key); + } + + LottieScalarFrame& newFrame() + { + if (!frames) frames = new Array>; + if (frames->count + 1 >= frames->reserved) { + auto old = frames->reserved; + frames->grow(frames->count + 2); + memset((void*)(frames->data + old), 0x00, sizeof(LottieScalarFrame) * (frames->reserved - old)); + } + ++frames->count; + return frames->last(); + } + + LottieScalarFrame& nextFrame() + { + return (*frames)[frames->count]; + } + + T operator()(float frameNo) + { + if (!frames) return value; + if (frames->count == 1 || frameNo <= frames->first().no) return frames->first().value; + if (frameNo >= frames->last().no) return frames->last().value; + + auto frame = frames->data + _bsearch(frames, frameNo); + if (tvg::equal(frame->no, frameNo)) return frame->value; + return frame->interpolate(frame + 1, frameNo); + } + + T operator()(float frameNo, LottieExpressions* exps) + { + if (exps && exp) { + T out{}; + if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp); + if (exps->result>(frameNo, out, exp)) return out; + } + return operator()(frameNo); + } + + LottieGenericProperty& operator=(const LottieGenericProperty& other) + { + //shallow copy, used for slot overriding + if (other.frames) { + frames = other.frames; + const_cast&>(other).frames = nullptr; + } else value = other.value; + return *this; + } + + float angle(float frameNo) { return 0; } + void prepare() {} +}; + + +struct LottiePathSet : LottieProperty +{ + Array>* frames = nullptr; + PathSet value; + + ~LottiePathSet() + { + release(); + } + + void release() + { + if (exp) { + delete(exp); + exp = nullptr; + } + + free(value.cmds); + free(value.pts); + + if (!frames) return; + + for (auto p = frames->begin(); p < frames->end(); ++p) { + free((*p).value.cmds); + free((*p).value.pts); + } + free(frames->data); + free(frames); + } + + uint32_t nearest(float frameNo) override + { + return _nearest(frames, frameNo); + } + + uint32_t frameCnt() override + { + return frames ? frames->count : 1; + } + + float frameNo(int32_t key) override + { + return _frameNo(frames, key); + } + + LottieScalarFrame& newFrame() + { + if (!frames) { + frames = static_cast>*>(calloc(1, sizeof(Array>))); + } + if (frames->count + 1 >= frames->reserved) { + auto old = frames->reserved; + frames->grow(frames->count + 2); + memset((void*)(frames->data + old), 0x00, sizeof(LottieScalarFrame) * (frames->reserved - old)); + } + ++frames->count; + return frames->last(); + } + + LottieScalarFrame& nextFrame() + { + return (*frames)[frames->count]; + } + + bool operator()(float frameNo, Array& cmds, Array& pts, Matrix* transform, const LottieRoundnessModifier* roundness, const LottieOffsetModifier* offsetPath) + { + PathSet* path = nullptr; + LottieScalarFrame* frame = nullptr; + float t; + bool interpolate = false; + + if (!frames) path = &value; + else if (frames->count == 1 || frameNo <= frames->first().no) path = &frames->first().value; + else if (frameNo >= frames->last().no) path = &frames->last().value; + else { + frame = frames->data + _bsearch(frames, frameNo); + if (tvg::equal(frame->no, frameNo)) path = &frame->value; + else if (frame->value.ptsCnt != (frame + 1)->value.ptsCnt) { + path = &frame->value; + TVGLOG("LOTTIE", "Different numbers of points in consecutive frames - interpolation omitted."); + } else { + t = (frameNo - frame->no) / ((frame + 1)->no - frame->no); + if (frame->interpolator) t = frame->interpolator->progress(t); + if (frame->hold) path = &(frame + ((t < 1.0f) ? 0 : 1))->value; + else interpolate = true; + } + } + + if (!interpolate) { + if (roundness) { + if (offsetPath) { + Array cmds1(path->cmdsCnt); + Array pts1(path->ptsCnt); + roundness->modifyPath(path->cmds, path->cmdsCnt, path->pts, path->ptsCnt, cmds1, pts1, transform); + return offsetPath->modifyPath(cmds1.data, cmds1.count, pts1.data, pts1.count, cmds, pts); + } + return roundness->modifyPath(path->cmds, path->cmdsCnt, path->pts, path->ptsCnt, cmds, pts, transform); + } + if (offsetPath) return offsetPath->modifyPath(path->cmds, path->cmdsCnt, path->pts, path->ptsCnt, cmds, pts); + + _copy(path, cmds); + _copy(path, pts, transform); + return true; + } + + auto s = frame->value.pts; + auto e = (frame + 1)->value.pts; + + if (!roundness && !offsetPath) { + for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e) { + auto pt = lerp(*s, *e, t); + if (transform) pt *= *transform; + pts.push(pt); + } + _copy(&frame->value, cmds); + return true; + } + + auto interpPts = (Point*)malloc(frame->value.ptsCnt * sizeof(Point)); + auto p = interpPts; + for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e, ++p) { + *p = lerp(*s, *e, t); + if (transform) *p *= *transform; + } + + if (roundness) { + if (offsetPath) { + Array cmds1; + Array pts1; + roundness->modifyPath(frame->value.cmds, frame->value.cmdsCnt, interpPts, frame->value.ptsCnt, cmds1, pts1, nullptr); + offsetPath->modifyPath(cmds1.data, cmds1.count, pts1.data, pts1.count, cmds, pts); + } else roundness->modifyPath(frame->value.cmds, frame->value.cmdsCnt, interpPts, frame->value.ptsCnt, cmds, pts, nullptr); + } else if (offsetPath) offsetPath->modifyPath(frame->value.cmds, frame->value.cmdsCnt, interpPts, frame->value.ptsCnt, cmds, pts); + + free(interpPts); + + return true; + } + + + bool operator()(float frameNo, Array& cmds, Array& pts, Matrix* transform, const LottieRoundnessModifier* roundness, const LottieOffsetModifier* offsetPath, LottieExpressions* exps) + { + if (exps && exp) { + if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp); + if (exps->result(frameNo, cmds, pts, transform, roundness, offsetPath, exp)) return true; + } + return operator()(frameNo, cmds, pts, transform, roundness, offsetPath); + } + + void prepare() {} +}; + + +struct LottieColorStop : LottieProperty +{ + Array>* frames = nullptr; + ColorStop value; + uint16_t count = 0; //colorstop count + bool populated = false; + + ~LottieColorStop() + { + release(); + } + + void release() + { + if (exp) { + delete(exp); + exp = nullptr; + } + + if (value.data) { + free(value.data); + value.data = nullptr; + } + + if (!frames) return; + + for (auto p = frames->begin(); p < frames->end(); ++p) { + free((*p).value.data); + } + free(frames->data); + free(frames); + frames = nullptr; + } + + uint32_t nearest(float frameNo) override + { + return _nearest(frames, frameNo); + } + + uint32_t frameCnt() override + { + return frames ? frames->count : 1; + } + + float frameNo(int32_t key) override + { + return _frameNo(frames, key); + } + + LottieScalarFrame& newFrame() + { + if (!frames) { + frames = static_cast>*>(calloc(1, sizeof(Array>))); + } + if (frames->count + 1 >= frames->reserved) { + auto old = frames->reserved; + frames->grow(frames->count + 2); + memset((void*)(frames->data + old), 0x00, sizeof(LottieScalarFrame) * (frames->reserved - old)); + } + ++frames->count; + return frames->last(); + } + + LottieScalarFrame& nextFrame() + { + return (*frames)[frames->count]; + } + + Result operator()(float frameNo, Fill* fill, LottieExpressions* exps) + { + if (exps && exp) { + if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp); + if (exps->result(frameNo, fill, exp)) return Result::Success; + } + + if (!frames) return fill->colorStops(value.data, count); + + if (frames->count == 1 || frameNo <= frames->first().no) { + return fill->colorStops(frames->first().value.data, count); + } + + if (frameNo >= frames->last().no) return fill->colorStops(frames->last().value.data, count); + + auto frame = frames->data + _bsearch(frames, frameNo); + if (tvg::equal(frame->no, frameNo)) return fill->colorStops(frame->value.data, count); + + //interpolate + auto t = (frameNo - frame->no) / ((frame + 1)->no - frame->no); + if (frame->interpolator) t = frame->interpolator->progress(t); + + if (frame->hold) { + if (t < 1.0f) fill->colorStops(frame->value.data, count); + else fill->colorStops((frame + 1)->value.data, count); + } + + auto s = frame->value.data; + auto e = (frame + 1)->value.data; + + Array result; + + for (auto i = 0; i < count; ++i, ++s, ++e) { + auto offset = lerp(s->offset, e->offset, t); + auto r = lerp(s->r, e->r, t); + auto g = lerp(s->g, e->g, t); + auto b = lerp(s->b, e->b, t); + auto a = lerp(s->a, e->a, t); + result.push({offset, r, g, b, a}); + } + return fill->colorStops(result.data, count); + } + + LottieColorStop& operator=(const LottieColorStop& other) + { + //shallow copy, used for slot overriding + if (other.frames) { + frames = other.frames; + const_cast(other).frames = nullptr; + } else { + value = other.value; + const_cast(other).value = {nullptr, nullptr}; + } + populated = other.populated; + count = other.count; + + return *this; + } + + void prepare() {} +}; + + +struct LottiePosition : LottieProperty +{ + Array>* frames = nullptr; + Point value; + + LottiePosition(Point v) : value(v) + { + } + + ~LottiePosition() + { + release(); + } + + void release() + { + delete(frames); + frames = nullptr; + + if (exp) { + delete(exp); + exp = nullptr; + } + } + + uint32_t nearest(float frameNo) override + { + return _nearest(frames, frameNo); + } + + uint32_t frameCnt() override + { + return frames ? frames->count : 1; + } + + float frameNo(int32_t key) override + { + return _frameNo(frames, key); + } + + LottieVectorFrame& newFrame() + { + if (!frames) frames = new Array>; + if (frames->count + 1 >= frames->reserved) { + auto old = frames->reserved; + frames->grow(frames->count + 2); + memset((void*)(frames->data + old), 0x00, sizeof(LottieVectorFrame) * (frames->reserved - old)); + } + ++frames->count; + return frames->last(); + } + + LottieVectorFrame& nextFrame() + { + return (*frames)[frames->count]; + } + + Point operator()(float frameNo) + { + if (!frames) return value; + if (frames->count == 1 || frameNo <= frames->first().no) return frames->first().value; + if (frameNo >= frames->last().no) return frames->last().value; + + auto frame = frames->data + _bsearch(frames, frameNo); + if (tvg::equal(frame->no, frameNo)) return frame->value; + return frame->interpolate(frame + 1, frameNo); + } + + Point operator()(float frameNo, LottieExpressions* exps) + { + Point out{}; + if (exps && exp) { + if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp); + if (exps->result(frameNo, out, exp)) return out; + } + return operator()(frameNo); + } + + float angle(float frameNo) + { + if (!frames || frames->count == 1) return 0; + + if (frameNo <= frames->first().no) return frames->first().angle(frames->data + 1, frames->first().no); + if (frameNo >= frames->last().no) { + auto frame = frames->data + frames->count - 2; + return frame->angle(frame + 1, frames->last().no); + } + + auto frame = frames->data + _bsearch(frames, frameNo); + return frame->angle(frame + 1, frameNo); + } + + void prepare() + { + if (!frames || frames->count < 2) return; + for (auto frame = frames->begin() + 1; frame < frames->end(); ++frame) { + (frame - 1)->prepare(frame); + } + } +}; + + +struct LottieTextDoc : LottieProperty +{ + Array>* frames = nullptr; + TextDocument value; + + ~LottieTextDoc() + { + release(); + } + + void release() + { + if (exp) { + delete(exp); + exp = nullptr; + } + + if (value.text) { + free(value.text); + value.text = nullptr; + } + if (value.name) { + free(value.name); + value.name = nullptr; + } + + if (!frames) return; + + for (auto p = frames->begin(); p < frames->end(); ++p) { + free((*p).value.text); + free((*p).value.name); + } + delete(frames); + frames = nullptr; + } + + uint32_t nearest(float frameNo) override + { + return _nearest(frames, frameNo); + } + + uint32_t frameCnt() override + { + return frames ? frames->count : 1; + } + + float frameNo(int32_t key) override + { + return _frameNo(frames, key); + } + + LottieScalarFrame& newFrame() + { + if (!frames) frames = new Array>; + if (frames->count + 1 >= frames->reserved) { + auto old = frames->reserved; + frames->grow(frames->count + 2); + memset((void*)(frames->data + old), 0x00, sizeof(LottieScalarFrame) * (frames->reserved - old)); + } + ++frames->count; + return frames->last(); + } + + LottieScalarFrame& nextFrame() + { + return (*frames)[frames->count]; + } + + TextDocument& operator()(float frameNo) + { + if (!frames) return value; + if (frames->count == 1 || frameNo <= frames->first().no) return frames->first().value; + if (frameNo >= frames->last().no) return frames->last().value; + + auto frame = frames->data + _bsearch(frames, frameNo); + return frame->value; + } + + LottieTextDoc& operator=(const LottieTextDoc& other) + { + //shallow copy, used for slot overriding + if (other.frames) { + frames = other.frames; + const_cast(other).frames = nullptr; + } else { + value = other.value; + const_cast(other).value.text = nullptr; + const_cast(other).value.name = nullptr; + } + return *this; + } + + void prepare() {} +}; + + +using LottiePoint = LottieGenericProperty; +using LottieFloat = LottieGenericProperty; +using LottieOpacity = LottieGenericProperty; +using LottieColor = LottieGenericProperty; +using LottieSlider = LottieFloat; +using LottieAngle = LottieFloat; +using LottieCheckbox = LottieGenericProperty; + +#endif //_TVG_LOTTIE_PROPERTY_H_ diff --git a/libfenrir/src/main/jni/animation/rlottie/src/vector/vraster.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieRenderPooler.h similarity index 55% rename from libfenrir/src/main/jni/animation/rlottie/src/vector/vraster.h rename to libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieRenderPooler.h index fec84135b..90216a2ba 100644 --- a/libfenrir/src/main/jni/animation/rlottie/src/vector/vraster.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieRenderPooler.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + * Copyright (c) 2024 the ThorVG project. All rights reserved. * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,31 +20,39 @@ * SOFTWARE. */ -#ifndef VRASTER_H -#define VRASTER_H -#include -#include "vglobal.h" -#include "vrect.h" +#ifndef _TVG_LOTTIE_RENDER_POOLER_H_ +#define _TVG_LOTTIE_RENDER_POOLER_H_ -V_BEGIN_NAMESPACE +#include "tvgCommon.h" +#include "tvgArray.h" +#include "tvgPaint.h" -class VPath; -class VRle; -class VRasterizer +template +struct LottieRenderPooler { -public: - void rasterize(VPath path, FillRule fillRule = FillRule::Winding, const VRect &clip = VRect()); - void rasterize(VPath path, CapStyle cap, JoinStyle join, float width, - float miterLimit, const VRect &clip = VRect()); - VRle rle(); -private: - struct VRasterizerImpl; - void init(); - void updateRequest(); - std::shared_ptr d{nullptr}; + Array pooler; + + ~LottieRenderPooler() + { + for (auto p = pooler.begin(); p < pooler.end(); ++p) { + if (PP(*p)->unref() == 0) delete(*p); + } + } + + T* pooling(bool copy = false) + { + //return available one. + for (auto p = pooler.begin(); p < pooler.end(); ++p) { + if (PP(*p)->refCnt == 1) return *p; + } + + //no empty, generate a new one. + auto p = copy ? static_cast(pooler[0]->duplicate()) : T::gen().release(); + PP(p)->ref(); + pooler.push(p); + return p; + } }; -V_END_NAMESPACE - -#endif // VRASTER_H +#endif //_TVG_LOTTIE_RENDER_POOLER_H_ \ No newline at end of file diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/raw/tvgRawLoader.cpp b/libfenrir/src/main/jni/animation/thorvg/src/loaders/raw/tvgRawLoader.cpp index b797f9821..68048e744 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/loaders/raw/tvgRawLoader.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/raw/tvgRawLoader.cpp @@ -45,7 +45,7 @@ RawLoader::~RawLoader() } -bool RawLoader::open(const uint32_t* data, uint32_t w, uint32_t h, bool copy) +bool RawLoader::open(const uint32_t* data, uint32_t w, uint32_t h, ColorSpace cs, bool copy) { if (!LoadModule::read()) return true; @@ -66,9 +66,9 @@ bool RawLoader::open(const uint32_t* data, uint32_t w, uint32_t h, bool copy) surface.stride = w; surface.w = w; surface.h = h; - surface.cs = ColorSpace::ARGB8888; + surface.cs = cs; surface.channelSize = sizeof(uint32_t); - surface.premultiplied = true; + surface.premultiplied = (cs == ColorSpace::ABGR8888 || cs == ColorSpace::ARGB8888) ? true : false; return true; } diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/raw/tvgRawLoader.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/raw/tvgRawLoader.h index 970cdd9c8..53afcaa78 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/loaders/raw/tvgRawLoader.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/raw/tvgRawLoader.h @@ -32,7 +32,7 @@ class RawLoader : public ImageLoader ~RawLoader(); using LoadModule::open; - bool open(const uint32_t* data, uint32_t w, uint32_t h, bool copy); + bool open(const uint32_t* data, uint32_t w, uint32_t h, ColorSpace cs, bool copy); bool read() override; }; diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/svg/tvgSvgLoader.cpp b/libfenrir/src/main/jni/animation/thorvg/src/loaders/svg/tvgSvgLoader.cpp index d3c7091ce..52e3185e8 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/loaders/svg/tvgSvgLoader.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/svg/tvgSvgLoader.cpp @@ -20,34 +20,6 @@ * SOFTWARE. */ -/* - * Copyright notice for the EFL: - - * Copyright (C) EFL developers (see AUTHORS) - - * All rights reserved. - - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - - * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, - * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - #include #include #include @@ -162,9 +134,7 @@ static void _parseAspectRatio(const char** content, AspectRatioAlign* align, Asp } -/** - * According to https://www.w3.org/TR/SVG/coords.html#Units - */ +// According to https://www.w3.org/TR/SVG/coords.html#Units static float _toFloat(const SvgParser* svgParse, const char* str, SvgParserLengthType type) { float parsedValue = strToFloat(str, nullptr); @@ -660,7 +630,7 @@ static bool _hslToRgb(float hue, float saturation, float brightness, uint8_t* re return true; } -extern void getCustomColor(const string& name, uint8_t* r, uint8_t* g, uint8_t* b); +extern void getCustomColorSVG(const string& name, uint8_t* r, uint8_t* g, uint8_t* b); static bool _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char** ref) { @@ -668,7 +638,7 @@ static bool _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char** char *red, *green, *blue; unsigned char tr, tg, tb; if (len > 2 && str[0] == '[' && str[1] == '|') { - getCustomColor(str, r, g, b); + getCustomColorSVG(str, r, g, b); return true; } else if (len == 4 && str[0] == '#') { @@ -715,7 +685,7 @@ static bool _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char** } return true; } else if (ref && len >= 3 && !strncmp(str, "url", 3)) { - if (*ref) free(*ref); + free(*ref); *ref = _idFromUrl((const char*)(str + 3)); return true; } else if (len >= 10 && (str[0] == 'h' || str[0] == 'H') && (str[1] == 's' || str[1] == 'S') && (str[2] == 'l' || str[2] == 'L') && str[3] == '(' && str[len - 1] == ')') { @@ -816,8 +786,7 @@ static Matrix* _parseTransformationMatrix(const char* value) const int POINT_CNT = 8; auto matrix = (Matrix*)malloc(sizeof(Matrix)); - if (!matrix) return nullptr; - *matrix = {1, 0, 0, 0, 1, 0, 0, 0, 1}; + tvg::identity(matrix); float points[POINT_CNT]; int ptCount = 0; @@ -899,7 +868,7 @@ static Matrix* _parseTransformationMatrix(const char* value) } return matrix; error: - if (matrix) free(matrix); + free(matrix); return nullptr; } @@ -916,37 +885,6 @@ static void _postpone(Array& nodes, SvgNode *node, char* id) } -/* -// TODO - remove? -static constexpr struct -{ - const char* tag; - int sz; - SvgLengthType type; -} lengthTags[] = { - LENGTH_DEF(%, SvgLengthType::Percent), - LENGTH_DEF(px, SvgLengthType::Px), - LENGTH_DEF(pc, SvgLengthType::Pc), - LENGTH_DEF(pt, SvgLengthType::Pt), - LENGTH_DEF(mm, SvgLengthType::Mm), - LENGTH_DEF(cm, SvgLengthType::Cm), - LENGTH_DEF(in, SvgLengthType::In) -}; - -static float _parseLength(const char* str, SvgLengthType* type) -{ - float value; - int sz = strlen(str); - - *type = SvgLengthType::Px; - for (unsigned int i = 0; i < sizeof(lengthTags) / sizeof(lengthTags[0]); i++) { - if (lengthTags[i].sz - 1 == sz && !strncmp(lengthTags[i].tag, str, sz)) *type = lengthTags[i].type; - } - value = svgUtilStrtof(str, nullptr); - return value; -} -*/ - static bool _parseStyleAttr(void* data, const char* key, const char* value); static bool _parseStyleAttr(void* data, const char* key, const char* value, bool style); @@ -1137,7 +1075,7 @@ static void _handleClipPathAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, SvgStyleProperty* style = node->style; int len = strlen(value); if (len >= 3 && !strncmp(value, "url", 3)) { - if (style->clipPath.url) free(style->clipPath.url); + free(style->clipPath.url); style->clipPath.url = _idFromUrl((const char*)(value + 3)); } } @@ -1148,7 +1086,7 @@ static void _handleMaskAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, con SvgStyleProperty* style = node->style; int len = strlen(value); if (len >= 3 && !strncmp(value, "url", 3)) { - if (style->mask.url) free(style->mask.url); + free(style->mask.url); style->mask.url = _idFromUrl((const char*)(value + 3)); } } @@ -1183,7 +1121,7 @@ static void _handleCssClassAttr(SvgLoaderData* loader, SvgNode* node, const char { auto cssClass = &node->style->cssClass; - if (*cssClass && value) free(*cssClass); + if (value) free(*cssClass); *cssClass = _copyId(value); bool cssClassFound = false; @@ -1298,7 +1236,7 @@ static bool _attrParseGNode(void* data, const char* key, const char* value) } else if (!strcmp(key, "transform")) { node->transform = _parseTransformationMatrix(value); } else if (!strcmp(key, "id")) { - if (node->id && value) free(node->id); + if (value) free(node->id); node->id = _copyId(value); } else if (!strcmp(key, "class")) { _handleCssClassAttr(loader, node, value); @@ -1327,7 +1265,7 @@ static bool _attrParseClipPathNode(void* data, const char* key, const char* valu } else if (!strcmp(key, "transform")) { node->transform = _parseTransformationMatrix(value); } else if (!strcmp(key, "id")) { - if (node->id && value) free(node->id); + if (value) free(node->id); node->id = _copyId(value); } else if (!strcmp(key, "class")) { _handleCssClassAttr(loader, node, value); @@ -1351,7 +1289,7 @@ static bool _attrParseMaskNode(void* data, const char* key, const char* value) } else if (!strcmp(key, "transform")) { node->transform = _parseTransformationMatrix(value); } else if (!strcmp(key, "id")) { - if (node->id && value) free(node->id); + if (value) free(node->id); node->id = _copyId(value); } else if (!strcmp(key, "class")) { _handleCssClassAttr(loader, node, value); @@ -1372,7 +1310,7 @@ static bool _attrParseCssStyleNode(void* data, const char* key, const char* valu SvgNode* node = loader->svgParse->node; if (!strcmp(key, "id")) { - if (node->id && value) free(node->id); + if (value) free(node->id); node->id = _copyId(value); } else { return _parseStyleAttr(loader, key, value, false); @@ -1577,7 +1515,7 @@ static bool _attrParsePathNode(void* data, const char* key, const char* value) SvgPathNode* path = &(node->node.path); if (!strcmp(key, "d")) { - if (path->path) free(path->path); + free(path->path); //Temporary: need to copy path->path = _copyId(value); } else if (!strcmp(key, "style")) { @@ -1587,7 +1525,7 @@ static bool _attrParsePathNode(void* data, const char* key, const char* value) } else if (!strcmp(key, "mask")) { _handleMaskAttr(loader, node, value); } else if (!strcmp(key, "id")) { - if (node->id && value) free(node->id); + if (value) free(node->id); node->id = _copyId(value); } else if (!strcmp(key, "class")) { _handleCssClassAttr(loader, node, value); @@ -1649,7 +1587,7 @@ static bool _attrParseCircleNode(void* data, const char* key, const char* value) } else if (!strcmp(key, "mask")) { _handleMaskAttr(loader, node, value); } else if (!strcmp(key, "id")) { - if (node->id && value) free(node->id); + if (value) free(node->id); node->id = _copyId(value); } else if (!strcmp(key, "class")) { _handleCssClassAttr(loader, node, value); @@ -1705,7 +1643,7 @@ static bool _attrParseEllipseNode(void* data, const char* key, const char* value } if (!strcmp(key, "id")) { - if (node->id && value) free(node->id); + if (value) free(node->id); node->id = _copyId(value); } else if (!strcmp(key, "class")) { _handleCssClassAttr(loader, node, value); @@ -1765,7 +1703,7 @@ static bool _attrParsePolygonNode(void* data, const char* key, const char* value } else if (!strcmp(key, "mask")) { _handleMaskAttr(loader, node, value); } else if (!strcmp(key, "id")) { - if (node->id && value) free(node->id); + if (value) free(node->id); node->id = _copyId(value); } else if (!strcmp(key, "class")) { _handleCssClassAttr(loader, node, value); @@ -1841,7 +1779,7 @@ static bool _attrParseRectNode(void* data, const char* key, const char* value) } if (!strcmp(key, "id")) { - if (node->id && value) free(node->id); + if (value) free(node->id); node->id = _copyId(value); } else if (!strcmp(key, "class")) { _handleCssClassAttr(loader, node, value); @@ -1906,7 +1844,7 @@ static bool _attrParseLineNode(void* data, const char* key, const char* value) } if (!strcmp(key, "id")) { - if (node->id && value) free(node->id); + if (value) free(node->id); node->id = _copyId(value); } else if (!strcmp(key, "class")) { _handleCssClassAttr(loader, node, value); @@ -1976,10 +1914,10 @@ static bool _attrParseImageNode(void* data, const char* key, const char* value) } if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) { - if (image->href && value) free(image->href); + if (value) free(image->href); image->href = _idFromHref(value); } else if (!strcmp(key, "id")) { - if (node->id && value) free(node->id); + if (value) free(node->id); node->id = _copyId(value); } else if (!strcmp(key, "class")) { _handleCssClassAttr(loader, node, value); @@ -2160,8 +2098,10 @@ static bool _attrParseTextNode(void* data, const char* key, const char* value) } if (!strcmp(key, "font-family")) { - if (text->fontFamily && value) free(text->fontFamily); - text->fontFamily = strdup(value); + if (value) { + free(text->fontFamily); + text->fontFamily = strdup(value); + } } else if (!strcmp(key, "style")) { return simpleXmlParseW3CAttribute(value, strlen(value), _parseStyleAttr, loader); } else if (!strcmp(key, "clip-path")) { @@ -2169,7 +2109,7 @@ static bool _attrParseTextNode(void* data, const char* key, const char* value) } else if (!strcmp(key, "mask")) { _handleMaskAttr(loader, node, value); } else if (!strcmp(key, "id")) { - if (node->id && value) free(node->id); + if (value) free(node->id); node->id = _copyId(value); } else if (!strcmp(key, "class")) { _handleCssClassAttr(loader, node, value); @@ -2496,13 +2436,13 @@ static bool _attrParseRadialGradientNode(void* data, const char* key, const char } if (!strcmp(key, "id")) { - if (grad->id && value) free(grad->id); + if (value) free(grad->id); grad->id = _copyId(value); } else if (!strcmp(key, "spreadMethod")) { grad->spread = _parseSpreadValue(value); grad->flags = (grad->flags | SvgGradientFlags::SpreadMethod); } else if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) { - if (grad->ref && value) free(grad->ref); + if (value) free(grad->ref); grad->ref = _idFromHref(value); } else if (!strcmp(key, "gradientUnits")) { if (!strcmp(value, "userSpaceOnUse")) grad->userSpace = true; @@ -2764,13 +2704,13 @@ static bool _attrParseLinearGradientNode(void* data, const char* key, const char } if (!strcmp(key, "id")) { - if (grad->id && value) free(grad->id); + if (value) free(grad->id); grad->id = _copyId(value); } else if (!strcmp(key, "spreadMethod")) { grad->spread = _parseSpreadValue(value); grad->flags = (grad->flags | SvgGradientFlags::SpreadMethod); } else if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) { - if (grad->ref && value) free(grad->ref); + if (value) free(grad->ref); grad->ref = _idFromHref(value); } else if (!strcmp(key, "gradientUnits")) { if (!strcmp(value, "userSpaceOnUse")) grad->userSpace = true; @@ -2982,7 +2922,7 @@ static void _styleInherit(SvgStyleProperty* child, const SvgStyleProperty* paren child->fill.paint.none = parent->fill.paint.none; child->fill.paint.curColor = parent->fill.paint.curColor; if (parent->fill.paint.url) { - if (child->fill.paint.url) free(child->fill.paint.url); + free(child->fill.paint.url); child->fill.paint.url = _copyId(parent->fill.paint.url); } } @@ -2998,7 +2938,7 @@ static void _styleInherit(SvgStyleProperty* child, const SvgStyleProperty* paren child->stroke.paint.none = parent->stroke.paint.none; child->stroke.paint.curColor = parent->stroke.paint.curColor; if (parent->stroke.paint.url) { - if (child->stroke.paint.url) free(child->stroke.paint.url); + free(child->stroke.paint.url); child->stroke.paint.url = _copyId(parent->stroke.paint.url); } } @@ -3056,7 +2996,7 @@ static void _styleCopy(SvgStyleProperty* to, const SvgStyleProperty* from) to->fill.paint.none = from->fill.paint.none; to->fill.paint.curColor = from->fill.paint.curColor; if (from->fill.paint.url) { - if (to->fill.paint.url) free(to->fill.paint.url); + free(to->fill.paint.url); to->fill.paint.url = _copyId(from->fill.paint.url); } } @@ -3073,7 +3013,7 @@ static void _styleCopy(SvgStyleProperty* to, const SvgStyleProperty* from) to->stroke.paint.none = from->stroke.paint.none; to->stroke.paint.curColor = from->stroke.paint.curColor; if (from->stroke.paint.url) { - if (to->stroke.paint.url) free(to->stroke.paint.url); + free(to->stroke.paint.url); to->stroke.paint.url = _copyId(from->stroke.paint.url); } } @@ -3118,11 +3058,11 @@ static void _copyAttr(SvgNode* to, const SvgNode* from) _styleCopy(to->style, from->style); to->style->flags = (to->style->flags | from->style->flags); if (from->style->clipPath.url) { - if (to->style->clipPath.url) free(to->style->clipPath.url); + free(to->style->clipPath.url); to->style->clipPath.url = strdup(from->style->clipPath.url); } if (from->style->mask.url) { - if (to->style->mask.url) free(to->style->mask.url); + free(to->style->mask.url); to->style->mask.url = strdup(from->style->mask.url); } @@ -3161,7 +3101,7 @@ static void _copyAttr(SvgNode* to, const SvgNode* from) } case SvgNodeType::Path: { if (from->node.path.path) { - if (to->node.path.path) free(to->node.path.path); + free(to->node.path.path); to->node.path.path = strdup(from->node.path.path); } break; @@ -3184,7 +3124,7 @@ static void _copyAttr(SvgNode* to, const SvgNode* from) to->node.image.w = from->node.image.w; to->node.image.h = from->node.image.h; if (from->node.image.href) { - if (to->node.image.href) free(to->node.image.href); + free(to->node.image.href); to->node.image.href = strdup(from->node.image.href); } break; @@ -3204,11 +3144,11 @@ static void _copyAttr(SvgNode* to, const SvgNode* from) to->node.text.y = from->node.text.y; to->node.text.fontSize = from->node.text.fontSize; if (from->node.text.text) { - if (to->node.text.text) free(to->node.text.text); + free(to->node.text.text); to->node.text.text = strdup(from->node.text.text); } if (from->node.text.fontFamily) { - if (to->node.text.fontFamily) free(to->node.text.fontFamily); + free(to->node.text.fontFamily); to->node.text.fontFamily = strdup(from->node.text.fontFamily); } break; @@ -3933,7 +3873,7 @@ bool SvgLoader::header() } -bool SvgLoader::open(const char* data, uint32_t size, bool copy) +bool SvgLoader::open(const char* data, uint32_t size, TVG_UNUSED const string& rpath, bool copy, const ColorReplace& colorReplacement) { clear(); @@ -3951,7 +3891,7 @@ bool SvgLoader::open(const char* data, uint32_t size, bool copy) } -bool SvgLoader::open(const string& path) +bool SvgLoader::open(const string& path, const ColorReplace& colorReplacement) { clear(); diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/svg/tvgSvgLoader.h b/libfenrir/src/main/jni/animation/thorvg/src/loaders/svg/tvgSvgLoader.h index b0e49b13a..adcdbd514 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/loaders/svg/tvgSvgLoader.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/svg/tvgSvgLoader.h @@ -42,8 +42,8 @@ class SvgLoader : public ImageLoader, public Task SvgLoader(); ~SvgLoader(); - bool open(const string& path) override; - bool open(const char* data, uint32_t size, bool copy) override; + bool open(const string& path, const ColorReplace& colorReplacement) override; + bool open(const char* data, uint32_t size, const string& rpath, bool copy, const ColorReplace& colorReplacement) override; bool resize(Paint* paint, float w, float h) override; bool read() override; bool close() override; diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/svg/tvgSvgPath.cpp b/libfenrir/src/main/jni/animation/thorvg/src/loaders/svg/tvgSvgPath.cpp index 57442139c..30a5c09b7 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/loaders/svg/tvgSvgPath.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/svg/tvgSvgPath.cpp @@ -20,34 +20,6 @@ * SOFTWARE. */ -/* - * Copyright notice for the EFL: - - * Copyright (C) EFL developers (see AUTHORS) - - * All rights reserved. - - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - - * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, - * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - #define _USE_MATH_DEFINES //Math Constants are not defined in Standard C/C++. #include diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp b/libfenrir/src/main/jni/animation/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp index 1ca44d600..9a193da9d 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp @@ -22,10 +22,8 @@ #include "tvgMath.h" /* to include math.h before cstring */ #include -#include #include "tvgShape.h" #include "tvgCompressor.h" -#include "tvgPaint.h" #include "tvgFill.h" #include "tvgStr.h" #include "tvgSvgLoaderCommon.h" @@ -37,9 +35,8 @@ /* Internal Class Implementation */ /************************************************************************/ -static bool _appendShape(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath); static bool _appendClipShape(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath, const Matrix* transform); -static unique_ptr _sceneBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath, bool mask, int depth, bool* isMaskWhite = nullptr); +static unique_ptr _sceneBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath, bool mask, int depth); static inline bool _isGroupType(SvgNodeType type) @@ -90,11 +87,9 @@ static void _transformMultiply(const Matrix* mBBox, Matrix* gradTransf) static unique_ptr _applyLinearGradientProperty(SvgStyleGradient* g, const Box& vBox, int opacity) { Fill::ColorStop* stops; - int stopCount = 0; auto fillGrad = LinearGradient::gen(); - - bool isTransform = (g->transform ? true : false); - Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1}; + auto isTransform = (g->transform ? true : false); + auto& finalTransform = fillGrad->transform(); if (isTransform) finalTransform = *g->transform; if (g->userSpace) { @@ -105,39 +100,32 @@ static unique_ptr _applyLinearGradientProperty(SvgStyleGradient* } else { Matrix m = {vBox.w, 0, vBox.x, 0, vBox.h, vBox.y, 0, 0, 1}; if (isTransform) _transformMultiply(&m, &finalTransform); - else { - finalTransform = m; - isTransform = true; - } + else finalTransform = m; } - if (isTransform) fillGrad->transform(finalTransform); - fillGrad->linear(g->linear->x1, g->linear->y1, g->linear->x2, g->linear->y2); fillGrad->spread(g->spread); //Update the stops - stopCount = g->stops.count; - if (stopCount > 0) { - stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop)); - if (!stops) return fillGrad; - auto prevOffset = 0.0f; - for (uint32_t i = 0; i < g->stops.count; ++i) { - auto colorStop = &g->stops[i]; - //Use premultiplied color - stops[i].r = colorStop->r; - stops[i].g = colorStop->g; - stops[i].b = colorStop->b; - stops[i].a = static_cast((colorStop->a * opacity) / 255); - stops[i].offset = colorStop->offset; - //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes - if (colorStop->offset < prevOffset) stops[i].offset = prevOffset; - else if (colorStop->offset > 1) stops[i].offset = 1; - prevOffset = stops[i].offset; - } - fillGrad->colorStops(stops, stopCount); - free(stops); + if (g->stops.count == 0) return fillGrad; + + stops = (Fill::ColorStop*)malloc(g->stops.count * sizeof(Fill::ColorStop)); + auto prevOffset = 0.0f; + for (uint32_t i = 0; i < g->stops.count; ++i) { + auto colorStop = &g->stops[i]; + //Use premultiplied color + stops[i].r = colorStop->r; + stops[i].g = colorStop->g; + stops[i].b = colorStop->b; + stops[i].a = static_cast((colorStop->a * opacity) / 255); + stops[i].offset = colorStop->offset; + //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes + if (colorStop->offset < prevOffset) stops[i].offset = prevOffset; + else if (colorStop->offset > 1) stops[i].offset = 1; + prevOffset = stops[i].offset; } + fillGrad->colorStops(stops, g->stops.count); + free(stops); return fillGrad; } @@ -145,11 +133,9 @@ static unique_ptr _applyLinearGradientProperty(SvgStyleGradient* static unique_ptr _applyRadialGradientProperty(SvgStyleGradient* g, const Box& vBox, int opacity) { Fill::ColorStop *stops; - int stopCount = 0; auto fillGrad = RadialGradient::gen(); - - bool isTransform = (g->transform ? true : false); - Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1}; + auto isTransform = (g->transform ? true : false); + auto& finalTransform = fillGrad->transform(); if (isTransform) finalTransform = *g->transform; if (g->userSpace) { @@ -164,39 +150,32 @@ static unique_ptr _applyRadialGradientProperty(SvgStyleGradient* } else { Matrix m = {vBox.w, 0, vBox.x, 0, vBox.h, vBox.y, 0, 0, 1}; if (isTransform) _transformMultiply(&m, &finalTransform); - else { - finalTransform = m; - isTransform = true; - } + else finalTransform = m; } - if (isTransform) fillGrad->transform(finalTransform); - P(fillGrad)->radial(g->radial->cx, g->radial->cy, g->radial->r, g->radial->fx, g->radial->fy, g->radial->fr); fillGrad->spread(g->spread); //Update the stops - stopCount = g->stops.count; - if (stopCount > 0) { - stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop)); - if (!stops) return fillGrad; - auto prevOffset = 0.0f; - for (uint32_t i = 0; i < g->stops.count; ++i) { - auto colorStop = &g->stops[i]; - //Use premultiplied color - stops[i].r = colorStop->r; - stops[i].g = colorStop->g; - stops[i].b = colorStop->b; - stops[i].a = static_cast((colorStop->a * opacity) / 255); - stops[i].offset = colorStop->offset; - //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes - if (colorStop->offset < prevOffset) stops[i].offset = prevOffset; - else if (colorStop->offset > 1) stops[i].offset = 1; - prevOffset = stops[i].offset; - } - fillGrad->colorStops(stops, stopCount); - free(stops); + if (g->stops.count == 0) return fillGrad; + + stops = (Fill::ColorStop*)malloc(g->stops.count * sizeof(Fill::ColorStop)); + auto prevOffset = 0.0f; + for (uint32_t i = 0; i < g->stops.count; ++i) { + auto colorStop = &g->stops[i]; + //Use premultiplied color + stops[i].r = colorStop->r; + stops[i].g = colorStop->g; + stops[i].b = colorStop->b; + stops[i].a = static_cast((colorStop->a * opacity) / 255); + stops[i].offset = colorStop->offset; + //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes + if (colorStop->offset < prevOffset) stops[i].offset = prevOffset; + else if (colorStop->offset > 1) stops[i].offset = 1; + prevOffset = stops[i].offset; } + fillGrad->colorStops(stops, g->stops.count); + free(stops); return fillGrad; } @@ -210,8 +189,7 @@ static bool _appendClipUseNode(SvgLoaderData& loaderData, SvgNode* node, Shape* Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1}; if (node->transform) finalTransform = *node->transform; if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) { - Matrix m = {1, 0, node->node.use.x, 0, 1, node->node.use.y, 0, 0, 1}; - finalTransform *= m; + finalTransform *= {1, 0, node->node.use.x, 0, 1, node->node.use.y, 0, 0, 1}; } if (child->transform) finalTransform = *child->transform * finalTransform; @@ -221,9 +199,7 @@ static bool _appendClipUseNode(SvgLoaderData& loaderData, SvgNode* node, Shape* static bool _appendClipChild(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath, bool clip) { - if (node->type == SvgNodeType::Use) { - return _appendClipUseNode(loaderData, node, shape, vBox, svgPath); - } + if (node->type == SvgNodeType::Use) return _appendClipUseNode(loaderData, node, shape, vBox, svgPath); return _appendClipShape(loaderData, node, shape, vBox, svgPath, nullptr); } @@ -241,91 +217,86 @@ static Matrix _compositionTransform(Paint* paint, const SvgNode* node, const Svg if (!compNode->node.clip.userSpace) { float x, y, w, h; P(paint)->bounds(&x, &y, &w, &h, false, false); - Matrix mBBox = {w, 0, x, 0, h, y, 0, 0, 1}; - m *= mBBox; + m *= {w, 0, x, 0, h, y, 0, 0, 1}; } return m; } -static void _applyComposition(SvgLoaderData& loaderData, Paint* paint, const SvgNode* node, const Box& vBox, const string& svgPath) +static Paint* _applyComposition(SvgLoaderData& loaderData, Paint* paint, const SvgNode* node, const Box& vBox, const string& svgPath) { - /* ClipPath */ /* Do not drop in Circular Dependency for ClipPath. Composition can be applied recursively if its children nodes have composition target to this one. */ - if (node->style->clipPath.applying) { - TVGLOG("SVG", "Multiple Composition Tried! Check out Circular dependency?"); - } else { - auto compNode = node->style->clipPath.node; - if (compNode && compNode->child.count > 0) { - node->style->clipPath.applying = true; + if (node->style->clipPath.applying || node->style->mask.applying) { + TVGLOG("SVG", "Multiple composition tried! Check out circular dependency?"); + return paint; + } - auto comp = Shape::gen(); + auto clipNode = node->style->clipPath.node; + auto maskNode = node->style->mask.node; + auto validClip = (clipNode && clipNode->child.count > 0) ? true : false; + auto validMask = (maskNode && maskNode->child.count > 0) ? true : false; - auto child = compNode->child.data; - auto valid = false; //Composite only when valid shapes exist + if (!validClip && !validMask) return paint; - for (uint32_t i = 0; i < compNode->child.count; ++i, ++child) { - if (_appendClipChild(loaderData, *child, comp.get(), vBox, svgPath, compNode->child.count > 1)) valid = true; - } + Scene* scene = Scene::gen().release(); + scene->push(tvg::cast(paint)); - if (valid) { - Matrix finalTransform = _compositionTransform(paint, node, compNode, SvgNodeType::ClipPath); - comp->transform(finalTransform); - paint->clip(std::move(comp)); - } + if (validClip) { + node->style->clipPath.applying = true; - node->style->clipPath.applying = false; + auto clipper = Shape::gen(); + auto child = clipNode->child.data; + auto valid = false; //Composite only when valid shapes exist + + for (uint32_t i = 0; i < clipNode->child.count; ++i, ++child) { + if (_appendClipChild(loaderData, *child, clipper.get(), vBox, svgPath, clipNode->child.count > 1)) valid = true; + } + + if (valid) { + Matrix finalTransform = _compositionTransform(paint, node, clipNode, SvgNodeType::ClipPath); + clipper->transform(finalTransform); + scene->clip(std::move(clipper)); } + + node->style->clipPath.applying = false; } /* Mask */ - /* Do not drop in Circular Dependency for Mask. - Composition can be applied recursively if its children nodes have composition target to this one. */ - if (node->style->mask.applying) { - TVGLOG("SVG", "Multiple Composition Tried! Check out Circular dependency?"); - } else { - auto compNode = node->style->mask.node; - if (compNode && compNode->child.count > 0) { - node->style->mask.applying = true; - - bool isMaskWhite = true; - if (auto comp = _sceneBuildHelper(loaderData, compNode, vBox, svgPath, true, 0, &isMaskWhite)) { - if (!compNode->node.mask.userSpace) { - Matrix finalTransform = _compositionTransform(paint, node, compNode, SvgNodeType::Mask); - comp->transform(finalTransform); - } else { - if (node->transform) comp->transform(*node->transform); - } - - if (compNode->node.mask.type == SvgMaskType::Luminance && !isMaskWhite) { - paint->composite(std::move(comp), CompositeMethod::LumaMask); - } else { - paint->composite(std::move(comp), CompositeMethod::AlphaMask); - } + if (validMask) { + node->style->mask.applying = true; + + if (auto mask = _sceneBuildHelper(loaderData, maskNode, vBox, svgPath, true, 0)) { + if (!maskNode->node.mask.userSpace) { + Matrix finalTransform = _compositionTransform(paint, node, maskNode, SvgNodeType::Mask); + mask->transform(finalTransform); + } else if (node->transform) { + mask->transform(*node->transform); } - - node->style->mask.applying = false; + scene->mask(std::move(mask), maskNode->node.mask.type == SvgMaskType::Luminance ? MaskMethod::Luma: MaskMethod::Alpha); } + + node->style->mask.applying = false; } + + return scene; } -static void _applyProperty(SvgLoaderData& loaderData, SvgNode* node, Shape* vg, const Box& vBox, const string& svgPath, bool clip) +static Paint* _applyProperty(SvgLoaderData& loaderData, SvgNode* node, Shape* vg, const Box& vBox, const string& svgPath, bool clip) { SvgStyleProperty* style = node->style; //Clip transformation is applied directly to the path in the _appendClipShape function if (node->transform && !clip) vg->transform(*node->transform); - if (node->type == SvgNodeType::Doc || !node->style->display) return; + if (node->type == SvgNodeType::Doc || !node->style->display) return vg; //If fill property is nullptr then do nothing if (style->fill.paint.none) { //Do nothing } else if (style->fill.paint.gradient) { - Box bBox = vBox; + auto bBox = vBox; if (!style->fill.paint.gradient->userSpace) bBox = _boundingBox(vg); - if (style->fill.paint.gradient->type == SvgGradientType::Linear) { auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, bBox, style->fill.opacity); vg->fill(std::move(linear)); @@ -334,7 +305,6 @@ static void _applyProperty(SvgLoaderData& loaderData, SvgNode* node, Shape* vg, vg->fill(std::move(radial)); } } else if (style->fill.paint.url) { - //TODO: Apply the color pointed by url TVGLOG("SVG", "The fill's url not supported."); } else if (style->fill.paint.curColor) { //Apply the current style color @@ -344,59 +314,44 @@ static void _applyProperty(SvgLoaderData& loaderData, SvgNode* node, Shape* vg, vg->fill(style->fill.paint.color.r, style->fill.paint.color.g, style->fill.paint.color.b, style->fill.opacity); } - //Apply the fill rule vg->fill((tvg::FillRule)style->fill.fillRule); - //Rendering order vg->order(!style->paintOrder); + vg->opacity(style->opacity); - //Apply node opacity - if (style->opacity < 255) vg->opacity(style->opacity); - - if (node->type == SvgNodeType::G || node->type == SvgNodeType::Use) return; + if (node->type == SvgNodeType::G || node->type == SvgNodeType::Use) return vg; //Apply the stroke style property - vg->stroke(style->stroke.width); - vg->stroke(style->stroke.cap); - vg->stroke(style->stroke.join); + vg->strokeWidth(style->stroke.width); + vg->strokeCap(style->stroke.cap); + vg->strokeJoin(style->stroke.join); vg->strokeMiterlimit(style->stroke.miterlimit); - if (style->stroke.dash.array.count > 0) { - P(vg)->strokeDash(style->stroke.dash.array.data, style->stroke.dash.array.count, style->stroke.dash.offset); - } + vg->strokeDash(style->stroke.dash.array.data, style->stroke.dash.array.count, style->stroke.dash.offset); //If stroke property is nullptr then do nothing if (style->stroke.paint.none) { - vg->stroke(0.0f); + vg->strokeWidth(0.0f); } else if (style->stroke.paint.gradient) { - Box bBox = vBox; + auto bBox = vBox; if (!style->stroke.paint.gradient->userSpace) bBox = _boundingBox(vg); - if (style->stroke.paint.gradient->type == SvgGradientType::Linear) { auto linear = _applyLinearGradientProperty(style->stroke.paint.gradient, bBox, style->stroke.opacity); - vg->stroke(std::move(linear)); + vg->strokeFill(std::move(linear)); } else if (style->stroke.paint.gradient->type == SvgGradientType::Radial) { auto radial = _applyRadialGradientProperty(style->stroke.paint.gradient, bBox, style->stroke.opacity); - vg->stroke(std::move(radial)); + vg->strokeFill(std::move(radial)); } } else if (style->stroke.paint.url) { //TODO: Apply the color pointed by url TVGLOG("SVG", "The stroke's url not supported."); } else if (style->stroke.paint.curColor) { //Apply the current style color - vg->stroke(style->color.r, style->color.g, style->color.b, style->stroke.opacity); + vg->strokeFill(style->color.r, style->color.g, style->color.b, style->stroke.opacity); } else { //Apply the stroke color - vg->stroke(style->stroke.paint.color.r, style->stroke.paint.color.g, style->stroke.paint.color.b, style->stroke.opacity); + vg->strokeFill(style->stroke.paint.color.r, style->stroke.paint.color.g, style->stroke.paint.color.b, style->stroke.opacity); } - _applyComposition(loaderData, vg, node, vBox, svgPath); -} - - -static unique_ptr _shapeBuildHelper(SvgLoaderData& loaderData, SvgNode* node, const Box& vBox, const string& svgPath) -{ - auto shape = Shape::gen(); - if (_appendShape(loaderData, node, shape.get(), vBox, svgPath)) return shape; - else return nullptr; + return _applyComposition(loaderData, vg, node, vBox, svgPath); } @@ -456,32 +411,26 @@ static bool _recognizeShape(SvgNode* node, Shape* shape) } -static bool _appendShape(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath) +static Paint* _shapeBuildHelper(SvgLoaderData& loaderData, SvgNode* node, const Box& vBox, const string& svgPath) { - if (!_recognizeShape(node, shape)) return false; - - _applyProperty(loaderData, node, shape, vBox, svgPath, false); - return true; + auto shape = Shape::gen(); + if (!_recognizeShape(node, shape.get())) return nullptr; + return _applyProperty(loaderData, node, shape.release(), vBox, svgPath, false); } static bool _appendClipShape(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath, const Matrix* transform) { + if (!_recognizeShape(node, shape)) return false; + //The 'transform' matrix has higher priority than the node->transform, since it already contains it auto m = transform ? transform : (node->transform ? node->transform : nullptr); uint32_t currentPtsCnt = 0; if (m) { - const Point *tmp = nullptr; - currentPtsCnt = shape->pathCoords(&tmp); - } - - if (!_recognizeShape(node, shape)) return false; - - if (m) { + currentPtsCnt = shape->pathCoords(nullptr); const Point *pts = nullptr; auto ptsCnt = shape->pathCoords(&pts); - auto p = const_cast(pts) + currentPtsCnt; while (currentPtsCnt++ < ptsCnt) { *p *= *m; @@ -500,10 +449,12 @@ enum class imageMimeTypeEncoding utf8 = 0x2 }; + constexpr imageMimeTypeEncoding operator|(imageMimeTypeEncoding a, imageMimeTypeEncoding b) { return static_cast(static_cast(a) | static_cast(b)); } + constexpr bool operator&(imageMimeTypeEncoding a, imageMimeTypeEncoding b) { return (static_cast(a) & static_cast(b)); } @@ -530,47 +481,47 @@ static bool _isValidImageMimeTypeAndEncoding(const char** href, const char** mim //mediatype := [ type "/" subtype ] *( ";" parameter ) //parameter := attribute "=" value for (unsigned int i = 0; i < sizeof(imageMimeTypes) / sizeof(imageMimeTypes[0]); i++) { - if (!strncmp(*href, imageMimeTypes[i].name, imageMimeTypes[i].sz - 1)) { - *href += imageMimeTypes[i].sz - 1; - *mimetype = imageMimeTypes[i].name; - - while (**href && **href != ',') { - while (**href && **href != ';') ++(*href); - if (!**href) return false; - ++(*href); - - if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::base64) { - if (!strncmp(*href, "base64,", sizeof("base64,") - 1)) { - *href += sizeof("base64,") - 1; - *encoding = imageMimeTypeEncoding::base64; - return true; //valid base64 - } - } - if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8) { - if (!strncmp(*href, "utf8,", sizeof("utf8,") - 1)) { - *href += sizeof("utf8,") - 1; - *encoding = imageMimeTypeEncoding::utf8; - return true; //valid utf8 - } + if (strncmp(*href, imageMimeTypes[i].name, imageMimeTypes[i].sz - 1)) continue; + *href += imageMimeTypes[i].sz - 1; + *mimetype = imageMimeTypes[i].name; + + while (**href && **href != ',') { + while (**href && **href != ';') ++(*href); + if (!**href) return false; + ++(*href); + + if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::base64) { + if (!strncmp(*href, "base64,", sizeof("base64,") - 1)) { + *href += sizeof("base64,") - 1; + *encoding = imageMimeTypeEncoding::base64; + return true; //valid base64 } } - //no encoding defined - if (**href == ',' && (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8)) { - ++(*href); - *encoding = imageMimeTypeEncoding::utf8; - return true; //allow no encoding defined if utf8 expected + if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8) { + if (!strncmp(*href, "utf8,", sizeof("utf8,") - 1)) { + *href += sizeof("utf8,") - 1; + *encoding = imageMimeTypeEncoding::utf8; + return true; //valid utf8 + } } - return false; } + //no encoding defined + if (**href == ',' && (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8)) { + ++(*href); + *encoding = imageMimeTypeEncoding::utf8; + return true; //allow no encoding defined if utf8 expected + } + return false; } return false; } #include "tvgTaskScheduler.h" -static unique_ptr _imageBuildHelper(SvgLoaderData& loaderData, SvgNode* node, const Box& vBox, const string& svgPath) +static Paint* _imageBuildHelper(SvgLoaderData& loaderData, SvgNode* node, const Box& vBox, const string& svgPath) { if (!node->node.image.href || !strlen(node->node.image.href)) return nullptr; + auto picture = Picture::gen(); TaskScheduler::async(false); //force to load a picture on the same thread @@ -584,14 +535,14 @@ static unique_ptr _imageBuildHelper(SvgLoaderData& loaderData, SvgNode* char *decoded = nullptr; if (encoding == imageMimeTypeEncoding::base64) { auto size = b64Decode(href, strlen(href), &decoded); - if (picture->load(decoded, size, mimetype, false) != Result::Success) { + if (picture->load(decoded, size, mimetype) != Result::Success) { free(decoded); TaskScheduler::async(true); return nullptr; } } else { auto size = svgUtilURLDecode(href, &decoded); - if (picture->load(decoded, size, mimetype, false) != Result::Success) { + if (picture->load(decoded, size, mimetype) != Result::Success) { free(decoded); TaskScheduler::async(true); return nullptr; @@ -613,7 +564,7 @@ static unique_ptr _imageBuildHelper(SvgLoaderData& loaderData, SvgNode* auto last = svgPath.find_last_of("/"); imagePath = svgPath.substr(0, (last == string::npos ? 0 : last + 1)) + imagePath; } - if (picture->load(imagePath) != Result::Success) { + if (picture->load(imagePath.c_str()) != Result::Success) { TaskScheduler::async(true); return nullptr; } @@ -622,18 +573,18 @@ static unique_ptr _imageBuildHelper(SvgLoaderData& loaderData, SvgNode* TaskScheduler::async(true); float w, h; - Matrix m = {1, 0, 0, 0, 1, 0, 0, 0, 1}; - if (picture->size(&w, &h) == Result::Success && w > 0 && h > 0) { + Matrix m; + if (picture->size(&w, &h) == Result::Success && w > 0 && h > 0) { auto sx = node->node.image.w / w; auto sy = node->node.image.h / h; m = {sx, 0, node->node.image.x, 0, sy, node->node.image.y, 0, 0, 1}; + } else { + m = {1, 0, 0, 0, 1, 0, 0, 0, 1}; } if (node->transform) m = *node->transform * m; picture->transform(m); - _applyComposition(loaderData, picture.get(), node, vBox, svgPath); - - return picture; + return _applyComposition(loaderData, picture.release(), node, vBox, svgPath); } @@ -644,8 +595,7 @@ static Matrix _calculateAspectRatioMatrix(AspectRatioAlign align, AspectRatioMee auto tvx = box.x * sx; auto tvy = box.y * sy; - if (align == AspectRatioAlign::None) - return {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1}; + if (align == AspectRatioAlign::None) return {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1}; //Scale if (meetOrSlice == AspectRatioMeetOrSlice::Meet) { @@ -711,9 +661,9 @@ static Matrix _calculateAspectRatioMatrix(AspectRatioAlign align, AspectRatioMee } -static unique_ptr _useBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath, int depth, bool* isMaskWhite) +static unique_ptr _useBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath, int depth) { - auto scene = _sceneBuildHelper(loaderData, node, vBox, svgPath, false, depth + 1, isMaskWhite); + auto scene = _sceneBuildHelper(loaderData, node, vBox, svgPath, false, depth + 1); // mUseTransform = mUseTransform * mTranslate Matrix mUseTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1}; @@ -725,7 +675,6 @@ static unique_ptr _useBuildHelper(SvgLoaderData& loaderData, const SvgNod if (node->node.use.symbol) { auto symbol = node->node.use.symbol->node.symbol; - auto width = (symbol.hasWidth ? symbol.w : vBox.w); if (node->node.use.isWidthSet) width = node->node.use.w; auto height = (symbol.hasHeight ? symbol.h : vBox.h);; @@ -801,14 +750,17 @@ static void _applyTextFill(SvgStyleProperty* style, Text* text, const Box& vBox) } -static unique_ptr _textBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath) +static Paint* _textBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath) { auto textNode = &node->node.text; if (!textNode->text) return nullptr; - auto text = Text::gen(); - Matrix textTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1}; + auto text = Text::gen().release(); + + Matrix textTransform; if (node->transform) textTransform = *node->transform; + else textTransform = {1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}; + translateR(&textTransform, node->node.text.x, node->node.text.y - textNode->fontSize); text->transform(textTransform); @@ -818,14 +770,13 @@ static unique_ptr _textBuildHelper(SvgLoaderData& loaderData, const SvgNod if (textNode->fontFamily) text->font(textNode->fontFamily, fontSizePt); text->text(textNode->text); - _applyTextFill(node->style, text.get(), vBox); - _applyComposition(loaderData, text.get(), node, vBox, svgPath); + _applyTextFill(node->style, text, vBox); - return text; + return _applyComposition(loaderData, text, node, vBox, svgPath); } -static unique_ptr _sceneBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath, bool mask, int depth, bool* isMaskWhite) +static unique_ptr _sceneBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath, bool mask, int depth) { /* Exception handling: Prevent invalid SVG data input. The size is the arbitrary value, we need an experimental size. */ @@ -834,49 +785,35 @@ static unique_ptr _sceneBuildHelper(SvgLoaderData& loaderData, const SvgN return nullptr; } - if (_isGroupType(node->type) || mask) { - auto scene = Scene::gen(); - // For a Symbol node, the viewBox transformation has to be applied first - see _useBuildHelper() - if (!mask && node->transform && node->type != SvgNodeType::Symbol) scene->transform(*node->transform); - - if (node->style->display && node->style->opacity != 0) { - auto child = node->child.data; - for (uint32_t i = 0; i < node->child.count; ++i, ++child) { - if (_isGroupType((*child)->type)) { - if ((*child)->type == SvgNodeType::Use) - scene->push(_useBuildHelper(loaderData, *child, vBox, svgPath, depth + 1, isMaskWhite)); - else if (!((*child)->type == SvgNodeType::Symbol && node->type != SvgNodeType::Use)) - scene->push(_sceneBuildHelper(loaderData, *child, vBox, svgPath, false, depth + 1, isMaskWhite)); - } else if ((*child)->type == SvgNodeType::Image) { - auto image = _imageBuildHelper(loaderData, *child, vBox, svgPath); - if (image) { - scene->push(std::move(image)); - if (isMaskWhite) *isMaskWhite = false; - } - } else if ((*child)->type == SvgNodeType::Text) { - auto text = _textBuildHelper(loaderData, *child, vBox, svgPath); - if (text) scene->push(std::move(text)); - } else if ((*child)->type != SvgNodeType::Mask) { - auto shape = _shapeBuildHelper(loaderData, *child, vBox, svgPath); - if (shape) { - if (isMaskWhite) { - uint8_t r, g, b; - shape->fillColor(&r, &g, &b); - if (shape->fill() || r < 255 || g < 255 || b < 255 || shape->strokeFill() || - (shape->strokeColor(&r, &g, &b) == Result::Success && (r < 255 || g < 255 || b < 255))) { - *isMaskWhite = false; - } - } - scene->push(std::move(shape)); - } - } + if (!_isGroupType(node->type) && !mask) return nullptr; + + auto scene = Scene::gen(); + // For a Symbol node, the viewBox transformation has to be applied first - see _useBuildHelper() + if (!mask && node->transform && node->type != SvgNodeType::Symbol) scene->transform(*node->transform); + + if (!node->style->display || node->style->opacity == 0) return scene; + + auto child = node->child.data; + for (uint32_t i = 0; i < node->child.count; ++i, ++child) { + if (_isGroupType((*child)->type)) { + if ((*child)->type == SvgNodeType::Use) + scene->push(_useBuildHelper(loaderData, *child, vBox, svgPath, depth + 1)); + else if (!((*child)->type == SvgNodeType::Symbol && node->type != SvgNodeType::Use)) + scene->push(_sceneBuildHelper(loaderData, *child, vBox, svgPath, false, depth + 1)); + if ((*child)->id) scene->id = djb2Encode((*child)->id); + } else { + Paint* paint = nullptr; + if ((*child)->type == SvgNodeType::Image) paint = _imageBuildHelper(loaderData, *child, vBox, svgPath); + else if ((*child)->type == SvgNodeType::Text) paint = _textBuildHelper(loaderData, *child, vBox, svgPath); + else if ((*child)->type != SvgNodeType::Mask) paint = _shapeBuildHelper(loaderData, *child, vBox, svgPath); + if (paint) { + if ((*child)->id) paint->id = djb2Encode((*child)->id); + scene->push(tvg::cast(paint)); } - _applyComposition(loaderData, scene.get(), node, vBox, svgPath); - scene->opacity(node->style->opacity); } - return scene; } - return nullptr; + scene->opacity(node->style->opacity); + return tvg::cast(_applyComposition(loaderData, scene.release(), node, vBox, svgPath)); } @@ -924,12 +861,12 @@ Scene* svgSceneBuild(SvgLoaderData& loaderData, Box vBox, float w, float h, Aspe auto viewBoxClip = Shape::gen(); viewBoxClip->appendRect(0, 0, w, h); - auto compositeLayer = Scene::gen(); - compositeLayer->clip(std::move(viewBoxClip)); - compositeLayer->push(std::move(docNode)); + auto clippingLayer = Scene::gen(); + clippingLayer->clip(std::move(viewBoxClip)); + clippingLayer->push(std::move(docNode)); auto root = Scene::gen(); - root->push(std::move(compositeLayer)); + root->push(std::move(clippingLayer)); loaderData.doc->node.doc.vx = vBox.x; loaderData.doc->node.doc.vy = vBox.y; diff --git a/libfenrir/src/main/jni/animation/thorvg/src/loaders/svg/tvgXmlParser.cpp b/libfenrir/src/main/jni/animation/thorvg/src/loaders/svg/tvgXmlParser.cpp index 09fc8aaac..da1cdae9e 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/loaders/svg/tvgXmlParser.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/loaders/svg/tvgXmlParser.cpp @@ -492,13 +492,13 @@ bool simpleXmlParseW3CAttribute(const char* buf, unsigned bufLength, simpleXMLAt key[0] = '\0'; val[0] = '\0'; - if (next == nullptr && sep != nullptr) { + if (sep != nullptr && next == nullptr) { memcpy(key, buf, sep - buf); key[sep - buf] = '\0'; memcpy(val, sep + 1, end - sep - 1); val[end - sep - 1] = '\0'; - } else if (sep < next && sep != nullptr) { + } else if (sep != nullptr && sep < next) { memcpy(key, buf, sep - buf); key[sep - buf] = '\0'; @@ -522,8 +522,9 @@ bool simpleXmlParseW3CAttribute(const char* buf, unsigned bufLength, simpleXMLAt } } + if (!next) break; buf = next + 1; - } while (next != nullptr); + } while (true); return true; } diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/config.h b/libfenrir/src/main/jni/animation/thorvg/src/renderer/config.h index cb0baaf75..036311a3f 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/config.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/config.h @@ -1,6 +1,9 @@ #define THORVG_VERSION_STRING "1.0.0" #define THORVG_SW_RASTER_SUPPORT #define THORVG_SVG_LOADER_SUPPORT +//#define THORVG_PNG_LOADER_SUPPORT +//#define THORVG_JPG_LOADER_SUPPORT +#define THORVG_LOTTIE_LOADER_SUPPORT #define THORVG_THREAD_SUPPORT #if defined(__ARM_NEON__) || defined(__aarch64__) #define THORVG_NEON_VECTOR_SUPPORT diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwCommon.h b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwCommon.h index f8491bbf2..825bf3cf4 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwCommon.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwCommon.h @@ -178,16 +178,12 @@ struct SwStroke SwFixed subPathLineLength; SwFixed width; SwFixed miterlimit; - - StrokeCap cap; - StrokeJoin join; - StrokeJoin joinSaved; SwFill* fill = nullptr; - SwStrokeBorder borders[2]; - float sx, sy; - + StrokeCap cap; + StrokeJoin join; + StrokeJoin joinSaved; bool firstPt; bool closedSubPath; bool handleWideStrokes; @@ -252,9 +248,9 @@ struct SwSurface : RenderSurface SwCompositor* compositor = nullptr; //compositor (optional) BlendMethod blendMethod = BlendMethod::Normal; - SwAlpha alpha(CompositeMethod method) + SwAlpha alpha(MaskMethod method) { - auto idx = (int)(method) - 2; //0: None, 1: ClipPath + auto idx = (int)(method) - 1; //-1 for None return alphas[idx > 3 ? 0 : idx]; //CompositeMethod has only four Matting methods. } @@ -488,6 +484,7 @@ SwFixed mathAtan(const SwPoint& pt); SwFixed mathCos(SwFixed angle); SwFixed mathSin(SwFixed angle); void mathSplitCubic(SwPoint* base); +void mathSplitLine(SwPoint* base); SwFixed mathDiff(SwFixed angle1, SwFixed angle2); SwFixed mathLength(const SwPoint& pt); int mathCubicAngle(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut); @@ -567,13 +564,17 @@ bool rasterStroke(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint bool rasterGradientStroke(SwSurface* surface, SwShape* shape, const Fill* fdata, uint8_t opacity); bool rasterClear(SwSurface* surface, uint32_t x, uint32_t y, uint32_t w, uint32_t h, pixel_t val = 0); void rasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len); +void rasterTranslucentPixel32(uint32_t* dst, uint32_t* src, uint32_t len, uint8_t opacity); +void rasterPixel32(uint32_t* dst, uint32_t* src, uint32_t len, uint8_t opacity); void rasterGrayscale8(uint8_t *dst, uint8_t val, uint32_t offset, int32_t len); void rasterXYFlip(uint32_t* src, uint32_t* dst, int32_t stride, int32_t w, int32_t h, const SwBBox& bbox, bool flipped); void rasterUnpremultiply(RenderSurface* surface); void rasterPremultiply(RenderSurface* surface); bool rasterConvertCS(RenderSurface* surface, ColorSpace to); -bool effectGaussianBlur(SwImage& image, SwImage& buffer, const SwBBox& bbox, const RenderEffectGaussian* params); -bool effectGaussianPrepare(RenderEffectGaussian* effect); +bool effectGaussianBlur(SwCompositor* cmp, SwSurface* surface, const RenderEffectGaussianBlur* params, bool direct); +bool effectGaussianBlurPrepare(RenderEffectGaussianBlur* effect); +bool effectDropShadow(SwCompositor* cmp, SwSurface* surfaces[2], const RenderEffectDropShadow* params, bool direct); +bool effectDropShadowPrepare(RenderEffectDropShadow* effect); #endif /* _TVG_SW_COMMON_H_ */ diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwFill.cpp b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwFill.cpp index 357df46ef..d8b731841 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwFill.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwFill.cpp @@ -207,7 +207,7 @@ static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface* } -bool _prepareLinear(SwFill* fill, const LinearGradient* linear, const Matrix& transform) +bool _prepareLinear(SwFill* fill, const LinearGradient* linear, const Matrix& pTransform) { float x1, x2, y1, y2; if (linear->linear(&x1, &y1, &x2, &y2) != Result::Success) return false; @@ -227,32 +227,22 @@ bool _prepareLinear(SwFill* fill, const LinearGradient* linear, const Matrix& tr fill->linear.dy /= len; fill->linear.offset = -fill->linear.dx * x1 - fill->linear.dy * y1; - auto gradTransform = linear->transform(); - bool isTransformation = !tvg::identity((const Matrix*)(&gradTransform)); + auto transform = pTransform * linear->transform(); - if (isTransformation) { - gradTransform = transform * gradTransform; - } else { - gradTransform = transform; - isTransformation = true; - } + Matrix itransform; + if (!inverse(&transform, &itransform)) return false; - if (isTransformation) { - Matrix invTransform; - if (!inverse(&gradTransform, &invTransform)) return false; + fill->linear.offset += fill->linear.dx * itransform.e13 + fill->linear.dy * itransform.e23; - fill->linear.offset += fill->linear.dx * invTransform.e13 + fill->linear.dy * invTransform.e23; - - auto dx = fill->linear.dx; - fill->linear.dx = dx * invTransform.e11 + fill->linear.dy * invTransform.e21; - fill->linear.dy = dx * invTransform.e12 + fill->linear.dy * invTransform.e22; - } + auto dx = fill->linear.dx; + fill->linear.dx = dx * itransform.e11 + fill->linear.dy * itransform.e21; + fill->linear.dy = dx * itransform.e12 + fill->linear.dy * itransform.e22; return true; } -bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix& transform) +bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix& pTransform) { auto cx = P(radial)->cx; auto cy = P(radial)->cy; @@ -294,29 +284,18 @@ bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix& tr if (fill->radial.a > 0) fill->radial.invA = 1.0f / fill->radial.a; - auto gradTransform = radial->transform(); - bool isTransformation = !tvg::identity((const Matrix*)(&gradTransform)); + auto transform = pTransform * radial->transform(); - if (isTransformation) gradTransform = transform * gradTransform; - else { - gradTransform = transform; - isTransformation = true; - } + Matrix itransform; + if (!inverse(&transform, &itransform)) return false; + + fill->radial.a11 = itransform.e11; + fill->radial.a12 = itransform.e12; + fill->radial.a13 = itransform.e13; + fill->radial.a21 = itransform.e21; + fill->radial.a22 = itransform.e22; + fill->radial.a23 = itransform.e23; - if (isTransformation) { - Matrix invTransform; - if (!inverse(&gradTransform, &invTransform)) return false; - fill->radial.a11 = invTransform.e11; - fill->radial.a12 = invTransform.e12; - fill->radial.a13 = invTransform.e13; - fill->radial.a21 = invTransform.e21; - fill->radial.a22 = invTransform.e22; - fill->radial.a23 = invTransform.e23; - } else { - fill->radial.a11 = fill->radial.a22 = 1.0f; - fill->radial.a12 = fill->radial.a13 = 0.0f; - fill->radial.a21 = fill->radial.a23 = 0.0f; - } return true; } @@ -643,7 +622,7 @@ void fillLinear(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32 auto t2 = static_cast(t * FIXPT_SIZE); auto inc2 = static_cast(inc * FIXPT_SIZE); for (uint32_t j = 0; j < len; ++j, ++dst) { - auto src = MULTIPLY(_fixedPixel(fill, t2), a); + auto src = MULTIPLY(A(_fixedPixel(fill, t2)), a); *dst = maskOp(src, *dst, ~src); t2 += inc2; } @@ -651,7 +630,7 @@ void fillLinear(const SwFill* fill, uint8_t* dst, uint32_t y, uint32_t x, uint32 } else { uint32_t counter = 0; while (counter++ < len) { - auto src = MULTIPLY(_pixel(fill, t / GRADIENT_STOP_SIZE), a); + auto src = MULTIPLY(A(_pixel(fill, t / GRADIENT_STOP_SIZE)), a); *dst = maskOp(src, *dst, ~src); ++dst; t += inc; diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwMath.cpp b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwMath.cpp index 0b85d8906..1ff99f6ae 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwMath.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwMath.cpp @@ -242,6 +242,15 @@ void mathSplitCubic(SwPoint* base) } +void mathSplitLine(SwPoint* base) +{ + base[2] = base[1]; + + base[1].x = (base[0].x + base[1].x) >> 1; + base[1].y = (base[0].y + base[1].y) >> 1; +} + + SwFixed mathDiff(SwFixed angle1, SwFixed angle2) { auto delta = angle2 - angle1; @@ -303,9 +312,7 @@ bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, S if (yMin > pt->y) yMin = pt->y; if (yMax < pt->y) yMax = pt->y; } - //Since no antialiasing is applied in the Fast Track case, - //the rasterization region has to be rearranged. - //https://github.com/Samsung/thorvg/issues/916 + if (fastTrack) { renderRegion.min.x = static_cast(nearbyint(xMin / 64.0f)); renderRegion.max.x = static_cast(nearbyint(xMax / 64.0f)); diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwPostEffect.cpp b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwPostEffect.cpp index 474c838f0..01e9e66d0 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwPostEffect.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwPostEffect.cpp @@ -20,10 +20,11 @@ * SOFTWARE. */ +#include "tvgMath.h" #include "tvgSwCommon.h" /************************************************************************/ -/* Gaussian Filter Implementation */ +/* Gaussian Blur Implementation */ /************************************************************************/ struct SwGaussianBlur @@ -37,61 +38,70 @@ struct SwGaussianBlur static void _gaussianExtendRegion(RenderRegion& region, int extra, int8_t direction) { //bbox region expansion for feathering - if (direction == 0 || direction == 1) { + if (direction != 2) { region.x = -extra; - region.y = -extra; - } - - if (direction == 0 || direction == 2) { region.w = extra * 2; + } + if (direction != 1) { + region.y = -extra; region.h = extra * 2; } } -static int _gaussianRemap(int end, int idx, int border) +static int _gaussianEdgeWrap(int end, int idx) { - //wrap - if (border == 1) return idx % end; + auto r = idx % end; + return (r < 0) ? end + r : r; +} - //duplicate + +static int _gaussianEdgeExtend(int end, int idx) +{ if (idx < 0) return 0; else if (idx >= end) return end - 1; return idx; } +static int _gaussianRemap(int end, int idx, int border) +{ + if (border == 1) return _gaussianEdgeWrap(end, idx); + return _gaussianEdgeExtend(end, idx); +} + + //TODO: SIMD OPTIMIZATION? -static void _gaussianBlur(uint8_t* src, uint8_t* dst, int32_t stride, int32_t w, int32_t h, const SwBBox& bbox, int32_t dimension, int border, bool flipped) +static void _gaussianFilter(uint8_t* dst, uint8_t* src, int32_t stride, int32_t w, int32_t h, const SwBBox& bbox, int32_t dimension, int border, bool flipped) { if (flipped) { - src += ((bbox.min.x * stride) + bbox.min.y) << 2; - dst += ((bbox.min.x * stride) + bbox.min.y) << 2; + src += (bbox.min.x * stride + bbox.min.y) << 2; + dst += (bbox.min.x * stride + bbox.min.y) << 2; } else { - src += ((bbox.min.y * stride) + bbox.min.x) << 2; - dst += ((bbox.min.y * stride) + bbox.min.x) << 2; + src += (bbox.min.y * stride + bbox.min.x) << 2; + dst += (bbox.min.y * stride + bbox.min.x) << 2; } auto iarr = 1.0f / (dimension + dimension + 1); #pragma omp parallel for - for (int x = 0; x < h; x++) { - auto p = x * stride; + for (int y = 0; y < h; ++y) { + auto p = y * stride; auto i = p * 4; //current index auto l = -(dimension + 1); //left index auto r = dimension; //right index int acc[4] = {0, 0, 0, 0}; //sliding accumulator - //initial acucmulation - for (int x2 = l; x2 < r; ++x2) { - auto id = (_gaussianRemap(w, x2, border) + p) * 4; + //initial accumulation + for (int x = l; x < r; ++x) { + auto id = (_gaussianRemap(w, x, border) + p) * 4; acc[0] += src[id++]; acc[1] += src[id++]; acc[2] += src[id++]; acc[3] += src[id]; } //perform filtering - for (int x2 = 0; x2 < w; ++x2, ++r, ++l) { + for (int x = 0; x < w; ++x, ++r, ++l) { auto rid = (_gaussianRemap(w, r, border) + p) * 4; auto lid = (_gaussianRemap(w, l, border) + p) * 4; acc[0] += src[rid++] - src[lid++]; @@ -107,97 +117,294 @@ static void _gaussianBlur(uint8_t* src, uint8_t* dst, int32_t stride, int32_t w, } -static int _gaussianInit(int* kernel, float sigma, int level) +static int _gaussianInit(SwGaussianBlur* data, float sigma, int quality) { - //compute the kernel - auto wl = (int) sqrt((12 * sigma / level) + 1); + const auto MAX_LEVEL = SwGaussianBlur::MAX_LEVEL; + + if (tvg::zero(sigma)) return 0; + + data->level = int(SwGaussianBlur::MAX_LEVEL * ((quality - 1) * 0.01f)) + 1; + + //compute box kernel sizes + auto wl = (int) sqrt((12 * sigma / MAX_LEVEL) + 1); if (wl % 2 == 0) --wl; auto wu = wl + 2; - auto mi = (12 * sigma - level * wl * wl - 4 * level * wl - 3 * level) / (-4 * wl - 4); + auto mi = (12 * sigma - MAX_LEVEL * wl * wl - 4 * MAX_LEVEL * wl - 3 * MAX_LEVEL) / (-4 * wl - 4); auto m = int(mi + 0.5f); auto extends = 0; - for (int i = 0; i < level; i++) { - kernel[i] = ((i < m ? wl : wu) - 1) / 2; - extends += kernel[i]; + for (int i = 0; i < data->level; i++) { + data->kernel[i] = ((i < m ? wl : wu) - 1) / 2; + extends += data->kernel[i]; } return extends; } -bool effectGaussianPrepare(RenderEffectGaussian* params) +bool effectGaussianBlurPrepare(RenderEffectGaussianBlur* params) { - auto data = (SwGaussianBlur*)malloc(sizeof(SwGaussianBlur)); + auto rd = (SwGaussianBlur*)malloc(sizeof(SwGaussianBlur)); - //compute box kernel sizes - data->level = int(SwGaussianBlur::MAX_LEVEL * ((params->quality - 1) * 0.01f)) + 1; - auto extends = _gaussianInit(data->kernel, params->sigma * params->sigma, data->level); + auto extends = _gaussianInit(rd, params->sigma * params->sigma, params->quality); - //skip, if the parameters are invalid. + //invalid if (extends == 0) { params->invalid = true; - free(data); + free(rd); return false; } _gaussianExtendRegion(params->extend, extends, params->direction); - params->rd = data; + params->rd = rd; return true; } -/* It is best to take advantage of the Gaussian blur’s separable property - by dividing the process into two passes. horizontal and vertical. - We can expect fewer calculations. */ -bool effectGaussianBlur(SwImage& image, SwImage& buffer, const SwBBox& bbox, const RenderEffectGaussian* params) +bool effectGaussianBlur(SwCompositor* cmp, SwSurface* surface, const RenderEffectGaussianBlur* params, TVG_UNUSED bool direct) { - if (params->invalid) return false; - - if (image.channelSize != sizeof(uint32_t)) { + if (cmp->image.channelSize != sizeof(uint32_t)) { TVGERR("SW_ENGINE", "Not supported grayscale Gaussian Blur!"); return false; } + auto& buffer = surface->compositor->image; auto data = static_cast(params->rd); + auto& bbox = cmp->bbox; auto w = (bbox.max.x - bbox.min.x); auto h = (bbox.max.y - bbox.min.y); - auto stride = image.stride; - auto front = image.buf8; - auto back = buffer.buf8; + auto stride = cmp->image.stride; + auto front = cmp->image.buf32; + auto back = buffer.buf32; auto swapped = false; TVGLOG("SW_ENGINE", "GaussianFilter region(%ld, %ld, %ld, %ld) params(%f %d %d), level(%d)", bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y, params->sigma, params->direction, params->border, data->level); + /* It is best to take advantage of the Gaussian blur’s separable property + by dividing the process into two passes. horizontal and vertical. + We can expect fewer calculations. */ + //horizontal - if (params->direction == 0 || params->direction == 1) { + if (params->direction != 2) { for (int i = 0; i < data->level; ++i) { - if (data->kernel[i] == 0) continue; - _gaussianBlur(front, back, stride, w, h, bbox, data->kernel[i], params->border, false); + _gaussianFilter(reinterpret_cast(back), reinterpret_cast(front), stride, w, h, bbox, data->kernel[i], params->border, false); std::swap(front, back); swapped = !swapped; } } //vertical. x/y flipping and horionztal access is pretty compatible with the memory architecture. - if (params->direction == 0 || params->direction == 2) { - rasterXYFlip(reinterpret_cast(front), reinterpret_cast(back), stride, w, h, bbox, false); + if (params->direction != 1) { + rasterXYFlip(front, back, stride, w, h, bbox, false); std::swap(front, back); for (int i = 0; i < data->level; ++i) { - if (data->kernel[i] == 0) continue; - _gaussianBlur(front, back, stride, h, w, bbox, data->kernel[i], params->border, true); + _gaussianFilter(reinterpret_cast(back), reinterpret_cast(front), stride, h, w, bbox, data->kernel[i], params->border, true); std::swap(front, back); swapped = !swapped; } - rasterXYFlip(reinterpret_cast(front), reinterpret_cast(back), stride, h, w, bbox, true); + rasterXYFlip(front, back, stride, h, w, bbox, true); std::swap(front, back); } - if (swapped) std::swap(image.buf8, buffer.buf8); + if (swapped) std::swap(cmp->image.buf8, buffer.buf8); + + return true; +} + +/************************************************************************/ +/* Drop Shadow Implementation */ +/************************************************************************/ + +struct SwDropShadow : SwGaussianBlur +{ + SwPoint offset; +}; + + +//TODO: SIMD OPTIMIZATION? +static void _dropShadowFilter(uint32_t* dst, uint32_t* src, int stride, int w, int h, const SwBBox& bbox, int32_t dimension, uint32_t color, bool flipped) +{ + if (flipped) { + src += (bbox.min.x * stride + bbox.min.y); + dst += (bbox.min.x * stride + bbox.min.y); + } else { + src += (bbox.min.y * stride + bbox.min.x); + dst += (bbox.min.y * stride + bbox.min.x); + } + auto iarr = 1.0f / (dimension + dimension + 1); + + //#pragma omp parallel for + for (int y = 0; y < h; ++y) { + auto p = y * stride; + auto i = p; //current index + auto l = -(dimension + 1); //left index + auto r = dimension; //right index + int acc = 0; //sliding accumulator + + //initial accumulation + for (int x = l; x < r; ++x) { + auto id = _gaussianEdgeExtend(w, x) + p; + acc += A(src[id]); + } + //perform filtering + for (int x = 0; x < w; ++x, ++r, ++l) { + auto rid = _gaussianEdgeExtend(w, r) + p; + auto lid = _gaussianEdgeExtend(w, l) + p; + acc += A(src[rid]) - A(src[lid]); + dst[i++] = ALPHA_BLEND(color, static_cast(acc * iarr + 0.5f)); + } + } +} + + +static void _dropShadowShift(uint32_t* dst, uint32_t* src, int stride, SwBBox& region, SwPoint& offset, uint8_t opacity, bool direct) +{ + src += (region.min.y * stride + region.min.x); + dst += (region.min.y * stride + region.min.x); + + auto w = region.max.x - region.min.x; + auto h = region.max.y - region.min.y; + auto translucent = (direct || opacity < 255); + + //shift offset + if (region.min.x + offset.x < 0) src -= offset.x; + else dst += offset.x; + + if (region.min.y + offset.y < 0) src -= (offset.y * stride); + else dst += (offset.y * stride); + + for (auto y = 0; y < h; ++y) { + if (translucent) rasterTranslucentPixel32(dst, src, w, opacity); + else rasterPixel32(dst, src, w, opacity); + src += stride; + dst += stride; + } +} + + +static void _dropShadowExtendRegion(RenderRegion& region, int extra, SwPoint& offset) +{ + //bbox region expansion for feathering + region.x = -extra; + region.w = extra * 2; + region.y = -extra; + region.h = extra * 2; + + region.x = std::min(region.x + (int32_t)offset.x, region.x); + region.y = std::min(region.y + (int32_t)offset.y, region.y); + region.w += abs(offset.x); + region.h += abs(offset.y); +} + + +bool effectDropShadowPrepare(RenderEffectDropShadow* params) +{ + auto rd = (SwDropShadow*)malloc(sizeof(SwDropShadow)); + + //compute box kernel sizes + auto extends = _gaussianInit(rd, params->sigma * params->sigma, params->quality); + + //invalid + if (extends == 0 || params->color[3] == 0) { + params->invalid = true; + free(rd); + return false; + } + + //offset + if (params->distance > 0.0f) { + auto radian = tvg::deg2rad(90.0f - params->angle); + rd->offset = {(SwCoord)(params->distance * cosf(radian)), (SwCoord)(-1.0f * params->distance * sinf(radian))}; + } else { + rd->offset = {0, 0}; + } + + //bbox region expansion for feathering + _dropShadowExtendRegion(params->extend, extends, rd->offset); + + params->rd = rd; + + return true; +} + + +//A quite same integration with effectGaussianBlur(). See it for detailed comments. +//surface[0]: the original image, to overlay it into the filtered image. +//surface[1]: temporary buffer for generating the filtered image. +bool effectDropShadow(SwCompositor* cmp, SwSurface* surface[2], const RenderEffectDropShadow* params, bool direct) +{ + if (cmp->image.channelSize != sizeof(uint32_t)) { + TVGERR("SW_ENGINE", "Not supported grayscale Drop Shadow!"); + return false; + } + + //FIXME: if the body is partially visible due to clipping, the shadow also becomes partially visible. + + auto data = static_cast(params->rd); + auto& bbox = cmp->bbox; + auto w = (bbox.max.x - bbox.min.x); + auto h = (bbox.max.y - bbox.min.y); + + //outside the screen + if (abs(data->offset.x) >= w || abs(data->offset.y) >= h) return true; + + SwImage* buffer[] = {&surface[0]->compositor->image, &surface[1]->compositor->image}; + auto color = cmp->recoverSfc->join(params->color[0], params->color[1], params->color[2], 255); + auto stride = cmp->image.stride; + auto front = cmp->image.buf32; + auto back = buffer[1]->buf32; + auto opacity = params->color[3]; + + TVGLOG("SW_ENGINE", "DropShadow region(%ld, %ld, %ld, %ld) params(%f %f %f), level(%d)", bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y, params->angle, params->distance, params->sigma, data->level); + + //saving the original image in order to overlay it into the filtered image. + _dropShadowFilter(back, front, stride, w, h, bbox, data->kernel[0], color, false); + std::swap(front, buffer[0]->buf32); + std::swap(front, back); + + //horizontal + for (int i = 1; i < data->level; ++i) { + _dropShadowFilter(back, front, stride, w, h, bbox, data->kernel[i], color, false); + std::swap(front, back); + } + + //vertical + rasterXYFlip(front, back, stride, w, h, bbox, false); + std::swap(front, back); + + for (int i = 0; i < data->level; ++i) { + _dropShadowFilter(back, front, stride, h, w, bbox, data->kernel[i], color, true); + std::swap(front, back); + } + + rasterXYFlip(front, back, stride, h, w, bbox, true); + std::swap(cmp->image.buf32, back); + + //draw to the main surface directly + if (direct) { + _dropShadowShift(cmp->recoverSfc->buf32, cmp->image.buf32, stride, bbox, data->offset, opacity, direct); + std::swap(cmp->image.buf32, buffer[0]->buf32); + return true; + } + + //draw to the intermediate surface + rasterClear(surface[1], bbox.min.x, bbox.min.y, w, h); + _dropShadowShift(buffer[1]->buf32, cmp->image.buf32, stride, bbox, data->offset, opacity, direct); + std::swap(cmp->image.buf32, buffer[1]->buf32); + + //compositing shadow and body + auto s = buffer[0]->buf32 + (bbox.min.y * buffer[0]->stride + bbox.min.x); + auto d = cmp->image.buf32 + (bbox.min.y * cmp->image.stride + bbox.min.x); + + for (auto y = 0; y < h; ++y) { + rasterTranslucentPixel32(d, s, w, 255); + s += buffer[0]->stride; + d += cmp->image.stride; + } return true; } \ No newline at end of file diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp index 142ae8acf..944a361cc 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp @@ -154,14 +154,14 @@ static inline bool _blending(const SwSurface* surface) This would help to enhance the performance by avoiding the unnecessary matting from the composition */ static inline bool _compositing(const SwSurface* surface) { - if (!surface->compositor || (int)surface->compositor->method <= (int)CompositeMethod::ClipPath) return false; + if (!surface->compositor || surface->compositor->method == MaskMethod::None) return false; return true; } static inline bool _matting(const SwSurface* surface) { - if ((int)surface->compositor->method < (int)CompositeMethod::AddMask) return true; + if ((int)surface->compositor->method < (int)MaskMethod::Add) return true; else return false; } @@ -206,22 +206,22 @@ static inline uint8_t _opMaskDarken(uint8_t s, uint8_t d, uint8_t a) } -static inline bool _direct(CompositeMethod method) +static inline bool _direct(MaskMethod method) { - if (method == CompositeMethod::SubtractMask || method == CompositeMethod::IntersectMask || method == CompositeMethod::DarkenMask) return true; + if (method == MaskMethod::Subtract || method == MaskMethod::Intersect || method == MaskMethod::Darken) return true; return false; } -static inline SwMask _getMaskOp(CompositeMethod method) +static inline SwMask _getMaskOp(MaskMethod method) { switch (method) { - case CompositeMethod::AddMask: return _opMaskAdd; - case CompositeMethod::SubtractMask: return _opMaskSubtract; - case CompositeMethod::DifferenceMask: return _opMaskDifference; - case CompositeMethod::IntersectMask: return _opMaskIntersect; - case CompositeMethod::LightenMask: return _opMaskLighten; - case CompositeMethod::DarkenMask: return _opMaskDarken; + case MaskMethod::Add: return _opMaskAdd; + case MaskMethod::Subtract: return _opMaskSubtract; + case MaskMethod::Difference: return _opMaskDifference; + case MaskMethod::Intersect: return _opMaskIntersect; + case MaskMethod::Lighten: return _opMaskLighten; + case MaskMethod::Darken: return _opMaskDarken; default: return nullptr; } } @@ -869,16 +869,7 @@ static bool _rasterDirectRleImage(SwSurface* surface, const SwImage* image, uint auto dst = &surface->buf32[span->y * surface->stride + span->x]; auto img = image->buf32 + (span->y + image->oy) * image->stride + (span->x + image->ox); auto alpha = MULTIPLY(span->coverage, opacity); - if (alpha == 255) { - for (uint32_t x = 0; x < span->len; ++x, ++dst, ++img) { - *dst = *img + ALPHA_BLEND(*dst, IA(*img)); - } - } else { - for (uint32_t x = 0; x < span->len; ++x, ++dst, ++img) { - auto src = ALPHA_BLEND(*img, alpha); - *dst = src + ALPHA_BLEND(*dst, IA(src)); - } - } + rasterTranslucentPixel32(dst, img, span->len, alpha); } return true; } @@ -1144,37 +1135,24 @@ static bool _rasterDirectImage(SwSurface* surface, const SwImage* image, const S //32bits channels if (surface->channelSize == sizeof(uint32_t)) { auto dbuffer = &surface->buf32[region.min.y * surface->stride + region.min.x]; - for (auto y = region.min.y; y < region.max.y; ++y) { - auto dst = dbuffer; - auto src = sbuffer; - if (opacity == 255) { - for (auto x = region.min.x; x < region.max.x; x++, dst++, src++) { - *dst = *src + ALPHA_BLEND(*dst, IA(*src)); - } - } else { - for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++src) { - auto tmp = ALPHA_BLEND(*src, opacity); - *dst = tmp + ALPHA_BLEND(*dst, IA(tmp)); - } - } + rasterTranslucentPixel32(dbuffer, sbuffer, region.max.x - region.min.x, opacity); dbuffer += surface->stride; sbuffer += image->stride; } //8bits grayscale } else if (surface->channelSize == sizeof(uint8_t)) { auto dbuffer = &surface->buf8[region.min.y * surface->stride + region.min.x]; - for (auto y = region.min.y; y < region.max.y; ++y, dbuffer += surface->stride, sbuffer += image->stride) { auto dst = dbuffer; auto src = sbuffer; if (opacity == 255) { for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++src) { - *dst = *src + MULTIPLY(*dst, ~*src); + *dst = *src + MULTIPLY(*dst, IA(*src)); } } else { for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++src) { - *dst = INTERPOLATE8(*src, *dst, opacity); + *dst = INTERPOLATE8(A(*src), *dst, opacity); } } } @@ -1528,8 +1506,7 @@ static bool _rasterTranslucentGradientRle(SwSurface* surface, const SwRle* rle, } else if (surface->channelSize == sizeof(uint8_t)) { for (uint32_t i = 0; i < rle->size; ++i, ++span) { auto dst = &surface->buf8[span->y * surface->stride + span->x]; - if (span->coverage == 255) fillMethod()(fill, dst, span->y, span->x, span->len, _opMaskNone, 255); - else fillMethod()(fill, dst, span->y, span->x, span->len, _opMaskAdd, span->coverage); + fillMethod()(fill, dst, span->y, span->x, span->len, _opMaskAdd, span->coverage); } } return true; @@ -1586,9 +1563,9 @@ static bool _rasterRadialGradientRle(SwSurface* surface, const SwRle* rle, const if (_matting(surface)) return _rasterGradientMattedRle(surface, rle, fill); else return _rasterGradientMaskedRle(surface, rle, fill); } else if (_blending(surface)) { - _rasterBlendingGradientRle(surface, rle, fill); + return _rasterBlendingGradientRle(surface, rle, fill); } else { - if (fill->translucent) _rasterTranslucentGradientRle(surface, rle, fill); + if (fill->translucent) return _rasterTranslucentGradientRle(surface, rle, fill); else return _rasterSolidGradientRle(surface, rle, fill); } return false; @@ -1599,6 +1576,19 @@ static bool _rasterRadialGradientRle(SwSurface* surface, const SwRle* rle, const /* External Class Implementation */ /************************************************************************/ +void rasterTranslucentPixel32(uint32_t* dst, uint32_t* src, uint32_t len, uint8_t opacity) +{ + //TODO: Support SIMD accelerations + cRasterTranslucentPixels(dst, src, len, opacity); +} + + +void rasterPixel32(uint32_t* dst, uint32_t* src, uint32_t len, uint8_t opacity) +{ + //TODO: Support SIMD accelerations + cRasterPixels(dst, src, len, opacity); +} + void rasterGrayscale8(uint8_t *dst, uint8_t val, uint32_t offset, int32_t len) { @@ -1626,7 +1616,7 @@ void rasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len) bool rasterCompositor(SwSurface* surface) { - //See CompositeMethod, Alpha:3, InvAlpha:4, Luma:5, InvLuma:6 + //See MaskMethod, Alpha:1, InvAlpha:2, Luma:3, InvLuma:4 surface->alphas[0] = _alpha; surface->alphas[1] = _ialpha; @@ -1639,7 +1629,7 @@ bool rasterCompositor(SwSurface* surface) surface->alphas[2] = _argbLuma; surface->alphas[3] = _argbInvLuma; } else { - TVGERR("SW_ENGINE", "Unsupported Colorspace(%d) is expected!", surface->cs); + TVGERR("SW_ENGINE", "Unsupported Colorspace(%d) is expected!", (int)surface->cs); return false; } return true; diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRasterC.h b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRasterC.h index 258edf84a..8bea1b137 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRasterC.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRasterC.h @@ -20,6 +20,38 @@ * SOFTWARE. */ + +template +static void inline cRasterTranslucentPixels(PIXEL_T* dst, PIXEL_T* src, uint32_t len, uint32_t opacity) +{ + //TODO: 64bits faster? + if (opacity == 255) { + for (uint32_t x = 0; x < len; ++x, ++dst, ++src) { + *dst = *src + ALPHA_BLEND(*dst, IA(*src)); + } + } else { + for (uint32_t x = 0; x < len; ++x, ++dst, ++src) { + auto tmp = ALPHA_BLEND(*src, opacity); + *dst = tmp + ALPHA_BLEND(*dst, IA(tmp)); + } + } +} + + +template +static void inline cRasterPixels(PIXEL_T* dst, PIXEL_T* src, uint32_t len, uint32_t opacity) +{ + //TODO: 64bits faster? + if (opacity == 255) { + for (uint32_t x = 0; x < len; ++x, ++dst, ++src) { + *dst = *src; + } + } else { + cRasterTranslucentPixels(dst, src, len, opacity); + } +} + + template static void inline cRasterPixels(PIXEL_T* dst, PIXEL_T val, uint32_t offset, int32_t len) { diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h index 3d85248f8..1f7e91d78 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h @@ -64,12 +64,10 @@ static bool _arrange(const SwImage* image, const SwBBox* region, int& yStart, in regionBottom = image->rle->spans[image->rle->size - 1].y; } - if (yStart >= regionBottom) return false; - if (yStart < regionTop) yStart = regionTop; if (yEnd > regionBottom) yEnd = regionBottom; - return true; + return yEnd > yStart; } diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp index 7684df55e..a3d87ef64 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp @@ -41,7 +41,7 @@ struct SwTask : Task { SwSurface* surface = nullptr; SwMpool* mpool = nullptr; - SwBBox bbox = {{0, 0}, {0, 0}}; //Whole Rendering Region + SwBBox bbox; //Rendering Region Matrix transform; Array clips; RenderUpdateFlag flags = RenderUpdateFlag::None; @@ -112,11 +112,15 @@ struct SwShapeTask : SwTask void run(unsigned tid) override { - if (opacity == 0 && !clipper) return; //Invisible + //Invisible + if (opacity == 0 && !clipper) { + bbox.reset(); + return; + } auto strokeWidth = validStrokeWidth(); - bool visibleFill = false; - auto clipRegion = bbox; + SwBBox renderRegion{}; + auto visibleFill = false; //This checks also for the case, if the invisible shape turned to visible by alpha. auto prepareShape = false; @@ -128,10 +132,11 @@ struct SwShapeTask : SwTask rshape->fillColor(nullptr, nullptr, nullptr, &alpha); alpha = MULTIPLY(alpha, opacity); visibleFill = (alpha > 0 || rshape->fill); + shapeReset(&shape); if (visibleFill || clipper) { - shapeReset(&shape); - if (!shapePrepare(&shape, rshape, transform, clipRegion, bbox, mpool, tid, clips.count > 0 ? true : false)) { + if (!shapePrepare(&shape, rshape, transform, bbox, renderRegion, mpool, tid, clips.count > 0 ? true : false)) { visibleFill = false; + renderRegion.reset(); } } } @@ -152,8 +157,8 @@ struct SwShapeTask : SwTask if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) { if (strokeWidth > 0.0f) { shapeResetStroke(&shape, rshape, transform); - if (!shapeGenStrokeRle(&shape, rshape, transform, clipRegion, bbox, mpool, tid)) goto err; + if (!shapeGenStrokeRle(&shape, rshape, transform, bbox, renderRegion, mpool, tid)) goto err; if (auto fill = rshape->strokeFill()) { auto ctable = (flags & RenderUpdateFlag::GradientStroke) ? true : false; if (ctable) shapeResetStrokeFill(&shape); @@ -177,9 +182,13 @@ struct SwShapeTask : SwTask //Clip stroke rle if (shape.strokeRle && !clipper->clip(shape.strokeRle)) goto err; } + + bbox = renderRegion; //sync + return; err: + bbox.reset(); shapeReset(&shape); shapeDelOutline(&shape, mpool, tid); } @@ -277,7 +286,7 @@ static void _renderStroke(SwShapeTask* task, SwSurface* surface, uint8_t opacity if (auto strokeFill = task->rshape->strokeFill()) { rasterGradientStroke(surface, &task->shape, strokeFill, opacity); } else { - if (task->rshape->strokeColor(&r, &g, &b, &a)) { + if (task->rshape->strokeFill(&r, &g, &b, &a)) { a = MULTIPLY(opacity, a); if (a > 0) rasterStroke(surface, &task->shape, r, g, b, a); } @@ -303,6 +312,13 @@ SwRenderer::~SwRenderer() bool SwRenderer::clear() +{ + if (surface) return rasterClear(surface, 0, 0, surface->w, surface->h); + return false; +} + + +bool SwRenderer::sync() { for (auto task = tasks.begin(); task < tasks.end(); ++task) { if ((*task)->disposed) { @@ -316,18 +332,6 @@ bool SwRenderer::clear() if (!sharedMpool) mpoolClear(mpool); - if (surface) { - vport.x = vport.y = 0; - vport.w = surface->w; - vport.h = surface->h; - } - - return true; -} - - -bool SwRenderer::sync() -{ return true; } @@ -367,7 +371,7 @@ bool SwRenderer::target(pixel_t* data, uint32_t stride, uint32_t w, uint32_t h, bool SwRenderer::preRender() { - return rasterClear(surface, 0, 0, surface->w, surface->h); + return true; } @@ -493,7 +497,7 @@ RenderRegion SwRenderer::region(RenderData data) } -bool SwRenderer::beginComposite(RenderCompositor* cmp, CompositeMethod method, uint8_t opacity) +bool SwRenderer::beginComposite(RenderCompositor* cmp, MaskMethod method, uint8_t opacity) { if (!cmp) return false; auto p = static_cast(cmp); @@ -502,7 +506,7 @@ bool SwRenderer::beginComposite(RenderCompositor* cmp, CompositeMethod method, u p->opacity = opacity; //Current Context? - if (p->method != CompositeMethod::None) { + if (p->method != MaskMethod::None) { surface = p->recoverSfc; surface->compositor = p; } @@ -626,7 +630,7 @@ bool SwRenderer::endComposite(RenderCompositor* cmp) surface->compositor = p->recoverCmp; //Default is alpha blending - if (p->method == CompositeMethod::None) { + if (p->method == MaskMethod::None) { Matrix m = {1, 0, 0, 0, 1, 0, 0, 0, 1}; return rasterImage(surface, &p->image, m, p->bbox, p->opacity); } @@ -638,19 +642,32 @@ bool SwRenderer::endComposite(RenderCompositor* cmp) bool SwRenderer::prepare(RenderEffect* effect) { switch (effect->type) { - case SceneEffect::GaussianBlur: return effectGaussianPrepare(static_cast(effect)); + case SceneEffect::GaussianBlur: return effectGaussianBlurPrepare(static_cast(effect)); + case SceneEffect::DropShadow: return effectDropShadowPrepare(static_cast(effect)); default: return false; } } -bool SwRenderer::effect(RenderCompositor* cmp, const RenderEffect* effect) +bool SwRenderer::effect(RenderCompositor* cmp, const RenderEffect* effect, bool direct) { + if (effect->invalid) return false; + auto p = static_cast(cmp); - auto& buffer = request(surface->channelSize)->compositor->image; switch (effect->type) { - case SceneEffect::GaussianBlur: return effectGaussianBlur(p->image, buffer, p->bbox, static_cast(effect)); + case SceneEffect::GaussianBlur: { + return effectGaussianBlur(p, request(surface->channelSize), static_cast(effect), direct); + } + case SceneEffect::DropShadow: { + auto cmp1 = request(surface->channelSize); + cmp1->compositor->valid = false; + auto cmp2 = request(surface->channelSize); + SwSurface* surfaces[] = {cmp1, cmp2}; + auto ret = effectDropShadow(p, surfaces, static_cast(effect), direct); + cmp1->compositor->valid = true; + return ret; + } default: return false; } } @@ -659,7 +676,7 @@ bool SwRenderer::effect(RenderCompositor* cmp, const RenderEffect* effect) ColorSpace SwRenderer::colorSpace() { if (surface) return surface->cs; - else return ColorSpace::Unsupported; + else return ColorSpace::Unknown; } diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRenderer.h b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRenderer.h index a5d630282..489a024bb 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRenderer.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRenderer.h @@ -56,12 +56,12 @@ class SwRenderer : public RenderMethod bool mempool(bool shared); RenderCompositor* target(const RenderRegion& region, ColorSpace cs) override; - bool beginComposite(RenderCompositor* cmp, CompositeMethod method, uint8_t opacity) override; + bool beginComposite(RenderCompositor* cmp, MaskMethod method, uint8_t opacity) override; bool endComposite(RenderCompositor* cmp) override; void clearCompositors(); bool prepare(RenderEffect* effect) override; - bool effect(RenderCompositor* cmp, const RenderEffect* effect) override; + bool effect(RenderCompositor* cmp, const RenderEffect* effect, bool direct) override; static SwRenderer* gen(); static bool init(uint32_t threads); diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRle.cpp b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRle.cpp index 84c497fb2..e4563cbd6 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRle.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwRle.cpp @@ -235,6 +235,7 @@ struct RleWorker SwPoint pos; SwPoint bezStack[32 * 3 + 1]; + SwPoint lineStack[32 + 1]; int levStack[32]; SwOutline* outline; @@ -513,103 +514,116 @@ static void _lineTo(RleWorker& rw, const SwPoint& to) return; } - auto diff = to - rw.pos; - auto f1 = rw.pos - SUBPIXELS(e1); - SwPoint f2; - - //inside one cell - if (e1 == e2) { - ; - //any horizontal line - } else if (diff.y == 0) { - e1.x = e2.x; - _setCell(rw, e1); - } else if (diff.x == 0) { - //vertical line up - if (diff.y > 0) { - do { - f2.y = ONE_PIXEL; - rw.cover += (f2.y - f1.y); - rw.area += (f2.y - f1.y) * f1.x * 2; - f1.y = 0; - ++e1.y; - _setCell(rw, e1); - } while(e1.y != e2.y); - //vertical line down + auto line = rw.lineStack; + line[0] = to; + line[1] = rw.pos; + + while (true) { + auto diff = line[0] - line[1]; + auto L = HYPOT(diff); + + if (L > SHRT_MAX) { + mathSplitLine(line); + ++line; + continue; + } + e1 = TRUNC(line[1]); + e2 = TRUNC(line[0]); + + auto f1 = line[1] - SUBPIXELS(e1); + SwPoint f2; + + //inside one cell + if (e1 == e2) { + ; + //any horizontal line + } else if (diff.y == 0) { + e1.x = e2.x; + _setCell(rw, e1); + } else if (diff.x == 0) { + //vertical line up + if (diff.y > 0) { + do { + f2.y = ONE_PIXEL; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * f1.x * 2; + f1.y = 0; + ++e1.y; + _setCell(rw, e1); + } while(e1.y != e2.y); + //vertical line down + } else { + do { + f2.y = 0; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * f1.x * 2; + f1.y = ONE_PIXEL; + --e1.y; + _setCell(rw, e1); + } while(e1.y != e2.y); + } + //any other line } else { + Area prod = diff.x * f1.y - diff.y * f1.x; + + /* These macros speed up repetitive divisions by replacing them + with multiplications and right shifts. */ + auto dx_r = static_cast(ULONG_MAX >> PIXEL_BITS) / (diff.x); + auto dy_r = static_cast(ULONG_MAX >> PIXEL_BITS) / (diff.y); + + /* The fundamental value `prod' determines which side and the */ + /* exact coordinate where the line exits current cell. It is */ + /* also easily updated when moving from one cell to the next. */ do { - f2.y = 0; - rw.cover += (f2.y - f1.y); - rw.area += (f2.y - f1.y) * f1.x * 2; - f1.y = ONE_PIXEL; - --e1.y; + auto px = diff.x * ONE_PIXEL; + auto py = diff.y * ONE_PIXEL; + + //left + if (prod <= 0 && prod - px > 0) { + f2 = {0, SW_UDIV(-prod, -dx_r)}; + prod -= py; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * (f1.x + f2.x); + f1 = {ONE_PIXEL, f2.y}; + --e1.x; + //up + } else if (prod - px <= 0 && prod - px + py > 0) { + prod -= px; + f2 = {SW_UDIV(-prod, dy_r), ONE_PIXEL}; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * (f1.x + f2.x); + f1 = {f2.x, 0}; + ++e1.y; + //right + } else if (prod - px + py <= 0 && prod + py >= 0) { + prod += py; + f2 = {ONE_PIXEL, SW_UDIV(prod, dx_r)}; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * (f1.x + f2.x); + f1 = {0, f2.y}; + ++e1.x; + //down + } else { + f2 = {SW_UDIV(prod, -dy_r), 0}; + prod += px; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * (f1.x + f2.x); + f1 = {f2.x, ONE_PIXEL}; + --e1.y; + } + _setCell(rw, e1); - } while(e1.y != e2.y); + + } while(e1 != e2); } - //any other line - } else { - Area prod = diff.x * f1.y - diff.y * f1.x; - - /* These macros speed up repetitive divisions by replacing them - with multiplications and right shifts. */ - auto dx_r = static_cast(ULONG_MAX >> PIXEL_BITS) / (diff.x); - auto dy_r = static_cast(ULONG_MAX >> PIXEL_BITS) / (diff.y); - - /* The fundamental value `prod' determines which side and the */ - /* exact coordinate where the line exits current cell. It is */ - /* also easily updated when moving from one cell to the next. */ - size_t idx = 0; - do { - if (++idx > 1000000000) { - TVGERR("SW_ENGINE", "Iteration limit reached during RLE generation. The resulting render may be incorrect."); - break; - } - auto px = diff.x * ONE_PIXEL; - auto py = diff.y * ONE_PIXEL; - - //left - if (prod <= 0 && prod - px > 0) { - f2 = {0, SW_UDIV(-prod, -dx_r)}; - prod -= py; - rw.cover += (f2.y - f1.y); - rw.area += (f2.y - f1.y) * (f1.x + f2.x); - f1 = {ONE_PIXEL, f2.y}; - --e1.x; - //up - } else if (prod - px <= 0 && prod - px + py > 0) { - prod -= px; - f2 = {SW_UDIV(-prod, dy_r), ONE_PIXEL}; - rw.cover += (f2.y - f1.y); - rw.area += (f2.y - f1.y) * (f1.x + f2.x); - f1 = {f2.x, 0}; - ++e1.y; - //right - } else if (prod - px + py <= 0 && prod + py >= 0) { - prod += py; - f2 = {ONE_PIXEL, SW_UDIV(prod, dx_r)}; - rw.cover += (f2.y - f1.y); - rw.area += (f2.y - f1.y) * (f1.x + f2.x); - f1 = {0, f2.y}; - ++e1.x; - //down - } else { - f2 = {SW_UDIV(prod, -dy_r), 0}; - prod += px; - rw.cover += (f2.y - f1.y); - rw.area += (f2.y - f1.y) * (f1.x + f2.x); - f1 = {f2.x, ONE_PIXEL}; - --e1.y; - } - _setCell(rw, e1); + f2 = {line[0].x - SUBPIXELS(e2.x), line[0].y - SUBPIXELS(e2.y)}; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * (f1.x + f2.x); + rw.pos = line[0]; - } while(e1 != e2); + if (line-- == rw.lineStack) return; } - - f2 = {to.x - SUBPIXELS(e2.x), to.y - SUBPIXELS(e2.y)}; - rw.cover += (f2.y - f1.y); - rw.area += (f2.y - f1.y) * (f1.x + f2.x); - rw.pos = to; } diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwShape.cpp b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwShape.cpp index fd0548d6e..4408db0b8 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwShape.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/sw_engine/tvgSwShape.cpp @@ -498,7 +498,6 @@ bool shapePrepare(SwShape* shape, const RenderShape* rshape, const Matrix& trans if (!_genOutline(shape, rshape, transform, mpool, tid, hasComposite)) return false; if (!mathUpdateOutlineBBox(shape->outline, clipRegion, renderRegion, shape->fastTrack)) return false; - //Keep it for Rasterization Region shape->bbox = renderRegion; //Check valid region diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgAccessor.cpp b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgAccessor.cpp index a14472680..076ffab33 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgAccessor.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgAccessor.cpp @@ -50,20 +50,6 @@ static bool accessChildren(Iterator* it, function Accessor::set(unique_ptr picture, function func) noexcept -{ - auto backward = [](const tvg::Paint* paint, void* data) -> bool - { - auto func = reinterpret_cast*>(data); - if (!(*func)(paint)) return false; - return true; - }; - - set(picture.get(), backward, reinterpret_cast(&func)); - return picture; -} - - Result Accessor::set(const Picture* picture, function func, void* data) noexcept { if (!picture || !func) return Result::InvalidArguments; diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgBinaryDesc.h b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgBinaryDesc.h deleted file mode 100644 index e40859b6d..000000000 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgBinaryDesc.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef _TVG_BINARY_DESC_H_ -#define _TVG_BINARY_DESC_H_ - -/* TODO: Need to consider whether uin8_t is enough size for extension... - Rather than optimal data, we can use enough size and data compress? */ - -using TvgBinByte = uint8_t; -using TvgBinCounter = uint32_t; -using TvgBinTag = TvgBinByte; -using TvgBinFlag = TvgBinByte; - - -//Header -#define TVG_HEADER_SIZE 33 //TVG_HEADER_SIGNATURE_LENGTH + TVG_HEADER_VERSION_LENGTH + 2*SIZE(float) + TVG_HEADER_RESERVED_LENGTH + TVG_HEADER_COMPRESS_SIZE -#define TVG_HEADER_SIGNATURE "ThorVG" -#define TVG_HEADER_SIGNATURE_LENGTH 6 -#define TVG_HEADER_VERSION "001500" //Major 00, Minor 15, Micro 00 -#define TVG_HEADER_VERSION_LENGTH 6 -#define TVG_HEADER_RESERVED_LENGTH 1 //Storing flags for extensions -#define TVG_HEADER_COMPRESS_SIZE 12 //TVG_HEADER_UNCOMPRESSED_SIZE + TVG_HEADER_COMPRESSED_SIZE + TVG_HEADER_COMPRESSED_SIZE_BITS -//Compress Size -#define TVG_HEADER_UNCOMPRESSED_SIZE 4 //SIZE (TvgBinCounter) -#define TVG_HEADER_COMPRESSED_SIZE 4 //SIZE (TvgBinCounter) -#define TVG_HEADER_COMPRESSED_SIZE_BITS 4 //SIZE (TvgBinCounter) -//Reserved Flag -#define TVG_HEAD_FLAG_COMPRESSED 0x01 - -//Paint Type -#define TVG_TAG_CLASS_PICTURE (TvgBinTag)0xfc -#define TVG_TAG_CLASS_SHAPE (TvgBinTag)0xfd -#define TVG_TAG_CLASS_SCENE (TvgBinTag)0xfe - - -//Paint -#define TVG_TAG_PAINT_OPACITY (TvgBinTag)0x10 -#define TVG_TAG_PAINT_TRANSFORM (TvgBinTag)0x11 -#define TVG_TAG_PAINT_CMP_TARGET (TvgBinTag)0x01 -#define TVG_TAG_PAINT_CMP_METHOD (TvgBinTag)0x20 - - -//TODO: Keep this for the compatibility, Remove in TVG 1.0 release -//Scene - #define TVG_TAG_SCENE_RESERVEDCNT (TvgBinTag)0x30 - - -//Shape -#define TVG_TAG_SHAPE_PATH (TvgBinTag)0x40 -#define TVG_TAG_SHAPE_STROKE (TvgBinTag)0x41 -#define TVG_TAG_SHAPE_FILL (TvgBinTag)0x42 -#define TVG_TAG_SHAPE_COLOR (TvgBinTag)0x43 -#define TVG_TAG_SHAPE_FILLRULE (TvgBinTag)0x44 - - -//Stroke -#define TVG_TAG_SHAPE_STROKE_CAP (TvgBinTag)0x50 -#define TVG_TAG_SHAPE_STROKE_JOIN (TvgBinTag)0x51 -#define TVG_TAG_SHAPE_STROKE_WIDTH (TvgBinTag)0x52 -#define TVG_TAG_SHAPE_STROKE_COLOR (TvgBinTag)0x53 -#define TVG_TAG_SHAPE_STROKE_FILL (TvgBinTag)0x54 -#define TVG_TAG_SHAPE_STROKE_DASHPTRN (TvgBinTag)0x55 -#define TVG_TAG_SHAPE_STROKE_MITERLIMIT (TvgBinTag)0x56 -#define TVG_TAG_SHAPE_STROKE_ORDER (TvgBinTag)0x57 -#define TVG_TAG_SHAPE_STROKE_DASH_OFFSET (TvgBinTag)0x58 - - -//Fill -#define TVG_TAG_FILL_LINEAR_GRADIENT (TvgBinTag)0x60 -#define TVG_TAG_FILL_RADIAL_GRADIENT (TvgBinTag)0x61 -#define TVG_TAG_FILL_COLORSTOPS (TvgBinTag)0x62 -#define TVG_TAG_FILL_FILLSPREAD (TvgBinTag)0x63 -#define TVG_TAG_FILL_TRANSFORM (TvgBinTag)0x64 -#define TVG_TAG_FILL_RADIAL_GRADIENT_FOCAL (TvgBinTag)0x65 - -//Picture -#define TVG_TAG_PICTURE_RAW_IMAGE (TvgBinTag)0x70 -#define TVG_TAG_PICTURE_MESH (TvgBinTag)0x71 - -#endif //_TVG_BINARY_DESC_H_ diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgCanvas.cpp b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgCanvas.cpp index 1a36e41e7..a87b8ce6a 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgCanvas.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgCanvas.cpp @@ -37,12 +37,6 @@ Canvas::~Canvas() } -Result Canvas::reserve(TVG_UNUSED uint32_t n) noexcept -{ - return Result::NonSupport; -} - - list& Canvas::paints() noexcept { return pImpl->paints; @@ -55,9 +49,9 @@ Result Canvas::push(unique_ptr paint) noexcept } -Result Canvas::clear(bool free) noexcept +Result Canvas::clear(bool paints, bool buffer) noexcept { - return pImpl->clear(free); + return pImpl->clear(paints, buffer); } diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgCanvas.h b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgCanvas.h index c5d2127f9..11519019a 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgCanvas.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgCanvas.h @@ -44,7 +44,6 @@ struct Canvas::Impl { //make it sure any deferred jobs renderer->sync(); - renderer->clear(); clearPaints(); @@ -72,15 +71,16 @@ struct Canvas::Impl return update(p, true); } - Result clear(bool free) + Result clear(bool paints, bool buffer) { - //Clear render target before drawing - if (!renderer->clear()) return Result::InsufficientCondition; + if (status == Status::Drawing) return Result::InsufficientCondition; - //Free paints - if (free) clearPaints(); + //Clear render target + if (buffer) { + if (!renderer->clear()) return Result::InsufficientCondition; + } - status = Status::Synced; + if (paints) clearPaints(); return Result::Success; } diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgCommon.h b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgCommon.h index 527221625..75e26b3a7 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgCommon.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgCommon.h @@ -23,6 +23,7 @@ #ifndef _TVG_COMMON_H_ #define _TVG_COMMON_H_ +#include #include "config.h" #include "thorvg.h" @@ -54,7 +55,7 @@ using namespace tvg; #define strdup _strdup #endif -enum class FileType { Png = 0, Jpg, Webp, Tvg, Svg, Lottie, Ttf, Raw, Gif, Unknown }; +enum class FileType { Png = 0, Jpg, Webp, Svg, Lottie, Ttf, Raw, Gif, Unknown }; using Size = Point; diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgFill.cpp b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgFill.cpp index 19edff5a2..394f52413 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgFill.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgFill.cpp @@ -134,18 +134,14 @@ FillSpread Fill::spread() const noexcept Result Fill::transform(const Matrix& m) noexcept { - if (!pImpl->transform) { - pImpl->transform = static_cast(malloc(sizeof(Matrix))); - } - *pImpl->transform = m; + pImpl->transform = m; return Result::Success; } -Matrix Fill::transform() const noexcept +Matrix& Fill::transform() const noexcept { - if (pImpl->transform) return *pImpl->transform; - return {1, 0, 0, 0, 1, 0, 0, 0, 1}; + return pImpl->transform; } @@ -155,12 +151,6 @@ Fill* Fill::duplicate() const noexcept } -TVG_DEPRECATED uint32_t Fill::identifier() const noexcept -{ - return (uint32_t) type(); -} - - RadialGradient::RadialGradient():pImpl(new Impl()) { Fill::pImpl->method(new FillDup(pImpl)); @@ -173,17 +163,20 @@ RadialGradient::~RadialGradient() } -Result RadialGradient::radial(float cx, float cy, float r) noexcept +Result RadialGradient::radial(float cx, float cy, float r, float fx, float fy, float fr) noexcept { - return pImpl->radial(cx, cy, r, cx, cy, 0.0f); + return pImpl->radial(cx, cy, r, fx, fy, fr); } -Result RadialGradient::radial(float* cx, float* cy, float* r) const noexcept +Result RadialGradient::radial(float* cx, float* cy, float* r, float* fx, float* fy, float* fr) const noexcept { if (cx) *cx = pImpl->cx; if (cy) *cy = pImpl->cy; if (r) *r = pImpl->r; + if (fx) *fx = pImpl->fx; + if (fy) *fy = pImpl->fy; + if (fr) *fr = pImpl->fr; return Result::Success; } @@ -195,12 +188,6 @@ unique_ptr RadialGradient::gen() noexcept } -TVG_DEPRECATED uint32_t RadialGradient::identifier() noexcept -{ - return (uint32_t) Type::RadialGradient; -} - - Type RadialGradient::type() const noexcept { return Type::RadialGradient; @@ -247,12 +234,6 @@ unique_ptr LinearGradient::gen() noexcept } -TVG_DEPRECATED uint32_t LinearGradient::identifier() noexcept -{ - return (uint32_t) Type::LinearGradient; -} - - Type LinearGradient::type() const noexcept { return Type::LinearGradient; diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgFill.h b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgFill.h index f249356aa..f7e4bb830 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgFill.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgFill.h @@ -51,16 +51,15 @@ struct FillDup : DuplicateMethod struct Fill::Impl { ColorStop* colorStops = nullptr; - Matrix* transform = nullptr; + Matrix transform = {1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}; uint32_t cnt = 0; - FillSpread spread; DuplicateMethod* dup = nullptr; + FillSpread spread; ~Impl() { delete(dup); free(colorStops); - free(transform); } void method(DuplicateMethod* dup) @@ -71,16 +70,13 @@ struct Fill::Impl Fill* duplicate() { auto ret = dup->duplicate(); - if (!ret) return nullptr; ret->pImpl->cnt = cnt; ret->pImpl->spread = spread; ret->pImpl->colorStops = static_cast(malloc(sizeof(ColorStop) * cnt)); memcpy(ret->pImpl->colorStops, colorStops, sizeof(ColorStop) * cnt); - if (transform) { - ret->pImpl->transform = static_cast(malloc(sizeof(Matrix))); - *ret->pImpl->transform = *transform; - } + ret->pImpl->transform = transform; + return ret; } }; diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgInitializer.cpp b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgInitializer.cpp index 292044354..8c7ae5efc 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgInitializer.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgInitializer.cpp @@ -86,26 +86,25 @@ static bool _buildVersionInfo(uint32_t* major, uint32_t* minor, uint32_t* micro) /* External Class Implementation */ /************************************************************************/ -Result Initializer::init(CanvasEngine engine, uint32_t threads) noexcept +Result Initializer::init(uint32_t threads, CanvasEngine engine) noexcept { auto nonSupport = true; - if (static_cast(engine) == 0) return Result::InvalidArguments; - if (engine & CanvasEngine::Sw) { + if (engine == CanvasEngine::All || engine & CanvasEngine::Sw) { #ifdef THORVG_SW_RASTER_SUPPORT if (!SwRenderer::init(threads)) return Result::FailedAllocation; nonSupport = false; #endif } - if (engine & CanvasEngine::Gl) { + if (engine == CanvasEngine::All || engine & CanvasEngine::Gl) { #ifdef THORVG_GL_RASTER_SUPPORT if (!GlRenderer::init(threads)) return Result::FailedAllocation; nonSupport = false; #endif } - if (engine & CanvasEngine::Wg) { + if (engine == CanvasEngine::All || engine & CanvasEngine::Wg) { #ifdef THORVG_WG_RASTER_SUPPORT if (!WgRenderer::init(threads)) return Result::FailedAllocation; nonSupport = false; @@ -131,23 +130,22 @@ Result Initializer::term(CanvasEngine engine) noexcept if (_initCnt == 0) return Result::InsufficientCondition; auto nonSupport = true; - if (static_cast(engine) == 0) return Result::InvalidArguments; - if (engine & CanvasEngine::Sw) { + if (engine == CanvasEngine::All || engine & CanvasEngine::Sw) { #ifdef THORVG_SW_RASTER_SUPPORT if (!SwRenderer::term()) return Result::InsufficientCondition; nonSupport = false; #endif } - if (engine & CanvasEngine::Gl) { + if (engine == CanvasEngine::All || engine & CanvasEngine::Gl) { #ifdef THORVG_GL_RASTER_SUPPORT if (!GlRenderer::term()) return Result::InsufficientCondition; nonSupport = false; #endif } - if (engine & CanvasEngine::Wg) { + if (engine == CanvasEngine::All || engine & CanvasEngine::Wg) { #ifdef THORVG_WG_RASTER_SUPPORT if (!WgRenderer::term()) return Result::InsufficientCondition; nonSupport = false; diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgLoadModule.h b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgLoadModule.h index a9c1a6854..9e34b5e09 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgLoadModule.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgLoadModule.h @@ -48,8 +48,8 @@ struct LoadModule if (pathcache) free(hashpath); } - virtual bool open(const string& path) { return false; } - virtual bool open(const char* data, uint32_t size, bool copy) { return false; } + virtual bool open(const string& path, const ColorReplace &colorReplacement) { return false; } + virtual bool open(const char* data, uint32_t size, const string& rpath, bool copy, const ColorReplace &colorReplacement) { return false; } virtual bool resize(Paint* paint, float w, float h) { return false; } virtual void sync() {}; //finish immediately if any async update jobs. diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgLoader.cpp b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgLoader.cpp index cd5019296..1e4ce454c 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgLoader.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgLoader.cpp @@ -23,6 +23,7 @@ #include #include "tvgInlist.h" +#include "tvgStr.h" #include "tvgLoader.h" #include "tvgLock.h" @@ -34,10 +35,6 @@ #include "tvgPngLoader.h" #endif -#ifdef THORVG_TVG_LOADER_SUPPORT - #include "tvgTvgLoader.h" -#endif - #ifdef THORVG_JPG_LOADER_SUPPORT #include "tvgJpgLoader.h" #endif @@ -90,12 +87,6 @@ static LoadModule* _find(FileType type) case FileType::Webp: { #ifdef THORVG_WEBP_LOADER_SUPPORT return new WebpLoader; -#endif - break; - } - case FileType::Tvg: { -#ifdef THORVG_TVG_LOADER_SUPPORT - return new TvgLoader; #endif break; } @@ -129,10 +120,6 @@ static LoadModule* _find(FileType type) #ifdef THORVG_LOG_ENABLED const char *format; switch(type) { - case FileType::Tvg: { - format = "TVG"; - break; - } case FileType::Svg: { format = "SVG"; break; @@ -172,53 +159,55 @@ static LoadModule* _find(FileType type) } -static LoadModule* _findByPath(const string& path) +static LoadModule* _findByPath(const char* filename) { - auto ext = path.substr(path.find_last_of(".") + 1); - if (!ext.compare("tvg")) return _find(FileType::Tvg); - if (!ext.compare("svg")) return _find(FileType::Svg); - if (!ext.compare("json")) return _find(FileType::Lottie); - if (!ext.compare("png")) return _find(FileType::Png); - if (!ext.compare("jpg")) return _find(FileType::Jpg); - if (!ext.compare("webp")) return _find(FileType::Webp); - if (!ext.compare("ttf") || !ext.compare("ttc")) return _find(FileType::Ttf); - if (!ext.compare("otf") || !ext.compare("otc")) return _find(FileType::Ttf); + auto ext = strExtension(filename); + if (!ext) return nullptr; + + if (!strcmp(ext, "svg")) return _find(FileType::Svg); + if (!strcmp(ext, "json")) return _find(FileType::Lottie); + if (!strcmp(ext, "png")) return _find(FileType::Png); + if (!strcmp(ext, "jpg")) return _find(FileType::Jpg); + if (!strcmp(ext, "webp")) return _find(FileType::Webp); + if (!strcmp(ext, "ttf") || !strcmp(ext, "ttc")) return _find(FileType::Ttf); + if (!strcmp(ext, "otf") || !strcmp(ext, "otc")) return _find(FileType::Ttf); return nullptr; } -static FileType _convert(const string& mimeType) +static FileType _convert(const char* mimeType) { + if (!mimeType) return FileType::Unknown; + auto type = FileType::Unknown; - if (mimeType == "tvg") type = FileType::Tvg; - else if (mimeType == "svg" || mimeType == "svg+xml") type = FileType::Svg; - else if (mimeType == "ttf" || mimeType == "otf") type = FileType::Ttf; - else if (mimeType == "lottie") type = FileType::Lottie; - else if (mimeType == "raw") type = FileType::Raw; - else if (mimeType == "png") type = FileType::Png; - else if (mimeType == "jpg" || mimeType == "jpeg") type = FileType::Jpg; - else if (mimeType == "webp") type = FileType::Webp; - else TVGLOG("RENDERER", "Given mimetype is unknown = \"%s\".", mimeType.c_str()); + if (!strcmp(mimeType, "svg") || !strcmp(mimeType, "svg+xml")) type = FileType::Svg; + else if (!strcmp(mimeType, "ttf") || !strcmp(mimeType, "otf")) type = FileType::Ttf; + else if (!strcmp(mimeType, "lottie")) type = FileType::Lottie; + else if (!strcmp(mimeType, "raw")) type = FileType::Raw; + else if (!strcmp(mimeType, "png")) type = FileType::Png; + else if (!strcmp(mimeType, "jpg") || !strcmp(mimeType, "jpeg")) type = FileType::Jpg; + else if (!strcmp(mimeType, "webp")) type = FileType::Webp; + else TVGLOG("RENDERER", "Given mimetype is unknown = \"%s\".", mimeType); return type; } -static LoadModule* _findByType(const string& mimeType) +static LoadModule* _findByType(const char* mimeType) { return _find(_convert(mimeType)); } -static LoadModule* _findFromCache(const string& path) +static LoadModule* _findFromCache(const char* filename) { ScopedLock lock(key); auto loader = _activeLoaders.head; while (loader) { - if (loader->pathcache && !strcmp(loader->hashpath, path.c_str())) { + if (loader->pathcache && !strcmp(loader->hashpath, filename)) { ++loader->sharing; return loader; } @@ -228,7 +217,7 @@ static LoadModule* _findFromCache(const string& path) } -static LoadModule* _findFromCache(const char* data, uint32_t size, const string& mimeType) +static LoadModule* _findFromCache(const char* data, uint32_t size, const char* mimeType) { auto type = _convert(mimeType); if (type == FileType::Unknown) return nullptr; @@ -290,23 +279,25 @@ bool LoaderMgr::retrieve(LoadModule* loader) } -LoadModule* LoaderMgr::loader(const string& path, bool* invalid) +LoadModule* LoaderMgr::loader(const char* filename, bool* invalid, std::unique_ptr colorReplacement) { *invalid = false; - //TODO: lottie is not sharable. + //TODO: svg & lottie is not sharable. auto allowCache = true; - auto ext = path.substr(path.find_last_of(".") + 1); - if (!ext.compare("json")) allowCache = false; + auto ext = strExtension(filename); + if (ext && (!strcmp(ext, "svg") || !strcmp(ext, "json"))) allowCache = false; if (allowCache) { - if (auto loader = _findFromCache(path)) return loader; + if (auto loader = _findFromCache(filename)) return loader; } - if (auto loader = _findByPath(path)) { - if (loader->open(path)) { + auto colorReplacementP = (colorReplacement == nullptr ? ColorReplace() : *colorReplacement); + + if (auto loader = _findByPath(filename)) { + if (loader->open(filename, colorReplacementP)) { if (allowCache) { - loader->hashpath = strdup(path.c_str()); + loader->hashpath = strdup(filename); loader->pathcache = true; { ScopedLock lock(key); @@ -320,9 +311,9 @@ LoadModule* LoaderMgr::loader(const string& path, bool* invalid) //Unknown MimeType. Try with the candidates in the order for (int i = 0; i < static_cast(FileType::Raw); i++) { if (auto loader = _find(static_cast(i))) { - if (loader->open(path)) { + if (loader->open(filename, colorReplacementP)) { if (allowCache) { - loader->hashpath = strdup(path.c_str()); + loader->hashpath = strdup(filename); loader->pathcache = true; { ScopedLock lock(key); @@ -339,13 +330,13 @@ LoadModule* LoaderMgr::loader(const string& path, bool* invalid) } -bool LoaderMgr::retrieve(const string& path) +bool LoaderMgr::retrieve(const char* filename) { - return retrieve(_findFromCache(path)); + return retrieve(_findFromCache(filename)); } -LoadModule* LoaderMgr::loader(const char* key) +LoadModule* LoaderMgr::loader(const char* key, std::unique_ptr colorReplacement) { auto loader = _activeLoaders.head; @@ -360,7 +351,7 @@ LoadModule* LoaderMgr::loader(const char* key) } -LoadModule* LoaderMgr::loader(const char* data, uint32_t size, const string& mimeType, bool copy) +LoadModule* LoaderMgr::loader(const char* data, uint32_t size, const char* mimeType, const char* rpath, bool copy, std::unique_ptr colorReplacement) { //Note that users could use the same data pointer with the different content. //Thus caching is only valid for shareable. @@ -376,10 +367,12 @@ LoadModule* LoaderMgr::loader(const char* data, uint32_t size, const string& mim if (auto loader = _findFromCache(data, size, mimeType)) return loader; } + auto colorReplacementP = (colorReplacement == nullptr ? ColorReplace() : *colorReplacement); + //Try with the given MimeType - if (!mimeType.empty()) { + if (!mimeType) { if (auto loader = _findByType(mimeType)) { - if (loader->open(data, size, copy)) { + if (loader->open(data, size, rpath, copy, colorReplacementP)) { if (allowCache) { loader->hashkey = HASH_KEY(data); ScopedLock lock(key); @@ -387,7 +380,7 @@ LoadModule* LoaderMgr::loader(const char* data, uint32_t size, const string& mim } return loader; } else { - TVGLOG("LOADER", "Given mimetype \"%s\" seems incorrect or not supported.", mimeType.c_str()); + TVGLOG("LOADER", "Given mimetype \"%s\" seems incorrect or not supported.", mimeType); delete(loader); } } @@ -396,7 +389,7 @@ LoadModule* LoaderMgr::loader(const char* data, uint32_t size, const string& mim for (int i = 0; i < static_cast(FileType::Raw); i++) { auto loader = _find(static_cast(i)); if (loader) { - if (loader->open(data, size, copy)) { + if (loader->open(data, size, rpath, copy, colorReplacementP)) { if (allowCache) { loader->hashkey = HASH_KEY(data); ScopedLock lock(key); @@ -411,7 +404,7 @@ LoadModule* LoaderMgr::loader(const char* data, uint32_t size, const string& mim } -LoadModule* LoaderMgr::loader(const uint32_t *data, uint32_t w, uint32_t h, bool copy) +LoadModule* LoaderMgr::loader(const uint32_t *data, uint32_t w, uint32_t h, ColorSpace cs, bool copy) { //Note that users could use the same data pointer with the different content. //Thus caching is only valid for shareable. @@ -422,7 +415,7 @@ LoadModule* LoaderMgr::loader(const uint32_t *data, uint32_t w, uint32_t h, bool //function is dedicated for raw images only auto loader = new RawLoader; - if (loader->open(data, w, h, copy)) { + if (loader->open(data, w, h, cs, copy)) { if (!copy) { loader->hashkey = HASH_KEY((const char*)data); ScopedLock lock(key); @@ -436,7 +429,7 @@ LoadModule* LoaderMgr::loader(const uint32_t *data, uint32_t w, uint32_t h, bool //loads fonts from memory - loader is cached (regardless of copy value) in order to access it while setting font -LoadModule* LoaderMgr::loader(const char* name, const char* data, uint32_t size, TVG_UNUSED const string& mimeType, bool copy) +LoadModule* LoaderMgr::loader(const char* name, const char* data, uint32_t size, TVG_UNUSED const char* mimeType, bool copy, std::unique_ptr colorReplacement) { #ifdef THORVG_TTF_LOADER_SUPPORT //TODO: add check for mimetype ? @@ -444,7 +437,7 @@ LoadModule* LoaderMgr::loader(const char* name, const char* data, uint32_t size, //function is dedicated for ttf loader (the only supported font loader) auto loader = new TtfLoader; - if (loader->open(data, size, copy)) { + if (loader->open(data, size, "", copy, std::move(colorReplacement))) { loader->hashpath = strdup(name); loader->pathcache = true; ScopedLock lock(key); diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgLoader.h b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgLoader.h index 74c4f4396..fd70b0d08 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgLoader.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgLoader.h @@ -29,12 +29,12 @@ struct LoaderMgr { static bool init(); static bool term(); - static LoadModule* loader(const string& path, bool* invalid); - static LoadModule* loader(const char* data, uint32_t size, const string& mimeType, bool copy); - static LoadModule* loader(const uint32_t* data, uint32_t w, uint32_t h, bool copy); - static LoadModule* loader(const char* name, const char* data, uint32_t size, const string& mimeType, bool copy); - static LoadModule* loader(const char* key); - static bool retrieve(const string& path); + static LoadModule* loader(const char* filename, bool* invalid, std::unique_ptr colorReplacement); + static LoadModule* loader(const char* data, uint32_t size, const char* mimeType, const char* rpath, bool copy, std::unique_ptr colorReplacement); + static LoadModule* loader(const uint32_t* data, uint32_t w, uint32_t h, ColorSpace cs, bool copy); + static LoadModule* loader(const char* name, const char* data, uint32_t size, const char* mimeType, bool copy, std::unique_ptr colorReplacement); + static LoadModule* loader(const char* key, std::unique_ptr colorReplacement); + static bool retrieve(const char* filename); static bool retrieve(LoadModule* loader); }; diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgPaint.cpp b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgPaint.cpp index d9a57241a..94838f300 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgPaint.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgPaint.cpp @@ -86,7 +86,7 @@ static Result _compFastTrack(RenderMethod* renderer, Paint* cmpTarget, const Mat if (ptsCnt == 0) return Result::InvalidArguments; if (ptsCnt != 4) return Result::InsufficientCondition; - auto& rm = P(cmpTarget)->transform(); + auto& rm = cmpTarget->transform(); //No rotation and no skewing, still can try out clipping the rect region. auto tryClip = false; @@ -153,7 +153,7 @@ Iterator* Paint::Impl::iterator() Paint* Paint::Impl::duplicate(Paint* ret) { - if (ret) ret->composite(nullptr, CompositeMethod::None); + if (ret) ret->mask(nullptr, MaskMethod::None); PAINT_METHOD(ret, duplicate(ret)); @@ -163,7 +163,7 @@ Paint* Paint::Impl::duplicate(Paint* ret) ret->pImpl->opacity = opacity; - if (compData) ret->pImpl->composite(ret, compData->target->duplicate(), compData->method); + if (maskData) ret->pImpl->mask(ret, maskData->target->duplicate(), maskData->method); if (clipper) ret->pImpl->clip(clipper->duplicate()); return ret; @@ -210,19 +210,19 @@ bool Paint::Impl::render(RenderMethod* renderer) RenderCompositor* cmp = nullptr; - if (compData && !(compData->target->pImpl->ctxFlag & ContextFlag::FastTrack)) { + if (maskData && !(maskData->target->pImpl->ctxFlag & ContextFlag::FastTrack)) { RenderRegion region; PAINT_METHOD(region, bounds(renderer)); - if (MASK_REGION_MERGING(compData->method)) region.add(P(compData->target)->bounds(renderer)); + if (MASK_REGION_MERGING(maskData->method)) region.add(P(maskData->target)->bounds(renderer)); if (region.w == 0 || region.h == 0) return true; - cmp = renderer->target(region, COMPOSITE_TO_COLORSPACE(renderer, compData->method)); - if (renderer->beginComposite(cmp, CompositeMethod::None, 255)) { - compData->target->pImpl->render(renderer); + cmp = renderer->target(region, MASK_TO_COLORSPACE(renderer, maskData->method)); + if (renderer->beginComposite(cmp, MaskMethod::None, 255)) { + maskData->target->pImpl->render(renderer); } } - if (cmp) renderer->beginComposite(cmp, compData->method, compData->target->pImpl->opacity); + if (cmp) renderer->beginComposite(cmp, maskData->method, maskData->target->pImpl->opacity); bool ret; PAINT_METHOD(ret, render(renderer)); @@ -248,20 +248,20 @@ RenderData Paint::Impl::update(RenderMethod* renderer, const Matrix& pm, Arraytarget; - auto method = compData->method; + if (maskData) { + auto target = maskData->target; + auto method = maskData->method; P(target)->ctxFlag &= ~ContextFlag::FastTrack; //reset - /* If the transformation has no rotational factors and the Alpha(InvAlpha)Masking involves a simple rectangle, - we can optimize by using the viewport instead of the regular AlphaMasking sequence for improved performance. */ + /* If the transformation has no rotational factors and the Alpha(InvAlpha) Masking involves a simple rectangle, + we can optimize by using the viewport instead of the regular Alphaing sequence for improved performance. */ if (target->type() == Type::Shape) { auto shape = static_cast(target); uint8_t a; shape->fillColor(nullptr, nullptr, nullptr, &a); - //no gradient fill & no compositions of the composition target. - if (!shape->fill() && !(PP(shape)->compData)) { - if ((method == CompositeMethod::AlphaMask && a == 255 && PP(shape)->opacity == 255) || (method == CompositeMethod::InvAlphaMask && (a == 0 || PP(shape)->opacity == 0))) { + //no gradient fill & no maskings of the masking target. + if (!shape->fill() && !(PP(shape)->maskData)) { + if ((method == MaskMethod::Alpha && a == 255 && PP(shape)->opacity == 255) || (method == MaskMethod::InvAlpha && (a == 0 || PP(shape)->opacity == 0))) { viewport = renderer->viewport(); if ((compFastTrack = _compFastTrack(renderer, target, pm, viewport)) == Result::Success) { P(target)->ctxFlag |= ContextFlag::FastTrack; @@ -361,10 +361,10 @@ void Paint::Impl::reset() clipper = nullptr; } - if (compData) { - if (P(compData->target)->unref() == 0) delete(compData->target); - free(compData); - compData = nullptr; + if (maskData) { + if (P(maskData->target)->unref() == 0) delete(maskData->target); + free(maskData); + maskData = nullptr; } tvg::identity(&tr.m); @@ -423,18 +423,12 @@ Result Paint::transform(const Matrix& m) noexcept } -Matrix Paint::transform() noexcept +Matrix& Paint::transform() noexcept { return pImpl->transform(); } -TVG_DEPRECATED Result Paint::bounds(float* x, float* y, float* w, float* h) const noexcept -{ - return this->bounds(x, y, w, h, false); -} - - Result Paint::bounds(float* x, float* y, float* w, float* h, bool transformed) const noexcept { if (pImpl->bounds(x, y, w, h, transformed, true, transformed)) return Result::Success; @@ -461,32 +455,24 @@ Result Paint::clip(std::unique_ptr clipper) noexcept } -Result Paint::composite(std::unique_ptr target, CompositeMethod method) noexcept +Result Paint::mask(std::unique_ptr target, MaskMethod method) noexcept { - //TODO: remove. Keep this for the backward compatibility - if (target && method == CompositeMethod::ClipPath) return clip(std::move(target)); - auto p = target.release(); - if (pImpl->composite(this, p, method)) return Result::Success; + if (pImpl->mask(this, p, method)) return Result::Success; delete(p); return Result::InvalidArguments; } -CompositeMethod Paint::composite(const Paint** target) const noexcept +MaskMethod Paint::mask(const Paint** target) const noexcept { - if (pImpl->compData) { - if (target) *target = pImpl->compData->target; - return pImpl->compData->method; + if (pImpl->maskData) { + if (target) *target = pImpl->maskData->target; + return pImpl->maskData->method; } else { - //TODO: remove. Keep this for the backward compatibility - if (pImpl->clipper) { - if (target) *target = pImpl->clipper; - return CompositeMethod::ClipPath; - } if (target) *target = nullptr; - return CompositeMethod::None; + return MaskMethod::None; } } @@ -508,12 +494,6 @@ uint8_t Paint::opacity() const noexcept } -TVG_DEPRECATED uint32_t Paint::identifier() const noexcept -{ - return (uint32_t) type(); -} - - Result Paint::blend(BlendMethod method) noexcept { //TODO: Remove later diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgPaint.h b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgPaint.h index 552d46897..95124fb98 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgPaint.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgPaint.h @@ -38,17 +38,17 @@ namespace tvg virtual void begin() = 0; }; - struct Composite + struct Mask { Paint* target; Paint* source; - CompositeMethod method; + MaskMethod method; }; struct Paint::Impl { Paint* paint = nullptr; - Composite* compData = nullptr; + Mask* maskData = nullptr; Paint* clipper = nullptr; RenderMethod* renderer = nullptr; struct { @@ -85,9 +85,9 @@ namespace tvg ~Impl() { - if (compData) { - if (P(compData->target)->unref() == 0) delete(compData->target); - free(compData); + if (maskData) { + if (P(maskData->target)->unref() == 0) delete(maskData->target); + free(maskData); } if (clipper && P(clipper)->unref() == 0) delete(clipper); if (renderer && (renderer->unref() == 0)) delete(renderer); @@ -107,7 +107,7 @@ namespace tvg bool transform(const Matrix& m) { - tr.m = m; + if (&tr.m != &m) tr.m = m; tr.overriding = true; renderFlag |= RenderUpdateFlag::Transform; @@ -122,44 +122,44 @@ namespace tvg return tr.m; } - void clip(Paint* clipper) + void clip(Paint* clp) { if (this->clipper) { P(this->clipper)->unref(); - if (this->clipper != clipper && P(this->clipper)->refCnt == 0) { + if (this->clipper != clp && P(this->clipper)->refCnt == 0) { delete(this->clipper); } } - this->clipper = clipper; - if (!clipper) return; + this->clipper = clp; + if (!clp) return; P(clipper)->ref(); } - bool composite(Paint* source, Paint* target, CompositeMethod method) + bool mask(Paint* source, Paint* target, MaskMethod method) { //Invalid case - if ((!target && method != CompositeMethod::None) || (target && method == CompositeMethod::None)) return false; + if ((!target && method != MaskMethod::None) || (target && method == MaskMethod::None)) return false; - if (compData) { - P(compData->target)->unref(); - if ((compData->target != target) && P(compData->target)->refCnt == 0) { - delete(compData->target); + if (maskData) { + P(maskData->target)->unref(); + if ((maskData->target != target) && P(maskData->target)->refCnt == 0) { + delete(maskData->target); } //Reset scenario - if (!target && method == CompositeMethod::None) { - free(compData); - compData = nullptr; + if (!target && method == MaskMethod::None) { + free(maskData); + maskData = nullptr; return true; } } else { - if (!target && method == CompositeMethod::None) return true; - compData = static_cast(calloc(1, sizeof(Composite))); + if (!target && method == MaskMethod::None) return true; + maskData = static_cast(malloc(sizeof(Mask))); } P(target)->ref(); - compData->target = target; - compData->source = source; - compData->method = method; + maskData->target = target; + maskData->source = source; + maskData->method = method; return true; } diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgPicture.cpp b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgPicture.cpp index 06f802076..fdceb66f4 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgPicture.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgPicture.cpp @@ -63,10 +63,8 @@ bool Picture::Impl::needComposition(uint8_t opacity) //Composition test const Paint* target; - auto method = picture->composite(&target); - if (!target || method == tvg::CompositeMethod::ClipPath) return false; - if (target->pImpl->opacity == 255 || target->pImpl->opacity == 0) return false; - + picture->mask(&target); + if (!target || target->pImpl->opacity == 255 || target->pImpl->opacity == 0) return false; return true; } @@ -81,7 +79,7 @@ bool Picture::Impl::render(RenderMethod* renderer) RenderCompositor* cmp = nullptr; if (needComp) { cmp = renderer->target(bounds(renderer), renderer->colorSpace()); - renderer->beginComposite(cmp, CompositeMethod::None, 255); + renderer->beginComposite(cmp, MaskMethod::None, 255); } ret = paint->pImpl->render(renderer); if (cmp) renderer->endComposite(cmp); @@ -150,45 +148,33 @@ unique_ptr Picture::gen() noexcept } -TVG_DEPRECATED uint32_t Picture::identifier() noexcept -{ - return (uint32_t) Type::Picture; -} - - Type Picture::type() const noexcept { return Type::Picture; } -Result Picture::load(const std::string& path) noexcept +Result Picture::load(const char* filename, std::unique_ptr colorReplacement) noexcept { - if (path.empty()) return Result::InvalidArguments; + if (!filename) return Result::InvalidArguments; - return pImpl->load(path); + return pImpl->load(filename, std::move(colorReplacement)); } -Result Picture::load(const char* data, uint32_t size, const string& mimeType, bool copy) noexcept +Result Picture::load(const char* data, uint32_t size, const char* mimeType, const char* rpath, bool copy, std::unique_ptr colorReplacement) noexcept { if (!data || size <= 0) return Result::InvalidArguments; - return pImpl->load(data, size, mimeType, copy); -} - - -TVG_DEPRECATED Result Picture::load(const char* data, uint32_t size, bool copy) noexcept -{ - return load(data, size, "", copy); + return pImpl->load(data, size, mimeType, rpath, copy, std::move(colorReplacement)); } -Result Picture::load(uint32_t* data, uint32_t w, uint32_t h, bool copy) noexcept +Result Picture::load(uint32_t* data, uint32_t w, uint32_t h, ColorSpace cs, bool copy) noexcept { - if (!data || w <= 0 || h <= 0) return Result::InvalidArguments; + if (!data || w <= 0 || h <= 0 || cs == ColorSpace::Unknown) return Result::InvalidArguments; - return pImpl->load(data, w, h, copy); + return pImpl->load(data, w, h, cs, copy); } diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgPicture.h b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgPicture.h index bbbc43910..6a450576f 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgPicture.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgPicture.h @@ -122,12 +122,12 @@ struct Picture::Impl return true; } - Result load(const string& path) + Result load(const char* filename, std::unique_ptr colorReplacement) { if (paint || surface) return Result::InsufficientCondition; bool invalid; //Invalid Path - auto loader = static_cast(LoaderMgr::loader(path, &invalid)); + auto loader = static_cast(LoaderMgr::loader(filename, &invalid, std::move(colorReplacement))); if (!loader) { if (invalid) return Result::InvalidArguments; return Result::NonSupport; @@ -135,19 +135,19 @@ struct Picture::Impl return load(loader); } - Result load(const char* data, uint32_t size, const string& mimeType, bool copy) + Result load(const char* data, uint32_t size, const char* mimeType, const char* rpath, bool copy, std::unique_ptr colorReplacement) { if (paint || surface) return Result::InsufficientCondition; - auto loader = static_cast(LoaderMgr::loader(data, size, mimeType, copy)); + auto loader = static_cast(LoaderMgr::loader(data, size, mimeType, rpath, copy, std::move(colorReplacement))); if (!loader) return Result::NonSupport; return load(loader); } - Result load(uint32_t* data, uint32_t w, uint32_t h, bool copy) + Result load(uint32_t* data, uint32_t w, uint32_t h, ColorSpace cs, bool copy) { if (paint || surface) return Result::InsufficientCondition; - auto loader = static_cast(LoaderMgr::loader(data, w, h, copy)); + auto loader = static_cast(LoaderMgr::loader(data, w, h, cs, copy)); if (!loader) return Result::FailedAllocation; return load(loader); diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgRender.h b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgRender.h index 030e78225..bc0e56fb8 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgRender.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgRender.h @@ -37,17 +37,6 @@ using pixel_t = uint32_t; enum RenderUpdateFlag : uint8_t {None = 0, Path = 1, Color = 2, Gradient = 4, Stroke = 8, Transform = 16, Image = 32, GradientStroke = 64, Blend = 128, All = 255}; -//TODO: Move this in public header unifying with SwCanvas::Colorspace -enum ColorSpace : uint8_t -{ - ABGR8888 = 0, //The channels are joined in the order: alpha, blue, green, red. Colors are alpha-premultiplied. - ARGB8888, //The channels are joined in the order: alpha, red, green, blue. Colors are alpha-premultiplied. - ABGR8888S, //The channels are joined in the order: alpha, blue, green, red. Colors are un-alpha-premultiplied. - ARGB8888S, //The channels are joined in the order: alpha, red, green, blue. Colors are un-alpha-premultiplied. - Grayscale8, //One single channel data. - Unsupported //TODO: Change to the default, At the moment, we put it in the last to align with SwCanvas::Colorspace. -}; - struct RenderSurface { union { @@ -58,7 +47,7 @@ struct RenderSurface Key key; //a reserved lock for the thread safety uint32_t stride = 0; uint32_t w = 0, h = 0; - ColorSpace cs = ColorSpace::Unsupported; + ColorSpace cs = ColorSpace::Unknown; uint8_t channelSize = 0; bool premultiplied = false; //Alpha-premultiplied @@ -76,13 +65,11 @@ struct RenderSurface channelSize = rhs->channelSize; premultiplied = rhs->premultiplied; } - - }; struct RenderCompositor { - CompositeMethod method; + MaskMethod method; uint8_t opacity; }; @@ -108,9 +95,9 @@ struct RenderStroke float* dashPattern = nullptr; uint32_t dashCnt = 0; float dashOffset = 0.0f; + float miterlimit = 4.0f; StrokeCap cap = StrokeCap::Square; StrokeJoin join = StrokeJoin::Bevel; - float miterlimit = 4.0f; bool strokeFirst = false; struct { @@ -186,8 +173,8 @@ struct RenderShape } path; Fill *fill = nullptr; - RenderStroke *stroke = nullptr; uint8_t color[4] = {0, 0, 0, 0}; //r, g, b, a + RenderStroke *stroke = nullptr; FillRule rule = FillRule::Winding; ~RenderShape() @@ -218,7 +205,7 @@ struct RenderShape return true; } - bool strokeColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const + bool strokeFill(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const { if (!stroke) return false; @@ -276,20 +263,17 @@ struct RenderEffect } }; -struct RenderEffectGaussian : RenderEffect +struct RenderEffectGaussianBlur : RenderEffect { float sigma; uint8_t direction; //0: both, 1: horizontal, 2: vertical uint8_t border; //0: duplicate, 1: wrap uint8_t quality; //0 ~ 100 (optional) - static RenderEffectGaussian* gen(va_list& args) + static RenderEffectGaussianBlur* gen(va_list& args) { - auto sigma = (float) va_arg(args, double); - if (sigma <= 0) return nullptr; - - auto inst = new RenderEffectGaussian; - inst->sigma = sigma; + auto inst = new RenderEffectGaussianBlur; + inst->sigma = std::max((float) va_arg(args, double), 0.0f); inst->direction = std::min(va_arg(args, int), 2); inst->border = std::min(va_arg(args, int), 1); inst->quality = std::min(va_arg(args, int), 100); @@ -298,6 +282,30 @@ struct RenderEffectGaussian : RenderEffect } }; +struct RenderEffectDropShadow : RenderEffect +{ + uint8_t color[4]; //rgba + float angle; + float distance; + float sigma; + uint8_t quality; //0 ~ 100 (optional) + + static RenderEffectDropShadow* gen(va_list& args) + { + auto inst = new RenderEffectDropShadow; + inst->color[0] = va_arg(args, int); + inst->color[1] = va_arg(args, int); + inst->color[2] = va_arg(args, int); + inst->color[3] = std::min(va_arg(args, int), 255); + inst->angle = (float) va_arg(args, double); + inst->distance = (float) va_arg(args, double); + inst->sigma = std::max((float) va_arg(args, double), 0.0f); + inst->quality = std::min(va_arg(args, int), 100); + inst->type = SceneEffect::DropShadow; + return inst; + } +}; + class RenderMethod { private: @@ -327,31 +335,31 @@ class RenderMethod virtual bool sync() = 0; virtual RenderCompositor* target(const RenderRegion& region, ColorSpace cs) = 0; - virtual bool beginComposite(RenderCompositor* cmp, CompositeMethod method, uint8_t opacity) = 0; + virtual bool beginComposite(RenderCompositor* cmp, MaskMethod method, uint8_t opacity) = 0; virtual bool endComposite(RenderCompositor* cmp) = 0; virtual bool prepare(RenderEffect* effect) = 0; - virtual bool effect(RenderCompositor* cmp, const RenderEffect* effect) = 0; + virtual bool effect(RenderCompositor* cmp, const RenderEffect* effect, bool direct) = 0; }; -static inline bool MASK_REGION_MERGING(CompositeMethod method) +static inline bool MASK_REGION_MERGING(MaskMethod method) { switch(method) { - case CompositeMethod::AlphaMask: - case CompositeMethod::InvAlphaMask: - case CompositeMethod::LumaMask: - case CompositeMethod::InvLumaMask: - case CompositeMethod::SubtractMask: - case CompositeMethod::IntersectMask: + case MaskMethod::Alpha: + case MaskMethod::InvAlpha: + case MaskMethod::Luma: + case MaskMethod::InvLuma: + case MaskMethod::Subtract: + case MaskMethod::Intersect: return false; //these might expand the rendering region - case CompositeMethod::AddMask: - case CompositeMethod::DifferenceMask: - case CompositeMethod::LightenMask: - case CompositeMethod::DarkenMask: + case MaskMethod::Add: + case MaskMethod::Difference: + case MaskMethod::Lighten: + case MaskMethod::Darken: return true; default: - TVGERR("RENDERER", "Unsupported Composite Method! = %d", (int)method); + TVGERR("RENDERER", "Unsupported Masking Method! = %d", (int)method); return false; } } @@ -366,32 +374,32 @@ static inline uint8_t CHANNEL_SIZE(ColorSpace cs) return sizeof(uint32_t); case ColorSpace::Grayscale8: return sizeof(uint8_t); - case ColorSpace::Unsupported: + case ColorSpace::Unknown: default: TVGERR("RENDERER", "Unsupported Channel Size! = %d", (int)cs); return 0; } } -static inline ColorSpace COMPOSITE_TO_COLORSPACE(RenderMethod* renderer, CompositeMethod method) +static inline ColorSpace MASK_TO_COLORSPACE(RenderMethod* renderer, MaskMethod method) { switch(method) { - case CompositeMethod::AlphaMask: - case CompositeMethod::InvAlphaMask: - case CompositeMethod::AddMask: - case CompositeMethod::DifferenceMask: - case CompositeMethod::SubtractMask: - case CompositeMethod::IntersectMask: - case CompositeMethod::LightenMask: - case CompositeMethod::DarkenMask: + case MaskMethod::Alpha: + case MaskMethod::InvAlpha: + case MaskMethod::Add: + case MaskMethod::Difference: + case MaskMethod::Subtract: + case MaskMethod::Intersect: + case MaskMethod::Lighten: + case MaskMethod::Darken: return ColorSpace::Grayscale8; //TODO: Optimize Luma/InvLuma colorspace to Grayscale8 - case CompositeMethod::LumaMask: - case CompositeMethod::InvLumaMask: + case MaskMethod::Luma: + case MaskMethod::InvLuma: return renderer->colorSpace(); default: - TVGERR("RENDERER", "Unsupported Composite Size! = %d", (int)method); - return ColorSpace::Unsupported; + TVGERR("RENDERER", "Unsupported Masking Size! = %d", (int)method); + return ColorSpace::Unknown; } } diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgSaveModule.h b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgSaveModule.h index fed05c52d..e610dca1b 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgSaveModule.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgSaveModule.h @@ -33,7 +33,7 @@ class SaveModule public: virtual ~SaveModule() {} - virtual bool save(Paint* paint, const string& path, bool compress) = 0; + virtual bool save(Paint* paint, Paint* bg, const string& path, uint32_t quality) = 0; virtual bool save(Animation* animation, Paint* bg, const string& path, uint32_t quality, uint32_t fps) = 0; virtual bool close() = 0; }; diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgSaver.cpp b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgSaver.cpp index d9cff5bce..6bf9fca10 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgSaver.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgSaver.cpp @@ -21,12 +21,10 @@ */ #include "tvgCommon.h" +#include "tvgStr.h" #include "tvgSaveModule.h" #include "tvgPaint.h" -#ifdef THORVG_TVG_SAVER_SUPPORT - #include "tvgTvgSaver.h" -#endif #ifdef THORVG_GIF_SAVER_SUPPORT #include "tvgGifSaver.h" #endif @@ -51,12 +49,6 @@ struct Saver::Impl static SaveModule* _find(FileType type) { switch(type) { - case FileType::Tvg: { -#ifdef THORVG_TVG_SAVER_SUPPORT - return new TvgSaver; -#endif - break; - } case FileType::Gif: { #ifdef THORVG_GIF_SAVER_SUPPORT return new GifSaver; @@ -71,10 +63,6 @@ static SaveModule* _find(FileType type) #ifdef THORVG_LOG_ENABLED const char *format; switch(type) { - case FileType::Tvg: { - format = "TVG"; - break; - } case FileType::Gif: { format = "GIF"; break; @@ -90,14 +78,10 @@ static SaveModule* _find(FileType type) } -static SaveModule* _find(const string& path) +static SaveModule* _find(const char* filename) { - auto ext = path.substr(path.find_last_of(".") + 1); - if (!ext.compare("tvg")) { - return _find(FileType::Tvg); - } else if (!ext.compare("gif")) { - return _find(FileType::Gif); - } + auto ext = strExtension(filename); + if (ext && !strcmp(ext, "gif")) return _find(FileType::Gif); return nullptr; } @@ -117,7 +101,7 @@ Saver::~Saver() } -Result Saver::save(std::unique_ptr paint, const string& path, bool compress) noexcept +Result Saver::save(unique_ptr paint, const char* filename, uint32_t quality) noexcept { auto p = paint.release(); if (!p) return Result::MemoryCorruption; @@ -128,8 +112,8 @@ Result Saver::save(std::unique_ptr paint, const string& path, bool compre return Result::InsufficientCondition; } - if (auto saveModule = _find(path)) { - if (saveModule->save(p, path, compress)) { + if (auto saveModule = _find(filename)) { + if (saveModule->save(p, pImpl->bg, filename, quality)) { pImpl->saveModule = saveModule; return Result::Success; } else { @@ -152,7 +136,7 @@ Result Saver::background(unique_ptr paint) noexcept } -Result Saver::save(unique_ptr animation, const string& path, uint32_t quality, uint32_t fps) noexcept +Result Saver::save(unique_ptr animation, const char* filename, uint32_t quality, uint32_t fps) noexcept { auto a = animation.release(); if (!a) return Result::MemoryCorruption; @@ -171,8 +155,8 @@ Result Saver::save(unique_ptr animation, const string& path, uint32_t return Result::InsufficientCondition; } - if (auto saveModule = _find(path)) { - if (saveModule->save(a, pImpl->bg, path, quality, fps)) { + if (auto saveModule = _find(filename)) { + if (saveModule->save(a, pImpl->bg, filename, quality, fps)) { pImpl->saveModule = saveModule; return Result::Success; } else { diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgScene.cpp b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgScene.cpp index 0b165c711..b86fb762f 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgScene.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgScene.cpp @@ -61,12 +61,6 @@ unique_ptr Scene::gen() noexcept } -TVG_DEPRECATED uint32_t Scene::identifier() noexcept -{ - return (uint32_t) Type::Scene; -} - - Type Scene::type() const noexcept { return Type::Scene; @@ -84,12 +78,6 @@ Result Scene::push(unique_ptr paint) noexcept } -Result Scene::reserve(TVG_UNUSED uint32_t size) noexcept -{ - return Result::NonSupport; -} - - Result Scene::clear(bool free) noexcept { pImpl->clear(free); @@ -117,7 +105,11 @@ Result Scene::push(SceneEffect effect, ...) noexcept switch (effect) { case SceneEffect::GaussianBlur: { - re = RenderEffectGaussian::gen(args); + re = RenderEffectGaussianBlur::gen(args); + break; + } + case SceneEffect::DropShadow: { + re = RenderEffectDropShadow::gen(args); break; } default: break; diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgScene.h b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgScene.h index cb2ef7f64..1ded536da 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgScene.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgScene.h @@ -89,11 +89,8 @@ struct Scene::Impl //post effects requires composition if (effects) return true; - //Masking may require composition (even if opacity == 255) - auto compMethod = scene->composite(nullptr); - if (compMethod != CompositeMethod::None && compMethod != CompositeMethod::ClipPath) return true; - - //Blending may require composition (even if opacity == 255) + //Masking / Blending may require composition (even if opacity == 255) + if (scene->mask(nullptr) != MaskMethod::None) return true; if (PP(scene)->blendMethod != BlendMethod::Normal) return true; //Half translucent requires intermediate composition. @@ -133,7 +130,7 @@ struct Scene::Impl if (needComp) { cmp = renderer->target(bounds(renderer), renderer->colorSpace()); - renderer->beginComposite(cmp, CompositeMethod::None, opacity); + renderer->beginComposite(cmp, MaskMethod::None, opacity); } for (auto paint : paints) { @@ -143,8 +140,9 @@ struct Scene::Impl if (cmp) { //Apply post effects if any. if (effects) { + auto direct = effects->count == 1 ? true : false; for (auto e = effects->begin(); e < effects->end(); ++e) { - renderer->effect(cmp, *e); + renderer->effect(cmp, *e, direct); } } renderer->endComposite(cmp); diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgShape.cpp b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgShape.cpp index 269d951f0..ada7323b3 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgShape.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgShape.cpp @@ -49,12 +49,6 @@ unique_ptr Shape::gen() noexcept } -uint32_t Shape::identifier() noexcept -{ - return (uint32_t) Type::Shape; -} - - Type Shape::type() const noexcept { return Type::Shape; @@ -156,68 +150,6 @@ Result Shape::appendCircle(float cx, float cy, float rx, float ry) noexcept } -TVG_DEPRECATED Result Shape::appendArc(float cx, float cy, float radius, float startAngle, float sweep, bool pie) noexcept -{ - //just circle - if (sweep >= 360.0f || sweep <= -360.0f) return appendCircle(cx, cy, radius, radius); - - const float arcPrecision = 1e-5f; - startAngle = deg2rad(startAngle); - sweep = deg2rad(sweep); - - auto nCurves = static_cast(fabsf(sweep / MATH_PI2)); - if (fabsf(sweep / MATH_PI2) - nCurves > arcPrecision) ++nCurves; - auto sweepSign = (sweep < 0 ? -1 : 1); - auto fract = fmodf(sweep, MATH_PI2); - fract = (fabsf(fract) < arcPrecision) ? MATH_PI2 * sweepSign : fract; - - //Start from here - Point start = {radius * cosf(startAngle), radius * sinf(startAngle)}; - - if (pie) { - pImpl->moveTo(cx, cy); - pImpl->lineTo(start.x + cx, start.y + cy); - } else { - pImpl->moveTo(start.x + cx, start.y + cy); - } - - for (int i = 0; i < nCurves; ++i) { - auto endAngle = startAngle + ((i != nCurves - 1) ? MATH_PI2 * sweepSign : fract); - Point end = {radius * cosf(endAngle), radius * sinf(endAngle)}; - - //variables needed to calculate bezier control points - - //get bezier control points using article: - //(http://itc.ktu.lt/index.php/ITC/article/view/11812/6479) - auto ax = start.x; - auto ay = start.y; - auto bx = end.x; - auto by = end.y; - auto q1 = ax * ax + ay * ay; - auto q2 = ax * bx + ay * by + q1; - auto k2 = (4.0f/3.0f) * ((sqrtf(2 * q1 * q2) - q2) / (ax * by - ay * bx)); - - start = end; //Next start point is the current end point - - end.x += cx; - end.y += cy; - - Point ctrl1 = {ax - k2 * ay + cx, ay + k2 * ax + cy}; - Point ctrl2 = {bx + k2 * by + cx, by - k2 * bx + cy}; - - pImpl->cubicTo(ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, end.x, end.y); - - startAngle = endAngle; - } - - if (pie) pImpl->close(); - - pImpl->flag |= RenderUpdateFlag::Path; - - return Result::Success; -} - - Result Shape::appendRect(float x, float y, float w, float h, float rx, float ry) noexcept { auto halfW = w * 0.5f; @@ -312,7 +244,7 @@ Result Shape::order(bool strokeFirst) noexcept } -Result Shape::stroke(float width) noexcept +Result Shape::strokeWidth(float width) noexcept { pImpl->strokeWidth(width); return Result::Success; @@ -325,22 +257,22 @@ float Shape::strokeWidth() const noexcept } -Result Shape::stroke(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept +Result Shape::strokeFill(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept { - pImpl->strokeColor(r, g, b, a); + pImpl->strokeFill(r, g, b, a); return Result::Success; } -Result Shape::strokeColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept +Result Shape::strokeFill(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept { - if (!pImpl->rs.strokeColor(r, g, b, a)) return Result::InsufficientCondition; + if (!pImpl->rs.strokeFill(r, g, b, a)) return Result::InsufficientCondition; return Result::Success; } -Result Shape::stroke(unique_ptr f) noexcept +Result Shape::strokeFill(unique_ptr f) noexcept { return pImpl->strokeFill(std::move(f)); } @@ -352,26 +284,26 @@ const Fill* Shape::strokeFill() const noexcept } -Result Shape::stroke(const float* dashPattern, uint32_t cnt) noexcept +Result Shape::strokeDash(const float* dashPattern, uint32_t cnt, float offset) noexcept { - return pImpl->strokeDash(dashPattern, cnt, 0); + return pImpl->strokeDash(dashPattern, cnt, offset); } -uint32_t Shape::strokeDash(const float** dashPattern) const noexcept +uint32_t Shape::strokeDash(const float** dashPattern, float* offset) const noexcept { - return pImpl->rs.strokeDash(dashPattern, nullptr); + return pImpl->rs.strokeDash(dashPattern, offset); } -Result Shape::stroke(StrokeCap cap) noexcept +Result Shape::strokeCap(StrokeCap cap) noexcept { pImpl->strokeCap(cap); return Result::Success; } -Result Shape::stroke(StrokeJoin join) noexcept +Result Shape::strokeJoin(StrokeJoin join) noexcept { pImpl->strokeJoin(join); return Result::Success; diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgShape.h b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgShape.h index 42f815206..ad89616eb 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgShape.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgShape.h @@ -59,7 +59,7 @@ struct Shape::Impl if (needComp) { cmp = renderer->target(bounds(renderer), renderer->colorSpace()); - renderer->beginComposite(cmp, CompositeMethod::None, opacity); + renderer->beginComposite(cmp, MaskMethod::None, opacity); } auto ret = renderer->renderShape(rd); @@ -80,19 +80,18 @@ struct Shape::Impl //Composition test const Paint* target; - auto method = shape->composite(&target); - if (!target || method == CompositeMethod::ClipPath) return false; - if (target->pImpl->opacity == 255 || target->pImpl->opacity == 0) { - if (target->type() == Type::Shape) { - auto shape = static_cast(target); - if (!shape->fill()) { - uint8_t r, g, b, a; - shape->fillColor(&r, &g, &b, &a); - if (a == 0 || a == 255) { - if (method == CompositeMethod::LumaMask || method == CompositeMethod::InvLumaMask) { - if ((r == 255 && g == 255 && b == 255) || (r == 0 && g == 0 && b == 0)) return false; - } else return false; - } + auto method = shape->mask(&target); + if (!target) return false; + + if ((target->pImpl->opacity == 255 || target->pImpl->opacity == 0) && target->type() == Type::Shape) { + auto shape = static_cast(target); + if (!shape->fill()) { + uint8_t r, g, b, a; + shape->fillColor(&r, &g, &b, &a); + if (a == 0 || a == 255) { + if (method == MaskMethod::Luma || method == MaskMethod::InvLuma) { + if ((r == 255 && g == 255 && b == 255) || (r == 0 && g == 0 && b == 0)) return false; + } else return false; } } } @@ -262,7 +261,7 @@ struct Shape::Impl flag |= RenderUpdateFlag::Stroke; } - void strokeColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a) + void strokeFill(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { if (!rs.stroke) rs.stroke = new RenderStroke(); if (rs.stroke->fill) { diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgSwCanvas.cpp b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgSwCanvas.cpp index 6c4b6da1d..f3ff2207f 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgSwCanvas.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgSwCanvas.cpp @@ -79,9 +79,12 @@ Result SwCanvas::mempool(MempoolPolicy policy) noexcept } -Result SwCanvas::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, Colorspace cs) noexcept +Result SwCanvas::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, ColorSpace cs) noexcept { #ifdef THORVG_SW_RASTER_SUPPORT + if (cs == ColorSpace::Unknown) return Result::InvalidArguments; + if (cs == ColorSpace::Grayscale8) return Result::NonSupport; + if (Canvas::pImpl->status != Status::Damaged && Canvas::pImpl->status != Status::Synced) { return Result::InsufficientCondition; } diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgText.cpp b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgText.cpp index b324b9504..be94ba24d 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgText.cpp +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgText.cpp @@ -58,10 +58,10 @@ Result Text::font(const char* name, float size, const char* style) noexcept } -Result Text::load(const std::string& path) noexcept +Result Text::load(const char* path) noexcept { bool invalid; //invalid path - if (!LoaderMgr::loader(path, &invalid)) { + if (!LoaderMgr::loader(path, &invalid, nullptr)) { if (invalid) return Result::InvalidArguments; else return Result::NonSupport; } @@ -70,7 +70,7 @@ Result Text::load(const std::string& path) noexcept } -Result Text::load(const char* name, const char* data, uint32_t size, const string& mimeType, bool copy) noexcept +Result Text::load(const char* name, const char* data, uint32_t size, const char* mimeType, bool copy) noexcept { if (!name || (size == 0 && data)) return Result::InvalidArguments; @@ -80,14 +80,14 @@ Result Text::load(const char* name, const char* data, uint32_t size, const strin return Result::InsufficientCondition; } - if (!LoaderMgr::loader(name, data, size, mimeType, copy)) return Result::NonSupport; + if (!LoaderMgr::loader(name, data, size, mimeType, copy, nullptr)) return Result::NonSupport; return Result::Success; } -Result Text::unload(const std::string& path) noexcept +Result Text::unload(const char* filename) noexcept { - if (LoaderMgr::retrieve(path)) return Result::Success; + if (LoaderMgr::retrieve(filename)) return Result::Success; return Result::InsufficientCondition; } diff --git a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgText.h b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgText.h index 11e01b58c..5770a17dc 100644 --- a/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgText.h +++ b/libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgText.h @@ -61,7 +61,7 @@ struct Text::Impl Result font(const char* name, float size, const char* style) { - auto loader = LoaderMgr::loader(name); + auto loader = LoaderMgr::loader(name, nullptr); if (!loader) return Result::InsufficientCondition; if (style && strstr(style, "italic")) italic = true; diff --git a/libfenrir/src/main/jni/animation/thorvg/thorvg-1.0-pre5.tar.gz b/libfenrir/src/main/jni/animation/thorvg/thorvg-1.0-pre5.tar.gz new file mode 100644 index 000000000..4c7d8f70e Binary files /dev/null and b/libfenrir/src/main/jni/animation/thorvg/thorvg-1.0-pre5.tar.gz differ diff --git a/libfenrir/src/main/jni/animation/thorvg_jni.cpp b/libfenrir/src/main/jni/animation/thorvg_jni.cpp index 41676c149..cec7da076 100644 --- a/libfenrir/src/main/jni/animation/thorvg_jni.cpp +++ b/libfenrir/src/main/jni/animation/thorvg_jni.cpp @@ -2,12 +2,25 @@ #include #include #include +#include +#include #include #include #include #include #include +#include +#include +#include #include "fenrir_native.h" +#include "tvgGifEncoder.h" + +class LottieInfo { +public: + std::unique_ptr animation; + std::unique_ptr canvas; + bool isCanvasPushed = false; +}; static pthread_mutex_t *lockMutex = nullptr; static std::map customColorsTable; @@ -16,7 +29,7 @@ extern std::string doDecompressResource(size_t length, char *bytes, bool &orig); extern bool fenrirNativeThorVGInited; -void getCustomColor(const std::string &name, uint8_t *r, uint8_t *g, uint8_t *b) { +void getCustomColorSVG(const std::string &name, uint8_t *r, uint8_t *g, uint8_t *b) { if (!lockMutex) { lockMutex = new pthread_mutex_t(); pthread_mutex_init(lockMutex, nullptr); @@ -30,9 +43,10 @@ void getCustomColor(const std::string &name, uint8_t *r, uint8_t *g, uint8_t *b) } extern "C" JNIEXPORT void -Java_dev_ragnarok_fenrir_module_thorvg_ThorVGRender_registerColorsNative(JNIEnv *env, jobject, - jstring name, - jint value) { +Java_dev_ragnarok_fenrir_module_animation_thorvg_ThorVGSVGRender_registerColorsNative(JNIEnv *env, + jobject, + jstring name, + jint value) { if (!fenrirNativeThorVGInited || !name) { return; } @@ -50,43 +64,504 @@ Java_dev_ragnarok_fenrir_module_thorvg_ThorVGRender_registerColorsNative(JNIEnv } extern "C" JNIEXPORT void -Java_dev_ragnarok_fenrir_module_thorvg_ThorVGRender_createBitmapNative(JNIEnv *env, jobject, - jlong res, jobject bitmap, - jint w, - jint h) { +Java_dev_ragnarok_fenrir_module_animation_thorvg_ThorVGSVGRender_createBitmapNative(JNIEnv *env, + jobject, + jlong res, + jobject bitmap, + jint w, + jint h) { if (!bitmap) { return; } - auto u = ((std::vector *) (intptr_t) - res); + auto u = reinterpret_cast *>(res); auto canvas = tvg::SwCanvas::gen(); if (!canvas) { return; } + canvas->mempool(tvg::SwCanvas::Individual); auto picture = tvg::Picture::gen(); bool orig; std::string jsonString = doDecompressResource(u->size(), u->data(), orig); - tvg::Result result = orig ? picture->load((const char *) u->data(), u->size(), "svg") + tvg::Result result = orig ? picture->load((const char *) u->data(), u->size(), "svg", "", true) : picture->load((const char *) jsonString.data(), jsonString.size(), - "svg"); + "svg", "", true); if (result != tvg::Result::Success) { return; } - picture->size((float) w, (float) h); + float scale; + float shiftX = 0.0f, shiftY = 0.0f; + float w2, h2; + picture->size(&w2, &h2); + + if (h >= w) { + if (w2 >= h2) { + scale = (float) w / w2; + shiftY = ((float) h - h2 * scale) * 0.5f; + } else { + scale = (float) h / h2; + shiftX = ((float) w - w2 * scale) * 0.5f; + } + } else { + if (w2 < h2) { + scale = (float) w / w2; + shiftY = ((float) h - h2 * scale) * 0.5f; + } else { + scale = (float) h / h2; + shiftX = ((float) w - w2 * scale) * 0.5f; + } + } + + picture->scale(scale); + picture->translate(shiftX, shiftY); void *pixels; if (AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0) { - if (canvas->target((uint32_t *) pixels, w, w, h, tvg::SwCanvas::ABGR8888) != + if (canvas->target((uint32_t *) pixels, w, w, h, tvg::ColorSpace::ABGR8888) != tvg::Result::Success) { AndroidBitmap_unlockPixels(env, bitmap); return; } canvas->push(std::move(picture)); - canvas->draw(); - canvas->sync(); - canvas->clear(true); + if (canvas->draw() == tvg::Result::Success) { + canvas->sync(); + } AndroidBitmap_unlockPixels(env, bitmap); } } + +std::string doDecompressResource(size_t length, char *bytes, bool &orig) { + orig = false; + std::string data; + if (length >= GZIP_HEADER_LENGTH && memcmp(bytes, GZIP_HEADER, GZIP_HEADER_LENGTH) == 0) { + z_stream zs; + memset(&zs, 0, sizeof(zs)); + + if (inflateInit2(&zs, 15 + 16) != Z_OK) { + return ""; + } + + zs.next_in = (Bytef *) bytes; + zs.avail_in = length; + + int ret; + std::vector outBuffer(32768); + + do { + zs.next_out = reinterpret_cast(outBuffer.data()); + zs.avail_out = outBuffer.size(); + ret = inflate(&zs, 0); + if (data.size() < zs.total_out) { + data.append(outBuffer.data(), zs.total_out - data.size()); + } + + } while (ret == Z_OK); + inflateEnd(&zs); + if (ret != Z_STREAM_END) { + return ""; + } + } else if (length >= MY_LZ4_HEADER_LENGTH && + memcmp(bytes, MY_LZ4_HEADER, MY_LZ4_HEADER_LENGTH) == 0) { + MY_LZ4HDR_PUSH hdr = {}; + memcpy(&hdr, bytes, sizeof(MY_LZ4HDR_PUSH)); + data.resize(hdr.size); + LZ4_decompress_safe(((const char *) bytes + sizeof(MY_LZ4HDR_PUSH)), (char *) data.data(), + (int) length - (int) sizeof(MY_LZ4HDR_PUSH), (int) hdr.size); + } else { + orig = true; + } + return data; +} + +extern "C" JNIEXPORT jlong +Java_dev_ragnarok_fenrir_module_animation_thorvg_ThorVGLottieDrawable_nLoadFromFile(JNIEnv *env, + jobject, + jstring srcPath, + jintArray data, + jintArray colorReplacement, + jboolean useMoveColor) { + auto *info = new LottieInfo(); + tvg::ColorReplace colors; + if (useMoveColor) { + colors.setUseCustomColorsLottieOffset(); + } + if (colorReplacement != nullptr) { + jint *arr = env->GetIntArrayElements(colorReplacement, nullptr); + if (arr != nullptr) { + jsize len = env->GetArrayLength(colorReplacement); + if (len % 2 == 0) { + for (int32_t a = 0; a < len / 2; a++) { + colors.registerCustomColorLottie(arr[a * 2], arr[a * 2 + 1]); + } + } + env->ReleaseIntArrayElements(colorReplacement, arr, 0); + } + } + + char const *srcString = SafeGetStringUTFChars(env, srcPath, nullptr); + std::string path = srcString; + if (srcString != nullptr) { + env->ReleaseStringUTFChars(srcPath, srcString); + } + std::ifstream f; + f.open(path); + if (!f.is_open()) { + delete info; + return 0; + } + f.seekg(0, std::ios::end); + auto length = f.tellg(); + f.seekg(0, std::ios::beg); + if (length <= 0) { + f.close(); + delete info; + return 0; + } + auto *arr = new char[(size_t) length + 1]; + f.read(arr, length); + f.close(); + arr[length] = '\0'; + bool orig; + std::string jsonString = doDecompressResource(length, arr, orig); + if (orig) { + info->animation = tvg::Animation::gen(); + info->animation->picture()->load(arr, length, "lottie", "", true, colors._ptr()); + } + delete[] arr; + if (!orig) { + if (jsonString.empty()) { + delete info; + return 0; + } + info->animation = tvg::Animation::gen(); + info->animation->picture()->load(jsonString.data(), jsonString.size(), "lottie", "", true, + colors._ptr()); + } + info->canvas = tvg::SwCanvas::gen(); + float tmpWidth = 0; + float tmpHeight = 0; + info->animation->picture()->size(&tmpWidth, &tmpHeight); + + if (!info->animation || !info->canvas || tmpWidth < 1 || tmpHeight < 1) { + delete info; + return 0; + } + jint *dataArr = env->GetIntArrayElements(data, nullptr); + if (dataArr != nullptr) { + dataArr[0] = (jint) info->animation->totalFrame(); + dataArr[1] = (jint) info->animation->duration(); + dataArr[2] = (jint) tmpWidth; + dataArr[3] = (jint) tmpHeight; + env->ReleaseIntArrayElements(data, dataArr, 0); + } + + return reinterpret_cast(info); +} + +extern "C" JNIEXPORT jlong +Java_dev_ragnarok_fenrir_module_animation_thorvg_ThorVGLottieDrawable_nLoadFromMemory(JNIEnv *env, + jobject, + jlong json, + jintArray data, + jintArray colorReplacement, + jboolean useMoveColor) { + tvg::ColorReplace colors; + if (useMoveColor) { + colors.setUseCustomColorsLottieOffset(); + } + if (colorReplacement != nullptr) { + jint *arr = env->GetIntArrayElements(colorReplacement, nullptr); + if (arr != nullptr) { + jsize len = env->GetArrayLength(colorReplacement); + if (len % 2 == 0) { + for (int32_t a = 0; a < len / 2; a++) { + colors.registerCustomColorLottie(arr[a * 2], arr[a * 2 + 1]); + } + } + env->ReleaseIntArrayElements(colorReplacement, arr, 0); + } + } + + auto *info = new LottieInfo(); + auto u = reinterpret_cast *>(json); + bool orig; + std::string jsonString = doDecompressResource(u->size(), u->data(), orig); + if (orig) { + info->animation = tvg::Animation::gen(); + info->animation->picture()->load(u->data(), u->size(), "lottie", "", true, colors._ptr()); + } else { + info->animation = tvg::Animation::gen(); + info->animation->picture()->load(jsonString.data(), jsonString.size(), "lottie", "", true, + colors._ptr()); + } + info->canvas = tvg::SwCanvas::gen(); + float tmpWidth = 0; + float tmpHeight = 0; + info->animation->picture()->size(&tmpWidth, &tmpHeight); + + if (!info->animation || !info->canvas || tmpWidth < 1 || tmpHeight < 1) { + delete info; + return 0; + } + jint *dataArr = env->GetIntArrayElements(data, nullptr); + if (dataArr != nullptr) { + dataArr[0] = (jint) info->animation->totalFrame(); + dataArr[1] = (jint) info->animation->duration(); + dataArr[2] = (jint) tmpWidth; + dataArr[3] = (jint) tmpHeight; + env->ReleaseIntArrayElements(data, dataArr, 0); + } + return reinterpret_cast(info); +} + +extern "C" JNIEXPORT void +Java_dev_ragnarok_fenrir_module_animation_thorvg_ThorVGLottieDrawable_nDestroy(JNIEnv *, jobject, + jlong ptr) { + if (!ptr) { + return; + } + auto *info = reinterpret_cast(ptr); + delete info; +} + +extern "C" void +Java_dev_ragnarok_fenrir_module_animation_thorvg_ThorVGLottieDrawable_nSetBufferSize( + JNIEnv *env, jobject, + jlong ptr, jobject bitmap, jfloat width, jfloat height) { + if (!ptr || !bitmap) { + return; + } + + auto *info = reinterpret_cast(ptr); + void *pixels; + if (AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0) { + info->canvas->sync(); + info->canvas->target((uint32_t *) pixels, (uint32_t) width, (uint32_t) width, + (uint32_t) height, + tvg::ColorSpace::ABGR8888); + info->canvas->clear(false); + + float scale; + float shiftX = 0.0f, shiftY = 0.0f; + float w2, h2; + info->animation->picture()->size(&w2, &h2); + + if (height >= width) { + if (w2 >= h2) { + scale = width / w2; + shiftY = (height - h2 * scale) * 0.5f; + } else { + scale = height / h2; + shiftX = (width - w2 * scale) * 0.5f; + } + } else { + if (w2 < h2) { + scale = width / w2; + shiftY = (height - h2 * scale) * 0.5f; + } else { + scale = height / h2; + shiftX = (width - w2 * scale) * 0.5f; + } + } + + info->animation->picture()->scale(scale); + info->animation->picture()->translate(shiftX, shiftY); + + AndroidBitmap_unlockPixels(env, bitmap); + } +} + +extern "C" JNIEXPORT void +Java_dev_ragnarok_fenrir_module_animation_thorvg_ThorVGLottieDrawable_nGetFrame(JNIEnv *env, + jobject, + jlong ptr, + jobject bitmap, + jint frame) { + if (!ptr || !bitmap) { + return; + } + auto *info = reinterpret_cast(ptr); + if (!info->canvas) { + return; + } + + void *pixels; + if (AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0) { + info->canvas->clear(false); + info->animation->frame((float) frame); + if (!info->isCanvasPushed) { + info->isCanvasPushed = true; + info->canvas->push(tvg::cast(info->animation->picture())); + } else { + info->canvas->update(info->animation->picture()); + } + if (info->canvas->draw() == tvg::Result::Success) { + info->canvas->sync(); + } + AndroidBitmap_unlockPixels(env, bitmap); + } +} + +extern "C" JNIEXPORT +jboolean +Java_dev_ragnarok_fenrir_module_animation_thorvg_ThorVGLottie2Gif_lottie2gif(JNIEnv *env, jobject, + jstring srcPath, + jint w, jint h, + jint bgColor, + jboolean transparent, + jint fps, + jstring gifName, + jobject listener) { + auto info = LottieInfo(); + char const *srcString = SafeGetStringUTFChars(env, srcPath, nullptr); + std::string path = srcString; + if (srcString != nullptr) { + env->ReleaseStringUTFChars(srcPath, srcString); + } + std::ifstream f; + f.open(path); + if (!f.is_open()) { + return 0; + } + f.seekg(0, std::ios::end); + auto length = f.tellg(); + f.seekg(0, std::ios::beg); + if (length <= 0) { + f.close(); + return 0; + } + auto *arr = new char[(size_t) length + 1]; + f.read(arr, length); + f.close(); + arr[length] = '\0'; + bool orig; + std::string jsonString = doDecompressResource(length, arr, orig); + if (orig) { + info.animation = tvg::Animation::gen(); + info.animation->picture()->load(arr, length, "lottie", "", true); + } + delete[] arr; + if (!orig) { + if (jsonString.empty()) { + return 0; + } + info.animation = tvg::Animation::gen(); + info.animation->picture()->load(jsonString.data(), jsonString.size(), "lottie", "", true); + } + if (!info.animation) { + return false; + } + + info.canvas = tvg::SwCanvas::gen(); + if (!info.canvas) { + return false; + } + auto *pixels = new uint32_t[w * h]; + info.canvas->mempool(tvg::SwCanvas::Individual); + info.canvas->target(pixels, w, w, h, + tvg::ColorSpace::ABGR8888S); + + if (!transparent) { + auto bg = tvg::Shape::gen(); + bg->appendRect(0, 0, (float) w, (float) h); + bg->fill(((bgColor >> 16) & 0xff), ((bgColor >> 8) & 0xff), (bgColor & 0xff)); + info.canvas->push(std::move(bg)); + } + + info.canvas->push(tvg::cast(info.animation->picture())); + + char const *gifNameString = SafeGetStringUTFChars(env, gifName, nullptr); + std::string gifNameStr = gifNameString; + if (gifNameString != nullptr) { + env->ReleaseStringUTFChars(gifName, gifNameString); + } + + float scale; + float shiftX = 0.0f, shiftY = 0.0f; + float w2, h2; + info.animation->picture()->size(&w2, &h2); + + if (h >= w) { + if (w2 >= h2) { + scale = (float) w / w2; + shiftY = ((float) h - h2 * scale) * 0.5f; + } else { + scale = (float) h / h2; + shiftX = ((float) w - w2 * scale) * 0.5f; + } + } else { + if (w2 < h2) { + scale = (float) w / w2; + shiftY = ((float) h - h2 * scale) * 0.5f; + } else { + scale = (float) h / h2; + shiftX = ((float) w - w2 * scale) * 0.5f; + } + } + + info.animation->picture()->scale(scale); + info.animation->picture()->translate(shiftX, shiftY); + + if (fps > 60) { + fps = 60; + } else if (fps <= 0) { + fps = (info.animation->totalFrame() / info.animation->duration()); + } + + auto delay = (1.0f / (float) fps); + GifWriter writer; + if (!gifBegin(&writer, gifNameStr.c_str(), w, h, uint32_t(delay * 100.f))) { + return false; + } + auto duration = info.animation->duration(); + if (listener != nullptr) { + jweak store_Wlistener = env->NewWeakGlobalRef(listener); + jclass clazz = env->GetObjectClass(store_Wlistener); + + jmethodID mth_update = env->GetMethodID(clazz, "onProgress", "(II)V"); + jmethodID mth_start = env->GetMethodID(clazz, "onStarted", "()V"); + jmethodID mth_end = env->GetMethodID(clazz, "onFinished", "()V"); + + env->CallVoidMethod(store_Wlistener, mth_start); + + for (auto p = 0.0f; p < duration; p += delay) { + info.canvas->clear(false); + auto frameNo = info.animation->totalFrame() * (p / duration); + info.animation->frame(frameNo); + info.canvas->update(); + if (info.canvas->draw() == tvg::Result::Success) { + info.canvas->sync(); + } + if (!gifWriteFrame(&writer, reinterpret_cast(pixels), w, h, + uint32_t(delay * 100.0f), transparent)) { + break; + } + + env->CallVoidMethod(store_Wlistener, mth_update, (jint) frameNo, + (jint) info.animation->totalFrame()); + } + + env->CallVoidMethod(store_Wlistener, mth_end); + } else { + for (auto p = 0.0f; p < duration; p += delay) { + info.canvas->clear(false); + auto frameNo = info.animation->totalFrame() * (p / duration); + info.animation->frame(frameNo); + info.canvas->update(info.animation->picture()); + if (info.canvas->draw() == tvg::Result::Success) { + info.canvas->sync(); + } + + if (!gifWriteFrame(&writer, reinterpret_cast(pixels), w, h, + uint32_t(delay * 100.0f), transparent)) { + break; + } + } + } + if (!gifEnd(&writer)) { + return false; + } + delete[] pixels; + return true; +} \ No newline at end of file diff --git a/libfenrir/src/main/jni/animation/tvgGifEncoder.h b/libfenrir/src/main/jni/animation/tvgGifEncoder.h new file mode 100644 index 000000000..eea107b2d --- /dev/null +++ b/libfenrir/src/main/jni/animation/tvgGifEncoder.h @@ -0,0 +1,655 @@ +/* + * Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +// +// gif.h +// by Charlie Tangora +// Public domain. +// Email me : ctangora -at- gmail -dot- com +// +// This file offers a simple, very limited way to create animated GIFs directly in code. +// +// Those looking for particular cleverness are likely to be disappointed; it's pretty +// much a straight-ahead implementation of the GIF format with optional Floyd-Steinberg +// dithering. (It does at least use delta encoding - only the changed portions of each +// frame are saved.) +// +// So resulting files are often quite large. The hope is that it will be handy nonetheless +// as a quick and easily-integrated way for programs to spit out animations. +// +// Only RGBA8 is currently supported as an input format. (The alpha is ignored.) +// +// USAGE: +// Create a GifWriter struct. Pass it to GifBegin() to initialize and write the header. +// Pass subsequent frames to GifWriteFrame(). +// Finally, call GifEnd() to close the file handle and free memory. +// + +#ifndef tvgGifEncoder_h +#define tvgGifEncoder_h + +#include +#include +#include + + +#define TRANSPARENT_IDX 0 +#define TRANSPARENT_THRESHOLD 127 +#define BIT_DEPTH 8 + +typedef struct +{ + uint8_t r[256]; + uint8_t g[256]; + uint8_t b[256]; + + // k-d tree over RGB space, organized in heap fashion + // i.e. left child of node i is node i*2, right child is node i*2+1 + // nodes 256-511 are implicitly the leaves, containing a color + uint8_t treeSplitElt[256]; + uint8_t treeSplit[256]; +} GifPalette; + + +typedef struct +{ + FILE* f; + uint8_t* oldImage; + uint8_t* tmpImage; + GifPalette pal; + bool firstFrame; +} GifWriter; + +// Simple structure to write out the LZW-compressed portion of the image +// one bit at a time +typedef struct +{ + uint8_t bitIndex; // how many bits in the partial byte written so far + uint8_t byte; // current partial byte + + uint32_t chunkIndex; + uint8_t chunk[256]; // bytes are written in here until we have 256 of them, then written to the file +} GifBitStatus; + + +// The LZW dictionary is a 256-ary tree constructed as the file is encoded, +// this is one node +typedef struct +{ + uint16_t m_next[256]; +} GifLzwNode; + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +// walks the k-d tree to pick the palette entry for a desired color. +// Takes as in/out parameters the current best color and its error - +// only changes them if it finds a better color in its subtree. +// this is the major hotspot in the code at the moment. +static void _getClosestPaletteColor( GifPalette* pPal, int r, int g, int b, int* bestInd, int* bestDiff, int treeRoot ) +{ + // base case, reached the bottom of the tree + if (treeRoot > (1 << BIT_DEPTH) - 1) { + int ind = treeRoot-(1 << BIT_DEPTH); + if(ind == TRANSPARENT_IDX) return; + + // check whether this color is better than the current winner + int r_err = r - ((int32_t)pPal->r[ind]); + int g_err = g - ((int32_t)pPal->g[ind]); + int b_err = b - ((int32_t)pPal->b[ind]); + int diff = abs(r_err)+ abs(g_err) + abs(b_err); + + if(diff < *bestDiff) { + *bestInd = ind; + *bestDiff = diff; + } + return; + } + + // take the appropriate color (r, g, or b) for this node of the k-d tree + int comps[3] = {r, g, b}; + + int splitComp = comps[pPal->treeSplitElt[treeRoot]]; + + int splitPos = pPal->treeSplit[treeRoot]; + if (splitPos > splitComp) { + // check the left subtree + _getClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2); + if (*bestDiff > splitPos - splitComp) { + // cannot prove there's not a better value in the right subtree, check that too + _getClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2+1); + } + } else { + _getClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2+1); + if (*bestDiff > splitComp - splitPos) { + _getClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2); + } + } +} + + +static void _swapPixels(uint8_t* image, int pixA, int pixB) +{ + uint8_t rA = image[pixA*4]; + uint8_t gA = image[pixA*4+1]; + uint8_t bA = image[pixA*4+2]; + uint8_t aA = image[pixA*4+3]; + + uint8_t rB = image[pixB*4]; + uint8_t gB = image[pixB*4+1]; + uint8_t bB = image[pixB*4+2]; + uint8_t aB = image[pixA*4+3]; + + image[pixA*4] = rB; + image[pixA*4+1] = gB; + image[pixA*4+2] = bB; + image[pixA*4+3] = aB; + + image[pixB*4] = rA; + image[pixB*4+1] = gA; + image[pixB*4+2] = bA; + image[pixB*4+3] = aA; +} + + +// just the partition operation from quicksort +static int _partition(uint8_t* image, const int left, const int right, const int elt, int pivotIndex) +{ + const int pivotValue = image[(pivotIndex)*4+elt]; + _swapPixels(image, pivotIndex, right-1); + int storeIndex = left; + bool split = 0; + + for (int ii = left; ii < right - 1; ++ii) { + int arrayVal = image[ii*4+elt]; + if(arrayVal < pivotValue) { + _swapPixels(image, ii, storeIndex); + ++storeIndex; + } else if(arrayVal == pivotValue) { + if (split) { + _swapPixels(image, ii, storeIndex); + ++storeIndex; + } + split = !split; + } + } + _swapPixels(image, storeIndex, right-1); + return storeIndex; +} + + +// Perform an incomplete sort, finding all elements above and below the desired median +static void _partitionByMedian(uint8_t* image, int left, int right, int com, int neededCenter) +{ + if (left < right-1) { + int pivotIndex = left + (right-left)/2; + pivotIndex = _partition(image, left, right, com, pivotIndex); + + // Only "sort" the section of the array that contains the median + if(pivotIndex > neededCenter) _partitionByMedian(image, left, pivotIndex, com, neededCenter); + if(pivotIndex < neededCenter) _partitionByMedian(image, pivotIndex+1, right, com, neededCenter); + } +} + + +// Builds a palette by creating a balanced k-d tree of all pixels in the image +static void _splitPalette(uint8_t* image, int numPixels, int firstElt, int lastElt, int splitElt, int splitDist, int treeNode, GifPalette* pal) +{ + if(lastElt <= firstElt || numPixels == 0) return; + + // base case, bottom of the tree + if (lastElt == firstElt + 1) { + // otherwise, take the average of all colors in this subcube + uint64_t r=0, g=0, b=0; + for(int ii=0; iir[firstElt] = (uint8_t)r; + pal->g[firstElt] = (uint8_t)g; + pal->b[firstElt] = (uint8_t)b; + return; + } + + // Find the axis with the largest range + int minR = 255, maxR = 0; + int minG = 255, maxG = 0; + int minB = 255, maxB = 0; + + for (int ii=0; ii maxR) maxR = r; + if(r < minR) minR = r; + + if(g > maxG) maxG = g; + if(g < minG) minG = g; + + if(b > maxB) maxB = b; + if(b < minB) minB = b; + } + + int rRange = maxR - minR; + int gRange = maxG - minG; + int bRange = maxB - minB; + + // and split along that axis. (incidentally, this means this isn't a "proper" k-d tree but I don't know what else to call it) + int splitCom = 1; + if (bRange > gRange) splitCom = 2; + if (rRange > bRange && rRange > gRange) splitCom = 0; + + int subPixelsA = numPixels * (splitElt - firstElt) / (lastElt - firstElt); + int subPixelsB = numPixels-subPixelsA; + + _partitionByMedian(image, 0, numPixels, splitCom, subPixelsA); + + pal->treeSplitElt[treeNode] = (uint8_t)splitCom; + pal->treeSplit[treeNode] = image[subPixelsA*4+splitCom]; + + _splitPalette(image, subPixelsA, firstElt, splitElt, splitElt-splitDist, splitDist/2, treeNode*2, pal); + _splitPalette(image+subPixelsA*4, subPixelsB, splitElt, lastElt, splitElt+splitDist, splitDist/2, treeNode*2+1, pal); +} + + +// Finds all pixels that have changed from the previous image and +// moves them to the from of the buffer. +// This allows us to build a palette optimized for the colors of the +// changed pixels only. +static int _pickChangedPixels(const uint8_t* lastFrame, uint8_t* frame, int numPixels, bool transparent) +{ + int numChanged = 0; + uint8_t* writeIter = frame; + + for (int ii=0; ii < numPixels; ++ii) { + if (frame[3] >= TRANSPARENT_THRESHOLD) { + if (transparent || (lastFrame[0] != frame[0] || lastFrame[1] != frame[1] || lastFrame[2] != frame[2])) { + writeIter[0] = frame[0]; + writeIter[1] = frame[1]; + writeIter[2] = frame[2]; + ++numChanged; + writeIter += 4; + } + } + lastFrame += 4; + frame += 4; + } + + return numChanged; +} + + +// Creates a palette by placing all the image pixels in a k-d tree and then averaging the blocks at the bottom. +// This is known as the "modified median split" technique +static void _makePalette(GifWriter* writer, const uint8_t* lastFrame, const uint8_t* nextFrame, uint32_t width, uint32_t height, int bitDepth, bool transparent) +{ + auto& pal = writer->pal; + + auto imageSize = (size_t)(width * height * 4 * sizeof(uint8_t)); + memcpy(writer->tmpImage, nextFrame, imageSize); + + int numPixels = (int)(width * height); + if (lastFrame) numPixels = _pickChangedPixels(lastFrame, writer->tmpImage, numPixels, transparent); + + const int lastElt = 1 << bitDepth; + const int splitElt = lastElt/2; + const int splitDist = splitElt/2; + + _splitPalette(writer->tmpImage, numPixels, 1, lastElt, splitElt, splitDist, 1, &pal); + + // add the bottom node for the transparency index + pal.treeSplit[1 << (bitDepth-1)] = 0; + pal.treeSplitElt[1 << (bitDepth-1)] = 0; + pal.r[0] = pal.g[0] = pal.b[0] = 0; +} + + +void _palettizePixel(const uint8_t* nextFrame, uint8_t* outFrame, GifPalette* pPal) +{ + int32_t bestDiff = 1000000; + int32_t bestInd = 1; + _getClosestPaletteColor(pPal, nextFrame[0], nextFrame[1], nextFrame[2], &bestInd, &bestDiff, 1); + + // Write the resulting color to the output buffer + outFrame[0] = pPal->r[bestInd]; + outFrame[1] = pPal->g[bestInd]; + outFrame[2] = pPal->b[bestInd]; + outFrame[3] = (uint8_t)bestInd; +} + + +// Picks palette colors for the image using simple threshholding, no dithering +static void _thresholdImage(GifWriter* writer, const uint8_t* lastFrame, const uint8_t* nextFrame, uint32_t width, uint32_t height, bool transparent) +{ + auto outFrame = writer->oldImage; + uint32_t numPixels = width*height; + + if (transparent) { + for (uint32_t ii = 0; ii < numPixels; ++ii) { + if (nextFrame[3] < TRANSPARENT_THRESHOLD) { + outFrame[0] = 0; + outFrame[1] = 0; + outFrame[2] = 0; + outFrame[3] = TRANSPARENT_IDX; + } else { + _palettizePixel(nextFrame, outFrame, &writer->pal); + } + if (lastFrame) lastFrame += 4; + outFrame += 4; + nextFrame += 4; + } + } else { + for (uint32_t ii = 0; ii < numPixels; ++ii) { + // if a previous color is available, and it matches the current color, + // set the pixel to transparent + if(lastFrame && lastFrame[0] == nextFrame[0] && lastFrame[1] == nextFrame[1] && lastFrame[2] == nextFrame[2]) { + outFrame[0] = lastFrame[0]; + outFrame[1] = lastFrame[1]; + outFrame[2] = lastFrame[2]; + outFrame[3] = TRANSPARENT_IDX; + } else { + _palettizePixel(nextFrame, outFrame, &writer->pal); + } + if (lastFrame) lastFrame += 4; + outFrame += 4; + nextFrame += 4; + } + } +} + + +// insert a single bit +static void _writeBit(GifBitStatus* stat, uint32_t bit) +{ + bit = bit & 1; + bit = bit << stat->bitIndex; + stat->byte |= bit; + + ++stat->bitIndex; + if (stat->bitIndex > 7) { + // move the newly-finished byte to the chunk buffer + stat->chunk[stat->chunkIndex++] = stat->byte; + // and start a new byte + stat->bitIndex = 0; + stat->byte = 0; + } +} + + +// write all bytes so far to the file +static void _writeChunk(FILE* f, GifBitStatus* stat) +{ + fputc((int)stat->chunkIndex, f); + fwrite(stat->chunk, 1, stat->chunkIndex, f); + + stat->bitIndex = 0; + stat->byte = 0; + stat->chunkIndex = 0; +} + + +static void _writeCode(FILE* f, GifBitStatus* stat, uint32_t code, uint32_t length) +{ + for (uint32_t ii = 0; ii < length; ++ii) { + _writeBit(stat, code); + code = code >> 1; + if (stat->chunkIndex == 255) _writeChunk(f, stat); + } +} + + +// write a 256-color (8-bit) image palette to the file +static void _writePalette(const GifPalette* pPal, FILE* f) +{ + fputc(0, f); // first color: transparency + fputc(0, f); + fputc(0, f); + + for (int ii = 1; ii < (1 << BIT_DEPTH); ++ii) { + uint32_t r = pPal->r[ii]; + uint32_t g = pPal->g[ii]; + uint32_t b = pPal->b[ii]; + + fputc((int)r, f); + fputc((int)g, f); + fputc((int)b, f); + } +} + + +// write the image header, LZW-compress and write out the image +static void _writeLzwImage(GifWriter* writer, uint32_t width, uint32_t height, uint32_t delay, bool transparent) +{ + auto f = writer->f; + auto image = writer->oldImage; + + // graphics control extension + fputc(0x21, f); + fputc(0xf9, f); + fputc(0x04, f); + fputc((transparent ? 0x09 : 0x05), f); //clear prev frame or not. + fputc(delay & 0xff, f); + fputc((delay >> 8) & 0xff, f); + fputc(TRANSPARENT_IDX, f); // transparent color index + fputc(0, f); + + fputc(0x2c, f); // image descriptor block + + // corner of image (left, top) in canvas space + fputc(0, f); + fputc(0, f); + fputc(0, f); + fputc(0, f); + + fputc(width & 0xff, f); // width and height of image + fputc((width >> 8) & 0xff, f); + fputc(height & 0xff, f); + fputc((height >> 8) & 0xff, f); + + //fputc(0, f); // no local color table, no transparency + //fputc(0x80, f); // no local color table, but transparency + + fputc(0x80 + BIT_DEPTH - 1, f); // local color table present, 2 ^ bitDepth entries + _writePalette(&writer->pal, f); + + const int minCodeSize = BIT_DEPTH; + const uint32_t clearCode = 1 << BIT_DEPTH; + + fputc(minCodeSize, f); // min code size 8 bits + + auto* codetree = (GifLzwNode*)malloc(sizeof(GifLzwNode)*4096); + + memset(codetree, 0, sizeof(GifLzwNode)*4096); + int32_t curCode = -1; + uint32_t codeSize = (uint32_t)minCodeSize + 1; + uint32_t maxCode = clearCode+1; + + GifBitStatus stat; + stat.byte = 0; + stat.bitIndex = 0; + stat.chunkIndex = 0; + + _writeCode(f, &stat, clearCode, codeSize); // start with a fresh LZW dictionary + + for (uint32_t yy = 0; yy < height; ++yy) { + for (uint32_t xx=0; xx= (1ul << codeSize)) { + // dictionary entry count has broken a size barrier, + // we need more bits for codes + codeSize++; + } + if (maxCode == 4095) { + // the dictionary is full, clear it out and begin anew + _writeCode(f, &stat, clearCode, codeSize); // clear tree + + memset(codetree, 0, sizeof(GifLzwNode)*4096); + codeSize = (uint32_t)(minCodeSize + 1); + maxCode = clearCode+1; + } + + curCode = nextValue; + } + } + } + + // compression footer + _writeCode(f, &stat, (uint32_t)curCode, codeSize); + _writeCode(f, &stat, clearCode, codeSize); + _writeCode(f, &stat, clearCode + 1, (uint32_t)minCodeSize + 1); + + // write out the last partial chunk + while (stat.bitIndex) _writeBit(&stat, 0); + if (stat.chunkIndex) _writeChunk(f, &stat); + + fputc(0, f); // image block terminator + + free(codetree); +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + + +bool gifBegin(GifWriter* writer, const char* filename, uint32_t width, uint32_t height, uint32_t delay) +{ +#if defined(_MSC_VER) && (_MSC_VER >= 1400) + writer->f = 0; + fopen_s(&writer->f, filename, "wb"); +#else + writer->f = fopen(filename, "wb"); +#endif + if (!writer->f) return false; + + writer->firstFrame = true; + + // allocate + writer->oldImage = (uint8_t*)malloc(width*height*4); + writer->tmpImage = (uint8_t*)malloc(width*height*4); + + fputs("GIF89a", writer->f); + + // screen descriptor + fputc(width & 0xff, writer->f); + fputc((width >> 8) & 0xff, writer->f); + fputc(height & 0xff, writer->f); + fputc((height >> 8) & 0xff, writer->f); + + fputc(0xf0, writer->f); // there is an unsorted global color table of 2 entries + fputc(0, writer->f); // background color + fputc(0, writer->f); // pixels are square (we need to specify this because it's 1989) + + // now the "global" palette (really just a dummy palette) + // color 0: black + fputc(0, writer->f); + fputc(0, writer->f); + fputc(0, writer->f); + // color 1: also black + fputc(0, writer->f); + fputc(0, writer->f); + fputc(0, writer->f); + + if(delay != 0) { + // animation header + fputc(0x21, writer->f); // extension + fputc(0xff, writer->f); // application specific + fputc(11, writer->f); // length 11 + fputs("NETSCAPE2.0", writer->f); // yes, really + fputc(3, writer->f); // 3 bytes of NETSCAPE2.0 data + + fputc(1, writer->f); // JUST BECAUSE + fputc(0, writer->f); // loop infinitely (byte 0) + fputc(0, writer->f); // loop infinitely (byte 1) + + fputc(0, writer->f); // block terminator + } + + return true; +} + + +bool gifWriteFrame(GifWriter* writer, const uint8_t* image, uint32_t width, uint32_t height, uint32_t delay, bool transparent) +{ + if (!writer->f) return false; + + const uint8_t* oldImage = writer->firstFrame? nullptr : writer->oldImage; + writer->firstFrame = false; + + _makePalette(writer, oldImage, image, width, height, 8, transparent); + _thresholdImage(writer, oldImage, image, width, height, transparent); + _writeLzwImage(writer, width, height, delay, transparent); + + return true; +} + + +bool gifEnd(GifWriter* writer) +{ + if (!writer->f) return false; + + fputc(0x3b, writer->f); // end of file + fclose(writer->f); + free(writer->oldImage); + free(writer->tmpImage); + + writer->f = nullptr; + writer->oldImage = nullptr; + + return true; +} + +#endif diff --git a/libfenrir/src/main/jni/audio/taglib/toolkit/tbytevector.cpp b/libfenrir/src/main/jni/audio/taglib/toolkit/tbytevector.cpp index 350e84980..7a9c67466 100644 --- a/libfenrir/src/main/jni/audio/taglib/toolkit/tbytevector.cpp +++ b/libfenrir/src/main/jni/audio/taglib/toolkit/tbytevector.cpp @@ -158,7 +158,7 @@ ByteVector fromNumber(T value, bool mostSignificantByteFirst) template TFloat toFloat(const ByteVector &v, size_t offset) { - if(offset > v.size() - sizeof(TInt)) { + if(offset + sizeof(TInt) > v.size()) { debug("toFloat() - offset is out of range. Returning 0."); return 0.0; } @@ -195,7 +195,7 @@ long double toFloat80(const ByteVector &v, size_t offset) { using std::swap; - if(offset > v.size() - 10) { + if(offset + 10 > v.size()) { debug("toFloat80() - offset is out of range. Returning 0."); return 0.0; } @@ -360,7 +360,7 @@ ByteVector::ByteVector(const char *data, unsigned int length) : } ByteVector::ByteVector(const char *data) : - d(std::make_unique(data, static_cast(::strlen(data)))) + d(std::make_unique(data, data ? static_cast(::strlen(data)) : 0)) { } @@ -767,6 +767,9 @@ bool ByteVector::operator!=(const ByteVector &v) const bool ByteVector::operator==(const char *s) const { + if(!s) + return isEmpty(); + if(size() != ::strlen(s)) return false; diff --git a/libfenrir/src/main/jni/audio/taglib/toolkit/tstring.cpp b/libfenrir/src/main/jni/audio/taglib/toolkit/tstring.cpp index f19a6755d..046de208d 100644 --- a/libfenrir/src/main/jni/audio/taglib/toolkit/tstring.cpp +++ b/libfenrir/src/main/jni/audio/taglib/toolkit/tstring.cpp @@ -195,23 +195,27 @@ String::String(const wchar_t *s) : String::String(const wchar_t *s, Type t) : d(std::make_shared()) { - if(t == UTF16 || t == UTF16BE || t == UTF16LE) { - copyFromUTF16(d->data, s, ::wcslen(s), t); - } - else { - debug("String::String() -- const wchar_t * should not contain Latin1 or UTF-8."); + if(s) { + if(t == UTF16 || t == UTF16BE || t == UTF16LE) { + copyFromUTF16(d->data, s, ::wcslen(s), t); + } + else { + debug("String::String() -- const wchar_t * should not contain Latin1 or UTF-8."); + } } } String::String(const char *s, Type t) : d(std::make_shared()) { - if(t == Latin1) - copyFromLatin1(d->data, s, ::strlen(s)); - else if(t == String::UTF8) - copyFromUTF8(d->data, s, ::strlen(s)); - else { - debug("String::String() -- const char * should not contain UTF16."); + if(s) { + if(t == Latin1) + copyFromLatin1(d->data, s, ::strlen(s)); + else if(t == String::UTF8) + copyFromUTF8(d->data, s, ::strlen(s)); + else { + debug("String::String() -- const char * should not contain UTF16."); + } } } @@ -546,6 +550,10 @@ bool String::operator!=(const String &s) const bool String::operator==(const char *s) const { + if(!s) { + return isEmpty(); + } + const wchar_t *p = toCWString(); while(*p != L'\0' || *s != '\0') { @@ -562,6 +570,10 @@ bool String::operator!=(const char *s) const bool String::operator==(const wchar_t *s) const { + if(!s) { + return isEmpty(); + } + return d->data == s; } @@ -580,18 +592,22 @@ String &String::operator+=(const String &s) String &String::operator+=(const wchar_t *s) { - detach(); + if(s) { + detach(); - d->data += s; + d->data += s; + } return *this; } String &String::operator+=(const char *s) { - detach(); + if(s) { + detach(); - for(int i = 0; s[i] != 0; i++) - d->data += static_cast(s[i]); + for(int i = 0; s[i] != 0; i++) + d->data += static_cast(s[i]); + } return *this; } diff --git a/libfenrir/src/main/jni/audio/taglib/utf8/utf8/core.h b/libfenrir/src/main/jni/audio/taglib/utf8/utf8/core.h index 4494c538e..627133c61 100644 --- a/libfenrir/src/main/jni/audio/taglib/utf8/utf8/core.h +++ b/libfenrir/src/main/jni/audio/taglib/utf8/utf8/core.h @@ -215,7 +215,7 @@ namespace internal UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) - code_point += (*it) & 0x3f; + code_point = static_cast(code_point + ((*it) & 0x3f)); return UTF8_OK; } @@ -234,11 +234,11 @@ namespace internal UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) - code_point += (utf8::internal::mask8(*it) << 6) & 0xfff; + code_point = static_cast(code_point + ((utf8::internal::mask8(*it) << 6) & 0xfff)); UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) - code_point += (*it) & 0x3f; + code_point = static_cast(code_point + ((*it) & 0x3f)); return UTF8_OK; } @@ -327,7 +327,7 @@ namespace internal else if (is_lead_surrogate(first_word)) { const utfchar16_t second_word = *it++; if (is_trail_surrogate(second_word)) { - code_point = (first_word << 10) + second_word + SURROGATE_OFFSET; + code_point = static_cast(first_word << 10) + second_word + SURROGATE_OFFSET; return UTF8_OK; } else err = INCOMPLETE_SEQUENCE; diff --git a/libfenrir/src/main/jni/audio/taglib/utf8/utf8/unchecked.h b/libfenrir/src/main/jni/audio/taglib/utf8/utf8/unchecked.h index 65d4948f2..bf2917893 100644 --- a/libfenrir/src/main/jni/audio/taglib/utf8/utf8/unchecked.h +++ b/libfenrir/src/main/jni/audio/taglib/utf8/utf8/unchecked.h @@ -115,15 +115,15 @@ namespace utf8 ++it; cp = ((cp << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); ++it; - cp += (*it) & 0x3f; + cp = static_cast(cp + ((*it) & 0x3f)); break; case 4: ++it; cp = ((cp << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); ++it; - cp += (utf8::internal::mask8(*it) << 6) & 0xfff; + cp = static_cast(cp + ((utf8::internal::mask8(*it) << 6) & 0xfff)); ++it; - cp += (*it) & 0x3f; + cp = static_cast(cp + ((*it) & 0x3f)); break; } ++it; diff --git a/libfenrir/src/main/jni/jni_call.cpp b/libfenrir/src/main/jni/jni_call.cpp index f05abf432..3f33bc88f 100644 --- a/libfenrir/src/main/jni/jni_call.cpp +++ b/libfenrir/src/main/jni/jni_call.cpp @@ -15,8 +15,8 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *) { } //auto threads = std::thread::hardware_concurrency(); - auto threads = 1; - if (tvg::Initializer::init(tvg::CanvasEngine::Sw, threads) == tvg::Result::Success) { + auto threads = 4; + if (tvg::Initializer::init(threads, tvg::CanvasEngine::Sw) == tvg::Result::Success) { fenrirNativeThorVGInited = true; } diff --git a/libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/animation/AnimatedFileDrawable.kt b/libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/animation/AnimatedFileDrawable.kt index 5d3ba6219..c973cc9fe 100644 --- a/libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/animation/AnimatedFileDrawable.kt +++ b/libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/animation/AnimatedFileDrawable.kt @@ -6,17 +6,18 @@ import android.graphics.drawable.Drawable import android.os.Handler import android.os.Looper import android.os.SystemClock +import androidx.annotation.IntDef import androidx.annotation.Keep +import androidx.annotation.RestrictTo import dev.ragnarok.fenrir.module.BuildConfig import dev.ragnarok.fenrir.module.DispatchQueue -import java.io.File import java.util.concurrent.ScheduledThreadPoolExecutor import java.util.concurrent.ThreadPoolExecutor import java.util.concurrent.TimeUnit import kotlin.math.abs class AnimatedFileDrawable( - file: File, + filePath: String, seekTo: Long, private val defaultWidth: Int, private val defaultHeight: Int, @@ -48,6 +49,17 @@ class AnimatedFileDrawable( private external fun prepareToSeek(ptr: Long) + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) + @IntDef(LoadedFrom.NET, LoadedFrom.NO, LoadedFrom.FILE, LoadedFrom.RES) + @Retention(AnnotationRetention.SOURCE) + annotation class LoadedFrom { + companion object { + const val NET = -1 + const val NO = 0 + const val FILE = 1 + const val RES = 3 + } + } private val metaData = IntArray(5) private val decoderCreated: Boolean @@ -585,7 +597,7 @@ class AnimatedFileDrawable( } init { - nativePtr = createDecoder(file.absolutePath, metaData) + nativePtr = createDecoder(filePath, metaData) if (nativePtr != 0L && (metaData[0] > 3840 || metaData[1] > 3840)) { destroyDecoder(nativePtr) nativePtr = 0 diff --git a/libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/rlottie/RLottie2Gif.kt b/libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/animation/thorvg/ThorVGLottie2Gif.kt similarity index 67% rename from libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/rlottie/RLottie2Gif.kt rename to libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/animation/thorvg/ThorVGLottie2Gif.kt index 338af1e0c..947e8d172 100644 --- a/libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/rlottie/RLottie2Gif.kt +++ b/libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/animation/thorvg/ThorVGLottie2Gif.kt @@ -1,25 +1,20 @@ -package dev.ragnarok.fenrir.module.rlottie +package dev.ragnarok.fenrir.module.animation.thorvg -import android.graphics.Bitmap import android.graphics.Color import androidx.annotation.Keep -import dev.ragnarok.fenrir.module.BufferWriteNative import dev.ragnarok.fenrir.module.DispatchQueuePool import java.io.File -class RLottie2Gif internal constructor(private val builder: Builder) { +class ThorVGLottie2Gif internal constructor(private val builder: Builder) { private external fun lottie2gif( - ptr: Long, - bitmap: Bitmap, + filePath: String, w: Int, h: Int, - stride: Int, bgColor: Int, transparent: Boolean, + fps: Int, gifPath: String, - bitDepth: Int, - dither: Boolean, listener: Lottie2GifListener ): Boolean @@ -39,7 +34,7 @@ class RLottie2Gif internal constructor(private val builder: Builder) { override fun onProgress(frame: Int, totalFrame: Int) { currentFrame = frame - this@RLottie2Gif.totalFrame = totalFrame + this@ThorVGLottie2Gif.totalFrame = totalFrame builder.listener?.onProgress(frame, totalFrame) } @@ -49,27 +44,16 @@ class RLottie2Gif internal constructor(private val builder: Builder) { } } var converter: Runnable = Runnable { - var bitmap: Bitmap? = null - try { - bitmap = Bitmap.createBitmap(builder.w, builder.h, Bitmap.Config.ARGB_8888) - } catch (e: Throwable) { - e.printStackTrace() - } - bitmap?.let { - isSuccessful = lottie2gif( - builder.jsonString.pointer, - it, - builder.w, - builder.h, - it.rowBytes, - builder.bgColor, - builder.bgColor == Color.TRANSPARENT, - builder.gifPath.absolutePath, - builder.bitDepth, - builder.dither, - listener - ) - } ?: run { isSuccessful = false } + isSuccessful = lottie2gif( + builder.file.absolutePath, + builder.w, + builder.h, + builder.bgColor, + builder.bgColor == Color.TRANSPARENT, + builder.fps, + builder.gifPath.absolutePath, + listener + ) } fun buildAgain(): Boolean { @@ -92,7 +76,7 @@ class RLottie2Gif internal constructor(private val builder: Builder) { override fun equals(other: Any?): Boolean { if (this === other) return true - if (other !is RLottie2Gif) return false + if (other !is ThorVGLottie2Gif) return false return other.builder.gifPath == builder.gifPath } @@ -111,15 +95,14 @@ class RLottie2Gif internal constructor(private val builder: Builder) { fun onFinished() } - class Builder(var jsonString: BufferWriteNative) { + class Builder(var file: File) { var w = 256 var h = 256 - var bgColor = Color.WHITE + var fps = 30 + var bgColor = Color.TRANSPARENT var listener: Lottie2GifListener? = null lateinit var gifPath: File var async = true - var bitDepth = 8 - var dither = false var cancelable = false /** @@ -165,16 +148,8 @@ class RLottie2Gif internal constructor(private val builder: Builder) { return this } - /** - * Implements Floyd-Steinberg dithering, writes palette value to alpha - */ - fun setDithering(enabled: Boolean): Builder { - dither = enabled - return this - } - - fun setBitDepth(bit: Int): Builder { - bitDepth = bit + fun setFPS(fps: Int): Builder { + this.fps = fps return this } @@ -183,19 +158,19 @@ class RLottie2Gif internal constructor(private val builder: Builder) { return this } - fun build(): RLottie2Gif { + fun build(): ThorVGLottie2Gif { if (w <= 0 || h <= 0) { throw RuntimeException("output gif width and height must be > 0") } - return RLottie2Gif(this) + return ThorVGLottie2Gif(this) } } companion object { private var runnableQueue: DispatchQueuePool? = null - fun create(jsonString: BufferWriteNative): Builder { - return Builder(jsonString) + fun create(file: File): Builder { + return Builder(file) } } @@ -203,4 +178,4 @@ class RLottie2Gif internal constructor(private val builder: Builder) { if (runnableQueue == null) runnableQueue = DispatchQueuePool(2) build() } -} \ No newline at end of file +} diff --git a/libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/animation/thorvg/ThorVGLottieDrawable.kt b/libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/animation/thorvg/ThorVGLottieDrawable.kt new file mode 100644 index 000000000..9e110aef5 --- /dev/null +++ b/libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/animation/thorvg/ThorVGLottieDrawable.kt @@ -0,0 +1,603 @@ +package dev.ragnarok.fenrir.module.animation.thorvg + +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.ColorFilter +import android.graphics.Paint +import android.graphics.PixelFormat +import android.graphics.drawable.Animatable +import android.graphics.drawable.Drawable +import android.graphics.drawable.Drawable.ConstantState +import android.os.Handler +import android.os.Looper +import androidx.annotation.FloatRange +import androidx.annotation.IntDef +import androidx.annotation.RawRes +import androidx.annotation.RestrictTo +import dev.ragnarok.fenrir.module.BufferWriteNative +import dev.ragnarok.fenrir.module.BuildConfig +import dev.ragnarok.fenrir.module.FenrirNative.appContext +import dev.ragnarok.fenrir.module.FenrirNative.density +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.Lottie +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.LottieAnimationListener +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.LottieDrawableState +import java.io.File +import java.io.InputStream +import kotlin.math.ceil +import kotlin.math.max +import kotlin.math.min + +class ThorVGLottieDrawable : Drawable, Animatable { + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) + @IntDef(RESTART, REVERSE) + @Retention(AnnotationRetention.SOURCE) + annotation class RepeatMode + + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) + @IntDef(LoadedFrom.NET, LoadedFrom.NO, LoadedFrom.FILE, LoadedFrom.RES) + @Retention(AnnotationRetention.SOURCE) + annotation class LoadedFrom { + companion object { + const val NET = -1 + const val NO = 0 + const val FILE = 1 + const val RES = 3 + } + } + + private var mLottieState: LottieDrawableState + + private var mListener: LottieAnimationListener? = null + + private var mRunning = false + + private var mEnded = false + + private var mStarted = false + + private var mRepeated = 0 + + private var mFrame = 0 + + private val mHandler = Handler(Looper.getMainLooper()) + + private val mNextFrameRunnable = Runnable { this.invalidateSelf() } + + private val mTmpPaint = Paint(Paint.FILTER_BITMAP_FLAG) + + private var mMutated = false + + private var startTime = 0L + + constructor( + filePath: String, + canDeleteError: Boolean, + colorReplacement: IntArray?, + useMoveColor: Boolean + ) { + mLottieState = LottieDrawableState() + mLottieState.mLottie = + Lottie().init(filePath, canDeleteError, colorReplacement, useMoveColor) + mLottieState.updateFrameInterval() + } + + constructor( + @RawRes rawRes: Int, + colorReplacement: IntArray?, + useMoveColor: Boolean + ) { + mLottieState = LottieDrawableState() + mLottieState.mLottie = Lottie().init(rawRes, colorReplacement, useMoveColor) + mLottieState.updateFrameInterval() + } + + private constructor(state: LottieDrawableState) { + mLottieState = state + } + + fun release() { + mLottieState.releaseLottie() + } + + protected fun finalize() { + try { + release() + } catch (e: Exception) { + if (BuildConfig.DEBUG) { + e.printStackTrace() + } + } + } + + override fun mutate(): Drawable { + if (!mMutated && super.mutate() === this) { + mLottieState = LottieDrawableState(mLottieState) + mMutated = true + } + return this + } + + override fun draw(canvas: Canvas) { + if (!mEnded && mLottieState.valid() && mRunning) { + if (!mStarted) { + mStarted = true + startTime = System.nanoTime() + dispatchAnimationStart() + } + + if (mRepeated >= mLottieState.mRepeatCount) { + if (!mEnded) { + mEnded = true + dispatchAnimationEnd() + } + getFrame(if (mLottieState.mFramesPerUpdate >= 0) mLottieState.getLastFrame() else mLottieState.mFirstFrame)?.let { + canvas.drawBitmap(it, 0f, 0f, mTmpPaint) + } + } else { + getFrame(mFrame)?.let { + canvas.drawBitmap(it, 0f, 0f, mTmpPaint) + } + + var resetFrame = false + mFrame += mLottieState.mFramesPerUpdate + if (mFrame > mLottieState.getLastFrame()) { + if (getRepeatMode() == REVERSE) { + mLottieState.mFramesPerUpdate = -1 + mFrame-- + } else { + mFrame = mLottieState.mFirstFrame + } + resetFrame = true + } else if (mFrame < mLottieState.mFirstFrame) { + if (getRepeatMode() == REVERSE) { + mLottieState.mFramesPerUpdate = 1 + mFrame++ + } else { + mFrame = mLottieState.getLastFrame() + } + resetFrame = true + } + if (resetFrame) { + mRepeated++ + dispatchAnimationRepeat() + } + val endTime = System.nanoTime() + mHandler.postDelayed( + mNextFrameRunnable, mLottieState.mFrameInterval + - ((endTime - startTime) / 1000000) + ) + startTime = System.nanoTime() + } + } else if (mLottieState.valid()) { + getFrame(mFrame)?.let { + canvas.drawBitmap(it, 0f, 0f, mTmpPaint) + } + } + } + + fun getFrame(frame: Int): Bitmap? { + return mLottieState.getLottieBuffer(frame) + } + + override fun setAlpha(alpha: Int) { + mTmpPaint.alpha = alpha + invalidateSelf() + } + + override fun setColorFilter(colorFilter: ColorFilter?) { + mTmpPaint.colorFilter = colorFilter + invalidateSelf() + } + + @Deprecated("", ReplaceWith("PixelFormat.TRANSPARENT", "android.graphics.PixelFormat")) + override fun getOpacity(): Int { + return PixelFormat.TRANSLUCENT + } + + override fun getIntrinsicWidth(): Int { + return mLottieState.mLottie?.mWidth ?: 0 + } + + override fun getIntrinsicHeight(): Int { + return mLottieState.mLottie?.mHeight ?: 0 + } + + fun setRepeatCount(count: Int) { + mLottieState.mRepeatCount = count + mRepeated = 0 + } + + fun getRepeatCount(): Int { + return mLottieState.mRepeatCount + } + + fun setRepeatMode(@RepeatMode mode: Int) { + mLottieState.setRepeatMode(mode) + } + + @RepeatMode + fun getRepeatMode(): Int { + return mLottieState.mRepeatMode + } + + fun setFirstFrame(frame: Int) { + mLottieState.setFirstFrame(frame) + } + + fun getFirstFrame(): Int { + return mLottieState.mFirstFrame + } + + fun setLastFrame(frame: Int) { + mLottieState.setLastFrame(frame) + } + + fun getLastFrame(): Int { + return mLottieState.getLastFrame() + } + + fun getDuration(): Long { + return mLottieState.mLottie?.mDuration ?: 0 + } + + fun getAnimationHeight(): Int { + return mLottieState.mLottie?.mHeight ?: 0 + } + + fun setSpeed(@FloatRange(from = 0.0) speed: Float) { + mLottieState.setSpeed(speed) + } + + @FloatRange(from = 0.0) + fun getSpeed(): Float { + return mLottieState.mSpeed + } + + fun setSize(width: Int, height: Int) { + require(width > 0) { "LottieDrawable requires width > 0" } + require(height > 0) { "LottieDrawable requires height > 0" } + mLottieState.setLottieSize(width, height) + } + + override fun isRunning(): Boolean { + return mRunning + } + + override fun start() { + mRunning = true + mEnded = false + mStarted = false + mRepeated = 0 + mFrame = mLottieState.mFirstFrame + invalidateSelf() + } + + override fun stop() { + mRunning = false + mHandler.removeCallbacks(mNextFrameRunnable) + } + + fun pause() { + mRunning = false + mHandler.removeCallbacks(mNextFrameRunnable) + } + + fun resume() { + startTime = System.nanoTime() + mRunning = true + invalidateSelf() + } + + fun setAnimationListener(listener: LottieAnimationListener?) { + mListener = listener + } + + fun dispatchAnimationStart() { + mListener?.onAnimationStart() + } + + fun dispatchAnimationRepeat() { + mListener?.onAnimationRepeat() + } + + fun dispatchAnimationEnd() { + mListener?.onAnimationEnd() + } + + inner class LottieDrawableState : ConstantState { + var mLottie: Lottie? = null + + var mRepeatMode = RESTART + + var mRepeatCount = 1 + + var mSpeed = 1.0f + + var mFirstFrame = 0 + private var mLastFrame = -1 + + var mFrameInterval: Long = 0 + + var mFramesPerUpdate = 1 + + fun getLastFrame(): Int { + if (mLastFrame < 0) { + return mLottie?.mFrameCount ?: 0 + } + return mLastFrame + } + + constructor(copy: LottieDrawableState?) { + if (copy != null) { + mLottie = Lottie(copy.mLottie) + mRepeatCount = copy.mRepeatCount + mRepeatMode = copy.mRepeatMode + mFramesPerUpdate = copy.mFramesPerUpdate + mSpeed = copy.mSpeed + updateFrameInterval() + } + } + + fun releaseLottie() { + mLottie?.destroy() + mLottie = null + } + + fun valid(): Boolean { + mLottie?.let { + return it.mNativePtr != 0L + } + return false + } + + fun setLottieSize(width: Int, height: Int) { + mLottie?.let { + if (width != it.mWidth || height != it.mHeight || it.mBuffer == null) { + it.mWidth = width + it.mHeight = height + mLottie?.setBufferSize(width, height) + invalidateSelf() + } + } + } + + fun getLottieBuffer(frame: Int): Bitmap? { + return mLottie?.getBuffer(frame) + } + + fun setRepeatMode(@RepeatMode mode: Int) { + mRepeatMode = mode + } + + fun setSpeed(@FloatRange(from = 0.0) speed: Float) { + mSpeed = speed + updateFrameInterval() + } + + fun setFirstFrame(frame: Int) { + mFirstFrame = min(frame, getLastFrame()) + updateFrameInterval() + } + + fun setLastFrame(frame: Int) { + mLastFrame = min(frame, mLottie?.mFrameCount ?: 0) + updateFrameInterval() + } + + fun updateFrameInterval() { + val frameCount = getLastFrame() - mFirstFrame + mFrameInterval = 16 + + val duration = mLottie?.mDuration?.toDouble() + if (frameCount <= 0 || duration == null || duration <= 0) { + return + } + mFrameInterval = (duration / max(frameCount.toDouble(), 1.0)).toLong() + } + + internal constructor() + + override fun newDrawable(): Drawable { + return ThorVGLottieDrawable(this) + } + + override fun getChangingConfigurations(): Int { + return 0 + } + } + + inner class Lottie { + internal var mNativePtr: Long = 0 + internal var mFrameCount = 0 + internal var mDuration: Long = 0 + internal var mWidth: Int = 0 + internal var mHeight: Int = 0 + internal var mBuffer: Bitmap? = null + private var colorReplacementTmp: IntArray? = null + private var useMoveColorTmp: Boolean = false + + @LoadedFrom + private var loadedFrom: Int = LoadedFrom.NO + private var filePathTmp: String? = null + + @RawRes + private var rawResTmp: Int? = null + + constructor() + + internal constructor(copy: Lottie?) { + if (copy == null || copy.loadedFrom == LoadedFrom.NO) { + mNativePtr = 0 + loadedFrom = LoadedFrom.NO + return + } + filePathTmp = copy.filePathTmp + rawResTmp = copy.rawResTmp + loadedFrom = copy.loadedFrom + useMoveColorTmp = copy.useMoveColorTmp + colorReplacementTmp = copy.colorReplacementTmp + when (copy.loadedFrom) { + LoadedFrom.RES -> { + rawResTmp?.let { + init(it, colorReplacementTmp, useMoveColorTmp) + } + } + + LoadedFrom.FILE -> { + filePathTmp?.let { + init(it, false, colorReplacementTmp, useMoveColorTmp) + } + } + } + if (mWidth != copy.mWidth || mHeight != copy.mHeight) { + mWidth = copy.mWidth + mHeight = copy.mHeight + } + if (copy.mBuffer != null) { + setBufferSize(mWidth, mHeight) + } + } + + fun init( + filePath: String, + canDeleteError: Boolean, + colorReplacement: IntArray?, + useMoveColor: Boolean + ): Lottie { + mNativePtr = 0 + val file = File(filePath) + if (!file.exists()) { + return this + } + val outValues = IntArray(LOTTIE_INFO_COUNT) + mNativePtr = nLoadFromFile(filePath, outValues, colorReplacement, useMoveColor) + if (mNativePtr == 0L) { + if (canDeleteError) { + file.delete() + } + return this + } + loadedFrom = LoadedFrom.FILE + filePathTmp = file.absolutePath + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + mFrameCount = outValues[LOTTIE_INFO_FRAME_COUNT] + mDuration = outValues[LOTTIE_INFO_DURATION] * 1000L + + mWidth = dp(outValues[LOTTIE_INFO_WIDTH]) + mHeight = dp(outValues[LOTTIE_INFO_HEIGHT]) + return this + } + + fun init( + @RawRes rawRes: Int, + colorReplacement: IntArray?, + useMoveColor: Boolean + ): Lottie { + mNativePtr = 0 + val jsonString = readRes(rawRes) ?: return this + + val outValues = IntArray(LOTTIE_INFO_COUNT) + mNativePtr = + nLoadFromMemory(jsonString.pointer, outValues, colorReplacement, useMoveColor) + if (mNativePtr == 0L) { + return this + } + loadedFrom = LoadedFrom.RES + rawResTmp = rawRes + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + mFrameCount = outValues[LOTTIE_INFO_FRAME_COUNT] + mDuration = outValues[LOTTIE_INFO_DURATION] * 1000L + + mWidth = dp(outValues[LOTTIE_INFO_WIDTH]) + mHeight = dp(outValues[LOTTIE_INFO_HEIGHT]) + return this + } + + fun destroy() { + if (mBuffer != null) { + mBuffer?.recycle() + mBuffer = null + } + nDestroy(mNativePtr) + } + + fun setBufferSize(width: Int, height: Int) { + mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + mBuffer?.let { nSetBufferSize(mNativePtr, it, width.toFloat(), height.toFloat()) } + } + + fun getBuffer(frame: Int): Bitmap? { + mBuffer?.let { nGetFrame(mNativePtr, it, frame) } + return mBuffer + } + } + + interface LottieAnimationListener { + fun onAnimationStart() + fun onAnimationEnd() + fun onAnimationRepeat() + } + + companion object { + const val RESTART: Int = 1 + const val REVERSE: Int = 2 + private const val LOTTIE_INFO_FRAME_COUNT = 0 + private const val LOTTIE_INFO_DURATION = 1 + private const val LOTTIE_INFO_WIDTH = 2 + private const val LOTTIE_INFO_HEIGHT = 3 + private const val LOTTIE_INFO_COUNT = 4 + + internal fun dp(value: Int): Int { + return if (value == 0) { + 0 + } else ceil((density * value.toFloat()).toDouble()) + .toInt() + } + + internal fun readRes(@RawRes rawRes: Int): BufferWriteNative? { + var inputStream: InputStream? = null + return try { + inputStream = appContext.resources.openRawResource(rawRes) + val res = BufferWriteNative.fromStreamEndlessNull(inputStream) + if (res.bufferSize() <= 0) { + inputStream.close() + return null + } + res + } catch (_: Throwable) { + return null + } finally { + try { + inputStream?.close() + } catch (_: Throwable) { + } + } + } + } + + private external fun nLoadFromFile( + src: String?, + params: IntArray?, + colorReplacement: IntArray?, + useMoveColor: Boolean + ): Long + + private external fun nLoadFromMemory( + json: Long, + params: IntArray?, + colorReplacement: IntArray?, + useMoveColor: Boolean + ): Long + + private external fun nSetBufferSize( + ptr: Long, + bitmap: Bitmap, + width: Float, + height: Float + ) + + private external fun nGetFrame(ptr: Long, bitmap: Bitmap, frame: Int) + private external fun nDestroy(ptr: Long) +} \ No newline at end of file diff --git a/libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/thorvg/ThorVGRender.kt b/libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/animation/thorvg/ThorVGSVGRender.kt similarity index 95% rename from libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/thorvg/ThorVGRender.kt rename to libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/animation/thorvg/ThorVGSVGRender.kt index 7501d2c5a..5a9308cbc 100644 --- a/libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/thorvg/ThorVGRender.kt +++ b/libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/animation/thorvg/ThorVGSVGRender.kt @@ -1,4 +1,4 @@ -package dev.ragnarok.fenrir.module.thorvg +package dev.ragnarok.fenrir.module.animation.thorvg import android.graphics.Bitmap import androidx.annotation.RawRes @@ -6,7 +6,7 @@ import dev.ragnarok.fenrir.module.BufferWriteNative import dev.ragnarok.fenrir.module.FenrirNative import java.io.InputStream -object ThorVGRender { +object ThorVGSVGRender { private external fun registerColorsNative(name: String, value: Int) private external fun createBitmapNative(res: Long, bitmap: Bitmap?, w: Int, h: Int) diff --git a/libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/rlottie/RLottieDrawable.kt b/libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/rlottie/RLottieDrawable.kt deleted file mode 100644 index 7857d1ee4..000000000 --- a/libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/rlottie/RLottieDrawable.kt +++ /dev/null @@ -1,786 +0,0 @@ -package dev.ragnarok.fenrir.module.rlottie - -import android.graphics.* -import android.graphics.drawable.Animatable -import android.graphics.drawable.Drawable -import android.os.Handler -import android.os.Looper -import android.os.SystemClock -import android.view.HapticFeedbackConstants -import android.view.View -import androidx.annotation.RawRes -import dev.ragnarok.fenrir.module.BufferWriteNative -import dev.ragnarok.fenrir.module.BuildConfig -import dev.ragnarok.fenrir.module.DispatchQueuePool -import dev.ragnarok.fenrir.module.FenrirNative.appContext -import dev.ragnarok.fenrir.module.FenrirNative.density -import java.io.File -import java.io.InputStream -import java.lang.ref.WeakReference -import java.util.concurrent.CountDownLatch -import kotlin.math.abs -import kotlin.math.ceil - -class RLottieDrawable : Drawable, Animatable { - private external fun create( - src: String?, - params: IntArray?, - colorReplacement: IntArray?, - useMoveColor: Boolean - ): Long - - private external fun createWithJson( - json: Long, - params: IntArray?, - colorReplacement: IntArray?, - useMoveColor: Boolean - ): Long - - private external fun destroy(ptr: Long) - - private external fun setLayerColor(ptr: Long, layer: String, color: Int) - private external fun replaceColors(ptr: Long, colorReplacement: IntArray) - private external fun getFrame( - ptr: Long, - frame: Int, - bitmap: Bitmap?, - w: Int, - h: Int, - stride: Int, - clear: Boolean - ): Int - - private val metaData = IntArray(2) - private val newColorUpdates = HashMap() - private val pendingColorUpdates = HashMap() - private val dstRect = Rect() - private val paint = Paint(Paint.FILTER_BITMAP_FLAG) - private var width: Int = 0 - private var height: Int = 0 - private var timeBetweenFrames: Int = 0 - var customEndFrame = -1 - private set - private var playInDirectionOfCustomEndFrame = false - private var onFinishCallback: WeakReference? = null - private var autoRepeat = 1 - private var autoRepeatPlayCount = 0 - - @Volatile - private var nextFrameIsLast = false - private var loadFrameTask: Runnable? = null - - @Volatile - var renderingBitmap: Bitmap? = null - private set - - @Volatile - var nextRenderingBitmap: Bitmap? = null - private set - - @Volatile - var backgroundBitmap: Bitmap? = null - private set - private var waitingForNextTask = false - private var frameWaitSync: CountDownLatch? = null - private var destroyWhenDone = false - private var currentFrame = 0 - - @Volatile - private var isRunning = false - - @Volatile - private var isRecycled = false - - @Volatile - private var nativePtr: Long = 0 - - private var loadingInBackground = false - private var secondLoadingInBackground = false - private var destroyAfterLoading = false - private var newReplaceColors: IntArray? = null - private var pendingReplaceColors: IntArray? = null - private var vibrationPattern: HashMap? = null - private var finishFrame = 0 - private var currentParentView: WeakReference? = null - private var lastFrameTime: Long = 0 - private var decodeSingleFrame = false - private var singleFrameDecoded = false - private var uiRunnableNoFrame = Runnable { - loadFrameTask = null - decodeFrameFinishedInternal() - } - private var uiRunnable = Runnable { - singleFrameDecoded = true - invalidateInternal() - decodeFrameFinishedInternal() - } - private var forceFrameRedraw = false - private var applyingLayerColors = false - private var shouldLimitFps = false - private var loadFrameRunnable = Runnable { - if (isRecycled) { - return@Runnable - } - if (nativePtr == 0L) { - frameWaitSync?.countDown() - uiHandler.post(uiRunnableNoFrame) - return@Runnable - } - if (backgroundBitmap == null) { - try { - backgroundBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) - } catch (e: Throwable) { - if (BuildConfig.DEBUG) { - e.printStackTrace() - } - } - } - backgroundBitmap?.let { - try { - if (pendingColorUpdates.isNotEmpty()) { - for ((key, value) in pendingColorUpdates) { - setLayerColor(nativePtr, key, value) - } - pendingColorUpdates.clear() - } - } catch (_: Exception) { - } - pendingReplaceColors?.let { pit -> - replaceColors(nativePtr, pit) - pendingReplaceColors = null - } - try { - val result = getFrame( - nativePtr, - currentFrame, - it, - width, - height, - it.rowBytes, - true - ) - if (result == -1) { - uiHandler.post(uiRunnableNoFrame) - frameWaitSync?.countDown() - return@Runnable - } - nextRenderingBitmap = backgroundBitmap - val framesPerUpdates = if (shouldLimitFps) 2 else 1 - if (customEndFrame >= 0 && playInDirectionOfCustomEndFrame) { - if (currentFrame > customEndFrame) { - if (currentFrame - framesPerUpdates >= customEndFrame) { - currentFrame -= framesPerUpdates - nextFrameIsLast = false - } else { - nextFrameIsLast = true - } - } else { - if (currentFrame + framesPerUpdates < customEndFrame) { - currentFrame += framesPerUpdates - nextFrameIsLast = false - } else { - nextFrameIsLast = true - } - } - } else { - if (currentFrame + framesPerUpdates < (if (customEndFrame >= 0) customEndFrame else metaData[0])) { - if (autoRepeat == 3) { - nextFrameIsLast = true - autoRepeatPlayCount++ - } else { - currentFrame += framesPerUpdates - nextFrameIsLast = false - } - } else if (autoRepeat == 1) { - currentFrame = 0 - nextFrameIsLast = false - } else if (autoRepeat == 2) { - currentFrame = 0 - nextFrameIsLast = true - autoRepeatPlayCount++ - } else { - nextFrameIsLast = true - } - } - } catch (e: Exception) { - if (BuildConfig.DEBUG) { - e.printStackTrace() - } else { - return@Runnable - } - } - } - uiHandler.post(uiRunnable) - frameWaitSync?.countDown() - } - - private var scaleX = 1.0f - private var scaleY = 1.0f - private var applyTransformation = false - private var needScale = false - private var invalidateOnProgressSet = false - private var isInvalid = false - private var doNotRemoveInvalidOnFrameReady = false - - constructor( - file: File, - canDeleteError: Boolean, - w: Int, - h: Int, - limitFps: Boolean, - colorReplacement: IntArray?, - useMoveColor: Boolean - ) { - width = w - height = h - shouldLimitFps = limitFps - nativePtr = create( - file.absolutePath, - metaData, - colorReplacement, - useMoveColor - ) - if (nativePtr == 0L && canDeleteError) { - file.delete() - } - if (shouldLimitFps && metaData[1] < 60) { - shouldLimitFps = false - } - timeBetweenFrames = - if (shouldLimitFps) 33 else 16.coerceAtLeast((1000.0f / metaData[1]).toInt()) - } - - constructor( - jsonString: BufferWriteNative, - w: Int, - h: Int, - limitFps: Boolean, - colorReplacement: IntArray?, - useMoveColor: Boolean - ) { - width = w - height = h - shouldLimitFps = limitFps - nativePtr = createWithJson(jsonString.pointer, metaData, colorReplacement, useMoveColor) - jsonString.release() - if (shouldLimitFps && metaData[1] < 60) { - shouldLimitFps = false - } - timeBetweenFrames = - if (shouldLimitFps) 33 else 16.coerceAtLeast((1000.0f / metaData[1]).toInt()) - } - - constructor( - @RawRes rawRes: Int, - w: Int, - h: Int, - startDecode: Boolean, - colorReplacement: IntArray?, - useMoveColor: Boolean - ) { - width = w - height = h - autoRepeat = 0 - val jsonString = readRes(rawRes) ?: return - nativePtr = createWithJson(jsonString.pointer, metaData, colorReplacement, useMoveColor) - jsonString.release() - timeBetweenFrames = 16.coerceAtLeast((1000.0f / metaData[1]).toInt()) - if (startDecode) { - setAllowDecodeSingleFrame(true) - } - } - - private fun checkRunningTasks() { - if (!hasParentView() && nextRenderingBitmap != null && loadFrameTask != null) { - loadFrameTask = null - nextRenderingBitmap = null - } - } - - private fun decodeFrameFinishedInternal() { - if (destroyWhenDone) { - checkRunningTasks() - if (loadFrameTask == null && nativePtr != 0L) { - destroy(nativePtr) - nativePtr = 0 - } - } - if (nativePtr == 0L) { - recycleResources() - return - } - waitingForNextTask = true - if (!hasParentView()) { - stop() - } - scheduleNextGetFrame() - } - - private fun recycleResources() { - try { - if (renderingBitmap != null) { - renderingBitmap?.recycle() - renderingBitmap = null - } - if (backgroundBitmap != null) { - backgroundBitmap?.recycle() - backgroundBitmap = null - } - if (nextRenderingBitmap != null) { - nextRenderingBitmap?.recycle() - nextRenderingBitmap = null - } - } catch (e: Exception) { - if (BuildConfig.DEBUG) { - e.printStackTrace() - } - nextRenderingBitmap = null - renderingBitmap = null - backgroundBitmap = null - } - invalidateInternal() - } - - fun setOnFinishCallback(callback: Runnable?, frame: Int) { - if (callback != null) { - onFinishCallback = WeakReference(callback) - finishFrame = frame - } else if (onFinishCallback != null) { - onFinishCallback = null - } - } - - fun getCurrentFrame(): Int { - return currentFrame - } - - fun setCurrentFrame(frame: Int) { - setCurrentFrame(frame, true) - } - - val duration: Long - get() = (metaData[0] / metaData[1].toFloat() * 1000).toLong() - - fun setPlayInDirectionOfCustomEndFrame(value: Boolean) { - playInDirectionOfCustomEndFrame = value - } - - fun setCustomEndFrame(frame: Int): Boolean { - if (customEndFrame == frame || frame > metaData[0]) { - return false - } - customEndFrame = frame - return true - } - - val framesCount: Int - get() = metaData[0] - - private fun hasParentView(): Boolean { - return callback != null - } - - private fun invalidateInternal() { - if (callback != null) { - invalidateSelf() - } - } - - fun setAllowDecodeSingleFrame(value: Boolean) { - decodeSingleFrame = value - if (decodeSingleFrame) { - scheduleNextGetFrame() - } - } - - fun recycle() { - isRunning = false - isRecycled = true - checkRunningTasks() - if (loadingInBackground || secondLoadingInBackground) { - destroyAfterLoading = true - } else if (loadFrameTask == null) { - if (nativePtr != 0L) { - destroy(nativePtr) - nativePtr = 0 - } - recycleResources() - } else { - destroyWhenDone = true - } - } - - fun setAutoRepeat(value: Int) { - if (autoRepeat == 2 && value == 3 && currentFrame != 0) { - return - } - autoRepeat = value - } - - protected fun finalize() { - try { - recycle() - } catch (e: Exception) { - if (BuildConfig.DEBUG) { - e.printStackTrace() - } - } - } - - @Deprecated("", ReplaceWith("PixelFormat.TRANSPARENT", "android.graphics.PixelFormat")) - override fun getOpacity(): Int { - return PixelFormat.TRANSPARENT - } - - override fun start() { - if (isRunning || autoRepeat >= 2 && autoRepeatPlayCount != 0 || !hasParentView()) { - return - } - isRunning = true - if (invalidateOnProgressSet) { - isInvalid = true - if (loadFrameTask != null) { - doNotRemoveInvalidOnFrameReady = true - } - } - scheduleNextGetFrame() - invalidateInternal() - } - - fun restart(): Boolean { - if (autoRepeat < 2 || autoRepeatPlayCount == 0) { - return false - } - autoRepeatPlayCount = 0 - autoRepeat = 2 - start() - return true - } - - fun setVibrationPattern(pattern: HashMap?) { - vibrationPattern = pattern - } - - fun beginApplyLayerColors() { - applyingLayerColors = true - } - - fun commitApplyLayerColors() { - if (!applyingLayerColors) { - return - } - applyingLayerColors = false - if (!isRunning && decodeSingleFrame) { - if (currentFrame <= 2) { - currentFrame = 0 - } - nextFrameIsLast = false - singleFrameDecoded = false - if (!scheduleNextGetFrame()) { - forceFrameRedraw = true - } - } - invalidateInternal() - } - - fun replaceColors(colors: IntArray?) { - newReplaceColors = colors - requestRedrawColors() - } - - fun setLayerColor(layerName: String, color: Int) { - newColorUpdates[layerName] = color - requestRedrawColors() - } - - private fun requestRedrawColors() { - if (!applyingLayerColors && !isRunning && decodeSingleFrame) { - if (currentFrame <= 2) { - currentFrame = 0 - } - nextFrameIsLast = false - singleFrameDecoded = false - if (!scheduleNextGetFrame()) { - forceFrameRedraw = true - } - } - invalidateInternal() - } - - private fun scheduleNextGetFrame(): Boolean { - if (loadFrameTask != null || nextRenderingBitmap != null || nativePtr == 0L || loadingInBackground || destroyWhenDone || !isRunning && (!decodeSingleFrame || singleFrameDecoded)) { - return false - } - if (newColorUpdates.isNotEmpty()) { - pendingColorUpdates.putAll(newColorUpdates) - newColorUpdates.clear() - } - if (newReplaceColors != null) { - pendingReplaceColors = newReplaceColors - newReplaceColors = null - } - loadFrameRunnableQueue.execute(loadFrameRunnable.also { loadFrameTask = it }) - return true - } - - override fun stop() { - isRunning = false - } - - fun setCurrentFrame(frame: Int, async: Boolean) { - setCurrentFrame(frame, async, false) - } - - fun setCurrentFrame(frame: Int, async: Boolean, resetFrame: Boolean) { - if (frame < 0 || frame > metaData[0] || currentFrame == frame) { - return - } - currentFrame = frame - nextFrameIsLast = false - singleFrameDecoded = false - if (invalidateOnProgressSet) { - isInvalid = true - if (loadFrameTask != null) { - doNotRemoveInvalidOnFrameReady = true - } - } - if ((!async || resetFrame) && waitingForNextTask && nextRenderingBitmap != null) { - backgroundBitmap = nextRenderingBitmap - nextRenderingBitmap = null - loadFrameTask = null - waitingForNextTask = false - } - if (!async) { - if (loadFrameTask == null) { - frameWaitSync = CountDownLatch(1) - } - } - if (scheduleNextGetFrame()) { - if (!async) { - try { - frameWaitSync?.await() - } catch (e: Exception) { - if (BuildConfig.DEBUG) { - e.printStackTrace() - } - } - frameWaitSync = null - } - } else { - forceFrameRedraw = true - } - invalidateSelf() - } - - fun setProgressMs(ms: Long) { - val frameNum = (0L.coerceAtLeast(ms) / timeBetweenFrames % metaData[0]).toInt() - setCurrentFrame(frameNum, async = true, resetFrame = true) - } - - fun setProgress(progress: Float) { - setProgress(progress, true) - } - - fun setProgress(pProgress: Float, async: Boolean) { - var progress = pProgress - if (progress < 0.0f) { - progress = 0.0f - } else if (progress > 1.0f) { - progress = 1.0f - } - setCurrentFrame((metaData[0] * progress).toInt(), async) - } - - fun setCurrentParentView(view: View?) { - currentParentView = WeakReference(view) - callback = view - } - - override fun isRunning(): Boolean { - return isRunning - } - - override fun getIntrinsicHeight(): Int { - return height - } - - override fun getIntrinsicWidth(): Int { - return width - } - - override fun onBoundsChange(bounds: Rect) { - super.onBoundsChange(bounds) - applyTransformation = true - } - - private fun setCurrentFrame(now: Long, timeDiff: Long, timeCheck: Long, force: Boolean) { - backgroundBitmap = renderingBitmap - renderingBitmap = nextRenderingBitmap - nextRenderingBitmap = null - if (nextFrameIsLast) { - stop() - } - loadFrameTask = null - if (doNotRemoveInvalidOnFrameReady) { - doNotRemoveInvalidOnFrameReady = false - } else if (isInvalid) { - isInvalid = false - } - singleFrameDecoded = true - waitingForNextTask = false - lastFrameTime = - if (screenRefreshRate <= 60) { - now - } else { - now - 16L.coerceAtMost(timeDiff - timeCheck) - } - if (force && forceFrameRedraw) { - singleFrameDecoded = false - forceFrameRedraw = false - } - if (onFinishCallback != null && currentFrame >= finishFrame) { - val runnable = onFinishCallback?.get() - runnable?.run() - } - scheduleNextGetFrame() - } - - override fun draw(canvas: Canvas) { - if (nativePtr == 0L || destroyWhenDone) { - return - } - updateCurrentFrame() - if (!isInvalid) { - renderingBitmap?.let { - if (applyTransformation) { - dstRect.set(bounds) - scaleX = dstRect.width().toFloat() / width - scaleY = dstRect.height().toFloat() / height - applyTransformation = false - needScale = - !(abs(dstRect.width() - width) < dp(1f) && abs(dstRect.width() - width) < dp( - 1f - )) - } - if (!needScale) { - canvas.drawBitmap( - it, - dstRect.left.toFloat(), - dstRect.top.toFloat(), - paint - ) - } else { - canvas.save() - canvas.translate(dstRect.left.toFloat(), dstRect.top.toFloat()) - canvas.scale(scaleX, scaleY) - canvas.drawBitmap(it, 0f, 0f, paint) - canvas.restore() - } - if (isRunning) { - invalidateInternal() - } - } - } - } - - fun updateCurrentFrame() { - val now = SystemClock.elapsedRealtime() - val timeDiff = abs(now - lastFrameTime) - val timeCheck: Int = if (screenRefreshRate <= 60) { - timeBetweenFrames - 6 - } else { - timeBetweenFrames - } - if (isRunning) { - if (renderingBitmap == null && nextRenderingBitmap == null) { - scheduleNextGetFrame() - } else if (nextRenderingBitmap != null && (renderingBitmap == null || timeDiff >= timeCheck)) { - if (vibrationPattern != null && currentParentView != null && currentParentView?.get() != null) { - val force = vibrationPattern?.get(currentFrame - 1) - if (force != null) { - currentParentView?.get()?.performHapticFeedback( - if (force == 1) HapticFeedbackConstants.LONG_PRESS else HapticFeedbackConstants.KEYBOARD_TAP, - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING - ) - } - } - setCurrentFrame(now, timeDiff, timeCheck.toLong(), false) - } - } else if ((forceFrameRedraw || decodeSingleFrame && timeDiff >= timeCheck) && nextRenderingBitmap != null) { - setCurrentFrame(now, timeDiff, timeCheck.toLong(), true) - } - } - - override fun setAlpha(alpha: Int) { - paint.alpha = alpha - invalidateSelf() - } - - override fun setColorFilter(colorFilter: ColorFilter?) { - paint.colorFilter = colorFilter - invalidateSelf() - } - - override fun getMinimumHeight(): Int { - return height - } - - override fun getMinimumWidth(): Int { - return width - } - - val animatedBitmap: Bitmap? - get() { - if (renderingBitmap != null) { - return renderingBitmap - } else if (nextRenderingBitmap != null) { - return nextRenderingBitmap - } - return null - } - - fun hasBitmap(): Boolean { - return nativePtr != 0L && (renderingBitmap != null || nextRenderingBitmap != null) && !isInvalid - } - - fun setInvalidateOnProgressSet(value: Boolean) { - invalidateOnProgressSet = value - } - - companion object { - private val uiHandler = Handler(Looper.getMainLooper()) - private val loadFrameRunnableQueue = DispatchQueuePool(4) - private var screenRefreshRate = 60 - - fun updateScreenRefreshRate(rate: Int) { - screenRefreshRate = rate - } - - internal fun readRes(@RawRes rawRes: Int): BufferWriteNative? { - var inputStream: InputStream? = null - return try { - inputStream = appContext.resources.openRawResource(rawRes) - val res = BufferWriteNative.fromStreamEndlessNull(inputStream) - if (res.bufferSize() <= 0) { - inputStream.close() - return null - } - res - } catch (_: Throwable) { - return null - } finally { - try { - inputStream?.close() - } catch (_: Throwable) { - } - } - } - - internal fun dp(value: Float): Int { - return if (value == 0f) { - 0 - } else ceil((density * value).toDouble()) - .toInt() - } - } -} diff --git a/material/build.gradle b/material/build.gradle index f9d665ad6..bdc0d284c 100644 --- a/material/build.gradle +++ b/material/build.gradle @@ -30,6 +30,7 @@ def srcDirs = [ 'com/google/android/material/elevation', 'com/google/android/material/expandable', 'com/google/android/material/floatingactionbutton', + 'com/google/android/material/floatingtoolbar', 'com/google/android/material/imageview', 'com/google/android/material/internal', 'com/google/android/material/loadingindicator', diff --git a/material/java/com/google/android/material/behavior/HideBottomViewOnScrollBehavior.java b/material/java/com/google/android/material/behavior/HideBottomViewOnScrollBehavior.java index cad7c8591..81a2d1454 100644 --- a/material/java/com/google/android/material/behavior/HideBottomViewOnScrollBehavior.java +++ b/material/java/com/google/android/material/behavior/HideBottomViewOnScrollBehavior.java @@ -46,12 +46,15 @@ /** * The {@link Behavior} for a View within a {@link CoordinatorLayout} to hide the view off the * bottom of the screen when scrolling down, and show it when scrolling up. + * + * @deprecated Use {@link HideViewOnScrollBehavior} instead. + *

TODO(b/378132394): Migrate usages of this class to {@link HideViewOnScrollBehavior}. */ +@Deprecated public class HideBottomViewOnScrollBehavior extends CoordinatorLayout.Behavior { /** - * Interface definition for a listener to be notified when the bottom view scroll state - * changes. + * Interface definition for a listener to be notified when the bottom view scroll state changes. */ public interface OnScrollStateChangedListener { @@ -82,9 +85,8 @@ public interface OnScrollStateChangedListener { /** State of the bottom view when it's scrolled down. */ public static final int STATE_SCROLLED_DOWN = 1; - /** - * State of the bottom view when it's scrolled up. - */ + + /** State of the bottom view when it's scrolled up. */ public static final int STATE_SCROLLED_UP = 2; private int height = 0; @@ -187,7 +189,7 @@ public boolean isScrolledUp() { * screen. */ public void slideUp(@NonNull V child) { - slideUp(child, /*animate=*/ true); + slideUp(child, /* animate= */ true); } /** @@ -224,7 +226,7 @@ public boolean isScrolledDown() { * the screen. */ public void slideDown(@NonNull V child) { - slideDown(child, /*animate=*/ true); + slideDown(child, /* animate= */ true); } /** diff --git a/material/java/com/google/android/material/behavior/HideBottomViewOnScrollDelegate.java b/material/java/com/google/android/material/behavior/HideBottomViewOnScrollDelegate.java new file mode 100644 index 000000000..ff6374495 --- /dev/null +++ b/material/java/com/google/android/material/behavior/HideBottomViewOnScrollDelegate.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.material.behavior; + +import android.view.View; +import android.view.ViewGroup.MarginLayoutParams; +import android.view.ViewPropertyAnimator; +import androidx.annotation.NonNull; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.coordinatorlayout.widget.CoordinatorLayout.Behavior; + +/** + * The {@link Behavior} for a View within a {@link CoordinatorLayout} to hide the view off of the + * bottom of the screen when scrolling down, and show it when scrolling up. + */ +final class HideBottomViewOnScrollDelegate extends HideViewOnScrollDelegate { + + HideBottomViewOnScrollDelegate() {} + + @Override + int getViewEdge() { + return HideOnScrollView.EDGE_BOTTOM; + } + + @Override + int getSize(@NonNull V child, @NonNull MarginLayoutParams paramsCompat) { + return child.getMeasuredHeight() + paramsCompat.bottomMargin; + } + + @Override + void setAdditionalHiddenOffset( + @NonNull V child, int size, int additionalHiddenOffset) { + child.setTranslationY(size + additionalHiddenOffset); + } + + @Override + int getTargetTranslation() { + return 0; + } + + @Override + void setViewTranslation(@NonNull V child, int targetTranslation) { + child.setTranslationY(targetTranslation); + } + + @Override + ViewPropertyAnimator getViewTranslationAnimator( + @NonNull V child, int targetTranslation) { + return child.animate().translationY(targetTranslation); + } +} diff --git a/material/java/com/google/android/material/behavior/HideLeftViewOnScrollDelegate.java b/material/java/com/google/android/material/behavior/HideLeftViewOnScrollDelegate.java new file mode 100644 index 000000000..09c5fc4fb --- /dev/null +++ b/material/java/com/google/android/material/behavior/HideLeftViewOnScrollDelegate.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.material.behavior; + +import android.view.View; +import android.view.ViewGroup.MarginLayoutParams; +import android.view.ViewPropertyAnimator; +import androidx.annotation.NonNull; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.coordinatorlayout.widget.CoordinatorLayout.Behavior; + +/** + * The {@link Behavior} for a View within a {@link CoordinatorLayout} to hide the view off of the + * left side of the screen when scrolling down, and show it when scrolling up. + */ +final class HideLeftViewOnScrollDelegate extends HideViewOnScrollDelegate { + + HideLeftViewOnScrollDelegate() {} + + @Override + int getViewEdge() { + return HideOnScrollView.EDGE_LEFT; + } + + @Override + int getSize(@NonNull V child, @NonNull MarginLayoutParams paramsCompat) { + return child.getMeasuredWidth() + paramsCompat.leftMargin; + } + + @Override + void setAdditionalHiddenOffset( + @NonNull V child, int size, int additionalHiddenOffset) { + child.setTranslationX(size - additionalHiddenOffset); + } + + @Override + int getTargetTranslation() { + return 0; + } + + @Override + void setViewTranslation(@NonNull V child, int targetTranslation) { + child.setTranslationX(-targetTranslation); + } + + @Override + ViewPropertyAnimator getViewTranslationAnimator( + @NonNull V child, int targetTranslation) { + return child.animate().translationX(-targetTranslation); + } +} diff --git a/material/java/com/google/android/material/behavior/HideOnScrollView.java b/material/java/com/google/android/material/behavior/HideOnScrollView.java new file mode 100644 index 000000000..ec9e41e8e --- /dev/null +++ b/material/java/com/google/android/material/behavior/HideOnScrollView.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.material.behavior; + +import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; + +import androidx.annotation.IntDef; +import androidx.annotation.RestrictTo; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Class for constants and {@code IntDefs} to be shared between the different {@link + * HideViewOnScrollBehavior} variations. + */ +final class HideOnScrollView { + + /** The sheet slides out from the right edge of the screen. */ + static final int EDGE_RIGHT = 0; + + /** The sheet slides out from the bottom edge of the screen. */ + static final int EDGE_BOTTOM = 1; + + /** The sheet slides out from the left edge of the screen. */ + static final int EDGE_LEFT = 2; + + /** + * The edge of the screen that a sheet slides out from. + * + * @hide + */ + @RestrictTo(LIBRARY_GROUP) + @IntDef({EDGE_RIGHT, EDGE_BOTTOM, EDGE_LEFT}) + @Retention(RetentionPolicy.SOURCE) + @interface ViewEdge {} + + private HideOnScrollView() {} +} diff --git a/material/java/com/google/android/material/behavior/HideRightViewOnScrollDelegate.java b/material/java/com/google/android/material/behavior/HideRightViewOnScrollDelegate.java new file mode 100644 index 000000000..c2aabee94 --- /dev/null +++ b/material/java/com/google/android/material/behavior/HideRightViewOnScrollDelegate.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.material.behavior; + +import android.view.View; +import android.view.ViewGroup.MarginLayoutParams; +import android.view.ViewPropertyAnimator; +import androidx.annotation.NonNull; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.coordinatorlayout.widget.CoordinatorLayout.Behavior; + +/** + * The {@link Behavior} for a View within a {@link CoordinatorLayout} to hide the view off of the + * right of the screen when scrolling down, and show it when scrolling up. + */ +final class HideRightViewOnScrollDelegate extends HideViewOnScrollDelegate { + + HideRightViewOnScrollDelegate() {} + + @Override + int getViewEdge() { + return HideOnScrollView.EDGE_RIGHT; + } + + @Override + int getSize(@NonNull V child, @NonNull MarginLayoutParams paramsCompat) { + return child.getMeasuredWidth() + paramsCompat.rightMargin; + } + + @Override + void setAdditionalHiddenOffset( + @NonNull V child, int size, int additionalHiddenOffset) { + child.setTranslationX(size + additionalHiddenOffset); + } + + @Override + int getTargetTranslation() { + return 0; + } + + @Override + void setViewTranslation(@NonNull V child, int targetTranslation) { + child.setTranslationX(targetTranslation); + } + + @Override + ViewPropertyAnimator getViewTranslationAnimator( + @NonNull V child, int targetTranslation) { + return child.animate().translationX(targetTranslation); + } +} diff --git a/material/java/com/google/android/material/behavior/HideViewOnScrollBehavior.java b/material/java/com/google/android/material/behavior/HideViewOnScrollBehavior.java new file mode 100644 index 000000000..dca07c37d --- /dev/null +++ b/material/java/com/google/android/material/behavior/HideViewOnScrollBehavior.java @@ -0,0 +1,363 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.material.behavior; + +import com.google.android.material.R; + +import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; +import static com.google.android.material.behavior.HideOnScrollView.EDGE_BOTTOM; +import static com.google.android.material.behavior.HideOnScrollView.EDGE_LEFT; +import static com.google.android.material.behavior.HideOnScrollView.EDGE_RIGHT; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.TimeInterpolator; +import android.content.Context; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewPropertyAnimator; +import androidx.annotation.Dimension; +import androidx.annotation.IntDef; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RestrictTo; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.coordinatorlayout.widget.CoordinatorLayout.Behavior; +import androidx.coordinatorlayout.widget.CoordinatorLayout.LayoutParams; +import androidx.core.view.ViewCompat; +import com.google.android.material.animation.AnimationUtils; +import com.google.android.material.behavior.HideOnScrollView.ViewEdge; +import com.google.android.material.motion.MotionUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.LinkedHashSet; + +/** + * The {@link Behavior} for a View within a {@link CoordinatorLayout} to hide the view off of the + * edge of the screen when scrolling down, and show it when scrolling up. + * + *

Supports hiding the View off of three screen edges: {@link HideOnScrollView#EDGE_RIGHT}, + * {@link HideOnScrollView#EDGE_BOTTOM} and {@link HideOnScrollView#EDGE_LEFT}. + */ +public class HideViewOnScrollBehavior extends Behavior { + + private HideViewOnScrollDelegate hideOnScrollViewDelegate; + + /** + * Interface definition for a listener to be notified when the bottom view scroll state changes. + */ + public interface OnScrollStateChangedListener { + + /** + * Called when the view changes its scrolled state. + * + * @param view The scrollable view. + * @param newState The new state. This will be one of {@link #STATE_SCROLLED_IN} or {@link + * #STATE_SCROLLED_OUT}. + */ + void onStateChanged(@NonNull View view, @ScrollState int newState); + } + + @NonNull + private final LinkedHashSet onScrollStateChangedListeners = + new LinkedHashSet<>(); + + private static final int DEFAULT_ENTER_ANIMATION_DURATION_MS = 225; + private static final int DEFAULT_EXIT_ANIMATION_DURATION_MS = 175; + private static final int ENTER_ANIM_DURATION_ATTR = R.attr.motionDurationLong2; + private static final int EXIT_ANIM_DURATION_ATTR = R.attr.motionDurationMedium4; + private static final int ENTER_EXIT_ANIM_EASING_ATTR = R.attr.motionEasingEmphasizedInterpolator; + + private int enterAnimDuration; + private int exitAnimDuration; + @Nullable private TimeInterpolator enterAnimInterpolator; + @Nullable private TimeInterpolator exitAnimInterpolator; + + /** State of the view when it's scrolled out. */ + public static final int STATE_SCROLLED_OUT = 1; + + /** State of the view when it's scrolled in. */ + public static final int STATE_SCROLLED_IN = 2; + + private int size = 0; + + /** + * Positions the scroll state can be set to. + * + * @hide + */ + @RestrictTo(LIBRARY_GROUP) + @IntDef({STATE_SCROLLED_OUT, STATE_SCROLLED_IN}) + @Retention(RetentionPolicy.SOURCE) + public @interface ScrollState {} + + @ScrollState private int currentState = STATE_SCROLLED_IN; + private int additionalHiddenOffset = 0; + @Nullable private ViewPropertyAnimator currentAnimator; + + public HideViewOnScrollBehavior() {} + + public HideViewOnScrollBehavior(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + private void setViewEdge(@NonNull V view, int layoutDirection) { + LayoutParams params = (LayoutParams) view.getLayoutParams(); + int viewGravity = params.gravity; + + if (isGravityBottom(viewGravity)) { + setViewEdge(EDGE_BOTTOM); + } else { + viewGravity = Gravity.getAbsoluteGravity(viewGravity, layoutDirection); + setViewEdge(isGravityLeft(viewGravity) ? EDGE_LEFT : EDGE_RIGHT); + } + } + + public void setViewEdge(@ViewEdge int viewEdge) { + if (hideOnScrollViewDelegate == null || hideOnScrollViewDelegate.getViewEdge() != viewEdge) { + switch (viewEdge) { + case EDGE_RIGHT: + this.hideOnScrollViewDelegate = new HideRightViewOnScrollDelegate(); + break; + case EDGE_BOTTOM: + this.hideOnScrollViewDelegate = new HideBottomViewOnScrollDelegate(); + break; + case EDGE_LEFT: + this.hideOnScrollViewDelegate = new HideLeftViewOnScrollDelegate(); + break; + default: + throw new IllegalArgumentException( + "Invalid view edge position value: " + + viewEdge + + ". Must be " + + EDGE_RIGHT + + ", " + + EDGE_BOTTOM + + " or " + + EDGE_LEFT + + "."); + } + } + } + + private boolean isGravityBottom(int viewGravity) { + return viewGravity == Gravity.BOTTOM || (viewGravity == (Gravity.BOTTOM | Gravity.CENTER)); + } + + private boolean isGravityLeft(int viewGravity) { + return viewGravity == Gravity.LEFT || (viewGravity == (Gravity.LEFT | Gravity.CENTER)); + } + + @Override + public boolean onLayoutChild( + @NonNull CoordinatorLayout parent, @NonNull V child, int layoutDirection) { + + ViewGroup.MarginLayoutParams marginParams = + (ViewGroup.MarginLayoutParams) child.getLayoutParams(); + setViewEdge(child, layoutDirection); + + this.size = hideOnScrollViewDelegate.getSize(child, marginParams); + + enterAnimDuration = + MotionUtils.resolveThemeDuration( + child.getContext(), ENTER_ANIM_DURATION_ATTR, DEFAULT_ENTER_ANIMATION_DURATION_MS); + exitAnimDuration = + MotionUtils.resolveThemeDuration( + child.getContext(), EXIT_ANIM_DURATION_ATTR, DEFAULT_EXIT_ANIMATION_DURATION_MS); + enterAnimInterpolator = + MotionUtils.resolveThemeInterpolator( + child.getContext(), + ENTER_EXIT_ANIM_EASING_ATTR, + AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR); + exitAnimInterpolator = + MotionUtils.resolveThemeInterpolator( + child.getContext(), + ENTER_EXIT_ANIM_EASING_ATTR, + AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR); + return super.onLayoutChild(parent, child, layoutDirection); + } + + /** + * Sets an additional offset used to hide the view. + * + * @param child the child view that is hidden by this behavior + * @param offset the additional offset in pixels that should be added when the view slides away + */ + public void setAdditionalHiddenOffset(@NonNull V child, @Dimension int offset) { + additionalHiddenOffset = offset; + + if (currentState == STATE_SCROLLED_OUT) { + hideOnScrollViewDelegate.setAdditionalHiddenOffset(child, size, additionalHiddenOffset); + } + } + + @Override + public boolean onStartNestedScroll( + @NonNull CoordinatorLayout coordinatorLayout, + @NonNull V child, + @NonNull View directTargetChild, + @NonNull View target, + int nestedScrollAxes, + int type) { + return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL; + } + + @Override + public void onNestedScroll( + @NonNull CoordinatorLayout coordinatorLayout, + @NonNull V child, + @NonNull View target, + int dxConsumed, + int dyConsumed, + int dxUnconsumed, + int dyUnconsumed, + int type, + @NonNull int[] consumed) { + if (dyConsumed > 0) { + slideOut(child); + } else if (dyConsumed < 0) { + slideIn(child); + } + } + + /** Returns true if the current state is scrolled in. */ + public boolean isScrolledIn() { + return currentState == STATE_SCROLLED_IN; + } + + /** + * Performs an animation that will slide the child from its current position to be totally on the + * screen. + */ + public void slideIn(@NonNull V child) { + slideIn(child, /* animate= */ true); + } + + /** + * Slides the child with or without animation from its current position to be totally on the + * screen. + * + * @param animate {@code true} to slide with animation. + */ + public void slideIn(@NonNull V child, boolean animate) { + if (isScrolledIn()) { + return; + } + + if (currentAnimator != null) { + currentAnimator.cancel(); + child.clearAnimation(); + } + updateCurrentState(child, STATE_SCROLLED_IN); + int targetTranslation = hideOnScrollViewDelegate.getTargetTranslation(); + + if (animate) { + animateChildTo(child, targetTranslation, enterAnimDuration, enterAnimInterpolator); + } else { + hideOnScrollViewDelegate.setViewTranslation(child, targetTranslation); + } + } + + /** Returns true if the current state is scrolled out. */ + public boolean isScrolledOut() { + return currentState == STATE_SCROLLED_OUT; + } + + /** + * Performs an animation that will slide the child from it's current position to be totally off + * the screen. + */ + public void slideOut(@NonNull V child) { + slideOut(child, /* animate= */ true); + } + + /** + * Slides the child with or without animation from its current position to be totally off the + * screen. + * + * @param animate {@code true} to slide with animation. + */ + public void slideOut(@NonNull V child, boolean animate) { + if (isScrolledOut()) { + return; + } + + if (currentAnimator != null) { + currentAnimator.cancel(); + child.clearAnimation(); + } + updateCurrentState(child, STATE_SCROLLED_OUT); + int targetTranslationY = size + additionalHiddenOffset; + if (animate) { + animateChildTo(child, targetTranslationY, exitAnimDuration, exitAnimInterpolator); + } else { + child.setTranslationY(targetTranslationY); + } + } + + private void updateCurrentState(@NonNull V child, @ScrollState int state) { + currentState = state; + for (OnScrollStateChangedListener listener : onScrollStateChangedListeners) { + listener.onStateChanged(child, currentState); + } + } + + private void animateChildTo( + @NonNull V child, + int targetTranslation, + long duration, + @NonNull TimeInterpolator interpolator) { + currentAnimator = + hideOnScrollViewDelegate + .getViewTranslationAnimator(child, targetTranslation) + .setInterpolator(interpolator) + .setDuration(duration) + .setListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + currentAnimator = null; + } + }); + } + + /** + * Adds a listener to be notified of View scroll state changes. + * + * @param listener The listener to notify when View scroll state changes. + */ + public void addOnScrollStateChangedListener(@NonNull OnScrollStateChangedListener listener) { + onScrollStateChangedListeners.add(listener); + } + + /** + * Removes a previously added listener. + * + * @param listener The listener to remove. + */ + public void removeOnScrollStateChangedListener(@NonNull OnScrollStateChangedListener listener) { + onScrollStateChangedListeners.remove(listener); + } + + /** Remove all previously added {@link OnScrollStateChangedListener}s. */ + public void clearOnScrollStateChangedListeners() { + onScrollStateChangedListeners.clear(); + } +} diff --git a/material/java/com/google/android/material/behavior/HideViewOnScrollDelegate.java b/material/java/com/google/android/material/behavior/HideViewOnScrollDelegate.java new file mode 100644 index 000000000..fa1288ad0 --- /dev/null +++ b/material/java/com/google/android/material/behavior/HideViewOnScrollDelegate.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.material.behavior; + +import android.view.View; +import android.view.ViewGroup.MarginLayoutParams; +import android.view.ViewPropertyAnimator; +import androidx.annotation.NonNull; +import com.google.android.material.behavior.HideOnScrollView.ViewEdge; + +/** A delegate for {@link HideViewOnScrollBehavior} to handle logic specific to the View's edge. */ +abstract class HideViewOnScrollDelegate { + /** + * Returns the edge of the screen from which the view should slide in and out. Must be a {@link + * com.google.android.material.behavior.HideOnScrollView.ViewEdge} value. + */ + @ViewEdge + abstract int getViewEdge(); + + /** + * Returns the size of the View. This is based on the height value for the bottom variation, and + * the width value for right and left variations. + */ + abstract int getSize(@NonNull V child, @NonNull MarginLayoutParams paramsCompat); + + /** + * Sets the additional offset to add when hiding the view. The offset will be added on the Y axis + * for the bottom variation, and on the X axis for the right and left variations. + */ + abstract void setAdditionalHiddenOffset( + @NonNull V child, int size, int additionalHiddenOffset); + + /** Returns the amount by which the View should be translated. */ + abstract int getTargetTranslation(); + + /** + * Sets the View's translation along the respective axis by the desired target translation amount. + */ + abstract void setViewTranslation(@NonNull V child, int targetTranslation); + + /** + * Returns an {@link ViewPropertyAnimator} that translates along the respective axis. + * + * @param child the View to animate + * @param targetTranslation the amount by which to translate the View + */ + abstract ViewPropertyAnimator getViewTranslationAnimator( + @NonNull V child, int targetTranslation); +} diff --git a/material/java/com/google/android/material/bottomnavigation/res/values/styles.xml b/material/java/com/google/android/material/bottomnavigation/res/values/styles.xml index b6d03a1aa..605e2d5a7 100644 --- a/material/java/com/google/android/material/bottomnavigation/res/values/styles.xml +++ b/material/java/com/google/android/material/bottomnavigation/res/values/styles.xml @@ -83,6 +83,7 @@ @dimen/m3_bottom_nav_min_height @style/ShapeAppearance.M3.Comp.NavigationBar.Container.Shape @dimen/m3_navigation_item_active_indicator_label_padding + @dimen/m3_navigation_item_active_indicator_label_padding + + + + + + + + + + diff --git a/material/java/com/google/android/material/dialog/res/values/themes_base.xml b/material/java/com/google/android/material/dialog/res/values/themes_base.xml index 44f00efee..6f9efc129 100644 --- a/material/java/com/google/android/material/dialog/res/values/themes_base.xml +++ b/material/java/com/google/android/material/dialog/res/values/themes_base.xml @@ -140,6 +140,9 @@ @style/Widget.Material3.Button.OutlinedButton @style/Widget.Material3.Button.ElevatedButton @style/Widget.Material3.MaterialButtonGroup + @style/Widget.Material3.MaterialSplitButton + @style/Widget.Material3.SplitButton.IconButton.Filled + @style/Widget.Material3.SplitButton.IconButton.Filled.Tonal @style/Widget.Material3.MaterialButtonToggleGroup ?attr/materialCardViewOutlinedStyle @style/Widget.Material3.CardView.Outlined @@ -422,6 +425,9 @@ @style/Widget.Material3.Button.OutlinedButton @style/Widget.Material3.Button.ElevatedButton @style/Widget.Material3.MaterialButtonGroup + @style/Widget.Material3.MaterialSplitButton + @style/Widget.Material3.SplitButton.IconButton.Filled + @style/Widget.Material3.SplitButton.IconButton.Filled.Tonal @style/Widget.Material3.MaterialButtonToggleGroup ?attr/materialCardViewOutlinedStyle @style/Widget.Material3.CardView.Outlined diff --git a/material/java/com/google/android/material/floatingactionbutton/ExtendedFloatingActionButton.java b/material/java/com/google/android/material/floatingactionbutton/ExtendedFloatingActionButton.java index 33074ae2b..ae6beabab 100644 --- a/material/java/com/google/android/material/floatingactionbutton/ExtendedFloatingActionButton.java +++ b/material/java/com/google/android/material/floatingactionbutton/ExtendedFloatingActionButton.java @@ -32,6 +32,7 @@ import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; +import android.graphics.Color; import android.graphics.Rect; import android.text.TextUtils; import android.util.AttributeSet; @@ -103,6 +104,8 @@ public class ExtendedFloatingActionButton extends MaterialButton implements Atta /** Strategy to extend the FAB. */ private static final int EXTEND = 3; + private boolean animationEnabled = true; + /** * The strategy type determines what motion strategy to apply on the FAB. * @@ -915,8 +918,22 @@ private boolean isOrWillBeHidden() { } } + /** + * Set whether or not animations are enabled. + */ + public void setAnimationEnabled(boolean animationEnabled) { + this.animationEnabled = animationEnabled; + } + + /** Return whether or not animations are enabled. */ + public boolean isAnimationEnabled() { + return animationEnabled; + } + private boolean shouldAnimateVisibilityChange() { - return (isLaidOut() || (!isOrWillBeShown() && animateShowBeforeLayout)) && !isInEditMode(); + return animationEnabled + && (isLaidOut() || (!isOrWillBeShown() && animateShowBeforeLayout)) + && !isInEditMode(); } /** @@ -1332,6 +1349,17 @@ public void performNow() { } layoutParams.width = size.getLayoutParams().width; layoutParams.height = size.getLayoutParams().height; + + if (extending) { // extending + silentlyUpdateTextColor(originalTextCsl); + } else if (getText() != null && getText() != "") { // shrinking + // We only update the text to transparent if it exists, otherwise, updating the + // text color to transparent affects the elevation of the view. + // It's okay for the text to be set afterwards and not be transparent, as it is cut off + // by forcing the layout param dimens. + silentlyUpdateTextColor(ColorStateList.valueOf(Color.TRANSPARENT)); + } + setPaddingRelative( size.getPaddingStart(), getPaddingTop(), diff --git a/material/java/com/google/android/material/floatingtoolbar/FloatingToolbarLayout.java b/material/java/com/google/android/material/floatingtoolbar/FloatingToolbarLayout.java new file mode 100644 index 000000000..92429f562 --- /dev/null +++ b/material/java/com/google/android/material/floatingtoolbar/FloatingToolbarLayout.java @@ -0,0 +1,112 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.material.floatingtoolbar; + +import com.google.android.material.R; + +import static com.google.android.material.theme.overlay.MaterialThemeOverlay.wrap; + +import android.content.Context; +import android.content.res.ColorStateList; +import androidx.appcompat.widget.TintTypedArray; +import android.util.AttributeSet; +import android.widget.FrameLayout; +import androidx.annotation.AttrRes; +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StyleRes; +import androidx.coordinatorlayout.widget.CoordinatorLayout.AttachedBehavior; +import com.google.android.material.behavior.HideViewOnScrollBehavior; +import com.google.android.material.internal.ThemeEnforcement; +import com.google.android.material.shape.MaterialShapeDrawable; +import com.google.android.material.shape.ShapeAppearanceModel; + +/** + * Provides an implementation of a floating toolbar. + * + *

Floating toolbars float above the body content and can be used to display contextual actions + * relevant to the body content or the specific page. + * + *

The floating toolbar supports a custom {@link android.view.ViewGroup} child. The toolbar + * provides styling for the provided child to give a uniform "floating toolbar" appearance to the + * {@link android.view.ViewGroup}. + */ +public class FloatingToolbarLayout extends FrameLayout implements AttachedBehavior { + private static final int DEF_STYLE_RES = R.style.Widget_Material3_FloatingToolbar; + @Nullable private Behavior behavior; + + public FloatingToolbarLayout(@NonNull Context context) { + this(context, null); + } + + public FloatingToolbarLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs, R.attr.floatingToolbarStyle); + } + + public FloatingToolbarLayout( + @NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { + this(context, attrs, defStyleAttr, DEF_STYLE_RES); + } + + public FloatingToolbarLayout( + @NonNull Context context, + @Nullable AttributeSet attrs, + @AttrRes int defStyleAttr, + @StyleRes int defStyleRes) { + super(wrap(context, attrs, defStyleAttr, defStyleRes), attrs, defStyleAttr); + + // Ensure we are using the correctly themed context rather than the context that was passed in. + context = getContext(); + + /* Custom attributes */ + TintTypedArray attributes = + ThemeEnforcement.obtainTintedStyledAttributes( + context, attrs, R.styleable.FloatingToolbar, defStyleAttr, defStyleRes); + + // Add a MaterialShapeDrawable as a background that supports tinting in every API level. + if (attributes.hasValue(R.styleable.FloatingToolbar_backgroundTint)) { + @ColorInt + int backgroundColor = attributes.getColor(R.styleable.FloatingToolbar_backgroundTint, 0); + + ShapeAppearanceModel shapeAppearanceModel = + ShapeAppearanceModel.builder(context, attrs, defStyleAttr, defStyleRes).build(); + MaterialShapeDrawable materialShapeDrawable = new MaterialShapeDrawable(shapeAppearanceModel); + materialShapeDrawable.setFillColor(ColorStateList.valueOf(backgroundColor)); + + setBackground(materialShapeDrawable); + } + + attributes.recycle(); + } + + @Override + @NonNull + public Behavior getBehavior() { + if (behavior == null) { + behavior = new Behavior(); + } + return behavior; + } + + /** + * Behavior designed for use with {@link FloatingToolbarLayout} instances. Its main function is to + * hide the {@link FloatingToolbarLayout} view when scrolling. Supports scrolling the floating + * toolbar off of either the right, bottom or left edge of the screen. + */ + public static class Behavior extends HideViewOnScrollBehavior {} +} diff --git a/material/java/com/google/android/material/floatingtoolbar/res-public/values/public.xml b/material/java/com/google/android/material/floatingtoolbar/res-public/values/public.xml new file mode 100644 index 000000000..ab0ec220f --- /dev/null +++ b/material/java/com/google/android/material/floatingtoolbar/res-public/values/public.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/material/java/com/google/android/material/floatingtoolbar/res/values/attrs.xml b/material/java/com/google/android/material/floatingtoolbar/res/values/attrs.xml new file mode 100644 index 000000000..9bb6373d8 --- /dev/null +++ b/material/java/com/google/android/material/floatingtoolbar/res/values/attrs.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + diff --git a/material/java/com/google/android/material/floatingtoolbar/res/values/dimens.xml b/material/java/com/google/android/material/floatingtoolbar/res/values/dimens.xml new file mode 100644 index 000000000..203c497ba --- /dev/null +++ b/material/java/com/google/android/material/floatingtoolbar/res/values/dimens.xml @@ -0,0 +1,21 @@ + + + + + 64dp + 64dp + diff --git a/material/java/com/google/android/material/floatingtoolbar/res/values/styles.xml b/material/java/com/google/android/material/floatingtoolbar/res/values/styles.xml new file mode 100644 index 000000000..d1ef280a2 --- /dev/null +++ b/material/java/com/google/android/material/floatingtoolbar/res/values/styles.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/material/java/com/google/android/material/navigation/NavigationBarItemView.java b/material/java/com/google/android/material/navigation/NavigationBarItemView.java index 714e9dd78..b76a8e9c5 100644 --- a/material/java/com/google/android/material/navigation/NavigationBarItemView.java +++ b/material/java/com/google/android/material/navigation/NavigationBarItemView.java @@ -94,6 +94,7 @@ public abstract class NavigationBarItemView extends FrameLayout private int itemPaddingTop; private int itemPaddingBottom; private int activeIndicatorLabelPadding; + private int iconLabelHorizontalSpacing; private float shiftAmountY; private float scaleUpFactor; private float scaleDownFactor; @@ -187,6 +188,7 @@ public NavigationBarItemView(@NonNull Context context) { itemPaddingTop = getResources().getDimensionPixelSize(getItemDefaultMarginResId()); itemPaddingBottom = labelGroup.getPaddingBottom(); activeIndicatorLabelPadding = 0; + iconLabelHorizontalSpacing = 0; // The labels used aren't always visible, so they are unreliable for accessibility. Instead, @@ -208,11 +210,11 @@ public NavigationBarItemView(@NonNull Context context) { } // If item icon gravity is start, we want to update the active indicator width in a layout // change listener to keep the active indicator size up to date with the content width. + LayoutParams lp = (LayoutParams) innerContentContainer.getLayoutParams(); + int newWidth = right - left + lp.rightMargin + lp.leftMargin; if (itemIconGravity == ITEM_ICON_GRAVITY_START && activeIndicatorExpandedDesiredWidth == ACTIVE_INDICATOR_WIDTH_WRAP_CONTENT - && (right - left) != (oldRight - oldLeft)) { - LayoutParams lp = (LayoutParams) innerContentContainer.getLayoutParams(); - int newWidth = right - left + lp.rightMargin + lp.leftMargin; + && newWidth != activeIndicatorView.getMeasuredWidth()) { LayoutParams indicatorParams = (LayoutParams) activeIndicatorView.getLayoutParams(); int minWidth = min( @@ -306,6 +308,16 @@ public int getItemPosition() { return itemPosition; } + @NonNull + public BaselineLayout getLabelGroup() { + return labelGroup; + } + + @NonNull + public BaselineLayout getExpandedLabelGroup() { + return expandedLabelGroup; + } + public void setShifting(boolean shifting) { if (isShifting != shifting) { isShifting = shifting; @@ -356,11 +368,8 @@ private void addDefaultExpandedLabelGroupViews() { LinearLayout.LayoutParams expandedLabelGroupLp = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); expandedLabelGroupLp.gravity = Gravity.CENTER; - expandedLabelGroupLp.rightMargin = - getLayoutDirection() == LAYOUT_DIRECTION_RTL ? activeIndicatorLabelPadding : 0; - expandedLabelGroupLp.leftMargin = - getLayoutDirection() == LAYOUT_DIRECTION_RTL ? 0 : activeIndicatorLabelPadding; innerContentContainer.addView(expandedLabelGroup, expandedLabelGroupLp); + setExpandedLabelGroupMargins(); } private void updateItemIconGravity() { @@ -785,6 +794,17 @@ public void setIconSize(int iconSize) { iconParams.width = iconSize; iconParams.height = iconSize; icon.setLayoutParams(iconParams); + // Reset expanded label group margins, in case the icon width is now 0 + setExpandedLabelGroupMargins(); + } + + private void setExpandedLabelGroupMargins() { + int margin = icon.getLayoutParams().width > 0 ? iconLabelHorizontalSpacing : 0; + LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) expandedLabelGroup.getLayoutParams(); + if (lp != null) { + lp.rightMargin = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? margin : 0; + lp.leftMargin = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? 0 : margin; + } } // TODO(b/338647654): We can remove this once navigation rail is updated @@ -1020,6 +1040,18 @@ public void setActiveIndicatorLabelPadding(int activeIndicatorLabelPadding) { } } + /** + * Set the horizontal distance between the icon and the item label which shows when the item is in + * the {@link NavigationBarView#ITEM_ICON_GRAVITY_START} configuration. + */ + public void setIconLabelHorizontalSpacing(int iconLabelHorizontalSpacing) { + if (this.iconLabelHorizontalSpacing != iconLabelHorizontalSpacing) { + this.iconLabelHorizontalSpacing = iconLabelHorizontalSpacing; + setExpandedLabelGroupMargins(); + requestLayout(); + } + } + /** Set whether or not this item should show an active indicator when checked. */ public void setActiveIndicatorEnabled(boolean enabled) { this.activeIndicatorEnabled = enabled; @@ -1081,7 +1113,11 @@ public void setActiveIndicatorExpandedHeight(int height) { private void updateActiveIndicatorLayoutParams(int availableWidth) { // Set width to the min of either the desired indicator width or the available width minus // a horizontal margin. - if (availableWidth <= 0) { + if (availableWidth <= 0 && getVisibility() == VISIBLE) { + // Return early if there's not yet an available width and the view is visible; this will be + // called again when there is an available width. Otherwise if the available width is 0 due to + // the view being gone, we still want to set layout params so that when the view appears, + // there is no jump in animation from turning visible and then adjusting the height/width. return; } @@ -1103,7 +1139,7 @@ private void updateActiveIndicatorLayoutParams(int availableWidth) { // If the label visibility is unlabeled, make the active indicator's height equal to its // width. indicatorParams.height = isActiveIndicatorResizeableAndUnlabeled() ? newWidth : newHeight; - indicatorParams.width = newWidth; + indicatorParams.width = max(0, newWidth); activeIndicatorView.setLayoutParams(indicatorParams); } diff --git a/material/java/com/google/android/material/navigation/NavigationBarMenuView.java b/material/java/com/google/android/material/navigation/NavigationBarMenuView.java index 9a3c8de5c..aa8bf770d 100644 --- a/material/java/com/google/android/material/navigation/NavigationBarMenuView.java +++ b/material/java/com/google/android/material/navigation/NavigationBarMenuView.java @@ -103,6 +103,7 @@ public abstract class NavigationBarMenuView extends ViewGroup implements MenuVie private int itemPaddingTop = NO_PADDING; private int itemPaddingBottom = NO_PADDING; private int itemActiveIndicatorLabelPadding = NO_PADDING; + private int iconLabelHorizontalSpacing = NO_PADDING; private boolean itemActiveIndicatorEnabled; private int itemActiveIndicatorWidth; private int itemActiveIndicatorHeight; @@ -522,6 +523,33 @@ public void setActiveIndicatorLabelPadding(@Px int activeIndicatorLabelPadding) } } + /** + * Get the horizontal distance between the item's icon and the label which + * is shown when the item is in the {@link NavigationBarView#ITEM_ICON_GRAVITY_START} + * configuration. + */ + @Px + public int getIconLabelHorizontalSpacing() { + return iconLabelHorizontalSpacing; + } + + /** + * Set the horizontal distance between the icon and the label which is shown when the item is in + * the {@link NavigationBarView#ITEM_ICON_GRAVITY_START} configuration. + */ + public void setIconLabelHorizontalSpacing( + @Px int iconLabelHorizontalSpacing) { + this.iconLabelHorizontalSpacing = iconLabelHorizontalSpacing; + if (buttons != null) { + for (NavigationBarMenuItemView item : buttons) { + if (item instanceof NavigationBarItemView) { + ((NavigationBarItemView) item) + .setIconLabelHorizontalSpacing(iconLabelHorizontalSpacing); + } + } + } + } + /** * Returns whether or not an active indicator is enabled for the navigation bar. * @@ -1052,6 +1080,9 @@ private NavigationBarItemView createMenuItem( if (itemActiveIndicatorLabelPadding != NO_PADDING) { child.setActiveIndicatorLabelPadding(itemActiveIndicatorLabelPadding); } + if (iconLabelHorizontalSpacing != NO_PADDING) { + child.setIconLabelHorizontalSpacing(iconLabelHorizontalSpacing); + } child.setActiveIndicatorWidth(itemActiveIndicatorWidth); child.setActiveIndicatorHeight(itemActiveIndicatorHeight); child.setActiveIndicatorExpandedWidth(itemActiveIndicatorExpandedWidth); diff --git a/material/java/com/google/android/material/navigation/NavigationBarView.java b/material/java/com/google/android/material/navigation/NavigationBarView.java index 3a42b05a8..d63ad2675 100644 --- a/material/java/com/google/android/material/navigation/NavigationBarView.java +++ b/material/java/com/google/android/material/navigation/NavigationBarView.java @@ -289,6 +289,11 @@ public NavigationBarView( attributes.getDimensionPixelSize(R.styleable.NavigationBarView_activeIndicatorLabelPadding, 0)); } + if (attributes.hasValue(R.styleable.NavigationBarView_iconLabelHorizontalSpacing)) { + setIconLabelHorizontalSpacing( + attributes.getDimensionPixelSize(R.styleable.NavigationBarView_iconLabelHorizontalSpacing, 0)); + } + if (attributes.hasValue(R.styleable.NavigationBarView_elevation)) { setElevation(attributes.getDimensionPixelSize(R.styleable.NavigationBarView_elevation, 0)); } @@ -718,6 +723,24 @@ public int getActiveIndicatorLabelPadding() { return menuView.getActiveIndicatorLabelPadding(); } + /** + * Set the horizontal distance between the icon and the item's label when the item is in the + * {@link NavigationBarView#ITEM_ICON_GRAVITY_START} configuration. + */ + public void setIconLabelHorizontalSpacing(@Px int iconLabelSpacing) { + menuView.setIconLabelHorizontalSpacing(iconLabelSpacing); + } + + /** + * Get the horizontal distance between the icon and the item's label when the item is in the + * {@link NavigationBarView#ITEM_ICON_GRAVITY_START} configuration. + */ + @Px + public int getIconLabelHorizontalSpacing() { + return menuView.getIconLabelHorizontalSpacing(); + } + + /** * Get whether or not a selected item should show an active indicator. * diff --git a/material/java/com/google/android/material/navigation/res-public/values/public.xml b/material/java/com/google/android/material/navigation/res-public/values/public.xml index b6510ff54..26219a998 100644 --- a/material/java/com/google/android/material/navigation/res-public/values/public.xml +++ b/material/java/com/google/android/material/navigation/res-public/values/public.xml @@ -56,6 +56,7 @@ + diff --git a/material/java/com/google/android/material/navigation/res/values/attrs.xml b/material/java/com/google/android/material/navigation/res/values/attrs.xml index 17d535f51..1080bd0c9 100644 --- a/material/java/com/google/android/material/navigation/res/values/attrs.xml +++ b/material/java/com/google/android/material/navigation/res/values/attrs.xml @@ -87,6 +87,10 @@ + + diff --git a/material/java/com/google/android/material/navigationrail/LabelMoveTransition.java b/material/java/com/google/android/material/navigationrail/LabelMoveTransition.java new file mode 100644 index 000000000..e59ef62eb --- /dev/null +++ b/material/java/com/google/android/material/navigationrail/LabelMoveTransition.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.material.navigationrail; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.transition.Transition; +import androidx.transition.TransitionValues; +import com.google.android.material.navigation.NavigationBarMenuItemView; + +/** + * A {@link Transition} that animates the {@link NavigationBarMenuItemView} label horizontally when + * the label is fading in. + */ +class LabelMoveTransition extends Transition { + + private static final String LABEL_VISIBILITY = "NavigationRailLabelVisibility"; + private static final float HORIZONTAL_DISTANCE = -30f; + + @Override + public void captureStartValues(@NonNull TransitionValues transitionValues) { + transitionValues.values.put(LABEL_VISIBILITY, transitionValues.view.getVisibility()); + } + + @Override + public void captureEndValues(@NonNull TransitionValues transitionValues) { + transitionValues.values.put(LABEL_VISIBILITY, transitionValues.view.getVisibility()); + } + + @Nullable + @Override + public Animator createAnimator(@NonNull ViewGroup sceneRoot, + @Nullable TransitionValues startValues, + @Nullable TransitionValues endValues) { + if (startValues == null || endValues == null + || startValues.values.get(LABEL_VISIBILITY) == null + || endValues.values.get(LABEL_VISIBILITY) == null) { + return super.createAnimator(sceneRoot, startValues, endValues); + } + // Only animate if the view is appearing + if ((int) startValues.values.get(LABEL_VISIBILITY) != View.GONE + || (int) endValues.values.get(LABEL_VISIBILITY) != View.VISIBLE) { + return super.createAnimator(sceneRoot, startValues, endValues); + } + View view = endValues.view; + ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f); + animator.addUpdateListener( + animation -> { + float progress = animation.getAnimatedFraction(); + view.setTranslationX(HORIZONTAL_DISTANCE * (1 - progress)); + }); + return animator; + } +} diff --git a/material/java/com/google/android/material/navigationrail/NavigationRailView.java b/material/java/com/google/android/material/navigationrail/NavigationRailView.java index 4c562194b..7107eec6d 100644 --- a/material/java/com/google/android/material/navigationrail/NavigationRailView.java +++ b/material/java/com/google/android/material/navigationrail/NavigationRailView.java @@ -25,12 +25,15 @@ import static java.lang.Math.max; import static java.lang.Math.min; +import android.animation.TimeInterpolator; import android.content.Context; import androidx.appcompat.widget.TintTypedArray; import android.util.AttributeSet; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; +import android.view.animation.PathInterpolator; import android.widget.FrameLayout; import android.widget.ScrollView; import androidx.annotation.LayoutRes; @@ -40,10 +43,16 @@ import androidx.annotation.RestrictTo; import androidx.core.graphics.Insets; import androidx.core.view.WindowInsetsCompat; +import androidx.transition.ChangeBounds; +import androidx.transition.Fade; +import androidx.transition.Transition; +import androidx.transition.TransitionManager; +import androidx.transition.TransitionSet; import com.google.android.material.animation.AnimationUtils; import com.google.android.material.internal.ThemeEnforcement; import com.google.android.material.internal.ViewUtils; import com.google.android.material.internal.ViewUtils.RelativePadding; +import com.google.android.material.navigation.NavigationBarItemView; import com.google.android.material.navigation.NavigationBarView; import com.google.android.material.resources.MaterialResources; @@ -111,6 +120,14 @@ public class NavigationRailView extends NavigationBarView { private static final int DEFAULT_HEADER_GRAVITY = Gravity.TOP | Gravity.CENTER_HORIZONTAL; static final int NO_ITEM_MINIMUM_HEIGHT = -1; + // These are the values for the cubic bezier curve to mimic the spring curve with a damping + // ratio of 0.8 and stiffness value of 380. + private static final int EXPAND_DURATION = 500; + private static final TimeInterpolator CUBIC_BEZIER_INTERPOLATOR = + new PathInterpolator(0.38f, 1.21f, 0.22f, 1.00f); + + private static final int FADE_DURATION = 100; + private final int contentMarginTop; private final int headerMarginBottom; private final int minExpandedWidth; @@ -240,6 +257,74 @@ public NavigationRailView( applyWindowInsets(); } + private void startTransitionAnimation() { + if (!isLaidOut()) { + return; + } + Transition changeBoundsTransition = new ChangeBounds().setDuration(EXPAND_DURATION) + .setInterpolator(CUBIC_BEZIER_INTERPOLATOR); + Transition labelFadeInTransition = new Fade().setDuration(FADE_DURATION); + Transition labelFadeOutTransition = new Fade().setDuration(FADE_DURATION); + Transition labelHorizontalMoveTransition = new LabelMoveTransition(); + Transition fadingItemsTransition = new Fade().setDuration(FADE_DURATION); + // Remove all label groups from being targeted by ChangeBounds as we want a different transition + // for it + int childCount = getNavigationRailMenuView().getChildCount(); + for (int i = 0; i < childCount; i++) { + View item = getNavigationRailMenuView().getChildAt(i); + if (item instanceof NavigationBarItemView) { + // Exclude labels from ChangeBounds transition + changeBoundsTransition.excludeTarget(((NavigationBarItemView) item).getLabelGroup(), true); + changeBoundsTransition.excludeTarget( + ((NavigationBarItemView) item).getExpandedLabelGroup(), true); + + // If currently expanded, we are fading out the expanded label group and fading in the + // collapsed label group + if (expanded) { + labelFadeOutTransition.addTarget(((NavigationBarItemView) item).getExpandedLabelGroup()); + labelFadeInTransition.addTarget(((NavigationBarItemView) item).getLabelGroup()); + } else { + // Otherwise if we are collapsed, we fade out the collapsed label group and fade in the + // expanded + labelFadeOutTransition.addTarget(((NavigationBarItemView) item).getLabelGroup()); + labelFadeInTransition.addTarget(((NavigationBarItemView) item).getExpandedLabelGroup()); + } + labelHorizontalMoveTransition.addTarget( + ((NavigationBarItemView) item).getExpandedLabelGroup()); + } + fadingItemsTransition.addTarget(item); + } + + TransitionSet changeBoundsFadeLabelInTransition = new TransitionSet(); + changeBoundsFadeLabelInTransition.setOrdering(TransitionSet.ORDERING_TOGETHER); + changeBoundsFadeLabelInTransition + .addTransition(changeBoundsTransition) + .addTransition(labelFadeInTransition) + .addTransition(labelHorizontalMoveTransition); + + // If collapsed, we want to fade in the hidden nav items with the labels + if (!expanded) { + changeBoundsFadeLabelInTransition.addTransition(fadingItemsTransition); + } + + TransitionSet fadeOutTransitions = new TransitionSet(); + fadeOutTransitions.setOrdering(TransitionSet.ORDERING_TOGETHER); + fadeOutTransitions.addTransition(labelFadeOutTransition); + + // If expanded, we want to fade out the nav items to hide with the labels + if (expanded) { + fadeOutTransitions.addTransition(fadingItemsTransition); + } + + TransitionSet transitionSet = new TransitionSet(); + transitionSet.setOrdering(TransitionSet.ORDERING_SEQUENTIAL); + transitionSet + .addTransition(fadeOutTransitions) + .addTransition(changeBoundsFadeLabelInTransition); + + TransitionManager.beginDelayedTransition((ViewGroup) getParent(), transitionSet); + } + @Override public void setItemIconGravity(int itemIconGravity) { collapsedIconGravity = itemIconGravity; @@ -268,6 +353,7 @@ private void setExpanded(boolean expanded) { if (this.expanded == expanded) { return; } + startTransitionAnimation(); this.expanded = expanded; int iconGravity = collapsedIconGravity; int itemSpacing = collapsedItemSpacing; @@ -286,6 +372,29 @@ private void setExpanded(boolean expanded) { getNavigationRailMenuView().setExpanded(expanded); } + /** Expand the navigation rail. */ + public void expand() { + if (expanded) { + return; + } + setExpanded(true); + announceForAccessibility(getResources().getString(R.string.nav_rail_expanded_a11y_label)); + } + + /** Returns whether or not the navigation rail is currently expanded. **/ + public boolean isExpanded() { + return expanded; + } + + /** Collapse the navigation rail. */ + public void collapse() { + if (!expanded) { + return; + } + setExpanded(false); + announceForAccessibility(getResources().getString(R.string.nav_rail_collapsed_a11y_label)); + } + private void applyWindowInsets() { ViewUtils.doOnApplyWindowInsets( this, @@ -343,6 +452,9 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (expanded) { // Try measuring child with no other restrictions than existing measure spec measureChild(getNavigationRailMenuView(), widthMeasureSpec, heightMeasureSpec); + if (headerView != null) { + measureChild(headerView, widthMeasureSpec, heightMeasureSpec); + } // Measure properly with the max child width minWidthSpec = makeExpandedWidthMeasureSpec(widthMeasureSpec, getMaxChildWidth()); } @@ -444,8 +556,12 @@ public void setItemMinimumHeight(@Px int minHeight) { menuView.setItemMinimumHeight(minHeight); } - // TODO: b/361189184 - Make public once expanded state is public - private void setCollapsedItemMinimumHeight(@Px int minHeight) { + /** + * Sets the minimum height of a navigation rail menu item when the navigation rail is collapsed. + * + * @param minHeight the min height of the item when the nav rail is collapsed + */ + public void setCollapsedItemMinimumHeight(@Px int minHeight) { collapsedItemMinHeight = minHeight; if (!expanded) { ((NavigationRailMenuView) getMenuView()).setItemMinimumHeight(minHeight); @@ -461,8 +577,13 @@ public void setItemSpacing(@Px int itemSpacing) { getNavigationRailMenuView().setItemSpacing(itemSpacing); } - // TODO: b/361189184 - Make public once expanded state is public - private void setCollapsedItemSpacing(@Px int itemSpacing) { + /** + * Sets the padding in between the navigation rail menu items when the navigation rail is + * collapsed. + * + * @param itemSpacing the desired item spacing in between the items when the nav rail is collapsed + */ + public void setCollapsedItemSpacing(@Px int itemSpacing) { this.collapsedItemSpacing = itemSpacing; if (!expanded) { getNavigationRailMenuView().setItemSpacing(itemSpacing); @@ -514,6 +635,10 @@ private int makeExpandedWidthMeasureSpec(int measureSpec, int measuredWidth) { if (MeasureSpec.getMode(measureSpec) != MeasureSpec.EXACTLY) { int newWidth = max(measuredWidth, minWidth); + // Also take into account header view max + if (headerView != null) { + newWidth = max(newWidth, headerView.getMeasuredWidth()); + } newWidth = max(getSuggestedMinimumWidth(), min(newWidth, maxExpandedWidth)); return MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY); } @@ -531,6 +656,7 @@ private void addContentContainer() { contentContainer = new NavigationRailFrameLayout(getContext()); contentContainer.setPaddingTop(contentMarginTop); contentContainer.setScrollingEnabled(scrollingEnabled); + contentContainer.setClipChildren(false); contentContainer.setLayoutParams(new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); menuView.setLayoutParams(new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); contentContainer.addView(menuView); diff --git a/material/java/com/google/android/material/navigationrail/res/values/dimens.xml b/material/java/com/google/android/material/navigationrail/res/values/dimens.xml index acb7856a5..8e158f81e 100644 --- a/material/java/com/google/android/material/navigationrail/res/values/dimens.xml +++ b/material/java/com/google/android/material/navigationrail/res/values/dimens.xml @@ -39,6 +39,7 @@ 4dp 2dp 4dp + 8dp 56dp 20dp diff --git a/material/java/com/google/android/material/navigationrail/res/values/strings.xml b/material/java/com/google/android/material/navigationrail/res/values/strings.xml new file mode 100644 index 000000000..4c89e80a8 --- /dev/null +++ b/material/java/com/google/android/material/navigationrail/res/values/strings.xml @@ -0,0 +1,24 @@ + + + + + Expanded + + + Collapsed + + diff --git a/material/java/com/google/android/material/navigationrail/res/values/styles.xml b/material/java/com/google/android/material/navigationrail/res/values/styles.xml index 20aef2856..b93b31a32 100644 --- a/material/java/com/google/android/material/navigationrail/res/values/styles.xml +++ b/material/java/com/google/android/material/navigationrail/res/values/styles.xml @@ -87,6 +87,7 @@ @macro/m3_comp_navigation_rail_label_text_type @style/ThemeOverlay.Material3.NavigationRailView @dimen/m3_navigation_rail_icon_label_padding + @dimen/m3_navigation_rail_icon_label_horizontal_padding