react-native-gradient-mask 0.0.1-beta.1 → 0.0.1-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.ja.md CHANGED
@@ -324,6 +324,30 @@ maskOpacity.value = withTiming(newValue, { duration: 300 });
324
324
 
325
325
  ---
326
326
 
327
+ ## Anini のために開発
328
+
329
+ このライブラリは [**Anini**](https://play.google.com/store/apps/details?id=com.gazai.aichat) のために開発されました — スムーズでネイティブ品質のユーザー体験を提供する AI チャットコンパニオンアプリです。
330
+
331
+ <p align="center">
332
+ <a href="https://play.google.com/store/apps/details?id=com.gazai.aichat">
333
+ <img src="https://img.shields.io/badge/Google_Play-Anini-414141?style=for-the-badge&logo=google-play&logoColor=white" alt="Google Play で Anini をダウンロード" />
334
+ </a>
335
+ </p>
336
+
337
+ ---
338
+
339
+ ## スポンサー
340
+
341
+ このプロジェクトは [**GAZAI**](https://gazai.io/EN/services) がスポンサーです — 革新的な AI 駆動アプリケーションを構築しています。
342
+
343
+ <p align="center">
344
+ <a href="https://github.com/sponsors/CS6">
345
+ <img src="https://img.shields.io/badge/スポンサー-GitHub_Sponsors-ea4aaa?style=for-the-badge&logo=github-sponsors&logoColor=white" alt="GitHub でスポンサーになる" />
346
+ </a>
347
+ </p>
348
+
349
+ ---
350
+
327
351
  ## ライセンス
328
352
 
329
353
  MIT © [DaYuan Lin (CS6)](https://github.com/CS6)
package/README.md CHANGED
@@ -324,6 +324,30 @@ maskOpacity.value = withTiming(newValue, { duration: 300 });
324
324
 
325
325
  ---
326
326
 
327
+ ## Built for Anini
328
+
329
+ This library was originally developed for [**Anini**](https://play.google.com/store/apps/details?id=com.gazai.aichat) - an AI chat companion app that delivers smooth, native-quality user experiences.
330
+
331
+ <p align="center">
332
+ <a href="https://play.google.com/store/apps/details?id=com.gazai.aichat">
333
+ <img src="https://img.shields.io/badge/Google_Play-Anini-414141?style=for-the-badge&logo=google-play&logoColor=white" alt="Get Anini on Google Play" />
334
+ </a>
335
+ </p>
336
+
337
+ ---
338
+
339
+ ## Sponsor
340
+
341
+ This project is sponsored by [**GAZAI**](https://gazai.io/EN/services) - Building innovative AI-powered applications.
342
+
343
+ <p align="center">
344
+ <a href="https://github.com/sponsors/CS6">
345
+ <img src="https://img.shields.io/badge/Sponsor-GitHub_Sponsors-ea4aaa?style=for-the-badge&logo=github-sponsors&logoColor=white" alt="Sponsor on GitHub" />
346
+ </a>
347
+ </p>
348
+
349
+ ---
350
+
327
351
  ## License
328
352
 
329
353
  MIT © [DaYuan Lin (CS6)](https://github.com/CS6)
package/README.zh-TW.md CHANGED
@@ -324,6 +324,30 @@ maskOpacity.value = withTiming(newValue, { duration: 300 });
324
324
 
325
325
  ---
326
326
 
327
+ ## 為 Anini 打造
328
+
329
+ 這個套件最初是為 [**Anini**](https://play.google.com/store/apps/details?id=com.gazai.aichat) 開發的 — 一款提供流暢、原生品質使用者體驗的 AI 聊天夥伴應用程式。
330
+
331
+ <p align="center">
332
+ <a href="https://play.google.com/store/apps/details?id=com.gazai.aichat">
333
+ <img src="https://img.shields.io/badge/Google_Play-Anini-414141?style=for-the-badge&logo=google-play&logoColor=white" alt="在 Google Play 下載 Anini" />
334
+ </a>
335
+ </p>
336
+
337
+ ---
338
+
339
+ ## 贊助
340
+
341
+ 本專案由 [**GAZAI**](https://gazai.io/EN/services) 贊助 — 打造創新的 AI 驅動應用程式。
342
+
343
+ <p align="center">
344
+ <a href="https://github.com/sponsors/CS6">
345
+ <img src="https://img.shields.io/badge/贊助-GitHub_Sponsors-ea4aaa?style=for-the-badge&logo=github-sponsors&logoColor=white" alt="在 GitHub 贊助" />
346
+ </a>
347
+ </p>
348
+
349
+ ---
350
+
327
351
  ## 授權
328
352
 
329
353
  MIT © [DaYuan Lin (CS6)](https://github.com/CS6)
@@ -19,6 +19,10 @@ import expo.modules.kotlin.views.ExpoView
19
19
  * maskOpacity 控制漸層 mask 效果的顯示程度:
20
20
  * - maskOpacity = 0 → 無漸層效果,內容完全可見(全部 alpha=255)
21
21
  * - maskOpacity = 1 → 完整漸層效果(使用原始 alpha)
22
+ *
23
+ * 效能優化:
24
+ * - 基礎漸層 bitmap (baseMaskBitmap) 只在 colors/locations/direction/size 變化時重建
25
+ * - maskOpacity 變化時只使用 ColorMatrix 調整 alpha,不重建 bitmap
22
26
  */
23
27
  class GradientMaskView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
24
28
 
@@ -30,12 +34,19 @@ class GradientMaskView(context: Context, appContext: AppContext) : ExpoView(cont
30
34
  // maskOpacity: 0 = 無漸層效果, 1 = 完整漸層效果
31
35
  private var maskOpacity: Float = 1f
32
36
 
33
- // Mask bitmap 和相關的 Paint
34
- private var maskBitmap: Bitmap? = null
35
- private var maskBitmapInvalidated = true
37
+ // 基礎漸層 bitmap(完整漸層效果,maskOpacity=1 時使用的原始漸層)
38
+ private var baseMaskBitmap: Bitmap? = null
39
+ // 是否需要重建基礎 bitmap(只有 colors/locations/direction/size 變化時才需要)
40
+ private var baseBitmapInvalidated = true
41
+
42
+ // 繪製用的 Paint
36
43
  private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
37
44
  private val porterDuffXferMode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)
38
45
 
46
+ // 用於調整 mask alpha 的 ColorMatrix
47
+ private val colorMatrix = ColorMatrix()
48
+ private val colorMatrixFilter = ColorMatrixColorFilter(colorMatrix)
49
+
39
50
  init {
40
51
  // 確保背景是透明的
41
52
  setBackgroundColor(Color.TRANSPARENT)
@@ -49,26 +60,30 @@ class GradientMaskView(context: Context, appContext: AppContext) : ExpoView(cont
49
60
 
50
61
  fun setColors(colorArray: List<Int>?) {
51
62
  colors = colorArray?.toIntArray()
52
- maskBitmapInvalidated = true
63
+ baseBitmapInvalidated = true
53
64
  invalidate()
54
65
  }
55
66
 
56
67
  fun setLocations(locationArray: List<Double>?) {
57
68
  locations = locationArray?.map { it.toFloat() }?.toFloatArray()
58
- maskBitmapInvalidated = true
69
+ baseBitmapInvalidated = true
59
70
  invalidate()
60
71
  }
61
72
 
62
73
  fun setDirection(dir: String) {
63
74
  direction = dir
64
- maskBitmapInvalidated = true
75
+ baseBitmapInvalidated = true
65
76
  invalidate()
66
77
  }
67
78
 
68
79
  fun setMaskOpacity(opacity: Double) {
69
- maskOpacity = opacity.toFloat().coerceIn(0f, 1f)
70
- maskBitmapInvalidated = true
71
- invalidate()
80
+ val newOpacity = opacity.toFloat().coerceIn(0f, 1f)
81
+ if (newOpacity != maskOpacity) {
82
+ maskOpacity = newOpacity
83
+ // 只需要 invalidate,不需要重建 bitmap
84
+ // dispatchDraw 時會使用 ColorMatrix 來調整 alpha
85
+ invalidate()
86
+ }
72
87
  }
73
88
 
74
89
  // MARK: - Layout
@@ -76,15 +91,15 @@ class GradientMaskView(context: Context, appContext: AppContext) : ExpoView(cont
76
91
  override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
77
92
  super.onSizeChanged(w, h, oldw, oldh)
78
93
  if (w > 0 && h > 0) {
79
- updateMaskBitmap()
80
- maskBitmapInvalidated = false
94
+ updateBaseMaskBitmap()
95
+ baseBitmapInvalidated = false
81
96
  }
82
97
  }
83
98
 
84
99
  override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
85
100
  super.onLayout(changed, l, t, r, b)
86
101
  if (changed) {
87
- maskBitmapInvalidated = true
102
+ baseBitmapInvalidated = true
88
103
  }
89
104
  }
90
105
 
@@ -97,13 +112,13 @@ class GradientMaskView(context: Context, appContext: AppContext) : ExpoView(cont
97
112
  return
98
113
  }
99
114
 
100
- // 如果 mask bitmap 需要更新,重新創建
101
- if (maskBitmapInvalidated) {
102
- updateMaskBitmap()
103
- maskBitmapInvalidated = false
115
+ // 如果基礎 bitmap 需要更新,重新創建
116
+ if (baseBitmapInvalidated) {
117
+ updateBaseMaskBitmap()
118
+ baseBitmapInvalidated = false
104
119
  }
105
120
 
106
- val bitmap = maskBitmap
121
+ val bitmap = baseMaskBitmap
107
122
  // 如果沒有 mask bitmap 或 maskOpacity=0,直接繪製子元件(無 mask 效果)
108
123
  if (bitmap == null || maskOpacity <= 0f) {
109
124
  super.dispatchDraw(canvas)
@@ -122,19 +137,48 @@ class GradientMaskView(context: Context, appContext: AppContext) : ExpoView(cont
122
137
  super.dispatchDraw(canvas)
123
138
 
124
139
  // 應用 mask(使用 DST_IN 模式)
140
+ // 使用 ColorMatrix 來調整 alpha,實現 maskOpacity 效果
141
+ // 這樣就不需要每次 maskOpacity 變化都重建 bitmap
125
142
  paint.xfermode = porterDuffXferMode
143
+ paint.colorFilter = if (maskOpacity < 1f) {
144
+ // 使用 ColorMatrix 來混合原始 alpha 和完全不透明
145
+ // maskOpacity = 0 → 所有像素的 alpha 變為 255(完全可見)
146
+ // maskOpacity = 1 → 使用原始 alpha
147
+ //
148
+ // ColorMatrix 的 alpha 行:[0, 0, 0, scale, translate]
149
+ // 結果 alpha = originalAlpha * scale + translate
150
+ //
151
+ // 我們想要:resultAlpha = 255 + (originalAlpha - 255) * maskOpacity
152
+ // = 255 * (1 - maskOpacity) + originalAlpha * maskOpacity
153
+ // 所以:scale = maskOpacity, translate = 255 * (1 - maskOpacity)
154
+ colorMatrix.set(floatArrayOf(
155
+ 1f, 0f, 0f, 0f, 0f, // R
156
+ 0f, 1f, 0f, 0f, 0f, // G
157
+ 0f, 0f, 1f, 0f, 0f, // B
158
+ 0f, 0f, 0f, maskOpacity, 255f * (1f - maskOpacity) // A
159
+ ))
160
+ ColorMatrixColorFilter(colorMatrix)
161
+ } else {
162
+ null
163
+ }
126
164
  canvas.drawBitmap(bitmap, 0f, 0f, paint)
127
165
  paint.xfermode = null
166
+ paint.colorFilter = null
128
167
  } finally {
129
168
  canvas.restoreToCount(saveCount)
130
169
  }
131
170
  }
132
171
 
133
- private fun updateMaskBitmap() {
172
+ /**
173
+ * 更新基礎漸層 bitmap
174
+ * 這個 bitmap 包含原始漸層效果(maskOpacity = 1 時的效果)
175
+ * maskOpacity 的調整在 dispatchDraw 時透過 ColorMatrix 實現
176
+ */
177
+ private fun updateBaseMaskBitmap() {
134
178
  if (width <= 0 || height <= 0) return
135
179
 
136
180
  // 回收舊的 bitmap
137
- maskBitmap?.recycle()
181
+ baseMaskBitmap?.recycle()
138
182
 
139
183
  // 創建新的 mask bitmap
140
184
  val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
@@ -148,25 +192,22 @@ class GradientMaskView(context: Context, appContext: AppContext) : ExpoView(cont
148
192
  currentColors.size != currentLocations.size ||
149
193
  currentColors.isEmpty()) {
150
194
  bitmapCanvas.drawColor(Color.WHITE)
151
- maskBitmap = bitmap
195
+ baseMaskBitmap = bitmap
152
196
  return
153
197
  }
154
198
 
155
- // 計算 effective colors(根據 maskOpacity 混合)
156
- val effectiveColors = IntArray(currentColors.size) { i ->
199
+ // 轉換顏色為白色 + 原始 alpha(mask 只需要 alpha 通道)
200
+ val maskColors = IntArray(currentColors.size) { i ->
157
201
  val originalColor = currentColors[i]
158
202
  val originalAlpha = Color.alpha(originalColor)
159
- // maskOpacity = 0,alpha = 255(完全不透明,內容完全可見)
160
- // 當 maskOpacity = 1,alpha = originalAlpha
161
- val blendedAlpha = (255 + (originalAlpha - 255) * maskOpacity).toInt()
162
- Color.argb(blendedAlpha, 255, 255, 255)
203
+ Color.argb(originalAlpha, 255, 255, 255)
163
204
  }
164
205
 
165
206
  // 建立 gradient shader
166
207
  val (startX, startY, endX, endY) = getGradientCoordinates()
167
208
  val shader = LinearGradient(
168
209
  startX, startY, endX, endY,
169
- effectiveColors,
210
+ maskColors,
170
211
  currentLocations,
171
212
  Shader.TileMode.CLAMP
172
213
  )
@@ -177,13 +218,13 @@ class GradientMaskView(context: Context, appContext: AppContext) : ExpoView(cont
177
218
  }
178
219
  bitmapCanvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), gradientPaint)
179
220
 
180
- maskBitmap = bitmap
221
+ baseMaskBitmap = bitmap
181
222
  }
182
223
 
183
224
  override fun onDetachedFromWindow() {
184
225
  super.onDetachedFromWindow()
185
- maskBitmap?.recycle()
186
- maskBitmap = null
226
+ baseMaskBitmap?.recycle()
227
+ baseMaskBitmap = null
187
228
  }
188
229
 
189
230
  private fun getGradientCoordinates(): List<Float> {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-gradient-mask",
3
- "version": "0.0.1-beta.1",
3
+ "version": "0.0.1-beta.2",
4
4
  "description": "A native gradient mask component for React Native with Reanimated animation support. Supports iOS, Android, and Web platforms.",
5
5
  "main": "build/index.js",
6
6
  "module": "build/index.js",