Pages

getteammates.com

Friday, 25 December 2015

A simple mask shader for Unity3D

Let me share a simple mask shader which takes two texture the main texture and the mask texture. Main texture is rendered if the mask texture does not have alpha as 0.

Shader "TRSquareLab/Mask" {

     Properties {
          // Main Texture
          _MainTex ("Texture", 2D) = "white" { }
          
          // Mask texture, color from main texture will be applied if
          // the mask texture does not have alpha as 0
          _MaskTex ("Mask", 2D) = "white" { }     
     }
     
     SubShader {
          Pass
               {
                    CGPROGRAM
                    
                    #pragma vertex vert
                    #pragma fragment frag
                    #include "UnityCG.cginc"
               
                    sampler2D _MainTex;
                    sampler2D _MaskTex;
               
                    struct v2f {
                         float4 pos : SV_POSITION;
                         float2 uv : TEXCOORD0;
                    };
                    
                    float4 _MainTex_ST;
                    
                    v2f vert (appdata_base v)
                    {
                         v2f o;
                         o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
                         o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
                         return o;
                    }
                    
                    fixed4 frag (v2f i) : SV_Target
                    {
                         fixed4 texcol = tex2D (_MainTex, i.uv);
                         fixed4 maskcol = tex2D (_MaskTex, i.uv);
                         // Discard the pixel if the alpha is 0
                         if (maskcol.a == 0) {
                              discard;
                         }
                         // Else return the main texture color
                         return texcol;
                    }
                    
                    ENDCG
               }
     }
     
     FallBack Off
}


A simple implementationof item decorator for RecyclerView

I am going to show you a simple implementation of an item decorator which fills the item and also draws a boarder.
package com.example.recycleviewexample;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.v7.widget.RecyclerView;
import android.view.View;

import java.util.Random;

/**
 * A simple item decorator which will,
 * 1. Fill the item view with a Random color from ColorList
 * 2. Draw a dark Gray coloured boarder
 */
public class SimpleItemDecorator extends RecyclerView.ItemDecoration {
    private Paint mPaintFill;    // This Paint object is used for filling the view
    private Paint mPaintStroke;  // This Paint object is used for drawing the boarder

    private Random mRandom = new Random();

    // Item view will be filled with one of the below given colour
    // which is selected randomly
    private static final int[] ColorList = {
            Color.RED,
            Color.GREEN,
            Color.BLUE,
            Color.YELLOW,
            Color.CYAN,
            Color.MAGENTA,
            Color.GRAY};

    public SimpleItemDecorator(Context context) {

        // Create the fill paint
        mPaintFill = new Paint();
        mPaintFill.setStyle(Paint.Style.FILL);

        // Create the stroke paint
        mPaintStroke = new Paint();
        mPaintStroke.setStyle(Paint.Style.STROKE);
        mPaintStroke.setStrokeWidth(5.0f);
        mPaintStroke.setColor(Color.DKGRAY);

        // Set seed for random number generator
        mRandom.setSeed(System.currentTimeMillis());
    }

    // This method is called before the item view is rendered
    // This method will be used for filling the item view
    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {

        int childCount = parent.getChildCount();

        // For each child view we would need to calculate the
        // bounds and fill it by drawing a rectangle
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);

            // Get the bounds
            RectF rect = getBounds(child);

            // Set a random color
            mPaintFill.setColor(ColorList[mRandom.nextInt(ColorList.length)]);

            // Fill the view by drawing a rectangle with rounded corner
            c.drawRoundRect (rect, 20, 20, mPaintFill);
        }
    }

    // This method is called after the item view is rendered
    // This method will be used for drawing the boarder
    @Override
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {

        int childCount = parent.getChildCount();

        // For each child view we would need to calculate the
        // bounds and fill it by drawing a rectangle
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);

            // Get the bounds
            RectF rect = getBounds(child);

            // Fill the view by drawing a rectangle with rounded corner
            c.drawRoundRect (rect, 10, 10, mPaintStroke);
        }
    }

    // Calculate the bounds for a child
    private RectF getBounds(View child) {
        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();

        int left = child.getPaddingLeft();
        int right = child.getWidth() - child.getPaddingRight();

        int top = child.getTop() - params.topMargin;
        int bottom = child.getBottom() + params.bottomMargin;

        return new RectF(left, top, right, bottom);
    }
}

Complete example can be found at https://github.com/trsquarelab/androidexamples/tree/master/RecycleViewExample

Tuesday, 22 December 2015

Creating custom item animator implementation for RecyclerView

This post is on creating custom animation in Android RecyclerView.

It is recommended that you go through the earlier post on RecyclerView.

To implement custom animation you can derive from RecyclerView.ItemAnimator or SimpleItemAnimator, which is derived from RecyclerView.ItemAnimator.

This post is based on the assumption that you are deriving your class from SimpleItemAnimator.

Types of animation in RecyclerView


Lets see the basic types of animation supported in a RecyclerView
  • Add animation : This is performed when adding a new element to the recycler view
  • Remove animation : This is performed when removing an element from the recycler view
  • Change animation : This is performed when an element is changed in the RecyclerView
  • Move animation : This is performed when element(s) are required to move to as a consequence of add or remove operation.

Implementing animations

For all the method you can return false to not to have animation.

Add Animation

Method animateAdd will be called when a new element is added , the prototype of the method is shown below,
public boolean animateAdd(final ViewHolder holder);

