Unverified Commit cf8b2b50 authored by Lucio Maciel's avatar Lucio Maciel Committed by GitHub

Merge pull request #621 from filipedelimabrito/improvement/svg-support

[IMPROVEMENT] SVG support
parents 046b60b9 d176a339
......@@ -71,6 +71,8 @@ dependencies {
implementation libraries.floatingSearchView
implementation libraries.androidSvg
testImplementation libraries.junit
androidTestImplementation (libraries.expressoCore , {
exclude group: 'com.android.support', module: 'support-annotations'
......
......@@ -30,7 +30,7 @@ class LayoutHelper {
childOfContent?.viewTreeObserver?.addOnGlobalLayoutListener(listener)
frameLayoutParams = childOfContent?.layoutParams as FrameLayout.LayoutParams
} catch (exception : ClassCastException) {
// TODO: are we using the android.util.Log for logging that type of errors?
// TODO: are we using the android.util.Log for logging that type of errors? or should we use the SDK logger?
Log.e("ERROR", exception.message)
}
}
......
......@@ -3,8 +3,11 @@ package chat.rocket.android.app
import android.app.Activity
import android.app.Application
import chat.rocket.android.BuildConfig
import chat.rocket.android.app.utils.CustomImageFormatConfigurator
import com.facebook.drawee.backends.pipeline.DraweeConfig
import chat.rocket.android.dagger.DaggerAppComponent
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.core.ImagePipelineConfig
import com.jakewharton.threetenabp.AndroidThreeTen
import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector
......@@ -22,12 +25,24 @@ class RocketChatApplication : Application(), HasActivityInjector {
DaggerAppComponent.builder().application(this).build().inject(this)
Fresco.initialize(this)
AndroidThreeTen.init(this)
setupFresco()
setupTimber()
}
private fun setupFresco() {
val imagePipelineConfig = ImagePipelineConfig.newBuilder(this)
.setImageDecoderConfig(CustomImageFormatConfigurator.createImageDecoderConfig())
.build()
val draweeConfigBuilder = DraweeConfig.newBuilder()
CustomImageFormatConfigurator.addCustomDrawableFactories(draweeConfigBuilder)
Fresco.initialize(this, imagePipelineConfig, draweeConfigBuilder.build())
}
private fun setupTimber() {
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
......
package chat.rocket.android.app.utils
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.support.annotation.ColorInt
import android.support.annotation.Nullable
import android.support.v4.graphics.ColorUtils
import com.facebook.common.internal.ByteStreams
import com.facebook.imageformat.ImageFormat
import com.facebook.imageformat.ImageFormatCheckerUtils
import com.facebook.imagepipeline.common.ImageDecodeOptions
import com.facebook.imagepipeline.decoder.ImageDecoder
import com.facebook.imagepipeline.drawable.DrawableFactory
import com.facebook.imagepipeline.image.CloseableImage
import com.facebook.imagepipeline.image.EncodedImage
import com.facebook.imagepipeline.image.QualityInfo
import java.io.IOException
/**
* Simple decoder that can decode color images that have the following format: <color>#FF5722</color>.
*
* @see {https://github.com/facebook/fresco/blob/master/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/imageformat/color/ColorImageExample.java}
*/
object ColorImage {
// Custom ImageFormat for color images.
private val imageFormatColor = ImageFormat("IMAGE_FORMAT_COLOR", "color")
// XML color tag that our colors must start with.
val colorTag = "<color>"
/**
* Creates a new image format checker for [ColorImage.imageFormatColor].
*
* @return the image format checker.
*/
fun createFormatChecker(): ImageFormat.FormatChecker = ColorFormatChecker()
/**
* Creates a new decoder that can decode [ColorImage.imageFormatColor] images.
*
* @return the decoder.
*/
fun createDecoder(): ImageDecoder = ColorDecoder()
fun createDrawableFactory(): ColorDrawableFactory = ColorDrawableFactory()
/**
* Custom color format checker that verifies that the header of the file corresponds to our [ColorImage.colorTag].
*/
class ColorFormatChecker : ImageFormat.FormatChecker {
private val header = ImageFormatCheckerUtils.asciiBytes(colorTag)
override fun getHeaderSize(): Int {
return header.size
}
@Nullable override fun determineFormat(headerBytes: ByteArray, headerSize: Int): ImageFormat? {
if (headerSize > getHeaderSize()) {
if (ImageFormatCheckerUtils.startsWithPattern(headerBytes, header)) {
return imageFormatColor
}
}
return null
}
}
/**
* Custom closeable color image that holds a single color int value.
*/
class CloseableColorImage(@field:ColorInt @get:ColorInt val color: Int) : CloseableImage() {
private var isClosed = false
override fun close() {
isClosed = true
}
override fun getSizeInBytes(): Int = 0
override fun isClosed(): Boolean = isClosed
override fun getWidth(): Int = 0
override fun getHeight(): Int = 0
}
/**
* Decodes a color XML tag: <color>#rrggbb</color>.
*/
class ColorDecoder : ImageDecoder {
@Nullable override fun decode(encodedImage: EncodedImage, length: Int, qualityInfo: QualityInfo, options: ImageDecodeOptions): CloseableImage? {
try {
// Read the file as a string
val text = String(ByteStreams.toByteArray(encodedImage.inputStream))
// Check if the string matches "<color>#"
if (!text.startsWith(colorTag + "#")) {
return null
}
// Parse the int value between # and <
val startIndex = colorTag.length + 1
val endIndex = text.lastIndexOf('<')
var color = Integer.parseInt(text.substring(startIndex, endIndex), 16)
// Add the alpha component so that we actually see the color
color = ColorUtils.setAlphaComponent(color, 255)
// Return the CloseableImage
return CloseableColorImage(color)
} catch (e: IOException) {
// TODO: are we using the android.util.Log for logging that type of errors? or should we use the SDK logger?
e.printStackTrace()
}
// Return nothing if an error occurred
return null
}
}
/**
* Color drawable factory that is able to render a [CloseableColorImage] by creating a new [ColorDrawable] for the given color.
*/
class ColorDrawableFactory : DrawableFactory {
override fun supportsImageType(image: CloseableImage): Boolean {
// We can only handle CloseableColorImages.
return image is CloseableColorImage
}
@Nullable override fun createDrawable(image: CloseableImage): Drawable? {
// Just return a simple ColorDrawable with the given color value.
return ColorDrawable((image as CloseableColorImage).color)
}
}
}
\ No newline at end of file
package chat.rocket.android.app.utils
import com.facebook.drawee.backends.pipeline.DraweeConfig
import com.facebook.imagepipeline.decoder.ImageDecoderConfig
/**
* Utility class to add custom decoders and drawable factories.
*
* @see {https://github.com/facebook/fresco/blob/master/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/CustomImageFormatConfigurator.java}
*/
object CustomImageFormatConfigurator {
fun createImageDecoderConfig() : ImageDecoderConfig {
return ImageDecoderConfig.newBuilder()
.addDecodingCapability(SvgDecoder.svgFormat, SvgDecoder.SvgFormatChecker(), SvgDecoder.Decoder())
.build()
}
fun addCustomDrawableFactories(draweeConfigBuilder: DraweeConfig.Builder) {
// We always add the color drawable factory so that it can be used for image decoder overrides.
draweeConfigBuilder.addCustomDrawableFactory(ColorImage.createDrawableFactory())
draweeConfigBuilder.addCustomDrawableFactory(SvgDecoder.SvgDrawableFactory())
}
}
\ No newline at end of file
package chat.rocket.android.app.utils
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.graphics.drawable.PictureDrawable
import android.support.annotation.Nullable
import com.caverock.androidsvg.SVG
import com.caverock.androidsvg.SVGParseException
import com.facebook.imageformat.ImageFormat
import com.facebook.imageformat.ImageFormatCheckerUtils
import com.facebook.imagepipeline.common.ImageDecodeOptions
import com.facebook.imagepipeline.decoder.ImageDecoder
import com.facebook.imagepipeline.drawable.DrawableFactory
import com.facebook.imagepipeline.image.CloseableImage
import com.facebook.imagepipeline.image.EncodedImage
import com.facebook.imagepipeline.image.QualityInfo
/**
* SVG example that defines all classes required to decode and render SVG images.
*
* @see {https://github.com/facebook/fresco/blob/master/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/imageformat/svg/SvgDecoderExample.java}
*/
object SvgDecoder {
val svgFormat = ImageFormat("SVG_FORMAT", "svg")
// We do not include the closing ">" since there can be additional information.
private val headerTag = "<?xml"
private val possibleHeaderTags = arrayOf(ImageFormatCheckerUtils.asciiBytes("<svg"))
/**
* Custom SVG format checker that verifies that the header of the file corresponds to our [SvgDecoder.headerTag] or [SvgDecoder.possibleHeaderTags].
*/
class SvgFormatChecker : ImageFormat.FormatChecker {
private val header = ImageFormatCheckerUtils.asciiBytes(headerTag)
override fun getHeaderSize(): Int {
return header.size
}
@Nullable override fun determineFormat(headerBytes: ByteArray, headerSize: Int): ImageFormat? {
if (headerSize > getHeaderSize()) {
if (ImageFormatCheckerUtils.startsWithPattern(headerBytes, header)) {
return svgFormat
}
if (possibleHeaderTags.any { ImageFormatCheckerUtils.startsWithPattern(headerBytes, it) && ImageFormatCheckerUtils.indexOfPattern(headerBytes, headerBytes.size, header, header.size) > -1 }) {
return svgFormat
}
}
return null
}
}
/**
* Custom closeable SVG image that holds a single SVG.
*/
class CloseableSvgImage(val svg: SVG) : CloseableImage() {
private var isClose = false
override fun close() {
isClose = true
}
override fun getSizeInBytes(): Int = 0
override fun isClosed(): Boolean = isClose
override fun getWidth(): Int = 0
override fun getHeight(): Int = 0
}
/**
* Decodes a [SvgDecoder.svgFormat] image.
*/
class Decoder : ImageDecoder {
@Nullable override fun decode(encodedImage: EncodedImage, length: Int, qualityInfo: QualityInfo, options: ImageDecodeOptions): CloseableImage? {
try {
val svg = SVG.getFromInputStream(encodedImage.inputStream)
return CloseableSvgImage(svg)
} catch (e: SVGParseException) {
// TODO: are we using the android.util.Log for logging that type of errors? or should we use the SDK logger?
e.printStackTrace()
}
// Return nothing if an error occurred
return null
}
}
/**
* SVG drawable factory that creates [PictureDrawable]s for SVG images.
*/
class SvgDrawableFactory : DrawableFactory {
override fun supportsImageType(image: CloseableImage): Boolean {
return image is CloseableSvgImage
}
@Nullable override fun createDrawable(image: CloseableImage): Drawable? {
return SvgPictureDrawable((image as CloseableSvgImage).svg)
}
}
class SvgPictureDrawable(private val svg: SVG) : PictureDrawable(null) {
override fun onBoundsChange(bounds: Rect) {
super.onBoundsChange(bounds)
picture = svg.renderToPicture(bounds.width(), bounds.height())
}
}
}
\ No newline at end of file
......@@ -23,6 +23,7 @@ ext {
timber : '4.5.1',
threeTenABP : '1.0.5',
floatingSearchView: '2.1.1',
androidSvg : '1.2.1',
// For testing
junit : '4.12',
......@@ -70,6 +71,8 @@ ext {
floatingSearchView : "com.github.arimorty:floatingsearchview:${versions.floatingSearchView}",
androidSvg : "com.caverock:androidsvg:${versions.androidSvg}",
// For testing
toolsJar : files(Jvm.current().getToolsJar()),
junit : "junit:junit:$versions.junit",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment