From 2815419f4fa7c7aec1598c57d65046cb56dc13aa Mon Sep 17 00:00:00 2001 From: Artem Umerov Date: Sun, 16 Apr 2023 18:38:00 +0300 Subject: [PATCH] 21 21 --- app_fenrir/build.gradle | 3 - .../fenrir/api/adapters/AbsAdapter.kt | 19 + .../fenrir/api/adapters/VideoDtoAdapter.kt | 2 +- .../fragment/base/RecyclerMenuAdapter.kt | 16 +- .../userdetails/UserDetailsPresenter.kt | 16 +- .../fenrir/model/menu/AdvancedItem.kt | 9 +- .../ragnarok/fenrir/view/TouchImageView.kt | 16 +- .../main/res/layout/item_advanced_menu.xml | 1 - .../layout/item_advanced_menu_alternative.xml | 1 - app_filegallery/build.gradle | 3 - .../filegallery/api/adapters/AbsAdapter.kt | 19 + .../filegallery/view/TouchImageView.kt | 16 +- build.gradle | 2 +- gradle.properties | 3 +- gradle/wrapper/gradle-wrapper.properties | 2 +- .../main/java/com/github/luben/zstd/Zstd.java | 19 + .../github/luben/zstd/ZstdDictCompress.java | 29 + .../github/luben/zstd/ZstdDictDecompress.java | 28 + .../jni/animation/libyuv/include/libyuv/row.h | 9 +- .../libyuv/include/libyuv/scale_row.h | 2 + .../animation/libyuv/include/libyuv/version.h | 2 +- .../animation/libyuv/source/convert_argb.cc | 5 + .../libyuv/source/convert_from_argb.cc | 10 + .../jni/animation/libyuv/source/cpu_id.cc | 6 +- .../jni/animation/libyuv/source/row_any.cc | 644 +++++----- .../jni/animation/libyuv/source/row_rvv.cc | 58 +- .../jni/animation/libyuv/source/scale_any.cc | 16 + .../animation/libyuv/source/scale_common.cc | 15 +- .../jni/animation/libyuv/source/scale_neon.cc | 39 + .../animation/libyuv/source/scale_neon64.cc | 39 + .../jni/animation/libyuv/source/scale_uv.cc | 34 +- .../main/jni/compress/zstd/jni_fast_zstd.c | 41 +- .../src/main/jni/compress/zstd/jni_zstd.c | 14 + .../thorvg/src/lib/sw_engine/tvgSwCommon.h | 6 +- .../src/lib/sw_engine/tvgSwRenderer.cpp | 135 +- .../thorvg/src/lib/sw_engine/tvgSwRenderer.h | 1 + .../jni/thorvg/src/lib/sw_engine/tvgSwRle.cpp | 175 ++- .../src/main/jni/thorvg/src/lib/tvgCommon.h | 10 +- .../src/main/jni/thorvg/src/lib/tvgPaint.cpp | 53 +- .../src/main/jni/thorvg/src/lib/tvgPaint.h | 4 +- .../main/jni/thorvg/src/lib/tvgPictureImpl.h | 30 +- .../src/main/jni/thorvg/src/lib/tvgRender.h | 1 + .../main/jni/thorvg/src/lib/tvgSceneImpl.h | 29 +- .../main/jni/thorvg/src/lib/tvgShapeImpl.h | 2 +- .../jni/thorvg/src/lib/tvgTaskScheduler.h | 2 +- .../thorvg/src/loaders/svg/tvgSvgLoader.cpp | 1101 ++++++++++------- .../src/loaders/svg/tvgSvgLoaderCommon.h | 17 + material/build.gradle | 2 +- .../android/material/appbar/AppBarLayout.java | 16 +- .../bottomsheet/BottomSheetBehavior.java | 62 +- .../carousel/MaskableFrameLayout.java | 45 +- .../carousel/MultiBrowseCarouselStrategy.java | 470 +++++-- .../material/carousel/res/values/dimens.xml | 3 +- .../google/android/material/chip/Chip.java | 2 +- .../res/drawable-v21/m3_tabs_background.xml | 6 +- .../res/drawable-v23/m3_tabs_background.xml | 5 +- .../material/tabs/res/values/tokens.xml | 5 +- .../material/textfield/EndCompoundLayout.java | 15 + .../textfield/StartCompoundLayout.java | 15 + .../material/textfield/TextInputLayout.java | 165 ++- .../textfield/res-public/values/public.xml | 2 + .../material/textfield/res/values/attrs.xml | 9 + .../material/textfield/res/values/styles.xml | 20 +- .../res/values/tokens_dropdown_menu.xml | 10 +- .../textfield/res/values/tokens_textfield.xml | 13 +- 65 files changed, 2421 insertions(+), 1148 deletions(-) diff --git a/app_fenrir/build.gradle b/app_fenrir/build.gradle index 3f3ed6686..67f4bff3d 100644 --- a/app_fenrir/build.gradle +++ b/app_fenrir/build.gradle @@ -9,9 +9,6 @@ android { buildFeatures { aidl = true } - configurations { - compile.exclude module: 'support-v4' - } packagingOptions { exclude("META-INF/notice.txt") exclude("META-INF/license.txt") diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/adapters/AbsAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/adapters/AbsAdapter.kt index f8b800eb1..c55d72ed6 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/adapters/AbsAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/adapters/AbsAdapter.kt @@ -116,6 +116,25 @@ abstract class AbsAdapter(name: String) : KSerializer { } } + @JvmOverloads + fun optString(json: JsonObject?, names: List, fallback: String? = null): String? { + contract { + returns(true) implies (json != null) + } + json ?: return fallback + for (i in names) { + try { + val element = json[i] + if (element is JsonPrimitive) return element.content else continue + } catch (e: Exception) { + if (Constants.IS_DEBUG) { + e.printStackTrace() + } + continue + } + } + return fallback + } fun optBoolean(json: JsonObject?, name: String): Boolean { contract { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/adapters/VideoDtoAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/adapters/VideoDtoAdapter.kt index e1acd7f12..8820ed008 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/adapters/VideoDtoAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/adapters/VideoDtoAdapter.kt @@ -70,7 +70,7 @@ class VideoDtoAdapter : AbsAdapter("VKApiVideo") { dto.mp4_1440 = optString(filesRoot, "mp4_1440") dto.mp4_2160 = optString(filesRoot, "mp4_2160") dto.external = optString(filesRoot, "external") - dto.hls = optString(filesRoot, "hls") + dto.hls = optString(filesRoot, listOf("hls", "hls_ondemand")) dto.live = optString(filesRoot, "live") } val sz = diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/base/RecyclerMenuAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/base/RecyclerMenuAdapter.kt index 6cf2e27d0..310a90d3a 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/base/RecyclerMenuAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/base/RecyclerMenuAdapter.kt @@ -1,5 +1,8 @@ package dev.ragnarok.fenrir.fragment.base +import android.text.util.Linkify.EMAIL_ADDRESSES +import android.text.util.Linkify.PHONE_NUMBERS +import android.text.util.Linkify.WEB_URLS import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -75,7 +78,6 @@ class RecyclerMenuAdapter : RecyclerView.Adapter { section != previous.section } } - holder.headerRoot.setOnClickListener { } if (headerVisible) { holder.headerRoot.visibility = View.VISIBLE holder.headerText.text = section?.title?.getText(context) @@ -93,6 +95,11 @@ class RecyclerMenuAdapter : RecyclerView.Adapter { holder.itemTitle.text = item.title?.getText(context) holder.itemSubtitle.visibility = if (item.subtitle == null) View.GONE else View.VISIBLE + if (item.autolink) { + holder.itemSubtitle.autoLinkMask = WEB_URLS or EMAIL_ADDRESSES or PHONE_NUMBERS + } else { + holder.itemSubtitle.autoLinkMask = 0 + } holder.itemSubtitle.text = item.subtitle?.getText(context) val last = position == itemCount - 1 val dividerVisible: Boolean = if (last) { @@ -106,11 +113,8 @@ class RecyclerMenuAdapter : RecyclerView.Adapter { actionListener?.onClick(item) } holder.itemRoot.setOnLongClickListener { - if (actionListener != null) { - actionListener?.onLongClick(item) - return@setOnLongClickListener true - } - false + actionListener?.onLongClick(item) ?: return@setOnLongClickListener false + true } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/userdetails/UserDetailsPresenter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/userdetails/UserDetailsPresenter.kt index 8febaeb94..3ee9c0c25 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/userdetails/UserDetailsPresenter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/userdetails/UserDetailsPresenter.kt @@ -95,7 +95,11 @@ class UserDetailsPresenter( val domain = if (user.domain.nonNullNoEmpty()) "@" + user.domain else "@" + user.getOwnerObjectId() items.add( - AdvancedItem(1, AdvancedItem.TYPE_COPY_DETAILS_ONLY, Text(R.string.id)) + AdvancedItem( + 1, Text(R.string.id), + AdvancedItem.TYPE_COPY_DETAILS_ONLY, + autolink = false + ) .setSubtitle(Text(domain)) .setIcon(Icon.fromResources(R.drawable.person)) .setSection(mainSection) @@ -125,7 +129,7 @@ class UserDetailsPresenter( if (user.bdate.nonNullNoEmpty()) { val formatted = getDateWithZeros(user.bdate) items.add( - AdvancedItem(3, Text(R.string.birthday)) + AdvancedItem(3, Text(R.string.birthday), autolink = false) .setSubtitle(Text(formatted)) .setIcon(Icon.fromResources(R.drawable.cake)) .setSection(mainSection) @@ -173,7 +177,7 @@ class UserDetailsPresenter( } if (details.getSkype().nonNullNoEmpty()) { items.add( - AdvancedItem(9, AdvancedItem.TYPE_COPY_DETAILS_ONLY, Text(R.string.skype)) + AdvancedItem(9, Text(R.string.skype), AdvancedItem.TYPE_COPY_DETAILS_ONLY) .setSubtitle(Text(details.getSkype())) .setIcon(R.drawable.ic_skype) .setSection(mainSection) @@ -181,7 +185,7 @@ class UserDetailsPresenter( } if (details.getInstagram().nonNullNoEmpty()) { items.add( - AdvancedItem(10, AdvancedItem.TYPE_OPEN_URL, Text(R.string.instagram)) + AdvancedItem(10, Text(R.string.instagram), AdvancedItem.TYPE_OPEN_URL) .setSubtitle(Text(details.getInstagram())) .setUrlPrefix("https://www.instagram.com") .setIcon(R.drawable.instagram) @@ -190,7 +194,7 @@ class UserDetailsPresenter( } if (details.getTwitter().nonNullNoEmpty()) { items.add( - AdvancedItem(11, AdvancedItem.TYPE_OPEN_URL, Text(R.string.twitter)) + AdvancedItem(11, Text(R.string.twitter), AdvancedItem.TYPE_OPEN_URL) .setSubtitle(Text(details.getTwitter())) .setIcon(R.drawable.twitter) .setUrlPrefix("https://mobile.twitter.com") @@ -199,7 +203,7 @@ class UserDetailsPresenter( } if (details.getFacebook().nonNullNoEmpty()) { items.add( - AdvancedItem(12, AdvancedItem.TYPE_OPEN_URL, Text(R.string.facebook)) + AdvancedItem(12, Text(R.string.facebook), AdvancedItem.TYPE_OPEN_URL) .setSubtitle(Text(details.getFacebook())) .setIcon(R.drawable.facebook) .setUrlPrefix("https://m.facebook.com") diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/model/menu/AdvancedItem.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/model/menu/AdvancedItem.kt index a56aa63f2..dc7fb55a5 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/model/menu/AdvancedItem.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/model/menu/AdvancedItem.kt @@ -4,7 +4,12 @@ import androidx.annotation.DrawableRes import dev.ragnarok.fenrir.model.Icon import dev.ragnarok.fenrir.model.Text -class AdvancedItem(val key: Long, val type: Int, val title: Text?) { +class AdvancedItem( + val key: Long, + val title: Text?, + val type: Int = TYPE_DEFAULT, + val autolink: Boolean = true +) { var urlPrefix: String? = null private set var icon: Icon? = null @@ -16,8 +21,6 @@ class AdvancedItem(val key: Long, val type: Int, val title: Text?) { var tag: Any? = null private set - constructor(key: Long, title: Text?) : this(key, TYPE_DEFAULT, title) - fun setUrlPrefix(urlPrefix: String?): AdvancedItem { this.urlPrefix = urlPrefix return this 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 18e9fe8d6..426a40739 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 @@ -397,7 +397,7 @@ open class TouchImageView @JvmOverloads constructor( public override fun onSaveInstanceState(): Parcelable? { val bundle = Bundle() - bundle.putParcelable("instanceState", super.onSaveInstanceState()) + bundle.putParcelable(STATE, super.onSaveInstanceState()) bundle.putInt("orientation", orientation) bundle.putFloat("saveScale", currentZoom) bundle.putFloat("matchViewHeight", matchViewHeight) @@ -430,7 +430,7 @@ open class TouchImageView @JvmOverloads constructor( if (orientation != oldOrientation) { orientationJustChanged = true } - super.onRestoreInstanceState(state.getParcelableCompat("instanceState")) + super.onRestoreInstanceState(state.getParcelableCompat(STATE)) return } super.onRestoreInstanceState(state) @@ -574,10 +574,8 @@ open class TouchImageView @JvmOverloads constructor( resetZoom() scaleImage(scale.toDouble(), viewWidth / 2.toFloat(), viewHeight / 2.toFloat(), true) touchMatrix.getValues(floatMatrix) - floatMatrix[Matrix.MTRANS_X] = - (viewWidth - matchViewWidth) / 2 - focusX * (scale - 1) * matchViewWidth - floatMatrix[Matrix.MTRANS_Y] = - (viewHeight - matchViewHeight) / 2 - focusY * (scale - 1) * matchViewHeight + floatMatrix[Matrix.MTRANS_X] = -(focusX * imageWidth - viewWidth * 0.5f) + floatMatrix[Matrix.MTRANS_Y] = -(focusY * imageHeight - viewHeight * 0.5f) touchMatrix.setValues(floatMatrix) fixTrans() savePreviousImageValues() @@ -784,6 +782,10 @@ open class TouchImageView @JvmOverloads constructor( if (drawable == null || drawable.intrinsicWidth == 0 || drawable.intrinsicHeight == 0) { return } + @Suppress("SENSELESS_COMPARISON") + if (touchMatrix == null || prevMatrix == null) { + return + } if (userSpecifiedMinScale == AUTOMATIC_MIN_ZOOM) { minZoom = AUTOMATIC_MIN_ZOOM if (currentZoom < minScale) { @@ -1548,6 +1550,8 @@ open class TouchImageView @JvmOverloads constructor( } companion object { + private const val STATE = "instanceState" + // SuperMin and SuperMax multipliers. Determine how much the image can be zoomed below or above the zoom boundaries, // before animating back to the min/max zoom boundary. private const val SUPER_MIN_MULTIPLIER = .75f diff --git a/app_fenrir/src/main/res/layout/item_advanced_menu.xml b/app_fenrir/src/main/res/layout/item_advanced_menu.xml index e70501d62..a6b6a141b 100644 --- a/app_fenrir/src/main/res/layout/item_advanced_menu.xml +++ b/app_fenrir/src/main/res/layout/item_advanced_menu.xml @@ -75,7 +75,6 @@ android:id="@+id/item_subtitle" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:autoLink="all" android:textAppearance="@style/TextAppearance.Material3.TitleSmall" tools:text="Example" /> diff --git a/app_fenrir/src/main/res/layout/item_advanced_menu_alternative.xml b/app_fenrir/src/main/res/layout/item_advanced_menu_alternative.xml index 7e5ad5a86..c6fc1493e 100644 --- a/app_fenrir/src/main/res/layout/item_advanced_menu_alternative.xml +++ b/app_fenrir/src/main/res/layout/item_advanced_menu_alternative.xml @@ -75,7 +75,6 @@ android:id="@+id/item_subtitle" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:autoLink="all" android:textAppearance="@style/TextAppearance.Material3.TitleSmall" tools:text="Example" /> diff --git a/app_filegallery/build.gradle b/app_filegallery/build.gradle index bc04c1c2e..d0b5794b0 100644 --- a/app_filegallery/build.gradle +++ b/app_filegallery/build.gradle @@ -9,9 +9,6 @@ android { buildFeatures { aidl = true } - configurations { - compile.exclude module: 'support-v4' - } packagingOptions { exclude("META-INF/notice.txt") exclude("META-INF/license.txt") diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/adapters/AbsAdapter.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/adapters/AbsAdapter.kt index 754dea6e2..67dcce546 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/adapters/AbsAdapter.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/api/adapters/AbsAdapter.kt @@ -116,6 +116,25 @@ abstract class AbsAdapter(name: String) : KSerializer { } } + @JvmOverloads + fun optString(json: JsonObject?, names: List, fallback: String? = null): String? { + contract { + returns(true) implies (json != null) + } + json ?: return fallback + for (i in names) { + try { + val element = json[i] + if (element is JsonPrimitive) return element.content else continue + } catch (e: Exception) { + if (Constants.IS_DEBUG) { + e.printStackTrace() + } + continue + } + } + return fallback + } fun optBoolean(json: JsonObject?, name: String): Boolean { contract { 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 f9e7df0ba..382ba8a1a 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 @@ -310,7 +310,7 @@ open class TouchImageView @JvmOverloads constructor( public override fun onSaveInstanceState(): Parcelable? { val bundle = Bundle() - bundle.putParcelable("instanceState", super.onSaveInstanceState()) + bundle.putParcelable(STATE, super.onSaveInstanceState()) bundle.putInt("orientation", orientation) bundle.putFloat("saveScale", currentZoom) bundle.putFloat("matchViewHeight", matchViewHeight) @@ -343,7 +343,7 @@ open class TouchImageView @JvmOverloads constructor( if (orientation != oldOrientation) { orientationJustChanged = true } - super.onRestoreInstanceState(state.getParcelableCompat("instanceState")) + super.onRestoreInstanceState(state.getParcelableCompat(STATE)) return } super.onRestoreInstanceState(state) @@ -487,10 +487,8 @@ open class TouchImageView @JvmOverloads constructor( resetZoom() scaleImage(scale.toDouble(), viewWidth / 2.toFloat(), viewHeight / 2.toFloat(), true) touchMatrix.getValues(floatMatrix) - floatMatrix[Matrix.MTRANS_X] = - (viewWidth - matchViewWidth) / 2 - focusX * (scale - 1) * matchViewWidth - floatMatrix[Matrix.MTRANS_Y] = - (viewHeight - matchViewHeight) / 2 - focusY * (scale - 1) * matchViewHeight + floatMatrix[Matrix.MTRANS_X] = -(focusX * imageWidth - viewWidth * 0.5f) + floatMatrix[Matrix.MTRANS_Y] = -(focusY * imageHeight - viewHeight * 0.5f) touchMatrix.setValues(floatMatrix) fixTrans() savePreviousImageValues() @@ -697,6 +695,10 @@ open class TouchImageView @JvmOverloads constructor( if (drawable == null || drawable.intrinsicWidth == 0 || drawable.intrinsicHeight == 0) { return } + @Suppress("SENSELESS_COMPARISON") + if (touchMatrix == null || prevMatrix == null) { + return + } if (userSpecifiedMinScale == AUTOMATIC_MIN_ZOOM) { minZoom = AUTOMATIC_MIN_ZOOM if (currentZoom < minScale) { @@ -1461,6 +1463,8 @@ open class TouchImageView @JvmOverloads constructor( } companion object { + private const val STATE = "instanceState" + // SuperMin and SuperMax multipliers. Determine how much the image can be zoomed below or above the zoom boundaries, // before animating back to the min/max zoom boundary. private const val SUPER_MIN_MULTIPLIER = .75f diff --git a/build.gradle b/build.gradle index e135a194f..a93818d2a 100644 --- a/build.gradle +++ b/build.gradle @@ -91,7 +91,7 @@ buildscript { mavenCentral() } dependencies { - classpath "com.android.tools.build:gradle:8.0.0-rc01" + classpath "com.android.tools.build:gradle:8.0.0" classpath "com.google.gms:google-services:4.3.15" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" diff --git a/gradle.properties b/gradle.properties index fc95b09f5..446d927d0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,4 +3,5 @@ android.useAndroidX=true kotlin.code.style=official android.nonTransitiveRClass=true org.gradle.warning.mode=all -android.defaults.buildfeatures.buildconfig=true \ No newline at end of file +android.defaults.buildfeatures.buildconfig=true +android.enableJetifier=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 10e9fa27b..4172f742b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Wed Aug 17 12:17:04 MSK 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/libfenrir/src/main/java/com/github/luben/zstd/Zstd.java b/libfenrir/src/main/java/com/github/luben/zstd/Zstd.java index a5c494bee..5e37aaa24 100644 --- a/libfenrir/src/main/java/com/github/luben/zstd/Zstd.java +++ b/libfenrir/src/main/java/com/github/luben/zstd/Zstd.java @@ -685,6 +685,25 @@ public static long decompressedDirectByteBufferSize(ByteBuffer src, int srcPosit */ public static native long getDictIdFromDict(byte[] dict); + private static native long getDictIdFromDictDirect(ByteBuffer dict, int offset, int length); + + /** + * Get DictId of a dictionary + * + * @param dict dictionary as Direct ByteBuffer + * @return DictId or 0 if not available + */ + public static long getDictIdFromDictDirect(ByteBuffer dict) { + int length = dict.limit() - dict.position(); + if (!dict.isDirect()) { + throw new IllegalArgumentException("dict must be a direct buffer"); + } + if (length < 0) { + throw new IllegalArgumentException("dict cannot be empty."); + } + return getDictIdFromDictDirect(dict, dict.position(), length); + } + /* Stub methods for backward comatibility */ diff --git a/libfenrir/src/main/java/com/github/luben/zstd/ZstdDictCompress.java b/libfenrir/src/main/java/com/github/luben/zstd/ZstdDictCompress.java index d514e243f..f30137500 100644 --- a/libfenrir/src/main/java/com/github/luben/zstd/ZstdDictCompress.java +++ b/libfenrir/src/main/java/com/github/luben/zstd/ZstdDictCompress.java @@ -1,5 +1,7 @@ package com.github.luben.zstd; +import java.nio.ByteBuffer; + public class ZstdDictCompress extends SharedDictBase { private long nativePtr; @@ -8,6 +10,8 @@ public class ZstdDictCompress extends SharedDictBase { private native void init(byte[] dict, int dict_offset, int dict_size, int level); + private native void initDirect(ByteBuffer dict, int dict_offset, int dict_size, int level); + private native void free(); /** @@ -44,6 +48,31 @@ public ZstdDictCompress(byte[] dict, int offset, int length, int level) { storeFence(); } + /** + * Create a new dictionary for use with fast compress. The provided bytebuffer is available for reuse when the method returns. + * + * @param dict Direct ByteBuffer containing dictionary using position and limit to define range in buffer. + * @param level compression level + */ + public ZstdDictCompress(ByteBuffer dict, int level) { + this.level = level; + int length = dict.limit() - dict.position(); + if (!dict.isDirect()) { + throw new IllegalArgumentException("dict must be a direct buffer"); + } + if (length < 0) { + throw new IllegalArgumentException("dict cannot be empty."); + } + initDirect(dict, dict.position(), length, level); + + if (nativePtr == 0L) { + throw new IllegalStateException("ZSTD_createCDict failed"); + } + // Ensures that even if ZstdDictCompress is created and published through a race, no thread could observe + // nativePtr == 0. + storeFence(); + } + int level() { return level; } diff --git a/libfenrir/src/main/java/com/github/luben/zstd/ZstdDictDecompress.java b/libfenrir/src/main/java/com/github/luben/zstd/ZstdDictDecompress.java index 03658d54a..8903d37ad 100644 --- a/libfenrir/src/main/java/com/github/luben/zstd/ZstdDictDecompress.java +++ b/libfenrir/src/main/java/com/github/luben/zstd/ZstdDictDecompress.java @@ -1,11 +1,15 @@ package com.github.luben.zstd; +import java.nio.ByteBuffer; + public class ZstdDictDecompress extends SharedDictBase { private long nativePtr; private native void init(byte[] dict, int dict_offset, int dict_size); + private native void initDirect(ByteBuffer dict, int dict_offset, int dict_size); + private native void free(); /** @@ -37,6 +41,30 @@ public ZstdDictDecompress(byte[] dict, int offset, int length) { } + /** + * Create a new dictionary for use with fast decompress. The provided bytebuffer is available for reuse when the method returns. + * + * @param dict Direct ByteBuffer containing dictionary using position and limit to define range in buffer. + */ + public ZstdDictDecompress(ByteBuffer dict) { + + int length = dict.limit() - dict.position(); + if (!dict.isDirect()) { + throw new IllegalArgumentException("dict must be a direct buffer"); + } + if (length < 0) { + throw new IllegalArgumentException("dict cannot be empty."); + } + initDirect(dict, dict.position(), length); + + if (nativePtr == 0L) { + throw new IllegalStateException("ZSTD_createDDict failed"); + } + // Ensures that even if ZstdDictDecompress is created and published through a race, no thread could observe + // nativePtr == 0. + storeFence(); + } + @Override void doClose() { if (nativePtr != 0) { 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 08004c0cc..6140443b0 100644 --- a/libfenrir/src/main/jni/animation/libyuv/include/libyuv/row.h +++ b/libfenrir/src/main/jni/animation/libyuv/include/libyuv/row.h @@ -758,9 +758,12 @@ extern "C" { #endif #if !defined(LIBYUV_DISABLE_RVV) && defined(__riscv) +#define HAS_ARGBTORAWROW_RVV +#define HAS_ARGBTORGB24ROW_RVV #define HAS_RAWTOARGBROW_RVV -#define HAS_RAWTORGBAROW_RVV #define HAS_RAWTORGB24ROW_RVV +#define HAS_RAWTORGBAROW_RVV +#define HAS_RGB24TOARGBROW_RVV #endif #if defined(_MSC_VER) && !defined(__CLR_VER) && !defined(__clang__) @@ -2961,6 +2964,7 @@ void RGB24ToARGBRow_LSX(const uint8_t* src_rgb24, uint8_t* dst_argb, int width); void RGB24ToARGBRow_LASX(const uint8_t* src_rgb24, uint8_t* dst_argb, int width); +void RGB24ToARGBRow_RVV(const uint8_t* src_rgb24, uint8_t* dst_argb, int width); void RAWToARGBRow_NEON(const uint8_t* src_raw, uint8_t* dst_argb, int width); void RAWToRGBARow_NEON(const uint8_t* src_raw, uint8_t* dst_rgba, int width); void RAWToARGBRow_MSA(const uint8_t* src_raw, uint8_t* dst_argb, int width); @@ -3197,6 +3201,9 @@ void ARGBToARGB4444Row_LASX(const uint8_t* src_argb, uint8_t* dst_rgb, int width); +void ARGBToRAWRow_RVV(const uint8_t* src_argb, uint8_t* dst_raw, int width); +void ARGBToRGB24Row_RVV(const uint8_t* src_argb, uint8_t* dst_rgb24, int width); + void ARGBToRGBARow_C(const uint8_t* src_argb, uint8_t* dst_rgb, int width); void ARGBToRGB24Row_C(const uint8_t* src_argb, uint8_t* dst_rgb, int width); void ARGBToRAWRow_C(const uint8_t* src_argb, uint8_t* dst_rgb, int width); 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 7996ea05d..a7957c3fe 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 @@ -133,6 +133,8 @@ extern "C" { #define HAS_SCALEROWDOWN34_NEON #define HAS_SCALEROWDOWN38_NEON #define HAS_SCALEROWDOWN4_NEON +#define HAS_SCALEUVROWDOWN2_NEON +#define HAS_SCALEUVROWDOWN2LINEAR_NEON #define HAS_SCALEUVROWDOWN2BOX_NEON #define HAS_SCALEUVROWDOWNEVEN_NEON #define HAS_SCALEROWUP2_LINEAR_NEON 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 c2b342ef9..a7a656351 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 1865 +#define LIBYUV_VERSION 1866 #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 e25ecefa9..f490e9c13 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/convert_argb.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/convert_argb.cc @@ -3049,6 +3049,11 @@ int RGB24ToARGB(const uint8_t* src_rgb24, } } #endif +#if defined(HAS_RGB24TOARGBROW_RVV) + if (TestCpuFlag(kCpuHasRVV)) { + RGB24ToARGBRow = RGB24ToARGBRow_RVV; + } +#endif for (y = 0; y < height; ++y) { RGB24ToARGBRow(src_rgb24, dst_argb, width); diff --git a/libfenrir/src/main/jni/animation/libyuv/source/convert_from_argb.cc b/libfenrir/src/main/jni/animation/libyuv/source/convert_from_argb.cc index 55516cbd8..e5608adba 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/convert_from_argb.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/convert_from_argb.cc @@ -1487,6 +1487,11 @@ int ARGBToRGB24(const uint8_t* src_argb, } } #endif +#if defined(HAS_ARGBTORGB24ROW_RVV) + if (TestCpuFlag(kCpuHasRVV)) { + ARGBToRGB24Row = ARGBToRGB24Row_RVV; + } +#endif for (y = 0; y < height; ++y) { ARGBToRGB24Row(src_argb, dst_rgb24, width); @@ -1561,6 +1566,11 @@ int ARGBToRAW(const uint8_t* src_argb, } } #endif +#if defined(HAS_ARGBTORAWROW_RVV) + if (TestCpuFlag(kCpuHasRVV)) { + ARGBToRAWRow = ARGBToRAWRow_RVV; + } +#endif for (y = 0; y < height; ++y) { ARGBToRAWRow(src_argb, dst_raw, width); 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 409456e8f..efad1b361 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/cpu_id.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/cpu_id.cc @@ -137,7 +137,7 @@ static int GetXCR0() { // For Arm, but public to allow testing on any CPU LIBYUV_API SAFEBUFFERS int ArmCpuCaps(const char* cpuinfo_name) { char cpuinfo_line[512]; - FILE* f = fopen(cpuinfo_name, "r"); + FILE* f = fopen(cpuinfo_name, "re"); if (!f) { // Assume Neon if /proc/cpuinfo is unavailable. // This will occur for Chrome sandbox for Pepper or Render process. @@ -166,7 +166,7 @@ LIBYUV_API SAFEBUFFERS int ArmCpuCaps(const char* cpuinfo_name) { LIBYUV_API SAFEBUFFERS int MipsCpuCaps(const char* cpuinfo_name) { char cpuinfo_line[512]; int flag = 0x0; - FILE* f = fopen(cpuinfo_name, "r"); + FILE* f = fopen(cpuinfo_name, "re"); if (!f) { // Assume nothing if /proc/cpuinfo is unavailable. // This will occur for Chrome sandbox for Pepper or Render process. @@ -194,7 +194,7 @@ LIBYUV_API SAFEBUFFERS int MipsCpuCaps(const char* cpuinfo_name) { LIBYUV_API SAFEBUFFERS int RiscvCpuCaps(const char* cpuinfo_name) { char cpuinfo_line[512]; int flag = 0x0; - FILE* f = fopen(cpuinfo_name, "r"); + FILE* f = fopen(cpuinfo_name, "re"); if (!f) { // Assume nothing if /proc/cpuinfo is unavailable. // This will occur for Chrome sandbox for Pepper or Render process. 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 9d33a294a..37aa1fba8 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/row_any.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/row_any.cc @@ -19,7 +19,7 @@ namespace libyuv { extern "C" { #endif -// memset for temp is meant to clear the source buffer (not dest) so that +// memset for vin is meant to clear the source buffer so that // SIMD that reads full multiple of 16 bytes will not trigger msan errors. // memset is not needed for production, as the garbage values are processed but // not used, although there may be edge cases for subsampling. @@ -35,20 +35,20 @@ extern "C" { void NAMEANY(const uint8_t* y_buf, const uint8_t* u_buf, \ const uint8_t* v_buf, const uint8_t* a_buf, uint8_t* dst_ptr, \ int width) { \ - SIMD_ALIGNED(uint8_t temp[64 * 5]); \ - memset(temp, 0, 64 * 4); /* for msan */ \ + SIMD_ALIGNED(uint8_t vin[64 * 4]); \ + SIMD_ALIGNED(uint8_t vout[64]); \ + memset(vin, 0, sizeof(vin)); /* for msan */ \ int r = width & MASK; \ int n = width & ~MASK; \ if (n > 0) { \ ANY_SIMD(y_buf, u_buf, v_buf, a_buf, dst_ptr, n); \ } \ - memcpy(temp, y_buf + n, r); \ - memcpy(temp + 64, u_buf + (n >> UVSHIFT), SS(r, UVSHIFT)); \ - memcpy(temp + 128, v_buf + (n >> UVSHIFT), SS(r, UVSHIFT)); \ - memcpy(temp + 192, a_buf + n, r); \ - ANY_SIMD(temp, temp + 64, temp + 128, temp + 192, temp + 256, MASK + 1); \ - memcpy(dst_ptr + (n >> DUVSHIFT) * BPP, temp + 256, \ - SS(r, DUVSHIFT) * BPP); \ + memcpy(vin, y_buf + n, r); \ + memcpy(vin + 64, u_buf + (n >> UVSHIFT), SS(r, UVSHIFT)); \ + memcpy(vin + 128, v_buf + (n >> UVSHIFT), SS(r, UVSHIFT)); \ + memcpy(vin + 192, a_buf + n, r); \ + ANY_SIMD(vin, vin + 64, vin + 128, vin + 192, vout, MASK + 1); \ + memcpy(dst_ptr + (n >> DUVSHIFT) * BPP, vout, SS(r, DUVSHIFT) * BPP); \ } #ifdef HAS_MERGEARGBROW_SSE2 @@ -68,25 +68,25 @@ ANY41(MergeARGBRow_Any_NEON, MergeARGBRow_NEON, 0, 0, 4, 15) void NAMEANY(const uint8_t* y_buf, const uint8_t* u_buf, \ const uint8_t* v_buf, const uint8_t* a_buf, uint8_t* dst_ptr, \ const struct YuvConstants* yuvconstants, int width) { \ - SIMD_ALIGNED(uint8_t temp[64 * 5]); \ - memset(temp, 0, 64 * 4); /* for msan */ \ + SIMD_ALIGNED(uint8_t vin[64 * 4]); \ + SIMD_ALIGNED(uint8_t vout[64]); \ + memset(vin, 0, sizeof(vin)); /* for msan */ \ int r = width & MASK; \ int n = width & ~MASK; \ if (n > 0) { \ ANY_SIMD(y_buf, u_buf, v_buf, a_buf, dst_ptr, yuvconstants, n); \ } \ - memcpy(temp, y_buf + n, r); \ - memcpy(temp + 64, u_buf + (n >> UVSHIFT), SS(r, UVSHIFT)); \ - memcpy(temp + 128, v_buf + (n >> UVSHIFT), SS(r, UVSHIFT)); \ - memcpy(temp + 192, a_buf + n, r); \ + memcpy(vin, y_buf + n, r); \ + memcpy(vin + 64, u_buf + (n >> UVSHIFT), SS(r, UVSHIFT)); \ + memcpy(vin + 128, v_buf + (n >> UVSHIFT), SS(r, UVSHIFT)); \ + memcpy(vin + 192, a_buf + n, r); \ if (width & 1) { \ - temp[64 + SS(r, UVSHIFT)] = temp[64 + SS(r, UVSHIFT) - 1]; \ - temp[128 + SS(r, UVSHIFT)] = temp[128 + SS(r, UVSHIFT) - 1]; \ + vin[64 + SS(r, UVSHIFT)] = vin[64 + SS(r, UVSHIFT) - 1]; \ + vin[128 + SS(r, UVSHIFT)] = vin[128 + SS(r, UVSHIFT) - 1]; \ } \ - ANY_SIMD(temp, temp + 64, temp + 128, temp + 192, temp + 256, \ - yuvconstants, MASK + 1); \ - memcpy(dst_ptr + (n >> DUVSHIFT) * BPP, temp + 256, \ - SS(r, DUVSHIFT) * BPP); \ + ANY_SIMD(vin, vin + 64, vin + 128, vin + 192, vout, yuvconstants, \ + MASK + 1); \ + memcpy(dst_ptr + (n >> DUVSHIFT) * BPP, vout, SS(r, DUVSHIFT) * BPP); \ } #ifdef HAS_I444ALPHATOARGBROW_SSSE3 @@ -123,21 +123,20 @@ ANY41C(I422AlphaToARGBRow_Any_LASX, I422AlphaToARGBRow_LASX, 1, 0, 4, 15) void NAMEANY(const T* y_buf, const T* u_buf, const T* v_buf, const T* a_buf, \ uint8_t* dst_ptr, const struct YuvConstants* yuvconstants, \ int width) { \ - SIMD_ALIGNED(T temp[16 * 4]); \ - SIMD_ALIGNED(uint8_t out[64]); \ - memset(temp, 0, 16 * 4 * SBPP); /* for YUY2 and msan */ \ + SIMD_ALIGNED(T vin[16 * 4]); \ + SIMD_ALIGNED(uint8_t vout[64]); \ + memset(vin, 0, sizeof(vin)); /* for YUY2 and msan */ \ int r = width & MASK; \ int n = width & ~MASK; \ if (n > 0) { \ ANY_SIMD(y_buf, u_buf, v_buf, a_buf, dst_ptr, yuvconstants, n); \ } \ - memcpy(temp, y_buf + n, r * SBPP); \ - memcpy(temp + 16, u_buf + (n >> UVSHIFT), SS(r, UVSHIFT) * SBPP); \ - memcpy(temp + 32, v_buf + (n >> UVSHIFT), SS(r, UVSHIFT) * SBPP); \ - memcpy(temp + 48, a_buf + n, r * SBPP); \ - ANY_SIMD(temp, temp + 16, temp + 32, temp + 48, out, yuvconstants, \ - MASK + 1); \ - memcpy(dst_ptr + (n >> DUVSHIFT) * BPP, out, SS(r, DUVSHIFT) * BPP); \ + memcpy(vin, y_buf + n, r * SBPP); \ + memcpy(vin + 16, u_buf + (n >> UVSHIFT), SS(r, UVSHIFT) * SBPP); \ + memcpy(vin + 32, v_buf + (n >> UVSHIFT), SS(r, UVSHIFT) * SBPP); \ + memcpy(vin + 48, a_buf + n, r * SBPP); \ + ANY_SIMD(vin, vin + 16, vin + 32, vin + 48, vout, yuvconstants, MASK + 1); \ + memcpy(dst_ptr + (n >> DUVSHIFT) * BPP, vout, SS(r, DUVSHIFT) * BPP); \ } #ifdef HAS_I210ALPHATOARGBROW_SSSE3 @@ -190,20 +189,20 @@ ANY41CT(I410AlphaToARGBRow_Any_AVX2, #define ANY41PT(NAMEANY, ANY_SIMD, STYPE, SBPP, DTYPE, BPP, MASK) \ void NAMEANY(const STYPE* r_buf, const STYPE* g_buf, const STYPE* b_buf, \ const STYPE* a_buf, DTYPE* dst_ptr, int depth, int width) { \ - SIMD_ALIGNED(STYPE temp[16 * 4]); \ - SIMD_ALIGNED(DTYPE out[64]); \ - memset(temp, 0, 16 * 4 * SBPP); /* for YUY2 and msan */ \ + SIMD_ALIGNED(STYPE vin[16 * 4]); \ + SIMD_ALIGNED(DTYPE vout[64]); \ + memset(vin, 0, sizeof(vin)); /* for msan */ \ int r = width & MASK; \ int n = width & ~MASK; \ if (n > 0) { \ ANY_SIMD(r_buf, g_buf, b_buf, a_buf, dst_ptr, depth, n); \ } \ - memcpy(temp, r_buf + n, r * SBPP); \ - memcpy(temp + 16, g_buf + n, r * SBPP); \ - memcpy(temp + 32, b_buf + n, r * SBPP); \ - memcpy(temp + 48, a_buf + n, r * SBPP); \ - ANY_SIMD(temp, temp + 16, temp + 32, temp + 48, out, depth, MASK + 1); \ - memcpy((uint8_t*)dst_ptr + n * BPP, out, r * BPP); \ + memcpy(vin, r_buf + n, r * SBPP); \ + memcpy(vin + 16, g_buf + n, r * SBPP); \ + memcpy(vin + 32, b_buf + n, r * SBPP); \ + memcpy(vin + 48, a_buf + n, r * SBPP); \ + ANY_SIMD(vin, vin + 16, vin + 32, vin + 48, vout, depth, MASK + 1); \ + memcpy((uint8_t*)dst_ptr + n * BPP, vout, r * BPP); \ } #ifdef HAS_MERGEAR64ROW_AVX2 @@ -237,22 +236,22 @@ ANY41PT(MergeARGB16To8Row_Any_NEON, #undef ANY41PT // Any 3 planes to 1. -#define ANY31(NAMEANY, ANY_SIMD, UVSHIFT, DUVSHIFT, BPP, MASK) \ - void NAMEANY(const uint8_t* y_buf, const uint8_t* u_buf, \ - const uint8_t* v_buf, uint8_t* dst_ptr, int width) { \ - SIMD_ALIGNED(uint8_t temp[64 * 4]); \ - memset(temp, 0, 64 * 3); /* for YUY2 and msan */ \ - int r = width & MASK; \ - int n = width & ~MASK; \ - if (n > 0) { \ - ANY_SIMD(y_buf, u_buf, v_buf, dst_ptr, n); \ - } \ - memcpy(temp, y_buf + n, r); \ - memcpy(temp + 64, u_buf + (n >> UVSHIFT), SS(r, UVSHIFT)); \ - memcpy(temp + 128, v_buf + (n >> UVSHIFT), SS(r, UVSHIFT)); \ - ANY_SIMD(temp, temp + 64, temp + 128, temp + 192, MASK + 1); \ - memcpy(dst_ptr + (n >> DUVSHIFT) * BPP, temp + 192, \ - SS(r, DUVSHIFT) * BPP); \ +#define ANY31(NAMEANY, ANY_SIMD, UVSHIFT, DUVSHIFT, BPP, MASK) \ + void NAMEANY(const uint8_t* y_buf, const uint8_t* u_buf, \ + const uint8_t* v_buf, uint8_t* dst_ptr, int width) { \ + SIMD_ALIGNED(uint8_t vin[64 * 3]); \ + SIMD_ALIGNED(uint8_t vout[64]); \ + memset(vin, 0, sizeof(vin)); /* for YUY2 and msan */ \ + int r = width & MASK; \ + int n = width & ~MASK; \ + if (n > 0) { \ + ANY_SIMD(y_buf, u_buf, v_buf, dst_ptr, n); \ + } \ + memcpy(vin, y_buf + n, r); \ + memcpy(vin + 64, u_buf + (n >> UVSHIFT), SS(r, UVSHIFT)); \ + memcpy(vin + 128, v_buf + (n >> UVSHIFT), SS(r, UVSHIFT)); \ + ANY_SIMD(vin, vin + 64, vin + 128, vout, MASK + 1); \ + memcpy(dst_ptr + (n >> DUVSHIFT) * BPP, vout, SS(r, DUVSHIFT) * BPP); \ } // Merge functions. @@ -308,28 +307,27 @@ ANY31(BlendPlaneRow_Any_SSSE3, BlendPlaneRow_SSSE3, 0, 0, 1, 7) // Note that odd width replication includes 444 due to implementation // on arm that subsamples 444 to 422 internally. // Any 3 planes to 1 with yuvconstants -#define ANY31C(NAMEANY, ANY_SIMD, UVSHIFT, DUVSHIFT, BPP, MASK) \ - void NAMEANY(const uint8_t* y_buf, const uint8_t* u_buf, \ - const uint8_t* v_buf, uint8_t* dst_ptr, \ - const struct YuvConstants* yuvconstants, int width) { \ - SIMD_ALIGNED(uint8_t temp[128 * 4]); \ - memset(temp, 0, 128 * 3); /* for YUY2 and msan */ \ - int r = width & MASK; \ - int n = width & ~MASK; \ - if (n > 0) { \ - ANY_SIMD(y_buf, u_buf, v_buf, dst_ptr, yuvconstants, n); \ - } \ - memcpy(temp, y_buf + n, r); \ - memcpy(temp + 128, u_buf + (n >> UVSHIFT), SS(r, UVSHIFT)); \ - memcpy(temp + 256, v_buf + (n >> UVSHIFT), SS(r, UVSHIFT)); \ - if (width & 1) { \ - temp[128 + SS(r, UVSHIFT)] = temp[128 + SS(r, UVSHIFT) - 1]; \ - temp[256 + SS(r, UVSHIFT)] = temp[256 + SS(r, UVSHIFT) - 1]; \ - } \ - ANY_SIMD(temp, temp + 128, temp + 256, temp + 384, yuvconstants, \ - MASK + 1); \ - memcpy(dst_ptr + (n >> DUVSHIFT) * BPP, temp + 384, \ - SS(r, DUVSHIFT) * BPP); \ +#define ANY31C(NAMEANY, ANY_SIMD, UVSHIFT, DUVSHIFT, BPP, MASK) \ + void NAMEANY(const uint8_t* y_buf, const uint8_t* u_buf, \ + const uint8_t* v_buf, uint8_t* dst_ptr, \ + const struct YuvConstants* yuvconstants, int width) { \ + SIMD_ALIGNED(uint8_t vin[128 * 3]); \ + SIMD_ALIGNED(uint8_t vout[128]); \ + memset(vin, 0, sizeof(vin)); /* for YUY2 and msan */ \ + int r = width & MASK; \ + int n = width & ~MASK; \ + if (n > 0) { \ + ANY_SIMD(y_buf, u_buf, v_buf, dst_ptr, yuvconstants, n); \ + } \ + memcpy(vin, y_buf + n, r); \ + memcpy(vin + 128, u_buf + (n >> UVSHIFT), SS(r, UVSHIFT)); \ + memcpy(vin + 256, v_buf + (n >> UVSHIFT), SS(r, UVSHIFT)); \ + if (width & 1) { \ + vin[128 + SS(r, UVSHIFT)] = vin[128 + SS(r, UVSHIFT) - 1]; \ + vin[256 + SS(r, UVSHIFT)] = vin[256 + SS(r, UVSHIFT) - 1]; \ + } \ + ANY_SIMD(vin, vin + 128, vin + 256, vout, yuvconstants, MASK + 1); \ + memcpy(dst_ptr + (n >> DUVSHIFT) * BPP, vout, SS(r, DUVSHIFT) * BPP); \ } #ifdef HAS_I422TOARGBROW_SSSE3 @@ -429,19 +427,19 @@ ANY31C(I444ToARGBRow_Any_LSX, I444ToARGBRow_LSX, 0, 0, 4, 15) void NAMEANY(const T* y_buf, const T* u_buf, const T* v_buf, \ uint8_t* dst_ptr, const struct YuvConstants* yuvconstants, \ int width) { \ - SIMD_ALIGNED(T temp[16 * 3]); \ - SIMD_ALIGNED(uint8_t out[64]); \ - memset(temp, 0, 16 * 3 * SBPP); /* for YUY2 and msan */ \ + SIMD_ALIGNED(T vin[16 * 3]); \ + SIMD_ALIGNED(uint8_t vout[64]); \ + memset(vin, 0, sizeof(vin)); /* for YUY2 and msan */ \ int r = width & MASK; \ int n = width & ~MASK; \ if (n > 0) { \ ANY_SIMD(y_buf, u_buf, v_buf, dst_ptr, yuvconstants, n); \ } \ - memcpy(temp, y_buf + n, r * SBPP); \ - memcpy(temp + 16, u_buf + (n >> UVSHIFT), SS(r, UVSHIFT) * SBPP); \ - memcpy(temp + 32, v_buf + (n >> UVSHIFT), SS(r, UVSHIFT) * SBPP); \ - ANY_SIMD(temp, temp + 16, temp + 32, out, yuvconstants, MASK + 1); \ - memcpy(dst_ptr + (n >> DUVSHIFT) * BPP, out, SS(r, DUVSHIFT) * BPP); \ + memcpy(vin, y_buf + n, r * SBPP); \ + memcpy(vin + 16, u_buf + (n >> UVSHIFT), SS(r, UVSHIFT) * SBPP); \ + memcpy(vin + 32, v_buf + (n >> UVSHIFT), SS(r, UVSHIFT) * SBPP); \ + ANY_SIMD(vin, vin + 16, vin + 32, vout, yuvconstants, MASK + 1); \ + memcpy(dst_ptr + (n >> DUVSHIFT) * BPP, vout, SS(r, DUVSHIFT) * BPP); \ } #ifdef HAS_I210TOAR30ROW_SSSE3 @@ -486,19 +484,19 @@ ANY31CT(I212ToAR30Row_Any_AVX2, I212ToAR30Row_AVX2, 1, 0, uint16_t, 2, 4, 15) #define ANY31PT(NAMEANY, ANY_SIMD, STYPE, SBPP, DTYPE, BPP, MASK) \ void NAMEANY(const STYPE* r_buf, const STYPE* g_buf, const STYPE* b_buf, \ DTYPE* dst_ptr, int depth, int width) { \ - SIMD_ALIGNED(STYPE temp[16 * 3]); \ - SIMD_ALIGNED(DTYPE out[64]); \ - memset(temp, 0, 16 * 3 * SBPP); /* for YUY2 and msan */ \ + SIMD_ALIGNED(STYPE vin[16 * 3]); \ + SIMD_ALIGNED(DTYPE vout[64]); \ + memset(vin, 0, sizeof(vin)); /* for YUY2 and msan */ \ int r = width & MASK; \ int n = width & ~MASK; \ if (n > 0) { \ ANY_SIMD(r_buf, g_buf, b_buf, dst_ptr, depth, n); \ } \ - memcpy(temp, r_buf + n, r * SBPP); \ - memcpy(temp + 16, g_buf + n, r * SBPP); \ - memcpy(temp + 32, b_buf + n, r * SBPP); \ - ANY_SIMD(temp, temp + 16, temp + 32, out, depth, MASK + 1); \ - memcpy((uint8_t*)dst_ptr + n * BPP, out, r * BPP); \ + memcpy(vin, r_buf + n, r * SBPP); \ + memcpy(vin + 16, g_buf + n, r * SBPP); \ + memcpy(vin + 32, b_buf + n, r * SBPP); \ + ANY_SIMD(vin, vin + 16, vin + 32, vout, depth, MASK + 1); \ + memcpy((uint8_t*)dst_ptr + n * BPP, vout, r * BPP); \ } #ifdef HAS_MERGEXR30ROW_AVX2 @@ -550,18 +548,19 @@ ANY31PT(MergeXRGB16To8Row_Any_NEON, #define ANY21(NAMEANY, ANY_SIMD, UVSHIFT, SBPP, SBPP2, BPP, MASK) \ void NAMEANY(const uint8_t* y_buf, const uint8_t* uv_buf, uint8_t* dst_ptr, \ int width) { \ - SIMD_ALIGNED(uint8_t temp[128 * 3]); \ - memset(temp, 0, 128 * 2); /* for msan */ \ + SIMD_ALIGNED(uint8_t vin[128 * 2]); \ + SIMD_ALIGNED(uint8_t vout[128]); \ + memset(vin, 0, sizeof(vin)); /* for msan */ \ int r = width & MASK; \ int n = width & ~MASK; \ if (n > 0) { \ ANY_SIMD(y_buf, uv_buf, dst_ptr, n); \ } \ - memcpy(temp, y_buf + n * SBPP, r * SBPP); \ - memcpy(temp + 128, uv_buf + (n >> UVSHIFT) * SBPP2, \ + memcpy(vin, y_buf + n * SBPP, r * SBPP); \ + memcpy(vin + 128, uv_buf + (n >> UVSHIFT) * SBPP2, \ SS(r, UVSHIFT) * SBPP2); \ - ANY_SIMD(temp, temp + 128, temp + 256, MASK + 1); \ - memcpy(dst_ptr + n * BPP, temp + 256, r * BPP); \ + ANY_SIMD(vin, vin + 128, vout, MASK + 1); \ + memcpy(dst_ptr + n * BPP, vout, r * BPP); \ } // Merge functions. @@ -681,18 +680,19 @@ ANY21(SobelXYRow_Any_LSX, SobelXYRow_LSX, 0, 1, 1, 4, 15) #define ANY21S(NAMEANY, ANY_SIMD, SBPP, BPP, MASK) \ void NAMEANY(const uint8_t* src_yuy2, int stride_yuy2, uint8_t* dst_uv, \ int width) { \ - SIMD_ALIGNED(uint8_t temp[32 * 3]); \ - memset(temp, 0, 32 * 2); /* for msan */ \ + SIMD_ALIGNED(uint8_t vin[32 * 2]); \ + SIMD_ALIGNED(uint8_t vout[32]); \ + memset(vin, 0, sizeof(vin)); /* for msan */ \ int awidth = (width + 1) / 2; \ int r = awidth & MASK; \ int n = awidth & ~MASK; \ if (n > 0) { \ ANY_SIMD(src_yuy2, stride_yuy2, dst_uv, n * 2); \ } \ - memcpy(temp, src_yuy2 + n * SBPP, r * SBPP); \ - memcpy(temp + 32, src_yuy2 + stride_yuy2 + n * SBPP, r * SBPP); \ - ANY_SIMD(temp, 32, temp + 64, MASK + 1); \ - memcpy(dst_uv + n * BPP, temp + 64, r * BPP); \ + memcpy(vin, src_yuy2 + n * SBPP, r * SBPP); \ + memcpy(vin + 32, src_yuy2 + stride_yuy2 + n * SBPP, r * SBPP); \ + ANY_SIMD(vin, 32, vout, MASK + 1); \ + memcpy(dst_uv + n * BPP, vout, r * BPP); \ } #ifdef HAS_YUY2TONVUVROW_NEON @@ -709,18 +709,19 @@ ANY21S(YUY2ToNVUVRow_Any_AVX2, YUY2ToNVUVRow_AVX2, 4, 2, 15) #define ANY21C(NAMEANY, ANY_SIMD, UVSHIFT, SBPP, SBPP2, BPP, MASK) \ void NAMEANY(const uint8_t* y_buf, const uint8_t* uv_buf, uint8_t* dst_ptr, \ const struct YuvConstants* yuvconstants, int width) { \ - SIMD_ALIGNED(uint8_t temp[128 * 3]); \ - memset(temp, 0, 128 * 2); /* for msan */ \ + SIMD_ALIGNED(uint8_t vin[128 * 2]); \ + SIMD_ALIGNED(uint8_t vout[128]); \ + memset(vin, 0, sizeof(vin)); /* for msan */ \ int r = width & MASK; \ int n = width & ~MASK; \ if (n > 0) { \ ANY_SIMD(y_buf, uv_buf, dst_ptr, yuvconstants, n); \ } \ - memcpy(temp, y_buf + n * SBPP, r * SBPP); \ - memcpy(temp + 128, uv_buf + (n >> UVSHIFT) * SBPP2, \ + memcpy(vin, y_buf + n * SBPP, r * SBPP); \ + memcpy(vin + 128, uv_buf + (n >> UVSHIFT) * SBPP2, \ SS(r, UVSHIFT) * SBPP2); \ - ANY_SIMD(temp, temp + 128, temp + 256, yuvconstants, MASK + 1); \ - memcpy(dst_ptr + n * BPP, temp + 256, r * BPP); \ + ANY_SIMD(vin, vin + 128, vout, yuvconstants, MASK + 1); \ + memcpy(dst_ptr + n * BPP, vout, r * BPP); \ } // Biplanar to RGB. @@ -799,21 +800,21 @@ ANY21C(NV12ToRGB565Row_Any_LASX, NV12ToRGB565Row_LASX, 1, 1, 2, 2, 15) #undef ANY21C // Any 2 planes of 16 bit to 1 with yuvconstants -#define ANY21CT(NAMEANY, ANY_SIMD, UVSHIFT, DUVSHIFT, T, SBPP, BPP, MASK) \ - void NAMEANY(const T* y_buf, const T* uv_buf, uint8_t* dst_ptr, \ - const struct YuvConstants* yuvconstants, int width) { \ - SIMD_ALIGNED(T temp[16 * 3]); \ - SIMD_ALIGNED(uint8_t out[64]); \ - memset(temp, 0, 16 * 3 * SBPP); /* for YUY2 and msan */ \ - int r = width & MASK; \ - int n = width & ~MASK; \ - if (n > 0) { \ - ANY_SIMD(y_buf, uv_buf, dst_ptr, yuvconstants, n); \ - } \ - memcpy(temp, y_buf + n, r * SBPP); \ - memcpy(temp + 16, uv_buf + 2 * (n >> UVSHIFT), SS(r, UVSHIFT) * SBPP * 2); \ - ANY_SIMD(temp, temp + 16, out, yuvconstants, MASK + 1); \ - memcpy(dst_ptr + (n >> DUVSHIFT) * BPP, out, SS(r, DUVSHIFT) * BPP); \ +#define ANY21CT(NAMEANY, ANY_SIMD, UVSHIFT, DUVSHIFT, T, SBPP, BPP, MASK) \ + void NAMEANY(const T* y_buf, const T* uv_buf, uint8_t* dst_ptr, \ + const struct YuvConstants* yuvconstants, int width) { \ + SIMD_ALIGNED(T vin[16 * 2]); \ + SIMD_ALIGNED(uint8_t vout[64]); \ + memset(vin, 0, sizeof(vin)); /* for msan */ \ + int r = width & MASK; \ + int n = width & ~MASK; \ + if (n > 0) { \ + ANY_SIMD(y_buf, uv_buf, dst_ptr, yuvconstants, n); \ + } \ + memcpy(vin, y_buf + n, r * SBPP); \ + memcpy(vin + 16, uv_buf + 2 * (n >> UVSHIFT), SS(r, UVSHIFT) * SBPP * 2); \ + ANY_SIMD(vin, vin + 16, vout, yuvconstants, MASK + 1); \ + memcpy(dst_ptr + (n >> DUVSHIFT) * BPP, vout, SS(r, DUVSHIFT) * BPP); \ } #ifdef HAS_P210TOAR30ROW_SSSE3 @@ -847,17 +848,18 @@ ANY21CT(P410ToAR30Row_Any_AVX2, P410ToAR30Row_AVX2, 0, 0, uint16_t, 2, 4, 15) #define ANY21PT(NAMEANY, ANY_SIMD, T, BPP, MASK) \ void NAMEANY(const T* src_u, const T* src_v, T* dst_uv, int depth, \ int width) { \ - SIMD_ALIGNED(T temp[16 * 4]); \ - memset(temp, 0, 16 * 4 * BPP); /* for msan */ \ + SIMD_ALIGNED(T vin[16 * 2]); \ + SIMD_ALIGNED(T vout[16]); \ + memset(vin, 0, sizeof(vin)); /* for msan */ \ int r = width & MASK; \ int n = width & ~MASK; \ if (n > 0) { \ ANY_SIMD(src_u, src_v, dst_uv, depth, n); \ } \ - memcpy(temp, src_u + n, r * BPP); \ - memcpy(temp + 16, src_v + n, r * BPP); \ - ANY_SIMD(temp, temp + 16, temp + 32, depth, MASK + 1); \ - memcpy(dst_uv + n * 2, temp + 32, r * BPP * 2); \ + memcpy(vin, src_u + n, r * BPP); \ + memcpy(vin + 16, src_v + n, r * BPP); \ + ANY_SIMD(vin, vin + 16, vout, depth, MASK + 1); \ + memcpy(dst_uv + n * 2, vout, r * BPP * 2); \ } #ifdef HAS_MERGEUVROW_16_AVX2 @@ -870,18 +872,19 @@ ANY21PT(MergeUVRow_16_Any_NEON, MergeUVRow_16_NEON, uint16_t, 2, 7) #undef ANY21CT // Any 1 to 1. -#define ANY11(NAMEANY, ANY_SIMD, UVSHIFT, SBPP, BPP, MASK) \ - void NAMEANY(const uint8_t* src_ptr, uint8_t* dst_ptr, int width) { \ - SIMD_ALIGNED(uint8_t temp[128 * 2]); \ - memset(temp, 0, 128); /* for YUY2 and msan */ \ - int r = width & MASK; \ - int n = width & ~MASK; \ - if (n > 0) { \ - ANY_SIMD(src_ptr, dst_ptr, n); \ - } \ - memcpy(temp, src_ptr + (n >> UVSHIFT) * SBPP, SS(r, UVSHIFT) * SBPP); \ - ANY_SIMD(temp, temp + 128, MASK + 1); \ - memcpy(dst_ptr + n * BPP, temp + 128, r * BPP); \ +#define ANY11(NAMEANY, ANY_SIMD, UVSHIFT, SBPP, BPP, MASK) \ + void NAMEANY(const uint8_t* src_ptr, uint8_t* dst_ptr, int width) { \ + SIMD_ALIGNED(uint8_t vin[128]); \ + SIMD_ALIGNED(uint8_t vout[128]); \ + memset(vin, 0, sizeof(vin)); /* for YUY2 and msan */ \ + int r = width & MASK; \ + int n = width & ~MASK; \ + if (n > 0) { \ + ANY_SIMD(src_ptr, dst_ptr, n); \ + } \ + memcpy(vin, src_ptr + (n >> UVSHIFT) * SBPP, SS(r, UVSHIFT) * SBPP); \ + ANY_SIMD(vin, vout, MASK + 1); \ + memcpy(dst_ptr + n * BPP, vout, r * BPP); \ } #ifdef HAS_COPYROW_AVX @@ -1324,19 +1327,21 @@ ANY11(ARGBExtractAlphaRow_Any_LSX, ARGBExtractAlphaRow_LSX, 0, 4, 1, 15) #undef ANY11 // Any 1 to 1 blended. Destination is read, modify, write. -#define ANY11B(NAMEANY, ANY_SIMD, UVSHIFT, SBPP, BPP, MASK) \ - void NAMEANY(const uint8_t* src_ptr, uint8_t* dst_ptr, int width) { \ - SIMD_ALIGNED(uint8_t temp[64 * 2]); \ - memset(temp, 0, 64 * 2); /* for msan */ \ - int r = width & MASK; \ - int n = width & ~MASK; \ - if (n > 0) { \ - ANY_SIMD(src_ptr, dst_ptr, n); \ - } \ - memcpy(temp, src_ptr + (n >> UVSHIFT) * SBPP, SS(r, UVSHIFT) * SBPP); \ - memcpy(temp + 64, dst_ptr + n * BPP, r * BPP); \ - ANY_SIMD(temp, temp + 64, MASK + 1); \ - memcpy(dst_ptr + n * BPP, temp + 64, r * BPP); \ +#define ANY11B(NAMEANY, ANY_SIMD, UVSHIFT, SBPP, BPP, MASK) \ + void NAMEANY(const uint8_t* src_ptr, uint8_t* dst_ptr, int width) { \ + SIMD_ALIGNED(uint8_t vin[64]); \ + SIMD_ALIGNED(uint8_t vout[64]); \ + memset(vin, 0, sizeof(vin)); /* for msan */ \ + memset(vout, 0, sizeof(vout)); /* for msan */ \ + int r = width & MASK; \ + int n = width & ~MASK; \ + if (n > 0) { \ + ANY_SIMD(src_ptr, dst_ptr, n); \ + } \ + memcpy(vin, src_ptr + (n >> UVSHIFT) * SBPP, SS(r, UVSHIFT) * SBPP); \ + memcpy(vout, dst_ptr + n * BPP, r * BPP); \ + ANY_SIMD(vin, vout, MASK + 1); \ + memcpy(dst_ptr + n * BPP, vout, r * BPP); \ } #ifdef HAS_ARGBCOPYALPHAROW_AVX2 @@ -1356,16 +1361,17 @@ ANY11B(ARGBCopyYToAlphaRow_Any_SSE2, ARGBCopyYToAlphaRow_SSE2, 0, 1, 4, 7) // Any 1 to 1 with parameter. #define ANY11P(NAMEANY, ANY_SIMD, T, SBPP, BPP, MASK) \ void NAMEANY(const uint8_t* src_ptr, uint8_t* dst_ptr, T param, int width) { \ - SIMD_ALIGNED(uint8_t temp[64 * 2]); \ - memset(temp, 0, 64); /* for msan */ \ + SIMD_ALIGNED(uint8_t vin[64]); \ + SIMD_ALIGNED(uint8_t vout[64]); \ + memset(vin, 0, sizeof(vin)); /* for msan */ \ int r = width & MASK; \ int n = width & ~MASK; \ if (n > 0) { \ ANY_SIMD(src_ptr, dst_ptr, param, n); \ } \ - memcpy(temp, src_ptr + n * SBPP, r * SBPP); \ - ANY_SIMD(temp, temp + 64, param, MASK + 1); \ - memcpy(dst_ptr + n * BPP, temp + 64, r * BPP); \ + memcpy(vin, src_ptr + n * SBPP, r * SBPP); \ + ANY_SIMD(vin, vout, param, MASK + 1); \ + memcpy(dst_ptr + n * BPP, vout, r * BPP); \ } #if defined(HAS_I400TOARGBROW_SSE2) @@ -1470,17 +1476,17 @@ ANY11P(ARGBShuffleRow_Any_LASX, ARGBShuffleRow_LASX, const uint8_t*, 4, 4, 15) // Any 1 to 1 with type #define ANY11T(NAMEANY, ANY_SIMD, SBPP, BPP, STYPE, DTYPE, MASK) \ void NAMEANY(const STYPE* src_ptr, DTYPE* dst_ptr, int width) { \ - SIMD_ALIGNED(uint8_t temp[(MASK + 1) * SBPP]); \ - SIMD_ALIGNED(uint8_t out[(MASK + 1) * BPP]); \ - memset(temp, 0, (MASK + 1) * SBPP); /* for msan */ \ + SIMD_ALIGNED(uint8_t vin[(MASK + 1) * SBPP]); \ + SIMD_ALIGNED(uint8_t vout[(MASK + 1) * BPP]); \ + memset(vin, 0, sizeof(vin)); /* for msan */ \ int r = width & MASK; \ int n = width & ~MASK; \ if (n > 0) { \ ANY_SIMD(src_ptr, dst_ptr, n); \ } \ - memcpy(temp, (uint8_t*)(src_ptr) + n * SBPP, r * SBPP); \ - ANY_SIMD((STYPE*)temp, (DTYPE*)out, MASK + 1); \ - memcpy((uint8_t*)(dst_ptr) + n * BPP, out, r * BPP); \ + memcpy(vin, (uint8_t*)(src_ptr) + n * SBPP, r * SBPP); \ + ANY_SIMD((STYPE*)vin, (DTYPE*)vout, MASK + 1); \ + memcpy((uint8_t*)(dst_ptr) + n * BPP, vout, r * BPP); \ } #ifdef HAS_ARGBTOAR64ROW_SSSE3 @@ -1536,17 +1542,17 @@ ANY11T(AB64ToARGBRow_Any_NEON, AB64ToARGBRow_NEON, 8, 4, uint16_t, uint8_t, 7) // Any 1 to 1 with parameter and shorts. BPP measures in shorts. #define ANY11C(NAMEANY, ANY_SIMD, SBPP, BPP, STYPE, DTYPE, MASK) \ void NAMEANY(const STYPE* src_ptr, DTYPE* dst_ptr, int scale, int width) { \ - SIMD_ALIGNED(STYPE temp[32]); \ - SIMD_ALIGNED(DTYPE out[32]); \ - memset(temp, 0, 32 * SBPP); /* for msan */ \ + SIMD_ALIGNED(STYPE vin[32]); \ + SIMD_ALIGNED(DTYPE vout[32]); \ + memset(vin, 0, sizeof(vin)); /* for msan */ \ int r = width & MASK; \ int n = width & ~MASK; \ if (n > 0) { \ ANY_SIMD(src_ptr, dst_ptr, scale, n); \ } \ - memcpy(temp, src_ptr + n, r * SBPP); \ - ANY_SIMD(temp, out, scale, MASK + 1); \ - memcpy(dst_ptr + n, out, r * BPP); \ + memcpy(vin, src_ptr + n, r * SBPP); \ + ANY_SIMD(vin, vout, scale, MASK + 1); \ + memcpy(dst_ptr + n, vout, r * BPP); \ } #ifdef HAS_CONVERT16TO8ROW_SSSE3 @@ -1623,17 +1629,17 @@ ANY11C(DivideRow_16_Any_NEON, DivideRow_16_NEON, 2, 2, uint16_t, uint16_t, 15) // Any 1 to 1 with parameter and shorts to byte. BPP measures in shorts. #define ANY11P16(NAMEANY, ANY_SIMD, ST, T, SBPP, BPP, MASK) \ void NAMEANY(const ST* src_ptr, T* dst_ptr, float param, int width) { \ - SIMD_ALIGNED(ST temp[32]); \ - SIMD_ALIGNED(T out[32]); \ - memset(temp, 0, SBPP * 32); /* for msan */ \ + SIMD_ALIGNED(ST vin[32]); \ + SIMD_ALIGNED(T vout[32]); \ + memset(vin, 0, sizeof(vin)); /* for msan */ \ int r = width & MASK; \ int n = width & ~MASK; \ if (n > 0) { \ ANY_SIMD(src_ptr, dst_ptr, param, n); \ } \ - memcpy(temp, src_ptr + n, r * SBPP); \ - ANY_SIMD(temp, out, param, MASK + 1); \ - memcpy(dst_ptr + n, out, r * BPP); \ + memcpy(vin, src_ptr + n, r * SBPP); \ + ANY_SIMD(vin, vout, param, MASK + 1); \ + memcpy(dst_ptr + n, vout, r * BPP); \ } #ifdef HAS_HALFFLOATROW_SSE2 @@ -1674,20 +1680,22 @@ ANY11P16(HalfFloatRow_Any_LSX, HalfFloatRow_LSX, uint16_t, uint16_t, 2, 2, 31) #undef ANY11P16 // Any 1 to 1 with yuvconstants -#define ANY11C(NAMEANY, ANY_SIMD, UVSHIFT, SBPP, BPP, MASK) \ - void NAMEANY(const uint8_t* src_ptr, uint8_t* dst_ptr, \ - const struct YuvConstants* yuvconstants, int width) { \ - SIMD_ALIGNED(uint8_t temp[128 * 2]); \ - memset(temp, 0, 128); /* for YUY2 and msan */ \ - int r = width & MASK; \ - int n = width & ~MASK; \ - if (n > 0) { \ - ANY_SIMD(src_ptr, dst_ptr, yuvconstants, n); \ - } \ - memcpy(temp, src_ptr + (n >> UVSHIFT) * SBPP, SS(r, UVSHIFT) * SBPP); \ - ANY_SIMD(temp, temp + 128, yuvconstants, MASK + 1); \ - memcpy(dst_ptr + n * BPP, temp + 128, r * BPP); \ +#define ANY11C(NAMEANY, ANY_SIMD, UVSHIFT, SBPP, BPP, MASK) \ + void NAMEANY(const uint8_t* src_ptr, uint8_t* dst_ptr, \ + const struct YuvConstants* yuvconstants, int width) { \ + SIMD_ALIGNED(uint8_t vin[128]); \ + SIMD_ALIGNED(uint8_t vout[128]); \ + memset(vin, 0, sizeof(vin)); /* for YUY2 and msan */ \ + int r = width & MASK; \ + int n = width & ~MASK; \ + if (n > 0) { \ + ANY_SIMD(src_ptr, dst_ptr, yuvconstants, n); \ + } \ + memcpy(vin, src_ptr + (n >> UVSHIFT) * SBPP, SS(r, UVSHIFT) * SBPP); \ + ANY_SIMD(vin, vout, yuvconstants, MASK + 1); \ + memcpy(dst_ptr + n * BPP, vout, r * BPP); \ } + #if defined(HAS_YUY2TOARGBROW_SSSE3) ANY11C(YUY2ToARGBRow_Any_SSSE3, YUY2ToARGBRow_SSSE3, 1, 4, 4, 15) ANY11C(UYVYToARGBRow_Any_SSSE3, UYVYToARGBRow_SSSE3, 1, 4, 4, 15) @@ -1714,21 +1722,21 @@ ANY11C(UYVYToARGBRow_Any_LSX, UYVYToARGBRow_LSX, 1, 4, 4, 7) #define ANY11I(NAMEANY, ANY_SIMD, TD, TS, SBPP, BPP, MASK) \ void NAMEANY(TD* dst_ptr, const TS* src_ptr, ptrdiff_t src_stride, \ int width, int source_y_fraction) { \ - SIMD_ALIGNED(TS temps[64 * 2]); \ - SIMD_ALIGNED(TD tempd[64]); \ - memset(temps, 0, sizeof(temps)); /* for msan */ \ + SIMD_ALIGNED(TS vin[64 * 2]); \ + SIMD_ALIGNED(TD vout[64]); \ + memset(vin, 0, sizeof(vin)); /* for msan */ \ int r = width & MASK; \ int n = width & ~MASK; \ if (n > 0) { \ ANY_SIMD(dst_ptr, src_ptr, src_stride, n, source_y_fraction); \ } \ - memcpy(temps, src_ptr + n * SBPP, r * SBPP * sizeof(TS)); \ + memcpy(vin, src_ptr + n * SBPP, r * SBPP * sizeof(TS)); \ if (source_y_fraction) { \ - memcpy(temps + 64, src_ptr + src_stride + n * SBPP, \ + memcpy(vin + 64, src_ptr + src_stride + n * SBPP, \ r * SBPP * sizeof(TS)); \ } \ - ANY_SIMD(tempd, temps, 64, MASK + 1, source_y_fraction); \ - memcpy(dst_ptr + n * BPP, tempd, r * BPP * sizeof(TD)); \ + ANY_SIMD(vout, vin, 64, MASK + 1, source_y_fraction); \ + memcpy(dst_ptr + n * BPP, vout, r * BPP * sizeof(TD)); \ } #ifdef HAS_INTERPOLATEROW_AVX2 @@ -1768,21 +1776,21 @@ ANY11I(InterpolateRow_16_Any_NEON, #define ANY11IS(NAMEANY, ANY_SIMD, TD, TS, SBPP, BPP, MASK) \ void NAMEANY(TD* dst_ptr, const TS* src_ptr, ptrdiff_t src_stride, \ int scale, int width, int source_y_fraction) { \ - SIMD_ALIGNED(TS temps[64 * 2]); \ - SIMD_ALIGNED(TD tempd[64]); \ - memset(temps, 0, sizeof(temps)); /* for msan */ \ + SIMD_ALIGNED(TS vin[64 * 2]); \ + SIMD_ALIGNED(TD vout[64]); \ + memset(vin, 0, sizeof(vin)); /* for msan */ \ int r = width & MASK; \ int n = width & ~MASK; \ if (n > 0) { \ ANY_SIMD(dst_ptr, src_ptr, src_stride, scale, n, source_y_fraction); \ } \ - memcpy(temps, src_ptr + n * SBPP, r * SBPP * sizeof(TS)); \ + memcpy(vin, src_ptr + n * SBPP, r * SBPP * sizeof(TS)); \ if (source_y_fraction) { \ - memcpy(temps + 64, src_ptr + src_stride + n * SBPP, \ + memcpy(vin + 64, src_ptr + src_stride + n * SBPP, \ r * SBPP * sizeof(TS)); \ } \ - ANY_SIMD(tempd, temps, 64, scale, MASK + 1, source_y_fraction); \ - memcpy(dst_ptr + n * BPP, tempd, r * BPP * sizeof(TD)); \ + ANY_SIMD(vout, vin, 64, scale, MASK + 1, source_y_fraction); \ + memcpy(dst_ptr + n * BPP, vout, r * BPP * sizeof(TD)); \ } #ifdef HAS_INTERPOLATEROW_16TO8_NEON @@ -1807,18 +1815,19 @@ ANY11IS(InterpolateRow_16To8_Any_AVX2, #undef ANY11IS // Any 1 to 1 mirror. -#define ANY11M(NAMEANY, ANY_SIMD, BPP, MASK) \ - void NAMEANY(const uint8_t* src_ptr, uint8_t* dst_ptr, int width) { \ - SIMD_ALIGNED(uint8_t temp[64 * 2]); \ - memset(temp, 0, 64); /* for msan */ \ - int r = width & MASK; \ - int n = width & ~MASK; \ - if (n > 0) { \ - ANY_SIMD(src_ptr + r * BPP, dst_ptr, n); \ - } \ - memcpy(temp, src_ptr, r* BPP); \ - ANY_SIMD(temp, temp + 64, MASK + 1); \ - memcpy(dst_ptr + n * BPP, temp + 64 + (MASK + 1 - r) * BPP, r * BPP); \ +#define ANY11M(NAMEANY, ANY_SIMD, BPP, MASK) \ + void NAMEANY(const uint8_t* src_ptr, uint8_t* dst_ptr, int width) { \ + SIMD_ALIGNED(uint8_t vin[64]); \ + SIMD_ALIGNED(uint8_t vout[64]); \ + memset(vin, 0, sizeof(vin)); /* for msan */ \ + int r = width & MASK; \ + int n = width & ~MASK; \ + if (n > 0) { \ + ANY_SIMD(src_ptr + r * BPP, dst_ptr, n); \ + } \ + memcpy(vin, src_ptr, r* BPP); \ + ANY_SIMD(vin, vout, MASK + 1); \ + memcpy(dst_ptr + n * BPP, vout + (MASK + 1 - r) * BPP, r * BPP); \ } #ifdef HAS_MIRRORROW_AVX2 @@ -1877,15 +1886,14 @@ ANY11M(RGB24MirrorRow_Any_NEON, RGB24MirrorRow_NEON, 3, 15) // Any 1 plane. (memset) #define ANY1(NAMEANY, ANY_SIMD, T, BPP, MASK) \ void NAMEANY(uint8_t* dst_ptr, T v32, int width) { \ - SIMD_ALIGNED(uint8_t temp[64]); \ - memset(temp, 0, 64); /* for msan */ \ + SIMD_ALIGNED(uint8_t vout[64]); \ int r = width & MASK; \ int n = width & ~MASK; \ if (n > 0) { \ ANY_SIMD(dst_ptr, v32, n); \ } \ - ANY_SIMD(temp, v32, MASK + 1); \ - memcpy(dst_ptr + n * BPP, temp, r * BPP); \ + ANY_SIMD(vout, v32, MASK + 1); \ + memcpy(dst_ptr + n * BPP, vout, r * BPP); \ } #ifdef HAS_SETROW_X86 @@ -1909,20 +1917,21 @@ ANY1(ARGBSetRow_Any_LSX, ARGBSetRow_LSX, uint32_t, 4, 3) #undef ANY1 // Any 1 to 2. Outputs UV planes. -#define ANY12(NAMEANY, ANY_SIMD, UVSHIFT, BPP, DUVSHIFT, MASK) \ - void NAMEANY(const uint8_t* src_ptr, uint8_t* dst_u, uint8_t* dst_v, \ - int width) { \ - SIMD_ALIGNED(uint8_t temp[128 * 3]); \ - memset(temp, 0, 128); /* for msan */ \ - int r = width & MASK; \ - int n = width & ~MASK; \ - if (n > 0) { \ - ANY_SIMD(src_ptr, dst_u, dst_v, n); \ - } \ - memcpy(temp, src_ptr + (n >> UVSHIFT) * BPP, SS(r, UVSHIFT) * BPP); \ - ANY_SIMD(temp, temp + 128, temp + 256, MASK + 1); \ - memcpy(dst_u + (n >> DUVSHIFT), temp + 128, SS(r, DUVSHIFT)); \ - memcpy(dst_v + (n >> DUVSHIFT), temp + 256, SS(r, DUVSHIFT)); \ +#define ANY12(NAMEANY, ANY_SIMD, UVSHIFT, BPP, DUVSHIFT, MASK) \ + void NAMEANY(const uint8_t* src_ptr, uint8_t* dst_u, uint8_t* dst_v, \ + int width) { \ + SIMD_ALIGNED(uint8_t vin[128]); \ + SIMD_ALIGNED(uint8_t vout[128 * 2]); \ + memset(vin, 0, sizeof(vin)); /* for msan */ \ + int r = width & MASK; \ + int n = width & ~MASK; \ + if (n > 0) { \ + ANY_SIMD(src_ptr, dst_u, dst_v, n); \ + } \ + memcpy(vin, src_ptr + (n >> UVSHIFT) * BPP, SS(r, UVSHIFT) * BPP); \ + ANY_SIMD(vin, vout, vout + 128, MASK + 1); \ + memcpy(dst_u + (n >> DUVSHIFT), vout, SS(r, DUVSHIFT)); \ + memcpy(dst_v + (n >> DUVSHIFT), vout + 128, SS(r, DUVSHIFT)); \ } #ifdef HAS_SPLITUVROW_SSE2 @@ -1971,17 +1980,18 @@ ANY12(UYVYToUV422Row_Any_LASX, UYVYToUV422Row_LASX, 1, 4, 1, 31) // Any 2 16 bit planes with parameter to 1 #define ANY12PT(NAMEANY, ANY_SIMD, T, BPP, MASK) \ void NAMEANY(const T* src_uv, T* dst_u, T* dst_v, int depth, int width) { \ - SIMD_ALIGNED(T temp[16 * 4]); \ - memset(temp, 0, 16 * 4 * BPP); /* for msan */ \ + SIMD_ALIGNED(T vin[16 * 2]); \ + SIMD_ALIGNED(T vout[16 * 2]); \ + memset(vin, 0, sizeof(vin)); /* for msan */ \ int r = width & MASK; \ int n = width & ~MASK; \ if (n > 0) { \ ANY_SIMD(src_uv, dst_u, dst_v, depth, n); \ } \ - memcpy(temp, src_uv + n * 2, r * BPP * 2); \ - ANY_SIMD(temp, temp + 32, temp + 48, depth, MASK + 1); \ - memcpy(dst_u + n, temp + 32, r * BPP); \ - memcpy(dst_v + n, temp + 48, r * BPP); \ + memcpy(vin, src_uv + n * 2, r * BPP * 2); \ + ANY_SIMD(vin, vout, vout + 16, depth, MASK + 1); \ + memcpy(dst_u + n, vout, r * BPP); \ + memcpy(dst_v + n, vout + 16, r * BPP); \ } #ifdef HAS_SPLITUVROW_16_AVX2 @@ -1995,21 +2005,22 @@ ANY12PT(SplitUVRow_16_Any_NEON, SplitUVRow_16_NEON, uint16_t, 2, 7) #undef ANY21CT // Any 1 to 3. Outputs RGB planes. -#define ANY13(NAMEANY, ANY_SIMD, BPP, MASK) \ - void NAMEANY(const uint8_t* src_ptr, uint8_t* dst_r, uint8_t* dst_g, \ - uint8_t* dst_b, int width) { \ - SIMD_ALIGNED(uint8_t temp[16 * 6]); \ - memset(temp, 0, 16 * 3); /* for msan */ \ - int r = width & MASK; \ - int n = width & ~MASK; \ - if (n > 0) { \ - ANY_SIMD(src_ptr, dst_r, dst_g, dst_b, n); \ - } \ - memcpy(temp, src_ptr + n * BPP, r * BPP); \ - ANY_SIMD(temp, temp + 16 * 3, temp + 16 * 4, temp + 16 * 5, MASK + 1); \ - memcpy(dst_r + n, temp + 16 * 3, r); \ - memcpy(dst_g + n, temp + 16 * 4, r); \ - memcpy(dst_b + n, temp + 16 * 5, r); \ +#define ANY13(NAMEANY, ANY_SIMD, BPP, MASK) \ + void NAMEANY(const uint8_t* src_ptr, uint8_t* dst_r, uint8_t* dst_g, \ + uint8_t* dst_b, int width) { \ + SIMD_ALIGNED(uint8_t vin[16 * 3]); \ + SIMD_ALIGNED(uint8_t vout[16 * 3]); \ + memset(vin, 0, sizeof(vin)); /* for msan */ \ + int r = width & MASK; \ + int n = width & ~MASK; \ + if (n > 0) { \ + ANY_SIMD(src_ptr, dst_r, dst_g, dst_b, n); \ + } \ + memcpy(vin, src_ptr + n * BPP, r * BPP); \ + ANY_SIMD(vin, vout, vout + 16, vout + 32, MASK + 1); \ + memcpy(dst_r + n, vout, r); \ + memcpy(dst_g + n, vout + 16, r); \ + memcpy(dst_b + n, vout + 32, r); \ } #ifdef HAS_SPLITRGBROW_SSSE3 @@ -2032,23 +2043,23 @@ ANY13(SplitXRGBRow_Any_NEON, SplitXRGBRow_NEON, 4, 15) #endif // Any 1 to 4. Outputs ARGB planes. -#define ANY14(NAMEANY, ANY_SIMD, BPP, MASK) \ - void NAMEANY(const uint8_t* src_ptr, uint8_t* dst_r, uint8_t* dst_g, \ - uint8_t* dst_b, uint8_t* dst_a, int width) { \ - SIMD_ALIGNED(uint8_t temp[16 * 8]); \ - memset(temp, 0, 16 * 4); /* for msan */ \ - int r = width & MASK; \ - int n = width & ~MASK; \ - if (n > 0) { \ - ANY_SIMD(src_ptr, dst_r, dst_g, dst_b, dst_a, n); \ - } \ - memcpy(temp, src_ptr + n * BPP, r * BPP); \ - ANY_SIMD(temp, temp + 16 * 4, temp + 16 * 5, temp + 16 * 6, temp + 16 * 7, \ - MASK + 1); \ - memcpy(dst_r + n, temp + 16 * 4, r); \ - memcpy(dst_g + n, temp + 16 * 5, r); \ - memcpy(dst_b + n, temp + 16 * 6, r); \ - memcpy(dst_a + n, temp + 16 * 7, r); \ +#define ANY14(NAMEANY, ANY_SIMD, BPP, MASK) \ + void NAMEANY(const uint8_t* src_ptr, uint8_t* dst_r, uint8_t* dst_g, \ + uint8_t* dst_b, uint8_t* dst_a, int width) { \ + SIMD_ALIGNED(uint8_t vin[16 * 4]); \ + SIMD_ALIGNED(uint8_t vout[16 * 4]); \ + memset(vin, 0, sizeof(vin)); /* for msan */ \ + int r = width & MASK; \ + int n = width & ~MASK; \ + if (n > 0) { \ + ANY_SIMD(src_ptr, dst_r, dst_g, dst_b, dst_a, n); \ + } \ + memcpy(vin, src_ptr + n * BPP, r * BPP); \ + ANY_SIMD(vin, vout, vout + 16, vout + 32, vout + 48, MASK + 1); \ + memcpy(dst_r + n, vout, r); \ + memcpy(dst_g + n, vout + 16, r); \ + memcpy(dst_b + n, vout + 32, r); \ + memcpy(dst_a + n, vout + 48, r); \ } #ifdef HAS_SPLITARGBROW_SSE2 @@ -2069,25 +2080,26 @@ ANY14(SplitARGBRow_Any_NEON, SplitARGBRow_NEON, 4, 15) #define ANY12S(NAMEANY, ANY_SIMD, UVSHIFT, BPP, MASK) \ void NAMEANY(const uint8_t* src_ptr, int src_stride, uint8_t* dst_u, \ uint8_t* dst_v, int width) { \ - SIMD_ALIGNED(uint8_t temp[128 * 4]); \ - memset(temp, 0, 128 * 2); /* for msan */ \ + SIMD_ALIGNED(uint8_t vin[128 * 2]); \ + SIMD_ALIGNED(uint8_t vout[128 * 2]); \ + memset(vin, 0, sizeof(vin)); /* for msan */ \ int r = width & MASK; \ int n = width & ~MASK; \ if (n > 0) { \ ANY_SIMD(src_ptr, src_stride, dst_u, dst_v, n); \ } \ - memcpy(temp, src_ptr + (n >> UVSHIFT) * BPP, SS(r, UVSHIFT) * BPP); \ - memcpy(temp + 128, src_ptr + src_stride + (n >> UVSHIFT) * BPP, \ + memcpy(vin, src_ptr + (n >> UVSHIFT) * BPP, SS(r, UVSHIFT) * BPP); \ + memcpy(vin + 128, src_ptr + src_stride + (n >> UVSHIFT) * BPP, \ SS(r, UVSHIFT) * BPP); \ if ((width & 1) && UVSHIFT == 0) { /* repeat last pixel for subsample */ \ - memcpy(temp + SS(r, UVSHIFT) * BPP, temp + SS(r, UVSHIFT) * BPP - BPP, \ + memcpy(vin + SS(r, UVSHIFT) * BPP, vin + SS(r, UVSHIFT) * BPP - BPP, \ BPP); \ - memcpy(temp + 128 + SS(r, UVSHIFT) * BPP, \ - temp + 128 + SS(r, UVSHIFT) * BPP - BPP, BPP); \ + memcpy(vin + 128 + SS(r, UVSHIFT) * BPP, \ + vin + 128 + SS(r, UVSHIFT) * BPP - BPP, BPP); \ } \ - ANY_SIMD(temp, 128, temp + 256, temp + 384, MASK + 1); \ - memcpy(dst_u + (n >> 1), temp + 256, SS(r, 1)); \ - memcpy(dst_v + (n >> 1), temp + 384, SS(r, 1)); \ + ANY_SIMD(vin, 128, vout, vout + 128, MASK + 1); \ + memcpy(dst_u + (n >> 1), vout, SS(r, 1)); \ + memcpy(dst_v + (n >> 1), vout + 128, SS(r, 1)); \ } #ifdef HAS_ARGBTOUVROW_AVX2 @@ -2255,24 +2267,25 @@ ANY12S(UYVYToUVRow_Any_LASX, UYVYToUVRow_LASX, 1, 4, 31) #define ANY11S(NAMEANY, ANY_SIMD, UVSHIFT, BPP, MASK) \ void NAMEANY(const uint8_t* src_ptr, int src_stride, uint8_t* dst_vu, \ int width) { \ - SIMD_ALIGNED(uint8_t temp[128 * 3]); \ - memset(temp, 0, 128 * 2); /* for msan */ \ + SIMD_ALIGNED(uint8_t vin[128 * 2]); \ + SIMD_ALIGNED(uint8_t vout[128]); \ + memset(vin, 0, sizeof(vin)); /* for msan */ \ int r = width & MASK; \ int n = width & ~MASK; \ if (n > 0) { \ ANY_SIMD(src_ptr, src_stride, dst_vu, n); \ } \ - memcpy(temp, src_ptr + (n >> UVSHIFT) * BPP, SS(r, UVSHIFT) * BPP); \ - memcpy(temp + 128, src_ptr + src_stride + (n >> UVSHIFT) * BPP, \ + memcpy(vin, src_ptr + (n >> UVSHIFT) * BPP, SS(r, UVSHIFT) * BPP); \ + memcpy(vin + 128, src_ptr + src_stride + (n >> UVSHIFT) * BPP, \ SS(r, UVSHIFT) * BPP); \ if ((width & 1) && UVSHIFT == 0) { /* repeat last pixel for subsample */ \ - memcpy(temp + SS(r, UVSHIFT) * BPP, temp + SS(r, UVSHIFT) * BPP - BPP, \ + memcpy(vin + SS(r, UVSHIFT) * BPP, vin + SS(r, UVSHIFT) * BPP - BPP, \ BPP); \ - memcpy(temp + 128 + SS(r, UVSHIFT) * BPP, \ - temp + 128 + SS(r, UVSHIFT) * BPP - BPP, BPP); \ + memcpy(vin + 128 + SS(r, UVSHIFT) * BPP, \ + vin + 128 + SS(r, UVSHIFT) * BPP - BPP, BPP); \ } \ - ANY_SIMD(temp, 128, temp + 256, MASK + 1); \ - memcpy(dst_vu + (n >> 1) * 2, temp + 256, SS(r, 1) * 2); \ + ANY_SIMD(vin, 128, vout, MASK + 1); \ + memcpy(dst_vu + (n >> 1) * 2, vout, SS(r, 1) * 2); \ } #ifdef HAS_AYUVTOVUROW_NEON @@ -2283,16 +2296,17 @@ ANY11S(AYUVToVURow_Any_NEON, AYUVToVURow_NEON, 0, 4, 15) #define ANYDETILE(NAMEANY, ANY_SIMD, T, BPP, MASK) \ void NAMEANY(const T* src, ptrdiff_t src_tile_stride, T* dst, int width) { \ - SIMD_ALIGNED(T temp[16 * 2]); \ - memset(temp, 0, sizeof(temp)); /* for msan */ \ + SIMD_ALIGNED(T vin[16]); \ + SIMD_ALIGNED(T vout[16]); \ + memset(vin, 0, sizeof(vin)); /* for msan */ \ int r = width & MASK; \ int n = width & ~MASK; \ if (n > 0) { \ ANY_SIMD(src, src_tile_stride, dst, n); \ } \ - memcpy(temp, src + (n / 16) * src_tile_stride, r * BPP); \ - ANY_SIMD(temp, src_tile_stride, temp + 16, MASK + 1); \ - memcpy(dst + n, temp + 16, r * BPP); \ + memcpy(vin, src + (n / 16) * src_tile_stride, r * BPP); \ + ANY_SIMD(vin, src_tile_stride, vout, MASK + 1); \ + memcpy(dst + n, vout, r * BPP); \ } #ifdef HAS_DETILEROW_NEON @@ -2311,20 +2325,22 @@ ANYDETILE(DetileRow_16_Any_SSE2, DetileRow_16_SSE2, uint16_t, 2, 15) ANYDETILE(DetileRow_16_Any_AVX, DetileRow_16_AVX, uint16_t, 2, 15) #endif +// DetileSplitUVRow width is in bytes #define ANYDETILESPLITUV(NAMEANY, ANY_SIMD, MASK) \ void NAMEANY(const uint8_t* src_uv, ptrdiff_t src_tile_stride, \ uint8_t* dst_u, uint8_t* dst_v, int width) { \ - SIMD_ALIGNED(uint8_t temp[16 * 2]); \ - memset(temp, 0, 16 * 2); /* for msan */ \ + SIMD_ALIGNED(uint8_t vin[16]); \ + SIMD_ALIGNED(uint8_t vout[8 * 2]); \ + memset(vin, 0, sizeof(vin)); /* for msan */ \ int r = width & MASK; \ int n = width & ~MASK; \ if (n > 0) { \ ANY_SIMD(src_uv, src_tile_stride, dst_u, dst_v, n); \ } \ - memcpy(temp, src_uv + (n / 16) * src_tile_stride, r); \ - ANY_SIMD(temp, src_tile_stride, temp + 16, temp + 24, r); \ - memcpy(dst_u + n / 2, temp + 16, (r + 1) / 2); \ - memcpy(dst_v + n / 2, temp + 24, (r + 1) / 2); \ + memcpy(vin, src_uv + (n / 16) * src_tile_stride, r); \ + ANY_SIMD(vin, src_tile_stride, vout, vout + 8, r); \ + memcpy(dst_u + n / 2, vout, (r + 1) / 2); \ + memcpy(dst_v + n / 2, vout + 8, (r + 1) / 2); \ } #ifdef HAS_DETILESPLITUVROW_NEON @@ -2338,19 +2354,19 @@ ANYDETILESPLITUV(DetileSplitUVRow_Any_SSSE3, DetileSplitUVRow_SSSE3, 15) void NAMEANY(const uint8_t* src_y, ptrdiff_t src_y_tile_stride, \ const uint8_t* src_uv, ptrdiff_t src_uv_tile_stride, \ uint8_t* dst_yuy2, int width) { \ - SIMD_ALIGNED(uint8_t temp[16 * 4]); \ - memset(temp, 0, 16 * 4); /* for msan */ \ + SIMD_ALIGNED(uint8_t vin[16 * 2]); \ + SIMD_ALIGNED(uint8_t vout[16 * 2]); \ + memset(vin, 0, sizeof(vin)); /* for msan */ \ int r = width & MASK; \ int n = width & ~MASK; \ if (n > 0) { \ ANY_SIMD(src_y, src_y_tile_stride, src_uv, src_uv_tile_stride, dst_yuy2, \ n); \ } \ - memcpy(temp, src_y + (n / 16) * src_y_tile_stride, r); \ - memcpy(temp + 16, src_uv + (n / 16) * src_uv_tile_stride, r); \ - ANY_SIMD(temp, src_y_tile_stride, temp + 16, src_uv_tile_stride, \ - temp + 32, r); \ - memcpy(dst_yuy2 + 2 * n, temp + 32, 2 * r); \ + memcpy(vin, src_y + (n / 16) * src_y_tile_stride, r); \ + memcpy(vin + 16, src_uv + (n / 16) * src_uv_tile_stride, r); \ + ANY_SIMD(vin, src_y_tile_stride, vin + 16, src_uv_tile_stride, vout, r); \ + memcpy(dst_yuy2 + 2 * n, vout, 2 * r); \ } #ifdef HAS_DETILETOYUY2_NEON diff --git a/libfenrir/src/main/jni/animation/libyuv/source/row_rvv.cc b/libfenrir/src/main/jni/animation/libyuv/source/row_rvv.cc index 0f264d349..629eca465 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/row_rvv.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/row_rvv.cc @@ -30,33 +30,33 @@ extern "C" { void RAWToARGBRow_RVV(const uint8_t* src_raw, uint8_t* dst_argb, int width) { size_t vl = __riscv_vsetvl_e8m2(width); vuint8m2_t v_a = __riscv_vmv_v_x_u8m2(255u, vl); - while (width > 0) { + do { vuint8m2_t v_b, v_g, v_r; - vl = __riscv_vsetvl_e8m2(width); __riscv_vlseg3e8_v_u8m2(&v_r, &v_g, &v_b, src_raw, vl); __riscv_vsseg4e8_v_u8m2(dst_argb, v_b, v_g, v_r, v_a, vl); width -= vl; src_raw += (3 * vl); dst_argb += (4 * vl); - } + vl = __riscv_vsetvl_e8m2(width); + } while (width > 0); } void RAWToRGBARow_RVV(const uint8_t* src_raw, uint8_t* dst_rgba, int width) { size_t vl = __riscv_vsetvl_e8m2(width); vuint8m2_t v_a = __riscv_vmv_v_x_u8m2(255u, vl); - while (width > 0) { + do { vuint8m2_t v_b, v_g, v_r; - vl = __riscv_vsetvl_e8m2(width); __riscv_vlseg3e8_v_u8m2(&v_r, &v_g, &v_b, src_raw, vl); __riscv_vsseg4e8_v_u8m2(dst_rgba, v_a, v_b, v_g, v_r, vl); width -= vl; src_raw += (3 * vl); dst_rgba += (4 * vl); - } + vl = __riscv_vsetvl_e8m2(width); + } while (width > 0); } void RAWToRGB24Row_RVV(const uint8_t* src_raw, uint8_t* dst_rgb24, int width) { - while (width > 0) { + do { vuint8m2_t v_b, v_g, v_r; size_t vl = __riscv_vsetvl_e8m2(width); __riscv_vlseg3e8_v_u8m2(&v_b, &v_g, &v_r, src_raw, vl); @@ -64,7 +64,49 @@ void RAWToRGB24Row_RVV(const uint8_t* src_raw, uint8_t* dst_rgb24, int width) { width -= vl; src_raw += (3 * vl); dst_rgb24 += (3 * vl); - } + } while (width > 0); +} + +void ARGBToRAWRow_RVV(const uint8_t* src_argb, uint8_t* dst_raw, int width) { + do { + vuint8m2_t v_b, v_g, v_r, v_a; + size_t vl = __riscv_vsetvl_e8m2(width); + __riscv_vlseg4e8_v_u8m2(&v_b, &v_g, &v_r, &v_a, src_argb, vl); + __riscv_vsseg3e8_v_u8m2(dst_raw, v_r, v_g, v_b, vl); + width -= vl; + src_argb += (4 * vl); + dst_raw += (3 * vl); + } while (width > 0); +} + +void ARGBToRGB24Row_RVV(const uint8_t* src_argb, + uint8_t* dst_rgb24, + int width) { + do { + vuint8m2_t v_b, v_g, v_r, v_a; + size_t vl = __riscv_vsetvl_e8m2(width); + __riscv_vlseg4e8_v_u8m2(&v_b, &v_g, &v_r, &v_a, src_argb, vl); + __riscv_vsseg3e8_v_u8m2(dst_rgb24, v_b, v_g, v_r, vl); + width -= vl; + src_argb += (4 * vl); + dst_rgb24 += (3 * vl); + } while (width > 0); +} + +void RGB24ToARGBRow_RVV(const uint8_t* src_rgb24, + uint8_t* dst_argb, + int width) { + size_t vl = __riscv_vsetvl_e8m2(width); + vuint8m2_t v_a = __riscv_vmv_v_x_u8m2(255u, vl); + do { + vuint8m2_t v_b, v_g, v_r; + __riscv_vlseg3e8_v_u8m2(&v_b, &v_g, &v_r, src_rgb24, vl); + __riscv_vsseg4e8_v_u8m2(dst_argb, v_b, v_g, v_r, v_a, vl); + width -= vl; + src_rgb24 += (3 * vl); + dst_argb += (4 * vl); + vl = __riscv_vsetvl_e8m2(width); + } while (width > 0); } #ifdef __cplusplus diff --git a/libfenrir/src/main/jni/animation/libyuv/source/scale_any.cc b/libfenrir/src/main/jni/animation/libyuv/source/scale_any.cc index 317041f80..f6576874a 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/scale_any.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/scale_any.cc @@ -128,6 +128,22 @@ SDODD(ScaleRowDown2Box_Odd_NEON, 1, 15) #endif +#ifdef HAS_SCALEUVROWDOWN2_NEON +SDANY(ScaleUVRowDown2_Any_NEON, + ScaleUVRowDown2_NEON, + ScaleUVRowDown2_C, + 2, + 2, + 7) +#endif +#ifdef HAS_SCALEUVROWDOWN2LINEAR_NEON +SDANY(ScaleUVRowDown2Linear_Any_NEON, + ScaleUVRowDown2Linear_NEON, + ScaleUVRowDown2Linear_C, + 2, + 2, + 7) +#endif #ifdef HAS_SCALEUVROWDOWN2BOX_NEON SDANY(ScaleUVRowDown2Box_Any_NEON, ScaleUVRowDown2Box_NEON, diff --git a/libfenrir/src/main/jni/animation/libyuv/source/scale_common.cc b/libfenrir/src/main/jni/animation/libyuv/source/scale_common.cc index da9ca713e..5e603fd40 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/scale_common.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/scale_common.cc @@ -1280,18 +1280,13 @@ void ScaleUVRowDown2_C(const uint8_t* src_uv, ptrdiff_t src_stride, uint8_t* dst_uv, int dst_width) { - const uint16_t* src = (const uint16_t*)(src_uv); - uint16_t* dst = (uint16_t*)(dst_uv); int x; (void)src_stride; - for (x = 0; x < dst_width - 1; x += 2) { - dst[0] = src[1]; - dst[1] = src[3]; - src += 2; - dst += 2; - } - if (dst_width & 1) { - dst[0] = src[1]; + for (x = 0; x < dst_width; ++x) { + dst_uv[0] = src_uv[2]; // Store the 2nd UV + dst_uv[1] = src_uv[3]; + src_uv += 4; + dst_uv += 2; } } diff --git a/libfenrir/src/main/jni/animation/libyuv/source/scale_neon.cc b/libfenrir/src/main/jni/animation/libyuv/source/scale_neon.cc index 6a0d6e1b4..ccc751062 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/scale_neon.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/scale_neon.cc @@ -1428,6 +1428,45 @@ void ScaleARGBFilterCols_NEON(uint8_t* dst_argb, #undef LOAD2_DATA32_LANE +void ScaleUVRowDown2_NEON(const uint8_t* src_ptr, + ptrdiff_t src_stride, + uint8_t* dst, + int dst_width) { + (void)src_stride; + asm volatile( + "1: \n" + "vld2.16 {d0, d2}, [%0]! \n" // load 8 UV pixels. + "vld2.16 {d1, d3}, [%0]! \n" // load next 8 UV + "subs %2, %2, #8 \n" // 8 processed per loop. + "vst1.16 {q1}, [%1]! \n" // store 8 UV + "bgt 1b \n" + : "+r"(src_ptr), // %0 + "+r"(dst), // %1 + "+r"(dst_width) // %2 + : + : "memory", "cc", "q0", "q1"); +} + +void ScaleUVRowDown2Linear_NEON(const uint8_t* src_ptr, + ptrdiff_t src_stride, + uint8_t* dst, + int dst_width) { + (void)src_stride; + asm volatile( + "1: \n" + "vld2.16 {d0, d2}, [%0]! \n" // load 8 UV pixels. + "vld2.16 {d1, d3}, [%0]! \n" // load next 8 UV + "subs %2, %2, #8 \n" // 8 processed per loop. + "vrhadd.u8 q0, q0, q1 \n" // rounding half add + "vst1.16 {q0}, [%1]! \n" // store 8 UV + "bgt 1b \n" + : "+r"(src_ptr), // %0 + "+r"(dst), // %1 + "+r"(dst_width) // %2 + : + : "memory", "cc", "q0", "q1"); +} + void ScaleUVRowDown2Box_NEON(const uint8_t* src_ptr, ptrdiff_t src_stride, uint8_t* dst, 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 9f9636e64..ad06ee83c 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/scale_neon64.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/scale_neon64.cc @@ -1568,6 +1568,45 @@ void ScaleRowUp2_16_NEON(const uint16_t* src_ptr, ); } +void ScaleUVRowDown2_NEON(const uint8_t* src_ptr, + ptrdiff_t src_stride, + uint8_t* dst, + int dst_width) { + (void)src_stride; + asm volatile( + "1: \n" + "ld2 {v0.8h,v1.8h}, [%0], #32 \n" // load 16 UV + "subs %w2, %w2, #8 \n" // 8 processed per loop. + "prfm pldl1keep, [%0, 448] \n" // prefetch 7 lines ahead + "st1 {v1.8h}, [%1], #16 \n" // store 8 UV + "b.gt 1b \n" + : "+r"(src_ptr), // %0 + "+r"(dst), // %1 + "+r"(dst_width) // %2 + : + : "memory", "cc", "v0", "v1"); +} + +void ScaleUVRowDown2Linear_NEON(const uint8_t* src_ptr, + ptrdiff_t src_stride, + uint8_t* dst, + int dst_width) { + (void)src_stride; + asm volatile( + "1: \n" + "ld2 {v0.8h,v1.8h}, [%0], #32 \n" // load 16 UV + "subs %w2, %w2, #8 \n" // 8 processed per loop. + "urhadd v0.16b, v0.16b, v1.16b \n" // rounding half add + "prfm pldl1keep, [%0, 448] \n" // prefetch 7 lines ahead + "st1 {v0.8h}, [%1], #16 \n" // store 8 UV + "b.gt 1b \n" + : "+r"(src_ptr), // %0 + "+r"(dst), // %1 + "+r"(dst_width) // %2 + : + : "memory", "cc", "v0", "v1"); +} + void ScaleUVRowDown2Box_NEON(const uint8_t* src_ptr, ptrdiff_t src_stride, uint8_t* dst, 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 1b5b1db1e..50daa5666 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/scale_uv.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/scale_uv.cc @@ -112,6 +112,22 @@ static void ScaleUVDown2(int src_width, } } #endif +#if defined(HAS_SCALEUVROWDOWN2_NEON) + if (TestCpuFlag(kCpuHasNEON)) { + ScaleUVRowDown2 = + filtering == kFilterNone + ? ScaleUVRowDown2_Any_NEON + : (filtering == kFilterLinear ? ScaleUVRowDown2Linear_Any_NEON + : ScaleUVRowDown2Box_Any_NEON); + if (IS_ALIGNED(dst_width, 8)) { + ScaleUVRowDown2 = + filtering == kFilterNone + ? ScaleUVRowDown2_NEON + : (filtering == kFilterLinear ? ScaleUVRowDown2Linear_NEON + : ScaleUVRowDown2Box_NEON); + } + } +#endif // This code is not enabled. Only box filter is available at this time. #if defined(HAS_SCALEUVROWDOWN2_SSSE3) @@ -130,23 +146,7 @@ static void ScaleUVDown2(int src_width, } } #endif -// This code is not enabled. Only box filter is available at this time. -#if defined(HAS_SCALEUVROWDOWN2_NEON) - if (TestCpuFlag(kCpuHasNEON)) { - ScaleUVRowDown2 = - filtering == kFilterNone - ? ScaleUVRowDown2_Any_NEON - : (filtering == kFilterLinear ? ScaleUVRowDown2Linear_Any_NEON - : ScaleUVRowDown2Box_Any_NEON); - if (IS_ALIGNED(dst_width, 8)) { - ScaleUVRowDown2 = - filtering == kFilterNone - ? ScaleUVRowDown2_NEON - : (filtering == kFilterLinear ? ScaleUVRowDown2Linear_NEON - : ScaleUVRowDown2Box_NEON); - } - } -#endif + #if defined(HAS_SCALEUVROWDOWN2_MSA) if (TestCpuFlag(kCpuHasMSA)) { ScaleUVRowDown2 = diff --git a/libfenrir/src/main/jni/compress/zstd/jni_fast_zstd.c b/libfenrir/src/main/jni/compress/zstd/jni_fast_zstd.c index 69d301a65..7e5f2bb3d 100644 --- a/libfenrir/src/main/jni/compress/zstd/jni_fast_zstd.c +++ b/libfenrir/src/main/jni/compress/zstd/jni_fast_zstd.c @@ -13,7 +13,7 @@ static jfieldID decompress_dict = 0; /* * Class: com_github_luben_zstd_ZstdDictCompress * Method: init - * Signature: ([BI)V + * Signature: ([BIII)V */ JNIEXPORT void JNICALL Java_com_github_luben_zstd_ZstdDictCompress_init (JNIEnv *env, jobject obj, jbyteArray dict, jint dict_offset, jint dict_size, jint level) @@ -29,6 +29,24 @@ JNIEXPORT void JNICALL Java_com_github_luben_zstd_ZstdDictCompress_init (*env)->SetLongField(env, obj, compress_dict, (jlong)(intptr_t) cdict); } +/* + * Class: com_github_luben_zstd_ZstdDictCompress + * Method: init + * Signature: (Ljava/nio/ByteBuffer;III)V + */ +JNIEXPORT void JNICALL Java_com_github_luben_zstd_ZstdDictCompress_initDirect + (JNIEnv *env, jobject obj, jobject dict, jint dict_offset, jint dict_size, jint level) +{ + jclass clazz = (*env)->GetObjectClass(env, obj); + compress_dict = (*env)->GetFieldID(env, clazz, "nativePtr", "J"); + if (NULL == dict) return; + void *dict_buff = (*env)->GetDirectBufferAddress(env, dict); + if (NULL == dict_buff) return; + ZSTD_CDict* cdict = ZSTD_createCDict(((char *)dict_buff) + dict_offset, dict_size, level); + if (NULL == cdict) return; + (*env)->SetLongField(env, obj, compress_dict, (jlong)(intptr_t) cdict); +} + /* * Class: com_github_luben_zstd_ZstdDictCompress * Method: free @@ -46,7 +64,7 @@ JNIEXPORT void JNICALL Java_com_github_luben_zstd_ZstdDictCompress_free /* * Class: com_github_luben_zstd_ZstdDictDecompress * Method: init - * Signature: ([B)V + * Signature: ([BII)V */ JNIEXPORT void JNICALL Java_com_github_luben_zstd_ZstdDictDecompress_init (JNIEnv *env, jobject obj, jbyteArray dict, jint dict_offset, jint dict_size) @@ -64,6 +82,25 @@ JNIEXPORT void JNICALL Java_com_github_luben_zstd_ZstdDictDecompress_init (*env)->SetLongField(env, obj, decompress_dict, (jlong)(intptr_t) ddict); } +/* + * Class: com_github_luben_zstd_ZstdDictDecompress + * Method: initDirect + * Signature: (Ljava/nio/ByteBuffer;II)V + */ +JNIEXPORT void JNICALL Java_com_github_luben_zstd_ZstdDictDecompress_initDirect + (JNIEnv *env, jobject obj, jobject dict, jint dict_offset, jint dict_size) +{ + jclass clazz = (*env)->GetObjectClass(env, obj); + decompress_dict = (*env)->GetFieldID(env, clazz, "nativePtr", "J"); + if (NULL == dict) return; + void *dict_buff = (*env)->GetDirectBufferAddress(env, dict); + + ZSTD_DDict* ddict = ZSTD_createDDict(((char *)dict_buff) + dict_offset, dict_size); + + if (NULL == ddict) return; + (*env)->SetLongField(env, obj, decompress_dict, (jlong)(intptr_t) ddict); +} + /* * Class: com_github_luben_zstd_ZstdDictDecompress * Method: free diff --git a/libfenrir/src/main/jni/compress/zstd/jni_zstd.c b/libfenrir/src/main/jni/compress/zstd/jni_zstd.c index 9deae72c0..d8c8def98 100644 --- a/libfenrir/src/main/jni/compress/zstd/jni_zstd.c +++ b/libfenrir/src/main/jni/compress/zstd/jni_zstd.c @@ -125,6 +125,20 @@ JNIEXPORT jlong JNICALL Java_com_github_luben_zstd_Zstd_getDictIdFromDict E1: return (jlong) dict_id; } +/* + * Class: com_github_luben_zstd_Zstd + * Method: getDictIdFromDict + * Signature: (Ljava/nio/ByteBuffer;II)J + */ +JNIEXPORT jlong JNICALL Java_com_github_luben_zstd_Zstd_getDictIdFromDictDirect +(JNIEnv *env, jclass obj, jobject src, jint offset, jint src_size) { + unsigned dict_id = 0; + char *src_buff = (char*)(*env)->GetDirectBufferAddress(env, src); + if (src_buff == NULL) goto E1; + dict_id = ZSTD_getDictID_fromDict(src_buff + offset, (size_t) src_size); +E1: return (jlong) dict_id; +} + /* * Class: com_github_luben_zstd_Zstd * Method: decompressedDirectByteBufferSize diff --git a/libfenrir/src/main/jni/thorvg/src/lib/sw_engine/tvgSwCommon.h b/libfenrir/src/main/jni/thorvg/src/lib/sw_engine/tvgSwCommon.h index e177f6118..db22a57e0 100644 --- a/libfenrir/src/main/jni/thorvg/src/lib/sw_engine/tvgSwCommon.h +++ b/libfenrir/src/main/jni/thorvg/src/lib/sw_engine/tvgSwCommon.h @@ -335,10 +335,12 @@ void fillFetchLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, void fillFetchRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len); SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& renderRegion, bool antiAlias); +SwRleData* rleRender(const SwBBox* bbox); void rleFree(SwRleData* rle); void rleReset(SwRleData* rle); -void rleClipPath(SwRleData *rle, const SwRleData *clip); -void rleClipRect(SwRleData *rle, const SwBBox* clip); +void rleMerge(SwRleData* rle, SwRleData* clip1, SwRleData* clip2); +void rleClipPath(SwRleData* rle, const SwRleData* clip); +void rleClipRect(SwRleData* rle, const SwBBox* clip); SwMpool* mpoolInit(uint32_t threads); bool mpoolTerm(SwMpool* mpool); diff --git a/libfenrir/src/main/jni/thorvg/src/lib/sw_engine/tvgSwRenderer.cpp b/libfenrir/src/main/jni/thorvg/src/lib/sw_engine/tvgSwRenderer.cpp index e589d6184..412cbe0b8 100644 --- a/libfenrir/src/main/jni/thorvg/src/lib/sw_engine/tvgSwRenderer.cpp +++ b/libfenrir/src/main/jni/thorvg/src/lib/sw_engine/tvgSwRenderer.cpp @@ -61,6 +61,8 @@ struct SwTask : Task } virtual bool dispose() = 0; + virtual bool clip(SwRleData* target) = 0; + virtual SwRleData* rle() = 0; virtual ~SwTask() { @@ -76,6 +78,23 @@ struct SwShapeTask : SwTask bool cmpStroking = false; bool clipper = false; + bool clip(SwRleData* target) override + { + if (shape.fastTrack) rleClipRect(target, &bbox); + else if (shape.rle) rleClipPath(target, shape.rle); + else return false; + + return true; + } + + SwRleData* rle() override + { + if (!shape.rle && shape.fastTrack) { + shape.rle = rleRender(&shape.bbox); + } + return shape.rle; + } + void run(unsigned tid) override { if (opacity == 0 && !clipper) return; //Invisible @@ -150,22 +169,13 @@ struct SwShapeTask : SwTask //Clip Path for (auto clip = clips.data; clip < (clips.data + clips.count); ++clip) { + auto clipper = static_cast(*clip); //Guarantee composition targets get ready. - static_cast(*clip)->done(tid); - - auto clipper = &static_cast(*clip)->shape; + clipper->done(tid); //Clip shape rle - if (shape.rle) { - if (clipper->fastTrack) rleClipRect(shape.rle, &clipper->bbox); - else if (clipper->rle) rleClipPath(shape.rle, clipper->rle); - else goto err; - } + if (shape.rle && !clipper->clip(shape.rle)) goto err; //Clip stroke rle - if (shape.strokeRle) { - if (clipper->fastTrack) rleClipRect(shape.strokeRle, &clipper->bbox); - else if (clipper->rle) rleClipPath(shape.strokeRle, clipper->rle); - else goto err; - } + if (shape.strokeRle && !clipper->clip(shape.strokeRle)) goto err; } goto end; @@ -183,12 +193,86 @@ struct SwShapeTask : SwTask }; +struct SwSceneTask : SwTask +{ + Array scene; //list of paints render data (SwTask) + SwRleData* sceneRle = nullptr; + + bool clip(SwRleData* target) override + { + //Only one shape + if (scene.count == 1) { + return static_cast(*scene.data)->clip(target); + } + + //More than one shapes + if (sceneRle) rleClipPath(target, sceneRle); + else TVGLOG("SW_ENGINE", "No clippers in a scene?"); + + return true; + } + + SwRleData* rle() override + { + return sceneRle; + } + + void run(unsigned tid) override + { + //TODO: Skip the run if the scene hans't changed. + if (!sceneRle) sceneRle = static_cast(calloc(1, sizeof(SwRleData))); + else rleReset(sceneRle); + + //Only one shape + if (scene.count == 1) { + auto clipper = static_cast(*scene.data); + clipper->done(tid); + //Merge shapes if it has more than one shapes + } else { + //Merge first two clippers + auto clipper1 = static_cast(*scene.data); + clipper1->done(tid); + + auto clipper2 = static_cast(*(scene.data + 1)); + clipper2->done(tid); + + rleMerge(sceneRle, clipper1->rle(), clipper2->rle()); + + //Unify the remained clippers + for (auto rd = scene.data + 2; rd < (scene.data + scene.count); ++rd) { + auto clipper = static_cast(*rd); + clipper->done(tid); + rleMerge(sceneRle, sceneRle, clipper->rle()); + } + } + } + + bool dispose() override + { + rleFree(sceneRle); + return true; + } +}; + + struct SwImageTask : SwTask { SwImage image; Polygon* triangles; uint32_t triangleCnt; + bool clip(SwRleData* target) override + { + TVGERR("SW_ENGINE", "Image is used as ClipPath?"); + return true; + } + + SwRleData* rle() override + { + TVGERR("SW_ENGINE", "Image is used as Scene ClipPath?"); + return nullptr; + } + void run(unsigned tid) override { auto clipRegion = bbox; @@ -205,13 +289,10 @@ struct SwImageTask : SwTask if (!imageGenRle(&image, bbox, false)) goto end; if (image.rle) { for (auto clip = clips.data; clip < (clips.data + clips.count); ++clip) { + auto clipper = static_cast(*clip); //Guarantee composition targets get ready. - static_cast(*clip)->done(tid); - - auto clipper = &static_cast(*clip)->shape; - if (clipper->fastTrack) rleClipRect(image.rle, &clipper->bbox); - else if (clipper->rle) rleClipPath(image.rle, clipper->rle); - else goto err; + clipper->done(tid); + if (!clipper->clip(image.rle)) goto err; } } } @@ -596,7 +677,7 @@ void* SwRenderer::prepareCommon(SwTask* task, const RenderTransform* transform, //Finish previous task if it has duplicated request. task->done(); - if (clips.count > 0) task->clips = clips; + task->clips = clips; if (transform) { if (!task->transform) task->transform = static_cast(malloc(sizeof(Matrix))); @@ -643,6 +724,17 @@ RenderData SwRenderer::prepare(Surface* image, Polygon* triangles, uint32_t tria } +RenderData SwRenderer::prepare(const Array& scene, RenderData data, const RenderTransform* transform, uint32_t opacity, Array& clips, RenderUpdateFlag flags) +{ + //prepare task + auto task = static_cast(data); + if (!task) task = new SwSceneTask; + task->scene = scene; + + return prepareCommon(task, transform, opacity, clips, flags); +} + + RenderData SwRenderer::prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, uint32_t opacity, Array& clips, RenderUpdateFlag flags, bool clipper) { //prepare task @@ -650,8 +742,9 @@ RenderData SwRenderer::prepare(const RenderShape& rshape, RenderData data, const if (!task) { task = new SwShapeTask; task->rshape = &rshape; - task->clipper = clipper; } + task->clipper = clipper; + return prepareCommon(task, transform, opacity, clips, flags); } diff --git a/libfenrir/src/main/jni/thorvg/src/lib/sw_engine/tvgSwRenderer.h b/libfenrir/src/main/jni/thorvg/src/lib/sw_engine/tvgSwRenderer.h index 690b7ff0d..5ceb1a1c5 100644 --- a/libfenrir/src/main/jni/thorvg/src/lib/sw_engine/tvgSwRenderer.h +++ b/libfenrir/src/main/jni/thorvg/src/lib/sw_engine/tvgSwRenderer.h @@ -37,6 +37,7 @@ class SwRenderer : public RenderMethod { public: RenderData prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, uint32_t opacity, Array& clips, RenderUpdateFlag flags, bool clipper) override; + RenderData prepare(const Array& scene, RenderData data, const RenderTransform* transform, uint32_t opacity, Array& clips, RenderUpdateFlag flags) override; RenderData prepare(Surface* image, Polygon* triangles, uint32_t triangleCnt, RenderData data, const RenderTransform* transform, uint32_t opacity, Array& clips, RenderUpdateFlag flags) override; bool preRender() override; bool renderShape(RenderData data) override; diff --git a/libfenrir/src/main/jni/thorvg/src/lib/sw_engine/tvgSwRle.cpp b/libfenrir/src/main/jni/thorvg/src/lib/sw_engine/tvgSwRle.cpp index 6651e4e0f..52c831ef3 100644 --- a/libfenrir/src/main/jni/thorvg/src/lib/sw_engine/tvgSwRle.cpp +++ b/libfenrir/src/main/jni/thorvg/src/lib/sw_engine/tvgSwRle.cpp @@ -773,60 +773,58 @@ static int _genRle(RleWorker& rw) } -SwSpan* _intersectSpansRegion(const SwRleData *clip, const SwRleData *targetRle, SwSpan *outSpans, uint32_t spanCnt) +static SwSpan* _intersectSpansRegion(const SwRleData *clip, const SwRleData *target, SwSpan *outSpans) { auto out = outSpans; - auto spans = targetRle->spans; - auto end = targetRle->spans + targetRle->size; + auto spans = target->spans; + auto end = target->spans + target->size; auto clipSpans = clip->spans; auto clipEnd = clip->spans + clip->size; - while (spanCnt > 0 && spans < end) { - if (clipSpans == clipEnd) { - spans = end; - break; - } + while (spans < end && clipSpans < clipEnd) { + //align y cooridnates. if (clipSpans->y > spans->y) { ++spans; continue; } - if (spans->y != clipSpans->y) { + if (spans->y > clipSpans->y) { ++clipSpans; continue; } - auto sx1 = spans->x; - auto sx2 = sx1 + spans->len; - auto cx1 = clipSpans->x; - auto cx2 = cx1 + clipSpans->len; - if (cx1 < sx1 && cx2 < sx1) { - ++clipSpans; - continue; - } - else if (sx1 < cx1 && sx2 < cx1) { - ++spans; - continue; - } - auto x = sx1 > cx1 ? sx1 : cx1; - auto len = (sx2 < cx2 ? sx2 : cx2) - x; - if (len) { - auto spansCorverage = spans->coverage; - auto clipSpansCoverage = clipSpans->coverage; - out->x = sx1 > cx1 ? sx1 : cx1; - out->len = (sx2 < cx2 ? sx2 : cx2) - out->x; - out->y = spans->y; - out->coverage = (uint8_t)(((spansCorverage * clipSpansCoverage) + 0xff) >> 8); - ++out; - --spanCnt; + //Try clipping with all clip spans which have a same y coordinate. + auto temp = clipSpans; + while(temp->y == clipSpans->y) { + auto sx1 = spans->x; + auto sx2 = sx1 + spans->len; + auto cx1 = temp->x; + auto cx2 = cx1 + temp->len; + + //The span must be left(x1) to right(x2) direction. Not intersected. + if (cx2 < sx1 || sx2 < cx1) { + ++temp; + continue; + } + + //clip span region. + auto x = sx1 > cx1 ? sx1 : cx1; + auto len = (sx2 < cx2 ? sx2 : cx2) - x; + if (len > 0) { + out->x = x; + out->y = temp->y; + out->len = len; + out->coverage = (uint8_t)(((spans->coverage * temp->coverage) + 0xff) >> 8); + ++out; + } + ++temp; } - if (sx2 < cx2) ++spans; - else ++clipSpans; + ++spans; } return out; } -SwSpan* _intersectSpansRect(const SwBBox *bbox, const SwRleData *targetRle, SwSpan *outSpans, uint32_t spanCnt) +static SwSpan* _intersectSpansRect(const SwBBox *bbox, const SwRleData *targetRle, SwSpan *outSpans, uint32_t spanCnt) { auto out = outSpans; auto spans = targetRle->spans; @@ -865,6 +863,46 @@ SwSpan* _intersectSpansRect(const SwBBox *bbox, const SwRleData *targetRle, SwSp } +static SwSpan* _mergeSpansRegion(const SwRleData *clip1, const SwRleData *clip2, SwSpan *outSpans) +{ + auto out = outSpans; + auto spans1 = clip1->spans; + auto end1 = clip1->spans + clip1->size; + auto spans2 = clip2->spans; + auto end2 = clip2->spans + clip2->size; + + //list two spans up in y order + //TODO: Remove duplicated regions? + while (spans1 < end1 && spans2 < end2) { + while (spans1 < end1 && spans1->y <= spans2->y) { + *out = *spans1; + ++spans1; + ++out; + } + if (spans1 >= end1) break; + while (spans2 < end2 && spans2->y <= spans1->y) { + *out = *spans2; + ++spans2; + ++out; + } + } + + //Leftovers + while (spans1 < end1) { + *out = *spans1; + ++spans1; + ++out; + } + while (spans2 < end2) { + *out = *spans2; + ++spans2; + ++out; + } + + return out; +} + + void _replaceClipSpan(SwRleData *rle, SwSpan* clippedSpans, uint32_t size) { free(rle->spans); @@ -1001,6 +1039,28 @@ SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& ren } +SwRleData* rleRender(const SwBBox* bbox) +{ + auto width = static_cast(bbox->max.x - bbox->min.x); + auto height = static_cast(bbox->max.y - bbox->min.y); + + auto rle = static_cast(malloc(sizeof(SwRleData))); + rle->spans = static_cast(malloc(sizeof(SwSpan) * height)); + rle->size = height; + rle->alloc = height; + + auto span = rle->spans; + for (uint16_t i = 0; i < height; ++i, ++span) { + span->x = bbox->min.x; + span->y = bbox->min.y + i; + span->len = width; + span->coverage = 255; + } + + return rle; +} + + void rleReset(SwRleData* rle) { if (!rle) return; @@ -1016,13 +1076,51 @@ void rleFree(SwRleData* rle) } +void rleMerge(SwRleData* rle, SwRleData* clip1, SwRleData* clip2) +{ + if (!rle || (!clip1 && !clip2)) return; + if (clip1 && clip1->size == 0 && clip2 && clip2->size == 0) return; + + TVGLOG("SW_ENGINE", "Unifying Rle!"); + + //clip1 is empty, just copy clip2 + if (!clip1 || clip1->size == 0) { + if (clip2) { + auto spans = static_cast(malloc(sizeof(SwSpan) * (clip2->size))); + memcpy(spans, clip2->spans, clip2->size); + _replaceClipSpan(rle, spans, clip2->size); + } else { + _replaceClipSpan(rle, nullptr, 0); + } + return; + } + + //clip2 is empty, just copy clip1 + if (!clip2 || clip2->size == 0) { + if (clip1) { + auto spans = static_cast(malloc(sizeof(SwSpan) * (clip1->size))); + memcpy(spans, clip1->spans, clip1->size); + _replaceClipSpan(rle, spans, clip1->size); + } else { + _replaceClipSpan(rle, nullptr, 0); + } + return; + } + + auto spanCnt = clip1->size + clip2->size; + auto spans = static_cast(malloc(sizeof(SwSpan) * spanCnt)); + auto spansEnd = _mergeSpansRegion(clip1, clip2, spans); + + _replaceClipSpan(rle, spans, spansEnd - spans); +} + + void rleClipPath(SwRleData *rle, const SwRleData *clip) { if (rle->size == 0 || clip->size == 0) return; auto spanCnt = rle->size > clip->size ? rle->size : clip->size; auto spans = static_cast(malloc(sizeof(SwSpan) * (spanCnt))); - if (!spans) return; - auto spansEnd = _intersectSpansRegion(clip, rle, spans, spanCnt); + auto spansEnd = _intersectSpansRegion(clip, rle, spans); _replaceClipSpan(rle, spans, spansEnd - spans); @@ -1034,10 +1132,9 @@ void rleClipRect(SwRleData *rle, const SwBBox* clip) { if (rle->size == 0) return; auto spans = static_cast(malloc(sizeof(SwSpan) * (rle->size))); - if (!spans) return; auto spansEnd = _intersectSpansRect(clip, rle, spans, rle->size); _replaceClipSpan(rle, spans, spansEnd - spans); TVGLOG("SW_ENGINE", "Using ClipRect!"); -} +} \ No newline at end of file diff --git a/libfenrir/src/main/jni/thorvg/src/lib/tvgCommon.h b/libfenrir/src/main/jni/thorvg/src/lib/tvgCommon.h index 52bdb5f23..9c3888a5c 100644 --- a/libfenrir/src/main/jni/thorvg/src/lib/tvgCommon.h +++ b/libfenrir/src/main/jni/thorvg/src/lib/tvgCommon.h @@ -65,8 +65,14 @@ using namespace tvg; enum class FileType { Tvg = 0, Svg, Raw, Png, Jpg, Unknown }; #ifdef THORVG_LOG_ENABLED - #define TVGLOG(tag, fmt, ...) fprintf(stdout, tag ": " fmt "\n", ##__VA_ARGS__) //Log Message for notifying user some useful info - #define TVGERR(tag, fmt, ...) fprintf(stderr, tag ": " fmt "\n", ##__VA_ARGS__) //Error Message for us to fix it + constexpr auto ErrorColor = "\033[31m"; //red + constexpr auto ErrorBgColor = "\033[41m";//bg red + constexpr auto LogColor = "\033[32m"; //green + constexpr auto LogBgColor = "\033[42m"; //bg green + constexpr auto GreyColor = "\033[90m"; //grey + constexpr auto ResetColors = "\033[0m"; //default + #define TVGERR(tag, fmt, ...) fprintf(stderr, "%s[E]%s %s" tag "%s (%s l.%d): %s" fmt "\n", ErrorBgColor, ResetColors, ErrorColor, GreyColor, __FILE__, __LINE__, ResetColors, ##__VA_ARGS__) + #define TVGLOG(tag, fmt, ...) fprintf(stderr, "%s[L]%s %s" tag "%s (%s l.%d): %s" fmt "\n", LogBgColor, ResetColors, LogColor, GreyColor, __FILE__, __LINE__, ResetColors, ##__VA_ARGS__) #else #define TVGERR(...) #define TVGLOG(...) diff --git a/libfenrir/src/main/jni/thorvg/src/lib/tvgPaint.cpp b/libfenrir/src/main/jni/thorvg/src/lib/tvgPaint.cpp index 506fc5522..029c6b46c 100644 --- a/libfenrir/src/main/jni/thorvg/src/lib/tvgPaint.cpp +++ b/libfenrir/src/main/jni/thorvg/src/lib/tvgPaint.cpp @@ -162,8 +162,6 @@ bool Paint::Impl::render(RenderMethod& renderer) { Compositor* cmp = nullptr; - //OPTIMIZE_ME: Can we replace the simple AlphaMasking with ClipPath? - /* Note: only ClipPath is processed in update() step. Create a composition image. */ if (compData && compData->method != CompositeMethod::ClipPath && !(compData->target->pImpl->ctxFlag & ContextFlag::FastTrack)) { @@ -184,7 +182,7 @@ bool Paint::Impl::render(RenderMethod& renderer) } -void* Paint::Impl::update(RenderMethod& renderer, const RenderTransform* pTransform, uint32_t opacity, Array& clips, uint32_t pFlag, bool clipper) +RenderData Paint::Impl::update(RenderMethod& renderer, const RenderTransform* pTransform, uint32_t opacity, Array& clips, uint32_t pFlag, bool clipper) { if (renderFlag & RenderUpdateFlag::Transform) { if (!rTransform) return nullptr; @@ -195,9 +193,10 @@ void* Paint::Impl::update(RenderMethod& renderer, const RenderTransform* pTransf } /* 1. Composition Pre Processing */ - void *tdata = nullptr; + RenderData trd = nullptr; //composite target render data RenderRegion viewport; bool compFastTrack = false; + bool childClipper = false; if (compData) { auto target = compData->target; @@ -207,48 +206,50 @@ void* Paint::Impl::update(RenderMethod& renderer, const RenderTransform* pTransf /* If transform has no rotation factors && ClipPath / AlphaMasking is a simple rectangle, we can avoid regular ClipPath / AlphaMasking sequence but use viewport for performance */ auto tryFastTrack = false; - if (method == CompositeMethod::ClipPath) tryFastTrack = true; - else if (method == CompositeMethod::AlphaMask && target->identifier() == TVG_CLASS_ID_SHAPE) { - auto shape = static_cast(target); - uint8_t a; - shape->fillColor(nullptr, nullptr, nullptr, &a); - if (a == 255 && shape->opacity() == 255 && !shape->fill()) tryFastTrack = true; - } - if (tryFastTrack) { - RenderRegion viewport2; - if ((compFastTrack = _compFastTrack(target, pTransform, target->pImpl->rTransform, viewport2))) { - viewport = renderer.viewport(); - viewport2.intersect(viewport); - renderer.viewport(viewport2); - target->pImpl->ctxFlag |= ContextFlag::FastTrack; + if (target->identifier() == TVG_CLASS_ID_SHAPE) { + if (method == CompositeMethod::ClipPath) tryFastTrack = true; + else if (method == CompositeMethod::AlphaMask) { + auto shape = static_cast(target); + uint8_t a; + shape->fillColor(nullptr, nullptr, nullptr, &a); + if (a == 255 && shape->opacity() == 255 && !shape->fill()) tryFastTrack = true; + } + if (tryFastTrack) { + RenderRegion viewport2; + if ((compFastTrack = _compFastTrack(target, pTransform, target->pImpl->rTransform, viewport2))) { + viewport = renderer.viewport(); + viewport2.intersect(viewport); + renderer.viewport(viewport2); + target->pImpl->ctxFlag |= ContextFlag::FastTrack; + } } } if (!compFastTrack) { - auto clipper = compData->method == CompositeMethod::ClipPath ? true : false; - tdata = target->pImpl->update(renderer, pTransform, 255, clips, pFlag, clipper); - if (method == CompositeMethod::ClipPath) clips.push(tdata); + childClipper = compData->method == CompositeMethod::ClipPath ? true : false; + trd = target->pImpl->update(renderer, pTransform, 255, clips, pFlag, childClipper); + if (childClipper) clips.push(trd); } } /* 2. Main Update */ - void *edata = nullptr; + RenderData rd = nullptr; auto newFlag = static_cast(pFlag | renderFlag); renderFlag = RenderUpdateFlag::None; opacity = (opacity * this->opacity) / 255; if (rTransform && pTransform) { RenderTransform outTransform(pTransform, rTransform); - edata = smethod->update(renderer, &outTransform, opacity, clips, newFlag, clipper); + rd = smethod->update(renderer, &outTransform, opacity, clips, newFlag, clipper); } else { auto outTransform = pTransform ? pTransform : rTransform; - edata = smethod->update(renderer, outTransform, opacity, clips, newFlag, clipper); + rd = smethod->update(renderer, outTransform, opacity, clips, newFlag, clipper); } /* 3. Composition Post Processing */ if (compFastTrack) renderer.viewport(viewport); - else if (tdata && compData->method == CompositeMethod::ClipPath) clips.pop(); + else if (childClipper) clips.pop(); - return edata; + return rd; } diff --git a/libfenrir/src/main/jni/thorvg/src/lib/tvgPaint.h b/libfenrir/src/main/jni/thorvg/src/lib/tvgPaint.h index 9ec023294..d70490c64 100644 --- a/libfenrir/src/main/jni/thorvg/src/lib/tvgPaint.h +++ b/libfenrir/src/main/jni/thorvg/src/lib/tvgPaint.h @@ -147,7 +147,7 @@ namespace tvg bool scale(float factor); bool translate(float x, float y); bool bounds(float* x, float* y, float* w, float* h, bool transformed); - void* update(RenderMethod& renderer, const RenderTransform* pTransform, uint32_t opacity, Array& clips, uint32_t pFlag, bool clipper = false); + RenderData update(RenderMethod& renderer, const RenderTransform* pTransform, uint32_t opacity, Array& clips, uint32_t pFlag, bool clipper = false); bool render(RenderMethod& renderer); Paint* duplicate(); }; @@ -176,7 +176,7 @@ namespace tvg return inst->dispose(renderer); } - void* update(RenderMethod& renderer, const RenderTransform* transform, uint32_t opacity, Array& clips, RenderUpdateFlag renderFlag, bool clipper) override + RenderData update(RenderMethod& renderer, const RenderTransform* transform, uint32_t opacity, Array& clips, RenderUpdateFlag renderFlag, bool clipper) override { return inst->update(renderer, transform, opacity, clips, renderFlag, clipper); } diff --git a/libfenrir/src/main/jni/thorvg/src/lib/tvgPictureImpl.h b/libfenrir/src/main/jni/thorvg/src/lib/tvgPictureImpl.h index 781e72a6c..3d2abdd86 100644 --- a/libfenrir/src/main/jni/thorvg/src/lib/tvgPictureImpl.h +++ b/libfenrir/src/main/jni/thorvg/src/lib/tvgPictureImpl.h @@ -65,11 +65,11 @@ struct Picture::Impl Paint* paint = nullptr; //vector picture uses Surface* surface = nullptr; //bitmap picture uses Polygon* triangles = nullptr; //mesh data - uint32_t triangleCnt = 0; //mesh triangle count - void* rdata = nullptr; //engine data + uint32_t triangleCnt = 0; //mesh triangle count + RenderData rd = nullptr; //engine data float w = 0, h = 0; - bool resizing = false; uint32_t rendererColorSpace = 0; + bool resizing = false; ~Impl() { @@ -81,12 +81,10 @@ struct Picture::Impl bool dispose(RenderMethod& renderer) { bool ret = true; - if (paint) { - ret = paint->pImpl->dispose(renderer); - } else if (surface) { - ret = renderer.dispose(rdata); - rdata = nullptr; - } + if (paint) ret = paint->pImpl->dispose(renderer); + else if (surface) ret = renderer.dispose(rd); + rd = nullptr; + return ret; } @@ -131,29 +129,29 @@ struct Picture::Impl else return RenderTransform(pTransform, &tmp); } - void* update(RenderMethod &renderer, const RenderTransform* pTransform, uint32_t opacity, Array& clips, RenderUpdateFlag pFlag, bool clipper) + RenderData update(RenderMethod &renderer, const RenderTransform* pTransform, uint32_t opacity, Array& clips, RenderUpdateFlag pFlag, bool clipper) { rendererColorSpace = renderer.colorSpace(); auto flag = reload(); if (surface) { auto transform = resizeTransform(pTransform); - rdata = renderer.prepare(surface, triangles, triangleCnt, rdata, &transform, opacity, clips, static_cast(pFlag | flag)); + rd = renderer.prepare(surface, triangles, triangleCnt, rd, &transform, opacity, clips, static_cast(pFlag | flag)); } else if (paint) { if (resizing) { loader->resize(paint, w, h); resizing = false; } - rdata = paint->pImpl->update(renderer, pTransform, opacity, clips, static_cast(pFlag | flag), clipper); + rd = paint->pImpl->update(renderer, pTransform, opacity, clips, static_cast(pFlag | flag), clipper); } - return rdata; + return rd; } bool render(RenderMethod &renderer) { if (surface) { - if (triangles) return renderer.renderImageMesh(rdata); - else return renderer.renderImage(rdata); + if (triangles) return renderer.renderImageMesh(rd); + else return renderer.renderImage(rd); } else if (paint) return paint->pImpl->render(renderer); return false; @@ -214,7 +212,7 @@ struct Picture::Impl RenderRegion bounds(RenderMethod& renderer) { - if (rdata) return renderer.region(rdata); + if (rd) return renderer.region(rd); if (paint) return paint->pImpl->bounds(renderer); return {0, 0, 0, 0}; } diff --git a/libfenrir/src/main/jni/thorvg/src/lib/tvgRender.h b/libfenrir/src/main/jni/thorvg/src/lib/tvgRender.h index 55bbec1fc..a52f203c5 100644 --- a/libfenrir/src/main/jni/thorvg/src/lib/tvgRender.h +++ b/libfenrir/src/main/jni/thorvg/src/lib/tvgRender.h @@ -186,6 +186,7 @@ class RenderMethod public: virtual ~RenderMethod() {} virtual RenderData prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, uint32_t opacity, Array& clips, RenderUpdateFlag flags, bool clipper) = 0; + virtual RenderData prepare(const Array& scene, RenderData data, const RenderTransform* transform, uint32_t opacity, Array& clips, RenderUpdateFlag flags) = 0; virtual RenderData prepare(Surface* image, Polygon* triangles, uint32_t triangleCnt, RenderData data, const RenderTransform* transform, uint32_t opacity, Array& clips, RenderUpdateFlag flags) = 0; virtual bool preRender() = 0; virtual bool renderShape(RenderData data) = 0; diff --git a/libfenrir/src/main/jni/thorvg/src/lib/tvgSceneImpl.h b/libfenrir/src/main/jni/thorvg/src/lib/tvgSceneImpl.h index 4265ff7ff..5766b9e75 100644 --- a/libfenrir/src/main/jni/thorvg/src/lib/tvgSceneImpl.h +++ b/libfenrir/src/main/jni/thorvg/src/lib/tvgSceneImpl.h @@ -61,6 +61,7 @@ struct Scene::Impl Array paints; uint8_t opacity; //for composition RenderMethod* renderer = nullptr; //keep it for explicit clear + RenderData rd = nullptr; Scene* scene = nullptr; Impl(Scene* s) : scene(s) @@ -80,9 +81,11 @@ struct Scene::Impl (*paint)->pImpl->dispose(renderer); } + auto ret = renderer.dispose(rd); this->renderer = nullptr; + this->rd = nullptr; - return true; + return ret; } bool needComposition(uint32_t opacity) @@ -102,23 +105,29 @@ struct Scene::Impl return false; } - void* update(RenderMethod &renderer, const RenderTransform* transform, uint32_t opacity, Array& clips, RenderUpdateFlag flag, bool clipper) + RenderData update(RenderMethod &renderer, const RenderTransform* transform, uint32_t opacity, Array& clips, RenderUpdateFlag flag, bool clipper) { /* Overriding opacity value. If this scene is half-translucent, It must do intermeidate composition with that opacity value. */ this->opacity = static_cast(opacity); if (needComposition(opacity)) opacity = 255; - for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) { - (*paint)->pImpl->update(renderer, transform, opacity, clips, static_cast(flag), clipper); - } - - /* FXIME: it requires to return list of children engine data - This is necessary for scene composition */ - this->renderer = &renderer; - return nullptr; + if (clipper) { + Array rds; + rds.reserve(paints.count); + for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) { + rds.push((*paint)->pImpl->update(renderer, transform, opacity, clips, flag, true)); + } + rd = renderer.prepare(rds, rd, transform, opacity, clips, flag); + return rd; + } else { + for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) { + (*paint)->pImpl->update(renderer, transform, opacity, clips, flag, false); + } + return nullptr; + } } bool render(RenderMethod& renderer) diff --git a/libfenrir/src/main/jni/thorvg/src/lib/tvgShapeImpl.h b/libfenrir/src/main/jni/thorvg/src/lib/tvgShapeImpl.h index 74988c31d..824142a81 100644 --- a/libfenrir/src/main/jni/thorvg/src/lib/tvgShapeImpl.h +++ b/libfenrir/src/main/jni/thorvg/src/lib/tvgShapeImpl.h @@ -48,7 +48,7 @@ struct Shape::Impl return renderer.renderShape(rd); } - void* update(RenderMethod& renderer, const RenderTransform* transform, uint32_t opacity, Array& clips, RenderUpdateFlag pFlag, bool clipper) + RenderData update(RenderMethod& renderer, const RenderTransform* transform, uint32_t opacity, Array& clips, RenderUpdateFlag pFlag, bool clipper) { rd = renderer.prepare(rs, rd, transform, opacity, clips, static_cast(pFlag | flag), clipper); flag = RenderUpdateFlag::None; diff --git a/libfenrir/src/main/jni/thorvg/src/lib/tvgTaskScheduler.h b/libfenrir/src/main/jni/thorvg/src/lib/tvgTaskScheduler.h index 8f60903f1..36c494c9e 100644 --- a/libfenrir/src/main/jni/thorvg/src/lib/tvgTaskScheduler.h +++ b/libfenrir/src/main/jni/thorvg/src/lib/tvgTaskScheduler.h @@ -88,7 +88,7 @@ struct Task finished = false; } - friend class TaskSchedulerImpl; + friend struct TaskSchedulerImpl; }; diff --git a/libfenrir/src/main/jni/thorvg/src/loaders/svg/tvgSvgLoader.cpp b/libfenrir/src/main/jni/thorvg/src/loaders/svg/tvgSvgLoader.cpp index a07472a85..6d41883a2 100644 --- a/libfenrir/src/main/jni/thorvg/src/loaders/svg/tvgSvgLoader.cpp +++ b/libfenrir/src/main/jni/thorvg/src/loaders/svg/tvgSvgLoader.cpp @@ -1881,520 +1881,308 @@ static SvgNode* _findNodeById(SvgNode *node, const char* id) } -static void _cloneGradStops(Array& dst, const Array& src) +static constexpr struct { - for (uint32_t i = 0; i < src.count; ++i) { - dst.push(src.data[i]); - } -} + const char* tag; + SvgParserLengthType type; + int sz; + size_t offset; +} useTags[] = { + {"x", SvgParserLengthType::Horizontal, sizeof("x"), offsetof(SvgUseNode, x)}, + {"y", SvgParserLengthType::Vertical, sizeof("y"), offsetof(SvgUseNode, y)}, + {"width", SvgParserLengthType::Horizontal, sizeof("width"), offsetof(SvgUseNode, w)}, + {"height", SvgParserLengthType::Vertical, sizeof("height"), offsetof(SvgUseNode, h)} +}; -static SvgStyleGradient* _cloneGradient(SvgStyleGradient* from) +static void _cloneNode(SvgNode* from, SvgNode* parent, int depth); +static bool _attrParseUseNode(void* data, const char* key, const char* value) { - if (!from) return nullptr; + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode *defs, *nodeFrom, *node = loader->svgParse->node; + char* id; - auto grad = (SvgStyleGradient*)(calloc(1, sizeof(SvgStyleGradient))); - if (!grad) return nullptr; + SvgUseNode* use = &(node->node.use); + int sz = strlen(key); + unsigned char* array = (unsigned char*)use; + for (unsigned int i = 0; i < sizeof(useTags) / sizeof(useTags[0]); i++) { + if (useTags[i].sz - 1 == sz && !strncmp(useTags[i].tag, key, sz)) { + *((float*)(array + useTags[i].offset)) = _toFloat(loader->svgParse, value, useTags[i].type); - grad->type = from->type; - grad->id = from->id ? _copyId(from->id) : nullptr; - grad->ref = from->ref ? _copyId(from->ref) : nullptr; - grad->spread = from->spread; - grad->userSpace = from->userSpace; + if (useTags[i].offset == offsetof(SvgUseNode, w)) use->isWidthSet = true; + else if (useTags[i].offset == offsetof(SvgUseNode, h)) use->isHeightSet = true; - if (from->transform) { - grad->transform = (Matrix*)calloc(1, sizeof(Matrix)); - if (grad->transform) memcpy(grad->transform, from->transform, sizeof(Matrix)); + return true; + } } - if (grad->type == SvgGradientType::Linear) { - grad->linear = (SvgLinearGradient*)calloc(1, sizeof(SvgLinearGradient)); - if (!grad->linear) goto error_grad_alloc; - memcpy(grad->linear, from->linear, sizeof(SvgLinearGradient)); - } else if (grad->type == SvgGradientType::Radial) { - grad->radial = (SvgRadialGradient*)calloc(1, sizeof(SvgRadialGradient)); - if (!grad->radial) goto error_grad_alloc; - memcpy(grad->radial, from->radial, sizeof(SvgRadialGradient)); + if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) { + id = _idFromHref(value); + defs = _getDefsNode(node); + nodeFrom = _findNodeById(defs, id); + if (nodeFrom) { + _cloneNode(nodeFrom, node, 0); + if (nodeFrom->type == SvgNodeType::Symbol) use->symbol = nodeFrom; + free(id); + } else { + //some svg export software include element at the end of the file + //if so the 'from' element won't be found now and we have to repeat finding + //after the whole file is parsed + _postpone(loader->cloneNodes, node, id); + } + } else { + return _attrParseGNode(data, key, value); } + return true; +} - _cloneGradStops(grad->stops, from->stops); - return grad; +static SvgNode* _createUseNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength, parseAttributes func) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Use); -error_grad_alloc: - if (grad) { - grad->clear(); - free(grad); - } - return nullptr; + if (!loader->svgParse->node) return nullptr; + + loader->svgParse->node->node.use.isWidthSet = false; + loader->svgParse->node->node.use.isHeightSet = false; + + func(buf, bufLength, _attrParseUseNode, loader); + return loader->svgParse->node; } -static void _styleInherit(SvgStyleProperty* child, const SvgStyleProperty* parent) +//TODO: Implement 'text' primitive +static constexpr struct { - if (parent == nullptr) return; - //Inherit the property of parent if not present in child. - if (!child->curColorSet) { - child->color = parent->color; - child->curColorSet = parent->curColorSet; - } - //Fill - if (!((int)child->fill.flags & (int)SvgFillFlags::Paint)) { - child->fill.paint.color = parent->fill.paint.color; - 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); - child->fill.paint.url = _copyId(parent->fill.paint.url); - } - } - if (!((int)child->fill.flags & (int)SvgFillFlags::Opacity)) { - child->fill.opacity = parent->fill.opacity; - } - if (!((int)child->fill.flags & (int)SvgFillFlags::FillRule)) { - child->fill.fillRule = parent->fill.fillRule; - } - //Stroke - if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Paint)) { - child->stroke.paint.color = parent->stroke.paint.color; - 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); - child->stroke.paint.url = _copyId(parent->stroke.paint.url); - } - } - if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Opacity)) { - child->stroke.opacity = parent->stroke.opacity; - } - if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Width)) { - child->stroke.width = parent->stroke.width; - } - if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Dash)) { - if (parent->stroke.dash.array.count > 0) { - child->stroke.dash.array.clear(); - child->stroke.dash.array.reserve(parent->stroke.dash.array.count); - for (uint32_t i = 0; i < parent->stroke.dash.array.count; ++i) { - child->stroke.dash.array.push(parent->stroke.dash.array.data[i]); - } - } - } - if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Cap)) { - child->stroke.cap = parent->stroke.cap; - } - if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Join)) { - child->stroke.join = parent->stroke.join; - } -} + const char* tag; + int sz; + FactoryMethod tagHandler; +} graphicsTags[] = { + {"use", sizeof("use"), _createUseNode}, + {"circle", sizeof("circle"), _createCircleNode}, + {"ellipse", sizeof("ellipse"), _createEllipseNode}, + {"path", sizeof("path"), _createPathNode}, + {"polygon", sizeof("polygon"), _createPolygonNode}, + {"rect", sizeof("rect"), _createRectNode}, + {"polyline", sizeof("polyline"), _createPolylineNode}, + {"line", sizeof("line"), _createLineNode}, + {"image", sizeof("image"), _createImageNode} +}; -static void _styleCopy(SvgStyleProperty* to, const SvgStyleProperty* from) +static constexpr struct { - if (from == nullptr) return; - //Copy the properties of 'from' only if they were explicitly set (not the default ones). - if (from->curColorSet) { - to->color = from->color; - to->curColorSet = true; - } - //Fill - to->fill.flags = (SvgFillFlags)((int)to->fill.flags | (int)from->fill.flags); - if (((int)from->fill.flags & (int)SvgFillFlags::Paint)) { - to->fill.paint.color = from->fill.paint.color; - 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); - to->fill.paint.url = _copyId(from->fill.paint.url); - } - } - if (((int)from->fill.flags & (int)SvgFillFlags::Opacity)) { - to->fill.opacity = from->fill.opacity; - } - if (((int)from->fill.flags & (int)SvgFillFlags::FillRule)) { - to->fill.fillRule = from->fill.fillRule; - } - //Stroke - to->stroke.flags = (SvgStrokeFlags)((int)to->stroke.flags | (int)from->stroke.flags); - if (((int)from->stroke.flags & (int)SvgStrokeFlags::Paint)) { - to->stroke.paint.color = from->stroke.paint.color; - 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); - to->stroke.paint.url = _copyId(from->stroke.paint.url); - } - } - if (((int)from->stroke.flags & (int)SvgStrokeFlags::Opacity)) { - to->stroke.opacity = from->stroke.opacity; - } - if (((int)from->stroke.flags & (int)SvgStrokeFlags::Width)) { - to->stroke.width = from->stroke.width; - } - if (((int)from->stroke.flags & (int)SvgStrokeFlags::Dash)) { - if (from->stroke.dash.array.count > 0) { - to->stroke.dash.array.clear(); - to->stroke.dash.array.reserve(from->stroke.dash.array.count); - for (uint32_t i = 0; i < from->stroke.dash.array.count; ++i) { - to->stroke.dash.array.push(from->stroke.dash.array.data[i]); - } - } - } - if (((int)from->stroke.flags & (int)SvgStrokeFlags::Cap)) { - to->stroke.cap = from->stroke.cap; + const char* tag; + int sz; + FactoryMethod tagHandler; +} groupTags[] = { + {"defs", sizeof("defs"), _createDefsNode}, + {"g", sizeof("g"), _createGNode}, + {"svg", sizeof("svg"), _createSvgNode}, + {"mask", sizeof("mask"), _createMaskNode}, + {"clipPath", sizeof("clipPath"), _createClipPathNode}, + {"style", sizeof("style"), _createCssStyleNode}, + {"symbol", sizeof("symbol"), _createSymbolNode} +}; + + +#define FIND_FACTORY(Short_Name, Tags_Array) \ + static FactoryMethod \ + _find##Short_Name##Factory(const char* name) \ + { \ + unsigned int i; \ + int sz = strlen(name); \ + \ + for (i = 0; i < sizeof(Tags_Array) / sizeof(Tags_Array[0]); i++) { \ + if (Tags_Array[i].sz - 1 == sz && !strncmp(Tags_Array[i].tag, name, sz)) { \ + return Tags_Array[i].tagHandler; \ + } \ + } \ + return nullptr; \ } - if (((int)from->stroke.flags & (int)SvgStrokeFlags::Join)) { - to->stroke.join = from->stroke.join; + +FIND_FACTORY(Group, groupTags) +FIND_FACTORY(Graphics, graphicsTags) + + +FillSpread _parseSpreadValue(const char* value) +{ + auto spread = FillSpread::Pad; + + if (!strcmp(value, "reflect")) { + spread = FillSpread::Reflect; + } else if (!strcmp(value, "repeat")) { + spread = FillSpread::Repeat; } + + return spread; } -static void _copyAttr(SvgNode* to, const SvgNode* from) +static void _handleRadialCxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) { - //Copy matrix attribute - if (from->transform) { - to->transform = (Matrix*)malloc(sizeof(Matrix)); - if (to->transform) *to->transform = *from->transform; - } - //Copy style attribute - _styleCopy(to->style, from->style); - to->style->flags = (SvgStyleFlags)((int)to->style->flags | (int)from->style->flags); - if (from->style->clipPath.url) { - if (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); - to->style->mask.url = strdup(from->style->mask.url); - } - - //Copy node attribute - switch (from->type) { - case SvgNodeType::Circle: { - to->node.circle.cx = from->node.circle.cx; - to->node.circle.cy = from->node.circle.cy; - to->node.circle.r = from->node.circle.r; - break; - } - case SvgNodeType::Ellipse: { - to->node.ellipse.cx = from->node.ellipse.cx; - to->node.ellipse.cy = from->node.ellipse.cy; - to->node.ellipse.rx = from->node.ellipse.rx; - to->node.ellipse.ry = from->node.ellipse.ry; - break; - } - case SvgNodeType::Rect: { - to->node.rect.x = from->node.rect.x; - to->node.rect.y = from->node.rect.y; - to->node.rect.w = from->node.rect.w; - to->node.rect.h = from->node.rect.h; - to->node.rect.rx = from->node.rect.rx; - to->node.rect.ry = from->node.rect.ry; - to->node.rect.hasRx = from->node.rect.hasRx; - to->node.rect.hasRy = from->node.rect.hasRy; - break; - } - case SvgNodeType::Line: { - to->node.line.x1 = from->node.line.x1; - to->node.line.y1 = from->node.line.y1; - to->node.line.x2 = from->node.line.x2; - to->node.line.y2 = from->node.line.y2; - break; - } - case SvgNodeType::Path: { - if (from->node.path.path) { - if (to->node.path.path) free(to->node.path.path); - to->node.path.path = strdup(from->node.path.path); - } - break; - } - case SvgNodeType::Polygon: { - if ((to->node.polygon.pointsCount = from->node.polygon.pointsCount)) { - to->node.polygon.points = (float*)malloc(to->node.polygon.pointsCount * sizeof(float)); - memcpy(to->node.polygon.points, from->node.polygon.points, to->node.polygon.pointsCount * sizeof(float)); - } - break; - } - case SvgNodeType::Polyline: { - if ((to->node.polyline.pointsCount = from->node.polyline.pointsCount)) { - to->node.polyline.points = (float*)malloc(to->node.polyline.pointsCount * sizeof(float)); - memcpy(to->node.polyline.points, from->node.polyline.points, to->node.polyline.pointsCount * sizeof(float)); - } - break; - } - case SvgNodeType::Image: { - to->node.image.x = from->node.image.x; - to->node.image.y = from->node.image.y; - 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); - to->node.image.href = strdup(from->node.image.href); - } - break; - } - default: { - break; - } + radial->cx = _gradientToFloat(loader->svgParse, value, radial->isCxPercentage); + if (!loader->svgParse->gradient.parsedFx) { + radial->fx = radial->cx; + radial->isFxPercentage = radial->isCxPercentage; } } -static void _cloneNode(SvgNode* from, SvgNode* parent, int depth) +static void _handleRadialCyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) { - /* Exception handling: Prevent invalid SVG data input. - The size is the arbitrary value, we need an experimental size. */ - if (depth == 8192) { - TVGERR("SVG", "Infinite recursive call - stopped after %d calls! Svg file may be incorrectly formatted.", depth); - return; + radial->cy = _gradientToFloat(loader->svgParse, value, radial->isCyPercentage); + if (!loader->svgParse->gradient.parsedFy) { + radial->fy = radial->cy; + radial->isFyPercentage = radial->isCyPercentage; } +} - SvgNode* newNode; - if (!from || !parent || from == parent) return; - newNode = _createNode(parent, from->type); - if (!newNode) return; +static void _handleRadialFxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) +{ + radial->fx = _gradientToFloat(loader->svgParse, value, radial->isFxPercentage); + loader->svgParse->gradient.parsedFx = true; +} - _styleInherit(newNode->style, parent->style); - _copyAttr(newNode, from); - auto child = from->child.data; - for (uint32_t i = 0; i < from->child.count; ++i, ++child) { - _cloneNode(*child, newNode, depth + 1); - } +static void _handleRadialFyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) +{ + radial->fy = _gradientToFloat(loader->svgParse, value, radial->isFyPercentage); + loader->svgParse->gradient.parsedFy = true; } -static void _clonePostponedNodes(Array* cloneNodes, SvgNode* doc) +static void _handleRadialRAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) { - for (uint32_t i = 0; i < cloneNodes->count; ++i) { - auto nodeIdPair = cloneNodes->data[i]; - auto defs = _getDefsNode(nodeIdPair.node); - auto nodeFrom = _findNodeById(defs, nodeIdPair.id); - if (!nodeFrom) nodeFrom = _findNodeById(doc, nodeIdPair.id); - _cloneNode(nodeFrom, nodeIdPair.node, 0); - if (nodeFrom && nodeFrom->type == SvgNodeType::Symbol && nodeIdPair.node->type == SvgNodeType::Use) { - nodeIdPair.node->node.use.symbol = nodeFrom; - } - free(nodeIdPair.id); - } + radial->r = _gradientToFloat(loader->svgParse, value, radial->isRPercentage); } -static constexpr struct +static void _recalcRadialCxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) { - const char* tag; - SvgParserLengthType type; - int sz; - size_t offset; -} useTags[] = { - {"x", SvgParserLengthType::Horizontal, sizeof("x"), offsetof(SvgUseNode, x)}, - {"y", SvgParserLengthType::Vertical, sizeof("y"), offsetof(SvgUseNode, y)}, - {"width", SvgParserLengthType::Horizontal, sizeof("width"), offsetof(SvgUseNode, w)}, - {"height", SvgParserLengthType::Vertical, sizeof("height"), offsetof(SvgUseNode, h)} -}; + if (userSpace && !radial->isCxPercentage) radial->cx = radial->cx / loader->svgParse->global.w; +} -static bool _attrParseUseNode(void* data, const char* key, const char* value) +static void _recalcRadialCyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) { - SvgLoaderData* loader = (SvgLoaderData*)data; - SvgNode *defs, *nodeFrom, *node = loader->svgParse->node; - char* id; + if (userSpace && !radial->isCyPercentage) radial->cy = radial->cy / loader->svgParse->global.h; +} - SvgUseNode* use = &(node->node.use); - int sz = strlen(key); - unsigned char* array = (unsigned char*)use; - for (unsigned int i = 0; i < sizeof(useTags) / sizeof(useTags[0]); i++) { - if (useTags[i].sz - 1 == sz && !strncmp(useTags[i].tag, key, sz)) { - *((float*)(array + useTags[i].offset)) = _toFloat(loader->svgParse, value, useTags[i].type); - if (useTags[i].offset == offsetof(SvgUseNode, w)) use->isWidthSet = true; - else if (useTags[i].offset == offsetof(SvgUseNode, h)) use->isHeightSet = true; +static void _recalcRadialFxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) +{ + if (userSpace && !radial->isFxPercentage) radial->fx = radial->fx / loader->svgParse->global.w; +} - return true; - } - } - if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) { - id = _idFromHref(value); - defs = _getDefsNode(node); - nodeFrom = _findNodeById(defs, id); - if (nodeFrom) { - _cloneNode(nodeFrom, node, 0); - if (nodeFrom->type == SvgNodeType::Symbol) use->symbol = nodeFrom; - free(id); - } else { - //some svg export software include element at the end of the file - //if so the 'from' element won't be found now and we have to repeat finding - //after the whole file is parsed - _postpone(loader->cloneNodes, node, id); - } - } else { - return _attrParseGNode(data, key, value); - } - return true; +static void _recalcRadialFyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) +{ + if (userSpace && !radial->isFyPercentage) radial->fy = radial->fy / loader->svgParse->global.h; } -static SvgNode* _createUseNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength, parseAttributes func) +static void _recalcRadialRAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) { - loader->svgParse->node = _createNode(parent, SvgNodeType::Use); - - if (!loader->svgParse->node) return nullptr; + // scaling factor based on the Units paragraph from : https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html + if (userSpace && !radial->isRPercentage) radial->r = radial->r / (sqrtf(powf(loader->svgParse->global.h, 2) + powf(loader->svgParse->global.w, 2)) / sqrtf(2.0)); +} - loader->svgParse->node->node.use.isWidthSet = false; - loader->svgParse->node->node.use.isHeightSet = false; - func(buf, bufLength, _attrParseUseNode, loader); - return loader->svgParse->node; +static void _recalcInheritedRadialCxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) +{ + if (!radial->isCxPercentage) { + if (userSpace) radial->cx /= loader->svgParse->global.w; + else radial->cx *= loader->svgParse->global.w; + } } -//TODO: Implement 'text' primitive -static constexpr struct +static void _recalcInheritedRadialCyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) { - const char* tag; - int sz; - FactoryMethod tagHandler; -} graphicsTags[] = { - {"use", sizeof("use"), _createUseNode}, - {"circle", sizeof("circle"), _createCircleNode}, - {"ellipse", sizeof("ellipse"), _createEllipseNode}, - {"path", sizeof("path"), _createPathNode}, - {"polygon", sizeof("polygon"), _createPolygonNode}, - {"rect", sizeof("rect"), _createRectNode}, - {"polyline", sizeof("polyline"), _createPolylineNode}, - {"line", sizeof("line"), _createLineNode}, - {"image", sizeof("image"), _createImageNode} -}; + if (!radial->isCyPercentage) { + if (userSpace) radial->cy /= loader->svgParse->global.h; + else radial->cy *= loader->svgParse->global.h; + } +} -static constexpr struct +static void _recalcInheritedRadialFxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) { - const char* tag; - int sz; - FactoryMethod tagHandler; -} groupTags[] = { - {"defs", sizeof("defs"), _createDefsNode}, - {"g", sizeof("g"), _createGNode}, - {"svg", sizeof("svg"), _createSvgNode}, - {"mask", sizeof("mask"), _createMaskNode}, - {"clipPath", sizeof("clipPath"), _createClipPathNode}, - {"style", sizeof("style"), _createCssStyleNode}, - {"symbol", sizeof("symbol"), _createSymbolNode} -}; + if (!radial->isFxPercentage) { + if (userSpace) radial->fx /= loader->svgParse->global.w; + else radial->fx *= loader->svgParse->global.w; + } +} -#define FIND_FACTORY(Short_Name, Tags_Array) \ - static FactoryMethod \ - _find##Short_Name##Factory(const char* name) \ - { \ - unsigned int i; \ - int sz = strlen(name); \ - \ - for (i = 0; i < sizeof(Tags_Array) / sizeof(Tags_Array[0]); i++) { \ - if (Tags_Array[i].sz - 1 == sz && !strncmp(Tags_Array[i].tag, name, sz)) { \ - return Tags_Array[i].tagHandler; \ - } \ - } \ - return nullptr; \ +static void _recalcInheritedRadialFyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) +{ + if (!radial->isFyPercentage) { + if (userSpace) radial->fy /= loader->svgParse->global.h; + else radial->fy *= loader->svgParse->global.h; } - -FIND_FACTORY(Group, groupTags) -FIND_FACTORY(Graphics, graphicsTags) +} -FillSpread _parseSpreadValue(const char* value) +static void _recalcInheritedRadialRAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) { - auto spread = FillSpread::Pad; - - if (!strcmp(value, "reflect")) { - spread = FillSpread::Reflect; - } else if (!strcmp(value, "repeat")) { - spread = FillSpread::Repeat; - } - - return spread; -} - - -static void _handleRadialCxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) -{ - radial->cx = _gradientToFloat(loader->svgParse, value, radial->isCxPercentage); - if (!loader->svgParse->gradient.parsedFx) { - radial->fx = radial->cx; - radial->isFxPercentage = radial->isCxPercentage; - } -} - - -static void _handleRadialCyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) -{ - radial->cy = _gradientToFloat(loader->svgParse, value, radial->isCyPercentage); - if (!loader->svgParse->gradient.parsedFy) { - radial->fy = radial->cy; - radial->isFyPercentage = radial->isCyPercentage; + if (!radial->isRPercentage) { + if (userSpace) radial->r /= sqrtf(powf(loader->svgParse->global.h, 2) + powf(loader->svgParse->global.w, 2)) / sqrtf(2.0); + else radial->r *= sqrtf(powf(loader->svgParse->global.h, 2) + powf(loader->svgParse->global.w, 2)) / sqrtf(2.0); } } -static void _handleRadialFxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) -{ - radial->fx = _gradientToFloat(loader->svgParse, value, radial->isFxPercentage); - loader->svgParse->gradient.parsedFx = true; -} - - -static void _handleRadialFyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) -{ - radial->fy = _gradientToFloat(loader->svgParse, value, radial->isFyPercentage); - loader->svgParse->gradient.parsedFy = true; -} - - -static void _handleRadialRAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) +static void _inheritRadialCxAttr(SvgStyleGradient* to, SvgStyleGradient* from) { - radial->r = _gradientToFloat(loader->svgParse, value, radial->isRPercentage); + to->radial->cx = from->radial->cx; + to->radial->isCxPercentage = from->radial->isCxPercentage; + to->flags = (SvgGradientFlags)((int)to->flags | (int)SvgGradientFlags::Cx); } -static void _recalcRadialCxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) +static void _inheritRadialCyAttr(SvgStyleGradient* to, SvgStyleGradient* from) { - if (userSpace && !radial->isCxPercentage) radial->cx = radial->cx / loader->svgParse->global.w; -} - - -static void _recalcRadialCyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) -{ - if (userSpace && !radial->isCyPercentage) radial->cy = radial->cy / loader->svgParse->global.h; + to->radial->cy = from->radial->cy; + to->radial->isCyPercentage = from->radial->isCyPercentage; + to->flags = (SvgGradientFlags)((int)to->flags | (int)SvgGradientFlags::Cy); } -static void _recalcRadialFxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) +static void _inheritRadialFxAttr(SvgStyleGradient* to, SvgStyleGradient* from) { - if (userSpace && !radial->isFxPercentage) radial->fx = radial->fx / loader->svgParse->global.w; + to->radial->fx = from->radial->fx; + to->radial->isFxPercentage = from->radial->isFxPercentage; + to->flags = (SvgGradientFlags)((int)to->flags | (int)SvgGradientFlags::Fx); } -static void _recalcRadialFyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) +static void _inheritRadialFyAttr(SvgStyleGradient* to, SvgStyleGradient* from) { - if (userSpace && !radial->isFyPercentage) radial->fy = radial->fy / loader->svgParse->global.h; + to->radial->fy = from->radial->fy; + to->radial->isFyPercentage = from->radial->isFyPercentage; + to->flags = (SvgGradientFlags)((int)to->flags | (int)SvgGradientFlags::Fy); } -static void _recalcRadialRAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) +static void _inheritRadialRAttr(SvgStyleGradient* to, SvgStyleGradient* from) { - // scaling factor based on the Units paragraph from : https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html - if (userSpace && !radial->isRPercentage) radial->r = radial->r / (sqrtf(powf(loader->svgParse->global.h, 2) + powf(loader->svgParse->global.w, 2)) / sqrtf(2.0)); + to->radial->r = from->radial->r; + to->radial->isRPercentage = from->radial->isRPercentage; + to->flags = (SvgGradientFlags)((int)to->flags | (int)SvgGradientFlags::R); } typedef void (*radialMethod)(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value); +typedef void (*radialInheritMethod)(SvgStyleGradient* to, SvgStyleGradient* from); typedef void (*radialMethodRecalc)(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace); -#define RADIAL_DEF(Name, Name1) \ +#define RADIAL_DEF(Name, Name1, Flag) \ { \ -#Name, sizeof(#Name), _handleRadial##Name1##Attr, _recalcRadial##Name1##Attr \ +#Name, sizeof(#Name), _handleRadial##Name1##Attr, _inheritRadial##Name1##Attr, _recalcRadial##Name1##Attr, _recalcInheritedRadial##Name1##Attr, Flag \ } @@ -2403,13 +2191,16 @@ static constexpr struct const char* tag; int sz; radialMethod tagHandler; + radialInheritMethod tagInheritHandler; radialMethodRecalc tagRecalc; + radialMethodRecalc tagInheritedRecalc; + SvgGradientFlags flag; } radialTags[] = { - RADIAL_DEF(cx, Cx), - RADIAL_DEF(cy, Cy), - RADIAL_DEF(fx, Fx), - RADIAL_DEF(fy, Fy), - RADIAL_DEF(r, R) + RADIAL_DEF(cx, Cx, SvgGradientFlags::Cx), + RADIAL_DEF(cy, Cy, SvgGradientFlags::Cy), + RADIAL_DEF(fx, Fx, SvgGradientFlags::Fx), + RADIAL_DEF(fy, Fy, SvgGradientFlags::Fy), + RADIAL_DEF(r, R, SvgGradientFlags::R) }; @@ -2423,6 +2214,7 @@ static bool _attrParseRadialGradientNode(void* data, const char* key, const char for (unsigned int i = 0; i < sizeof(radialTags) / sizeof(radialTags[0]); i++) { if (radialTags[i].sz - 1 == sz && !strncmp(radialTags[i].tag, key, sz)) { radialTags[i].tagHandler(loader, radial, value); + grad->flags = (SvgGradientFlags)((int)grad->flags | (int)radialTags[i].flag); return true; } } @@ -2432,11 +2224,13 @@ static bool _attrParseRadialGradientNode(void* data, const char* key, const char grad->id = _copyId(value); } else if (!strcmp(key, "spreadMethod")) { grad->spread = _parseSpreadValue(value); + grad->flags = (SvgGradientFlags)((int)grad->flags | (int)SvgGradientFlags::SpreadMethod); } else if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) { if (grad->ref && value) free(grad->ref); grad->ref = _idFromHref(value); } else if (!strcmp(key, "gradientUnits")) { if (!strcmp(value, "userSpaceOnUse")) grad->userSpace = true; + grad->flags = (SvgGradientFlags)((int)grad->flags | (int)SvgGradientFlags::GradientUnits); } else if (!strcmp(key, "gradientTransform")) { grad->transform = _parseTransformationMatrix(value); } else { @@ -2452,6 +2246,7 @@ static SvgStyleGradient* _createRadialGradient(SvgLoaderData* loader, const char auto grad = (SvgStyleGradient*)(calloc(1, sizeof(SvgStyleGradient))); loader->svgParse->styleGrad = grad; + grad->flags = SvgGradientFlags::None; grad->type = SvgGradientType::Radial; grad->userSpace = false; grad->radial = (SvgRadialGradient*)calloc(1, sizeof(SvgRadialGradient)); @@ -2579,13 +2374,82 @@ static void _recalcLinearY2Attr(SvgLoaderData* loader, SvgLinearGradient* linear } +static void _recalcInheritedLinearX1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace) +{ + if (!linear->isX1Percentage) { + if (userSpace) linear->x1 /= loader->svgParse->global.w; + else linear->x1 *= loader->svgParse->global.w; + } +} + + +static void _recalcInheritedLinearX2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace) +{ + if (!linear->isX2Percentage) { + if (userSpace) linear->x2 /= loader->svgParse->global.w; + else linear->x2 *= loader->svgParse->global.w; + } +} + + +static void _recalcInheritedLinearY1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace) +{ + if (!linear->isY1Percentage) { + if (userSpace) linear->y1 /= loader->svgParse->global.h; + else linear->y1 *= loader->svgParse->global.h; + } +} + + +static void _recalcInheritedLinearY2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace) +{ + if (!linear->isY2Percentage) { + if (userSpace) linear->y2 /= loader->svgParse->global.h; + else linear->y2 *= loader->svgParse->global.h; + } +} + + +static void _inheritLinearX1Attr(SvgStyleGradient* to, SvgStyleGradient* from) +{ + to->linear->x1 = from->linear->x1; + to->linear->isX1Percentage = from->linear->isX1Percentage; + to->flags = (SvgGradientFlags)((int)to->flags | (int)SvgGradientFlags::X1); +} + + +static void _inheritLinearX2Attr(SvgStyleGradient* to, SvgStyleGradient* from) +{ + to->linear->x2 = from->linear->x2; + to->linear->isX2Percentage = from->linear->isX2Percentage; + to->flags = (SvgGradientFlags)((int)to->flags | (int)SvgGradientFlags::X2); +} + + +static void _inheritLinearY1Attr(SvgStyleGradient* to, SvgStyleGradient* from) +{ + to->linear->y1 = from->linear->y1; + to->linear->isY1Percentage = from->linear->isY1Percentage; + to->flags = (SvgGradientFlags)((int)to->flags | (int)SvgGradientFlags::Y1); +} + + +static void _inheritLinearY2Attr(SvgStyleGradient* to, SvgStyleGradient* from) +{ + to->linear->y2 = from->linear->y2; + to->linear->isY2Percentage = from->linear->isY2Percentage; + to->flags = (SvgGradientFlags)((int)to->flags | (int)SvgGradientFlags::Y2); +} + + typedef void (*Linear_Method)(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value); +typedef void (*Linear_Inherit_Method)(SvgStyleGradient* to, SvgStyleGradient* from); typedef void (*Linear_Method_Recalc)(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace); -#define LINEAR_DEF(Name, Name1) \ +#define LINEAR_DEF(Name, Name1, Flag) \ { \ -#Name, sizeof(#Name), _handleLinear##Name1##Attr, _recalcLinear##Name1##Attr \ +#Name, sizeof(#Name), _handleLinear##Name1##Attr, _inheritLinear##Name1##Attr, _recalcLinear##Name1##Attr, _recalcInheritedLinear##Name1##Attr, Flag \ } @@ -2594,12 +2458,15 @@ static constexpr struct const char* tag; int sz; Linear_Method tagHandler; + Linear_Inherit_Method tagInheritHandler; Linear_Method_Recalc tagRecalc; + Linear_Method_Recalc tagInheritedRecalc; + SvgGradientFlags flag; } linear_tags[] = { - LINEAR_DEF(x1, X1), - LINEAR_DEF(y1, Y1), - LINEAR_DEF(x2, X2), - LINEAR_DEF(y2, Y2) + LINEAR_DEF(x1, X1, SvgGradientFlags::X1), + LINEAR_DEF(y1, Y1, SvgGradientFlags::Y1), + LINEAR_DEF(x2, X2, SvgGradientFlags::X2), + LINEAR_DEF(y2, Y2, SvgGradientFlags::Y2) }; @@ -2613,6 +2480,7 @@ static bool _attrParseLinearGradientNode(void* data, const char* key, const char for (unsigned int i = 0; i < sizeof(linear_tags) / sizeof(linear_tags[0]); i++) { if (linear_tags[i].sz - 1 == sz && !strncmp(linear_tags[i].tag, key, sz)) { linear_tags[i].tagHandler(loader, linear, value); + grad->flags = (SvgGradientFlags)((int)grad->flags | (int)linear_tags[i].flag); return true; } } @@ -2622,11 +2490,13 @@ static bool _attrParseLinearGradientNode(void* data, const char* key, const char grad->id = _copyId(value); } else if (!strcmp(key, "spreadMethod")) { grad->spread = _parseSpreadValue(value); + grad->flags = (SvgGradientFlags)((int)grad->flags | (int)SvgGradientFlags::SpreadMethod); } else if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) { if (grad->ref && value) free(grad->ref); grad->ref = _idFromHref(value); } else if (!strcmp(key, "gradientUnits")) { if (!strcmp(value, "userSpaceOnUse")) grad->userSpace = true; + grad->flags = (SvgGradientFlags)((int)grad->flags | (int)SvgGradientFlags::GradientUnits); } else if (!strcmp(key, "gradientTransform")) { grad->transform = _parseTransformationMatrix(value); } else { @@ -2642,6 +2512,7 @@ static SvgStyleGradient* _createLinearGradient(SvgLoaderData* loader, const char auto grad = (SvgStyleGradient*)(calloc(1, sizeof(SvgStyleGradient))); loader->svgParse->styleGrad = grad; + grad->flags = SvgGradientFlags::None; grad->type = SvgGradientType::Linear; grad->userSpace = false; grad->linear = (SvgLinearGradient*)calloc(1, sizeof(SvgLinearGradient)); @@ -2701,6 +2572,367 @@ static GradientFactoryMethod _findGradientFactory(const char* name) } +static void _cloneGradStops(Array& dst, const Array& src) +{ + for (uint32_t i = 0; i < src.count; ++i) { + dst.push(src.data[i]); + } +} + + +static void _inheritGradient(SvgLoaderData* loader, SvgStyleGradient* to, SvgStyleGradient* from) +{ + if (!to || !from) return; + + if (!((int)to->flags & (int)SvgGradientFlags::SpreadMethod) && + ((int)from->flags & (int)SvgGradientFlags::SpreadMethod)) { + to->spread = from->spread; + to->flags = (SvgGradientFlags)((int)to->flags | (int)SvgGradientFlags::SpreadMethod); + } + bool gradUnitSet = (int)to->flags & (int)SvgGradientFlags::GradientUnits; + if (!((int)to->flags & (int)SvgGradientFlags::GradientUnits) && + ((int)from->flags & (int)SvgGradientFlags::GradientUnits)) { + to->userSpace = from->userSpace; + to->flags = (SvgGradientFlags)((int)to->flags | (int)SvgGradientFlags::GradientUnits); + } + + if (!to->transform && from->transform) { + to->transform = (Matrix*)malloc(sizeof(Matrix)); + if (to->transform) memcpy(to->transform, from->transform, sizeof(Matrix)); + } + + if (to->type == SvgGradientType::Linear && from->type == SvgGradientType::Linear) { + for (unsigned int i = 0; i < sizeof(linear_tags) / sizeof(linear_tags[0]); i++) { + bool coordSet = (int)to->flags & (int)linear_tags[i].flag; + if (!((int)to->flags & (int)linear_tags[i].flag) && ((int)from->flags & (int)linear_tags[i].flag)) { + linear_tags[i].tagInheritHandler(to, from); + } + + //GradUnits not set directly, coord set + if (!gradUnitSet && coordSet) { + linear_tags[i].tagRecalc(loader, to->linear, to->userSpace); + } + //GradUnits set, coord not set directly + if (to->userSpace == from->userSpace) continue; + if (gradUnitSet && !coordSet) { + linear_tags[i].tagInheritedRecalc(loader, to->linear, to->userSpace); + } + } + } else if (to->type == SvgGradientType::Radial && from->type == SvgGradientType::Radial) { + for (unsigned int i = 0; i < sizeof(radialTags) / sizeof(radialTags[0]); i++) { + bool coordSet = (int)to->flags & (int)radialTags[i].flag; + if (!((int)to->flags & (int)radialTags[i].flag) && ((int)from->flags & (int)radialTags[i].flag)) { + radialTags[i].tagInheritHandler(to, from); + } + + //GradUnits not set directly, coord set + if (!gradUnitSet && coordSet) { + radialTags[i].tagRecalc(loader, to->radial, to->userSpace); + } + //GradUnits set, coord not set directly + if (to->userSpace == from->userSpace) continue; + if (gradUnitSet && !coordSet) { + radialTags[i].tagInheritedRecalc(loader, to->radial, to->userSpace); + } + } + } + + if (to->stops.count == 0) _cloneGradStops(to->stops, from->stops); +} + + +static SvgStyleGradient* _cloneGradient(SvgStyleGradient* from) +{ + if (!from) return nullptr; + + auto grad = (SvgStyleGradient*)(calloc(1, sizeof(SvgStyleGradient))); + if (!grad) return nullptr; + + grad->type = from->type; + grad->id = from->id ? _copyId(from->id) : nullptr; + grad->ref = from->ref ? _copyId(from->ref) : nullptr; + grad->spread = from->spread; + grad->userSpace = from->userSpace; + grad->flags = from->flags; + + if (from->transform) { + grad->transform = (Matrix*)calloc(1, sizeof(Matrix)); + if (grad->transform) memcpy(grad->transform, from->transform, sizeof(Matrix)); + } + + if (grad->type == SvgGradientType::Linear) { + grad->linear = (SvgLinearGradient*)calloc(1, sizeof(SvgLinearGradient)); + if (!grad->linear) goto error_grad_alloc; + memcpy(grad->linear, from->linear, sizeof(SvgLinearGradient)); + } else if (grad->type == SvgGradientType::Radial) { + grad->radial = (SvgRadialGradient*)calloc(1, sizeof(SvgRadialGradient)); + if (!grad->radial) goto error_grad_alloc; + memcpy(grad->radial, from->radial, sizeof(SvgRadialGradient)); + } + + _cloneGradStops(grad->stops, from->stops); + + return grad; + + error_grad_alloc: + if (grad) { + grad->clear(); + free(grad); + } + return nullptr; +} + + +static void _styleInherit(SvgStyleProperty* child, const SvgStyleProperty* parent) +{ + if (parent == nullptr) return; + //Inherit the property of parent if not present in child. + if (!child->curColorSet) { + child->color = parent->color; + child->curColorSet = parent->curColorSet; + } + //Fill + if (!((int)child->fill.flags & (int)SvgFillFlags::Paint)) { + child->fill.paint.color = parent->fill.paint.color; + 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); + child->fill.paint.url = _copyId(parent->fill.paint.url); + } + } + if (!((int)child->fill.flags & (int)SvgFillFlags::Opacity)) { + child->fill.opacity = parent->fill.opacity; + } + if (!((int)child->fill.flags & (int)SvgFillFlags::FillRule)) { + child->fill.fillRule = parent->fill.fillRule; + } + //Stroke + if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Paint)) { + child->stroke.paint.color = parent->stroke.paint.color; + 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); + child->stroke.paint.url = _copyId(parent->stroke.paint.url); + } + } + if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Opacity)) { + child->stroke.opacity = parent->stroke.opacity; + } + if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Width)) { + child->stroke.width = parent->stroke.width; + } + if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Dash)) { + if (parent->stroke.dash.array.count > 0) { + child->stroke.dash.array.clear(); + child->stroke.dash.array.reserve(parent->stroke.dash.array.count); + for (uint32_t i = 0; i < parent->stroke.dash.array.count; ++i) { + child->stroke.dash.array.push(parent->stroke.dash.array.data[i]); + } + } + } + if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Cap)) { + child->stroke.cap = parent->stroke.cap; + } + if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Join)) { + child->stroke.join = parent->stroke.join; + } +} + + +static void _styleCopy(SvgStyleProperty* to, const SvgStyleProperty* from) +{ + if (from == nullptr) return; + //Copy the properties of 'from' only if they were explicitly set (not the default ones). + if (from->curColorSet) { + to->color = from->color; + to->curColorSet = true; + } + //Fill + to->fill.flags = (SvgFillFlags)((int)to->fill.flags | (int)from->fill.flags); + if (((int)from->fill.flags & (int)SvgFillFlags::Paint)) { + to->fill.paint.color = from->fill.paint.color; + 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); + to->fill.paint.url = _copyId(from->fill.paint.url); + } + } + if (((int)from->fill.flags & (int)SvgFillFlags::Opacity)) { + to->fill.opacity = from->fill.opacity; + } + if (((int)from->fill.flags & (int)SvgFillFlags::FillRule)) { + to->fill.fillRule = from->fill.fillRule; + } + //Stroke + to->stroke.flags = (SvgStrokeFlags)((int)to->stroke.flags | (int)from->stroke.flags); + if (((int)from->stroke.flags & (int)SvgStrokeFlags::Paint)) { + to->stroke.paint.color = from->stroke.paint.color; + 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); + to->stroke.paint.url = _copyId(from->stroke.paint.url); + } + } + if (((int)from->stroke.flags & (int)SvgStrokeFlags::Opacity)) { + to->stroke.opacity = from->stroke.opacity; + } + if (((int)from->stroke.flags & (int)SvgStrokeFlags::Width)) { + to->stroke.width = from->stroke.width; + } + if (((int)from->stroke.flags & (int)SvgStrokeFlags::Dash)) { + if (from->stroke.dash.array.count > 0) { + to->stroke.dash.array.clear(); + to->stroke.dash.array.reserve(from->stroke.dash.array.count); + for (uint32_t i = 0; i < from->stroke.dash.array.count; ++i) { + to->stroke.dash.array.push(from->stroke.dash.array.data[i]); + } + } + } + if (((int)from->stroke.flags & (int)SvgStrokeFlags::Cap)) { + to->stroke.cap = from->stroke.cap; + } + if (((int)from->stroke.flags & (int)SvgStrokeFlags::Join)) { + to->stroke.join = from->stroke.join; + } +} + + +static void _copyAttr(SvgNode* to, const SvgNode* from) +{ + //Copy matrix attribute + if (from->transform) { + to->transform = (Matrix*)malloc(sizeof(Matrix)); + if (to->transform) *to->transform = *from->transform; + } + //Copy style attribute + _styleCopy(to->style, from->style); + to->style->flags = (SvgStyleFlags)((int)to->style->flags | (int)from->style->flags); + if (from->style->clipPath.url) { + if (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); + to->style->mask.url = strdup(from->style->mask.url); + } + + //Copy node attribute + switch (from->type) { + case SvgNodeType::Circle: { + to->node.circle.cx = from->node.circle.cx; + to->node.circle.cy = from->node.circle.cy; + to->node.circle.r = from->node.circle.r; + break; + } + case SvgNodeType::Ellipse: { + to->node.ellipse.cx = from->node.ellipse.cx; + to->node.ellipse.cy = from->node.ellipse.cy; + to->node.ellipse.rx = from->node.ellipse.rx; + to->node.ellipse.ry = from->node.ellipse.ry; + break; + } + case SvgNodeType::Rect: { + to->node.rect.x = from->node.rect.x; + to->node.rect.y = from->node.rect.y; + to->node.rect.w = from->node.rect.w; + to->node.rect.h = from->node.rect.h; + to->node.rect.rx = from->node.rect.rx; + to->node.rect.ry = from->node.rect.ry; + to->node.rect.hasRx = from->node.rect.hasRx; + to->node.rect.hasRy = from->node.rect.hasRy; + break; + } + case SvgNodeType::Line: { + to->node.line.x1 = from->node.line.x1; + to->node.line.y1 = from->node.line.y1; + to->node.line.x2 = from->node.line.x2; + to->node.line.y2 = from->node.line.y2; + break; + } + case SvgNodeType::Path: { + if (from->node.path.path) { + if (to->node.path.path) free(to->node.path.path); + to->node.path.path = strdup(from->node.path.path); + } + break; + } + case SvgNodeType::Polygon: { + if ((to->node.polygon.pointsCount = from->node.polygon.pointsCount)) { + to->node.polygon.points = (float*)malloc(to->node.polygon.pointsCount * sizeof(float)); + memcpy(to->node.polygon.points, from->node.polygon.points, to->node.polygon.pointsCount * sizeof(float)); + } + break; + } + case SvgNodeType::Polyline: { + if ((to->node.polyline.pointsCount = from->node.polyline.pointsCount)) { + to->node.polyline.points = (float*)malloc(to->node.polyline.pointsCount * sizeof(float)); + memcpy(to->node.polyline.points, from->node.polyline.points, to->node.polyline.pointsCount * sizeof(float)); + } + break; + } + case SvgNodeType::Image: { + to->node.image.x = from->node.image.x; + to->node.image.y = from->node.image.y; + 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); + to->node.image.href = strdup(from->node.image.href); + } + break; + } + default: { + break; + } + } +} + + +static void _cloneNode(SvgNode* from, SvgNode* parent, int depth) +{ + /* Exception handling: Prevent invalid SVG data input. + The size is the arbitrary value, we need an experimental size. */ + if (depth == 8192) { + TVGERR("SVG", "Infinite recursive call - stopped after %d calls! Svg file may be incorrectly formatted.", depth); + return; + } + + SvgNode* newNode; + if (!from || !parent || from == parent) return; + + newNode = _createNode(parent, from->type); + if (!newNode) return; + + _styleInherit(newNode->style, parent->style); + _copyAttr(newNode, from); + + auto child = from->child.data; + for (uint32_t i = 0; i < from->child.count; ++i, ++child) { + _cloneNode(*child, newNode, depth + 1); + } +} + + +static void _clonePostponedNodes(Array* cloneNodes, SvgNode* doc) +{ + for (uint32_t i = 0; i < cloneNodes->count; ++i) { + auto nodeIdPair = cloneNodes->data[i]; + auto defs = _getDefsNode(nodeIdPair.node); + auto nodeFrom = _findNodeById(defs, nodeIdPair.id); + if (!nodeFrom) nodeFrom = _findNodeById(doc, nodeIdPair.id); + _cloneNode(nodeFrom, nodeIdPair.node, 0); + if (nodeFrom && nodeFrom->type == SvgNodeType::Symbol && nodeIdPair.node->type == SvgNodeType::Use) { + nodeIdPair.node->node.use.symbol = nodeFrom; + } + free(nodeIdPair.id); + } +} + + static constexpr struct { const char* tag; @@ -2951,7 +3183,7 @@ static void _updateStyle(SvgNode* node, SvgStyleProperty* parentStyle) } -static SvgStyleGradient* _gradientDup(Array* gradients, const char* id) +static SvgStyleGradient* _gradientDup(SvgLoaderData* loader, Array* gradients, const char* id) { SvgStyleGradient* result = nullptr; @@ -2969,8 +3201,7 @@ static SvgStyleGradient* _gradientDup(Array* gradients, const gradList = gradients->data; for (uint32_t i = 0; i < gradients->count; ++i) { if ((*gradList)->id && !strcmp((*gradList)->id, result->ref)) { - if (result->stops.count == 0) _cloneGradStops(result->stops, (*gradList)->stops); - //TODO: Properly inherit other property + _inheritGradient(loader, result, *gradList); break; } ++gradList; @@ -2981,12 +3212,12 @@ static SvgStyleGradient* _gradientDup(Array* gradients, const } -static void _updateGradient(SvgNode* node, Array* gradients) +static void _updateGradient(SvgLoaderData* loader, SvgNode* node, Array* gradients) { if (node->child.count > 0) { auto child = node->child.data; for (uint32_t i = 0; i < node->child.count; ++i, ++child) { - _updateGradient(*child, gradients); + _updateGradient(loader, *child, gradients); } } else { if (node->style->fill.paint.url) { @@ -2994,14 +3225,14 @@ static void _updateGradient(SvgNode* node, Array* gradients) node->style->fill.paint.gradient->clear(); free(node->style->fill.paint.gradient); } - node->style->fill.paint.gradient = _gradientDup(gradients, node->style->fill.paint.url); + node->style->fill.paint.gradient = _gradientDup(loader, gradients, node->style->fill.paint.url); } if (node->style->stroke.paint.url) { if (node->style->stroke.paint.gradient) { node->style->stroke.paint.gradient->clear(); free(node->style->stroke.paint.gradient); } - node->style->stroke.paint.gradient = _gradientDup(gradients, node->style->stroke.paint.url); + node->style->stroke.paint.gradient = _gradientDup(loader, gradients, node->style->stroke.paint.url); } } } @@ -3208,8 +3439,8 @@ void SvgLoader::run(unsigned tid) _updateComposite(loaderData.doc, loaderData.doc); if (defs) _updateComposite(loaderData.doc, defs); - if (loaderData.gradients.count > 0) _updateGradient(loaderData.doc, &loaderData.gradients); - if (defs) _updateGradient(loaderData.doc, &defs->node.defs.gradients); + if (loaderData.gradients.count > 0) _updateGradient(&loaderData, loaderData.doc, &loaderData.gradients); + if (defs) _updateGradient(&loaderData, loaderData.doc, &defs->node.defs.gradients); _updateStyle(loaderData.doc, nullptr); } diff --git a/libfenrir/src/main/jni/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h b/libfenrir/src/main/jni/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h index ebcbf018e..3bcc031b0 100644 --- a/libfenrir/src/main/jni/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h +++ b/libfenrir/src/main/jni/thorvg/src/loaders/svg/tvgSvgLoaderCommon.h @@ -125,6 +125,22 @@ enum class SvgStopStyleFlags StopColor = 0x02 }; +enum class SvgGradientFlags +{ + None = 0x0, + GradientUnits = 0x1, + SpreadMethod = 0x2, + X1 = 0x4, + X2 = 0x8, + Y1 = 0x10, + Y2 = 0x20, + Cx = 0x40, + Cy = 0x80, + R = 0x100, + Fx = 0x200, + Fy = 0x400 +}; + enum class SvgFillRule { Winding = 0, @@ -349,6 +365,7 @@ struct SvgStyleGradient SvgLinearGradient* linear; Matrix* transform; Array stops; + SvgGradientFlags flags; bool userSpace; void clear() diff --git a/material/build.gradle b/material/build.gradle index c396bf607..5711913cd 100644 --- a/material/build.gradle +++ b/material/build.gradle @@ -3,7 +3,7 @@ plugins { id("kotlin-android") } -//1.9.0-beta01 +//1.9.0-rc01 def srcDirs = [ 'com/google/android/material/animation', diff --git a/material/java/com/google/android/material/appbar/AppBarLayout.java b/material/java/com/google/android/material/appbar/AppBarLayout.java index bfe9c41fc..b53e4fdf6 100644 --- a/material/java/com/google/android/material/appbar/AppBarLayout.java +++ b/material/java/com/google/android/material/appbar/AppBarLayout.java @@ -280,8 +280,8 @@ public AppBarLayout(@NonNull Context context, @Nullable AttributeSet attrs, int if (a.hasValue(R.styleable.AppBarLayout_expanded)) { setExpanded( a.getBoolean(R.styleable.AppBarLayout_expanded, false), - false, /* animate */ - false /* force */); + /* animate */ false, + /* force */ false); } if (Build.VERSION.SDK_INT >= 21 && a.hasValue(R.styleable.AppBarLayout_elevation)) { @@ -2043,7 +2043,7 @@ void onFlingFinished(@NonNull CoordinatorLayout parent, @NonNull T layout) { @Override int getMaxDragOffset(@NonNull T view) { - return -view.getDownNestedScrollRange(); + return -view.getDownNestedScrollRange() + view.getTopInset(); } @Override @@ -2214,7 +2214,15 @@ private void updateAppBarLayoutDrawableState( if (forceJump || (changed && shouldJumpElevationState(parent, layout))) { // If the collapsed state changed, we may need to // jump to the current state if we have an overlapping view - layout.jumpDrawablesToCurrentState(); + if (layout.getBackground() != null) { + layout.getBackground().jumpToCurrentState(); + } + if (VERSION.SDK_INT >= VERSION_CODES.M && layout.getForeground() != null) { + layout.getForeground().jumpToCurrentState(); + } + if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && layout.getStateListAnimator() != null) { + layout.getStateListAnimator().jumpToCurrentState(); + } } } diff --git a/material/java/com/google/android/material/bottomsheet/BottomSheetBehavior.java b/material/java/com/google/android/material/bottomsheet/BottomSheetBehavior.java index 236012d93..9c185cd19 100644 --- a/material/java/com/google/android/material/bottomsheet/BottomSheetBehavior.java +++ b/material/java/com/google/android/material/bottomsheet/BottomSheetBehavior.java @@ -37,6 +37,7 @@ import android.util.SparseIntArray; import android.util.TypedValue; import android.view.MotionEvent; +import android.view.RoundedCorner; import android.view.VelocityTracker; import android.view.View; import android.view.View.MeasureSpec; @@ -44,12 +45,14 @@ import android.view.ViewGroup; import android.view.ViewGroup.MarginLayoutParams; import android.view.ViewParent; +import android.view.WindowInsets; import android.view.accessibility.AccessibilityEvent; import androidx.annotation.FloatRange; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.Px; +import androidx.annotation.RequiresApi; import androidx.annotation.RestrictTo; import androidx.annotation.StringRes; import androidx.annotation.VisibleForTesting; @@ -217,6 +220,8 @@ void onLayout(@NonNull View bottomSheet) {} private static final int VIEW_INDEX_BOTTOM_SHEET = 0; + private static final int INVALID_POSITION = -1; + @VisibleForTesting static final int VIEW_INDEX_ACCESSIBILITY_DELEGATE_VIEW = 1; @@ -321,7 +326,7 @@ void onLayout(@NonNull View bottomSheet) {} int activePointerId; - private int initialY; + private int initialY = INVALID_POSITION; boolean touchingScrollingChild; @@ -563,11 +568,12 @@ public boolean onLayoutChild( if (parentHeight - childHeight < insetTop) { if (paddingTopSystemWindowInsets) { // If the bottomsheet would land in the middle of the status bar when fully expanded add - // extra space to make sure it goes all the way. - childHeight = parentHeight; + // extra space to make sure it goes all the way up or up to max height if it is specified. + childHeight = (maxHeight == NO_MAX_SIZE) ? parentHeight : min(parentHeight, maxHeight); } else { // If we don't want the bottomsheet to go under the status bar we cap its height - childHeight = parentHeight - insetTop; + int insetHeight = parentHeight - insetTop; + childHeight = (maxHeight == NO_MAX_SIZE) ? insetHeight : min(insetHeight, maxHeight); } } fitToContentsOffset = max(0, parentHeight - childHeight); @@ -655,6 +661,7 @@ public boolean onInterceptTouchEvent( && state != STATE_DRAGGING && !parent.isPointInChildBounds(scroll, (int) event.getX(), (int) event.getY()) && viewDragHelper != null + && initialY != INVALID_POSITION && Math.abs(initialY - event.getY()) > viewDragHelper.getTouchSlop(); } @@ -1408,7 +1415,7 @@ private void updateDrawableForTargetState(@State int state, boolean animate) { if (interpolatorAnimator.isRunning()) { interpolatorAnimator.reverse(); } else { - float to = removeCorners ? 0f : 1f; + float to = removeCorners ? calculateInterpolationWithCornersRemoved() : 1f; float from = 1f - to; interpolatorAnimator.setFloatValues(from, to); interpolatorAnimator.start(); @@ -1417,8 +1424,48 @@ private void updateDrawableForTargetState(@State int state, boolean animate) { if (interpolatorAnimator != null && interpolatorAnimator.isRunning()) { interpolatorAnimator.cancel(); } - materialShapeDrawable.setInterpolation(expandedCornersRemoved ? 0f : 1f); + materialShapeDrawable.setInterpolation( + expandedCornersRemoved ? calculateInterpolationWithCornersRemoved() : 1f); + } + } + + private float calculateInterpolationWithCornersRemoved() { + if (materialShapeDrawable != null + && viewRef != null + && viewRef.get() != null + && VERSION.SDK_INT >= VERSION_CODES.S) { + V view = viewRef.get(); + int[] location = new int[2]; + view.getLocationOnScreen(location); + // Only use device corner radius if sheet is touching top of screen. + if (location[1] == 0) { + final WindowInsets insets = view.getRootWindowInsets(); + if (insets != null) { + float topLeftInterpolation = + calculateCornerInterpolation( + materialShapeDrawable.getTopLeftCornerResolvedSize(), + insets.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT)); + float topRightInterpolation = + calculateCornerInterpolation( + materialShapeDrawable.getTopRightCornerResolvedSize(), + insets.getRoundedCorner(RoundedCorner.POSITION_TOP_RIGHT)); + return Math.max(topLeftInterpolation, topRightInterpolation); + } + } + } + return 0; + } + + @RequiresApi(VERSION_CODES.S) + private float calculateCornerInterpolation( + float materialShapeDrawableCornerSize, @Nullable RoundedCorner deviceRoundedCorner) { + if (deviceRoundedCorner != null) { + float deviceCornerRadius = deviceRoundedCorner.getRadius(); + if (deviceCornerRadius > 0 && materialShapeDrawableCornerSize > 0) { + return deviceCornerRadius / materialShapeDrawableCornerSize; + } } + return 0; } private boolean isExpandedAndShouldRemoveCorners() { @@ -1462,6 +1509,7 @@ private float calculateSlideOffsetWithTop(int top) { private void reset() { activePointerId = ViewDragHelper.INVALID_POINTER; + initialY = INVALID_POSITION; if (velocityTracker != null) { velocityTracker.recycle(); velocityTracker = null; @@ -1573,7 +1621,7 @@ MaterialShapeDrawable getMaterialShapeDrawable() { } private void createShapeValueAnimator() { - interpolatorAnimator = ValueAnimator.ofFloat(0f, 1f); + interpolatorAnimator = ValueAnimator.ofFloat(calculateInterpolationWithCornersRemoved(), 1f); interpolatorAnimator.setDuration(CORNER_ANIMATION_DURATION); interpolatorAnimator.addUpdateListener( new AnimatorUpdateListener() { diff --git a/material/java/com/google/android/material/carousel/MaskableFrameLayout.java b/material/java/com/google/android/material/carousel/MaskableFrameLayout.java index 89f9bcf8a..6feabe0eb 100644 --- a/material/java/com/google/android/material/carousel/MaskableFrameLayout.java +++ b/material/java/com/google/android/material/carousel/MaskableFrameLayout.java @@ -54,6 +54,7 @@ public class MaskableFrameLayout extends FrameLayout implements Maskable, Shapea @Nullable private OnMaskChangedListener onMaskChangedListener; @NonNull private ShapeAppearanceModel shapeAppearanceModel; private final MaskableDelegate maskableDelegate = createMaskableDelegate(); + @Nullable private Boolean savedForceCompatClippingEnabled = null; public MaskableFrameLayout(@NonNull Context context) { this(context, null); @@ -73,8 +74,8 @@ public MaskableFrameLayout( private MaskableDelegate createMaskableDelegate() { if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) { return new MaskableDelegateV33(this); - } else if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - return new MaskableDelegateV21(this); + } else if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP_MR1) { + return new MaskableDelegateV22(this); } else { return new MaskableDelegateV14(); } @@ -86,6 +87,24 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) { onMaskChanged(); } + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + // Restore any saved force compat clipping setting. + if (savedForceCompatClippingEnabled != null) { + maskableDelegate.setForceCompatClippingEnabled(this, savedForceCompatClippingEnabled); + } + } + + @Override + protected void onDetachedFromWindow() { + // When detaching from the window, force canvas clipping to avoid any transitions from releasing + // the mask outline set by the MaskableDelegate's ViewOutlineProvider, if any. + savedForceCompatClippingEnabled = maskableDelegate.isForceCompatClippingEnabled(); + maskableDelegate.setForceCompatClippingEnabled(this, true); + super.onDetachedFromWindow(); + } + @Override public void setShapeAppearanceModel(@NonNull ShapeAppearanceModel shapeAppearanceModel) { this.shapeAppearanceModel = @@ -160,7 +179,6 @@ private void onMaskChanged() { } } - /** * Set whether this view should always use canvas clipping to clip to its masked shape. * @@ -222,6 +240,10 @@ private abstract static class MaskableDelegate { */ abstract boolean shouldUseCompatClipping(); + boolean isForceCompatClippingEnabled() { + return forceCompatClippingEnabled; + } + /** * Set whether the client would like to always use compat clipping regardless of whether other * means are available. @@ -280,7 +302,7 @@ void maybeClip(Canvas canvas, CanvasOperation op) { } /** - * A {@link MaskableDelegate} implementation for API 14-20 that always clips using canvas + * A {@link MaskableDelegate} implementation for API 14-21 that always clips using canvas * clipping. */ private static class MaskableDelegateV14 extends MaskableDelegate { @@ -303,19 +325,20 @@ void invalidateClippingMethod(View view) { } /** - * A {@link MaskableDelegate} for API 21-32 that uses {@link ViewOutlineProvider} to clip when the + * A {@link MaskableDelegate} for API 22-32 that uses {@link ViewOutlineProvider} to clip when the * shape being clipped is a round rect with symmetrical corners and canvas clipping for all other - * shapes. + * shapes. This way is not used for API 21 because outline invalidation is incorrectly implemented + * in this version. * *

{@link Outline#setRoundRect(Rect, float)} is only able to clip to a rectangle with a single * corner radius for all four corners. */ - @RequiresApi(VERSION_CODES.LOLLIPOP) - private static class MaskableDelegateV21 extends MaskableDelegate { + @RequiresApi(VERSION_CODES.LOLLIPOP_MR1) + private static class MaskableDelegateV22 extends MaskableDelegate { private boolean isShapeRoundRect = false; - MaskableDelegateV21(View view) { + MaskableDelegateV22(View view) { initMaskOutlineProvider(view); } @@ -366,8 +389,8 @@ public void getOutline(View view, Outline outline) { } /** - * A {@link MaskableDelegate} for API 33+ that uses {@link ViewOutlineProvider} to clip for - * all shapes. + * A {@link MaskableDelegate} for API 33+ that uses {@link ViewOutlineProvider} to clip for all + * shapes. * *

{@link Outline#setPath(Path)} was added in API 33 and allows using {@link * ViewOutlineProvider} to clip for all shapes. diff --git a/material/java/com/google/android/material/carousel/MultiBrowseCarouselStrategy.java b/material/java/com/google/android/material/carousel/MultiBrowseCarouselStrategy.java index 4d91e3005..34bb1a911 100644 --- a/material/java/com/google/android/material/carousel/MultiBrowseCarouselStrategy.java +++ b/material/java/com/google/android/material/carousel/MultiBrowseCarouselStrategy.java @@ -19,8 +19,10 @@ import com.google.android.material.R; import static java.lang.Math.abs; +import static java.lang.Math.ceil; +import static java.lang.Math.floor; import static java.lang.Math.max; -import static java.lang.Math.round; +import static java.lang.Math.min; import android.content.Context; import androidx.recyclerview.widget.RecyclerView.LayoutParams; @@ -28,6 +30,8 @@ import androidx.annotation.NonNull; import androidx.annotation.RestrictTo; import androidx.annotation.RestrictTo.Scope; +import androidx.annotation.VisibleForTesting; +import androidx.core.math.MathUtils; /** * A {@link CarouselStrategy} that knows how to size and fit large, medium and small items into a @@ -44,18 +48,19 @@ */ public final class MultiBrowseCarouselStrategy extends CarouselStrategy { - // The percentage by which a medium item needs to be larger than a small item and smaller - // than an large item. This is used to ensure a medium item is truly somewhere between the - // small and large sizes, making for a visually balanced arrangement. - // 0F would mean a medium item could be >= small item size and <= a large item size. - // .25F means the medium item must be >= 125% of the small item size and <= 75% of the - // large item size. - private static final float MEDIUM_SIZE_PERCENTAGE_DELTA = .25F; + // Specifies a percentage of a medium item's size by which it can be increased or decreased to + // help fit an arrangement into the carousel's available space. + private static final float MEDIUM_ITEM_FLEX_PERCENTAGE = .1F; + + private static final int[] SMALL_COUNTS = new int[] {1}; + private static final int[] MEDIUM_COUNTS = new int[] {1, 0}; + private static final int[] MEDIUM_COUNTS_COMPACT = new int[] {0}; // True if medium items should never be added and arrangements should consist of only large and // small items. This will often result in a greater number of large items but more variability in // large item size. This can be desirable when optimizing for the greatest number of fully // unmasked items visible at once. + // TODO(b/274604170): Remove this option private final boolean forceCompactArrangement; public MultiBrowseCarouselStrategy() { @@ -68,7 +73,6 @@ public MultiBrowseCarouselStrategy() { * @param forceCompactArrangement true if items should be fit in a way that maximizes the number * of large, unmasked items. false if this strategy is free to determine an opinionated * balance between item sizes. - * * @hide */ @RestrictTo(Scope.LIBRARY_GROUP) @@ -80,131 +84,375 @@ private float getExtraSmallSize(@NonNull Context context) { return context.getResources().getDimension(R.dimen.m3_carousel_gone_size); } - private float getSmallSize(@NonNull Context context) { - return context.getResources().getDimension(R.dimen.m3_carousel_small_item_size); + private float getSmallSizeMin(@NonNull Context context) { + return context.getResources().getDimension(R.dimen.m3_carousel_small_item_size_min); + } + + private float getSmallSizeMax(@NonNull Context context) { + return context.getResources().getDimension(R.dimen.m3_carousel_small_item_size_max); } @Override @NonNull - KeylineState onFirstChildMeasuredWithMargins( - @NonNull Carousel carousel, @NonNull View child) { + KeylineState onFirstChildMeasuredWithMargins(@NonNull Carousel carousel, @NonNull View child) { + float availableSpace = carousel.getContainerWidth(); + LayoutParams childLayoutParams = (LayoutParams) child.getLayoutParams(); float childHorizontalMargins = childLayoutParams.leftMargin + childLayoutParams.rightMargin; - float smallChildWidth = getSmallSize(child.getContext()) + childHorizontalMargins; - float extraSmallChildWidth = getExtraSmallSize(child.getContext()) + childHorizontalMargins; - - float availableSpace = carousel.getContainerWidth(); + float smallChildWidthMin = getSmallSizeMin(child.getContext()) + childHorizontalMargins; + float smallChildWidthMax = getSmallSizeMax(child.getContext()) + childHorizontalMargins; - // The minimum viable arrangement is 1 large and 1 small child. A single large item size - // cannot be greater than the available space minus a small child width. - float maxLargeChildSpace = availableSpace - smallChildWidth; - float largeChildWidth = child.getMeasuredWidth() + childHorizontalMargins; - - int largeCount; - int mediumCount; - int smallCount; - float mediumChildWidth; - - if (maxLargeChildSpace <= smallChildWidth) { - // There is not enough space to show a small and a large item. Remove the small item and - // default to showing a single, fullscreen item. - largeCount = 1; - largeChildWidth = availableSpace; - mediumCount = 0; - mediumChildWidth = 0; - smallCount = 0; - } else if (largeChildWidth >= maxLargeChildSpace) { - // There is only enough space to show 1 large, and 1 small item. - largeCount = 1; - largeChildWidth = maxLargeChildSpace; - mediumCount = 0; - mediumChildWidth = 0F; - smallCount = 1; - } else { - // There is enough space for some combination of large items, an optional medium item, - // and a small item. Find the arrangement where large items need to be adjusted in - // size by the least amount. - float mediumChildMinWidth = - smallChildWidth + (smallChildWidth * MEDIUM_SIZE_PERCENTAGE_DELTA); - // TODO: Ensure this is always <= expanded size even after expanded size is adjusted. - float mediumChildMaxWidth = - largeChildWidth - (largeChildWidth * MEDIUM_SIZE_PERCENTAGE_DELTA); - - float largeRangeMin = availableSpace - (mediumChildMaxWidth + smallChildWidth); - float largeRangeMax = availableSpace - (mediumChildMinWidth + smallChildWidth); - - // The standard arrangement is `x` large, 1 medium, and 1 small item where `x` is the - // maximum number of large items that can fit within the available space. - float standardLargeRangeCenter = (largeRangeMin + largeRangeMax) / 2; - float standardLargeQuotient = standardLargeRangeCenter / largeChildWidth; - int standardLargeCount = round(standardLargeQuotient); - float standardLargeChildWidth = largeChildWidth; - // If the largeChildWidth * count falls outside of the large min-max range, the width of - // large children for the standard arrangement needs to be adjusted. Make the smallest - // adjustment possible to bring the number of large children back to fit within the - // available space. - if (largeChildWidth * standardLargeCount < largeRangeMin) { - standardLargeChildWidth = largeRangeMin / standardLargeCount; - } else if (largeChildWidth * standardLargeCount > largeRangeMax) { - standardLargeChildWidth = largeRangeMax / standardLargeCount; - } + float measuredChildWidth = child.getMeasuredWidth(); + float targetLargeChildWidth = min(measuredChildWidth + childHorizontalMargins, availableSpace); + // Ideally we would like to create a balanced arrangement where a small item is 1/3 the size of + // the large item and medium items are sized between large and small items. Clamp the small + // target size within our min-max range and as close to 1/3 of the target large item size as + // possible. + float targetSmallChildWidth = + MathUtils.clamp( + measuredChildWidth / 3F + childHorizontalMargins, + getSmallSizeMin(child.getContext()) + childHorizontalMargins, + getSmallSizeMax(child.getContext()) + childHorizontalMargins); + float targetMediumChildWidth = (targetLargeChildWidth + targetSmallChildWidth) / 2F; - // The compact arrangement is `x` large, and 1 small item where `x` is the maximum - // number of large items that can fit within the available space. - float compactLargeQuotient = (availableSpace - smallChildWidth) / largeChildWidth; - int compactLargeCount = round(compactLargeQuotient); - // Adjust the largeChildWidth so largeChildWidth * largeCount fits perfectly within - // the available space. - float compactLargeChildWidth = (availableSpace - smallChildWidth) / compactLargeCount; - - // Use the arrangement type which requires the large item size to be adjusted the least, - // retaining the developer specified item size as much as possible. - if (abs(largeChildWidth - standardLargeChildWidth) - <= abs(largeChildWidth - compactLargeChildWidth) - && !forceCompactArrangement) { - largeCount = standardLargeCount; - largeChildWidth = standardLargeChildWidth; - mediumCount = 1; - mediumChildWidth = availableSpace - (largeChildWidth * largeCount) - smallChildWidth; - smallCount = 1; - } else { - largeCount = compactLargeCount; - largeChildWidth = compactLargeChildWidth; - mediumCount = 0; - mediumChildWidth = 0; - smallCount = 1; - } + // Create arrays representing the possible count of small, medium, and large items. These are + // not in an asc./dec. order but are in order of priority. A small count array of { 2, 3, 1 } + // says that ideally an arrangement with 2 small items is found, then 3 is next most desirable, + // then finally 1. + int[] smallCounts = SMALL_COUNTS; + int[] mediumCounts = forceCompactArrangement ? MEDIUM_COUNTS_COMPACT : MEDIUM_COUNTS; + // Find the minimum space left for large items after filling the carousel with the most + // permissible medium and small items to determine a plausible minimum large count. + float minAvailableLargeSpace = + availableSpace + - (targetMediumChildWidth * maxValue(mediumCounts)) + - (smallChildWidthMax * maxValue(smallCounts)); + int largeCountMin = (int) max(1, floor(minAvailableLargeSpace / targetLargeChildWidth)); + int largeCountMax = (int) ceil(availableSpace / targetLargeChildWidth); + int[] largeCounts = new int[largeCountMax - largeCountMin + 1]; + for (int i = 0; i < largeCounts.length; i++) { + largeCounts[i] = largeCountMax - i; } + Arrangement arrangement = + findLowestCostArrangement( + availableSpace, + targetSmallChildWidth, + smallChildWidthMin, + smallChildWidthMax, + smallCounts, + targetMediumChildWidth, + mediumCounts, + targetLargeChildWidth, + largeCounts); + + float extraSmallChildWidth = getExtraSmallSize(child.getContext()) + childHorizontalMargins; + float start = 0F; float extraSmallHeadCenterX = start - (extraSmallChildWidth / 2F); - float largeStartCenterX = start + (largeChildWidth / 2F); - float largeEndCenterX = largeStartCenterX + (max(0, largeCount - 1) * largeChildWidth); - start = largeEndCenterX + largeChildWidth / 2F; + float largeStartCenterX = start + (arrangement.largeSize / 2F); + float largeEndCenterX = + largeStartCenterX + (max(0, arrangement.largeCount - 1) * arrangement.largeSize); + start = largeEndCenterX + arrangement.largeSize / 2F; - float mediumCenterX = mediumCount > 0 ? start + (mediumChildWidth / 2F) : largeEndCenterX; - start = mediumCount > 0 ? mediumCenterX + (mediumChildWidth / 2F) : start; + float mediumCenterX = + arrangement.mediumCount > 0 ? start + (arrangement.mediumSize / 2F) : largeEndCenterX; + start = arrangement.mediumCount > 0 ? mediumCenterX + (arrangement.mediumSize / 2F) : start; - float smallStartCenterX = smallCount > 0 ? start + (smallChildWidth / 2F) : mediumCenterX; + float smallStartCenterX = + arrangement.smallCount > 0 ? start + (arrangement.smallSize / 2F) : mediumCenterX; float extraSmallTailCenterX = carousel.getContainerWidth() + (extraSmallChildWidth / 2F); float extraSmallMask = - getChildMaskPercentage(extraSmallChildWidth, largeChildWidth, childHorizontalMargins); + getChildMaskPercentage(extraSmallChildWidth, arrangement.largeSize, childHorizontalMargins); float smallMask = - getChildMaskPercentage(smallChildWidth, largeChildWidth, childHorizontalMargins); + getChildMaskPercentage( + arrangement.smallSize, arrangement.largeSize, childHorizontalMargins); float mediumMask = - getChildMaskPercentage(mediumChildWidth, largeChildWidth, childHorizontalMargins); + getChildMaskPercentage( + arrangement.mediumSize, arrangement.largeSize, childHorizontalMargins); float largeMask = 0F; - return new KeylineState.Builder(largeChildWidth) - .addKeyline(extraSmallHeadCenterX, extraSmallMask, extraSmallChildWidth) - .addKeylineRange(largeStartCenterX, largeMask, largeChildWidth, largeCount, true) - .addKeyline(mediumCenterX, mediumMask, mediumChildWidth) - .addKeylineRange(smallStartCenterX, smallMask, smallChildWidth, smallCount) - .addKeyline(extraSmallTailCenterX, extraSmallMask, extraSmallChildWidth) - .build(); + KeylineState.Builder builder = + new KeylineState.Builder(arrangement.largeSize) + .addKeyline(extraSmallHeadCenterX, extraSmallMask, extraSmallChildWidth) + .addKeylineRange( + largeStartCenterX, largeMask, arrangement.largeSize, arrangement.largeCount, true); + if (arrangement.mediumCount > 0) { + builder.addKeyline(mediumCenterX, mediumMask, arrangement.mediumSize); + } + if (arrangement.smallCount > 0) { + builder.addKeylineRange( + smallStartCenterX, smallMask, arrangement.smallSize, arrangement.smallCount); + } + builder.addKeyline(extraSmallTailCenterX, extraSmallMask, extraSmallChildWidth); + return builder.build(); + } + + /** + * Create an arrangement for all possible permutations for {@code smallCounts}, {@code + * mediumCounts}, and {@code largeCounts}, fit each into the available space, and return the + * arrangement with the lowest cost. + * + *

Keep in mind that the returned arrangements do not take into account the available space + * from the carousel. They will all occupy varying degrees of more or less space. The caller needs + * to handle sorting the returned list, picking the most desirable arrangement, and fitting the + * arrangement to the size of the carousel. + * + * @param availableSpace the space the arrangmenet needs to fit + * @param targetSmallSize the size small items would like to be + * @param minSmallSize the minimum size small items are allowed to be + * @param maxSmallSize the maximum size small items are allowed to be + * @param smallCounts an array of small item counts for a valid arrangement + * @param targetMediumSize the size medium items would like to be + * @param mediumCounts an array of medium item counts for a valid arrangement + * @param targetLargeSize the size large items would like to be + * @param largeCounts an array of large item counts for a valid arrangement + * @return the arrangement that is considered the most desirable and has been adjusted to fit + * within the available space + */ + private static Arrangement findLowestCostArrangement( + float availableSpace, + float targetSmallSize, + float minSmallSize, + float maxSmallSize, + int[] smallCounts, + float targetMediumSize, + int[] mediumCounts, + float targetLargeSize, + int[] largeCounts) { + Arrangement lowestCostArrangement = null; + int priority = 1; + for (int largeCount : largeCounts) { + for (int mediumCount : mediumCounts) { + for (int smallCount : smallCounts) { + Arrangement arrangement = + new Arrangement( + priority, + targetSmallSize, + minSmallSize, + maxSmallSize, + smallCount, + targetMediumSize, + mediumCount, + targetLargeSize, + largeCount, + availableSpace); + if (lowestCostArrangement == null || arrangement.cost < lowestCostArrangement.cost) { + lowestCostArrangement = arrangement; + if (lowestCostArrangement.cost == 0F) { + // If the new lowestCostArrangement has a cost of 0, we know it didn't have to alter + // the large item size at all. We also know that arrangement permutations will be + // generated in order of priority. We can exit early knowing there will not be an + // arrangement with a better cost or priority. + return lowestCostArrangement; + } + } + priority++; + } + } + } + return lowestCostArrangement; + } + + private static int maxValue(int[] array) { + int largest = Integer.MIN_VALUE; + for (int j : array) { + if (j > largest) { + largest = j; + } + } + + return largest; + } + + /** + * An object that holds data about a combination of large, medium, and small items, knows how to + * alter an arrangement to fit within an available space, and can assess the arrangement's + * desirability. + */ + @VisibleForTesting + static final class Arrangement { + final int priority; + float smallSize; + final int smallCount; + final int mediumCount; + float mediumSize; + float largeSize; + final int largeCount; + final float cost; + + /** + * Creates a new arrangement by taking in a number of small, medium, and large items and the + * size each would like to be and then fitting the sizes to work within the {@code + * availableSpace}. + * + *

Note: The values for each item size after construction will likely differ from the target + * values passed to the constructor since the constructor handles altering the sizes until the + * total count is able to fit within the space see {@link #fit(float, float, float, float)} for + * more details. + * + * @param priority the order in which this arrangement should be preferred against other + * arrangements that fit + * @param targetSmallSize the size of a small item in this arrangement + * @param minSmallSize the minimum size a small item is allowed to be + * @param maxSmallSize the maximum size a small item is allowed to be + * @param smallCount the number of small items in this arrangement + * @param targetMediumSize the size of medium items in this arrangement + * @param mediumCount the number of medium items in this arrangement + * @param targetLargeSize the size of large items in this arrangement + * @param largeCount the number of large items in this arrangement + * @param availableSpace the space this arrangement needs to fit within + */ + Arrangement( + int priority, + float targetSmallSize, + float minSmallSize, + float maxSmallSize, + int smallCount, + float targetMediumSize, + int mediumCount, + float targetLargeSize, + int largeCount, + float availableSpace) { + this.priority = priority; + this.smallSize = MathUtils.clamp(targetSmallSize, minSmallSize, maxSmallSize); + this.smallCount = smallCount; + this.mediumSize = targetMediumSize; + this.mediumCount = mediumCount; + this.largeSize = targetLargeSize; + this.largeCount = largeCount; + + fit(availableSpace, minSmallSize, maxSmallSize, targetLargeSize); + this.cost = cost(targetLargeSize); + } + + @NonNull + @Override + public String toString() { + return "Arrangement [priority=" + + priority + + ", smallCount=" + + smallCount + + ", smallSize=" + + smallSize + + ", mediumCount=" + + mediumCount + + ", mediumSize=" + + mediumSize + + ", largeCount=" + + largeCount + + ", largeSize=" + + largeSize + + ", cost=" + + cost + + "]"; + } + + /** Gets the total space taken by this arrangement. */ + private float getSpace() { + return (largeSize * largeCount) + (mediumSize * mediumCount) + (smallSize * smallCount); + } + + /** + * Alters the item sizes of this arrangement until the space occupied fits within the {@code + * availableSpace}. + * + *

This method tries to adjust the size of large items as little as possible by first + * adjusting small items as much as possible, then adjusting medium items as much as possible, + * and finally adjusting large items if the arrangement is still unable to fit. + * + * @param availableSpace the size of the carousel this arrangement needs to fit + * @param minSmallSize the minimum size small items can be + * @param maxSmallSize the maximum size medium items can be + */ + private void fit( + float availableSpace, float minSmallSize, float maxSmallSize, float targetLargeSize) { + float delta = availableSpace - getSpace(); + // First, resize small items within their allowable min-max range to try to fit the + // arrangement into the available space. + if (smallCount > 0 && delta > 0) { + // grow the small items + smallSize += min(delta / smallCount, maxSmallSize - smallSize); + } else if (smallCount > 0 && delta < 0) { + // shrink the small items + smallSize += max(delta / smallCount, minSmallSize - smallSize); + } + + largeSize = + calculateLargeSize(availableSpace, smallCount, smallSize, mediumCount, largeCount); + mediumSize = (largeSize + smallSize) / 2F; + + // If the large size has been adjusted away from its target size to fit the arrangement, + // counter this as much as possible by altering the medium item within its acceptable flex + // range. + if (mediumCount > 0 && largeSize != targetLargeSize) { + float targetAdjustment = (targetLargeSize - largeSize) * largeCount; + float availableMediumFlex = (mediumSize * MEDIUM_ITEM_FLEX_PERCENTAGE) * mediumCount; + float distribute = min(abs(targetAdjustment), availableMediumFlex); + if (targetAdjustment > 0F) { + // Reduce the size of the medium item and give it back to the large items + mediumSize -= (distribute / mediumCount); + largeSize += (distribute / largeCount); + } else { + // Increase the size of the medium item and take from the large items + mediumSize += (distribute / mediumCount); + largeSize -= (distribute / largeCount); + } + } + } + + /** + * Calculates the large size that is able to fit within the available space given item counts, + * the small size, and that the medium size is {@code (largeSize + smallSize) / 2}. + * + *

This method solves the following equation for largeSize: + * + *

{@code availableSpace = (largeSize * largeCount) + (((largeSize + smallSize) / 2) * + * mediumCount) + (smallSize * smallCount)} + * + * @param availableSpace the total available space + * @param smallCount the number of small items in the arrangement + * @param smallSize the size of small items in the arrangement + * @param mediumCount the number of medium items in the arrangement + * @param largeCount the number of large items in the arrangement + * @return the large item size which will fit for the available space and other item constraints + */ + private float calculateLargeSize( + float availableSpace, int smallCount, float smallSize, int mediumCount, int largeCount) { + // Zero out small size if there are no small items + smallSize = smallCount > 0 ? smallSize : 0F; + return (availableSpace - (((float) smallCount) + ((float) mediumCount) / 2F) * smallSize) + / (((float) largeCount) + ((float) mediumCount) / 2F); + } + + private boolean isValid() { + if (largeCount > 0 && smallCount > 0 && mediumCount > 0) { + return largeSize > mediumSize && mediumSize > smallSize; + } else if (largeCount > 0 && smallCount > 0) { + return largeSize > smallSize; + } + + return true; + } + + /** + * Calculates the cost of this arrangement to determine visual desirability and adherence to + * inputs. + * + * @param targetLargeSize the size large items would like to be + * @return a float representing the cost of this arrangement where the lower the cost the better + */ + private float cost(float targetLargeSize) { + if (!isValid()) { + return Float.MAX_VALUE; + } + // Arrangements have a lower cost if they have a priority closer to 1 and their largeSize is + // altered as little as possible. + return abs(targetLargeSize - largeSize) * priority; + } } } diff --git a/material/java/com/google/android/material/carousel/res/values/dimens.xml b/material/java/com/google/android/material/carousel/res/values/dimens.xml index 93da3891b..3f14cfd82 100644 --- a/material/java/com/google/android/material/carousel/res/values/dimens.xml +++ b/material/java/com/google/android/material/carousel/res/values/dimens.xml @@ -18,7 +18,8 @@ 28dp 2dp - 56dp + 40dp + 56dp 10dp 1dp diff --git a/material/java/com/google/android/material/chip/Chip.java b/material/java/com/google/android/material/chip/Chip.java index a99f5d076..f37b3d4d0 100644 --- a/material/java/com/google/android/material/chip/Chip.java +++ b/material/java/com/google/android/material/chip/Chip.java @@ -968,7 +968,7 @@ public PointerIcon onResolvePointerIcon(@NonNull MotionEvent event, int pointerI if (getCloseIconTouchBounds().contains(event.getX(), event.getY()) && isEnabled()) { return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND); } - return null; + return super.onResolvePointerIcon(event, pointerIndex); } /** @hide */ diff --git a/material/java/com/google/android/material/tabs/res/drawable-v21/m3_tabs_background.xml b/material/java/com/google/android/material/tabs/res/drawable-v21/m3_tabs_background.xml index 253180818..6ce9a8c58 100644 --- a/material/java/com/google/android/material/tabs/res/drawable-v21/m3_tabs_background.xml +++ b/material/java/com/google/android/material/tabs/res/drawable-v21/m3_tabs_background.xml @@ -19,12 +19,12 @@ - + + - + diff --git a/material/java/com/google/android/material/tabs/res/drawable-v23/m3_tabs_background.xml b/material/java/com/google/android/material/tabs/res/drawable-v23/m3_tabs_background.xml index 1c24d7523..a2fb349a8 100644 --- a/material/java/com/google/android/material/tabs/res/drawable-v23/m3_tabs_background.xml +++ b/material/java/com/google/android/material/tabs/res/drawable-v23/m3_tabs_background.xml @@ -26,8 +26,9 @@ - - + + + diff --git a/material/java/com/google/android/material/tabs/res/values/tokens.xml b/material/java/com/google/android/material/tabs/res/values/tokens.xml index b962f2beb..bac53cb50 100644 --- a/material/java/com/google/android/material/tabs/res/values/tokens.xml +++ b/material/java/com/google/android/material/tabs/res/values/tokens.xml @@ -15,7 +15,7 @@ ~ limitations under the License. --> - + @@ -30,9 +30,6 @@ 24dp ?attr/colorPrimary ?attr/colorOnSurfaceVariant - - ?attr/colorSurfaceVariant - 1dp ?attr/colorPrimary 3dp diff --git a/material/java/com/google/android/material/textfield/EndCompoundLayout.java b/material/java/com/google/android/material/textfield/EndCompoundLayout.java index fb119d70c..7465f87e3 100644 --- a/material/java/com/google/android/material/textfield/EndCompoundLayout.java +++ b/material/java/com/google/android/material/textfield/EndCompoundLayout.java @@ -737,6 +737,21 @@ void updateSuffixTextViewPadding() { textInputLayout.editText.getPaddingBottom()); } + int getSuffixTextEndOffset() { + int endIconOffset; + if (isEndIconVisible() || isErrorIconVisible()) { + endIconOffset = + endIconView.getMeasuredWidth() + + MarginLayoutParamsCompat.getMarginStart( + (MarginLayoutParams) endIconView.getLayoutParams()); + } else { + endIconOffset = 0; + } + return ViewCompat.getPaddingEnd(this) + + ViewCompat.getPaddingEnd(suffixTextView) + + endIconOffset; + } + @Nullable CheckableImageButton getCurrentEndIconView() { if (isErrorIconVisible()) { diff --git a/material/java/com/google/android/material/textfield/StartCompoundLayout.java b/material/java/com/google/android/material/textfield/StartCompoundLayout.java index ebf845551..61f0df192 100644 --- a/material/java/com/google/android/material/textfield/StartCompoundLayout.java +++ b/material/java/com/google/android/material/textfield/StartCompoundLayout.java @@ -328,6 +328,21 @@ void updatePrefixTextViewPadding() { editText.getCompoundPaddingBottom()); } + int getPrefixTextStartOffset() { + int startIconOffset; + if (isStartIconVisible()) { + startIconOffset = + startIconView.getMeasuredWidth() + + MarginLayoutParamsCompat.getMarginEnd( + (MarginLayoutParams) startIconView.getLayoutParams()); + } else { + startIconOffset = 0; + } + return ViewCompat.getPaddingStart(this) + + ViewCompat.getPaddingStart(prefixTextView) + + startIconOffset; + } + void onHintStateChanged(boolean hintExpanded) { this.hintExpanded = hintExpanded; updateVisibility(); diff --git a/material/java/com/google/android/material/textfield/TextInputLayout.java b/material/java/com/google/android/material/textfield/TextInputLayout.java index f1032ad26..ec0ade5d5 100644 --- a/material/java/com/google/android/material/textfield/TextInputLayout.java +++ b/material/java/com/google/android/material/textfield/TextInputLayout.java @@ -73,6 +73,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.Px; +import androidx.annotation.RequiresApi; import androidx.annotation.RestrictTo; import androidx.annotation.StringRes; import androidx.annotation.StyleRes; @@ -258,6 +259,9 @@ public interface LengthCounter { @Nullable private ColorStateList counterTextColor; @Nullable private ColorStateList counterOverflowTextColor; + @Nullable private ColorStateList cursorColor; + @Nullable private ColorStateList cursorErrorColor; + private boolean hintEnabled; private CharSequence hint; @@ -610,6 +614,9 @@ public TextInputLayout(@NonNull Context context, @Nullable AttributeSet attrs, i setHintTextAppearance(a.getResourceId(R.styleable.TextInputLayout_hintTextAppearance, 0)); } + cursorColor = a.getColorStateList(R.styleable.TextInputLayout_cursorColor); + cursorErrorColor = a.getColorStateList(R.styleable.TextInputLayout_cursorErrorColor); + final int errorTextAppearance = a.getResourceId(R.styleable.TextInputLayout_errorTextAppearance, 0); final CharSequence errorContentDescription = @@ -1542,6 +1549,10 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {} this.isProvidingHint = true; } + if (VERSION.SDK_INT >= VERSION_CODES.Q) { + updateCursorColor(); + } + if (counterView != null) { updateCounter(this.editText.getText()); } @@ -2530,6 +2541,80 @@ public int getPlaceholderTextAppearance() { return placeholderTextAppearance; } + /** + * Sets the cursor color. Using this method will take precedence over using the + * value of {@code ?attr/colorControlActivated}. + * + *

Note: This method only has effect on API levels 28+. On lower API levels + * {@code ?attr/colorControlActivated} will be used for the cursor color. + * + * @param cursorColor the cursor color to be set + * @see #getCursorColor + * @see #setCursorErrorColor + * @attr ref com.google.android.material.R.styleable#TextInputLayout_cursorColor + */ + @RequiresApi(VERSION_CODES.Q) + public void setCursorColor(@Nullable ColorStateList cursorColor) { + if (this.cursorColor != cursorColor) { + this.cursorColor = cursorColor; + updateCursorColor(); + } + } + + /** + * Returns the cursor color. It will return the value of {@code app:cursorColor} if set, or + * null otherwise. + * + *

Note: This value only has effect on API levels 28+. On lower API levels + * {@code ?attr/colorControlActivated} will be used for the cursor color. + * + * @see #setCursorColor + * @attr ref com.google.android.material.R.styleable#TextInputLayout_cursorColor + */ + @Nullable + @RequiresApi(VERSION_CODES.Q) + public ColorStateList getCursorColor() { + return cursorColor; + } + + /** + * Sets the cursor color when an error is being displayed. If null, the cursor doesn't change its + * color when the text field is in an error state. + * + *

Note: This method only has effect on API levels 28+. On lower API levels + * {@code ?attr/colorControlActivated} will be used for the cursor color. + * + * @param cursorErrorColor the error color to use for the cursor + * @see #getCursorErrorColor + * @see #setCursorColor + * @see #setError(CharSequence) + * @attr ref com.google.android.material.R.styleable#TextInputLayout_cursorErrorColor + */ + @RequiresApi(VERSION_CODES.Q) + public void setCursorErrorColor(@Nullable ColorStateList cursorErrorColor) { + if (this.cursorErrorColor != cursorErrorColor) { + this.cursorErrorColor = cursorErrorColor; + if (isOnError()) { + updateCursorColor(); + } + } + } + + /** + * Returns the cursor error color. + * + *

Note: This value only has effect on API levels 28+. On lower API levels + * {@code ?attr/colorControlActivated} will be used for the cursor color. + * + * @see #setCursorErrorColor + * @attr ref com.google.android.material.R.styleable#TextInputLayout_cursorErrorColor + */ + @Nullable + @RequiresApi(VERSION_CODES.Q) + public ColorStateList getCursorErrorColor() { + return cursorErrorColor; + } + /** * Sets prefix text that will be displayed in the input area when the hint is collapsed before * text is entered. If the {@code prefix} is {@code null}, any previous prefix text will be hidden @@ -2769,35 +2854,37 @@ private Rect calculateCollapsedTextBounds(@NonNull Rect rect) { bounds.right = rect.right - editText.getPaddingRight(); return bounds; case BOX_BACKGROUND_FILLED: - bounds.left = getLabelLeftBoundAlightWithPrefix(rect.left, isRtl); + bounds.left = getLabelLeftBoundAlignedWithPrefixAndSuffix(rect.left, isRtl); bounds.top = rect.top + boxCollapsedPaddingTopPx; - bounds.right = getLabelRightBoundAlignedWithSuffix(rect.right, isRtl); + bounds.right = getLabelRightBoundAlignedWithPrefixAndSuffix(rect.right, isRtl); return bounds; case BOX_BACKGROUND_NONE: default: - bounds.left = getLabelLeftBoundAlightWithPrefix(rect.left, isRtl); + bounds.left = getLabelLeftBoundAlignedWithPrefixAndSuffix(rect.left, isRtl); bounds.top = getPaddingTop(); - bounds.right = getLabelRightBoundAlignedWithSuffix(rect.right, isRtl); + bounds.right = getLabelRightBoundAlignedWithPrefixAndSuffix(rect.right, isRtl); return bounds; } } - private int getLabelLeftBoundAlightWithPrefix(int rectLeft, boolean isRtl) { - int left = rectLeft + editText.getCompoundPaddingLeft(); - if (getPrefixText() != null && !isRtl) { - // Label should be vertically aligned with prefix - left = left - getPrefixTextView().getMeasuredWidth() + getPrefixTextView().getPaddingLeft(); + private int getLabelLeftBoundAlignedWithPrefixAndSuffix(int rectLeft, boolean isRtl) { + if (!isRtl && getPrefixText() != null) { + return rectLeft + startLayout.getPrefixTextStartOffset(); + } + if (isRtl && getSuffixText() != null) { + return rectLeft + endLayout.getSuffixTextEndOffset(); } - return left; + return rectLeft + editText.getCompoundPaddingLeft(); } - private int getLabelRightBoundAlignedWithSuffix(int rectRight, boolean isRtl) { - int right = rectRight - editText.getCompoundPaddingRight(); - if (getPrefixText() != null && isRtl) { - // Label should be vertically aligned with prefix if in RTL - right += getPrefixTextView().getMeasuredWidth() - getPrefixTextView().getPaddingRight(); + private int getLabelRightBoundAlignedWithPrefixAndSuffix(int rectRight, boolean isRtl) { + if (!isRtl && getSuffixText() != null) { + return rectRight - endLayout.getSuffixTextEndOffset(); } - return right; + if (isRtl && getPrefixText() != null) { + return rectRight - startLayout.getPrefixTextStartOffset(); + } + return rectRight - editText.getCompoundPaddingRight(); } @NonNull @@ -3590,9 +3677,9 @@ public int getEndIconMinSize() { } /** - * Sets {@link ImageView.ScaleType} for the start icon's ImageButton. + * Sets {@link android.widget.ImageView.ScaleType} for the start icon's ImageButton. * - * @param scaleType {@link ImageView.ScaleType} for the start icon's ImageButton. + * @param scaleType {@link android.widget.ImageView.ScaleType} for the start icon's ImageButton. * @attr ref android.support.design.button.R.styleable#TextInputLayout_startIconScaleType * @see #getStartIconScaleType() */ @@ -3601,9 +3688,9 @@ public void setStartIconScaleType(@NonNull ScaleType scaleType) { } /** - * Returns the {@link ImageView.ScaleType} for the start icon's ImageButton. + * Returns the {@link android.widget.ImageView.ScaleType} for the start icon's ImageButton. * - * @return Returns the {@link ImageView.ScaleType} for the start icon's ImageButton. + * @return Returns the {@link android.widget.ImageView.ScaleType} for the start icon's ImageButton. * @attr ref android.support.design.button.R.styleable#TextInputLayout_startIconScaleType * @see #setStartIconScaleType(ScaleType) */ @@ -3613,9 +3700,9 @@ public ScaleType getStartIconScaleType() { } /** - * Sets {@link ImageView.ScaleType} for the end icon's ImageButton. + * Sets {@link android.widget.ImageView.ScaleType} for the end icon's ImageButton. * - * @param scaleType {@link ImageView.ScaleType} for the end icon's ImageButton. + * @param scaleType {@link android.widget.ImageView.ScaleType} for the end icon's ImageButton. * @attr ref android.support.design.button.R.styleable#TextInputLayout_endIconScaleType * @see #getEndIconScaleType() */ @@ -3624,9 +3711,9 @@ public void setEndIconScaleType(@NonNull ScaleType scaleType) { } /** - * Returns the {@link ImageView.ScaleType} for the end icon's ImageButton. + * Returns the {@link android.widget.ImageView.ScaleType} for the end icon's ImageButton. * - * @return Returns the {@link ImageView.ScaleType} for the end icon's ImageButton. + * @return Returns the {@link android.widget.ImageView.ScaleType} for the end icon's ImageButton. * @attr ref android.support.design.button.R.styleable#TextInputLayout_endIconScaleType * @see #setEndIconScaleType(ScaleType) */ @@ -4230,7 +4317,6 @@ void updateTextInputBoxState() { final boolean hasFocus = isFocused() || (editText != null && editText.hasFocus()); final boolean isHovered = isHovered() || (editText != null && editText.isHovered()); - final boolean isOnError = shouldShowError() || (counterView != null && counterOverflowed); // Update the text box's stroke color based on the current state. if (!isEnabled()) { @@ -4256,7 +4342,7 @@ void updateTextInputBoxState() { } if (VERSION.SDK_INT >= VERSION_CODES.Q) { - updateCursorColor(isOnError); + updateCursorColor(); } endLayout.onTextInputBoxStateUpdated(); @@ -4296,6 +4382,10 @@ void updateTextInputBoxState() { applyBoxAttributes(); } + private boolean isOnError() { + return shouldShowError() || (counterView != null && counterOverflowed); + } + private void updateStrokeErrorColor(boolean hasFocus, boolean isHovered) { int defaultStrokeErrorColor = strokeErrorColor.getDefaultColor(); int hoveredStrokeErrorColor = @@ -4315,23 +4405,22 @@ private void updateStrokeErrorColor(boolean hasFocus, boolean isHovered) { } } - @TargetApi(VERSION_CODES.Q) - private void updateCursorColor(boolean isOnError) { - ColorStateList cursorColor = - MaterialColors.getColorStateListOrNull(getContext(), androidx.appcompat.R.attr.colorControlActivated); - if (editText == null || editText.getTextCursorDrawable() == null || cursorColor == null) { - // If there's no cursor or if its color is null, return. + @RequiresApi(VERSION_CODES.Q) + private void updateCursorColor() { + ColorStateList color = cursorColor != null + ? cursorColor + : MaterialColors.getColorStateListOrNull(getContext(), androidx.appcompat.R.attr.colorControlActivated); + + if (editText == null || editText.getTextCursorDrawable() == null) { + // If there's no cursor, return. return; } Drawable cursorDrawable = editText.getTextCursorDrawable(); - if (isOnError) { - // Use the stroke error color for the cursor error color, or the box stroke color if - // strokeErrorColor is null. - cursorColor = - strokeErrorColor != null ? strokeErrorColor : ColorStateList.valueOf(boxStrokeColor); + if (isOnError() && cursorErrorColor != null) { + color = cursorErrorColor; } - DrawableCompat.setTintList(cursorDrawable, cursorColor); + DrawableCompat.setTintList(cursorDrawable, color); } private void expandHint(boolean animate) { diff --git a/material/java/com/google/android/material/textfield/res-public/values/public.xml b/material/java/com/google/android/material/textfield/res-public/values/public.xml index 9c707a886..2ac7a4ff3 100644 --- a/material/java/com/google/android/material/textfield/res-public/values/public.xml +++ b/material/java/com/google/android/material/textfield/res-public/values/public.xml @@ -84,6 +84,8 @@ + + diff --git a/material/java/com/google/android/material/textfield/res/values/attrs.xml b/material/java/com/google/android/material/textfield/res/values/attrs.xml index 9c59726cc..d17c11218 100644 --- a/material/java/com/google/android/material/textfield/res/values/attrs.xml +++ b/material/java/com/google/android/material/textfield/res/values/attrs.xml @@ -116,6 +116,15 @@ + + + + + diff --git a/material/java/com/google/android/material/textfield/res/values/styles.xml b/material/java/com/google/android/material/textfield/res/values/styles.xml index d677d0d34..25d656490 100644 --- a/material/java/com/google/android/material/textfield/res/values/styles.xml +++ b/material/java/com/google/android/material/textfield/res/values/styles.xml @@ -70,6 +70,9 @@ @color/mtrl_error @dimen/mtrl_textinput_box_stroke_width_default @dimen/mtrl_textinput_box_stroke_width_focused + + @null + @color/mtrl_error ?attr/textAppearanceCaption ?attr/textAppearanceCaption @@ -356,6 +359,9 @@ @macro/m3_comp_outlined_text_field_input_text_type ?attr/textAppearanceTitleMedium ?attr/textAppearanceTitleMedium + + @null + @macro/m3_comp_outlined_text_field_error_outline_color @macro/m3_comp_outlined_text_field_container_shape @null @@ -398,16 +404,18 @@ @color/m3_textfield_indicator_text_color @drawable/m3_password_eye - - @macro/m3_comp_outlined_text_field_supporting_text_type - @macro/m3_comp_outlined_text_field_supporting_text_type - @macro/m3_comp_outlined_text_field_supporting_text_type - @macro/m3_comp_outlined_text_field_supporting_text_type - @macro/m3_comp_outlined_text_field_supporting_text_type + @macro/m3_comp_filled_text_field_supporting_text_type + @macro/m3_comp_filled_text_field_supporting_text_type + @macro/m3_comp_filled_text_field_supporting_text_type + @macro/m3_comp_filled_text_field_supporting_text_type + @macro/m3_comp_filled_text_field_supporting_text_type @macro/m3_comp_filled_text_field_input_text_type ?attr/textAppearanceTitleMedium ?attr/textAppearanceTitleMedium + + @null + @macro/m3_comp_filled_text_field_error_active_indicator_color @macro/m3_comp_filled_text_field_container_shape @style/ShapeAppearanceOverlay.Material3.Corner.Top diff --git a/material/java/com/google/android/material/textfield/res/values/tokens_dropdown_menu.xml b/material/java/com/google/android/material/textfield/res/values/tokens_dropdown_menu.xml index 6e5d51cd9..84ead687a 100644 --- a/material/java/com/google/android/material/textfield/res/values/tokens_dropdown_menu.xml +++ b/material/java/com/google/android/material/textfield/res/values/tokens_dropdown_menu.xml @@ -15,24 +15,24 @@ ~ limitations under the License. --> - + - + + @dimen/m3_sys_elevation_level2 - ?attr/colorSurfaceVariant ?attr/textAppearanceBodyLarge - + @dimen/m3_sys_elevation_level2 - ?attr/colorSurfaceVariant ?attr/textAppearanceBodyLarge ?attr/colorPrimary + diff --git a/material/java/com/google/android/material/textfield/res/values/tokens_textfield.xml b/material/java/com/google/android/material/textfield/res/values/tokens_textfield.xml index 86be16b16..0ddab52bb 100644 --- a/material/java/com/google/android/material/textfield/res/values/tokens_textfield.xml +++ b/material/java/com/google/android/material/textfield/res/values/tokens_textfield.xml @@ -15,14 +15,18 @@ ~ limitations under the License. --> - + - + + ?attr/colorSurfaceVariant - + ?attr/shapeAppearanceCornerExtraSmall + + ?attr/textAppearanceBodySmall ?attr/textAppearanceBodyLarge @@ -32,7 +36,7 @@ ?attr/colorError ?attr/colorError - + ?attr/shapeAppearanceCornerExtraSmall @@ -78,4 +82,5 @@ ?attr/colorError ?attr/colorError ?attr/colorError +