about summary refs log tree commit diff
diff options
context:
space:
mode:
authordan <dan.abramov@gmail.com>2024-11-09 22:41:14 +0000
committerGitHub <noreply@github.com>2024-11-09 22:41:14 +0000
commit42abd97f61b6813ab7dc6126d167b4130532326a (patch)
treeed027d346a27170ce60862cbd6849bbc63302157
parent2d73c5a24cf8ad06dbebcf44c8f4f053eedda5a4 (diff)
downloadvoidsky-42abd97f61b6813ab7dc6126d167b4130532326a.tar.zst
[Lightbox] Always rely on Expo Image cache (#6189)
* Inline useImageAspectRatio

* Switch AutoSizedImage to read dimensions from Expo Image cache

* Include thumbnail dimensions in image data

* Use dims from Expo Image cache in lightbox

* Fix wiring so all thumbnails get dimensions

* Fix type

* Oops
-rw-r--r--src/lib/media/image-sizes.ts93
-rw-r--r--src/screens/Profile/Header/Shell.tsx1
-rw-r--r--src/view/com/lightbox/ImageViewing/@types/index.ts3
-rw-r--r--src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx10
-rw-r--r--src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx13
-rw-r--r--src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx2
-rw-r--r--src/view/com/lightbox/ImageViewing/index.tsx22
-rw-r--r--src/view/com/profile/ProfileSubpageHeader.tsx1
-rw-r--r--src/view/com/util/images/AutoSizedImage.tsx67
-rw-r--r--src/view/com/util/images/Gallery.tsx16
-rw-r--r--src/view/com/util/images/ImageLayoutGrid.tsx13
-rw-r--r--src/view/com/util/post-embeds/index.tsx10
12 files changed, 105 insertions, 146 deletions
diff --git a/src/lib/media/image-sizes.ts b/src/lib/media/image-sizes.ts
deleted file mode 100644
index 8eaa9467f..000000000
--- a/src/lib/media/image-sizes.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-import {useEffect, useState} from 'react'
-import {Image} from 'react-native'
-
-import type {Dimensions} from '#/lib/media/types'
-
-type CacheStorageItem<T> = {key: string; value: T}
-const createCache = <T>(cacheSize: number) => ({
-  _storage: [] as CacheStorageItem<T>[],
-  get(key: string) {
-    const {value} =
-      this._storage.find(({key: storageKey}) => storageKey === key) || {}
-    return value
-  },
-  set(key: string, value: T) {
-    if (this._storage.length >= cacheSize) {
-      this._storage.shift()
-    }
-    this._storage.push({key, value})
-  },
-})
-
-const sizes = createCache<Dimensions>(50)
-const activeRequests: Map<string, Promise<Dimensions>> = new Map()
-
-export function get(uri: string): Dimensions | undefined {
-  return sizes.get(uri)
-}
-
-export function fetch(uri: string): Promise<Dimensions> {
-  const dims = sizes.get(uri)
-  if (dims) {
-    return Promise.resolve(dims)
-  }
-  const activeRequest = activeRequests.get(uri)
-  if (activeRequest) {
-    return activeRequest
-  }
-  const prom = new Promise<Dimensions>((resolve, reject) => {
-    Image.getSize(
-      uri,
-      (width: number, height: number) => {
-        const size = {width, height}
-        sizes.set(uri, size)
-        resolve(size)
-      },
-      (err: any) => {
-        console.error('Failed to fetch image dimensions for', uri, err)
-        reject(new Error('Could not fetch dimensions'))
-      },
-    )
-  }).finally(() => {
-    activeRequests.delete(uri)
-  })
-  activeRequests.set(uri, prom)
-  return prom
-}
-
-export function useImageDimensions({
-  src,
-  knownDimensions,
-}: {
-  src: string
-  knownDimensions: Dimensions | null
-}): [number | undefined, Dimensions | undefined] {
-  const [dims, setDims] = useState(() => knownDimensions ?? get(src))
-  const [prevSrc, setPrevSrc] = useState(src)
-  if (src !== prevSrc) {
-    setDims(knownDimensions ?? get(src))
-    setPrevSrc(src)
-  }
-
-  useEffect(() => {
-    let aborted = false
-    if (dims !== undefined) return
-    fetch(src).then(newDims => {
-      if (aborted) return
-      setDims(newDims)
-    })
-    return () => {
-      aborted = true
-    }
-  }, [dims, setDims, src])
-
-  let aspectRatio: number | undefined
-  if (dims) {
-    aspectRatio = dims.width / dims.height
-    if (Number.isNaN(aspectRatio)) {
-      aspectRatio = undefined
-    }
-  }
-
-  return [aspectRatio, dims]
-}
diff --git a/src/screens/Profile/Header/Shell.tsx b/src/screens/Profile/Header/Shell.tsx
index 093b9190a..1a1e7d4a2 100644
--- a/src/screens/Profile/Header/Shell.tsx
+++ b/src/screens/Profile/Header/Shell.tsx
@@ -72,6 +72,7 @@ let ProfileHeaderShell = ({
               height: 1000,
               width: 1000,
             },
+            thumbDimensions: null,
             type: 'circle-avi',
           },
         ],