In this method you should,
  1. stop any previously running animation for this view
  2. register the view for add animation, do not start with the animation here, a different method will be called for that purpose. There is no method to register for animation, you can probably keep the view in a array.

Remove Animation

Method animateRemove will be called when an element is removed, the prototype of the method is shown below,
public boolean animateRemove(final ViewHolder holder)
The step you take when this method is called is very similar to that of animateAdd

In this method you should,
  1. stop any previously running animation for this view
  2. register the view for remove animation, do not start with the animation here. A different method will be called for that purpose.

Change Animation

animateChange will be called to start the animations for changing an element. The prototype of the method is shown below,
public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder,
                                 int fromX, int fromY, int toX, int toY)

In this method you should,
  1. stop any previously running animation for this view
  2. register the views for change animation, do not start with the animation here. A different method will be called for that purpose.

Move animation

animateMove is called when elements are required to move to accommodate a new element or to fill a vacant spot when an element is removed.
The prototype of the method is shown below,
public boolean animateMove(final ViewHolder holder, int fromX, int fromY,
                               int toX, int toY)

In this method you should,
  1. stop any previously running animation for this view
  2. register the views for move animation, do not start with the animation here. A different method will be called for that purpose.

Running the animation

runPendingAnimations method will be called to start the animations for the views for which one of the animateXXX method is called.

In this method run the animation in the following order,
  1. Remove animations
  2. Move animations once the remove animation is complete. Use getRemoveDuration to get the duration of remove animation. Use ViewCompat.postOnAnimationDelayed to run an animation after a specified delay.
  3. Change animations once remove animations and move animations are complete. Use getMoveDuration to get the duration of move animations.
  4. Add animations once remove animations, move animations and change animations are complete. Use getChangeDuration to get the duration of change animations.
Please remember to call the corresponding dispatch method. For example dispatchAddStarting when add animation is starting, dispatchAddFinished when add animation is finished. There is dispatch method for each type of animations.
These method will be calling RecyclerView.dispatchAnimationStarted and RecyclerView.dispatchAnimationsFinished eventually.

End calls to stop the animation

endAnimation will be called to stop animation on a specified view.
endAnimations will be called to stop all the running animations.

You would need to keep track of running animations. Method isRunning will be called to check whether animations are running or not.

Thats it, now you can start implementing your own animation for RecyclerView!

As you can see, creating a custom animation is not a very simple process.

The easy way

Let me share with you an easy way to implement custom animation. By default RecyclerView has DefaultItemAnimator set as the animator.
To add your own animation you can take the code of DefaultItemAnimator and modify it according to your need.


You would want to modify method animateRemoveImpl, animateMoveImpl, animateChangeImpl or animateAddImpl to add your custom animation for remove, move, change and add operations respectively.

I have modified the file to support my own animation. My animations are,
  1. Scale to right side when element is removed
  2. Scale from left side when a new element is added
  3. Do animation 1 and 2 in that order when element is changed.
I have kept the same implementation for the move animation.  


Modified file is pasted below for your reference, I have highlighted the modified part.

/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * 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.example.recycleviewexample;

import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorListener;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.support.v7.widget.SimpleItemAnimator;
import android.support.v4.view.ViewPropertyAnimatorCompat;
import android.util.Log;
import android.view.View;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;

public class ScaleInItemAnimator extends SimpleItemAnimator {
    private ArrayList<ViewHolder> mPendingRemovals = new ArrayList<ViewHolder>();
    private ArrayList<ViewHolder> mPendingAdditions = new ArrayList<ViewHolder>();
    private ArrayList<MoveInfo> mPendingMoves = new ArrayList<MoveInfo>();
    private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<ChangeInfo>();


    private ArrayList<ArrayList<ViewHolder>> mAdditionsList =
            new ArrayList<ArrayList<ViewHolder>>();
    private ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<ArrayList<MoveInfo>>();
    private ArrayList<ArrayList<ChangeInfo>> mChangesList = new ArrayList<ArrayList<ChangeInfo>>();


    private ArrayList<ViewHolder> mAddAnimations = new ArrayList<ViewHolder>();
    private ArrayList<ViewHolder> mMoveAnimations = new ArrayList<ViewHolder>();
    private ArrayList<ViewHolder> mRemoveAnimations = new ArrayList<ViewHolder>();
    private ArrayList<ViewHolder> mChangeAnimations = new ArrayList<ViewHolder>();

    private static class MoveInfo {
        public ViewHolder holder;
        public int fromX, fromY, toX, toY;
        private MoveInfo(ViewHolder holder, int fromX, int fromY, int toX, int toY) {
            this.holder = holder;
            this.fromX = fromX;
            this.fromY = fromY;
            this.toX = toX;
            this.toY = toY;
        }
    }

