Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add gif support to network requests #2342

Open
wants to merge 1 commit into
base: cdrury/hunterDrawable
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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