Skip to content

Releases: saket/telephoto

0.19.0

02 Apr 07:00

Choose a tag to compare

Improvements

  • #163: Added interactionSource parameter to Modifier.zoomable(), ZoomableImage(), and friends for observing press gestures. See its recipe for more details.

Bug fixes

  • #106: Fallback to non-sub-sampled image when disk cache entry is missing
  • #142: Catch FileNotFoundException exceptions in canBeSubSampled()

0.18.0

04 Oct 18:21

Choose a tag to compare

Improvements

  • #162: Added support for rendering images flipped using exif metadata

0.17.0

15 Sep 05:28

Choose a tag to compare

Highlights

  • #45, #88, #140, #150, #152, #158: Upscaled images can now be zoomed.
  • #135: Zoom to a specific pixel using ZoomFocalPoint:
    val imageState = rememberZoomableImageState()
    imageState.zoomableState.zoomTo(
      zoomFactor = 2f,
      focal = ZoomFocalPoint.moveToViewportCenter(…), // or zoomAround()
    )
  • #141: Selectively disable zooming or panning:
    ZoomableAsyncImage(
      model = "https://dog.ceo",
      contentDescription = "",
      gestures = EnabledZoomGestures.ZoomOnly,  // or PanOnly
    )

⚠️ Potentially breaking changes

  • When a small image is upscaled to fit the viewport, its starting scale can be larger than its maximum zoom limit. In such cases, the image wouldn't respond to zoom gestures. telephoto now automatically increases the max zoom limit when this happens, ensuring the image can still be zoomed.
    • To disable this, pass DynamicZoomSpec.fixed(…) to rememberZoomableState().
    • If you're using a custom DoubleClickToZoomListener listener, review its behavior with small images.
  • The receiver of DoubleClickToZoomListener#onDoubleClick() has changed. This API was already marked experimental.
  • FlickToDismiss() now uses rubber banding. More on this below.

Improvements

  • ZoomableImage() / Modifier.zoomable
    • Added DynamicZoomSpec for lazy calculation of zoom limits using the layout info.
    • Improved zooming using mouse-wheel scrolls on JVM.
    • Migrated to LocalHapticFeedback so haptics can be customized or disabled.
  • SubSamplingImage()
    • #149: Tile sizes are now consistent, making it easier to render PDFs (by @sigmabeta).

Bug fixes

  • #146: Added missing contentPadding param to ZoomableAsyncImage() (by @xertrec).
  • #160: Fixed ignored overzoom effect for under-zooms (by @FooIbar).
  • Fixed broken double-click zooming for images that use ContentScale.FillBounds.
  • zoomTo() and zoomBy() no longer crash when given invalid or out-of-bound zoom factors.

Unreleased libraries

This release also includes updates to FlickToDismiss(). While it hasn't been officially released yet, some major apps have already started using it. I plan to announce it soon, but in the meantime here are the changes:

  • Improved drag physics and added a rubber banding effect. You can customize this via rememberFlickToDismissState().
  • A haptic feedback is now played when the dismiss threshold is crossed. This can customized via LocalHapticFeedback.

0.16.0

20 May 04:26

Choose a tag to compare

Highlights

  • Spatial geometry types: A new system of spatial geometry makes it easy to convert coordinates between the viewport and the zoomable content's coordinate space: SpatialOffset and SpatialRect. See these recipes for their usage examples:

  • Content padding: Modifier.zoomable() and ZoomableImage() now accept a contentPadding parameter. This is similar to using Modifier.padding, but allows the zoomable content to display and receive touch events in the padded region. This is intended to be used by image croppers.

  • A new sample demonstrating how telephoto can be used to build image croppers.

image.cropper.mp4

Other changes

  • #132, #137: Support for Wasm and JS targets for Modifier.zoomable()
  • Call prepareToDraw() on sub-sampled bitmaps for better performance

