Showing posts with label ImageDecoder. Show all posts
Showing posts with label ImageDecoder. Show all posts

Tuesday, January 1, 2019

Mix using of ImageDecoder and ObjectAnimator

Last posts show how to "Display animated GIF using ImageDecoder" and "Apply PostProcess for ImageDecoder". This example I try to mix using of ImageDecoder and ObjectAnimator, to apply animation on animated GIF.



Modify MainActivity.java
package com.blogspot.android_er.androidimagedecoder;

import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.app.Activity;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ImageDecoder;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.PostProcessor;
import android.graphics.drawable.AnimatedImageDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.ImageView;
import android.widget.Toast;

import java.io.IOException;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ImageView imageView1 = findViewById(R.id.gifimage1);
        ImageView imageView2 = findViewById(R.id.gifimage2);

        loadGif(imageView1);
        loadRoundGif(imageView2);

        imageView1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                prepareObjectAnimatorRotate((ImageView)v);
            }
        });

        imageView2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                prepareObjectAnimatorAlpha((ImageView)v);
            }
        });
    }

    PostProcessor myPostProcessor =
            new PostProcessor(){
                @Override
                public int onPostProcess(
                        Canvas canvas) {
                    // This will create rounded corners.
                    Path path = new Path();
                    path.setFillType(Path.FillType.INVERSE_EVEN_ODD);
                    int width = canvas.getWidth();
                    int height = canvas.getHeight();
                    path.addRoundRect(0, 0,
                            width, height, 150, 150,
                            Path.Direction.CW);
                    Paint paint = new Paint();
                    paint.setAntiAlias(true);
                    paint.setColor(Color.TRANSPARENT);
                    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
                    canvas.drawPath(path, paint);
                    return PixelFormat.TRANSLUCENT;
                }
            };


    ImageDecoder.OnHeaderDecodedListener myOnHeaderDecodedListener =
            new ImageDecoder.OnHeaderDecodedListener(){
                @Override
                public void onHeaderDecoded
                        (ImageDecoder decoder,
                         ImageDecoder.ImageInfo info,
                         ImageDecoder.Source source) {
                    decoder.setPostProcessor(myPostProcessor);
                }
            };

    private void loadGif(ImageView iv){

        try {
            ImageDecoder.Source source =
                    ImageDecoder.createSource(getResources(), R.drawable.android_er);

            Drawable drawable = ImageDecoder.decodeDrawable(source);

            iv.setImageDrawable(drawable);

            if (drawable instanceof AnimatedImageDrawable) {
                ((AnimatedImageDrawable) drawable).start();
                Toast.makeText(getApplicationContext(),
                        "loadGif: Animation started",
                        Toast.LENGTH_LONG).show();
            }

        } catch (IOException e) {
            e.printStackTrace();
            Toast.makeText(getApplicationContext(),
                    "loadGif: IOException: \n" + e.getMessage(),
                    Toast.LENGTH_LONG).show();
        }

    }

    private void loadRoundGif(ImageView iv){

        try {
            ImageDecoder.Source source =
                    ImageDecoder.createSource(getResources(), R.drawable.android_er);

            //Drawable drawable = ImageDecoder.decodeDrawable(source);
            Drawable drawable = ImageDecoder.decodeDrawable(source,
                    myOnHeaderDecodedListener);

            iv.setImageDrawable(drawable);

            if (drawable instanceof AnimatedImageDrawable) {
                ((AnimatedImageDrawable) drawable).start();
                Toast.makeText(getApplicationContext(),
                        "loadRoundGif: Animation started",
                        Toast.LENGTH_LONG).show();
            }

        } catch (IOException e) {
            e.printStackTrace();
            Toast.makeText(getApplicationContext(),
                    "loadRoundGif: IOException: \n" + e.getMessage(),
                    Toast.LENGTH_LONG).show();
        }

    }

    private void prepareObjectAnimatorRotate(ImageView image){
        TimeInterpolator timeInterpolator =
                new AccelerateDecelerateInterpolator();
        float propertyStart = 0f;
        float propertyEnd = 360f;
        String propertyName = "rotation";
        ObjectAnimator objectAnimator
                = ObjectAnimator.ofFloat(
                        image, propertyName, propertyStart, propertyEnd);
        objectAnimator.setDuration(5000);
        objectAnimator.setRepeatCount(1);
        objectAnimator.setRepeatMode(ObjectAnimator.REVERSE);
        objectAnimator.setInterpolator(timeInterpolator);
        objectAnimator.start();
    }

    private void prepareObjectAnimatorAlpha(ImageView image){
        TimeInterpolator timeInterpolator =
                new AccelerateDecelerateInterpolator();
        float propertyStart = 1f;
        float propertyEnd = 0f;
        String propertyName = "alpha";
        ObjectAnimator objectAnimator
                = ObjectAnimator.ofFloat(
                        image, propertyName, propertyStart, propertyEnd);
        objectAnimator.setDuration(5000);
        objectAnimator.setRepeatCount(1);
        objectAnimator.setRepeatMode(ObjectAnimator.REVERSE);
        objectAnimator.setInterpolator(timeInterpolator);
        objectAnimator.start();
    }
}


