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