    private static class ChangeInfo {
        public ViewHolder oldHolder, newHolder;
        public int fromX, fromY, toX, toY;
        private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder) {
            this.oldHolder = oldHolder;
            this.newHolder = newHolder;
        }
        private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder,
                           int fromX, int fromY, int toX, int toY) {
            this(oldHolder, newHolder);
            this.fromX = fromX;
            this.fromY = fromY;
            this.toX = toX;
            this.toY = toY;
        }
    }

    @Override
    public void runPendingAnimations() {
        boolean removalsPending = !mPendingRemovals.isEmpty();
        boolean movesPending = !mPendingMoves.isEmpty();
        boolean changesPending = !mPendingChanges.isEmpty();
        boolean additionsPending = !mPendingAdditions.isEmpty();
        if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
            // nothing to animate
            return;
        }
        // First, remove stuff
        for (ViewHolder holder : mPendingRemovals) {
            animateRemoveImpl(holder);
        }
        mPendingRemovals.clear();
        // Next, move stuff
        if (movesPending) {
            final ArrayList<MoveInfo> moves = new ArrayList<MoveInfo>();
            moves.addAll(mPendingMoves);
            mMovesList.add(moves);
            mPendingMoves.clear();
            Runnable mover = new Runnable() {
                @Override
                public void run() {
                    for (MoveInfo moveInfo : moves) {
                        animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
                                moveInfo.toX, moveInfo.toY);
                    }
                    moves.clear();
                    mMovesList.remove(moves);
                }
            };
            if (removalsPending) {
                View view = moves.get(0).holder.itemView;
                ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
            } else {
                mover.run();
            }
        }
        // Next, change stuff, to run in parallel with move animations
        if (changesPending) {
            final ArrayList<ChangeInfo> changes = new ArrayList<ChangeInfo>();
            changes.addAll(mPendingChanges);
            mChangesList.add(changes);
            mPendingChanges.clear();
            Runnable changer = new Runnable() {
                @Override
                public void run() {
                    for (ChangeInfo change : changes) {
                        animateChangeImpl(change);
                    }
                    changes.clear();
                    mChangesList.remove(changes);
                }
            };
            if (removalsPending) {
                ViewHolder holder = changes.get(0).oldHolder;
                ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
            } else {
                changer.run();
            }
        }
        // Next, add stuff
        if (additionsPending) {
            final ArrayList<ViewHolder> additions = new ArrayList<ViewHolder>();
            additions.addAll(mPendingAdditions);
            mAdditionsList.add(additions);
            mPendingAdditions.clear();
            Runnable adder = new Runnable() {
                public void run() {
                    for (ViewHolder holder : additions) {
                        animateAddImpl(holder);
                    }
                    additions.clear();
                    mAdditionsList.remove(additions);
                }
            };
            if (removalsPending || movesPending || changesPending) {
                long removeDuration = removalsPending ? getRemoveDuration() : 0;
                long moveDuration = movesPending ? getMoveDuration() : 0;
                long changeDuration = changesPending ? getChangeDuration() : 0;
                long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
                View view = additions.get(0).itemView;
                ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
            } else {
                adder.run();
            }
        }
    }

    @Override
    public boolean animateRemove(final ViewHolder holder) {
        endAnimation(holder);
        mPendingRemovals.add(holder);
        return true;
    }

    private void animateRemoveImpl(final ViewHolder holder) {
        final View view = holder.itemView;
  
  // Set the pivot to width so that the element scale to the right
  // Also changed the animation to scaleX
        ViewCompat.setPivotX(view, view.getWidth());
        final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
        animation.setDuration(getRemoveDuration())
                .scaleX(0).setListener(new VpaListenerAdapter() {
            @Override
            public void onAnimationStart(View view) {
                dispatchRemoveStarting(holder);
            }

            @Override
            public void onAnimationEnd(View view) {
                animation.setListener(null);
                ViewCompat.setAlpha(view, 1);
                dispatchRemoveFinished(holder);
                mRemoveAnimations.remove(holder);
                dispatchFinishedWhenDone();
            }
        }).start();
        mRemoveAnimations.add(holder);
    }

    @Override
    public boolean animateAdd(final ViewHolder holder) {
        endAnimation(holder);
        ViewCompat.setAlpha(holder.itemView, 0);

        mPendingAdditions.add(holder);
        return true;
    }

    private void animateAddImpl(final ViewHolder holder) {
        final View view = holder.itemView;
        mAddAnimations.add(holder);

  // Set the pivot to width so that the element scale from left
  // Also changed the animation to scaleX
        ViewCompat.setPivotX(view, 0.0f);
        ViewCompat.setScaleX(view, 0.0f);
        ViewCompat.setAlpha(view, 1.0f);

        final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
        animation.scaleX(1.0f).setDuration(getAddDuration()).
                setListener(new VpaListenerAdapter() {
                    @Override
                    public void onAnimationStart(View view) {
                        dispatchAddStarting(holder);
                    }

                    @Override
                    public void onAnimationCancel(View view) {
                        ViewCompat.setAlpha(view, 1);
                    }

                    @Override
                    public void onAnimationEnd(View view) {
                        animation.setListener(null);
                        dispatchAddFinished(holder);
                        mAddAnimations.remove(holder);
                        dispatchFinishedWhenDone();
                    }
                }).start();
    }

    @Override
    public boolean animateMove(final ViewHolder holder, int fromX, int fromY,
                               int toX, int toY) {
        final View view = holder.itemView;
        fromX += ViewCompat.getTranslationX(holder.itemView);
        fromY += ViewCompat.getTranslationY(holder.itemView);
        endAnimation(holder);
        int deltaX = toX - fromX;
        int deltaY = toY - fromY;
        if (deltaX == 0 && deltaY == 0) {
            dispatchMoveFinished(holder);
            return false;
        }
        if (deltaX != 0) {
            ViewCompat.setTranslationX(view, -deltaX);
        }
        if (deltaY != 0) {
            ViewCompat.setTranslationY(view, -deltaY);
        }
        mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
        return true;
    }

    private void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) {
        final View view = holder.itemView;
        final int deltaX = toX - fromX;
        final int deltaY = toY - fromY;
        if (deltaX != 0) {
            ViewCompat.animate(view).translationX(0);
        }
        if (deltaY != 0) {
            ViewCompat.animate(view).translationY(0);
        }
        // TODO: make EndActions end listeners instead, since end actions aren't called when
        // vpas are canceled (and can't end them. why?)
        // need listener functionality in VPACompat for this. Ick.
        mMoveAnimations.add(holder);
        final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
        animation.setDuration(getMoveDuration()).setListener(new VpaListenerAdapter() {
            @Override
            public void onAnimationStart(View view) {
                dispatchMoveStarting(holder);
            }
            @Override
            public void onAnimationCancel(View view) {
                if (deltaX != 0) {
                    ViewCompat.setTranslationX(view, 0);
                }
                if (deltaY != 0) {
                    ViewCompat.setTranslationY(view, 0);
                }
            }
            @Override
            public void onAnimationEnd(View view) {
                animation.setListener(null);
                dispatchMoveFinished(holder);
                mMoveAnimations.remove(holder);
                dispatchFinishedWhenDone();
            }
        }).start();
    }

    @Override
    public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder,
                                 int fromX, int fromY, int toX, int toY) {
        final float prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView);
        final float prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView);
        final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView);
        endAnimation(oldHolder);
        int deltaX = (int) (toX - fromX - prevTranslationX);
        int deltaY = (int) (toY - fromY - prevTranslationY);
        // recover prev translation state after ending animation
        ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX);
        ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY);
        ViewCompat.setAlpha(oldHolder.itemView, prevAlpha);
        if (newHolder != null && newHolder.itemView != null) {
            // carry over translation values
            endAnimation(newHolder);
            ViewCompat.setTranslationX(newHolder.itemView, -deltaX);
            ViewCompat.setTranslationY(newHolder.itemView, -deltaY);
            ViewCompat.setAlpha(newHolder.itemView, 0);
        }
        mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
        return true;
    }

    private void animateChangeImpl(final ChangeInfo changeInfo) {
        final ViewHolder holder = changeInfo.oldHolder;
        final View view = holder == null ? null : holder.itemView;
        final ViewHolder newHolder = changeInfo.newHolder;
        final View newView = newHolder != null ? newHolder.itemView : null;
        if (view != null) {
            mChangeAnimations.add(changeInfo.oldHolder);
            final ViewPropertyAnimatorCompat oldViewAnim = ViewCompat.animate(view).setDuration(
                    getChangeDuration());
            oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
            oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
   
   // Same as remove animation
            ViewCompat.setPivotX(view, view.getWidth());
            oldViewAnim.scaleX(0).setListener(new VpaListenerAdapter() {
                @Override
                public void onAnimationStart(View view) {
                    dispatchChangeStarting(changeInfo.oldHolder, true);
                }
                @Override
                public void onAnimationEnd(View view) {
                    oldViewAnim.setListener(null);
                    ViewCompat.setAlpha(view, 1);
                    ViewCompat.setTranslationX(view, 0);
                    ViewCompat.setTranslationY(view, 0);
                    ViewCompat.setPivotX(view, 0);
                    dispatchChangeFinished(changeInfo.oldHolder, true);
                    mChangeAnimations.remove(changeInfo.oldHolder);
                    dispatchFinishedWhenDone();
                }
            }).start();
        }
        if (newView != null) {
            mChangeAnimations.add(changeInfo.newHolder);
            final ViewPropertyAnimatorCompat newViewAnimation = ViewCompat.animate(newView);

            Log.d(Common.Tag, " Pivot : " + ViewCompat.getPivotX(view));

   // Same as add animation
            ViewCompat.setPivotX(newView, 0.0f);
            ViewCompat.setScaleX(newView, 0.0f);
            newViewAnimation.translationX(0).translationY(0).scaleX(1.0f).setDuration(getChangeDuration()).
                    alpha(1).setListener(new VpaListenerAdapter() {
                @Override
                public void onAnimationStart(View view) {
                    dispatchChangeStarting(changeInfo.newHolder, false);
                }
                @Override
                public void onAnimationEnd(View view) {
                    newViewAnimation.setListener(null);
                    ViewCompat.setAlpha(newView, 1);
                    ViewCompat.setTranslationX(newView, 0);
                    ViewCompat.setTranslationY(newView, 0);
                    dispatchChangeFinished(changeInfo.newHolder, false);
                    mChangeAnimations.remove(changeInfo.newHolder);
                    dispatchFinishedWhenDone();
                }
            }).start();
        }
    }

    private void endChangeAnimation(List<ChangeInfo> infoList, ViewHolder item) {
        for (int i = infoList.size() - 1; i >= 0; i--) {
            ChangeInfo changeInfo = infoList.get(i);
            if (endChangeAnimationIfNecessary(changeInfo, item)) {
                if (changeInfo.oldHolder == null && changeInfo.newHolder == null) {
                    infoList.remove(changeInfo);
                }
            }
        }
    }

    private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) {
        if (changeInfo.oldHolder != null) {
            endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder);
        }
        if (changeInfo.newHolder != null) {
            endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder);
        }
    }

    private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, ViewHolder item) {
        boolean oldItem = false;
        if (changeInfo.newHolder == item) {
            changeInfo.newHolder = null;
        } else if (changeInfo.oldHolder == item) {
            changeInfo.oldHolder = null;
            oldItem = true;
        } else {
            return false;
        }
        ViewCompat.setAlpha(item.itemView, 1);
        ViewCompat.setTranslationX(item.itemView, 0);
        ViewCompat.setTranslationY(item.itemView, 0);
        dispatchChangeFinished(item, oldItem);
        return true;
    }

    @Override
    public void endAnimation(ViewHolder item) {
        final View view = item.itemView;
        // this will trigger end callback which should set properties to their target values.
        ViewCompat.animate(view).cancel();
        // TODO if some other animations are chained to end, how do we cancel them as well?
        for (int i = mPendingMoves.size() - 1; i >= 0; i--) {
            MoveInfo moveInfo = mPendingMoves.get(i);
            if (moveInfo.holder == item) {
                ViewCompat.setTranslationY(view, 0);
                ViewCompat.setTranslationX(view, 0);
                dispatchMoveFinished(item);
                mPendingMoves.remove(item);
            }
        }
        endChangeAnimation(mPendingChanges, item);
        if (mPendingRemovals.remove(item)) {
            ViewCompat.setAlpha(view, 1);
            dispatchRemoveFinished(item);
        }
        if (mPendingAdditions.remove(item)) {
            ViewCompat.setAlpha(view, 1);
            dispatchAddFinished(item);
        }
        for (int i = mChangesList.size() - 1; i >= 0; i--) {
            ArrayList<ChangeInfo> changes = mChangesList.get(i);
            endChangeAnimation(changes, item);
            if (changes.isEmpty()) {
                mChangesList.remove(changes);
            }
        }
        for (int i = mMovesList.size() - 1; i >= 0; i--) {
            ArrayList<MoveInfo> moves = mMovesList.get(i);
            for (int j = moves.size() - 1; j >= 0; j--) {
                MoveInfo moveInfo = moves.get(j);
                if (moveInfo.holder == item) {
                    ViewCompat.setTranslationY(view, 0);
                    ViewCompat.setTranslationX(view, 0);
                    dispatchMoveFinished(item);
                    moves.remove(j);
                    if (moves.isEmpty()) {
                        mMovesList.remove(moves);
                    }
                    break;
                }
            }
        }
        for (int i = mAdditionsList.size() - 1; i >= 0; i--) {
            ArrayList<ViewHolder> additions = mAdditionsList.get(i);
            if (additions.remove(item)) {
                ViewCompat.setAlpha(view, 1);
                dispatchAddFinished(item);
                if (additions.isEmpty()) {
                    mAdditionsList.remove(additions);
                }
            }
        }
        dispatchFinishedWhenDone();
    }

    @Override
    public boolean isRunning() {
        return (!mPendingAdditions.isEmpty() ||
                !mPendingChanges.isEmpty() ||
                !mPendingMoves.isEmpty() ||
                !mPendingRemovals.isEmpty() ||
                !mMoveAnimations.isEmpty() ||
                !mRemoveAnimations.isEmpty() ||
                !mAddAnimations.isEmpty() ||
                !mChangeAnimations.isEmpty() ||
                !mMovesList.isEmpty() ||
                !mAdditionsList.isEmpty() ||
                !mChangesList.isEmpty());
    }

    private void dispatchFinishedWhenDone() {
        if (!isRunning()) {
            dispatchAnimationsFinished();
        }
    }

    @Override
    public void endAnimations() {
        int count = mPendingMoves.size();
        for (int i = count - 1; i >= 0; i--) {
            MoveInfo item = mPendingMoves.get(i);
            View view = item.holder.itemView;
            ViewCompat.setTranslationY(view, 0);
            ViewCompat.setTranslationX(view, 0);
            dispatchMoveFinished(item.holder);
            mPendingMoves.remove(i);
        }
        count = mPendingRemovals.size();
        for (int i = count - 1; i >= 0; i--) {
            ViewHolder item = mPendingRemovals.get(i);
            dispatchRemoveFinished(item);
            mPendingRemovals.remove(i);
        }
        count = mPendingAdditions.size();
        for (int i = count - 1; i >= 0; i--) {
            ViewHolder item = mPendingAdditions.get(i);
            View view = item.itemView;
            ViewCompat.setAlpha(view, 1);
            dispatchAddFinished(item);
            mPendingAdditions.remove(i);
        }
        count = mPendingChanges.size();
        for (int i = count - 1; i >= 0; i--) {
            endChangeAnimationIfNecessary(mPendingChanges.get(i));
        }
        mPendingChanges.clear();
        if (!isRunning()) {
            return;
        }
        int listCount = mMovesList.size();
        for (int i = listCount - 1; i >= 0; i--) {
            ArrayList<MoveInfo> moves = mMovesList.get(i);
            count = moves.size();
            for (int j = count - 1; j >= 0; j--) {
                MoveInfo moveInfo = moves.get(j);
                ViewHolder item = moveInfo.holder;
                View view = item.itemView;
                ViewCompat.setTranslationY(view, 0);
                ViewCompat.setTranslationX(view, 0);
                dispatchMoveFinished(moveInfo.holder);
                moves.remove(j);
                if (moves.isEmpty()) {
                    mMovesList.remove(moves);
                }
            }
        }
        listCount = mAdditionsList.size();
        for (int i = listCount - 1; i >= 0; i--) {
            ArrayList<ViewHolder> additions = mAdditionsList.get(i);
            count = additions.size();
            for (int j = count - 1; j >= 0; j--) {
                ViewHolder item = additions.get(j);
                View view = item.itemView;
                ViewCompat.setAlpha(view, 1);
                dispatchAddFinished(item);
                additions.remove(j);
                if (additions.isEmpty()) {
                    mAdditionsList.remove(additions);
                }
            }
        }
        listCount = mChangesList.size();
        for (int i = listCount - 1; i >= 0; i--) {
            ArrayList<ChangeInfo> changes = mChangesList.get(i);
            count = changes.size();
            for (int j = count - 1; j >= 0; j--) {
                endChangeAnimationIfNecessary(changes.get(j));
                if (changes.isEmpty()) {
                    mChangesList.remove(changes);
                }
            }
        }
        cancelAll(mRemoveAnimations);
        cancelAll(mMoveAnimations);
        cancelAll(mAddAnimations);
        cancelAll(mChangeAnimations);
        dispatchAnimationsFinished();
    }
    void cancelAll(List<ViewHolder> viewHolders) {
        for (int i = viewHolders.size() - 1; i >= 0; i--) {
            ViewCompat.animate(viewHolders.get(i).itemView).cancel();
        }
    }
    private static class VpaListenerAdapter implements ViewPropertyAnimatorListener {
        @Override
        public void onAnimationStart(View view) {}
        @Override
        public void onAnimationEnd(View view) {}
        @Override
        public void onAnimationCancel(View view) {}
    };

    private static final int AnimDuration = 500;

    @Override
    public long getAddDuration() {
        return AnimDuration;
    }

    @Override
    public long getRemoveDuration() {
        return AnimDuration;
    }

    @Override
    public long getChangeDuration() {
        return AnimDuration;
    }

    @Override
    public long getMoveDuration() {
        return AnimDuration;
    }
}

