## Subsampling Translations: [简体中文](subsampling.zh.md) > [!TIP] > * The following example takes precedence over the Compose version component for demonstration > * [ZoomState].zoomable is equivalent to [ZoomImageView].zoomable > * [ZoomState].subsampling is equivalent to [ZoomImageView].subsampling Some images are huge in size, if they are read completely into memory will make the app crash due to insufficient memory, the image loading framework will usually sample and then load, then the size of the image will become smaller, but the content of the image will also become blurry Therefore, it is necessary that ZoomImage can support subsampling when zooming, and the user can subsampling wherever he slides, and then display the clear original image fragments on the screen, so that it can display a clear image when zooming without crashing the app ### Features * [Exif Orientation](#exif-orientation). Support reading the Exif Orientation information of the image and then rotating the image * [Tile Animation](#tile-animation). Support transparency animation when displaying Tile, making the transition more natural * [Background tiles](#background-tiles). When switching sampleSize, the image clarity changes step by step, making the transition more natural. * [Pause load tiles](#pause-load-tiles). Pause loading of tiles during continuous transformations to improve performance * [Stop load tiles](#stop-load-tiles). Listen to Lifecycle, stop loading tiles and release loaded tiles at stop to improve performance * [Memory cache](#memory-cache). Avoid repeated decoding and improve performance * [Public Properties](#public-properties). Can read sampling size, image information, tile list and other information ### Prefix When will subsampling be enabled? * contentSize is smaller than contentOriginSize * The scaling factor of the sides of contentSize and contentOriginSize does not differ by more than 1f * On Android, it must be a type supported by BitmapRegionDecoder, non-Android platforms are fine as long as they are not GIFs ### Use the subsampling feature Components that integrate the image loading library can use the subsampling function without any additional work [ZoomImage] and [ZoomImageView] do not have an integrated image loading library and require an additional call to the `setSubsamplingImage()` method to use the subsampling function example: ```kotlin val zoomState: ZoomState by rememberZoomState() val imageSource = remember { val resUri = Res.getUri("files/huge_world.jpeg") ImageSource.fromComposeResource(resUri) } zoomState.setSubsamplingImage(imageSource) ZoomImage( painter = painterResource(Res.drawable.huge_world_thumbnail), contentDescription = "view image", modifier = Modifier.fillMaxSize(), zoomState = zoomState, ) ``` view: ```kotlin val zoomImageView = ZoomImageView(context) zoomImageView.setImageResource(R.drawable.huge_world_thumbnail) val imageSource = ImageSource.fromResource(context, R.raw.huge_world) zoomImageView.setSubsamplingImage(imageSource) ``` ### ImageSource [ImageSource] is responsible for providing image data to ZoomImage for decoding. ZoomImage provides a variety of [ImageSource] implementations to support loading images from various sources, as follows: * [AssetImageSource]: Load images from Android's assets directory.[ImageSource.fromAsset(context, "huge_world.jpeg")][AssetImageSource] * [ByteArrayImageSource]: Load images from ByteArray. [ImageSource.fromByteArray(byteArray)][ByteArrayImageSource] * [ComposeResourceImageSource]: Load images from Compose's resource directory. [ImageSource.fromComposeResource(Res.getUri("files/huge_world.jpeg"))][ComposeResourceImageSource] * [ContentImageSource]: Load images from Android's ContentProvider. [ImageSource.fromContent(context, contentUri)][ContentImageSource] * [FileImageSource]: Load image from file. [ImageSource.fromFile(file)][FileImageSource] * [KotlinResourceImageSource]: Load images from the Kotlin resource directory on desktop or ios platforms. [ImageSource.fromKotlinResource("huge_world.jpeg")][KotlinResourceImageSource] * [ResourceImageSource]: Load images from Android's res directory. [ImageSource.fromResource(context, R.raw.huge_world)][ResourceImageSource] ### \*SubsamplingImageGenerator The components of the Sketch, Coil, Glide, and Picasso series must create a SubsamplingImage based on data or uri after the image is loaded successfully. To support subsampling functionality, they all have their default SubsamplingImageGenerator implementation If the default implementation cannot correctly convert model or data to ImageSource when creating [SubsamplingImage] or you need to intercept the creation process, then you can customize a SubsamplingImageGenerator and apply it. The following takes the Sketch component as an example. Other components are similar: ```kotlin class MySketchComposeSubsamplingImageGenerator : SketchComposeSubsamplingImageGenerator { override fun generateImage( sketch: Sketch, request: ImageRequest, result: ImageResult.Success, painter: Painter ): SubsamplingImageGenerateResult? { // If the conditions are not met, skip the current SubsamplingImageGenerator if (true) { return null } // If the conditions are not met, the generation fails and a failure result is returned. if (true) { return SubsamplingImageGenerateResult.Error("message") } // Success val imageSource: ImageSource = ... val imageInfo: ImageInfo = ... val subsamplingImage = SubsamplingImage(imageSource, imageInfo) return SubsamplingImageGenerateResult.Success(subsamplingImage) } override fun equals(other: Any?): Boolean { if (this === other) return true return other != null && this::class == other::class } override fun hashCode(): Int { return this::class.hashCode() } override fun toString(): String { return "MySketchComposeSubsamplingImageGenerator" } } val subsamplingImageGenerators = remember { listOf(MySketchComposeSubsamplingImageGenerator()).toImmutableList() } val sketchZoomState = rememberSketchZoomState(subsamplingImageGenerators) SketchAsyncZoomImage( zoomState = sketchZoomState, ... ) class MySketchViewSubsamplingImageGenerator : SketchViewSubsamplingImageGenerator { override fun generateImage( sketch: Sketch, request: ImageRequest, result: ImageResult.Success, drawable: Drawable ): SubsamplingImageGenerateResult? { // If the conditions are not met, skip the current SubsamplingImageGenerator if (true) { return null } // If the conditions are not met, the generation fails and a failure result is returned. if (true) { return SubsamplingImageGenerateResult.Error("message") } // Success val imageSource: ImageSource = ... val imageInfo: ImageInfo = ... val subsamplingImage = SubsamplingImage(imageSource, imageInfo) return SubsamplingImageGenerateResult.Success(subsamplingImage) } override fun equals(other: Any?): Boolean { if (this === other) return true return other != null && this::class == other::class } override fun hashCode(): Int { return this::class.hashCode() } override fun toString(): String { return "MySketchViewSubsamplingImageGenerator" } } val sketchZoomImageView = SketchZoomImageView(context) sketchZoomImageView.setSubsamplingImageGenerators(MySketchViewSubsamplingImageGenerator()) ``` > [!TIP] > If you customize mode or data, you must customize a SubsamplingImageGenerator and apply it, > otherwise you will not be able to use the subsampling function ### Exif Orientation By default, ZoomImage will read the Exif Orientation information of the image, and then rotate the image, you can't disable it ### Tile Animation ZoomImage supports transparency animation when displaying Tile. The animation is enabled by default, with a duration of 200 milliseconds and a refresh interval of 8 milliseconds. You can pass `tileAnimationSpec` parameters to turn off animation or modify animation duration and refresh interval example: ```kotlin val zoomState: ZoomState by rememberSketchZoomState() // Turn off animations zoomState.subsampling.setTileAnimationSpec(TileAnimationSpec.None) // Modify the duration and refresh interval of the animation zoomState.subsampling.setTileAnimationSpec(TileAnimationSpec(duration = 400, interval = 16)) SketchZoomAsyncImage( uri = "https://sample.com/sample.jpeg", contentDescription = "view image", modifier = Modifier.fillMaxSize(), zoomState = zoomState, ) ``` ### Pause load tiles ZoomImage divides the continuous transformation behavior into five types: `SCALE`, `OFFSET`, `LOCATE`, `GESTURE`, `FLING`, and supports configuring the specified type of continuous transformation to pause loading tiles, which can improve performance The default configuration of ZoomImage is 'SCALE', 'OFFSET', 'LOCATE' three types of continuous transformations that pause the loading of tiles, 'GESTURE', The 'FLING' two types load tiles in real time, which you can configure via the `pausedContinuousTransformTypes` property example: ```kotlin val zoomState: ZoomState by rememberSketchZoomState() // All continuous transform types load tiles in real time zoomState.subsampling.setPausedContinuousTransformTypes(0) // All continuous transform types pause loading of tiles zoomState.subsampling.setPausedContinuousTransformTypes( TileManager.DefaultPausedContinuousTransformType or ContinuousTransformType.GESTURE or ContinuousTransformType.FLING ) SketchZoomAsyncImage( uri = "https://sample.com/sample.jpeg", contentDescription = "view image", modifier = Modifier.fillMaxSize(), zoomState = zoomState, ) ``` ### Stop load tiles ZoomImage supports stopping subsampling, which free the loaded tile after stopping and no new tiles are loaded, and automatically reloads the tiles after restarting, you can configure it via the `stopped` attribute example: ```kotlin val zoomState: ZoomState by rememberSketchZoomState() // stop zoomState.subsampling.setStopped(true) // restart zoomState.subsampling.setStopped(false) SketchZoomAsyncImage( uri = "https://sample.com/sample.jpeg", contentDescription = "view image", modifier = Modifier.fillMaxSize(), zoomState = zoomState, ) ``` #### Lifecycle By default, ZoomImage automatically fetches the most recent Lifecycle and listens to its state, pausing or resuming subsampling at the Lifecycle stop or start Get the latest Lifecycle in View through View.findViewTreeLifecycleOwner() API; in Compose, get Lifecycle through LocalLifecycleOwner.current API If you do not need this feature, you can turn it off via the `disabledAutoStopWithLifecycle` property as follows: ```kotlin val zoomState: ZoomState by rememberSketchZoomState() zoomState.subsampling.setDisabledAutoStopWithLifecycle(true) SketchZoomAsyncImage( uri = "https://sample.com/sample.jpeg", contentDescription = "view image", modifier = Modifier.fillMaxSize(), zoomState = zoomState, ) ``` ### Background tiles ZoomImage uses background tiles to change sampleSize when switching sampleSize The change in the clarity of the image also changes step by step, and the basemap will not be exposed during the process of loading new tiles, which ensures the continuity of the clarity change and the user experience is better However, this feature uses more memory, which may affect fluency on devices with poor performance, and this feature is turned on by default, you can pass `disabledBackgroundTiles` property to close it example: ```kotlin val zoomState: ZoomState by rememberSketchZoomState() zoomState.subsampling.setDisabledBackgroundTiles(true) SketchZoomAsyncImage( uri = "https://sample.com/sample.jpeg", contentDescription = "view image", modifier = Modifier.fillMaxSize(), zoomState = zoomState, ) ``` ### Memory Cache The subsampling feature supports memory caching, which can cache Bitmap in memory, which can avoid repeated decoding and improve performance Components that integrate the image loading library can use the memory caching feature without any additional work, while components that do not integrate the image loading library need to implement their own first [TileImageCache] Then set the `tileImageCache` property to use the memory cache feature example: ```kotlin val zoomState: ZoomState by rememberSketchZoomState() val titleImageCache = remember { MyTileImageCache() } zoomState.subsampling.setTileImageCache(titleImageCache) SketchZoomAsyncImage( uri = "https://sample.com/sample.jpeg", contentDescription = "view image", modifier = Modifier.fillMaxSize(), zoomState = zoomState, ) ``` After setting the tileImageCache property, the memory caching function is turned on, and it can be passed without modifying the tileImageCache property The `disabledTileImageCache` property controls the use of the memory cache feature example: ```kotlin val zoomState: ZoomState by rememberSketchZoomState() // Disable memory caching zoomState.subsampling.setDisabledTileImageCache(true) // Memory caching is allowed zoomState.subsampling.setDisabledTileImageCache(false) SketchZoomAsyncImage( uri = "https://sample.com/sample.jpeg", contentDescription = "view image", modifier = Modifier.fillMaxSize(), zoomState = zoomState, ) ``` ### RegionDecoder ZoomImage uses BitmapRegionDecoder on the Android platform to decode images, and non-Android platforms use Skia to decode images, but they support limited image types. You can expand the supported image types through the [RegionDecoder] interface. First implement the [RegionDecoder] interface and its Factory interface to define your [RegionDecoder], refer to [SkiaRegionDecoder] and [AndroidRegionDecoder] Then apply your [RegionDecoder] on [SubsamplingState] or [SubsamplingEngine] as follows: ```kotlin val zoomState: ZoomState by rememberSketchZoomState() zoomState.subsampling.setRegionDecoders(listOf(MyRegionDecoder.Factory())) SketchZoomAsyncImage( uri = "https://sample.com/sample.jpeg", contentDescription = "view image", modifier = Modifier.fillMaxSize(), zoomState = zoomState, ) val sketchZoomImageView = SketchZoomImageView(context) sketchZoomImageView.subsampling.setRegionDecoders(listOf(MyRegionDecoder.Factory())) ``` ### Disable subsampling The subsampling function is enabled by default, you can disable it through the disabled property, as follows: ```kotlin val zoomState: ZoomState by rememberSketchZoomState() zoomState.subsampling.setDisabled(true) SketchZoomAsyncImage( uri = "https://sample.com/sample.jpeg", contentDescription = "view image", modifier = Modifier.fillMaxSize(), zoomState = zoomState, ) ``` ### Public Properties and Methods ```kotlin // compose val zoomState: ZoomState by rememberSketchZoomState() SketchZoomAsyncImage(zoomState = zoomState) val subsampling: SubsamplingState = zoomState.subsampling // view val sketchZoomImageView = SketchZoomImageView(context) val subsampling: SubsamplingEngine = sketchZoomImageView.subsampling ``` > [!TIP] > * Note: The relevant properties of the view version are wrapped in StateFlow, so its name is suffixed with State compared to the compose version Readable properties: * `subsampling.disabled: Boolean`: Whether to disable subsampling function * `subsampling.tileImageCache: TileImageCache?`: The memory cache of Tile tile is null by default. * `subsampling.disabledTileImageCache: Boolean`: Whether to disable the memory cache of Tile tile, default to false * `subsampling.tileAnimationSpec: TileAnimationSpec`: The configuration of tile animation is default to TileAnimationSpec.Default * `subsampling.pausedContinuousTransformTypes: Int`: Pauses the configuration of continuous transformation types for loading tiles * `subsampling.disabledBackgroundTiles: Boolean`: Whether to disable background tile, default to false * `subsampling.stopped: Boolean`: Whether to stop loading tiles, default to false * `subsampling.disabledAutoStopWithLifecycle: Boolean`: Whether to disable automatic stop loading of tiles based on Lifecycle, default to false * `subsampling.regionDecoders: List`: Add a custom RegionDecoder, default to an empty list * `subsampling.showTileBounds: Boolean`: Whether to display the boundary of Tile, default to false * `subsampling.ready: Boolean`: Whether the image is ready for subsampling * `subsampling.imageInfo: ImageInfo`: The information of the image, including width, height, format, exif information, etc * `subsampling.tileGridSizeMap: Map`: Tile grid size map * `subsampling.sampleSize: Int`: The sample size of the image * `subsampling.imageLoadRect: IntRect`: The image load rect * `subsampling.foregroundTiles: List`: List of current foreground tiles * `subsampling.backgroundTiles: List`: List of current background tiles Interactive methods: * `subsampling.setImage(): Boolean`: Set the subsampling image, return whether it is successful, the components that integrate the image loader will automatically set the subsampling image * `subsampling.setDisabled(Boolean)`: Set whether to disable subsampling function * `subsampling.setTileImageCache(TileImageCache?)`: Set the memory cache of Tile tile, and the components that integrate the picture loader will automatically set it * `subsampling.setDisabledTileImageCache(Boolean)`: Set whether to disable the memory cache of Tile tiles * `subsampling.setTileAnimationSpec(TileAnimationSpec)`: Set tile animation configuration * `subsampling.setPausedContinuousTransformTypes(Int)`: Set the configuration of the continuous transformation type that pauses loading tile. Multiple types can be combined by bits or operators. The default is TileManager.DefaultPausedContinuousTransformType * `subsampling.setDisabledBackgroundTiles(Boolean)`: Set whether to disable background tiles * `subsampling.setStopped(Boolean)`: Set whether to stop loading tiles * `subsampling.setDisabledAutoStopWithLifecycle(Boolean)`: Set whether to disable the automatic stop loading of tiles according to Lifecycle * `subsampling.setRegionDecoders(List)`: Set up a custom RegionDecoder * `subsampling.setShowTileBounds(Boolean)`: Set whether to display the boundary of Tile #### Listen property changed * The relevant properties of the compose version are wrapped in State and can be read directly in the Composable function to implement listening * The relevant properties of the view are wrapped in StateFlow, and its collect function can be called to implement the listening [ZoomImageView]: ../zoomimage-view/src/main/kotlin/com/github/panpf/zoomimage/ZoomImageView.kt [ZoomImage]: ../zoomimage-compose/src/commonMain/kotlin/com/github/panpf/zoomimage/ZoomImage.kt [ZoomState]: ../zoomimage-compose/src/commonMain/kotlin/com/github/panpf/zoomimage/compose/ZoomState.kt [TileImageCache]: ../zoomimage-core/src/commonMain/kotlin/com/github/panpf/zoomimage/subsampling/TileImageCache.kt [SubsamplingState]: ../zoomimage-compose/src/commonMain/kotlin/com/github/panpf/zoomimage/compose/subsampling/SubsamplingState.kt [ImageSource]: ../zoomimage-core/src/commonMain/kotlin/com/github/panpf/zoomimage/subsampling/ImageSource.kt [AssetImageSource]: ../zoomimage-core/src/androidMain/kotlin/com/github/panpf/zoomimage/subsampling/AssetImageSource.kt [ByteArrayImageSource]: ../zoomimage-core/src/commonMain/kotlin/com/github/panpf/zoomimage/subsampling/ByteArrayImageSource.kt [ComposeResourceImageSource]: ../zoomimage-compose-resources/src/commonMain/kotlin/com/github/panpf/zoomimage/subsampling/ComposeResourceImageSource.kt [ContentImageSource]: ../zoomimage-core/src/androidMain/kotlin/com/github/panpf/zoomimage/subsampling/ContentImageSource.kt [FileImageSource]: ../zoomimage-core/src/commonMain/kotlin/com/github/panpf/zoomimage/subsampling/FileImageSource.kt [KotlinResourceImageSource]: ../zoomimage-core/src/desktopMain/kotlin/com/github/panpf/zoomimage/subsampling/KotlinResourceImageSource.kt [ResourceImageSource]: ../zoomimage-core/src/androidMain/kotlin/com/github/panpf/zoomimage/subsampling/ResourceImageSource.kt [SubsamplingImage]: ../zoomimage-core/src/commonMain/kotlin/com/github/panpf/zoomimage/subsampling/SubsamplingImage.kt [RegionDecoder]: ../zoomimage-core/src/commonMain/kotlin/com/github/panpf/zoomimage/subsampling/RegionDecoder.kt [SkiaRegionDecoder]: ../zoomimage-core/src/nonAndroidMain/kotlin/com/github/panpf/zoomimage/subsampling/internal/SkiaRegionDecoder.kt [AndroidRegionDecoder]: ../zoomimage-core/src/androidMain/kotlin/com/github/panpf/zoomimage/subsampling/internal/AndroidRegionDecoder.kt [SubsamplingState]: ../zoomimage-compose/src/commonMain/kotlin/com/github/panpf/zoomimage/compose/subsampling/SubsamplingState.kt [SubsamplingEngine]: ../zoomimage-view/src/main/kotlin/com/github/panpf/zoomimage/view/subsampling/SubsamplingEngine.kt