Skip to content

Commit

Permalink
Add gif support to network requests
Browse files Browse the repository at this point in the history
  • Loading branch information
gamepro65 committed Nov 29, 2022
1 parent d6538e0 commit f3616ff
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 3 deletions.
3 changes: 2 additions & 1 deletion picasso-sample/src/main/java/com/example/picasso/Data.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.example.picasso
internal object Data {
private const val BASE = "https://i.imgur.com/"
private const val EXT = ".jpg"
private const val GIF_EXT = ".gif"

@JvmField
val URLS = arrayOf(
Expand All @@ -32,6 +33,6 @@ internal object Data {
BASE + "Q54zMKT" + EXT, BASE + "9t6hLbm" + EXT, BASE + "F8n3Ic6" + EXT,
BASE + "P5ZRSvT" + EXT, BASE + "jbemFzr" + EXT, BASE + "8B7haIK" + EXT,
BASE + "aSeTYQr" + EXT, BASE + "OKvWoTh" + EXT, BASE + "zD3gT4Z" + EXT,
BASE + "z77CaIt" + EXT
BASE + "z77CaIt" + EXT, BASE + "B3wgCmW" + GIF_EXT
)
}
43 changes: 43 additions & 0 deletions picasso/src/main/java/com/squareup/picasso3/BitmapUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@ import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.ImageDecoder
import android.graphics.Movie
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.os.Build.VERSION
import android.util.TypedValue
import androidx.annotation.DrawableRes
import androidx.annotation.RequiresApi
import com.squareup.picasso3.Utils.isGifFile
import okio.Buffer
import okio.BufferedSource
import okio.ForwardingSource
Expand Down Expand Up @@ -186,6 +190,45 @@ internal object BitmapUtils {
}
}

/**
* Decode a byte stream into a Drawable. This method will take into account additional information
* about the supplied request in order to do the decoding efficiently.
*/
fun decodeDrawableStream(source: Source, request: Request): Drawable {
val exceptionCatchingSource = ExceptionCatchingSource(source)
val bufferedSource = exceptionCatchingSource.buffer()
val drawable = if (VERSION.SDK_INT >= 28) {
val imageSource = ImageDecoder.createSource(ByteBuffer.wrap(bufferedSource.readByteArray()))
decodeDrawableSourceP(imageSource, request)
} else {
if (isGifFile(bufferedSource)) {
val movie = Movie.decodeStream(bufferedSource.inputStream())
MovieDrawable(movie)
} else {
BitmapDrawable(decodeStreamPreP(request, bufferedSource))
}
}
exceptionCatchingSource.throwIfCaught()
return drawable
}

@RequiresApi(28)
fun decodeDrawableSourceP(imageSource: ImageDecoder.Source, request: Request): Drawable {
return ImageDecoder.decodeDrawable(imageSource) { imageDecoder, imageInfo, source ->
if (request.hasSize()) {
val size = imageInfo.size
val width = size.width
val height = size.height
val targetWidth = request.targetWidth
val targetHeight = request.targetHeight
if (shouldResize(request.onlyScaleDown, width, height, targetWidth, targetHeight)) {
val ratio = ratio(targetWidth, targetHeight, width, height, request)
imageDecoder.setTargetSize(width / ratio, height / ratio)
}
}
}
}