That was easy!
You could change the class in such a way that you delegate animation setting and resetting step to further derived classes for more customization. 


Event handling and add, remove and change operations in Android RecyclerView

It is highly recommended that you go through the earlier post on RecyclerView, please click here to go to there.

We will create a simple layout file with RecyclerView and radio buttons to select the operations

Creating the layout file.

Layout file is shown below, lets name it as main2.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/alphaList"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical" />

    <RadioGroup
        android:id="@+id/opGroup"
        android:layout_alignParentBottom="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <RadioButton
            android:id="@+id/opAdd"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Add"
            android:checked="true"/>
        <RadioButton
            android:id="@+id/opRemove"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Remove"
            android:checked="false"/>
        <RadioButton
            android:id="@+id/opChange"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Change"
            android:checked="false"/>
    </RadioGroup>

</RelativeLayout>

Create one more layout file to represent a row in the RecyclerView. Let us name the file as fruit.xml.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/fruitNameText"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:background="@android:color/holo_blue_light"
        android:layout_margin="1dp"
        android:text="name"/>

    <TextView
        android:id="@+id/vitaminCText"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:background="@android:color/holo_blue_light"
        android:layout_margin="1dp"
        android:text="description"/>

</LinearLayout>


In our example we will have fruit and its vitamin C content as a list.

