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