diff --git a/src/view/com/lightbox/ImageViewing/@types/index.ts b/src/view/com/lightbox/ImageViewing/@types/index.ts
index 1a3543c26..779b95bfc 100644
--- a/src/view/com/lightbox/ImageViewing/@types/index.ts
+++ b/src/view/com/lightbox/ImageViewing/@types/index.ts
@@ -21,10 +21,11 @@ export type Position = {
 
 export type ImageSource = {
   uri: string
+  dimensions: Dimensions | null
   thumbUri: string
+  thumbDimensions: Dimensions | null
   thumbRect: MeasuredDimensions | null
   alt?: string
-  dimensions: Dimensions | null
   type: 'image' | 'circle-avi' | 'rect-avi'
 }
 
diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx
index 069f9eb40..4652cb606 100644
--- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx
+++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx
@@ -41,6 +41,7 @@ type Props = {
   onRequestClose: () => void
   onTap: () => void
   onZoom: (isZoomed: boolean) => void
+  onLoad: (dims: ImageDimensions) => void
   isScrollViewBeingDragged: boolean
   showControls: boolean
   measureSafeArea: () => {
@@ -66,6 +67,7 @@ const ImageItem = ({
   imageSrc,
   onTap,
   onZoom,
+  onLoad,
   isScrollViewBeingDragged,
   measureSafeArea,
   imageAspect,
@@ -330,8 +332,8 @@ const ImageItem = ({
       transform: scaleAndMoveTransform.concat(manipulationTransform),
       width: screenSize.width,
       maxHeight: screenSize.height,
-      aspectRatio: imageAspect,
       alignSelf: 'center',
+      aspectRatio: imageAspect ?? 1 /* force onLoad */,
     }
   })
 
@@ -349,6 +351,7 @@ const ImageItem = ({
     return {
       flex: 1,
       transform: cropContentTransform,
+      opacity: imageAspect === undefined ? 0 : 1,
     }
   })
 
@@ -393,7 +396,10 @@ const ImageItem = ({
                 placeholderContentFit="cover"
                 placeholder={{uri: imageSrc.thumbUri}}
                 accessibilityLabel={imageSrc.alt}
-                onLoad={() => setHasLoaded(false)}
+                onLoad={e => {
+                  setHasLoaded(true)
+                  onLoad({width: e.source.width, height: e.source.height})
+                }}
                 style={{flex: 1, borderRadius}}
                 accessibilityHint=""
                 accessibilityIgnoresInvertColors
diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx
index 7a9a18b91..e6ec64c3d 100644
--- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx
+++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx
@@ -38,6 +38,7 @@ type Props = {
   onRequestClose: () => void
   onTap: () => void
   onZoom: (scaled: boolean) => void
+  onLoad: (dims: ImageDimensions) => void
   isScrollViewBeingDragged: boolean
   showControls: boolean
   measureSafeArea: () => {
@@ -64,6 +65,7 @@ const ImageItem = ({
   imageSrc,
   onTap,
   onZoom,
+  onLoad,
   showControls,
   measureSafeArea,
   imageAspect,
@@ -162,8 +164,9 @@ const ImageItem = ({
       transform: cropFrameTransform,
       width: screenSize.width,
       maxHeight: screenSize.height,
-      aspectRatio: imageAspect,
       alignSelf: 'center',
+      aspectRatio: imageAspect ?? 1 /* force onLoad */,
+      opacity: imageAspect === undefined ? 0 : 1,
     }
   })
 
@@ -172,7 +175,8 @@ const ImageItem = ({
     return {
       transform: cropContentTransform,
       width: '100%',
-      aspectRatio: imageAspect,
+      aspectRatio: imageAspect ?? 1 /* force onLoad */,
+      opacity: imageAspect === undefined ? 0 : 1,
     }
   })
 
@@ -224,7 +228,10 @@ const ImageItem = ({
               accessibilityHint=""
               enableLiveTextInteraction={showControls && !scaled}
               accessibilityIgnoresInvertColors
-              onLoad={() => setHasLoaded(true)}
+              onLoad={e => {
+                setHasLoaded(true)
+                onLoad({width: e.source.width, height: e.source.height})
+              }}
             />
           </Animated.View>
         </Animated.View>
diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx
index 543fad772..b41e16383 100644
--- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx
+++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx
@@ -5,6 +5,7 @@ import {View} from 'react-native'
 import {PanGesture} from 'react-native-gesture-handler'
 import {SharedValue} from 'react-native-reanimated'
 
+import {Dimensions} from '#/lib/media/types'
 import {
   Dimensions as ImageDimensions,
   ImageSource,
@@ -16,6 +17,7 @@ type Props = {
   onRequestClose: () => void
   onTap: () => void
   onZoom: (scaled: boolean) => void
+  onLoad: (dims: Dimensions) => void
   isScrollViewBeingDragged: boolean
   showControls: boolean
   measureSafeArea: () => {
diff --git a/src/view/com/lightbox/ImageViewing/index.tsx b/src/view/com/lightbox/ImageViewing/index.tsx
index 030c8dcf3..ab8306b36 100644
--- a/src/view/com/lightbox/ImageViewing/index.tsx
+++ b/src/view/com/lightbox/ImageViewing/index.tsx
@@ -42,7 +42,7 @@ import {
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {Trans} from '@lingui/macro'
 
-import {useImageDimensions} from '#/lib/media/image-sizes'
+import {Dimensions} from '#/lib/media/types'
 import {colors, s} from '#/lib/styles'
 import {isIOS} from '#/platform/detection'
 import {Lightbox} from '#/state/lightbox'
@@ -92,7 +92,9 @@ export default function ImageViewRoot({
 
     const canAnimate =
       !PlatformInfo.getIsReducedMotionEnabled() &&
-      nextLightbox.images.every(img => img.dimensions && img.thumbRect)
+      nextLightbox.images.every(
+        img => img.thumbRect && (img.dimensions || img.thumbDimensions),
+      )
 
     // https://github.com/software-mansion/react-native-reanimated/issues/6677
     requestAnimationFrame(() => {
@@ -345,10 +347,15 @@ function LightboxImage({
   openProgress: SharedValue<number>
   dismissSwipeTranslateY: SharedValue<number>
 }) {
-  const [imageAspect, imageDimensions] = useImageDimensions({
-    src: imageSrc.uri,
-    knownDimensions: imageSrc.dimensions,
-  })
+  const [fetchedDims, setFetchedDims] = React.useState<Dimensions | null>(null)
+  const dims = fetchedDims ?? imageSrc.dimensions ?? imageSrc.thumbDimensions
+  let imageAspect: number | undefined
+  if (dims) {
+    imageAspect = dims.width / dims.height
+    if (Number.isNaN(imageAspect)) {
+      imageAspect = undefined
+    }
+  }
 
   const safeFrameDelayedForJSThreadOnly = useSafeAreaFrame()
   const safeInsetsDelayedForJSThreadOnly = useSafeAreaInsets()
@@ -452,11 +459,12 @@ function LightboxImage({
       onTap={onTap}
       onZoom={onZoom}
       onRequestClose={onRequestClose}
+      onLoad={setFetchedDims}
       isScrollViewBeingDragged={isScrollViewBeingDragged}
       showControls={showControls}
       measureSafeArea={measureSafeArea}
       imageAspect={imageAspect}
-      imageDimensions={imageDimensions}
+      imageDimensions={dims ?? undefined}
       dismissSwipePan={dismissSwipePan}
       transforms={transforms}
     />
diff --git a/src/view/com/profile/ProfileSubpageHeader.tsx b/src/view/com/profile/ProfileSubpageHeader.tsx
index 13d14ec50..d73b322f2 100644
--- a/src/view/com/profile/ProfileSubpageHeader.tsx
+++ b/src/view/com/profile/ProfileSubpageHeader.tsx
@@ -87,6 +87,7 @@ export function ProfileSubpageHeader({
               height: 1000,
               width: 1000,
             },
+            thumbDimensions: null,
             type: 'rect-avi',
           },
         ],
diff --git a/src/view/com/util/images/AutoSizedImage.tsx b/src/view/com/util/images/AutoSizedImage.tsx
index 21f6c529e..c26df8335 100644
--- a/src/view/com/util/images/AutoSizedImage.tsx
+++ b/src/view/com/util/images/AutoSizedImage.tsx
@@ -6,8 +6,7 @@ import {AppBskyEmbedImages} from '@atproto/api'
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
-import {useImageDimensions} from '#/lib/media/image-sizes'
-import {Dimensions} from '#/lib/media/types'
+import type {Dimensions} from '#/lib/media/types'
 import {isNative} from '#/platform/detection'
 import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
 import {atoms as a, useBreakpoints, useTheme} from '#/alf'
@@ -15,30 +14,6 @@ import {ArrowsDiagonalOut_Stroke2_Corner0_Rounded as Fullscreen} from '#/compone
 import {MediaInsetBorder} from '#/components/MediaInsetBorder'
 import {Text} from '#/components/Typography'
 
-function useImageAspectRatio({
-  src,
-  knownDimensions,
-}: {
-  src: string
-  knownDimensions: Dimensions | null
-}) {
-  const [raw] = useImageDimensions({src, knownDimensions})
-  let constrained: number | undefined
-  let max: number | undefined
-  let isCropped: boolean | undefined
-  if (raw !== undefined) {
-    const ratio = 1 / 2 // max of 1:2 ratio in feeds
-    constrained = Math.max(raw, ratio)
-    max = Math.max(raw, 0.25) // max of 1:4 in thread
-    isCropped = raw < constrained
-  }
-  return {
-    constrained,
-    max,
-    isCropped,
-  }
-}
-
 export function ConstrainedImage({
   aspectRatio,
   fullBleed,
@@ -93,23 +68,38 @@ export function AutoSizedImage({
   image: AppBskyEmbedImages.ViewImage
   crop?: 'none' | 'square' | 'constrained'
   hideBadge?: boolean
-  onPress?: (containerRef: AnimatedRef<React.Component<{}, {}, any>>) => void
+  onPress?: (
+    containerRef: AnimatedRef<React.Component<{}, {}, any>>,
+    fetchedDims: Dimensions | null,
+  ) => void
   onLongPress?: () => void
   onPressIn?: () => void
 }) {
   const t = useTheme()
   const {_} = useLingui()
   const largeAlt = useLargeAltBadgeEnabled()
-  const {
-    constrained,
-    max,
-    isCropped: rawIsCropped,
-  } = useImageAspectRatio({
-    src: image.thumb,
-    knownDimensions: image.aspectRatio ?? null,
-  })
   const containerRef = useAnimatedRef()
 
+  const [fetchedDims, setFetchedDims] = React.useState<Dimensions | null>(null)
+  const dims = fetchedDims ?? image.aspectRatio
+  let aspectRatio: number | undefined
+  if (dims) {
+    aspectRatio = dims.width / dims.height
+    if (Number.isNaN(aspectRatio)) {
+      aspectRatio = undefined
+    }
+  }
+
+  let constrained: number | undefined
+  let max: number | undefined
+  let rawIsCropped: boolean | undefined
+  if (aspectRatio !== undefined) {
+    const ratio = 1 / 2 // max of 1:2 ratio in feeds
+    constrained = Math.max(aspectRatio, ratio)
+    max = Math.max(aspectRatio, 0.25) // max of 1:4 in thread
+    rawIsCropped = aspectRatio < constrained
+  }
+
   const cropDisabled = crop === 'none'
   const isCropped = rawIsCropped && !cropDisabled
   const hasAlt = !!image.alt
@@ -123,6 +113,9 @@ export function AutoSizedImage({
         accessibilityIgnoresInvertColors
         accessibilityLabel={image.alt}
         accessibilityHint=""
+        onLoad={e => {
+          setFetchedDims({width: e.source.width, height: e.source.height})
+        }}
       />
       <MediaInsetBorder />
 
@@ -194,7 +187,7 @@ export function AutoSizedImage({
   if (cropDisabled) {
     return (
       <Pressable
-        onPress={() => onPress?.(containerRef)}
+        onPress={() => onPress?.(containerRef, fetchedDims)}
         onLongPress={onLongPress}
         onPressIn={onPressIn}
         // alt here is what screen readers actually use
@@ -216,7 +209,7 @@ export function AutoSizedImage({
         fullBleed={crop === 'square'}
         aspectRatio={constrained ?? 1}>
         <Pressable
-          onPress={() => onPress?.(containerRef)}
+          onPress={() => onPress?.(containerRef, fetchedDims)}
           onLongPress={onLongPress}
           onPressIn={onPressIn}
           // alt here is what screen readers actually use
diff --git a/src/view/com/util/images/Gallery.tsx b/src/view/com/util/images/Gallery.tsx
index 0c691ec9a..9d0817bd2 100644
--- a/src/view/com/util/images/Gallery.tsx
+++ b/src/view/com/util/images/Gallery.tsx
@@ -6,6 +6,7 @@ import {AppBskyEmbedImages} from '@atproto/api'
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
+import {Dimensions} from '#/lib/media/types'
 import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
 import {PostEmbedViewContext} from '#/view/com/util/post-embeds/types'
 import {atoms as a, useTheme} from '#/alf'
@@ -20,6 +21,7 @@ interface Props {
   onPress?: (
     index: number,
     containerRefs: AnimatedRef<React.Component<{}, {}, any>>[],
+    fetchedDims: (Dimensions | null)[],
   ) => void
   onLongPress?: EventFunction
   onPressIn?: EventFunction
@@ -27,6 +29,7 @@ interface Props {
   viewContext?: PostEmbedViewContext
   insetBorderStyle?: StyleProp<ViewStyle>
   containerRefs: AnimatedRef<React.Component<{}, {}, any>>[]
+  thumbDimsRef: React.MutableRefObject<(Dimensions | null)[]>
 }
 
 export function GalleryItem({
@@ -39,6 +42,7 @@ export function GalleryItem({
   viewContext,
   insetBorderStyle,
   containerRefs,
+  thumbDimsRef,
 }: Props) {
   const t = useTheme()
   const {_} = useLingui()
@@ -53,7 +57,11 @@ export function GalleryItem({
       ref={containerRefs[index]}
       collapsable={false}>
       <Pressable
-        onPress={onPress ? () => onPress(index, containerRefs) : undefined}
+        onPress={
+          onPress
+            ? () => onPress(index, containerRefs, thumbDimsRef.current.slice())
+            : undefined
+        }
         onPressIn={onPressIn ? () => onPressIn(index) : undefined}
         onLongPress={onLongPress ? () => onLongPress(index) : undefined}
         style={[
@@ -72,6 +80,12 @@ export function GalleryItem({
           accessibilityLabel={image.alt}
           accessibilityHint=""
           accessibilityIgnoresInvertColors
+          onLoad={e => {
+            thumbDimsRef.current[index] = {
+              width: e.source.width,
+              height: e.source.height,
+            }
+          }}
         />
         <MediaInsetBorder style={insetBorderStyle} />
       </Pressable>
diff --git a/src/view/com/util/images/ImageLayoutGrid.tsx b/src/view/com/util/images/ImageLayoutGrid.tsx
index b9b966302..dcc330dac 100644
--- a/src/view/com/util/images/ImageLayoutGrid.tsx
+++ b/src/view/com/util/images/ImageLayoutGrid.tsx
@@ -5,6 +5,7 @@ import {AppBskyEmbedImages} from '@atproto/api'
 
 import {PostEmbedViewContext} from '#/view/com/util/post-embeds/types'
 import {atoms as a, useBreakpoints} from '#/alf'
+import {Dimensions} from '../../lightbox/ImageViewing/@types'
 import {GalleryItem} from './Gallery'
 
 interface ImageLayoutGridProps {
@@ -12,6 +13,7 @@ interface ImageLayoutGridProps {
   onPress?: (
     index: number,
     containerRefs: AnimatedRef<React.Component<{}, {}, any>>[],
+    fetchedDims: (Dimensions | null)[],
   ) => void
   onLongPress?: (index: number) => void
   onPressIn?: (index: number) => void
@@ -42,6 +44,7 @@ interface ImageLayoutGridInnerProps {
   onPress?: (
     index: number,
     containerRefs: AnimatedRef<React.Component<{}, {}, any>>[],
+    fetchedDims: (Dimensions | null)[],
   ) => void
   onLongPress?: (index: number) => void
   onPressIn?: (index: number) => void
@@ -57,6 +60,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
   const containerRef2 = useAnimatedRef()
   const containerRef3 = useAnimatedRef()
   const containerRef4 = useAnimatedRef()
+  const thumbDimsRef = React.useRef<(Dimensions | null)[]>([])
 
   switch (count) {
     case 2: {
@@ -69,6 +73,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
               index={0}
               insetBorderStyle={noCorners(['topRight', 'bottomRight'])}
               containerRefs={containerRefs}
+              thumbDimsRef={thumbDimsRef}
             />
           </View>
           <View style={[a.flex_1, {aspectRatio: 1}]}>
@@ -77,6 +82,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
               index={1}
               insetBorderStyle={noCorners(['topLeft', 'bottomLeft'])}
               containerRefs={containerRefs}
+              thumbDimsRef={thumbDimsRef}
             />
           </View>
         </View>
@@ -93,6 +99,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
               index={0}
               insetBorderStyle={noCorners(['topRight', 'bottomRight'])}
               containerRefs={containerRefs}
+              thumbDimsRef={thumbDimsRef}
             />
           </View>
           <View style={[a.flex_1, {aspectRatio: 1}, gap]}>
@@ -106,6 +113,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
                   'bottomRight',
                 ])}
                 containerRefs={containerRefs}
+                thumbDimsRef={thumbDimsRef}
               />
             </View>
             <View style={[a.flex_1]}>
@@ -118,6 +126,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
                   'topRight',
                 ])}
                 containerRefs={containerRefs}
+                thumbDimsRef={thumbDimsRef}
               />
             </View>
           </View>
@@ -145,6 +154,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
                   'bottomRight',
                 ])}
                 containerRefs={containerRefs}
+                thumbDimsRef={thumbDimsRef}
               />
             </View>
             <View style={[a.flex_1, {aspectRatio: 1.5}]}>
@@ -157,6 +167,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
                   'bottomRight',
                 ])}
                 containerRefs={containerRefs}
+                thumbDimsRef={thumbDimsRef}
               />
             </View>
           </View>
@@ -171,6 +182,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
                   'bottomRight',
                 ])}
                 containerRefs={containerRefs}
+                thumbDimsRef={thumbDimsRef}
               />
             </View>
             <View style={[a.flex_1, {aspectRatio: 1.5}]}>
@@ -183,6 +195,7 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
                   'topRight',
                 ])}
                 containerRefs={containerRefs}
+                thumbDimsRef={thumbDimsRef}
               />
             </View>
           </View>
diff --git a/src/view/com/util/post-embeds/index.tsx b/src/view/com/util/post-embeds/index.tsx
index ab2471b33..1351a2cbc 100644
--- a/src/view/com/util/post-embeds/index.tsx
+++ b/src/view/com/util/post-embeds/index.tsx
@@ -35,6 +35,7 @@ import {atoms as a, useTheme} from '#/alf'
 import * as ListCard from '#/components/ListCard'
 import {Embed as StarterPackCard} from '#/components/StarterPack/StarterPackCard'
 import {ContentHider} from '../../../../components/moderation/ContentHider'
+import {Dimensions} from '../../lightbox/ImageViewing/@types'
 import {AutoSizedImage} from '../images/AutoSizedImage'
 import {ImageLayoutGrid} from '../images/ImageLayoutGrid'
 import {ExternalLinkEmbed} from './ExternalLinkEmbed'
@@ -148,11 +149,13 @@ export function PostEmbeds({
       const _openLightbox = (
         index: number,
         thumbRects: (MeasuredDimensions | null)[],
+        fetchedDims: (Dimensions | null)[],
       ) => {
         openLightbox({
           images: items.map((item, i) => ({
             ...item,
             thumbRect: thumbRects[i] ?? null,
+            thumbDimensions: fetchedDims[i] ?? null,
             type: 'image',
           })),
           index,
@@ -161,11 +164,12 @@ export function PostEmbeds({
       const onPress = (
         index: number,
         refs: AnimatedRef<React.Component<{}, {}, any>>[],
+        fetchedDims: (Dimensions | null)[],
       ) => {
         runOnUI(() => {
           'worklet'
           const rects = refs.map(ref => (ref ? measure(ref) : null))
-          runOnJS(_openLightbox)(index, rects)
+          runOnJS(_openLightbox)(index, rects, fetchedDims)
         })()
       }
       const onPressIn = (_: number) => {
@@ -189,7 +193,9 @@ export function PostEmbeds({
                     : 'constrained'
                 }
                 image={image}
-                onPress={containerRef => onPress(0, [containerRef])}
+                onPress={(containerRef, dims) =>
+                  onPress(0, [containerRef], [dims])
+                }
                 onPressIn={() => onPressIn(0)}
                 hideBadge={
                   viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia