Skip to content

Commit

Permalink
Add BlackholeDecoder to simplify disk-only preloading. (#2599)
Browse files Browse the repository at this point in the history
* Add BlackholeDecoder to simplify disk-only preloading.

* Fix test.

* Update API.
  • Loading branch information
colinrtwhite authored Oct 27, 2024
1 parent 37ef235 commit 8032fd9
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 2 deletions.
18 changes: 18 additions & 0 deletions coil-core/api/android/coil-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,24 @@ public final class coil3/decode/BitmapFactoryDecoder$Factory : coil3/decode/Deco
public fun create (Lcoil3/fetch/SourceFetchResult;Lcoil3/request/Options;Lcoil3/ImageLoader;)Lcoil3/decode/Decoder;
}

public final class coil3/decode/BlackholeDecoder : coil3/decode/Decoder {
public fun <init> (Lkotlin/jvm/functions/Function0;)V
public fun decode (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class coil3/decode/BlackholeDecoder$Factory : coil3/decode/Decoder$Factory {
public static final field Companion Lcoil3/decode/BlackholeDecoder$Factory$Companion;
public static final field EMPTY_IMAGE Lcoil3/Image;
public fun <init> ()V
public fun <init> (Lkotlin/jvm/functions/Function0;)V
public synthetic fun <init> (Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun create (Lcoil3/fetch/SourceFetchResult;Lcoil3/request/Options;Lcoil3/ImageLoader;)Lcoil3/decode/BlackholeDecoder;
public synthetic fun create (Lcoil3/fetch/SourceFetchResult;Lcoil3/request/Options;Lcoil3/ImageLoader;)Lcoil3/decode/Decoder;
}

public final class coil3/decode/BlackholeDecoder$Factory$Companion {
}

public final class coil3/decode/ByteBufferMetadata : coil3/decode/ImageSource$Metadata {
public fun <init> (Ljava/nio/ByteBuffer;)V
public final fun getByteBuffer ()Ljava/nio/ByteBuffer;
Expand Down
17 changes: 17 additions & 0 deletions coil-core/api/coil-core.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,23 @@ abstract class coil3/PlatformContext { // coil3/PlatformContext|null[0]
}
}

final class coil3.decode/BlackholeDecoder : coil3.decode/Decoder { // coil3.decode/BlackholeDecoder|null[0]
constructor <init>(kotlin/Function0<coil3/Image>) // coil3.decode/BlackholeDecoder.<init>|<init>(kotlin.Function0<coil3.Image>){}[0]

final suspend fun decode(): coil3.decode/DecodeResult // coil3.decode/BlackholeDecoder.decode|decode(){}[0]

final class Factory : coil3.decode/Decoder.Factory { // coil3.decode/BlackholeDecoder.Factory|null[0]
constructor <init>(kotlin/Function0<coil3/Image> = ...) // coil3.decode/BlackholeDecoder.Factory.<init>|<init>(kotlin.Function0<coil3.Image>){}[0]

final fun create(coil3.fetch/SourceFetchResult, coil3.request/Options, coil3/ImageLoader): coil3.decode/BlackholeDecoder // coil3.decode/BlackholeDecoder.Factory.create|create(coil3.fetch.SourceFetchResult;coil3.request.Options;coil3.ImageLoader){}[0]

final object Companion { // coil3.decode/BlackholeDecoder.Factory.Companion|null[0]
final val EMPTY_IMAGE // coil3.decode/BlackholeDecoder.Factory.Companion.EMPTY_IMAGE|{}EMPTY_IMAGE[0]
final fun <get-EMPTY_IMAGE>(): coil3/Image // coil3.decode/BlackholeDecoder.Factory.Companion.EMPTY_IMAGE.<get-EMPTY_IMAGE>|<get-EMPTY_IMAGE>(){}[0]
}
}
}

final class coil3.decode/DecodeResult { // coil3.decode/DecodeResult|null[0]
constructor <init>(coil3/Image, kotlin/Boolean) // coil3.decode/DecodeResult.<init>|<init>(coil3.Image;kotlin.Boolean){}[0]

Expand Down
18 changes: 18 additions & 0 deletions coil-core/api/jvm/coil-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,24 @@ public abstract interface annotation class coil3/annotation/ExperimentalCoilApi
public abstract interface annotation class coil3/annotation/InternalCoilApi : java/lang/annotation/Annotation {
}

public final class coil3/decode/BlackholeDecoder : coil3/decode/Decoder {
public fun <init> (Lkotlin/jvm/functions/Function0;)V
public fun decode (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class coil3/decode/BlackholeDecoder$Factory : coil3/decode/Decoder$Factory {
public static final field Companion Lcoil3/decode/BlackholeDecoder$Factory$Companion;
public static final field EMPTY_IMAGE Lcoil3/Image;
public fun <init> ()V
public fun <init> (Lkotlin/jvm/functions/Function0;)V
public synthetic fun <init> (Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun create (Lcoil3/fetch/SourceFetchResult;Lcoil3/request/Options;Lcoil3/ImageLoader;)Lcoil3/decode/BlackholeDecoder;
public synthetic fun create (Lcoil3/fetch/SourceFetchResult;Lcoil3/request/Options;Lcoil3/ImageLoader;)Lcoil3/decode/Decoder;
}

public final class coil3/decode/BlackholeDecoder$Factory$Companion {
}

public final class coil3/decode/ByteBufferMetadata : coil3/decode/ImageSource$Metadata {
public fun <init> (Ljava/nio/ByteBuffer;)V
public final fun getByteBuffer ()Ljava/nio/ByteBuffer;
Expand Down
50 changes: 50 additions & 0 deletions coil-core/src/commonMain/kotlin/coil3/decode/BlackholeDecoder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package coil3.decode

import coil3.Canvas
import coil3.Image
import coil3.ImageLoader
import coil3.annotation.ExperimentalCoilApi
import coil3.fetch.SourceFetchResult
import coil3.request.Options
import kotlin.jvm.JvmField

/**
* A [Decoder] that ignores the [SourceFetchResult] and always returns the [Image] returned by
* [imageFactory].
*
* This is useful for skipping the decoding step, for instance when you only want to preload to disk
* and do not want to decode the image into memory.
*/
@ExperimentalCoilApi
class BlackholeDecoder(
private val imageFactory: () -> Image,
) : Decoder {

override suspend fun decode(): DecodeResult {
return DecodeResult(
image = imageFactory(),
isSampled = false,
)
}

class Factory(
private val imageFactory: () -> Image = { EMPTY_IMAGE },
) : Decoder.Factory {

override fun create(
result: SourceFetchResult,
options: Options,
imageLoader: ImageLoader,
) = BlackholeDecoder(imageFactory)

companion object {
@JvmField val EMPTY_IMAGE = object : Image {
override val size get() = 0L
override val width get() = -1
override val height get() = -1
override val shareable get() = true
override fun draw(canvas: Canvas) { /* Draw nothing. */ }
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package coil3.decode

import coil3.ImageLoader
import coil3.fetch.SourceFetchResult
import coil3.request.Options
import coil3.test.utils.RobolectricTest
import coil3.test.utils.context
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlinx.coroutines.test.runTest
import okio.Buffer
import okio.fakefilesystem.FakeFileSystem

class BlackholeDecoderTest : RobolectricTest() {

@Test
fun basic() = runTest {
val decoderFactory = BlackholeDecoder.Factory()
val bufferSize = 1024
val buffer = Buffer().apply { write(ByteArray(bufferSize)) }
val fetchResult = SourceFetchResult(
source = ImageSource(
source = buffer,
fileSystem = FakeFileSystem(),
),
mimeType = null,
dataSource = DataSource.MEMORY,
)
val decoder = decoderFactory.create(fetchResult, Options(context), ImageLoader(context))

assertEquals(BlackholeDecoder.Factory.EMPTY_IMAGE, decoder.decode().image)
assertEquals(bufferSize.toLong(), buffer.size)
}
}
26 changes: 24 additions & 2 deletions docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,33 @@ Yes! [Read here](java_compatibility.md).

## How do I preload an image?

[Read here](getting_started.md#preloading).
Launch an image request with no target:

```kotlin
val request = ImageRequest.Builder(context)
.data("https://www.example.com/image.jpg")
.build()
imageLoader.enqueue(request)
```

That will preload the image and save it to the disk and memory caches.

If you only want to preload to the disk cache you can skip decoding and saving to the memory cache like so:

```kotlin
val request = ImageRequest.Builder(context)
.data("https://www.example.com/image.jpg")
// Disables writing to the memory cache.
.memoryCachePolicy(CachePolicy.DISABLED)
// Skips the decode step so we don't waste time/memory decoding the image into memory.
.decoderFactory(BlackholeDecoder.Factory())
.build()
imageLoader.enqueue(request)
```

## How do I enable logging?

Set `logger(DebugLogger())` when [constructing your `ImageLoader`](getting_started.md#image-loaders).
Set `logger(DebugLogger())` when [constructing your `ImageLoader`](getting_started.md#configuring-the-singleton-imageloader).

!!! Note
`DebugLogger` should only be used in debug builds.
Expand Down

0 comments on commit 8032fd9

Please sign in to comment.