Skip to content

Commit d04a77d

Browse files
Neha Guptameta-codesync[bot]
authored andcommitted
Add SimilarImageLookup interface and wire into ImagePipelineConfig
Reviewed By: oprisnik Differential Revision: D106009647 fbshipit-source-id: 33cadae7eed7f9ea6c69dfc28d3bfff238b7f7db
1 parent de4f23c commit d04a77d

3 files changed

Lines changed: 112 additions & 0 deletions

File tree

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.imagepipeline.cache
9+
10+
/**
11+
* Enables producers to find larger cached variants of the same image content to avoid redundant
12+
* network fetches. Implementations track known cache keys grouped by content identity (groupKey)
13+
* and find the smallest entry that is still larger than the requested dimensions.
14+
*
15+
* All gating logic (e.g. which image types or caller modules are eligible) is the responsibility of
16+
* the implementation — producers simply call [findLargerMemoryKey]/[findLargerDiskKey] and act on
17+
* the result.
18+
*/
19+
interface SimilarImageLookup {
20+
21+
/**
22+
* Find a larger variant of the same image in the memory cache key tracker.
23+
*
24+
* Producer contract: the consuming producer must NOT call this for cache-only requests (e.g.
25+
* [ImageRequest.RequestLevel.BITMAP_MEMORY_CACHE]). Cache-only callers expect exact-match
26+
* semantics — returning a resized approximate would violate that contract. The returned result is
27+
* a raw cache key; the producer is responsible for any resize or re-cache of the larger bitmap.
28+
*
29+
* @param cacheKeyString the exact cache key string of the requested image
30+
* @param groupKey content identity key grouping different resolutions of the same image. This is
31+
* typically the base cache key string without dimensions (e.g., normalized URI or
32+
* server-provided content ID). All cached variants of the same media share the same groupKey.
33+
* Pass null if grouping is not available — implementations should return null in that case.
34+
* @param width requested image width
35+
* @param height requested image height
36+
* @param callerContext app-specific caller context (e.g., IG passes IgCallerContext).
37+
* Implementations downcast to their app's type and return null for unrecognized types.
38+
* @return the cache key of a larger variant, or null if none found or lookup is disabled
39+
*/
40+
fun findLargerMemoryCacheKey(
41+
cacheKeyString: String,
42+
groupKey: String?,
43+
width: Int,
44+
height: Int,
45+
callerContext: Any?,
46+
): SimilarImageResult?
47+
48+
/**
49+
* Find a larger variant of the same image in the disk cache key tracker.
50+
*
51+
* Producer contract: the consuming producer must NOT call this when all attached requests are
52+
* cache-only (no network-permitted requests). Same cache-only semantics as
53+
* [findLargerMemoryCacheKey].
54+
*
55+
* @param diskCacheKey the disk cache key string (expected format: "groupKey_width_height")
56+
* @param callerContext app-specific caller context (e.g., IG passes IgCallerContext).
57+
* Implementations downcast to their app's type and return null for unrecognized types.
58+
* @return the cache key of a larger variant, or null if none found or lookup is disabled
59+
*/
60+
fun findLargerDiskCacheKey(diskCacheKey: String, callerContext: Any?): SimilarImageResult?
61+
62+
/**
63+
* Track a memory cache key for future similarity lookups. Called on cache insertion.
64+
*
65+
* Producers must only call this after a successful decode — not for intermediate or failed
66+
* results.
67+
*
68+
* @param cacheKeyString the exact cache key string of the decoded image
69+
* @param groupKey content identity key (same as [findLargerMemoryCacheKey]). Pass null if not
70+
* available.
71+
* @param width decoded image width
72+
* @param height decoded image height
73+
*/
74+
fun trackMemoryCacheKey(cacheKeyString: String, groupKey: String?, width: Int, height: Int)
75+
76+
/**
77+
* Track a disk cache key for future similarity lookups. Called on disk cache write.
78+
*
79+
* @param diskCacheKey the disk cache key string
80+
* @param isFullImage true if the image is fully decoded (all scans complete). Partial progressive
81+
* scan data must NOT be tracked, as it would pollute the similarity index with incomplete
82+
* images that cannot serve as valid "larger" sources.
83+
*/
84+
fun trackDiskCacheKey(diskCacheKey: String, isFullImage: Boolean = true)
85+
86+
/** Remove a tracked memory key. Called on cache eviction. */
87+
fun removeMemoryCacheKey(cacheKeyString: String, groupKey: String?, width: Int, height: Int)
88+
}
89+
90+
/** Result of a similar-image lookup — the cache key of a larger cached variant. */
91+
data class SimilarImageResult(
92+
val cacheKeyString: String,
93+
val groupKey: String?,
94+
val width: Int,
95+
val height: Int,
96+
)

