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.sqrt((px - x) * (px - x) + (py - y) * (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.sqrt((px - x) * (px - x) + (py - y)
    231                     * (py - y));
    232             if (currentDelta < delta) {
    233                 delta = currentDelta;
    234                 pick = i;
    235             }
    236         }
    237 
    238         if (!mDidAddPoint && (delta * getWidth() > 100)
    239                 && (spline.getNbPoints() < 10)) {
    240             return -1;
    241         }
    242 
    243         return pick;
    244     }
    245 
    246     private String getFilterName() {
    247         return "Curves";
    248     }
    249 
    250     @Override
    251     public synchronized boolean onTouchEvent(MotionEvent e) {
    252         if (e.getPointerCount() != 1) {
    253             return true;
    254         }
    255 
    256         if (didFinishScalingOperation()) {
    257             return true;
    258         }
    259 
    260         float margin = Spline.curveHandleSize() / 2;
    261         float posX = e.getX();
    262         if (posX < margin) {
    263             posX = margin;
    264         }
    265         float posY = e.getY();
    266         if (posY < margin) {
    267             posY = margin;
    268         }
    269         if (posX > getWidth() - margin) {
    270             posX = getWidth() - margin;
    271         }
    272         if (posY > getHeight() - margin) {
    273             posY = getHeight() - margin;
    274         }
    275         posX = (posX - margin) / (getWidth() - 2 * margin);
    276         posY = (posY - margin) / (getHeight() - 2 * margin);
    277 
    278         if (e.getActionMasked() == MotionEvent.ACTION_UP) {
    279             mCurrentControlPoint = null;
    280             mCurrentPick = -1;
    281             updateCachedImage();
    282             mDidAddPoint = false;
    283             if (mDidDelete) {
    284                 mDidDelete = false;
    285             }
    286             mDoingTouchMove = false;
    287             return true;
    288         }
    289 
    290         if (mDidDelete) {
    291             return true;
    292         }
    293 
    294         if (curves() == null) {
    295             return true;
    296         }
    297 
    298         if (e.getActionMasked() == MotionEvent.ACTION_MOVE) {
    299             mDoingTouchMove = true;
    300             Spline spline = getSpline(mCurrentCurveIndex);
    301             int pick = mCurrentPick;
    302             if (mCurrentControlPoint == null) {
    303                 pick = pickControlPoint(posX, posY);
    304                 if (pick == -1) {
    305                     mCurrentControlPoint = new ControlPoint(posX, posY);
    306                     pick = spline.addPoint(mCurrentControlPoint);
    307                     mDidAddPoint = true;
    308                 } else {
    309                     mCurrentControlPoint = spline.getPoint(pick);
    310                 }
    311                 mCurrentPick = pick;
    312             }
    313 
    314             if (spline.isPointContained(posX, pick)) {
    315                 spline.movePoint(pick, posX, posY);
    316             } else if (pick != -1 && spline.getNbPoints() > 2) {
    317                 spline.deletePoint(pick);
    318                 mDidDelete = true;
    319             }
    320             updateCachedImage();
    321             invalidate();
    322         }
    323         return true;
    324     }
    325 
    326     public synchronized void updateCachedImage() {
    327         if (getImagePreset() != null) {
    328             resetImageCaches(this);
    329             if (mEditorCurves != null) {
    330                 mEditorCurves.commitLocalRepresentation();
    331             }
    332             invalidate();
    333         }
    334     }
    335 
    336     class ComputeHistogramTask extends AsyncTask<Bitmap, Void, int[]> {
    337         @Override
    338         protected int[] doInBackground(Bitmap... params) {
    339             int[] histo = new int[256 * 3];
    340             Bitmap bitmap = params[0];
    341             int w = bitmap.getWidth();
    342             int h = bitmap.getHeight();
    343             int[] pixels = new int[w * h];
    344             bitmap.getPixels(pixels, 0, w, 0, 0, w, h);
    345             for (int i = 0; i < w; i++) {
    346                 for (int j = 0; j < h; j++) {
    347                     int index = j * w + i;
    348                     int r = Color.red(pixels[index]);
    349                     int g = Color.green(pixels[index]);
    350                     int b = Color.blue(pixels[index]);
    351                     histo[r]++;
    352                     histo[256 + g]++;
    353                     histo[512 + b]++;
    354                 }
    355             }
    356             return histo;
    357         }
    358 
    359         @Override
    360         protected void onPostExecute(int[] result) {
    361             System.arraycopy(result, 0, redHistogram, 0, 256);
    362             System.arraycopy(result, 256, greenHistogram, 0, 256);
    363             System.arraycopy(result, 512, blueHistogram, 0, 256);
    364             invalidate();
    365         }
    366     }
    367 
    368     private void drawHistogram(Canvas canvas, int[] histogram, int color, PorterDuff.Mode mode) {
    369         int max = 0;
    370         for (int i = 0; i < histogram.length; i++) {
    371             if (histogram[i] > max) {
    372                 max = histogram[i];
    373             }
    374         }
    375         float w = getWidth() - Spline.curveHandleSize();
    376         float h = getHeight() - Spline.curveHandleSize() / 2.0f;
    377         float dx = Spline.curveHandleSize() / 2.0f;
    378         float wl = w / histogram.length;
    379         float wh = (0.3f * h) / max;
    380         Paint paint = new Paint();
    381         paint.setARGB(100, 255, 255, 255);
    382         paint.setStrokeWidth((int) Math.ceil(wl));
    383 
    384         Paint paint2 = new Paint();
    385         paint2.setColor(color);
    386         paint2.setStrokeWidth(6);
    387         paint2.setXfermode(new PorterDuffXfermode(mode));
    388         gHistoPath.reset();
    389         gHistoPath.moveTo(dx, h);
    390         boolean firstPointEncountered = false;
    391         float prev = 0;
    392         float last = 0;
    393         for (int i = 0; i < histogram.length; i++) {
    394             float x = i * wl + dx;
    395             float l = histogram[i] * wh;
    396             if (l != 0) {
    397                 float v = h - (l + prev) / 2.0f;
    398                 if (!firstPointEncountered) {
    399                     gHistoPath.lineTo(x, h);
    400                     firstPointEncountered = true;
    401                 }
    402                 gHistoPath.lineTo(x, v);
    403                 prev = l;
    404                 last = x;
    405             }
    406         }
    407         gHistoPath.lineTo(last, h);
    408         gHistoPath.lineTo(w, h);
    409         gHistoPath.close();
    410         canvas.drawPath(gHistoPath, paint2);
    411         paint2.setStrokeWidth(2);
    412         paint2.setStyle(Paint.Style.STROKE);
    413         paint2.setARGB(255, 200, 200, 200);
    414         canvas.drawPath(gHistoPath, paint2);
    415     }
    416 
    417     public void setChannel(int itemId) {
    418         switch (itemId) {
    419             case R.id.curve_menu_rgb: {
    420                 mCurrentCurveIndex = Spline.RGB;
    421                 break;
    422             }
    423             case R.id.curve_menu_red: {
    424                 mCurrentCurveIndex = Spline.RED;
    425                 break;
    426             }
    427             case R.id.curve_menu_green: {
    428                 mCurrentCurveIndex = Spline.GREEN;
    429                 break;
    430             }
    431             case R.id.curve_menu_blue: {
    432                 mCurrentCurveIndex = Spline.BLUE;
    433                 break;
    434             }
    435         }
    436         mEditorCurves.commitLocalRepresentation();
    437         invalidate();
    438     }
    439 
    440     public void setEditor(EditorCurves editorCurves) {
    441         mEditorCurves = editorCurves;
    442     }
    443 
    444     public void setFilterDrawRepresentation(FilterCurvesRepresentation drawRep) {
    445         mFilterCurvesRepresentation = drawRep;
    446     }
    447 }
    448