1 /*
<lambda>null2  * Copyright 2019 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.compose.ui.graphics
18 
19 import androidx.compose.runtime.Immutable
20 import androidx.compose.runtime.Stable
21 import androidx.compose.ui.geometry.Offset
22 import androidx.compose.ui.geometry.Size
23 import androidx.compose.ui.geometry.center
24 import androidx.compose.ui.geometry.isFinite
25 import androidx.compose.ui.geometry.isSpecified
26 import androidx.compose.ui.geometry.isUnspecified
27 import androidx.compose.ui.util.fastIsFinite
28 import kotlin.math.abs
29 
30 @Immutable
31 sealed class Brush {
32 
33     /**
34      * Return the intrinsic size of the [Brush]. If the there is no intrinsic size (i.e. filling
35      * bounds with an arbitrary color) return [Size.Unspecified]. If there is no intrinsic size in a
36      * single dimension, return [Size] with [Float.NaN] in the desired dimension.
37      */
38     open val intrinsicSize: Size = Size.Unspecified
39 
40     abstract fun applyTo(size: Size, p: Paint, alpha: Float)
41 
42     companion object {
43 
44         /**
45          * Creates a linear gradient with the provided colors along the given start and end
46          * coordinates. The colors are dispersed at the provided offset defined in the colorstop
47          * pair.
48          *
49          * ```
50          *  Brush.linearGradient(
51          *      0.0f to Color.Red,
52          *      0.3f to Color.Green,
53          *      1.0f to Color.Blue,
54          *      start = Offset(0.0f, 50.0f),
55          *      end = Offset(0.0f, 100.0f)
56          * )
57          * ```
58          *
59          * @sample androidx.compose.ui.graphics.samples.LinearGradientColorStopSample
60          * @sample androidx.compose.ui.graphics.samples.GradientBrushSample
61          * @param colorStops Colors and their offset in the gradient area
62          * @param start Starting position of the linear gradient. This can be set to [Offset.Zero]
63          *   to position at the far left and top of the drawing area
64          * @param end Ending position of the linear gradient. This can be set to [Offset.Infinite]
65          *   to position at the far right and bottom of the drawing area
66          * @param tileMode Determines the behavior for how the shader is to fill a region outside
67          *   its bounds. Defaults to [TileMode.Clamp] to repeat the edge pixels
68          */
69         @Stable
70         fun linearGradient(
71             vararg colorStops: Pair<Float, Color>,
72             start: Offset = Offset.Zero,
73             end: Offset = Offset.Infinite,
74             tileMode: TileMode = TileMode.Clamp
75         ): Brush =
76             LinearGradient(
77                 colors = List<Color>(colorStops.size) { i -> colorStops[i].second },
78                 stops = List<Float>(colorStops.size) { i -> colorStops[i].first },
79                 start = start,
80                 end = end,
81                 tileMode = tileMode
82             )
83 
84         /**
85          * Creates a linear gradient with the provided colors along the given start and end
86          * coordinates. The colors are
87          *
88          * ```
89          *  Brush.linearGradient(
90          *      listOf(Color.Red, Color.Green, Color.Blue),
91          *      start = Offset(0.0f, 50.0f),
92          *      end = Offset(0.0f, 100.0f)
93          * )
94          * ```
95          *
96          * @sample androidx.compose.ui.graphics.samples.LinearGradientSample
97          * @sample androidx.compose.ui.graphics.samples.GradientBrushSample
98          * @param colors Colors to be rendered as part of the gradient
99          * @param start Starting position of the linear gradient. This can be set to [Offset.Zero]
100          *   to position at the far left and top of the drawing area
101          * @param end Ending position of the linear gradient. This can be set to [Offset.Infinite]
102          *   to position at the far right and bottom of the drawing area
103          * @param tileMode Determines the behavior for how the shader is to fill a region outside
104          *   its bounds. Defaults to [TileMode.Clamp] to repeat the edge pixels
105          */
106         @Stable
107         fun linearGradient(
108             colors: List<Color>,
109             start: Offset = Offset.Zero,
110             end: Offset = Offset.Infinite,
111             tileMode: TileMode = TileMode.Clamp
112         ): Brush =
113             LinearGradient(
114                 colors = colors,
115                 stops = null,
116                 start = start,
117                 end = end,
118                 tileMode = tileMode
119             )
120 
121         /**
122          * Creates a horizontal gradient with the given colors evenly dispersed within the gradient
123          *
124          * Ex:
125          * ```
126          *  Brush.horizontalGradient(
127          *      listOf(Color.Red, Color.Green, Color.Blue),
128          *      startX = 10.0f,
129          *      endX = 20.0f
130          * )
131          * ```
132          *
133          * @sample androidx.compose.ui.graphics.samples.HorizontalGradientSample
134          * @sample androidx.compose.ui.graphics.samples.GradientBrushSample
135          * @param colors colors Colors to be rendered as part of the gradient
136          * @param startX Starting x position of the horizontal gradient. Defaults to 0 which
137          *   represents the left of the drawing area
138          * @param endX Ending x position of the horizontal gradient. Defaults to
139          *   [Float.POSITIVE_INFINITY] which indicates the right of the specified drawing area
140          * @param tileMode Determines the behavior for how the shader is to fill a region outside
141          *   its bounds. Defaults to [TileMode.Clamp] to repeat the edge pixels
142          */
143         @Stable
144         fun horizontalGradient(
145             colors: List<Color>,
146             startX: Float = 0.0f,
147             endX: Float = Float.POSITIVE_INFINITY,
148             tileMode: TileMode = TileMode.Clamp
149         ): Brush = linearGradient(colors, Offset(startX, 0.0f), Offset(endX, 0.0f), tileMode)
150 
151         /**
152          * Creates a horizontal gradient with the given colors dispersed at the provided offset
153          * defined in the colorstop pair.
154          *
155          * Ex:
156          * ```
157          *  Brush.horizontalGradient(
158          *      0.0f to Color.Red,
159          *      0.3f to Color.Green,
160          *      1.0f to Color.Blue,
161          *      startX = 0.0f,
162          *      endX = 100.0f
163          * )
164          * ```
165          *
166          * @sample androidx.compose.ui.graphics.samples.HorizontalGradientColorStopSample
167          * @sample androidx.compose.ui.graphics.samples.GradientBrushSample
168          * @param colorStops Colors and offsets to determine how the colors are dispersed throughout
169          *   the vertical gradient
170          * @param startX Starting x position of the horizontal gradient. Defaults to 0 which
171          *   represents the left of the drawing area
172          * @param endX Ending x position of the horizontal gradient. Defaults to
173          *   [Float.POSITIVE_INFINITY] which indicates the right of the specified drawing area
174          * @param tileMode Determines the behavior for how the shader is to fill a region outside
175          *   its bounds. Defaults to [TileMode.Clamp] to repeat the edge pixels
176          */
177         @Stable
178         fun horizontalGradient(
179             vararg colorStops: Pair<Float, Color>,
180             startX: Float = 0.0f,
181             endX: Float = Float.POSITIVE_INFINITY,
182             tileMode: TileMode = TileMode.Clamp
183         ): Brush =
184             linearGradient(
185                 *colorStops,
186                 start = Offset(startX, 0.0f),
187                 end = Offset(endX, 0.0f),
188                 tileMode = tileMode
189             )
190 
191         /**
192          * Creates a vertical gradient with the given colors evenly dispersed within the gradient
193          * Ex:
194          * ```
195          *  Brush.verticalGradient(
196          *      listOf(Color.Red, Color.Green, Color.Blue),
197          *      startY = 0.0f,
198          *      endY = 100.0f
199          * )
200          * ```
201          *
202          * @sample androidx.compose.ui.graphics.samples.VerticalGradientSample
203          * @sample androidx.compose.ui.graphics.samples.GradientBrushSample
204          * @param colors colors Colors to be rendered as part of the gradient
205          * @param startY Starting y position of the vertical gradient. Defaults to 0 which
206          *   represents the top of the drawing area
207          * @param endY Ending y position of the vertical gradient. Defaults to
208          *   [Float.POSITIVE_INFINITY] which indicates the bottom of the specified drawing area
209          * @param tileMode Determines the behavior for how the shader is to fill a region outside
210          *   its bounds. Defaults to [TileMode.Clamp] to repeat the edge pixels
211          */
212         @Stable
213         fun verticalGradient(
214             colors: List<Color>,
215             startY: Float = 0.0f,
216             endY: Float = Float.POSITIVE_INFINITY,
217             tileMode: TileMode = TileMode.Clamp
218         ): Brush = linearGradient(colors, Offset(0.0f, startY), Offset(0.0f, endY), tileMode)
219 
220         /**
221          * Creates a vertical gradient with the given colors at the provided offset defined in the
222          * [Pair<Float, Color>]
223          *
224          * Ex:
225          * ```
226          *  Brush.verticalGradient(
227          *      0.1f to Color.Red,
228          *      0.3f to Color.Green,
229          *      0.5f to Color.Blue,
230          *      startY = 0.0f,
231          *      endY = 100.0f
232          * )
233          * ```
234          *
235          * @sample androidx.compose.ui.graphics.samples.VerticalGradientColorStopSample
236          * @sample androidx.compose.ui.graphics.samples.GradientBrushSample
237          * @param colorStops Colors and offsets to determine how the colors are dispersed throughout
238          *   the vertical gradient
239          * @param startY Starting y position of the vertical gradient. Defaults to 0 which
240          *   represents the top of the drawing area
241          * @param endY Ending y position of the vertical gradient. Defaults to
242          *   [Float.POSITIVE_INFINITY] which indicates the bottom of the specified drawing area
243          * @param tileMode Determines the behavior for how the shader is to fill a region outside
244          *   its bounds. Defaults to [TileMode.Clamp] to repeat the edge pixels
245          */
246         @Stable
247         fun verticalGradient(
248             vararg colorStops: Pair<Float, Color>,
249             startY: Float = 0f,
250             endY: Float = Float.POSITIVE_INFINITY,
251             tileMode: TileMode = TileMode.Clamp
252         ): Brush =
253             linearGradient(
254                 *colorStops,
255                 start = Offset(0.0f, startY),
256                 end = Offset(0.0f, endY),
257                 tileMode = tileMode
258             )
259 
260         /**
261          * Creates a radial gradient with the given colors at the provided offset defined in the
262          * colorstop pair.
263          *
264          * ```
265          * Brush.radialGradient(
266          *      0.0f to Color.Red,
267          *      0.3f to Color.Green,
268          *      1.0f to Color.Blue,
269          *      center = Offset(side1 / 2.0f, side2 / 2.0f),
270          *      radius = side1 / 2.0f,
271          *      tileMode = TileMode.Repeated
272          * )
273          * ```
274          *
275          * @sample androidx.compose.ui.graphics.samples.RadialBrushColorStopSample
276          * @sample androidx.compose.ui.graphics.samples.GradientBrushSample
277          * @param colorStops Colors and offsets to determine how the colors are dispersed throughout
278          *   the radial gradient
279          * @param center Center position of the radial gradient circle. If this is set to
280          *   [Offset.Unspecified] then the center of the drawing area is used as the center for the
281          *   radial gradient. [Float.POSITIVE_INFINITY] can be used for either [Offset.x] or
282          *   [Offset.y] to indicate the far right or far bottom of the drawing area respectively.
283          * @param radius Radius for the radial gradient. Defaults to positive infinity to indicate
284          *   the largest radius that can fit within the bounds of the drawing area
285          * @param tileMode Determines the behavior for how the shader is to fill a region outside
286          *   its bounds. Defaults to [TileMode.Clamp] to repeat the edge pixels
287          */
288         @Stable
289         fun radialGradient(
290             vararg colorStops: Pair<Float, Color>,
291             center: Offset = Offset.Unspecified,
292             radius: Float = Float.POSITIVE_INFINITY,
293             tileMode: TileMode = TileMode.Clamp
294         ): Brush =
295             RadialGradient(
296                 colors = List<Color>(colorStops.size) { i -> colorStops[i].second },
297                 stops = List<Float>(colorStops.size) { i -> colorStops[i].first },
298                 center = center,
299                 radius = radius,
300                 tileMode = tileMode
301             )
302 
303         /**
304          * Creates a radial gradient with the given colors evenly dispersed within the gradient
305          *
306          * ```
307          * Brush.radialGradient(
308          *      listOf(Color.Red, Color.Green, Color.Blue),
309          *      center = Offset(side1 / 2.0f, side2 / 2.0f),
310          *      radius = side1 / 2.0f,
311          *      tileMode = TileMode.Repeated
312          * )
313          * ```
314          *
315          * @sample androidx.compose.ui.graphics.samples.RadialBrushSample
316          * @sample androidx.compose.ui.graphics.samples.GradientBrushSample
317          * @param colors Colors to be rendered as part of the gradient
318          * @param center Center position of the radial gradient circle. If this is set to
319          *   [Offset.Unspecified] then the center of the drawing area is used as the center for the
320          *   radial gradient. [Float.POSITIVE_INFINITY] can be used for either [Offset.x] or
321          *   [Offset.y] to indicate the far right or far bottom of the drawing area respectively.
322          * @param radius Radius for the radial gradient. Defaults to positive infinity to indicate
323          *   the largest radius that can fit within the bounds of the drawing area
324          * @param tileMode Determines the behavior for how the shader is to fill a region outside
325          *   its bounds. Defaults to [TileMode.Clamp] to repeat the edge pixels
326          */
327         @Stable
328         fun radialGradient(
329             colors: List<Color>,
330             center: Offset = Offset.Unspecified,
331             radius: Float = Float.POSITIVE_INFINITY,
332             tileMode: TileMode = TileMode.Clamp
333         ): Brush =
334             RadialGradient(
335                 colors = colors,
336                 stops = null,
337                 center = center,
338                 radius = radius,
339                 tileMode = tileMode
340             )
341 
342         /**
343          * Creates a sweep gradient with the given colors dispersed around the center with offsets
344          * defined in each colorstop pair. The sweep begins relative to 3 o'clock and continues
345          * clockwise until it reaches the starting position again.
346          *
347          * Ex:
348          * ```
349          *  Brush.sweepGradient(
350          *      0.0f to Color.Red,
351          *      0.3f to Color.Green,
352          *      1.0f to Color.Blue,
353          *      center = Offset(0.0f, 100.0f)
354          * )
355          * ```
356          *
357          * @sample androidx.compose.ui.graphics.samples.SweepGradientColorStopSample
358          * @sample androidx.compose.ui.graphics.samples.GradientBrushSample
359          * @param colorStops Colors and offsets to determine how the colors are dispersed throughout
360          *   the sweep gradient
361          * @param center Center position of the sweep gradient circle. If this is set to
362          *   [Offset.Unspecified] then the center of the drawing area is used as the center for the
363          *   sweep gradient
364          */
365         @Stable
366         fun sweepGradient(
367             vararg colorStops: Pair<Float, Color>,
368             center: Offset = Offset.Unspecified
369         ): Brush =
370             SweepGradient(
371                 colors = List<Color>(colorStops.size) { i -> colorStops[i].second },
372                 stops = List<Float>(colorStops.size) { i -> colorStops[i].first },
373                 center = center
374             )
375 
376         /**
377          * Creates a sweep gradient with the given colors dispersed evenly around the center. The
378          * sweep begins relative to 3 o'clock and continues clockwise until it reaches the starting
379          * position again.
380          *
381          * Ex:
382          * ```
383          *  Brush.sweepGradient(
384          *      listOf(Color.Red, Color.Green, Color.Blue),
385          *      center = Offset(10.0f, 20.0f)
386          * )
387          * ```
388          *
389          * @sample androidx.compose.ui.graphics.samples.SweepGradientSample
390          * @sample androidx.compose.ui.graphics.samples.GradientBrushSample
391          * @param colors List of colors to fill the sweep gradient
392          * @param center Center position of the sweep gradient circle. If this is set to
393          *   [Offset.Unspecified] then the center of the drawing area is used as the center for the
394          *   sweep gradient
395          */
396         @Stable
397         fun sweepGradient(colors: List<Color>, center: Offset = Offset.Unspecified): Brush =
398             SweepGradient(colors = colors, stops = null, center = center)
399     }
400 }
401 
402 @Immutable
403 class SolidColor(val value: Color) : Brush() {
applyTonull404     override fun applyTo(size: Size, p: Paint, alpha: Float) {
405         p.alpha = DefaultAlpha
406         p.color =
407             if (alpha != DefaultAlpha) {
408                 value.copy(alpha = value.alpha * alpha)
409             } else {
410                 value
411             }
412         if (p.shader != null) p.shader = null
413     }
414 
equalsnull415     override fun equals(other: Any?): Boolean {
416         if (this === other) return true
417         if (other !is SolidColor) return false
418         if (value != other.value) return false
419 
420         return true
421     }
422 
hashCodenull423     override fun hashCode(): Int {
424         return value.hashCode()
425     }
426 
toStringnull427     override fun toString(): String {
428         return "SolidColor(value=$value)"
429     }
430 }
431 
432 /** Brush implementation used to apply a linear gradient on a given [Paint] */
433 @Immutable
434 class LinearGradient
435 internal constructor(
436     private val colors: List<Color>,
437     private val stops: List<Float>? = null,
438     private val start: Offset,
439     private val end: Offset,
440     private val tileMode: TileMode = TileMode.Clamp
441 ) : ShaderBrush() {
442 
443     override val intrinsicSize: Size
444         get() =
445             Size(
446                 if (start.x.isFinite() && end.x.isFinite()) abs(start.x - end.x) else Float.NaN,
447                 if (start.y.isFinite() && end.y.isFinite()) abs(start.y - end.y) else Float.NaN
448             )
449 
createShadernull450     override fun createShader(size: Size): Shader {
451         val startX = if (start.x == Float.POSITIVE_INFINITY) size.width else start.x
452         val startY = if (start.y == Float.POSITIVE_INFINITY) size.height else start.y
453         val endX = if (end.x == Float.POSITIVE_INFINITY) size.width else end.x
454         val endY = if (end.y == Float.POSITIVE_INFINITY) size.height else end.y
455         return LinearGradientShader(
456             colors = colors,
457             colorStops = stops,
458             from = Offset(startX, startY),
459             to = Offset(endX, endY),
460             tileMode = tileMode
461         )
462     }
463 
equalsnull464     override fun equals(other: Any?): Boolean {
465         if (this === other) return true
466         if (other !is LinearGradient) return false
467 
468         if (colors != other.colors) return false
469         if (stops != other.stops) return false
470         if (start != other.start) return false
471         if (end != other.end) return false
472         if (tileMode != other.tileMode) return false
473 
474         return true
475     }
476 
hashCodenull477     override fun hashCode(): Int {
478         var result = colors.hashCode()
479         result = 31 * result + (stops?.hashCode() ?: 0)
480         result = 31 * result + start.hashCode()
481         result = 31 * result + end.hashCode()
482         result = 31 * result + tileMode.hashCode()
483         return result
484     }
485 
toStringnull486     override fun toString(): String {
487         val startValue = if (start.isFinite) "start=$start, " else ""
488         val endValue = if (end.isFinite) "end=$end, " else ""
489         return "LinearGradient(colors=$colors, " +
490             "stops=$stops, " +
491             startValue +
492             endValue +
493             "tileMode=$tileMode)"
494     }
495 }
496 
497 /** Brush implementation used to apply a radial gradient on a given [Paint] */
498 @Immutable
499 class RadialGradient
500 internal constructor(
501     private val colors: List<Color>,
502     private val stops: List<Float>? = null,
503     private val center: Offset,
504     private val radius: Float,
505     private val tileMode: TileMode = TileMode.Clamp
506 ) : ShaderBrush() {
507 
508     override val intrinsicSize: Size
509         get() =
510             if (radius.fastIsFinite()) {
511                 Size(radius * 2, radius * 2)
512             } else {
513                 Size.Unspecified
514             }
515 
createShadernull516     override fun createShader(size: Size): Shader {
517         val centerX: Float
518         val centerY: Float
519         if (center.isUnspecified) {
520             val drawCenter = size.center
521             centerX = drawCenter.x
522             centerY = drawCenter.y
523         } else {
524             centerX = if (center.x == Float.POSITIVE_INFINITY) size.width else center.x
525             centerY = if (center.y == Float.POSITIVE_INFINITY) size.height else center.y
526         }
527 
528         return RadialGradientShader(
529             colors = colors,
530             colorStops = stops,
531             center = Offset(centerX, centerY),
532             radius = if (radius == Float.POSITIVE_INFINITY) size.minDimension / 2 else radius,
533             tileMode = tileMode
534         )
535     }
536 
equalsnull537     override fun equals(other: Any?): Boolean {
538         if (this === other) return true
539         if (other !is RadialGradient) return false
540 
541         if (colors != other.colors) return false
542         if (stops != other.stops) return false
543         if (center != other.center) return false
544         if (radius != other.radius) return false
545         if (tileMode != other.tileMode) return false
546 
547         return true
548     }
549 
hashCodenull550     override fun hashCode(): Int {
551         var result = colors.hashCode()
552         result = 31 * result + (stops?.hashCode() ?: 0)
553         result = 31 * result + center.hashCode()
554         result = 31 * result + radius.hashCode()
555         result = 31 * result + tileMode.hashCode()
556         return result
557     }
558 
toStringnull559     override fun toString(): String {
560         val centerValue = if (center.isSpecified) "center=$center, " else ""
561         val radiusValue = if (radius.fastIsFinite()) "radius=$radius, " else ""
562         return "RadialGradient(" +
563             "colors=$colors, " +
564             "stops=$stops, " +
565             centerValue +
566             radiusValue +
567             "tileMode=$tileMode)"
568     }
569 }
570 
571 /** Brush implementation used to apply a sweep gradient on a given [Paint] */
572 @Immutable
573 class SweepGradient
574 internal constructor(
575     private val center: Offset,
576     private val colors: List<Color>,
577     private val stops: List<Float>? = null
578 ) : ShaderBrush() {
579 
createShadernull580     override fun createShader(size: Size): Shader =
581         SweepGradientShader(
582             if (center.isUnspecified) {
583                 size.center
584             } else {
585                 Offset(
586                     if (center.x == Float.POSITIVE_INFINITY) size.width else center.x,
587                     if (center.y == Float.POSITIVE_INFINITY) size.height else center.y
588                 )
589             },
590             colors,
591             stops
592         )
593 
equalsnull594     override fun equals(other: Any?): Boolean {
595         if (this === other) return true
596         if (other !is SweepGradient) return false
597 
598         if (center != other.center) return false
599         if (colors != other.colors) return false
600         if (stops != other.stops) return false
601 
602         return true
603     }
604 
hashCodenull605     override fun hashCode(): Int {
606         var result = center.hashCode()
607         result = 31 * result + colors.hashCode()
608         result = 31 * result + (stops?.hashCode() ?: 0)
609         return result
610     }
611 
toStringnull612     override fun toString(): String {
613         val centerValue = if (center.isSpecified) "center=$center, " else ""
614         return "SweepGradient(" + centerValue + "colors=$colors, stops=$stops)"
615     }
616 }
617 
618 /**
619  * Convenience method to create a ShaderBrush that always returns the same shader instance
620  * regardless of size
621  */
ShaderBrushnull622 fun ShaderBrush(shader: Shader) =
623     object : ShaderBrush() {
624 
625         /** Create a shader based on the given size that represents the current drawing area */
626         override fun createShader(size: Size): Shader = shader
627     }
628 
629 /**
630  * Brush implementation that wraps and applies a the provided shader to a [Paint] The shader can be
631  * lazily created based on a given size, or provided directly as a parameter
632  */
633 @Immutable
634 abstract class ShaderBrush() : Brush() {
635 
636     private var internalShader: Shader? = null
637     private var createdSize = Size.Unspecified
638 
createShadernull639     abstract fun createShader(size: Size): Shader
640 
641     final override fun applyTo(size: Size, p: Paint, alpha: Float) {
642         var shader = internalShader
643         if (shader == null || createdSize != size) {
644             if (size.isEmpty()) {
645                 shader = null
646                 internalShader = null
647                 createdSize = Size.Unspecified
648             } else {
649                 shader = createShader(size).also { internalShader = it }
650                 createdSize = size
651             }
652         }
653         if (p.color != Color.Black) p.color = Color.Black
654         if (p.shader != shader) p.shader = shader
655         if (p.alpha != alpha) p.alpha = alpha
656     }
657 }
658