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.imageshow; 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.MenuItem; 30import android.view.MotionEvent; 31import android.view.View; 32import android.widget.Button; 33import android.widget.LinearLayout; 34import android.widget.PopupMenu; 35 36import com.android.gallery3d.R; 37import com.android.gallery3d.filtershow.FilterShowActivity; 38import com.android.gallery3d.filtershow.editors.Editor; 39import com.android.gallery3d.filtershow.editors.EditorCurves; 40import com.android.gallery3d.filtershow.filters.FilterCurvesRepresentation; 41import com.android.gallery3d.filtershow.filters.FiltersManager; 42import com.android.gallery3d.filtershow.filters.ImageFilterCurves; 43import com.android.gallery3d.filtershow.pipeline.ImagePreset; 44 45import java.util.HashMap; 46 47public 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