1a9f280f938b5fd5891c5cfe0999f8f1d4945d7a1nicolasroard/*
2a9f280f938b5fd5891c5cfe0999f8f1d4945d7a1nicolasroard * Copyright (C) 2012 The Android Open Source Project
3a9f280f938b5fd5891c5cfe0999f8f1d4945d7a1nicolasroard *
4a9f280f938b5fd5891c5cfe0999f8f1d4945d7a1nicolasroard * Licensed under the Apache License, Version 2.0 (the "License");
5a9f280f938b5fd5891c5cfe0999f8f1d4945d7a1nicolasroard * you may not use this file except in compliance with the License.
6a9f280f938b5fd5891c5cfe0999f8f1d4945d7a1nicolasroard * You may obtain a copy of the License at
7a9f280f938b5fd5891c5cfe0999f8f1d4945d7a1nicolasroard *
8a9f280f938b5fd5891c5cfe0999f8f1d4945d7a1nicolasroard *      http://www.apache.org/licenses/LICENSE-2.0
9a9f280f938b5fd5891c5cfe0999f8f1d4945d7a1nicolasroard *
10a9f280f938b5fd5891c5cfe0999f8f1d4945d7a1nicolasroard * Unless required by applicable law or agreed to in writing, software
11a9f280f938b5fd5891c5cfe0999f8f1d4945d7a1nicolasroard * distributed under the License is distributed on an "AS IS" BASIS,
12a9f280f938b5fd5891c5cfe0999f8f1d4945d7a1nicolasroard * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13a9f280f938b5fd5891c5cfe0999f8f1d4945d7a1nicolasroard * See the License for the specific language governing permissions and
14a9f280f938b5fd5891c5cfe0999f8f1d4945d7a1nicolasroard * limitations under the License.
15a9f280f938b5fd5891c5cfe0999f8f1d4945d7a1nicolasroard */
160d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard
17368ae73ad0627dac41709b925fc02a12548ab80dnicolasroardpackage com.android.gallery3d.filtershow.imageshow;
180d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard
190d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroardimport android.content.Context;
203992ae6cf1cbb7b57a22d8287c40451558408335nicolasroardimport android.graphics.Bitmap;
210d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroardimport android.graphics.Canvas;
223992ae6cf1cbb7b57a22d8287c40451558408335nicolasroardimport android.graphics.Color;
230d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroardimport android.graphics.Paint;
240d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroardimport android.graphics.Path;
253992ae6cf1cbb7b57a22d8287c40451558408335nicolasroardimport android.graphics.PorterDuff;
263992ae6cf1cbb7b57a22d8287c40451558408335nicolasroardimport android.graphics.PorterDuffXfermode;
273992ae6cf1cbb7b57a22d8287c40451558408335nicolasroardimport android.os.AsyncTask;
280d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroardimport android.util.AttributeSet;
299820e7e753b7e1977ef3d2163605431769ce9165nicolasroardimport android.view.MenuItem;
300d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroardimport android.view.MotionEvent;
319820e7e753b7e1977ef3d2163605431769ce9165nicolasroardimport android.view.View;
326a07dfbe03c8396cc43840a1630ea480a2cff663John Hofordimport android.widget.Button;
339820e7e753b7e1977ef3d2163605431769ce9165nicolasroardimport android.widget.LinearLayout;
349820e7e753b7e1977ef3d2163605431769ce9165nicolasroardimport android.widget.PopupMenu;
350d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard
36c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroardimport com.android.gallery3d.R;
3764fc2ed005e2f1dc9ed0b34481c46a370c244b86John Hofordimport com.android.gallery3d.filtershow.FilterShowActivity;
387638772c0ceb5528b12c9943bad5391658d6c78dJohn Reckimport com.android.gallery3d.filtershow.editors.Editor;
3919161944e0efb1ffe23274d7cf5315ce047e9dacJohn Hofordimport com.android.gallery3d.filtershow.editors.EditorCurves;
4019161944e0efb1ffe23274d7cf5315ce047e9dacJohn Hofordimport com.android.gallery3d.filtershow.filters.FilterCurvesRepresentation;
4171f04cbaedbb89e313e0b86b531640db2d3f6016nicolasroardimport com.android.gallery3d.filtershow.filters.FiltersManager;
4292e2341248e99c691f38820503984bc5e2f18811nicolasroardimport com.android.gallery3d.filtershow.filters.ImageFilterCurves;
43ce9ceff5776a9b0479c30cbeb2a9388b44df1865nicolasroardimport com.android.gallery3d.filtershow.pipeline.ImagePreset;
4492e2341248e99c691f38820503984bc5e2f18811nicolasroard
456a07dfbe03c8396cc43840a1630ea480a2cff663John Hofordimport java.util.HashMap;
466a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford
4763a7dac01d394523799939596b960cb03321798dJohn Hofordpublic class ImageCurves extends ImageShow {
480d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard
490d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard    private static final String LOGTAG = "ImageCurves";
500d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard    Paint gPaint = new Paint();
510d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard    Path gPathSpline = new Path();
526a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford    HashMap<Integer, String> mIdStrLut;
533992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard
54c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard    private int mCurrentCurveIndex = Spline.RGB;
550d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard    private boolean mDidAddPoint = false;
560d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard    private boolean mDidDelete = false;
570d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard    private ControlPoint mCurrentControlPoint = null;
58952144dd3146a195ea4bd0be3f88275df8b31c64nicolasroard    private int mCurrentPick = -1;
593992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard    private ImagePreset mLastPreset = null;
603992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard    int[] redHistogram = new int[256];
613992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard    int[] greenHistogram = new int[256];
623992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard    int[] blueHistogram = new int[256];
633992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard    Path gHistoPath = new Path();
640d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard
65c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard    boolean mDoingTouchMove = false;
6619161944e0efb1ffe23274d7cf5315ce047e9dacJohn Hoford    private EditorCurves mEditorCurves;
6719161944e0efb1ffe23274d7cf5315ce047e9dacJohn Hoford    private FilterCurvesRepresentation mFilterCurvesRepresentation;
68c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard
690d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard    public ImageCurves(Context context) {
700d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard        super(context);
71f7cfa85f7dc2bb29affefff9374f3a4029b8a057nicolasroard        setLayerType(LAYER_TYPE_SOFTWARE, gPaint);
720d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard        resetCurve();
730d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard    }
740d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard
750d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard    public ImageCurves(Context context, AttributeSet attrs) {
760d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard        super(context, attrs);
77f7cfa85f7dc2bb29affefff9374f3a4029b8a057nicolasroard        setLayerType(LAYER_TYPE_SOFTWARE, gPaint);
780d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard        resetCurve();
790d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard    }
800d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard
819820e7e753b7e1977ef3d2163605431769ce9165nicolasroard    @Override
823024aa22d372c0d6608b143a0c46b86c2104f9b1nicolasroard    protected boolean enableComparison() {
833024aa22d372c0d6608b143a0c46b86c2104f9b1nicolasroard        return false;
843024aa22d372c0d6608b143a0c46b86c2104f9b1nicolasroard    }
853024aa22d372c0d6608b143a0c46b86c2104f9b1nicolasroard
863024aa22d372c0d6608b143a0c46b86c2104f9b1nicolasroard    @Override
879820e7e753b7e1977ef3d2163605431769ce9165nicolasroard    public boolean useUtilityPanel() {
889820e7e753b7e1977ef3d2163605431769ce9165nicolasroard        return true;
899820e7e753b7e1977ef3d2163605431769ce9165nicolasroard    }
909820e7e753b7e1977ef3d2163605431769ce9165nicolasroard
919820e7e753b7e1977ef3d2163605431769ce9165nicolasroard    private void showPopupMenu(LinearLayout accessoryViewList) {
926a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford        final Button button = (Button) accessoryViewList.findViewById(
936a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford                R.id.applyEffect);
949820e7e753b7e1977ef3d2163605431769ce9165nicolasroard        if (button == null) {
959820e7e753b7e1977ef3d2163605431769ce9165nicolasroard            return;
969820e7e753b7e1977ef3d2163605431769ce9165nicolasroard        }
976a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford        if (mIdStrLut == null){
986a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford            mIdStrLut = new HashMap<Integer, String>();
996a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford            mIdStrLut.put(R.id.curve_menu_rgb,
1006a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford                    getContext().getString(R.string.curves_channel_rgb));
1016a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford            mIdStrLut.put(R.id.curve_menu_red,
1026a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford                    getContext().getString(R.string.curves_channel_red));
1036a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford            mIdStrLut.put(R.id.curve_menu_green,
1046a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford                    getContext().getString(R.string.curves_channel_green));
1056a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford            mIdStrLut.put(R.id.curve_menu_blue,
1066a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford                    getContext().getString(R.string.curves_channel_blue));
1076a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford        }
1089820e7e753b7e1977ef3d2163605431769ce9165nicolasroard        PopupMenu popupMenu = new PopupMenu(getActivity(), button);
1099820e7e753b7e1977ef3d2163605431769ce9165nicolasroard        popupMenu.getMenuInflater().inflate(R.menu.filtershow_menu_curves, popupMenu.getMenu());
1109820e7e753b7e1977ef3d2163605431769ce9165nicolasroard        popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
1119820e7e753b7e1977ef3d2163605431769ce9165nicolasroard            @Override
1129820e7e753b7e1977ef3d2163605431769ce9165nicolasroard            public boolean onMenuItemClick(MenuItem item) {
1139820e7e753b7e1977ef3d2163605431769ce9165nicolasroard                setChannel(item.getItemId());
1146a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford                button.setText(mIdStrLut.get(item.getItemId()));
1159820e7e753b7e1977ef3d2163605431769ce9165nicolasroard                return true;
1169820e7e753b7e1977ef3d2163605431769ce9165nicolasroard            }
1179820e7e753b7e1977ef3d2163605431769ce9165nicolasroard        });
1187638772c0ceb5528b12c9943bad5391658d6c78dJohn Reck        Editor.hackFixStrings(popupMenu.getMenu());
1199820e7e753b7e1977ef3d2163605431769ce9165nicolasroard        popupMenu.show();
12064fc2ed005e2f1dc9ed0b34481c46a370c244b86John Hoford        ((FilterShowActivity)getContext()).onShowMenu(popupMenu);
1219820e7e753b7e1977ef3d2163605431769ce9165nicolasroard    }
1229820e7e753b7e1977ef3d2163605431769ce9165nicolasroard
1239820e7e753b7e1977ef3d2163605431769ce9165nicolasroard    @Override
1249820e7e753b7e1977ef3d2163605431769ce9165nicolasroard    public void openUtilityPanel(final LinearLayout accessoryViewList) {
1256a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford        Context context = accessoryViewList.getContext();
1266a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford        Button view = (Button) accessoryViewList.findViewById(R.id.applyEffect);
1276a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford        view.setText(context.getString(R.string.curves_channel_rgb));
1286a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford        view.setVisibility(View.VISIBLE);
1296a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford
1306a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford        view.setOnClickListener(new OnClickListener() {
1319820e7e753b7e1977ef3d2163605431769ce9165nicolasroard                @Override
1326a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford            public void onClick(View arg0) {
1336a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford                showPopupMenu(accessoryViewList);
1346a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford            }
1356a07dfbe03c8396cc43840a1630ea480a2cff663John Hoford        });
1369820e7e753b7e1977ef3d2163605431769ce9165nicolasroard
1379820e7e753b7e1977ef3d2163605431769ce9165nicolasroard        if (view != null) {
1389820e7e753b7e1977ef3d2163605431769ce9165nicolasroard            view.setVisibility(View.VISIBLE);
1399820e7e753b7e1977ef3d2163605431769ce9165nicolasroard        }
1409820e7e753b7e1977ef3d2163605431769ce9165nicolasroard    }
1419820e7e753b7e1977ef3d2163605431769ce9165nicolasroard
1423992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard    public void nextChannel() {
1433992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        mCurrentCurveIndex = ((mCurrentCurveIndex + 1) % 4);
1443992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        invalidate();
1453992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard    }
1463992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard
147d7899c56b8df278dfd6720ae11eadc2f89fe8094nicolasroard    private ImageFilterCurves curves() {
14863a7dac01d394523799939596b960cb03321798dJohn Hoford        String filterName = getFilterName();
14963a7dac01d394523799939596b960cb03321798dJohn Hoford        ImagePreset p = getImagePreset();
15063a7dac01d394523799939596b960cb03321798dJohn Hoford        if (p != null) {
15171f04cbaedbb89e313e0b86b531640db2d3f6016nicolasroard            return (ImageFilterCurves) FiltersManager.getManager().getFilter(ImageFilterCurves.class);
1520d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard        }
153d7899c56b8df278dfd6720ae11eadc2f89fe8094nicolasroard        return null;
154d7899c56b8df278dfd6720ae11eadc2f89fe8094nicolasroard    }
155d7899c56b8df278dfd6720ae11eadc2f89fe8094nicolasroard
156d7899c56b8df278dfd6720ae11eadc2f89fe8094nicolasroard    private Spline getSpline(int index) {
157d009e38321c3e46966370623f950215534d96925John Hoford        return mFilterCurvesRepresentation.getSpline(index);
1580d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard    }
1590d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard
16092e2341248e99c691f38820503984bc5e2f18811nicolasroard    @Override
16192e2341248e99c691f38820503984bc5e2f18811nicolasroard    public void resetParameter() {
16292e2341248e99c691f38820503984bc5e2f18811nicolasroard        super.resetParameter();
16392e2341248e99c691f38820503984bc5e2f18811nicolasroard        resetCurve();
1643992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        mLastPreset = null;
1653992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        invalidate();
16692e2341248e99c691f38820503984bc5e2f18811nicolasroard    }
16792e2341248e99c691f38820503984bc5e2f18811nicolasroard
1680d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard    public void resetCurve() {
169d009e38321c3e46966370623f950215534d96925John Hoford        if (mFilterCurvesRepresentation != null) {
170d009e38321c3e46966370623f950215534d96925John Hoford            mFilterCurvesRepresentation.reset();
171d7899c56b8df278dfd6720ae11eadc2f89fe8094nicolasroard            updateCachedImage();
1720d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard        }
1730d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard    }
1740d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard
17592e2341248e99c691f38820503984bc5e2f18811nicolasroard    @Override
1760d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard    public void onDraw(Canvas canvas) {
1770d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard        super.onDraw(canvas);
178f5eedf1635eba7edfa7d41fd4e991cced978c4b2nicolasroard        if (mFilterCurvesRepresentation == null) {
179f5eedf1635eba7edfa7d41fd4e991cced978c4b2nicolasroard            return;
180f5eedf1635eba7edfa7d41fd4e991cced978c4b2nicolasroard        }
1810d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard
1820d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard        gPaint.setAntiAlias(true);
1830d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard
18431529940021b9a18611b1a3fb4a0317ab8c89618nicolasroard        if (getImagePreset() != mLastPreset && getFilteredImage() != null) {
18531529940021b9a18611b1a3fb4a0317ab8c89618nicolasroard            new ComputeHistogramTask().execute(getFilteredImage());
1863992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard            mLastPreset = getImagePreset();
1870d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard        }
1880d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard
18915a056e3cc1970bc0b99dba71df8222ba0345859nicolasroard        if (curves() == null) {
19015a056e3cc1970bc0b99dba71df8222ba0345859nicolasroard            return;
19115a056e3cc1970bc0b99dba71df8222ba0345859nicolasroard        }
19215a056e3cc1970bc0b99dba71df8222ba0345859nicolasroard
1933992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        if (mCurrentCurveIndex == Spline.RGB || mCurrentCurveIndex == Spline.RED) {
1943992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard            drawHistogram(canvas, redHistogram, Color.RED, PorterDuff.Mode.SCREEN);
1953992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        }
1963992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        if (mCurrentCurveIndex == Spline.RGB || mCurrentCurveIndex == Spline.GREEN) {
1973992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard            drawHistogram(canvas, greenHistogram, Color.GREEN, PorterDuff.Mode.SCREEN);
1983992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        }
1993992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        if (mCurrentCurveIndex == Spline.RGB || mCurrentCurveIndex == Spline.BLUE) {
2003992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard            drawHistogram(canvas, blueHistogram, Color.BLUE, PorterDuff.Mode.SCREEN);
2013992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        }
2023992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        // We only display the other channels curves when showing the RGB curve
2033992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        if (mCurrentCurveIndex == Spline.RGB) {
2043992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard            for (int i = 0; i < 4; i++) {
205d7899c56b8df278dfd6720ae11eadc2f89fe8094nicolasroard                Spline spline = getSpline(i);
2063992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard                if (i != mCurrentCurveIndex && !spline.isOriginal()) {
2073992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard                    // And we only display a curve if it has more than two
2083992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard                    // points
209c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard                    spline.draw(canvas, Spline.colorForCurve(i), getWidth(),
210c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard                            getHeight(), false, mDoingTouchMove);
2113992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard                }
2120d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard            }
2130d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard        }
2143992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        // ...but we always display the current curve.
215d7899c56b8df278dfd6720ae11eadc2f89fe8094nicolasroard        getSpline(mCurrentCurveIndex)
2163992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard                .draw(canvas, Spline.colorForCurve(mCurrentCurveIndex), getWidth(), getHeight(),
217c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard                        true, mDoingTouchMove);
2180d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard
2190d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard    }
2200d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard
2210d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard    private int pickControlPoint(float x, float y) {
2220d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard        int pick = 0;
223d7899c56b8df278dfd6720ae11eadc2f89fe8094nicolasroard        Spline spline = getSpline(mCurrentCurveIndex);
224d7899c56b8df278dfd6720ae11eadc2f89fe8094nicolasroard        float px = spline.getPoint(0).x;
225d7899c56b8df278dfd6720ae11eadc2f89fe8094nicolasroard        float py = spline.getPoint(0).y;
2268a55d3ae7486b798e4c26eeb91993916145f3cefNeil Fuller        double delta = Math.hypot(px - x, py - y);
227d7899c56b8df278dfd6720ae11eadc2f89fe8094nicolasroard        for (int i = 1; i < spline.getNbPoints(); i++) {
228d7899c56b8df278dfd6720ae11eadc2f89fe8094nicolasroard            px = spline.getPoint(i).x;
229d7899c56b8df278dfd6720ae11eadc2f89fe8094nicolasroard            py = spline.getPoint(i).y;
2308a55d3ae7486b798e4c26eeb91993916145f3cefNeil Fuller            double currentDelta = Math.hypot(px - x, py - y);
2310d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard            if (currentDelta < delta) {
2320d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard                delta = currentDelta;
2330d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard                pick = i;
2340d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard            }
2350d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard        }
2360d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard
2370d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard        if (!mDidAddPoint && (delta * getWidth() > 100)
238d7899c56b8df278dfd6720ae11eadc2f89fe8094nicolasroard                && (spline.getNbPoints() < 10)) {
2390d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard            return -1;
2400d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard        }
2410d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard
2423992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        return pick;
2430d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard    }
2440d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard
2450d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard    private String getFilterName() {
2463992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        return "Curves";
2470d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard    }
2480d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard
2490d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard    @Override
2500d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard    public synchronized boolean onTouchEvent(MotionEvent e) {
251df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard        if (e.getPointerCount() != 1) {
252df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard            return true;
253df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard        }
254df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard
255df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard        if (didFinishScalingOperation()) {
256df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard            return true;
257df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard        }
258df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard
2593992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        float margin = Spline.curveHandleSize() / 2;
2601fe7caeeaab314cd70d0b54f8f3dd3225c71c081nicolasroard        float posX = e.getX();
2611fe7caeeaab314cd70d0b54f8f3dd3225c71c081nicolasroard        if (posX < margin) {
2621fe7caeeaab314cd70d0b54f8f3dd3225c71c081nicolasroard            posX = margin;
2631fe7caeeaab314cd70d0b54f8f3dd3225c71c081nicolasroard        }
2641fe7caeeaab314cd70d0b54f8f3dd3225c71c081nicolasroard        float posY = e.getY();
2653992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        if (posY < margin) {
2663992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard            posY = margin;
2673992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        }
2681fe7caeeaab314cd70d0b54f8f3dd3225c71c081nicolasroard        if (posX > getWidth() - margin) {
2691fe7caeeaab314cd70d0b54f8f3dd3225c71c081nicolasroard            posX = getWidth() - margin;
2701fe7caeeaab314cd70d0b54f8f3dd3225c71c081nicolasroard        }
2713992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        if (posY > getHeight() - margin) {
2723992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard            posY = getHeight() - margin;
2733992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        }
2741fe7caeeaab314cd70d0b54f8f3dd3225c71c081nicolasroard        posX = (posX - margin) / (getWidth() - 2 * margin);
2753992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        posY = (posY - margin) / (getHeight() - 2 * margin);
2760d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard
2770d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard        if (e.getActionMasked() == MotionEvent.ACTION_UP) {
2780d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard            mCurrentControlPoint = null;
279952144dd3146a195ea4bd0be3f88275df8b31c64nicolasroard            mCurrentPick = -1;
280d7899c56b8df278dfd6720ae11eadc2f89fe8094nicolasroard            updateCachedImage();
2810d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard            mDidAddPoint = false;
2820d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard            if (mDidDelete) {
2830d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard                mDidDelete = false;
2840d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard            }
285c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard            mDoingTouchMove = false;
2860d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard            return true;
2870d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard        }
2880d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard
2890d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard        if (mDidDelete) {
2900d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard            return true;
2910d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard        }
2920d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard
29331529940021b9a18611b1a3fb4a0317ab8c89618nicolasroard        if (curves() == null) {
29431529940021b9a18611b1a3fb4a0317ab8c89618nicolasroard            return true;
29531529940021b9a18611b1a3fb4a0317ab8c89618nicolasroard        }
29631529940021b9a18611b1a3fb4a0317ab8c89618nicolasroard
297df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard        if (e.getActionMasked() == MotionEvent.ACTION_MOVE) {
298df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard            mDoingTouchMove = true;
299df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard            Spline spline = getSpline(mCurrentCurveIndex);
300df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard            int pick = mCurrentPick;
301df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard            if (mCurrentControlPoint == null) {
302df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard                pick = pickControlPoint(posX, posY);
303df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard                if (pick == -1) {
304df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard                    mCurrentControlPoint = new ControlPoint(posX, posY);
305df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard                    pick = spline.addPoint(mCurrentControlPoint);
306df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard                    mDidAddPoint = true;
307df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard                } else {
308df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard                    mCurrentControlPoint = spline.getPoint(pick);
309df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard                }
310df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard                mCurrentPick = pick;
3110d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard            }
3123992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard
313df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard            if (spline.isPointContained(posX, pick)) {
314df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard                spline.movePoint(pick, posX, posY);
315df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard            } else if (pick != -1 && spline.getNbPoints() > 2) {
316df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard                spline.deletePoint(pick);
317df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard                mDidDelete = true;
318df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard            }
319df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard            updateCachedImage();
320df0b0c4eaafcc9e4652fe6ec0791e4abe1c3b750nicolasroard            invalidate();
3210d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard        }
3220d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard        return true;
3230d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard    }
3240d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard
325d7899c56b8df278dfd6720ae11eadc2f89fe8094nicolasroard    public synchronized void updateCachedImage() {
3260d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard        if (getImagePreset() != null) {
32731529940021b9a18611b1a3fb4a0317ab8c89618nicolasroard            resetImageCaches(this);
32819161944e0efb1ffe23274d7cf5315ce047e9dacJohn Hoford            if (mEditorCurves != null) {
32919161944e0efb1ffe23274d7cf5315ce047e9dacJohn Hoford                mEditorCurves.commitLocalRepresentation();
33019161944e0efb1ffe23274d7cf5315ce047e9dacJohn Hoford            }
3310d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard            invalidate();
3320d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard        }
3330d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard    }
3340d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard
3353992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard    class ComputeHistogramTask extends AsyncTask<Bitmap, Void, int[]> {
3363992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        @Override
3373992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        protected int[] doInBackground(Bitmap... params) {
3383992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard            int[] histo = new int[256 * 3];
3393992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard            Bitmap bitmap = params[0];
3403992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard            int w = bitmap.getWidth();
3413992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard            int h = bitmap.getHeight();
3423992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard            int[] pixels = new int[w * h];
3433992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard            bitmap.getPixels(pixels, 0, w, 0, 0, w, h);
3443992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard            for (int i = 0; i < w; i++) {
3453992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard                for (int j = 0; j < h; j++) {
3463992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard                    int index = j * w + i;
3473992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard                    int r = Color.red(pixels[index]);
3483992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard                    int g = Color.green(pixels[index]);
3493992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard                    int b = Color.blue(pixels[index]);
3503992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard                    histo[r]++;
3513992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard                    histo[256 + g]++;
3523992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard                    histo[512 + b]++;
3533992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard                }
3543992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard            }
3553992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard            return histo;
3563992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        }
3573992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard
3583992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        @Override
3593992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        protected void onPostExecute(int[] result) {
3603992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard            System.arraycopy(result, 0, redHistogram, 0, 256);
3613992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard            System.arraycopy(result, 256, greenHistogram, 0, 256);
3623992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard            System.arraycopy(result, 512, blueHistogram, 0, 256);
3633992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard            invalidate();
3643992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        }
3653992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard    }
3663992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard
3673992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard    private void drawHistogram(Canvas canvas, int[] histogram, int color, PorterDuff.Mode mode) {
3683992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        int max = 0;
3693992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        for (int i = 0; i < histogram.length; i++) {
3703992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard            if (histogram[i] > max) {
3713992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard                max = histogram[i];
3723992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard            }
3733992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        }
374599393ecad6803161d5e901ef625e34cfe088009nicolasroard        float w = getWidth() - Spline.curveHandleSize();
375599393ecad6803161d5e901ef625e34cfe088009nicolasroard        float h = getHeight() - Spline.curveHandleSize() / 2.0f;
376599393ecad6803161d5e901ef625e34cfe088009nicolasroard        float dx = Spline.curveHandleSize() / 2.0f;
3773992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        float wl = w / histogram.length;
3783992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        float wh = (0.3f * h) / max;
3793992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        Paint paint = new Paint();
3803992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        paint.setARGB(100, 255, 255, 255);
3813992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        paint.setStrokeWidth((int) Math.ceil(wl));
3823992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard
3833992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        Paint paint2 = new Paint();
3843992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        paint2.setColor(color);
3853992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        paint2.setStrokeWidth(6);
3863992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        paint2.setXfermode(new PorterDuffXfermode(mode));
3873992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        gHistoPath.reset();
388599393ecad6803161d5e901ef625e34cfe088009nicolasroard        gHistoPath.moveTo(dx, h);
3893992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        boolean firstPointEncountered = false;
3903992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        float prev = 0;
3913992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        float last = 0;
3923992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        for (int i = 0; i < histogram.length; i++) {
393599393ecad6803161d5e901ef625e34cfe088009nicolasroard            float x = i * wl + dx;
3943992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard            float l = histogram[i] * wh;
3953992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard            if (l != 0) {
3963992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard                float v = h - (l + prev) / 2.0f;
3973992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard                if (!firstPointEncountered) {
3983992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard                    gHistoPath.lineTo(x, h);
3993992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard                    firstPointEncountered = true;
4003992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard                }
4013992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard                gHistoPath.lineTo(x, v);
4023992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard                prev = l;
4033992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard                last = x;
4043992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard            }
4053992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        }
4063992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        gHistoPath.lineTo(last, h);
4073992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        gHistoPath.lineTo(w, h);
4083992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        gHistoPath.close();
4093992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        canvas.drawPath(gHistoPath, paint2);
4103992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        paint2.setStrokeWidth(2);
4113992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        paint2.setStyle(Paint.Style.STROKE);
4123992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        paint2.setARGB(255, 200, 200, 200);
4133992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard        canvas.drawPath(gHistoPath, paint2);
4143992ae6cf1cbb7b57a22d8287c40451558408335nicolasroard    }
415c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard
416c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard    public void setChannel(int itemId) {
417c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard        switch (itemId) {
418c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard            case R.id.curve_menu_rgb: {
419c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard                mCurrentCurveIndex = Spline.RGB;
420c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard                break;
421c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard            }
422c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard            case R.id.curve_menu_red: {
423c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard                mCurrentCurveIndex = Spline.RED;
424c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard                break;
425c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard            }
426c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard            case R.id.curve_menu_green: {
427c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard                mCurrentCurveIndex = Spline.GREEN;
428c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard                break;
429c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard            }
430c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard            case R.id.curve_menu_blue: {
431c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard                mCurrentCurveIndex = Spline.BLUE;
432c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard                break;
433c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard            }
434c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard        }
43519161944e0efb1ffe23274d7cf5315ce047e9dacJohn Hoford        mEditorCurves.commitLocalRepresentation();
436c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard        invalidate();
437c5590eb1a20b112e67e4c43684790587f844fc6bnicolasroard    }
43819161944e0efb1ffe23274d7cf5315ce047e9dacJohn Hoford
43919161944e0efb1ffe23274d7cf5315ce047e9dacJohn Hoford    public void setEditor(EditorCurves editorCurves) {
44019161944e0efb1ffe23274d7cf5315ce047e9dacJohn Hoford        mEditorCurves = editorCurves;
44119161944e0efb1ffe23274d7cf5315ce047e9dacJohn Hoford    }
44219161944e0efb1ffe23274d7cf5315ce047e9dacJohn Hoford
44319161944e0efb1ffe23274d7cf5315ce047e9dacJohn Hoford    public void setFilterDrawRepresentation(FilterCurvesRepresentation drawRep) {
44419161944e0efb1ffe23274d7cf5315ce047e9dacJohn Hoford        mFilterCurvesRepresentation = drawRep;
44519161944e0efb1ffe23274d7cf5315ce047e9dacJohn Hoford    }
4460d7cdf8e763fb65c32bfad65245b3753deb75737nicolasroard}
447