Skip to content

Commit

Permalink
Fix #1020: Rendering of Svg images from server. (#1019)
Browse files Browse the repository at this point in the history
* Added Svgencoder

* Update UrlImageParser.kt

* updated kdoc

* Update SvgSoftwareLayerSetter.kt

* Update SvgEncoder.kt

* Update HtmlParserTest.kt

* Update RepositoryGlideModule.kt

* Update RepositoryGlideModule.kt

* Update RepositoryGlideModule.kt

* Update SvgSoftwareLayerSetter.kt

* Update RepositoryGlideModule.kt

* Update UrlImageParser.kt

* update changes.

* updated changes.

* updated nit changes

* Fixed lint errors

* updated new changes

* removed encoder file.

* nit changes

* Update GlideImageLoader.kt

* updated from PictureDrawable to Picture.

* Update UrlImageParser.kt

* Update GlideImageLoader.kt
  • Loading branch information
veena14cs committed May 12, 2020
1 parent 5b5c6f9 commit d16d6d6
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 20 deletions.
10 changes: 0 additions & 10 deletions app/src/sharedTest/java/org/oppia/app/parser/HtmlParserTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -151,16 +151,6 @@ class HtmlParserTest {
assertThat(bulletSpan1).isNotNull()
}

class FakeImageLoader : ImageLoader {
override fun load(imageUrl: String, target: CustomTarget<Bitmap>) {

}
}

private fun getResources(): Resources {
return ApplicationProvider.getApplicationContext<Context>().resources
}

@Qualifier annotation class TestDispatcher