Keep using the layout in last post "Apply PostProcess for ImageDecoder".

If you target to animated GIF and it cannot shown, try to disable hardwareAccelerated, refer to "Display animated GIF using ImageDecoder".


Related old post:
Interpolator effect on ObjectAnimator


Sunday, December 30, 2018

Apply PostProcess for ImageDecoder

Last post show a simple example to "Display animated GIF using ImageDecoder". This example show how to implement our own PostProcess, to apply effect on the decoded images.


Modify layout, to have two ImageView:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/background_dark"
    tools:context=".MainActivity">

    <android.support.constraint.Guideline
        android:id="@+id/guideline"
        android:layout_width="1dp"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.5"/>

    <ImageView
        android:id="@+id/gifimage1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toTopOf="@+id/guideline"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

    <ImageView
        android:id="@+id/gifimage2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/guideline"/>

</android.support.constraint.ConstraintLayout>


Java code:
package com.blogspot.android_er.androidimagedecoder;

import android.app.Activity;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ImageDecoder;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.PostProcessor;
import android.graphics.drawable.AnimatedImageDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.Toast;

import java.io.IOException;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ImageView imageView1 = findViewById(R.id.gifimage1);
        ImageView imageView2 = findViewById(R.id.gifimage2);

        loadGif(imageView1);
        loadRoundGif(imageView2);
    }

    PostProcessor myPostProcessor =
            new PostProcessor(){
                @Override
                public int onPostProcess(
                        Canvas canvas) {
                    // This will create rounded corners.
                    Path path = new Path();
                    path.setFillType(Path.FillType.INVERSE_EVEN_ODD);
                    int width = canvas.getWidth();
                    int height = canvas.getHeight();
                    path.addRoundRect(0, 0,
                            width, height, 150, 150,
                            Path.Direction.CW);
                    Paint paint = new Paint();
                    paint.setAntiAlias(true);
                    paint.setColor(Color.TRANSPARENT);
                    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
                    canvas.drawPath(path, paint);
                    return PixelFormat.TRANSLUCENT;
                }
            };


    ImageDecoder.OnHeaderDecodedListener myOnHeaderDecodedListener =
            new ImageDecoder.OnHeaderDecodedListener(){
                @Override
                public void onHeaderDecoded
                        (ImageDecoder decoder,
                         ImageDecoder.ImageInfo info,
                         ImageDecoder.Source source) {
                    decoder.setPostProcessor(myPostProcessor);
                }
            };

    private void loadGif(ImageView iv){

        try {
            ImageDecoder.Source source =
                    ImageDecoder.createSource(getResources(), R.drawable.android_er);

            Drawable drawable = ImageDecoder.decodeDrawable(source);

            iv.setImageDrawable(drawable);

            if (drawable instanceof AnimatedImageDrawable) {
                ((AnimatedImageDrawable) drawable).start();
                Toast.makeText(getApplicationContext(),
                        "loadGif: Animation started",
                        Toast.LENGTH_LONG).show();
            }

        } catch (IOException e) {
            e.printStackTrace();
            Toast.makeText(getApplicationContext(),
                    "loadGif: IOException: \n" + e.getMessage(),
                    Toast.LENGTH_LONG).show();
        }

    }

    private void loadRoundGif(ImageView iv){

        try {
            ImageDecoder.Source source =
                    ImageDecoder.createSource(getResources(), R.drawable.android_er);

            //Drawable drawable = ImageDecoder.decodeDrawable(source);
            Drawable drawable = ImageDecoder.decodeDrawable(source,
                    myOnHeaderDecodedListener);

            iv.setImageDrawable(drawable);

            if (drawable instanceof AnimatedImageDrawable) {
                ((AnimatedImageDrawable) drawable).start();
                Toast.makeText(getApplicationContext(),
                        "loadRoundGif: Animation started",
                        Toast.LENGTH_LONG).show();
            }

        } catch (IOException e) {
            e.printStackTrace();
            Toast.makeText(getApplicationContext(),
                    "loadRoundGif: IOException: \n" + e.getMessage(),
                    Toast.LENGTH_LONG).show();
        }

    }
}