Lets create a simple class to store the values.
package com.example.recycleviewexample;

public class FruitData {
    public String mName;
    public String mVitaminC;

    public FruitData(String n, String d) {
        mName = n;
        mVitaminC = d;
    }

    // These data represent the initial data the the RecyclerView will have
    // These are just sample data
    static final FruitData[] getList1() {
        return new FruitData[] {
                new FruitData("Apricots", "5000"),
                new FruitData("Apple", "5000"),
                new FruitData("Banana", "10.000"),
                new FruitData("Blackberries", "150.000"),
                new FruitData("Cherries", "10.000"),
                new FruitData("Grapefruit", "40.000"),
                new FruitData("Grapes", "3000")
        };
    }

    // These data will be added/changed when the user select the corresponding operations
    static final FruitData[] getList2() {
        return new FruitData[] {
                new FruitData("Kiwi", "70.000"),
                new FruitData("Lemon", "40.000"),
                new FruitData("Lychee", "23.000"),
                new FruitData("Mango", "23.000"),
                new FruitData("Orange", "49.000"),
                new FruitData("Peach", "7000"),
                new FruitData("Pear", "4000"),
                new FruitData("Pineapple", "25.000"),
                new FruitData("Plum", "5000"),
                new FruitData("Pumpkin", "16.000"),
                new FruitData("Raspberries", "5000")
        };
    }
}

