Commit 800b4657 authored by MozLando's avatar MozLando
Browse files

Merge #7534 #7625

7534: Closes #7533 - Allow loading a thumbnail at maximum size. r=jonalmeida a=person808



7625: Update LeakCanary to 2.4 and add to Sample Browser r=pocmo,JohanLorenzo,jonalmeida a=csadilek

Sets up LeakCanary in Sample Browser. With the upgrade to 2.4 we also don't need custom code for instrumented tests anymore: https://square.github.io/leakcanary/upgrading-to-leakcanary-2.0/

Co-authored-by: default avatarKainalu Hagiwara <kainaluh808@gmail.com>
Co-authored-by: default avatarChristian Sadilek <christian.sadilek@gmail.com>
......@@ -25,7 +25,7 @@ object Versions {
const val zxing = "3.3.0"
const val jna = "5.2.0"
const val disklrucache = "2.0.2"
const val leakcanary = "1.6.3"
const val leakcanary = "2.4"
const val mozilla_appservices = "61.0.7"
......@@ -116,6 +116,8 @@ object Dependencies {
const val google_material = "com.google.android.material:material:${Versions.material}"
const val google_nearby = "com.google.android.gms:play-services-nearby:${Versions.nearby}"
const val leakcanary = "com.squareup.leakcanary:leakcanary-android:${Versions.leakcanary}"
const val tools_dokka = "org.jetbrains.dokka:dokka-android-gradle-plugin:${Versions.dokka}"
const val tools_androidgradle = "com.android.tools.build:gradle:${Versions.android_gradle_plugin}"
const val tools_kotlingradle = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}"
......
......@@ -8,14 +8,17 @@ import android.content.res.ColorStateList
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.Dimension
import androidx.annotation.Dimension.DP
import androidx.appcompat.widget.AppCompatImageButton
import androidx.recyclerview.widget.RecyclerView
import mozilla.components.browser.tabstray.thumbnail.TabThumbnailView
import mozilla.components.concept.tabstray.Tab
import mozilla.components.concept.tabstray.TabsTray
import mozilla.components.support.base.observer.Observable
import mozilla.components.support.images.ImageRequest
import mozilla.components.support.images.ImageLoadRequest
import mozilla.components.support.images.loader.ImageLoader
import mozilla.components.support.ktx.android.util.dpToPx
import mozilla.components.support.ktx.kotlin.tryGetHostFromUrl
/**
......@@ -84,11 +87,18 @@ class DefaultTabViewHolder(
// In the final else case, we have no cache or fresh screenshot; do nothing instead of clearing the image.
if (thumbnailLoader != null && tab.thumbnail == null) {
thumbnailLoader.loadIntoView(thumbnailView, ImageRequest(tab.id))
val thumbnailSize = THUMBNAIL_SIZE.dpToPx(thumbnailView.context.resources.displayMetrics)
thumbnailLoader.loadIntoView(
thumbnailView,
ImageLoadRequest(id = tab.id, size = thumbnailSize))
} else if (tab.thumbnail != null) {
thumbnailView.setImageBitmap(tab.thumbnail)
}
iconView?.setImageBitmap(tab.icon)
}
companion object {
@Dimension(unit = DP) private const val THUMBNAIL_SIZE = 100
}
}
......@@ -13,7 +13,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.components.concept.tabstray.Tab
import mozilla.components.concept.tabstray.TabsTray
import mozilla.components.support.base.observer.ObserverRegistry
import mozilla.components.support.images.ImageRequest
import mozilla.components.support.images.ImageLoadRequest
import mozilla.components.support.images.loader.ImageLoader
import mozilla.components.support.test.any
import mozilla.components.support.test.eq
......@@ -162,11 +162,11 @@ class DefaultTabViewHolderTest {
viewHolder.bind(tabWithThumbnail, false, mock())
verify(loader, never()).loadIntoView(any(), eq(ImageRequest("123")), nullable(), nullable())
verify(loader, never()).loadIntoView(any(), eq(ImageLoadRequest("123", 100)), nullable(), nullable())
viewHolder.bind(tab, false, mock())
verify(loader).loadIntoView(any(), eq(ImageRequest("123")), nullable(), nullable())
verify(loader).loadIntoView(any(), eq(ImageLoadRequest("123", 100)), nullable(), nullable())
}
@Test
......
......@@ -11,7 +11,6 @@ import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.thumbnails.storage.ThumbnailStorage
import mozilla.components.lib.state.Middleware
import mozilla.components.lib.state.MiddlewareStore
import mozilla.components.support.images.ImageRequest
/**
* [Middleware] implementation for handling [ContentAction.UpdateThumbnailAction] and storing
......@@ -29,7 +28,7 @@ class ThumbnailsMiddleware(
is ContentAction.UpdateThumbnailAction -> {
// Store the captured tab screenshot from the EngineView when the session's
// thumbnail is updated.
thumbnailStorage.saveThumbnail(ImageRequest(action.sessionId), action.thumbnail)
thumbnailStorage.saveThumbnail(action.sessionId, action.thumbnail)
}
is TabListAction.RemoveAllNormalTabsAction -> {
store.state.tabs.filterNot { it.content.private }.forEach { tab ->
......
......@@ -15,7 +15,7 @@ import kotlinx.coroutines.launch
import mozilla.components.browser.thumbnails.R
import mozilla.components.browser.thumbnails.storage.ThumbnailStorage
import mozilla.components.support.images.CancelOnDetach
import mozilla.components.support.images.ImageRequest
import mozilla.components.support.images.ImageLoadRequest
import mozilla.components.support.images.loader.ImageLoader
import java.lang.ref.WeakReference
......@@ -26,7 +26,7 @@ class ThumbnailLoader(private val storage: ThumbnailStorage) : ImageLoader {
override fun loadIntoView(
view: ImageView,
request: ImageRequest,
request: ImageLoadRequest,
placeholder: Drawable?,
error: Drawable?
) {
......@@ -38,7 +38,7 @@ class ThumbnailLoader(private val storage: ThumbnailStorage) : ImageLoader {
@MainThread
private suspend fun loadIntoViewInternal(
view: WeakReference<ImageView>,
request: ImageRequest,
request: ImageLoadRequest,
placeholder: Drawable?,
error: Drawable?
) {
......
......@@ -18,7 +18,8 @@ import mozilla.components.browser.thumbnails.R
import mozilla.components.browser.thumbnails.utils.ThumbnailDiskCache
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.images.DesiredSize
import mozilla.components.support.images.ImageRequest
import mozilla.components.support.images.ImageLoadRequest
import mozilla.components.support.images.ImageSaveRequest
import mozilla.components.support.images.decoder.AndroidImageDecoder
import java.util.concurrent.Executors
......@@ -63,9 +64,9 @@ class ThumbnailStorage(
}
/**
* Asynchronously loads a thumbnail [Bitmap] for the given [ImageRequest].
* Asynchronously loads a thumbnail [Bitmap] for the given [ImageLoadRequest].
*/
fun loadThumbnail(request: ImageRequest): Deferred<Bitmap?> = scope.async {
fun loadThumbnail(request: ImageLoadRequest): Deferred<Bitmap?> = scope.async {
loadThumbnailInternal(request).also { loadedThumbnail ->
if (loadedThumbnail != null) {
logger.debug(
......@@ -79,9 +80,9 @@ class ThumbnailStorage(
}
@WorkerThread
private fun loadThumbnailInternal(request: ImageRequest): Bitmap? {
private fun loadThumbnailInternal(request: ImageLoadRequest): Bitmap? {
val desiredSize = DesiredSize(
targetSize = context.resources.getDimensionPixelSize(request.size.dimen),
targetSize = request.size,
maxSize = maximumSize,
maxScaleFactor = MAXIMUM_SCALE_FACTOR
)
......@@ -96,13 +97,13 @@ class ThumbnailStorage(
}
/**
* Stores the given thumbnail [Bitmap] into the disk cache with the provided [ImageRequest]
* Stores the given thumbnail [Bitmap] into the disk cache with the provided [ImageLoadRequest]
* as its key.
*/
fun saveThumbnail(request: ImageRequest, bitmap: Bitmap): Job =
fun saveThumbnail(request: ImageSaveRequest, bitmap: Bitmap): Job =
scope.launch {
logger.debug(
"Saved thumbnail to disk (id = ${request.id}, " +
"Saved thumbnail to disk (id = $request, " +
"generationId = ${bitmap.generationId})"
)
sharedDiskCache.putThumbnailBitmap(context, request, bitmap)
......
......@@ -8,7 +8,8 @@ import android.content.Context
import android.graphics.Bitmap
import com.jakewharton.disklrucache.DiskLruCache
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.images.ImageRequest
import mozilla.components.support.images.ImageLoadRequest
import mozilla.components.support.images.ImageSaveRequest
import java.io.File
import java.io.IOException
......@@ -35,10 +36,10 @@ class ThumbnailDiskCache {
* Retrieves the thumbnail data from the disk cache for the given session ID or URL.
*
* @param context the application [Context].
* @param request [ImageRequest] providing the session ID or URL of the thumbnail to retrieve.
* @param request [ImageLoadRequest] providing the session ID or URL of the thumbnail to retrieve.
* @return the [ByteArray] of the thumbnail or null if the snapshot of the entry does not exist.
*/
internal fun getThumbnailData(context: Context, request: ImageRequest): ByteArray? {
internal fun getThumbnailData(context: Context, request: ImageLoadRequest): ByteArray? {
val snapshot = getThumbnailCache(context).get(request.id) ?: return null
return try {
......@@ -55,14 +56,14 @@ class ThumbnailDiskCache {
* Stores the given session ID or URL's thumbnail [Bitmap] into the disk cache.
*
* @param context the application [Context].
* @param request [ImageRequest] providing the session ID or URL of the thumbnail to retrieve.
* @param request [ImageSaveRequest] providing the session ID or URL of the thumbnail to retrieve.
* @param bitmap the thumbnail [Bitmap] to store.
*/
internal fun putThumbnailBitmap(context: Context, request: ImageRequest, bitmap: Bitmap) {
internal fun putThumbnailBitmap(context: Context, request: ImageSaveRequest, bitmap: Bitmap) {
try {
synchronized(thumbnailCacheWriteLock) {
val editor = getThumbnailCache(context)
.edit(request.id) ?: return
.edit(request) ?: return
editor.newOutputStream(0).use { stream ->
bitmap.compress(Bitmap.CompressFormat.WEBP, WEBP_QUALITY, stream)
......
......@@ -2,6 +2,7 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<resources xmlns:tools="http://schemas.android.com/tools">
<dimen name="mozac_browser_thumbnails_maximum_size" tools:ignore="PxUsage">2153px</dimen>
<resources>
<!-- Maximum size to save thumbnails at. We want full size thumbnails, so we use a large value -->
<dimen name="mozac_browser_thumbnails_maximum_size">99999dp</dimen>
</resources>
......@@ -12,7 +12,6 @@ import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.createTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.browser.thumbnails.storage.ThumbnailStorage
import mozilla.components.support.images.ImageRequest
import mozilla.components.support.test.ext.joinBlocking
import mozilla.components.support.test.mock
import org.junit.Test
......@@ -24,7 +23,7 @@ class ThumbnailsMiddlewareTest {
@Test
fun `thumbnail storage stores the provided thumbnail on update thumbnail action`() {
val request = ImageRequest("test-tab1")
val request = "test-tab1"
val tab = createTab("https://www.mozilla.org", id = "test-tab1")
val thumbnailStorage: ThumbnailStorage = mock()
val store = BrowserStore(
......@@ -33,7 +32,7 @@ class ThumbnailsMiddlewareTest {
)
val bitmap: Bitmap = mock()
store.dispatch(ContentAction.UpdateThumbnailAction(request.id, bitmap)).joinBlocking()
store.dispatch(ContentAction.UpdateThumbnailAction(request, bitmap)).joinBlocking()
verify(thumbnailStorage).saveThumbnail(request, bitmap)
}
......
......@@ -16,8 +16,7 @@ import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import mozilla.components.browser.thumbnails.R
import mozilla.components.browser.thumbnails.storage.ThumbnailStorage
import mozilla.components.support.images.ImageRequest
import mozilla.components.support.images.ImageRequest.Size
import mozilla.components.support.images.ImageLoadRequest
import mozilla.components.support.test.any
import mozilla.components.support.test.eq
import mozilla.components.support.test.mock
......@@ -57,7 +56,7 @@ class ThumbnailLoaderTest {
val view: ImageView = mock()
val storage: ThumbnailStorage = mock()
val loader = spy(ThumbnailLoader(storage))
val request = ImageRequest("123", Size.DEFAULT)
val request = ImageLoadRequest("123", 100)
doReturn(result).`when`(storage).loadThumbnail(request)
......@@ -83,7 +82,7 @@ class ThumbnailLoaderTest {
val error: Drawable = mock()
val storage: ThumbnailStorage = mock()
val loader = spy(ThumbnailLoader(storage))
val request = ImageRequest("123", Size.DEFAULT)
val request = ImageLoadRequest("123", 100)
doReturn(result).`when`(storage).loadThumbnail(request)
......@@ -105,7 +104,7 @@ class ThumbnailLoaderTest {
val previousJob: Job = mock()
val storage: ThumbnailStorage = mock()
val loader = spy(ThumbnailLoader(storage))
val request = ImageRequest("123")
val request = ImageLoadRequest("123", 100)
doReturn(previousJob).`when`(view).getTag(R.id.mozac_browser_thumbnails_tag_job)
doReturn(result).`when`(storage).loadThumbnail(request)
......
......@@ -8,7 +8,7 @@ import android.graphics.Bitmap
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.runBlocking
import mozilla.components.support.images.ImageRequest
import mozilla.components.support.images.ImageLoadRequest
import mozilla.components.support.test.ext.joinBlocking
import mozilla.components.support.test.mock
import mozilla.components.support.test.robolectric.testContext
......@@ -39,57 +39,56 @@ class ThumbnailStorageTest {
val bitmap: Bitmap = mock()
val thumbnailStorage = spy(ThumbnailStorage(testContext, testDispatcher))
thumbnailStorage.saveThumbnail(ImageRequest("test-tab1"), bitmap).joinBlocking()
thumbnailStorage.saveThumbnail(ImageRequest("test-tab2"), bitmap).joinBlocking()
var thumbnail1 = thumbnailStorage.loadThumbnail(ImageRequest("test-tab1")).await()
var thumbnail2 = thumbnailStorage.loadThumbnail(ImageRequest("test-tab2")).await()
thumbnailStorage.saveThumbnail("test-tab1", bitmap).joinBlocking()
thumbnailStorage.saveThumbnail("test-tab2", bitmap).joinBlocking()
var thumbnail1 = thumbnailStorage.loadThumbnail(ImageLoadRequest("test-tab1", 100)).await()
var thumbnail2 = thumbnailStorage.loadThumbnail(ImageLoadRequest("test-tab2", 100)).await()
assertNotNull(thumbnail1)
assertNotNull(thumbnail2)
thumbnailStorage.clearThumbnails()
thumbnail1 = thumbnailStorage.loadThumbnail(ImageRequest("test-tab1")).await()
thumbnail2 = thumbnailStorage.loadThumbnail(ImageRequest("test-tab2")).await()
thumbnail1 = thumbnailStorage.loadThumbnail(ImageLoadRequest("test-tab1", 100)).await()
thumbnail2 = thumbnailStorage.loadThumbnail(ImageLoadRequest("test-tab2", 100)).await()
assertNull(thumbnail1)
assertNull(thumbnail2)
}
@Test
fun `deleteThumbnail`() = runBlocking {
val id = "test-tab1"
val request = ImageRequest(id)
val request = "test-tab1"
val bitmap: Bitmap = mock()
val thumbnailStorage = spy(ThumbnailStorage(testContext, testDispatcher))
thumbnailStorage.saveThumbnail(request, bitmap).joinBlocking()
var thumbnail = thumbnailStorage.loadThumbnail(request).await()
var thumbnail = thumbnailStorage.loadThumbnail(ImageLoadRequest(request, 100)).await()
assertNotNull(thumbnail)
thumbnailStorage.deleteThumbnail(id).joinBlocking()
thumbnail = thumbnailStorage.loadThumbnail(request).await()
thumbnailStorage.deleteThumbnail(request).joinBlocking()
thumbnail = thumbnailStorage.loadThumbnail(ImageLoadRequest(request, 100)).await()
assertNull(thumbnail)
}
@Test
fun `saveThumbnail`() = runBlocking {
val request = ImageRequest("test-tab1")
val request = ImageLoadRequest("test-tab1", 100)
val bitmap: Bitmap = mock()
val thumbnailStorage = spy(ThumbnailStorage(testContext))
var thumbnail = thumbnailStorage.loadThumbnail(request).await()
assertNull(thumbnail)
thumbnailStorage.saveThumbnail(request, bitmap).joinBlocking()
thumbnailStorage.saveThumbnail(request.id, bitmap).joinBlocking()
thumbnail = thumbnailStorage.loadThumbnail(request).await()
assertNotNull(thumbnail)
}
@Test
fun `loadThumbnail`() = runBlocking {
val request = ImageRequest("test-tab1")
val request = ImageLoadRequest("test-tab1", 100)
val bitmap: Bitmap = mock()
val thumbnailStorage = spy(ThumbnailStorage(testContext, testDispatcher))
thumbnailStorage.saveThumbnail(request, bitmap)
thumbnailStorage.saveThumbnail(request.id, bitmap)
`when`(thumbnailStorage.loadThumbnail(request)).thenReturn(CompletableDeferred(bitmap))
val thumbnail = thumbnailStorage.loadThumbnail(request).await()
......
......@@ -6,7 +6,7 @@ package mozilla.components.browser.thumbnails.utils
import android.graphics.Bitmap
import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.components.support.images.ImageRequest
import mozilla.components.support.images.ImageLoadRequest
import mozilla.components.support.test.any
import mozilla.components.support.test.mock
import mozilla.components.support.test.robolectric.testContext
......@@ -25,7 +25,7 @@ class ThumbnailDiskCacheTest {
@Test
fun `Writing and reading bitmap bytes`() {
val cache = ThumbnailDiskCache()
val request = ImageRequest("123")
val request = ImageLoadRequest("123", 100)
val bitmap: Bitmap = mock()
Mockito.`when`(bitmap.compress(any(), ArgumentMatchers.anyInt(), any())).thenAnswer {
......@@ -40,7 +40,7 @@ class ThumbnailDiskCacheTest {
true
}
cache.putThumbnailBitmap(testContext, request, bitmap)
cache.putThumbnailBitmap(testContext, request.id, bitmap)
val data = cache.getThumbnailData(testContext, request)
assertNotNull(data!!)
......@@ -50,10 +50,10 @@ class ThumbnailDiskCacheTest {
@Test
fun `Removing bitmap from disk cache`() {
val cache = ThumbnailDiskCache()
val request = ImageRequest("123")
val request = ImageLoadRequest("123", 100)
val bitmap: Bitmap = mock()
cache.putThumbnailBitmap(testContext, request, bitmap)
cache.putThumbnailBitmap(testContext, request.id, bitmap)
var data = cache.getThumbnailData(testContext, request)
assertNotNull(data!!)
......@@ -65,10 +65,10 @@ class ThumbnailDiskCacheTest {
@Test
fun `Clearing bitmap from disk cache`() {
val cache = ThumbnailDiskCache()
val request = ImageRequest("123")
val request = ImageLoadRequest("123", 100)
val bitmap: Bitmap = mock()
cache.putThumbnailBitmap(testContext, request, bitmap)
cache.putThumbnailBitmap(testContext, request.id, bitmap)
var data = cache.getThumbnailData(testContext, request)
assertNotNull(data!!)
......
......@@ -41,7 +41,7 @@ class SwipeRefreshFeature(
* Start feature: Starts adding pull to refresh behavior for the active session.
*/
override fun start() {
store.flowScoped { flow ->
scope = store.flowScoped { flow ->
flow.map { state -> state.findTabOrCustomTabOrSelectedTab(tabId) }
.map { tab -> tab?.content?.loading ?: false }
.ifChanged()
......
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.support.android.test.leaks
import android.app.Application
import androidx.test.core.app.ApplicationProvider
import com.squareup.leakcanary.InstrumentationLeakDetector
import org.junit.rules.MethodRule
import org.junit.runners.model.FrameworkMethod
import org.junit.runners.model.Statement
/**
* JUnit rule that will install LeakCanary to detect memory leaks happening while the instrumented tests are running.
*
* If a leak is found the test will fail and the test report will contain information about the leak.
*
* Note that additionally to adding this rule you need to add the following instrumentation argument:
*
* ```
* android {
* defaultConfig {
* // ...
*
* testInstrumentationRunnerArgument "listener", "com.squareup.leakcanary.FailTestOnLeakRunListener"
* }
* }
* ```
*/
class LeakDetectionRule : MethodRule {
override fun apply(base: Statement, method: FrameworkMethod, target: Any): Statement {
return object : Statement() {
override fun evaluate() {
try {
val application = ApplicationProvider.getApplicationContext<Application>()
InstrumentationLeakDetector.instrumentationRefWatcher(application)
.buildAndInstall()
} catch (e: UnsupportedOperationException) {
// Ignore
}
base.evaluate()
}
}
}
}
......@@ -4,23 +4,20 @@
package mozilla.components.support.images
import androidx.annotation.DimenRes
import androidx.annotation.Px
/**
* A request to save an image. This is an alias for the id of the image.
*/
typealias ImageSaveRequest = String
/**
* A request to load an image.
*
* @property id The id of the image to retrieve.
* @property size The preferred size of the image that should be loaded.
* @property size The preferred size of the image that should be loaded in pixels.
*/
data class ImageRequest(
data class ImageLoadRequest(
val id: String,
val size: Size = Size.DEFAULT
) {
/**
* Supported sizes.
*/
enum class Size(@DimenRes val dimen: Int) {
DEFAULT(R.dimen.mozac_support_images_size_default)
}
}
@Px val size: Int
)
......@@ -2,11 +2,12 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.browser.thumbnails.ext
package mozilla.components.support.images.ext
import android.widget.ImageView
import mozilla.components.browser.thumbnails.loader.ThumbnailLoader
import mozilla.components.support.images.ImageRequest
import mozilla.components.support.images.ImageLoadRequest
import mozilla.components.support.images.loader.ImageLoader
import kotlin.math.max
/**
* Loads an image asynchronously and then displays it in the [ImageView].
......@@ -15,4 +16,5 @@ import mozilla.components.support.images.ImageRequest
* @param view [ImageView] to load the image into.
* @param id Load image for this given ID.
*/
fun ThumbnailLoader.loadIntoView(view: ImageView, id: String) = loadIntoView(view, ImageRequest(id))
fun ImageLoader.loadIntoView(view: ImageView, id: String) =
loadIntoView(view, ImageLoadRequest(id, max(view.height, view.width)))
......@@ -7,7 +7,7 @@ package mozilla.components.support.images.loader
import android.graphics.drawable.Drawable
import android.widget.ImageView
import androidx.annotation.MainThread
import mozilla.components.support.images.ImageRequest
import mozilla.components.support.images.ImageLoadRequest
/**
* A loader that can load an image from an ID directly into an [ImageView].
......@@ -19,14 +19,14 @@ interface ImageLoader {
* If the view is detached from the window before loading is completed, then loading is cancelled.
*
* @param view [ImageView] to load the image into.
* @param request [ImageRequest] Load image for this given request.
* @param request [ImageLoadRequest] Load image for this given request.