/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.gallery3d.filtershow.ui; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.os.AsyncTask; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; import android.widget.PopupMenu; import com.android.gallery3d.R; import com.android.gallery3d.filtershow.editors.EditorCurves; import com.android.gallery3d.filtershow.filters.FilterCurvesRepresentation; import com.android.gallery3d.filtershow.filters.FiltersManager; import com.android.gallery3d.filtershow.filters.ImageFilterCurves; import com.android.gallery3d.filtershow.imageshow.ImageShow; import com.android.gallery3d.filtershow.presets.ImagePreset; import java.util.HashMap; public class ImageCurves extends ImageShow { private static final String LOGTAG = "ImageCurves"; Paint gPaint = new Paint(); Path gPathSpline = new Path(); HashMap mIdStrLut; private int mCurrentCurveIndex = Spline.RGB; private boolean mDidAddPoint = false; private boolean mDidDelete = false; private ControlPoint mCurrentControlPoint = null; private int mCurrentPick = -1; private ImagePreset mLastPreset = null; int[] redHistogram = new int[256]; int[] greenHistogram = new int[256]; int[] blueHistogram = new int[256]; Path gHistoPath = new Path(); boolean mDoingTouchMove = false; private EditorCurves mEditorCurves; private FilterCurvesRepresentation mFilterCurvesRepresentation; public ImageCurves(Context context) { super(context); setLayerType(LAYER_TYPE_SOFTWARE, gPaint); resetCurve(); } public ImageCurves(Context context, AttributeSet attrs) { super(context, attrs); setLayerType(LAYER_TYPE_SOFTWARE, gPaint); resetCurve(); } @Override protected boolean enableComparison() { return false; } @Override public boolean useUtilityPanel() { return true; } private void showPopupMenu(LinearLayout accessoryViewList) { final Button button = (Button) accessoryViewList.findViewById( R.id.applyEffect); if (button == null) { return; } if (mIdStrLut == null){ mIdStrLut = new HashMap(); mIdStrLut.put(R.id.curve_menu_rgb, getContext().getString(R.string.curves_channel_rgb)); mIdStrLut.put(R.id.curve_menu_red, getContext().getString(R.string.curves_channel_red)); mIdStrLut.put(R.id.curve_menu_green, getContext().getString(R.string.curves_channel_green)); mIdStrLut.put(R.id.curve_menu_blue, getContext().getString(R.string.curves_channel_blue)); } PopupMenu popupMenu = new PopupMenu(getActivity(), button); popupMenu.getMenuInflater().inflate(R.menu.filtershow_menu_curves, popupMenu.getMenu()); popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { setChannel(item.getItemId()); button.setText(mIdStrLut.get(item.getItemId())); return true; } }); popupMenu.show(); } @Override public void openUtilityPanel(final LinearLayout accessoryViewList) { Context context = accessoryViewList.getContext(); Button view = (Button) accessoryViewList.findViewById(R.id.applyEffect); view.setText(context.getString(R.string.curves_channel_rgb)); view.setVisibility(View.VISIBLE); view.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { showPopupMenu(accessoryViewList); } }); if (view != null) { view.setVisibility(View.VISIBLE); } } public void nextChannel() { mCurrentCurveIndex = ((mCurrentCurveIndex + 1) % 4); invalidate(); } @Override public boolean showTitle() { return false; } private ImageFilterCurves curves() { String filterName = getFilterName(); ImagePreset p = getImagePreset(); if (p != null) { return (ImageFilterCurves) FiltersManager.getManager().getFilter(ImageFilterCurves.class); } return null; } private Spline getSpline(int index) { return mFilterCurvesRepresentation.getSpline(index); } @Override public void resetParameter() { super.resetParameter(); resetCurve(); mLastPreset = null; invalidate(); } public void resetCurve() { if (mFilterCurvesRepresentation != null) { mFilterCurvesRepresentation.reset(); updateCachedImage(); } } @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); if (mFilterCurvesRepresentation == null) { return; } gPaint.setAntiAlias(true); if (getImagePreset() != mLastPreset && getFilteredImage() != null) { new ComputeHistogramTask().execute(getFilteredImage()); mLastPreset = getImagePreset(); } if (curves() == null) { return; } if (mCurrentCurveIndex == Spline.RGB || mCurrentCurveIndex == Spline.RED) { drawHistogram(canvas, redHistogram, Color.RED, PorterDuff.Mode.SCREEN); } if (mCurrentCurveIndex == Spline.RGB || mCurrentCurveIndex == Spline.GREEN) { drawHistogram(canvas, greenHistogram, Color.GREEN, PorterDuff.Mode.SCREEN); } if (mCurrentCurveIndex == Spline.RGB || mCurrentCurveIndex == Spline.BLUE) { drawHistogram(canvas, blueHistogram, Color.BLUE, PorterDuff.Mode.SCREEN); } // We only display the other channels curves when showing the RGB curve if (mCurrentCurveIndex == Spline.RGB) { for (int i = 0; i < 4; i++) { Spline spline = getSpline(i); if (i != mCurrentCurveIndex && !spline.isOriginal()) { // And we only display a curve if it has more than two // points spline.draw(canvas, Spline.colorForCurve(i), getWidth(), getHeight(), false, mDoingTouchMove); } } } // ...but we always display the current curve. getSpline(mCurrentCurveIndex) .draw(canvas, Spline.colorForCurve(mCurrentCurveIndex), getWidth(), getHeight(), true, mDoingTouchMove); drawToast(canvas); } private int pickControlPoint(float x, float y) { int pick = 0; Spline spline = getSpline(mCurrentCurveIndex); float px = spline.getPoint(0).x; float py = spline.getPoint(0).y; double delta = Math.sqrt((px - x) * (px - x) + (py - y) * (py - y)); for (int i = 1; i < spline.getNbPoints(); i++) { px = spline.getPoint(i).x; py = spline.getPoint(i).y; double currentDelta = Math.sqrt((px - x) * (px - x) + (py - y) * (py - y)); if (currentDelta < delta) { delta = currentDelta; pick = i; } } if (!mDidAddPoint && (delta * getWidth() > 100) && (spline.getNbPoints() < 10)) { return -1; } return pick; } private String getFilterName() { return "Curves"; } @Override public synchronized boolean onTouchEvent(MotionEvent e) { if (e.getPointerCount() != 1) { return true; } if (didFinishScalingOperation()) { return true; } float margin = Spline.curveHandleSize() / 2; float posX = e.getX(); if (posX < margin) { posX = margin; } float posY = e.getY(); if (posY < margin) { posY = margin; } if (posX > getWidth() - margin) { posX = getWidth() - margin; } if (posY > getHeight() - margin) { posY = getHeight() - margin; } posX = (posX - margin) / (getWidth() - 2 * margin); posY = (posY - margin) / (getHeight() - 2 * margin); if (e.getActionMasked() == MotionEvent.ACTION_UP) { mCurrentControlPoint = null; mCurrentPick = -1; updateCachedImage(); mDidAddPoint = false; if (mDidDelete) { mDidDelete = false; } mDoingTouchMove = false; return true; } if (mDidDelete) { return true; } if (curves() == null) { return true; } if (e.getActionMasked() == MotionEvent.ACTION_MOVE) { mDoingTouchMove = true; Spline spline = getSpline(mCurrentCurveIndex); int pick = mCurrentPick; if (mCurrentControlPoint == null) { pick = pickControlPoint(posX, posY); if (pick == -1) { mCurrentControlPoint = new ControlPoint(posX, posY); pick = spline.addPoint(mCurrentControlPoint); mDidAddPoint = true; } else { mCurrentControlPoint = spline.getPoint(pick); } mCurrentPick = pick; } if (spline.isPointContained(posX, pick)) { spline.movePoint(pick, posX, posY); } else if (pick != -1 && spline.getNbPoints() > 2) { spline.deletePoint(pick); mDidDelete = true; } updateCachedImage(); invalidate(); } return true; } public synchronized void updateCachedImage() { if (getImagePreset() != null) { resetImageCaches(this); if (mEditorCurves != null) { mEditorCurves.commitLocalRepresentation(); } invalidate(); } } class ComputeHistogramTask extends AsyncTask { @Override protected int[] doInBackground(Bitmap... params) { int[] histo = new int[256 * 3]; Bitmap bitmap = params[0]; int w = bitmap.getWidth(); int h = bitmap.getHeight(); int[] pixels = new int[w * h]; bitmap.getPixels(pixels, 0, w, 0, 0, w, h); for (int i = 0; i < w; i++) { for (int j = 0; j < h; j++) { int index = j * w + i; int r = Color.red(pixels[index]); int g = Color.green(pixels[index]); int b = Color.blue(pixels[index]); histo[r]++; histo[256 + g]++; histo[512 + b]++; } } return histo; } @Override protected void onPostExecute(int[] result) { System.arraycopy(result, 0, redHistogram, 0, 256); System.arraycopy(result, 256, greenHistogram, 0, 256); System.arraycopy(result, 512, blueHistogram, 0, 256); invalidate(); } } private void drawHistogram(Canvas canvas, int[] histogram, int color, PorterDuff.Mode mode) { int max = 0; for (int i = 0; i < histogram.length; i++) { if (histogram[i] > max) { max = histogram[i]; } } float w = getWidth() - Spline.curveHandleSize(); float h = getHeight() - Spline.curveHandleSize() / 2.0f; float dx = Spline.curveHandleSize() / 2.0f; float wl = w / histogram.length; float wh = (0.3f * h) / max; Paint paint = new Paint(); paint.setARGB(100, 255, 255, 255); paint.setStrokeWidth((int) Math.ceil(wl)); Paint paint2 = new Paint(); paint2.setColor(color); paint2.setStrokeWidth(6); paint2.setXfermode(new PorterDuffXfermode(mode)); gHistoPath.reset(); gHistoPath.moveTo(dx, h); boolean firstPointEncountered = false; float prev = 0; float last = 0; for (int i = 0; i < histogram.length; i++) { float x = i * wl + dx; float l = histogram[i] * wh; if (l != 0) { float v = h - (l + prev) / 2.0f; if (!firstPointEncountered) { gHistoPath.lineTo(x, h); firstPointEncountered = true; } gHistoPath.lineTo(x, v); prev = l; last = x; } } gHistoPath.lineTo(last, h); gHistoPath.lineTo(w, h); gHistoPath.close(); canvas.drawPath(gHistoPath, paint2); paint2.setStrokeWidth(2); paint2.setStyle(Paint.Style.STROKE); paint2.setARGB(255, 200, 200, 200); canvas.drawPath(gHistoPath, paint2); } public void setChannel(int itemId) { switch (itemId) { case R.id.curve_menu_rgb: { mCurrentCurveIndex = Spline.RGB; break; } case R.id.curve_menu_red: { mCurrentCurveIndex = Spline.RED; break; } case R.id.curve_menu_green: { mCurrentCurveIndex = Spline.GREEN; break; } case R.id.curve_menu_blue: { mCurrentCurveIndex = Spline.BLUE; break; } } mEditorCurves.commitLocalRepresentation(); invalidate(); } public void setEditor(EditorCurves editorCurves) { mEditorCurves = editorCurves; } public void setFilterDrawRepresentation(FilterCurvesRepresentation drawRep) { mFilterCurvesRepresentation = drawRep; } }