Adapter and View holder

package com.example.recycleviewexample;

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.ArrayList;

// An implementation of the RecyclerView.Adapter with ClickableFruitAdapter.ViewHolder as ViewHolder
public class ClickableFruitAdapter extends RecyclerView.Adapter<ClickableFruitAdapter.ViewHolder> {

    // Upon click event mListener.onClicked(int pos) will be raised
    private FruitItemClickedListener mListener;

    // View holder, it will have views for fruitNameText(fruit name) and vitaminCText(vitamin C content). 
    public class ViewHolder extends RecyclerView.ViewHolder {
        public TextView mNameText;
        public TextView mVitaminCText;

 // In the constructor we will assign the views
        public ViewHolder(View v) {
            super(v);
            mNameText = (TextView) v.findViewById(R.id.fruitNameText);
            mVitaminCText = (TextView) v.findViewById(R.id.vitaminCText);
        }
    }

    private ArrayList<FruitData> mFruits = new ArrayList<FruitData>();

    public ClickableFruitAdapter(FruitData[] desc, FruitItemClickedListener l) {
        for (FruitData d: desc) {
            mFruits.add(d);
        }
        mListener = l;
    }

    // Add a fruit to the RecyclerView
    public void addFruit(int pos, FruitData data) {
        mFruits.add(pos, data);
        notifyItemInserted(pos);
    }

