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