‎imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipelineConfig.kt‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import com.facebook.imagepipeline.cache.MemoryCache.CacheTrimStrategy
4040
import com.facebook.imagepipeline.cache.MemoryCacheParams
4141
import com.facebook.imagepipeline.cache.NativeMemoryCacheTrimStrategy
4242
import com.facebook.imagepipeline.cache.NoOpImageCacheStatsTracker
43+
import com.facebook.imagepipeline.cache.SimilarImageLookup
4344
import com.facebook.imagepipeline.debug.CloseableReferenceLeakTracker
4445
import com.facebook.imagepipeline.debug.NoOpCloseableReferenceLeakTracker
4546
import com.facebook.imagepipeline.decoder.ImageDecoder
@@ -115,6 +116,7 @@ class ImagePipelineConfig private constructor(builder: Builder) : ImagePipelineC
115116
override val callerContextVerifier: CallerContextVerifier?
116117
override val closeableReferenceLeakTracker: CloseableReferenceLeakTracker
117118
override val bitmapCacheOverride: MemoryCache<CacheKey, CloseableImage>?
119+
override val similarImageLookup: SimilarImageLookup?
118120
override val encodedMemoryCacheOverride: MemoryCache<CacheKey, PooledByteBuffer>?
119121
override val executorServiceForAnimatedImages: SerialExecutorService?
120122
override val bitmapMemoryCacheFactory: BitmapMemoryCacheFactory
@@ -185,6 +187,7 @@ class ImagePipelineConfig private constructor(builder: Builder) : ImagePipelineC
185187
callerContextVerifier = builder.callerContextVerifier
186188
closeableReferenceLeakTracker = builder.closeableReferenceLeakTracker
187189
bitmapCacheOverride = builder.bitmapMemoryCache
190+
similarImageLookup = builder.similarImageLookup
188191
bitmapMemoryCacheFactory =
189192
builder.bitmapMemoryCacheFactory ?: CountingLruBitmapMemoryCacheFactory()
190193
nonBitmapImageMemoryCacheParamsSupplier = builder.nonBitmapImageMemoryCacheParamsSupplier
@@ -323,6 +326,9 @@ class ImagePipelineConfig private constructor(builder: Builder) : ImagePipelineC
323326
var bitmapMemoryCache: MemoryCache<CacheKey, CloseableImage>? = null
324327
private set
325328

329+
var similarImageLookup: SimilarImageLookup? = null
330+
private set
331+
326332
var encodedMemoryCache: MemoryCache<CacheKey, PooledByteBuffer>? = null
327333
private set
328334

@@ -535,6 +541,10 @@ class ImagePipelineConfig private constructor(builder: Builder) : ImagePipelineC
535541
this.nonBitmapImageMemoryCacheParamsSupplier = nonBitmapImageMemoryCacheParamsSupplier
536542
}
537543

544+
fun setSimilarImageLookup(similarImageLookup: SimilarImageLookup?): Builder = apply {
545+
this.similarImageLookup = similarImageLookup
546+
}
547+
538548
fun setDynamicDiskCacheConfigMap(
539549
dynamicDiskCacheConfigMap: Map<String, DiskCacheConfig>
540550
): Builder = apply { this.dynamicDiskCacheConfigMap = dynamicDiskCacheConfigMap }

‎imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipelineConfigInterface.kt‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import com.facebook.imagepipeline.cache.ImageCacheStatsTracker
2424
import com.facebook.imagepipeline.cache.MemoryCache
2525
import com.facebook.imagepipeline.cache.MemoryCache.CacheTrimStrategy
2626
import com.facebook.imagepipeline.cache.MemoryCacheParams
27+
import com.facebook.imagepipeline.cache.SimilarImageLookup
2728
import com.facebook.imagepipeline.debug.CloseableReferenceLeakTracker
2829
import com.facebook.imagepipeline.decoder.ImageDecoder
2930
import com.facebook.imagepipeline.decoder.ImageDecoderConfig
@@ -72,6 +73,11 @@ interface ImagePipelineConfigInterface {
7273
// Falls back to [bitmapMemoryCacheParamsSupplier] when null.
7374
val nonBitmapImageMemoryCacheParamsSupplier: Supplier<MemoryCacheParams>?
7475

76+
// Similar-image lookup (optional). When non-null, future similarity producers can find larger
77+
// cached variants of the same image to avoid redundant network fetches.
78+
val similarImageLookup: SimilarImageLookup?
79+
get() = null
80+
7581
// Network configuration
7682
val networkFetcher: NetworkFetcher<*>
7783
val isResizeAndRotateEnabledForNetwork: Boolean

0 commit comments

Comments
 (0)