    // Remove a fruit from the RecyclerView
    public FruitData removeFruit(int pos) {
        FruitData d = mFruits.get(pos);
        mFruits.remove(pos);
        notifyItemRemoved(pos);
        return d;
    }

     // Change a fruit in the RecyclerView
    public FruitData changeFruit(int pos, FruitData data) {
        FruitData d = mFruits.get(pos);
        mFruits.set(pos, data);
        notifyItemChanged(pos);
        return d;
    }

    // In this method we should create a row for our RecyclerView
    // and return an instance of ViewHolder
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit, parent, false);
        return new ViewHolder(v);
    }

    public static class NameClickListener implements View.OnClickListener {

        private ViewHolder mHolder;
        private FruitItemClickedListener mListener;

        public NameClickListener(ViewHolder holder, FruitItemClickedListener listener) {
            mHolder = holder;
            mListener = listener;
        }

        @Override
        public void onClick(View view) {
            if (mHolder.getLayoutPosition() >= 0) {
                // Raise the onClicked event after getting the position using getLayoutPosition() method,
  // You can also use getAdapterPosition () method to retrieve the position
  // getLayoutPosition()  returns the position of the ViewHolder in terms of the latest layout pass.
  // getAdapterPosition() returns the Adapter position of the item represented by this ViewHolder.
  // getAdapterPosition() might be different than the getLayoutPosition() if there are pending adapter updates but a new layout pass has not happened yet.
                mListener.onClicked(mHolder.getLayoutPosition());
            }
        }
    }

    // Assign the values for the ViewHolder
    // This method can be called when we have to assign a newly created row or when recycling is required.
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        final FruitData fruit = mFruits.get(position);
        holder.mNameText.setText(fruit.mName);
        holder.mVitaminCText.setText(fruit.mVitaminC);

 // Set click listener for the view
        holder.itemView.setOnClickListener(new NameClickListener(holder, mListener));
    }

    @Override
    public int getItemCount() {
        return mFruits.size();
    }
}

The listener class
package com.example.recycleviewexample;

public interface FruitItemClickedListener {

    void onClicked(int pos);
}

The Activity implementation

Now we have almost everything ready, lets put everything together in our Activity implementation.
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

import android.os.Bundle;
import android.util.Log;
import android.widget.RadioGroup;

import java.util.ArrayList;

public class RecyclerItemOperationActivity extends AppCompatActivity implements FruitItemClickedListener {
    protected RecyclerView mRecyclerView;
    protected RecyclerView.LayoutManager mLayoutManager;

    protected ArrayList<fruitdata> mExtraFruits = new ArrayList<fruitdata>();
    protected ClickableFruitAdapter mAdapter;
    protected int mMode = R.id.opAdd;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main2);

        mRecyclerView  = (RecyclerView)findViewById(R.id.alphaList);
        mLayoutManager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(mLayoutManager);

        mAdapter = new ClickableFruitAdapter(FruitData.getList1(), this);
        mRecyclerView.setAdapter(mAdapter);

        for (FruitData d: FruitData.getList2()) {
            mExtraFruits.add(d);
        }

        RadioGroup opGroup = (RadioGroup)findViewById(R.id.opGroup);
        opGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(RadioGroup radioGroup, int i) {
                mMode = i;
            }
        });

    }

    @Override
    public void onClicked(int pos) {
        if (mMode == R.id.opAdd) {
            if (mExtraFruits.size() > 0) {
                mAdapter.addFruit(pos, mExtraFruits.get(0));
                mExtraFruits.remove(0);
            }
        } else if (mMode == R.id.opRemove) {
            mExtraFruits.add(mAdapter.removeFruit(pos));
        } else if (mMode == R.id.opChange) {
            if (mExtraFruits.size() > 0) {
                FruitData d = mExtraFruits.get(0);
                mExtraFruits.add(mAdapter.changeFruit(pos, d));
                mExtraFruits.remove(0);
                mExtraFruits.add(d);
            }
        }
    }
}


Introduction to Android RecyclerView


RecyclerView can be used to display large data. RecyclerView provides multiple enhancement over ListView or a GridView.

In RecyclerView we can set custom layout management for its children's, it has animation support built-in, has item decorator support and it uses the View-holder pattern.

Adding RecyclerView support

It is provided as part of the v7 support library.

To add RecyclerView support add the following dependency in your gradle build file.

dependencies {
 compile "com.android.support:recyclerview-v7:23.1.1"
}

Adding RecyclerView in your application is very similar to that of ListView. Lets go through the steps,

Create the layout file

Let activity_main.xml be our main layout file. Layout file should have android.support.v7.widget.RecyclerView.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/fruitList"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical" />
  
</RelativeLayout>

Create one more layout file to represent a row in the RecyclerView. Let us name the file as fruit.xml.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/fruitNameText"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:background="@android:color/holo_blue_light"
        android:layout_margin="1dp"
        android:text="name"/>

    <TextView
        android:id="@+id/vitaminCText"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:background="@android:color/holo_blue_light"
        android:layout_margin="1dp"
        android:text="description"/>

</LinearLayout>

In our example we will have fruit and its vitamin C content as a list.
Lets create a simple class to store the values.
package com.example.recycleviewexample;

public class FruitData {
    public String mName;
    public String mVitaminC;

    public FruitData(String n, String d) {
        mName = n;
        mVitaminC = d;
    }

