Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.gallery3d.filtershow.ui;
     18 
     19 import android.content.Context;
     20 import android.graphics.Bitmap;
     21 import android.graphics.Canvas;
     22 import android.graphics.Color;
     23 import android.graphics.Paint;
     24 import android.graphics.Path;
     25 import android.graphics.PorterDuff;
     26 import android.graphics.PorterDuffXfermode;
     27 import android.os.AsyncTask;
     28 import android.util.AttributeSet;
     29 import android.util.Log;
     30 import android.view.LayoutInflater;
     31 import android.view.MenuItem;
     32 import android.view.MotionEvent;
     33 import android.view.View;
     34 import android.widget.Button;
     35 import android.widget.LinearLayout;
     36 import android.widget.PopupMenu;
     37 
     38 import com.android.gallery3d.R;
     39 import com.android.gallery3d.filtershow.editors.Editor;
     40 import com.android.gallery3d.filtershow.editors.EditorCurves;
     41 import com.android.gallery3d.filtershow.filters.FilterCurvesRepresentation;
     42 import com.android.gallery3d.filtershow.filters.FiltersManager;
     43 import com.android.gallery3d.filtershow.filters.ImageFilterCurves;
     44 import com.android.gallery3d.filtershow.imageshow.ImageShow;
     45 import com.android.gallery3d.filtershow.presets.ImagePreset;
     46 
     47 import java.util.HashMap;
     48 
     49 public class ImageCurves extends ImageShow {
     50 
     51     private static final String LOGTAG = "ImageCurves";
     52     Paint gPaint = new Paint();
     53     Path gPathSpline = new Path();
     54     HashMap<Integer, String> mIdStrLut;
     55 
     56     private int mCurrentCurveIndex = Spline.RGB;
     57     private boolean mDidAddPoint = false;
     58     private boolean mDidDelete = false;
     59     private ControlPoint mCurrentControlPoint = null;
     60     private int mCurrentPick = -1;
     61     private ImagePreset mLastPreset = null;
     62     int[] redHistogram = new int[256];
     63     int[] greenHistogram = new int[256];
     64     int[] blueHistogram = new int[256];
     65     Path gHistoPath = new Path();
     66 
     67     boolean mDoingTouchMove = false;
     68     private EditorCurves mEditorCurves;
     69     private FilterCurvesRepresentation mFilterCurvesRepresentation;
     70 
     71     public ImageCurves(Context context) {
     72         super(context);
     73         setLayerType(LAYER_TYPE_SOFTWARE, gPaint);
     74         resetCurve();
     75     }
     76 
     77     public ImageCurves(Context context, AttributeSet attrs) {
     78         super(context, attrs);
     79         setLayerType(LAYER_TYPE_SOFTWARE, gPaint);
     80         resetCurve();
     81     }
     82 
     83     @Override
     84     protected boolean enableComparison() {
     85         return false;
     86     }
     87 
     88     @Override
     89     public boolean useUtilityPanel() {
     90         return true;
     91     }
     92 
     93     private void showPopupMenu(LinearLayout accessoryViewList) {
     94         final Button button = (Button) accessoryViewList.findViewById(
     95                 R.id.applyEffect);
     96         if (button == null) {
     97             return;
     98         }
     99         if (mIdStrLut == null){
    100             mIdStrLut = new HashMap<Integer, String>();
    101             mIdStrLut.put(R.id.curve_menu_rgb,
    102                     getContext().getString(R.string.curves_channel_rgb));
    103             mIdStrLut.put(R.id.curve_menu_red,
    104                     getContext().getString(R.string.curves_channel_red));
    105             mIdStrLut.put(R.id.curve_menu_green,
    106                     getContext().getString(R.string.curves_channel_green));
    107             mIdStrLut.put(R.id.curve_menu_blue,
    108                     getContext().getString(R.string.curves_channel_blue));
    109         }
    110         PopupMenu popupMenu = new PopupMenu(getActivity(), button);
    111         popupMenu.getMenuInflater().inflate(R.menu.filtershow_menu_curves, popupMenu.getMenu());
    112         popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
    113             @Override
    114             public boolean onMenuItemClick(MenuItem item) {
    115                 setChannel(item.getItemId());
    116                 button.setText(mIdStrLut.get(item.getItemId()));
    117                 return true;
    118             }
    119         });
    120         Editor.hackFixStrings(popupMenu.getMenu());
    121         popupMenu.show();
    122     }
    123 
    124     @Override
    125     public void openUtilityPanel(final LinearLayout accessoryViewList) {
    126         Context context = accessoryViewList.getContext();
    127         Button view = (Button) accessoryViewList.findViewById(R.id.applyEffect);
    128         view.setText(context.getString(R.string.curves_channel_rgb));
    129         view.setVisibility(View.VISIBLE);
    130 
    131         view.setOnClickListener(new OnClickListener() {
    132                 @Override
    133             public void onClick(View arg0) {
    134                 showPopupMenu(accessoryViewList);
    135             }
    136         });
    137 
    138         if (view != null) {
    139             view.setVisibility(View.VISIBLE);
    140         }
    141     }
    142 
    143     public void nextChannel() {
    144         mCurrentCurveIndex = ((mCurrentCurveIndex + 1) % 4);
    145         invalidate();
    146     }
    147 
    148     @Override
    149     public boolean showTitle() {
    150         return false;
    151     }
    152 
    153     private ImageFilterCurves curves() {
    154         String filterName = getFilterName();
    155         ImagePreset p = getImagePreset();
    156         if (p != null) {
    157             return (ImageFilterCurves) FiltersManager.getManager().getFilter(ImageFilterCurves.class);
    158         }
    159         return null;
    160     }
    161 
    162     private Spline getSpline(int index) {
    163         return mFilterCurvesRepresentation.getSpline(index);
    164     }
    165 
    166     @Override
    167     public void resetParameter() {
    168         super.resetParameter();
    169         resetCurve();
    170         mLastPreset = null;
    171         invalidate();
    172     }
    173 
    174     public void resetCurve() {
    175         if (mFilterCurvesRepresentation != null) {
    176             mFilterCurvesRepresentation.reset();
    177             updateCachedImage();
    178         }
    179     }
    180 
    181     @Override
    182     public void onDraw(Canvas canvas) {
    183         super.onDraw(canvas);
    184         if (mFilterCurvesRepresentation == null) {
    185             return;
    186         }
    187 
    188         gPaint.setAntiAlias(true);
    189 
    190         if (getImagePreset() != mLastPreset && getFilteredImage() != null) {
    191             new ComputeHistogramTask().execute(getFilteredImage());
    192             mLastPreset = getImagePreset();
    193         }
    194 
    195         if (curves() == null) {
    196             return;
    197         }
    198 
    199         if (mCurrentCurveIndex == Spline.RGB || mCurrentCurveIndex == Spline.RED) {
    200             drawHistogram(canvas, redHistogram, Color.RED, PorterDuff.Mode.SCREEN);
    201         }
    202         if (mCurrentCurveIndex == Spline.RGB || mCurrentCurveIndex == Spline.GREEN) {
    203             drawHistogram(canvas, greenHistogram, Color.GREEN, PorterDuff.Mode.SCREEN);
    204         }
    205         if (mCurrentCurveIndex == Spline.RGB || mCurrentCurveIndex == Spline.BLUE) {
    206             drawHistogram(canvas, blueHistogram, Color.BLUE, PorterDuff.Mode.SCREEN);
    207         }
    208         // We only display the other channels curves when showing the RGB curve
    209         if (mCurrentCurveIndex == Spline.RGB) {
    210             for (int i = 0; i < 4; i++) {
    211                 Spline spline = getSpline(i);
    212                 if (i != mCurrentCurveIndex && !spline.isOriginal()) {
    213                     // And we only display a curve if it has more than two
    214                     // points
    215                     spline.draw(canvas, Spline.colorForCurve(i), getWidth(),
    216                             getHeight(), false, mDoingTouchMove);
    217                 }
    218             }
    219         }
    220         // ...but we always display the current curve.
    221         getSpline(mCurrentCurveIndex)
    222                 .draw(canvas, Spline.colorForCurve(mCurrentCurveIndex), getWidth(), getHeight(),
    223                         true, mDoingTouchMove);
    224         drawToast(canvas);
    225 
    226     }
    227 
    228     private int pickControlPoint(float x, float y) {
    229         int pick = 0;
    230         Spline spline = getSpline(mCurrentCurveIndex);
    231         float px = spline.getPoint(0).x;
    232         float py = spline.getPoint(0).y;
    233         double delta = Math.sqrt((px - x) * (px - x) + (py - y) * (py - y));
    234         for (int i = 1; i < spline.getNbPoints(); i++) {
    235             px = spline.getPoint(i).x;
    236             py = spline.getPoint(i).y;
    237             double currentDelta = Math.sqrt((px - x) * (px - x) + (py - y)
    238                     * (py - y));
    239             if (currentDelta < delta) {
    240                 delta = currentDelta;
    241                 pick = i;
    242             }
    243         }
    244 
    245         if (!mDidAddPoint && (delta * getWidth() > 100)
    246                 && (spline.getNbPoints() < 10)) {
    247             return -1;
    248         }
    249 
    250         return pick;
    251     }
    252 
    253     private String getFilterName() {
    254         return "Curves";
    255     }
    256 
    257     @Override
    258     public synchronized boolean onTouchEvent(MotionEvent e) {
    259         if (e.getPointerCount() != 1) {
    260             return true;
    261         }
    262 
    263         if (didFinishScalingOperation()) {
    264             return true;
    265         }
    266 
    267         float margin = Spline.curveHandleSize() / 2;
    268         float posX = e.getX();
    269         if (posX < margin) {
    270             posX = margin;
    271         }
    272         float posY = e.getY();
    273         if (posY < margin) {
    274             posY = margin;
    275         }
    276         if (posX > getWidth() - margin) {
    277             posX = getWidth() - margin;
    278         }
    279         if (posY > getHeight() - margin) {
    280             posY = getHeight() - margin;
    281         }
    282         posX = (posX - margin) / (getWidth() - 2 * margin);
    283         posY = (posY - margin) / (getHeight() - 2 * margin);
    284 
    285         if (e.getActionMasked() == MotionEvent.ACTION_UP) {
    286             mCurrentControlPoint = null;
    287             mCurrentPick = -1;
    288             updateCachedImage();
    289             mDidAddPoint = false;
    290             if (mDidDelete) {
    291                 mDidDelete = false;
    292             }
    293             mDoingTouchMove = false;
    294             return true;
    295         }
    296 
    297         if (mDidDelete) {
    298             return true;
    299         }
    300 
    301         if (curves() == null) {
    302             return true;
    303         }
    304 
    305         if (e.getActionMasked() == MotionEvent.ACTION_MOVE) {
    306             mDoingTouchMove = true;
    307             Spline spline = getSpline(mCurrentCurveIndex);
    308             int pick = mCurrentPick;
    309             if (mCurrentControlPoint == null) {
    310                 pick = pickControlPoint(posX, posY);
    311                 if (pick == -1) {
    312                     mCurrentControlPoint = new ControlPoint(posX, posY);
    313                     pick = spline.addPoint(mCurrentControlPoint);
    314                     mDidAddPoint = true;
    315                 } else {
    316                     mCurrentControlPoint = spline.getPoint(pick);
    317                 }
    318                 mCurrentPick = pick;
    319             }
    320 
    321             if (spline.isPointContained(posX, pick)) {
    322                 spline.movePoint(pick, posX, posY);
    323             } else if (pick != -1 && spline.getNbPoints() > 2) {
    324                 spline.deletePoint(pick);
    325                 mDidDelete = true;
    326             }
    327             updateCachedImage();
    328             invalidate();
    329         }
    330         return true;
    331     }
    332 
    333     public synchronized void updateCachedImage() {
    334         if (getImagePreset() != null) {
    335             resetImageCaches(this);
    336             if (mEditorCurves != null) {
    337                 mEditorCurves.commitLocalRepresentation();
    338             }
    339             invalidate();
    340         }
    341     }
    342 
    343     class ComputeHistogramTask extends AsyncTask<Bitmap, Void, int[]> {
    344         @Override
    345         protected int[] doInBackground(Bitmap... params) {
    346             int[] histo = new int[256 * 3];
    347             Bitmap bitmap = params[0];
    348             int w = bitmap.getWidth();
    349             int h = bitmap.getHeight();
    350             int[] pixels = new int[w * h];
    351             bitmap.getPixels(pixels, 0, w, 0, 0, w, h);
    352             for (int i = 0; i < w; i++) {
    353                 for (int j = 0; j < h; j++) {
    354                     int index = j * w + i;
    355                     int r = Color.red(pixels[index]);
    356                     int g = Color.green(pixels[index]);
    357                     int b = Color.blue(pixels[index]);
    358                     histo[r]++;
    359                     histo[256 + g]++;
    360                     histo[512 + b]++;
    361                 }
    362             }
    363             return histo;
    364         }
    365 
    366         @Override
    367         protected void onPostExecute(int[] result) {
    368             System.arraycopy(result, 0, redHistogram, 0, 256);
    369             System.arraycopy(result, 256, greenHistogram, 0, 256);
    370             System.arraycopy(result, 512, blueHistogram, 0, 256);
    371             invalidate();
    372         }
    373     }
    374 
    375     private void drawHistogram(Canvas canvas, int[] histogram, int color, PorterDuff.Mode mode) {
    376         int max = 0;
    377         for (int i = 0; i < histogram.length; i++) {
    378             if (histogram[i] > max) {
    379                 max = histogram[i];
    380             }
    381         }
    382         float w = getWidth() - Spline.curveHandleSize();
    383         float h = getHeight() - Spline.curveHandleSize() / 2.0f;
    384         float dx = Spline.curveHandleSize() / 2.0f;
    385         float wl = w / histogram.length;
    386         float wh = (0.3f * h) / max;
    387         Paint paint = new Paint();
    388         paint.setARGB(100, 255, 255, 255);
    389         paint.setStrokeWidth((int) Math.ceil(wl));
    390 
    391         Paint paint2 = new Paint();
    392         paint2.setColor(color);
    393         paint2.setStrokeWidth(6);
    394         paint2.setXfermode(new PorterDuffXfermode(mode));
    395         gHistoPath.reset();
    396         gHistoPath.moveTo(dx, h);
    397         boolean firstPointEncountered = false;
    398         float prev = 0;
    399         float last = 0;
    400         for (int i = 0; i < histogram.length; i++) {
    401             float x = i * wl + dx;
    402             float l = histogram[i] * wh;
    403             if (l != 0) {
    404                 float v = h - (l + prev) / 2.0f;
    405                 if (!firstPointEncountered) {
    406                     gHistoPath.lineTo(x, h);
    407                     firstPointEncountered = true;
    408                 }
    409                 gHistoPath.lineTo(x, v);
    410                 prev = l;
    411                 last = x;
    412             }
    413         }
    414         gHistoPath.lineTo(last, h);
    415         gHistoPath.lineTo(w, h);
    416         gHistoPath.close();
    417         canvas.drawPath(gHistoPath, paint2);
    418         paint2.setStrokeWidth(2);
    419         paint2.setStyle(Paint.Style.STROKE);
    420         paint2.setARGB(255, 200, 200, 200);
    421         canvas.drawPath(gHistoPath, paint2);
    422     }
    423 
    424     public void setChannel(int itemId) {
    425         switch (itemId) {
    426             case R.id.curve_menu_rgb: {
    427                 mCurrentCurveIndex = Spline.RGB;
    428                 break;
    429             }
    430             case R.id.curve_menu_red: {
    431                 mCurrentCurveIndex = Spline.RED;
    432                 break;
    433             }
    434             case R.id.curve_menu_green: {
    435                 mCurrentCurveIndex = Spline.GREEN;
    436                 break;
    437             }
    438             case R.id.curve_menu_blue: {
    439                 mCurrentCurveIndex = Spline.BLUE;
    440                 break;
    441             }
    442         }
    443         mEditorCurves.commitLocalRepresentation();
    444         invalidate();
    445     }
    446 
    447     public void setEditor(EditorCurves editorCurves) {
    448         mEditorCurves = editorCurves;
    449     }
    450 
    451     public void setFilterDrawRepresentation(FilterCurvesRepresentation drawRep) {
    452         mFilterCurvesRepresentation = drawRep;
    453     }
    454 }
    455