Bug fixes

  • #139: Fixed ConnectivityManager$TooManyRequestsException in ZoomableAsyncImage
  • Small fixes for improved state restoration

Dependency updates

  • Kotlin 2.1.20
  • Compose Multiplatform 1.8.0

0.15.1

16 Feb 21:15

Choose a tag to compare

Bug fixes

  • Fixed a bug that prevented double tap and quick zooms from working if a gesture was received before the image was fully loaded

Dependency updates

  • Compose multiplatform 1.7.3

0.15.0

16 Feb 03:07

Choose a tag to compare

This release introduces a new library for telephoto: Modifier.zoomablePeekOverlay(), a modifier for displaying transient overlaid zoom effect, inspired by Instagram.

AsyncImage(
  modifier = Modifier.zoomablePeekOverlay(…),
  model = "https://example.com/image.jpg",
  contentDescription = "",
)
zoom-overlay-demo.mp4

Other changes

  • Auto-enabled ultra HDR mode for compatible images
  • Customizable over-zoom behavior using OverzoomEffect (example).
  • #9: iOS support for Modifier.zoomable()
  • #118: Added partial support for AVIF images
  • #120: SubSamplingImageSource is no longer sealed. Apps can now display custom content such as PDFs and maps using SubSamplingImage().

Bug fixes

  • #114: Fixed flicker in ZoomableImage() when a new image is loaded
  • #122: Fixed a bug where images stopped loading under system stress
  • #129, #110: Worked around crashes related to color spaces in Compose UI

Dependency updates

  • Compose multiplatform: 1.7.1
  • Compile SDK: 35
  • Kotlin: 2.1.0

0.14.0

13 Nov 22:36

Choose a tag to compare

New changes

Bug fixes

  • #95: Removed explicit recycling of bitmap decoders
  • #97: Added protection against NaN velocities
  • #99: Added handling for non-existent content URIs to prevent crashes
  • #110: Added workaround for a Compose UI issue with unknown color spaces
  • ZoomableImage() will now display its contentDescription even if the image isn't loaded yet
  • Content alignment can now be updated even if the image is zoomed in
  • Prevented multiple buffering of SubSamplingImageSource.rawSource()

Deprecations

  • ZoomableContentLocation#size() is no longer used
  • SubSamplingImageState#isImageLoadedisImageDisplayed
  • SubSamplingImageState#isImageLoadedInFullQualityisImageDisplayedInFullQuality
  • ZoomableState#setContentLocation()setContentLocationSynchronously()

0.13.0

22 Aug 03:21

Choose a tag to compare

Bug fixes

  • #94: Sub-sampling does not work for images smaller than the minimum tile size in height
  • #95: IllegalStateException: getWidth called on recycled region decoder
  • #96: IllegalStateException: another transformation is already in progress

0.12.1

20 Jul 05:50

Choose a tag to compare

New changes

Bug fixes

  • Fixed a bug that prevented back buttons from working when ZoomableImage() was focused.

0.12.0

13 Jul 20:08

Choose a tag to compare

New changes

  • #78: Support for keyboard and mouse shortcuts (by @evant)
  • #67: New APIs in ZoomableState for controlling zoom from code: panBy, zoomBy, and zoomTo
  • #32: New onDoubleClick parameters in ZoomableImage() and Modifier.zoomable() for customizing double-click behavior
  • #91: Reduced minSdk to 21 (by @iwb-florien-flament)
  • Significantly reduced the amount of work required by a ZoomableImageSource by offloading the detection of bad content URIs to SubSamplingImageSource.contentUriOrNull()

Bug fixes

  • #93: NullPointerException when an image is zoomed before it is initialized
  • #50: FileNotFoundException: No content provider when disk caching of an image is disabled using Cache-Control HTTP headers.
  • Fixed infinite reloading of images when unstable image request listener are used in ZoomableAsyncImage(). This removes the need for using remember with ImageRequest.listener and ImageRequest.placeholder values.