 // These data represent the initial data the the RecyclerView will have
 // These are just sample data
    static final FruitData[] getList1() {
        return new FruitData[] {
                new FruitData("Apricots", "5000"),
                new FruitData("Apple", "5000"),
                new FruitData("Banana", "10.000"),
                new FruitData("Blackberries", "150.000"),
                new FruitData("Cherries", "10.000"),
                new FruitData("Grapefruit", "40.000"),
                new FruitData("Grapes", "3000")
        };
    }
}

To add RecyclerView support we should have at-least three component, Adapter, ViewHolder and the RecyclerView itself.

Adapter and ViewHolder

Adapter manages the data, it should be an implementation of RecyclerView.Adapter. Our adapter class is shown below,

ViewHolder stores the view which we want to recycle. It will store view for one entry in the RecyclerView.
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.ArrayList;

// An implementation of the RecyclerView.Adapter with BasicFruitAdapter.ViewHolder as ViewHolder
public class BasicFruitAdapter extends RecyclerView.Adapter {

 // View holder, it will have views for fruitNameText(fruit name) and vitaminCText(vitamin C content). 
    public class ViewHolder extends RecyclerView.ViewHolder {
        public TextView mNameText;  // for fruitNameText
        public TextView mVitaminCText;  // for vitaminCText

  // In the constructor we will assign the views
        public ViewHolder(View v) {
            super(v);
            mNameText = (TextView) v.findViewById(R.id.fruitNameText);
            mVitaminCText = (TextView) v.findViewById(R.id.vitaminCText);
        }
    }

 // Stores the array of fruit data, FruitData has name and vitamin c content
    private ArrayList<fruitdata> mFruits = new ArrayList<fruitdata>();

    public BasicFruitAdapter(FruitData[] desc) {
        for (FruitData d: desc) {
            mFruits.add(d);
        }
    }

 // In this method we should create a row for our RecyclerView
 // and return an instance of ViewHolder
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit, parent, false);
        return new ViewHolder(v);
    }

 // Assign the values for the ViewHolder
 // This method can be called when we have to assign a newly created row or when recycling is required.
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        final FruitData fruit = mFruits.get(position);
        holder.mNameText.setText(fruit.mName);
        holder.mVitaminCText.setText(fruit.mVitaminC);
    }

    @Override
    public int getItemCount() {
        return mFruits.size();
    }
}

The final touch

Now we have almost everything ready, lets put everything together in our Activity implementation. 
package com.example.recycleviewexample;

import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

import android.os.Bundle;

public class RecyclerBasicActivity extends AppCompatActivity {
    protected RecyclerView mRecyclerView;
    protected RecyclerView.LayoutManager mLayoutManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main1);

  // Retrieve the RecyclerView
        mRecyclerView  = (RecyclerView)findViewById(R.id.alphaList);
  
  // Set LinearLayoutManager as we want to just display the data in linear fashion
        mLayoutManager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(mLayoutManager);
  
  // Set the adapter to the RecyclerView
        mRecyclerView.setAdapter(new BasicFruitAdapter(FruitData.getList1()));
    }
} 

That's it, we should now be able to display the data using the RecyclerView.


Monday, 7 December 2015

A simple box blur implementation in OpenGL/ES using shaders

Here I am going to share one of the simplest approach to apply blur. This is not an optimized solution. For an optimized solution you can go for multi-pass rendering and (or) down sampling.
In this approach bluring will be done by taking average color value around a rectangular are of the pixel. Most of this operation will be performed in the fragment shader. This is a single pass algorithm.

Vertex Shader

As we are doing most of the operation in the fragment shader, vertex shader will be very straight forward.

// Vertex coordinate
attribute vec4 aPosition;

// Texture coordinate
attribute vec2 aTexCoordinate;

// Texture coordinate to be passed to fragment shader
varying  vec2 vTexCoordinate;

void main() {
    vTexCoordinate = aTexCoordinate;
    gl_Position = aPosition;
}

Fragment Shader

precision mediump float;

// The sampler object
uniform sampler2D uSampler;

// Texture coordinate passed from vertex shader
varying  vec2 vTexCoordinate;

// This is the value that needs to be added(subtracted) to a 
// texture point to get the next(previous) pixel in the X direction
// This value can be calculated as 1/texturewidth
uniform float uXPixelDistance;

// This is the value that needs to be added(subtracted) to a
// texture point to get the next(previous) pixel in the Y direction
// This value can be calculated as 1/textureheight
uniform float uYPixelDistance;

// How to jump to next pixel, 1 means the very next pixel,
// 2 means the 2th pixel, and so on.
const float jump = 2.0f;

// Number of points around the current point
const float pointRange = 10.0f;

void main() {
    vec4 color = vec4(0, 0, 0, 0);
    vec2 point;
    int count = 0;
 
    // Calculate the total color intensity around the pixel
    // In this case we are calculating pixel intensity around 10 pixels
    for(float u = -pointRange; u < pointRange ; u+=jump) {
        for(float v = -pointRange ; v < pointRange ; v+=jump) {
            point.x = vTexCoordinate.x  + u * uXPixelDistance;
            point.y = vTexCoordinate.y  + v * uYPixelDistance;
            
            // If the point is within the range[0, 1]
            if (point.y >= 0.0f && point.x >= 0.0f &&
                point.y <= 1.0f && point.x <= 1.0f ) {
                ++count;
                color += texture2D(uSampler, point.xy);
            }
        }
    }
    
    // Take the average intensity for the region
    color = color / float(count);
 
    gl_FragColor = vec4(color.xyz, 1.0f);
}