private fun ratio(
requestWidth: Int,
requestHeight: Int,
Expand Down
72 changes: 72 additions & 0 deletions picasso/src/main/java/com/squareup/picasso3/MovieDrawable.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright (C) 2022 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.picasso3

import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.Movie
import android.graphics.Paint
import android.graphics.PixelFormat
import android.graphics.drawable.Animatable
import android.graphics.drawable.Drawable
import android.os.SystemClock

internal class MovieDrawable(
private val movie: Movie
) : Drawable(), Animatable {
private var paint = Paint(Paint.ANTI_ALIAS_FLAG)
private var start = 0

override fun draw(canvas: Canvas) {
if (start > 0) {
movie.setTime(((SystemClock.uptimeMillis() - start).toInt()) % movie.duration())
invalidateSelf()
}

movie.draw(canvas, 0f, 0f, paint)
}

override fun setAlpha(alpha: Int) {
paint.alpha = alpha
}

override fun setColorFilter(colorFilter: ColorFilter?) {
paint.colorFilter = colorFilter
}

override fun getOpacity(): Int {
return PixelFormat.OPAQUE
}

override fun getIntrinsicWidth(): Int {
return movie.width()
}

override fun getIntrinsicHeight(): Int {
return movie.height()
}

override fun start() {
start = SystemClock.uptimeMillis().toInt()
invalidateSelf()
}

override fun stop() {
start = 0
}

override fun isRunning() = start != 0
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@
package com.squareup.picasso3

import android.net.NetworkInfo
import com.squareup.picasso3.BitmapUtils.decodeDrawableStream
import com.squareup.picasso3.BitmapUtils.decodeStream
import com.squareup.picasso3.NetworkPolicy.Companion.isOfflineOnly
import com.squareup.picasso3.NetworkPolicy.Companion.shouldReadFromDiskCache
import com.squareup.picasso3.NetworkPolicy.Companion.shouldWriteToDiskCache
import com.squareup.picasso3.Picasso.LoadedFrom.DISK
import com.squareup.picasso3.Picasso.LoadedFrom.NETWORK
import com.squareup.picasso3.Utils.isGifFile
import okhttp3.CacheControl
import okhttp3.Call
import okhttp3.Response
Expand Down Expand Up @@ -67,8 +69,15 @@ internal class NetworkRequestHandler(
picasso.downloadFinished(body.contentLength())
}
try {
val bitmap = decodeStream(body!!.source(), request)
callback.onSuccess(Result.Bitmap(bitmap, loadedFrom))
val bufferedSource = body!!.source()
val result = if (isGifFile(bufferedSource)) {
val drawable = decodeDrawableStream(bufferedSource, request)
Result.Drawable(drawable, loadedFrom)
} else {
val bitmap = decodeStream(bufferedSource, request)
Result.Bitmap(bitmap, loadedFrom)
}
callback.onSuccess(result)
} catch (e: IOException) {
body!!.close()
callback.onError(e)
Expand Down
7 changes: 7 additions & 0 deletions picasso/src/main/java/com/squareup/picasso3/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ internal object Utils {
*/
private val WEBP_FILE_HEADER_RIFF: ByteString = "RIFF".encodeUtf8()
private val WEBP_FILE_HEADER_WEBP: ByteString = "WEBP".encodeUtf8()
private val GIF_FILE_HEADER_87A: ByteString = "GIF87a".encodeUtf8()
private val GIF_FILE_HEADER_89A: ByteString = "GIF89a".encodeUtf8()

fun <T> checkNotNull(value: T?, message: String?): T {
if (value == null) {
Expand Down Expand Up @@ -178,6 +180,11 @@ internal object Utils {
source.rangeEquals(8, WEBP_FILE_HEADER_WEBP)
}

fun isGifFile(source: BufferedSource): Boolean {
return source.rangeEquals(0, GIF_FILE_HEADER_87A) ||
source.rangeEquals(0, GIF_FILE_HEADER_89A)
}

fun getResourceId(resources: Resources, data: Request): Int {
if (data.resourceId != 0 || data.uri == null) {
return data.resourceId
Expand Down
6 changes: 6 additions & 0 deletions picasso/src/test/java/com/squareup/picasso3/UtilsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.squareup.picasso3.TestUtils.RESOURCE_ID_URI
import com.squareup.picasso3.TestUtils.RESOURCE_TYPE_URI
import com.squareup.picasso3.TestUtils.URI_1
import com.squareup.picasso3.TestUtils.mockPackageResourceContext
import com.squareup.picasso3.Utils.isGifFile
import com.squareup.picasso3.Utils.isWebPFile
import okio.Buffer
import org.junit.Test
Expand Down Expand Up @@ -61,6 +62,11 @@ class UtilsTest {
assertThat(isWebPFile(Buffer().writeUtf8("RIFFxxWEBP"))).isFalse()
}

@Test fun detectGifFile() {
assertThat(isGifFile(Buffer().writeUtf8("GIF87a"))).isTrue()
assertThat(isGifFile(Buffer().writeUtf8("GIF89a"))).isTrue()
}

@Test fun ensureBuilderIsCleared() {
Request.Builder(RESOURCE_ID_URI).build()
assertThat(Utils.MAIN_THREAD_KEY_BUILDER.length).isEqualTo(0)
Expand Down

0 comments on commit f3616ff

Please sign in to comment.