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
17package com.android.gallery3d.filtershow.ui;
18
19import android.content.Context;
20import android.graphics.Bitmap;
21import android.graphics.Canvas;
22import android.graphics.Color;
23import android.graphics.Paint;
24import android.graphics.Path;
25import android.graphics.PorterDuff;
26import android.graphics.PorterDuffXfermode;
27import android.os.AsyncTask;
28import android.util.AttributeSet;
29import android.view.MotionEvent;
30
31import com.android.gallery3d.R;
32import com.android.gallery3d.filtershow.filters.ImageFilterCurves;
33import com.android.gallery3d.filtershow.imageshow.ImageSlave;
34import com.android.gallery3d.filtershow.presets.ImagePreset;
35
36public class ImageCurves extends ImageSlave {
37
38    private static final String LOGTAG = "ImageCurves";
39    Paint gPaint = new Paint();
40    Path gPathSpline = new Path();
41
42    private int mCurrentCurveIndex = Spline.RGB;
43    private boolean mDidAddPoint = false;
44    private boolean mDidDelete = false;
45    private ControlPoint mCurrentControlPoint = null;
46    private ImagePreset mLastPreset = null;
47    int[] redHistogram = new int[256];
48    int[] greenHistogram = new int[256];
49    int[] blueHistogram = new int[256];
50    Path gHistoPath = new Path();
51
52    boolean mDoingTouchMove = false;
53
54    public ImageCurves(Context context) {
55        super(context);
56        resetCurve();
57    }
58
59    public ImageCurves(Context context, AttributeSet attrs) {
60        super(context, attrs);
61        resetCurve();
62    }
63
64    public void nextChannel() {
65        mCurrentCurveIndex = ((mCurrentCurveIndex + 1) % 4);
66        invalidate();
67    }
68
69    @Override
70    public boolean showTitle() {
71        return false;
72    }
73
74    private ImageFilterCurves curves() {
75        if (getMaster() != null) {
76            String filterName = getFilterName();
77            return (ImageFilterCurves) getImagePreset().getFilter(filterName);
78        }
79        return null;
80    }
81
82    private Spline getSpline(int index) {
83        return curves().getSpline(index);
84    }
85
86    @Override
87    public void resetParameter() {
88        super.resetParameter();
89        resetCurve();
90        mLastPreset = null;
91        invalidate();
92    }
93
94    public void resetCurve() {
95        if (getMaster() != null && curves() != null) {
96            curves().reset();
97            updateCachedImage();
98        }
99    }
100
101    @Override
102    public void onDraw(Canvas canvas) {
103        super.onDraw(canvas);
104
105        gPaint.setAntiAlias(true);
106
107        if (getImagePreset() != mLastPreset && getFilteredImage() != null) {
108            new ComputeHistogramTask().execute(getFilteredImage());
109            mLastPreset = getImagePreset();
110        }
111
112        if (curves() == null) {
113            return;
114        }
115
116        if (mCurrentCurveIndex == Spline.RGB || mCurrentCurveIndex == Spline.RED) {
117            drawHistogram(canvas, redHistogram, Color.RED, PorterDuff.Mode.SCREEN);
118        }
119        if (mCurrentCurveIndex == Spline.RGB || mCurrentCurveIndex == Spline.GREEN) {
120            drawHistogram(canvas, greenHistogram, Color.GREEN, PorterDuff.Mode.SCREEN);
121        }
122        if (mCurrentCurveIndex == Spline.RGB || mCurrentCurveIndex == Spline.BLUE) {
123            drawHistogram(canvas, blueHistogram, Color.BLUE, PorterDuff.Mode.SCREEN);
124        }
125        // We only display the other channels curves when showing the RGB curve
126        if (mCurrentCurveIndex == Spline.RGB) {
127            for (int i = 0; i < 4; i++) {
128                Spline spline = getSpline(i);
129                if (i != mCurrentCurveIndex && !spline.isOriginal()) {
130                    // And we only display a curve if it has more than two
131                    // points
132                    spline.draw(canvas, Spline.colorForCurve(i), getWidth(),
133                            getHeight(), false, mDoingTouchMove);
134                }
135            }
136        }
137        // ...but we always display the current curve.
138        getSpline(mCurrentCurveIndex)
139                .draw(canvas, Spline.colorForCurve(mCurrentCurveIndex), getWidth(), getHeight(),
140                        true, mDoingTouchMove);
141        drawToast(canvas);
142
143    }
144
145    private int pickControlPoint(float x, float y) {
146        int pick = 0;
147        Spline spline = getSpline(mCurrentCurveIndex);
148        float px = spline.getPoint(0).x;
149        float py = spline.getPoint(0).y;
150        double delta = Math.sqrt((px - x) * (px - x) + (py - y) * (py - y));
151        for (int i = 1; i < spline.getNbPoints(); i++) {
152            px = spline.getPoint(i).x;
153            py = spline.getPoint(i).y;
154            double currentDelta = Math.sqrt((px - x) * (px - x) + (py - y)
155                    * (py - y));
156            if (currentDelta < delta) {
157                delta = currentDelta;
158                pick = i;
159            }
160        }
161
162        if (!mDidAddPoint && (delta * getWidth() > 100)
163                && (spline.getNbPoints() < 10)) {
164            return -1;
165        }
166
167        return pick;
168    }
169
170    private String getFilterName() {
171        return "Curves";
172    }
173
174    @Override
175    public synchronized boolean onTouchEvent(MotionEvent e) {
176        float posX = e.getX() / getWidth();
177        float posY = e.getY();
178        float margin = Spline.curveHandleSize() / 2;
179        if (posY < margin) {
180            posY = margin;
181        }
182        if (posY > getHeight() - margin) {
183            posY = getHeight() - margin;
184        }
185        posY = (posY - margin) / (getHeight() - 2 * margin);
186
187        if (e.getActionMasked() == MotionEvent.ACTION_UP) {
188            mCurrentControlPoint = null;
189            updateCachedImage();
190            mDidAddPoint = false;
191            if (mDidDelete) {
192                mDidDelete = false;
193            }
194            mDoingTouchMove = false;
195            return true;
196        }
197        mDoingTouchMove = true;
198
199        if (mDidDelete) {
200            return true;
201        }
202
203        if (curves() == null) {
204            return true;
205        }
206
207        Spline spline = getSpline(mCurrentCurveIndex);
208        int pick = pickControlPoint(posX, posY);
209        if (mCurrentControlPoint == null) {
210            if (pick == -1) {
211                mCurrentControlPoint = new ControlPoint(posX, posY);
212                pick = spline.addPoint(mCurrentControlPoint);
213                mDidAddPoint = true;
214            } else {
215                mCurrentControlPoint = spline.getPoint(pick);
216            }
217        }
218
219        if (spline.isPointContained(posX, pick)) {
220            spline.didMovePoint(mCurrentControlPoint);
221            spline.movePoint(pick, posX, posY);
222        } else if (pick != -1 && spline.getNbPoints() > 2) {
223            spline.deletePoint(pick);
224            mDidDelete = true;
225        }
226        updateCachedImage();
227        invalidate();
228        return true;
229    }
230
231    public synchronized void updateCachedImage() {
232        // update image
233        if (getImagePreset() != null) {
234            resetImageCaches(this);
235            invalidate();
236        }
237    }
238
239    class ComputeHistogramTask extends AsyncTask<Bitmap, Void, int[]> {
240        @Override
241        protected int[] doInBackground(Bitmap... params) {
242            int[] histo = new int[256 * 3];
243            Bitmap bitmap = params[0];
244            int w = bitmap.getWidth();
245            int h = bitmap.getHeight();
246            int[] pixels = new int[w * h];
247            bitmap.getPixels(pixels, 0, w, 0, 0, w, h);
248            for (int i = 0; i < w; i++) {
249                for (int j = 0; j < h; j++) {
250                    int index = j * w + i;
251                    int r = Color.red(pixels[index]);
252                    int g = Color.green(pixels[index]);
253                    int b = Color.blue(pixels[index]);
254                    histo[r]++;
255                    histo[256 + g]++;
256                    histo[512 + b]++;
257                }
258            }
259            return histo;
260        }
261
262        @Override
263        protected void onPostExecute(int[] result) {
264            System.arraycopy(result, 0, redHistogram, 0, 256);
265            System.arraycopy(result, 256, greenHistogram, 0, 256);
266            System.arraycopy(result, 512, blueHistogram, 0, 256);
267            invalidate();
268        }
269    }
270
271    private void drawHistogram(Canvas canvas, int[] histogram, int color, PorterDuff.Mode mode) {
272        int max = 0;
273        for (int i = 0; i < histogram.length; i++) {
274            if (histogram[i] > max) {
275                max = histogram[i];
276            }
277        }
278        float w = getWidth();
279        float h = getHeight();
280        float wl = w / histogram.length;
281        float wh = (0.3f * h) / max;
282        Paint paint = new Paint();
283        paint.setARGB(100, 255, 255, 255);
284        paint.setStrokeWidth((int) Math.ceil(wl));
285
286        Paint paint2 = new Paint();
287        paint2.setColor(color);
288        paint2.setStrokeWidth(6);
289        paint2.setXfermode(new PorterDuffXfermode(mode));
290        gHistoPath.reset();
291        gHistoPath.moveTo(0, h);
292        boolean firstPointEncountered = false;
293        float prev = 0;
294        float last = 0;
295        for (int i = 0; i < histogram.length; i++) {
296            float x = i * wl;
297            float l = histogram[i] * wh;
298            if (l != 0) {
299                float v = h - (l + prev) / 2.0f;
300                if (!firstPointEncountered) {
301                    gHistoPath.lineTo(x, h);
302                    firstPointEncountered = true;
303                }
304                gHistoPath.lineTo(x, v);
305                prev = l;
306                last = x;
307            }
308        }
309        gHistoPath.lineTo(last, h);
310        gHistoPath.lineTo(w, h);
311        gHistoPath.close();
312        canvas.drawPath(gHistoPath, paint2);
313        paint2.setStrokeWidth(2);
314        paint2.setStyle(Paint.Style.STROKE);
315        paint2.setARGB(255, 200, 200, 200);
316        canvas.drawPath(gHistoPath, paint2);
317    }
318
319    public void setChannel(int itemId) {
320        switch (itemId) {
321            case R.id.curve_menu_rgb: {
322                mCurrentCurveIndex = Spline.RGB;
323                break;
324            }
325            case R.id.curve_menu_red: {
326                mCurrentCurveIndex = Spline.RED;
327                break;
328            }
329            case R.id.curve_menu_green: {
330                mCurrentCurveIndex = Spline.GREEN;
331                break;
332            }
333            case R.id.curve_menu_blue: {
334                mCurrentCurveIndex = Spline.BLUE;
335                break;
336            }
337        }
338        invalidate();
339    }
340}
341