// TODO(#89): Move this to a common test application component.
Expand Down
1 change: 1 addition & 0 deletions utility/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ dependencies {
implementation(
'androidx.appcompat:appcompat:1.0.2',
'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha03',
'com.caverock:androidsvg-aar:1.4',
'com.github.bumptech.glide:glide:4.9.0',
'com.google.dagger:dagger:2.24',
"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version",
Expand Down
22 changes: 22 additions & 0 deletions utility/src/main/java/org/oppia/util/parser/GlideImageLoader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package org.oppia.util.parser

import android.content.Context
import android.graphics.Bitmap
import android.graphics.Picture
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.CustomTarget
import javax.inject.Inject
import org.oppia.util.caching.AssetRepository
Expand All @@ -14,6 +17,7 @@ class GlideImageLoader @Inject constructor(
@CacheAssetsLocally private val cacheAssetsLocally: Boolean,
private val assetRepository: AssetRepository
) : ImageLoader {

override fun load(imageUrl: String, target: CustomTarget<Bitmap>) {
val model: Any = if (cacheAssetsLocally) {
object : ImageAssetFetcher {
Expand All @@ -27,4 +31,22 @@ class GlideImageLoader @Inject constructor(
.load(model)
.into(target)
}

override fun loadSvg(imageUrl: String, target: CustomTarget<Picture>) {
val model: Any = if (cacheAssetsLocally) {
object : ImageAssetFetcher {
override fun fetchImage(): ByteArray = assetRepository.loadRemoteBinaryAsset(imageUrl)()

override fun getImageIdentifier(): String = imageUrl
}
} else imageUrl

// TODO(#45): Ensure the image caching flow is properly hooked up.
Glide.with(context)
.`as`(Picture::class.java)
.fitCenter()
.apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.NONE))
.load(model)
.into(target)
}
}
3 changes: 3 additions & 0 deletions utility/src/main/java/org/oppia/util/parser/ImageLoader.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package org.oppia.util.parser

import android.graphics.Bitmap
import android.graphics.Picture
import com.bumptech.glide.request.target.CustomTarget

/** Loads an image from the provided URL into the specified target, optionally caching it. */
interface ImageLoader {

fun load(imageUrl: String, target: CustomTarget<Bitmap>)

fun loadSvg(imageUrl: String, target: CustomTarget<Picture>)
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
package org.oppia.util.parser

import android.content.Context
import android.graphics.Picture
import com.bumptech.glide.Glide
import com.bumptech.glide.Registry
import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.module.AppGlideModule
import com.caverock.androidsvg.SVG
import java.io.InputStream
import org.oppia.util.caching.AssetRepository

/** Custom [AppGlideModule] to enable loading images from [AssetRepository] via Glide. */
@GlideModule
class RepositoryGlideModule : AppGlideModule() {
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
registry.prepend(ImageAssetFetcher::class.java, InputStream::class.java, RepositoryModelLoader.Factory())
// TODO(#1039): Introduce custom type OppiaImage for rendering Bitmap and Svg.
registry.register(SVG::class.java, Picture::class.java, SvgDrawableTranscoder())
.append(InputStream::class.java, SVG::class.java, SvgDecoder())
.append(ImageAssetFetcher::class.java, InputStream::class.java, RepositoryModelLoader.Factory())
}
}
31 changes: 31 additions & 0 deletions utility/src/main/java/org/oppia/util/parser/SvgDecoder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.oppia.util.parser

import com.bumptech.glide.load.Options
import com.bumptech.glide.load.ResourceDecoder
import com.bumptech.glide.load.engine.Resource
import com.bumptech.glide.load.resource.SimpleResource
import com.caverock.androidsvg.SVG
import com.caverock.androidsvg.SVGParseException
import java.io.IOException
import java.io.InputStream

/** Decodes an SVG internal representation from an {@link InputStream}. */
class SvgDecoder : ResourceDecoder<InputStream?, SVG?> {

override fun handles(source: InputStream, options: Options): Boolean {
return true
}

override fun decode(
source: InputStream,
width: Int,
height: Int,
options: Options
): Resource<SVG?>? {
return try {
SimpleResource(source.use { SVG.getFromInputStream(it) })
} catch (ex: SVGParseException) {
throw IOException("Cannot load SVG from stream", ex)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.oppia.util.parser

import android.graphics.Picture
import com.bumptech.glide.load.Options
import com.bumptech.glide.load.engine.Resource
import com.bumptech.glide.load.resource.SimpleResource
import com.bumptech.glide.load.resource.transcode.ResourceTranscoder
import com.caverock.androidsvg.SVG

/** SvgDrawableTranscoder converts SVG to PictureDrawable. */
class SvgDrawableTranscoder : ResourceTranscoder<SVG?, Picture?> {
override fun transcode(
toTranscode: Resource<SVG?>,
options: Options
): Resource<Picture?>? {
val svg: SVG = toTranscode.get()
val picture = svg.renderToPicture()
return SimpleResource(picture)
}
}
41 changes: 32 additions & 9 deletions utility/src/main/java/org/oppia/util/parser/UrlImageParser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package org.oppia.util.parser
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Picture
import android.graphics.Rect
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.PictureDrawable
import android.text.Html
import android.view.ViewTreeObserver
import android.widget.TextView
Expand Down Expand Up @@ -36,21 +38,41 @@ class UrlImageParser private constructor(
override fun getDrawable(urlString: String): Drawable {
val imageUrl = String.format(imageDownloadUrlTemplate, entityType, entityId, urlString)
val urlDrawable = UrlDrawable()
val target = BitmapTarget(urlDrawable)
imageLoader.load(
gcsPrefix + gcsResource + imageUrl,
target
)
// TODO(#1039): Introduce custom type OppiaImage for rendering Bitmap and Svg.
if (imageUrl.endsWith("svg", ignoreCase = true)) {
val target = SvgTarget(urlDrawable)
imageLoader.loadSvg(
gcsPrefix + gcsResource + imageUrl,
target
)
} else {
val target = BitmapTarget(urlDrawable)
imageLoader.load(
gcsPrefix + gcsResource + imageUrl,
target
)
}
return urlDrawable
}

private inner class BitmapTarget(private val urlDrawable: UrlDrawable) : CustomTarget<Bitmap>() {
private inner class BitmapTarget(urlDrawable: UrlDrawable) : CustomImageTarget<Bitmap>(
urlDrawable, { resource -> BitmapDrawable(context.resources, resource) }
)

private inner class SvgTarget(urlDrawable: UrlDrawable) : CustomImageTarget<Picture>(
urlDrawable, { resource -> PictureDrawable(resource) }
)

private open inner class CustomImageTarget<T>(
private val urlDrawable: UrlDrawable,
private val drawableFactory: (T) -> Drawable
) : CustomTarget<T>() {
override fun onLoadCleared(placeholder: Drawable?) {
// No resources to clear.
}

override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
val drawable = BitmapDrawable(context.resources, resource)
override fun onResourceReady(resource: T, transition: Transition<in T>?) {
val drawable = drawableFactory(resource)
htmlContentTextView.post {
htmlContentTextView.width {
val drawableHeight = drawable.intrinsicHeight
Expand All @@ -60,7 +82,8 @@ class UrlImageParser private constructor(
} else {
0
}
val rect = Rect(initialDrawableMargin, 0, drawableWidth + initialDrawableMargin, drawableHeight)
val rect =
Rect(initialDrawableMargin, 0, drawableWidth + initialDrawableMargin, drawableHeight)
drawable.bounds = rect
urlDrawable.bounds = rect
urlDrawable.drawable = drawable
Expand Down

0 comments on commit d16d6d6

Please sign in to comment.