com.fractview.ImageViewFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.fractview.ImageViewFragment.java

Source

/*
 * This file is part of FractView.
 *
 * FractView is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * FractView is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FractView.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.fractview;

import com.fractview.modes.AbstractImgCache;
import com.fractview.modes.ScaleableCache;
import com.fractview.modes.ScaleablePrefs;

import android.graphics.Matrix;
import android.graphics.RectF;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.ImageView;
import android.widget.Toast;

public class ImageViewFragment extends Fragment {

    private static final String TAG = "ImageViewFragment";

    public static final String TASK_TAG = "at.fractview.TASK_FRAGMENT_TAG";

    private static int MIN_TOUCH_TIME = 166; // Touch event must last at least this many MS, otherwise it is considered to be accidential

    private static final int TASK_FRAGMENT = 0;

    private static final int MILLISECONDS_TILL_UPDATE = 166; // 6 times per second

    private static final Matrix.ScaleToFit viewPolicy = Matrix.ScaleToFit.CENTER;

    private ImageView imageView; // Viewer for image

    private Handler handler; // Handler to regularly update view
    private Runnable updateView;

    private Matrix viewToImageMatrix; // Matrix to scale image to screen size
    private Matrix inverseViewToImageMatrix; // Corresponding inverse matrix

    private Matrix imageMatrix; // Matrix that is used on the bitmap

    private EscapeTimeFragment taskFragment;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        View v = inflater.inflate(R.layout.imageview, container, false);

        imageView = (ImageView) v.findViewById(R.id.imageView);

        imageView.setOnTouchListener(new TouchListener());

        // Create data structures
        viewToImageMatrix = new Matrix();
        inverseViewToImageMatrix = new Matrix();

        imageMatrix = new Matrix();

        // Create handler to get regular updates
        this.handler = new Handler();
        this.updateView = new Runnable() {
            // Careful: this runnable holds a reference to the MainActivity

            @Override
            public void run() {
                // update view
                imageView.invalidate();

                if (taskFragment.taskIsRunning()) {
                    handler.postDelayed(this, MILLISECONDS_TILL_UPDATE);
                } else {
                    // Log.d(this.toString(), "No further updates because task is not running anymore");

                    if (!taskFragment.taskIsCancelled()) {
                        String time = String.format("%.2f", taskFragment.getElapsedTime());
                        // Show toast
                        Toast.makeText(getActivity(), "Calculation finished: " + time + " sec", Toast.LENGTH_SHORT)
                                .show();
                    }
                }
            }
        };

        // Get (if there is) old DataFragment
        taskFragment = (EscapeTimeFragment) getFragmentManager().findFragmentByTag(TASK_TAG);

        if (taskFragment == null) {
            // Creeate new task fragment
            taskFragment = new EscapeTimeFragment();
            getFragmentManager().beginTransaction().add(taskFragment, TASK_TAG).commit();

            // The handlers are started from the task fragment.
            taskFragment.setTargetFragment(this, TASK_FRAGMENT);

            // Start task and also handler
            // taskFragment.startTask(); This is done inside onCreate of taskFragment
            // via target this will call back to here.
        } else {
            // set old task fragment
            taskFragment.setTargetFragment(this, TASK_FRAGMENT);

            // set bitmap and start handler
            initializeTaskView();
        }

        // And set matrices as soon as view is layouted
        imageView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                initImageMatrix();
            }
        });

        // Inflate the layout for this fragment
        return v;
    }

    @Override
    public void onDestroy() {
        stopRedrawing();
        super.onDestroy();
    }

    @Override
    public void onPause() {
        stopRedrawing();
        super.onPause();
    }

    @Override
    public void onResume() {
        startRedrawing();
        super.onResume();
    }

    public void stopRedrawing() {
        handler.removeCallbacks(updateView);
    }

    public void startRedrawing() {
        // Make sure that it is only once in handler.
        handler.removeCallbacks(updateView);
        handler.post(updateView);
    }

    private void initImageMatrix() {
        // Set initMatrix + inverse to current view-size
        float vw = imageView.getWidth();
        float vh = imageView.getHeight();

        // Right after creation, this view might not 
        // be inside anything and therefore have size 0.
        if (vw <= 0 && vh <= 0)
            return; // do nothing.

        float bw = taskFragment.bitmap().getWidth();
        float bh = taskFragment.bitmap().getHeight();

        RectF viewRect = new RectF(0f, 0f, vw, vh);
        RectF bitmapRect;

        if (vw > vh) {
            // if width of view is bigger, match longer side to it
            bitmapRect = new RectF(0f, 0f, Math.max(bw, bh), Math.min(bw, bh));
        } else {
            bitmapRect = new RectF(0f, 0f, Math.min(bw, bh), Math.max(bw, bh));
        }

        viewToImageMatrix.setRectToRect(bitmapRect, viewRect, viewPolicy);

        if (vw > vh ^ bw > bh) {
            // Turn centerImageMatrix by 90 degrees
            Matrix m = new Matrix();
            m.postRotate(90f);
            m.postTranslate(bh, 0);

            viewToImageMatrix.preConcat(m);
        }

        if (!viewToImageMatrix.invert(inverseViewToImageMatrix)) {
            throw new IllegalArgumentException("matrix cannot be inverted...");
        }

        imageView.setImageMatrix(viewToImageMatrix);
    }

    public void initializeTaskView() {
        if (taskFragment != null) {
            if (taskFragment.getTargetFragment() != this) {
                Log.e(TAG, "target of task fragment is not this image view!");
            }

            imageView.setImageBitmap(taskFragment.bitmap());
            initImageMatrix(); // Bitmap might have been resized
            startRedrawing();
        } else {
            Log.e(TAG, "createView was not yet called!");
        }
    }

    private void applyTouch(Matrix bitmapMatrix, Matrix prefsMatrix) {
        // Did we only move?
        final float[] bm = new float[9];
        bitmapMatrix.getValues(bm);

        if (bm[0] == 1f && bm[1] == 0f && bm[3] == 0f && bm[4] == 1f) {
            Log.d(TAG, "We only moved!");
            taskFragment.modifyImage(new UnsafeImageEditor() {
                @Override
                public void edit(AbstractImgCache cache) {
                    ScaleableCache scaleable = (ScaleableCache) cache;
                    scaleable.move((int) Math.round(bm[2]), (int) Math.round(bm[5]));
                }
            }, true);
        } else {
            // Update zoom
            Log.d(TAG, "We need to scale");
            Matrix m = new Matrix();
            prefsMatrix.invert(m);

            final float[] matrix = new float[9];
            m.getValues(matrix);

            taskFragment.modifyImage(new UnsafeImageEditor() {
                @Override
                public void edit(AbstractImgCache cache) {
                    ScaleableCache scaleable = (ScaleableCache) cache;
                    scaleable.newRelativeScale(matrix);
                }
            }, true);
        }
    }

    private class TouchListener implements OnTouchListener {

        private MultiTouch bitmapTouch; // current touch event, scaled to bitmap size
        private MultiTouch prefsTouch; // current touch event, scaled to preferences size

        private long touchStartTime;

        @Override
        public boolean onTouch(View v, MotionEvent evt) {
            // Careful not to confuse index and id of evt

            ImageView view = (ImageView) v;
            ScaleablePrefs scaleable = (ScaleablePrefs) taskFragment.prefs();

            int w = taskFragment.bitmap().getWidth();
            int h = taskFragment.bitmap().getHeight();

            switch (evt.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                bitmapTouch = new MultiTouch();
                prefsTouch = new MultiTouch();

                touchStartTime = System.currentTimeMillis();

                for (int i = 0; i < evt.getPointerCount(); i++) {
                    // Yes, in fact pointerCounter can be > 1.

                    float[] p = new float[] { evt.getX(i), evt.getY(i) };
                    inverseViewToImageMatrix.mapPoints(p);

                    int id = evt.getPointerId(i);

                    bitmapTouch.down(id, p);

                    p[0] = scaleable.normX(p[0], w, h);
                    p[1] = scaleable.normY(p[1], w, h);

                    prefsTouch.down(id, p);
                }

                break;
            case MotionEvent.ACTION_UP:
                // Apply selection: Change prefs, create new bitmap with preview and set it in view
                if (bitmapTouch != null) {
                    long duration = System.currentTimeMillis() - touchStartTime;

                    if (duration > MIN_TOUCH_TIME) {
                        applyTouch(bitmapTouch.matrix(), prefsTouch.matrix());
                    } else { // otherwise ignore
                        Log.d(TAG, "Touch event ignored because it was too short");
                        imageView.setImageMatrix(viewToImageMatrix);
                    }

                    bitmapTouch = prefsTouch = null;
                } else {
                    Log.w(TAG, "Received Action-up, but there's no touch-object...");
                }
                break;

            case MotionEvent.ACTION_MOVE:
                if (bitmapTouch != null) {
                    for (int i = 0; i < evt.getPointerCount(); i++) {
                        float[] p = new float[] { evt.getX(i), evt.getY(i) };
                        inverseViewToImageMatrix.mapPoints(p);

                        int id = evt.getPointerId(i); // Careful, we need the id in the touch event
                        bitmapTouch.moveTo(id, p);

                        p[0] = scaleable.normX(p[0], w, h);
                        p[1] = scaleable.normY(p[1], w, h);

                        prefsTouch.moveTo(id, p);
                    }
                }

                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                if (bitmapTouch != null) {
                    int i = evt.getActionIndex();
                    int id = evt.getPointerId(i);

                    float[] p = new float[] { evt.getX(i), evt.getY(i) };
                    inverseViewToImageMatrix.mapPoints(p);

                    bitmapTouch.down(id, p);

                    p[0] = scaleable.normX(p[0], w, h);
                    p[1] = scaleable.normY(p[1], w, h);

                    prefsTouch.down(id, p);
                }
                break;
            case MotionEvent.ACTION_POINTER_UP:
                if (bitmapTouch != null) {
                    int id = evt.getPointerId(evt.getActionIndex());
                    bitmapTouch.up(id);
                    prefsTouch.up(id);
                }
                break;
            default:
            }

            if (bitmapTouch != null) {
                imageMatrix.setConcat(viewToImageMatrix, bitmapTouch.matrix());
                view.setImageMatrix(imageMatrix);
                v.invalidate();
            }

            return true;
        }
    }
}