If you target to animated GIF and it cannot shown, try to disable hardwareAccelerated, refer to "Display animated GIF using ImageDecoder".

Next:
Mix using of ImageDecoder and ObjectAnimator


Saturday, December 29, 2018

Display animated GIF using ImageDecoder

android.graphics.ImageDecoder, introduced from API 28,  is a class for converting encoded images (like PNG, JPEG, WEBP, GIF, or HEIF) into Drawable or Bitmap objects. Before API 28, it have no official supporte for animated GIF, with ImageDecoder, we can easy display animated GIF in our Android app.

Create a new Android project with empty activity target API 28. Currently, ImageDecoder have not imcluded in Support Library, so make sure to uncheck Backwards Compatibility. May be it will be included in someday.




Copy your GIF to /res/drawable / folder


Modify layout to include a ImageView
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/gifimage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

</android.support.constraint.ConstraintLayout>

Example code to display animated GIF using ImageDecoder
package com.blogspot.android_er.androidimagedecoder;

import android.app.Activity;
import android.graphics.ImageDecoder;
import android.graphics.drawable.AnimatedImageDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.Toast;

import java.io.IOException;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ImageView imageView = findViewById(R.id.gifimage);

        loadGif(imageView);
    }

    private void loadGif(ImageView iv){

        try {
            ImageDecoder.Source source =
                    ImageDecoder.createSource(getResources(), R.drawable.android_er);

            Drawable drawable = ImageDecoder.decodeDrawable(source);
            iv.setImageDrawable(drawable);

            if (drawable instanceof AnimatedImageDrawable) {
                ((AnimatedImageDrawable) drawable).start();
                Toast.makeText(getApplicationContext(),
                        "Animation started",
                        Toast.LENGTH_LONG).show();
            }

        } catch (IOException e) {
            e.printStackTrace();
            Toast.makeText(getApplicationContext(),
                    "IOException: \n" + e.getMessage(),
                    Toast.LENGTH_LONG).show();
        }

    }
}


When I test on Android Emulator, I have to disable hardwareAccelerated in AndroidManifest.xml, otherwise the GIF will not shown.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.blogspot.android_er.androidimagedecoder">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity"
            android:hardwareAccelerated="false">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

This video show how it work on Android P Emulator.


Next:
Apply PostProcess for ImageDecoder
Mix using of ImageDecoder and ObjectAnimator