1 /*
2 * Copyright 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package androidx.glance
18
19 import android.graphics.Bitmap
20 import android.graphics.drawable.Icon
21 import android.os.Build
22 import androidx.annotation.DrawableRes
23 import androidx.annotation.FloatRange
24 import androidx.annotation.RequiresApi
25 import androidx.annotation.RestrictTo
26 import androidx.compose.runtime.Composable
27 import androidx.glance.layout.ContentScale
28 import androidx.glance.semantics.SemanticsModifier
29 import androidx.glance.semantics.SemanticsProperties
30 import androidx.glance.semantics.contentDescription
31 import androidx.glance.semantics.semantics
32 import androidx.glance.unit.ColorProvider
33
34 /** Interface representing an Image source which can be used with a Glance [Image] element. */
35 interface ImageProvider
36
37 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
38 class AndroidResourceImageProvider(@DrawableRes val resId: Int) : ImageProvider {
toStringnull39 override fun toString() = "AndroidResourceImageProvider(resId=$resId)"
40 }
41
42 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
43 class BitmapImageProvider(val bitmap: Bitmap) : ImageProvider {
44 override fun toString() =
45 "BitmapImageProvider(bitmap=Bitmap(${bitmap.width}px x ${bitmap.height}px))"
46 }
47
48 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
49 class IconImageProvider(val icon: Icon) : ImageProvider {
toStringnull50 override fun toString() = "IconImageProvider(icon=$icon)"
51 }
52
53 /**
54 * Image resource from an Android Drawable resource.
55 *
56 * @param resId The resource ID of the Drawable resource to be used.
57 */
58 fun ImageProvider(@DrawableRes resId: Int): ImageProvider = AndroidResourceImageProvider(resId)
59
60 /**
61 * Image resource from a bitmap.
62 *
63 * @param bitmap The bitmap to be displayed.
64 */
65 fun ImageProvider(bitmap: Bitmap): ImageProvider = BitmapImageProvider(bitmap)
66
67 /**
68 * Image resource from an icon.
69 *
70 * @param icon The icon to be displayed.
71 */
72 @RequiresApi(Build.VERSION_CODES.M)
73 fun ImageProvider(icon: Icon): ImageProvider = IconImageProvider(icon)
74
75 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) interface ColorFilterParams
76
77 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
78 class TintColorFilterParams(val colorProvider: ColorProvider) : ColorFilterParams {
79 override fun toString() = "TintColorFilterParams(colorProvider=$colorProvider))"
80 }
81
82 /** Effects used to modify the color of an image. */
83 class ColorFilter
84 internal constructor(
85 @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) val colorFilterParams: ColorFilterParams
86 ) {
87 companion object {
88 /**
89 * Set a tinting option for the image using the platform-specific default blending mode.
90 *
91 * @param colorProvider Provider used to get the color for blending the source content.
92 */
tintnull93 fun tint(colorProvider: ColorProvider): ColorFilter =
94 ColorFilter(TintColorFilterParams(colorProvider))
95 }
96 }
97
98 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
99 class EmittableImage : Emittable {
100 override var modifier: GlanceModifier = GlanceModifier
101 var provider: ImageProvider? = null
102 var colorFilterParams: ColorFilterParams? = null
103 var alpha: Float? = null // null retains the source image's alpha
104 var contentScale: ContentScale = ContentScale.Fit
105
106 override fun copy(): Emittable =
107 EmittableImage().also {
108 it.modifier = modifier
109 it.provider = provider
110 it.colorFilterParams = colorFilterParams
111 it.alpha = alpha
112 it.contentScale = contentScale
113 }
114
115 override fun toString(): String =
116 "EmittableImage(" +
117 "modifier=$modifier, " +
118 "provider=$provider, " +
119 "colorFilterParams=$colorFilterParams, " +
120 "alpha=$alpha, " +
121 "contentScale=$contentScale" +
122 ")"
123 }
124
125 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
isDecorativenull126 fun EmittableImage.isDecorative(): Boolean {
127 val semanticsConfiguration = modifier.findModifier<SemanticsModifier>()?.configuration
128 return semanticsConfiguration
129 ?.getOrNull(SemanticsProperties.ContentDescription)
130 ?.get(0)
131 .isNullOrEmpty()
132 }
133
134 /**
135 * A composable which lays out and draws the image specified in [provider]. This will attempt to lay
136 * out the image using the intrinsic width and height of the provided image, but this can be
137 * overridden by using a modifier to set the width or height of this element.
138 *
139 * @param provider The image provider to use to draw the image
140 * @param contentDescription text used by accessibility services to describe what this image
141 * represents. This should always be provided unless this image is used for decorative purposes,
142 * and does not represent a meaningful action that a user can take. This text should be localized.
143 * @param modifier Modifier used to adjust the layout algorithm or draw decoration content.
144 * @param contentScale How to lay the image out with respect to its bounds, if the bounds are
145 * smaller than the image.
146 * @param colorFilter The effects to use to modify the color of an image.
147 */
148 @Composable
Imagenull149 fun Image(
150 provider: ImageProvider,
151 contentDescription: String?,
152 modifier: GlanceModifier = GlanceModifier,
153 contentScale: ContentScale = ContentScale.Fit,
154 colorFilter: ColorFilter? = null,
155 ) = ImageElement(provider, contentDescription, modifier, contentScale, colorFilter, alpha = null)
156
157 /**
158 * A composable which lays out and draws the image specified in [provider]. This will attempt to lay
159 * out the image using the intrinsic width and height of the provided image, but this can be
160 * overridden by using a modifier to set the width or height of this element.
161 *
162 * @param provider The image provider to use to draw the image
163 * @param contentDescription text used by accessibility services to describe what this image
164 * represents. This should always be provided unless this image is used for decorative purposes,
165 * and does not represent a meaningful action that a user can take. This text should be localized.
166 * @param alpha Opacity (0f to 1f) to apply to the image.
167 * @param modifier Modifier used to adjust the layout algorithm or draw decoration content.
168 * @param contentScale How to lay the image out with respect to its bounds, if the bounds are
169 * smaller than the image.
170 * @param colorFilter The effects to use to modify the color of an image.
171 */
172 @Composable
173 fun Image(
174 provider: ImageProvider,
175 contentDescription: String?,
176 @FloatRange(from = 0.0, to = 1.0) alpha: Float,
177 modifier: GlanceModifier = GlanceModifier,
178 contentScale: ContentScale = ContentScale.Fit,
179 colorFilter: ColorFilter? = null,
180 ) = ImageElement(provider, contentDescription, modifier, contentScale, colorFilter, alpha)
181
182 @Composable
183 internal fun ImageElement(
184 provider: ImageProvider,
185 contentDescription: String?,
186 modifier: GlanceModifier = GlanceModifier,
187 contentScale: ContentScale = ContentScale.Fit,
188 colorFilter: ColorFilter? = null,
189 alpha: Float? = null,
190 ) {
191 val finalModifier =
192 if (contentDescription != null) {
193 modifier.semantics { this.contentDescription = contentDescription }
194 } else {
195 modifier
196 }
197
198 GlanceNode(
199 factory = ::EmittableImage,
200 update = {
201 this.set(provider) { this.provider = it }
202 this.set(finalModifier) { this.modifier = it }
203 this.set(contentScale) { this.contentScale = it }
204 this.set(colorFilter) { this.colorFilterParams = it?.colorFilterParams }
205 this.set(alpha) { this.alpha = it }
206 }
207 )
208 }
209