1 /* 2 * Copyright (C) 2007 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 /* 18 * 090408 19 * Keith Wiley 20 * kwiley (at) keithwiley.com 21 * http://keithwiley.com 22 * 23 * UberColorPickerDialog v1.1 24 * 25 * This color picker was implemented as a (significant) extension of the 26 * ColorPickerDialog class provided in the Android API Demos. You are free 27 * to drop it unchanged into your own projects or to modify it as you see 28 * fit. I would appreciate it if this comment block were let intact, 29 * merely for credit's sake. 30 * 31 * Enjoy! 32 */ 33 34 package org.connectbot.util; 35 36 import android.app.Dialog; 37 import android.content.Context; 38 import android.graphics.Bitmap; 39 import android.graphics.Canvas; 40 import android.graphics.Color; 41 import android.graphics.ColorMatrix; 42 import android.graphics.ComposeShader; 43 import android.graphics.Paint; 44 import android.graphics.PorterDuff; 45 import android.graphics.PorterDuffXfermode; 46 import android.graphics.RadialGradient; 47 import android.graphics.Rect; 48 import android.graphics.RectF; 49 import android.graphics.Shader; 50 import android.graphics.SweepGradient; 51 import android.graphics.drawable.GradientDrawable; 52 import android.graphics.drawable.GradientDrawable.Orientation; 53 import android.os.Bundle; 54 import android.util.DisplayMetrics; 55 import android.view.MotionEvent; 56 import android.view.View; 57 58 /** 59 * UberColorPickerDialog is a seriously enhanced version of the UberColorPickerDialog 60 * class provided in the Android API Demos.<p> 61 * 62 * NOTE (from Kenny Root): This is a VERY slimmed down version custom for ConnectBot. 63 * Visit Keith's site for the full version at the URL listed in the author line.<p> 64 * 65 */ 66 public class UberColorPickerDialog extends Dialog { 67 private OnColorChangedListener mListener; 68 private int mInitialColor; 69 70 /** 71 * Callback to the creator of the dialog, informing the creator of a new color and notifying that the dialog is about to dismiss. 72 */ 73 public interface OnColorChangedListener { 74 void colorChanged(int color); 75 } 76 77 /** 78 * Ctor 79 * @param context 80 * @param listener 81 * @param initialColor 82 * @param showTitle If true, a title is shown across the top of the dialog. If false a toast is shown instead. 83 */ 84 public UberColorPickerDialog(Context context, 85 OnColorChangedListener listener, 86 int initialColor) { 87 super(context); 88 89 mListener = listener; 90 mInitialColor = initialColor; 91 } 92 93 /** 94 * Activity entry point 95 */ 96 @Override 97 protected void onCreate(Bundle savedInstanceState) { 98 super.onCreate(savedInstanceState); 99 OnColorChangedListener l = new OnColorChangedListener() { 100 public void colorChanged(int color) { 101 mListener.colorChanged(color); 102 dismiss(); 103 } 104 }; 105 106 DisplayMetrics dm = new DisplayMetrics(); 107 getWindow().getWindowManager().getDefaultDisplay().getMetrics(dm); 108 int screenWidth = dm.widthPixels; 109 int screenHeight = dm.heightPixels; 110 111 setTitle("Pick a color (try the trackball)"); 112 113 try { 114 setContentView(new ColorPickerView(getContext(), l, screenWidth, screenHeight, mInitialColor)); 115 } 116 catch (Exception e) { 117 //There is currently only one kind of ctor exception, that where no methods are enabled. 118 dismiss(); //This doesn't work! The dialog is still shown (its title at least, the layout is empty from the exception being thrown). <sigh> 119 } 120 } 121 122 /** 123 * ColorPickerView is the meat of this color picker (as opposed to the enclosing class). 124 * All the heavy lifting is done directly by this View subclass. 125 * <P> 126 * You can enable/disable whichever color chooser methods you want by modifying the ENABLED_METHODS switches. They *should* 127 * do all the work required to properly enable/disable methods without losing track of what goes with what and what maps to what. 128 * <P> 129 * If you add a new color chooser method, do a text search for "NEW_METHOD_WORK_NEEDED_HERE". That tag indicates all 130 * the locations in the code that will have to be amended in order to properly add a new color chooser method. 131 * I highly recommend adding new methods to the end of the list. If you want to try to reorder the list, you're on your own. 132 */ 133 private static class ColorPickerView extends View { 134 private static int SWATCH_WIDTH = 95; 135 private static final int SWATCH_HEIGHT = 60; 136 137 private static int PALETTE_POS_X = 0; 138 private static int PALETTE_POS_Y = SWATCH_HEIGHT; 139 private static final int PALETTE_DIM = SWATCH_WIDTH * 2; 140 private static final int PALETTE_RADIUS = PALETTE_DIM / 2; 141 private static final int PALETTE_CENTER_X = PALETTE_RADIUS; 142 private static final int PALETTE_CENTER_Y = PALETTE_RADIUS; 143 144 private static final int SLIDER_THICKNESS = 40; 145 146 private static int VIEW_DIM_X = PALETTE_DIM; 147 private static int VIEW_DIM_Y = SWATCH_HEIGHT; 148 149 //NEW_METHOD_WORK_NEEDED_HERE 150 private static final int METHOD_HS_V_PALETTE = 0; 151 152 //NEW_METHOD_WORK_NEEDED_HERE 153 //Add a new entry to the list for each controller in the new method 154 private static final int TRACKED_NONE = -1; //No object on screen is currently being tracked 155 private static final int TRACK_SWATCH_OLD = 10; 156 private static final int TRACK_SWATCH_NEW = 11; 157 private static final int TRACK_HS_PALETTE = 30; 158 private static final int TRACK_VER_VALUE_SLIDER = 31; 159 160 private static final int TEXT_SIZE = 12; 161 private static int[] TEXT_HSV_POS = new int[2]; 162 private static int[] TEXT_RGB_POS = new int[2]; 163 private static int[] TEXT_YUV_POS = new int[2]; 164 private static int[] TEXT_HEX_POS = new int[2]; 165 166 private static final float PI = 3.141592653589793f; 167 168 private int mMethod = METHOD_HS_V_PALETTE; 169 private int mTracking = TRACKED_NONE; //What object on screen is currently being tracked for movement 170 171 //Zillions of persistant Paint objecs for drawing the View 172 173 private Paint mSwatchOld, mSwatchNew; 174 175 //NEW_METHOD_WORK_NEEDED_HERE 176 //Add Paints to represent the palettes of the new method's UI controllers 177 private Paint mOvalHueSat; 178 179 private Bitmap mVerSliderBM; 180 private Canvas mVerSliderCv; 181 182 private Bitmap[] mHorSlidersBM = new Bitmap[3]; 183 private Canvas[] mHorSlidersCv = new Canvas[3]; 184 185 private Paint mValDimmer; 186 187 //NEW_METHOD_WORK_NEEDED_HERE 188 //Add Paints to represent the icon for the new method 189 private Paint mOvalHueSatSmall; 190 191 private Paint mPosMarker; 192 private Paint mText; 193 194 private Rect mOldSwatchRect = new Rect(); 195 private Rect mNewSwatchRect = new Rect(); 196 private Rect mPaletteRect = new Rect(); 197 private Rect mVerSliderRect = new Rect(); 198 199 private int[] mSpectrumColorsRev; 200 private int mOriginalColor = 0; //The color passed in at the beginning, which can be reverted to at any time by tapping the old swatch. 201 private float[] mHSV = new float[3]; 202 private int[] mRGB = new int[3]; 203 private float[] mYUV = new float[3]; 204 private String mHexStr = ""; 205 private boolean mHSVenabled = true; //Only true if an HSV method is enabled 206 private boolean mRGBenabled = true; //Only true if an RGB method is enabled 207 private boolean mYUVenabled = true; //Only true if a YUV method is enabled 208 private boolean mHexenabled = true; //Only true if an RGB method is enabled 209 private int[] mCoord = new int[3]; //For drawing slider/palette markers 210 private int mFocusedControl = -1; //Which control receives trackball events. 211 private OnColorChangedListener mListener; 212 213 /** 214 * Ctor. 215 * @param c 216 * @param l 217 * @param width Used to determine orientation and adjust layout accordingly 218 * @param height Used to determine orientation and adjust layout accordingly 219 * @param color The initial color 220 * @throws Exception 221 */ 222 ColorPickerView(Context c, OnColorChangedListener l, int width, int height, int color) 223 throws Exception { 224 super(c); 225 226 //We need to make the dialog focusable to retrieve trackball events. 227 setFocusable(true); 228 229 mListener = l; 230 231 mOriginalColor = color; 232 233 Color.colorToHSV(color, mHSV); 234 235 updateAllFromHSV(); 236 237 //Setup the layout based on whether this is a portrait or landscape orientation. 238 if (width <= height) { //Portrait layout 239 SWATCH_WIDTH = (PALETTE_DIM + SLIDER_THICKNESS) / 2; 240 241 PALETTE_POS_X = 0; 242 PALETTE_POS_Y = TEXT_SIZE * 4 + SWATCH_HEIGHT; 243 244 //Set more rects, lots of rects 245 mOldSwatchRect.set(0, TEXT_SIZE * 4, SWATCH_WIDTH, TEXT_SIZE * 4 + SWATCH_HEIGHT); 246 mNewSwatchRect.set(SWATCH_WIDTH, TEXT_SIZE * 4, SWATCH_WIDTH * 2, TEXT_SIZE * 4 + SWATCH_HEIGHT); 247 mPaletteRect.set(0, PALETTE_POS_Y, PALETTE_DIM, PALETTE_POS_Y + PALETTE_DIM); 248 mVerSliderRect.set(PALETTE_DIM, PALETTE_POS_Y, PALETTE_DIM + SLIDER_THICKNESS, PALETTE_POS_Y + PALETTE_DIM); 249 250 TEXT_HSV_POS[0] = 3; 251 TEXT_HSV_POS[1] = 0; 252 TEXT_RGB_POS[0] = TEXT_HSV_POS[0] + 50; 253 TEXT_RGB_POS[1] = TEXT_HSV_POS[1]; 254 TEXT_YUV_POS[0] = TEXT_HSV_POS[0] + 100; 255 TEXT_YUV_POS[1] = TEXT_HSV_POS[1]; 256 TEXT_HEX_POS[0] = TEXT_HSV_POS[0] + 150; 257 TEXT_HEX_POS[1] = TEXT_HSV_POS[1]; 258 259 VIEW_DIM_X = PALETTE_DIM + SLIDER_THICKNESS; 260 VIEW_DIM_Y = SWATCH_HEIGHT + PALETTE_DIM + TEXT_SIZE * 4; 261 } 262 else { //Landscape layout 263 SWATCH_WIDTH = 110; 264 265 PALETTE_POS_X = SWATCH_WIDTH; 266 PALETTE_POS_Y = 0; 267 268 //Set more rects, lots of rects 269 mOldSwatchRect.set(0, TEXT_SIZE * 7, SWATCH_WIDTH, TEXT_SIZE * 7 + SWATCH_HEIGHT); 270 mNewSwatchRect.set(0, TEXT_SIZE * 7 + SWATCH_HEIGHT, SWATCH_WIDTH, TEXT_SIZE * 7 + SWATCH_HEIGHT * 2); 271 mPaletteRect.set(SWATCH_WIDTH, PALETTE_POS_Y, SWATCH_WIDTH + PALETTE_DIM, PALETTE_POS_Y + PALETTE_DIM); 272 mVerSliderRect.set(SWATCH_WIDTH + PALETTE_DIM, PALETTE_POS_Y, SWATCH_WIDTH + PALETTE_DIM + SLIDER_THICKNESS, PALETTE_POS_Y + PALETTE_DIM); 273 274 TEXT_HSV_POS[0] = 3; 275 TEXT_HSV_POS[1] = 0; 276 TEXT_RGB_POS[0] = TEXT_HSV_POS[0]; 277 TEXT_RGB_POS[1] = (int)(TEXT_HSV_POS[1] + TEXT_SIZE * 3.5); 278 TEXT_YUV_POS[0] = TEXT_HSV_POS[0] + 50; 279 TEXT_YUV_POS[1] = (int)(TEXT_HSV_POS[1] + TEXT_SIZE * 3.5); 280 TEXT_HEX_POS[0] = TEXT_HSV_POS[0] + 50; 281 TEXT_HEX_POS[1] = TEXT_HSV_POS[1]; 282 283 VIEW_DIM_X = PALETTE_POS_X + PALETTE_DIM + SLIDER_THICKNESS; 284 VIEW_DIM_Y = Math.max(mNewSwatchRect.bottom, PALETTE_DIM); 285 } 286 287 //Rainbows make everybody happy! 288 mSpectrumColorsRev = new int[] { 289 0xFFFF0000, 0xFFFF00FF, 0xFF0000FF, 0xFF00FFFF, 290 0xFF00FF00, 0xFFFFFF00, 0xFFFF0000, 291 }; 292 293 //Setup all the Paint and Shader objects. There are lots of them! 294 295 //NEW_METHOD_WORK_NEEDED_HERE 296 //Add Paints to represent the palettes of the new method's UI controllers 297 298 mSwatchOld = new Paint(Paint.ANTI_ALIAS_FLAG); 299 mSwatchOld.setStyle(Paint.Style.FILL); 300 mSwatchOld.setColor(Color.HSVToColor(mHSV)); 301 302 mSwatchNew = new Paint(Paint.ANTI_ALIAS_FLAG); 303 mSwatchNew.setStyle(Paint.Style.FILL); 304 mSwatchNew.setColor(Color.HSVToColor(mHSV)); 305 306 Shader shaderA = new SweepGradient(0, 0, mSpectrumColorsRev, null); 307 Shader shaderB = new RadialGradient(0, 0, PALETTE_CENTER_X, 0xFFFFFFFF, 0xFF000000, Shader.TileMode.CLAMP); 308 Shader shader = new ComposeShader(shaderA, shaderB, PorterDuff.Mode.SCREEN); 309 mOvalHueSat = new Paint(Paint.ANTI_ALIAS_FLAG); 310 mOvalHueSat.setShader(shader); 311 mOvalHueSat.setStyle(Paint.Style.FILL); 312 mOvalHueSat.setDither(true); 313 314 mVerSliderBM = Bitmap.createBitmap(SLIDER_THICKNESS, PALETTE_DIM, Bitmap.Config.RGB_565); 315 mVerSliderCv = new Canvas(mVerSliderBM); 316 317 for (int i = 0; i < 3; i++) { 318 mHorSlidersBM[i] = Bitmap.createBitmap(PALETTE_DIM, SLIDER_THICKNESS, Bitmap.Config.RGB_565); 319 mHorSlidersCv[i] = new Canvas(mHorSlidersBM[i]); 320 } 321 322 mValDimmer = new Paint(Paint.ANTI_ALIAS_FLAG); 323 mValDimmer.setStyle(Paint.Style.FILL); 324 mValDimmer.setDither(true); 325 mValDimmer.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)); 326 327 //Whew, we're done making the big Paints and Shaders for the swatches, palettes, and sliders. 328 //Now we need to make the Paints and Shaders that will draw the little method icons in the method selector list. 329 330 //NEW_METHOD_WORK_NEEDED_HERE 331 //Add Paints to represent the icon for the new method 332 333 shaderA = new SweepGradient(0, 0, mSpectrumColorsRev, null); 334 shaderB = new RadialGradient(0, 0, PALETTE_DIM / 2, 0xFFFFFFFF, 0xFF000000, Shader.TileMode.CLAMP); 335 shader = new ComposeShader(shaderA, shaderB, PorterDuff.Mode.SCREEN); 336 mOvalHueSatSmall = new Paint(Paint.ANTI_ALIAS_FLAG); 337 mOvalHueSatSmall.setShader(shader); 338 mOvalHueSatSmall.setStyle(Paint.Style.FILL); 339 340 //Make a simple stroking Paint for drawing markers and borders and stuff like that. 341 mPosMarker = new Paint(Paint.ANTI_ALIAS_FLAG); 342 mPosMarker.setStyle(Paint.Style.STROKE); 343 mPosMarker.setStrokeWidth(2); 344 345 //Make a basic text Paint. 346 mText = new Paint(Paint.ANTI_ALIAS_FLAG); 347 mText.setTextSize(TEXT_SIZE); 348 mText.setColor(Color.WHITE); 349 350 //Kickstart 351 initUI(); 352 } 353 354 /** 355 * Draw the entire view (the entire dialog). 356 */ 357 @Override 358 protected void onDraw(Canvas canvas) { 359 //Draw the old and new swatches 360 drawSwatches(canvas); 361 362 //Write the text 363 writeColorParams(canvas); 364 365 //Draw the palette and sliders (the UI) 366 if (mMethod == METHOD_HS_V_PALETTE) 367 drawHSV1Palette(canvas); 368 } 369 370 /** 371 * Draw the old and new swatches. 372 * @param canvas 373 */ 374 private void drawSwatches(Canvas canvas) { 375 float[] hsv = new float[3]; 376 377 mText.setTextSize(16); 378 379 //Draw the original swatch 380 canvas.drawRect(mOldSwatchRect, mSwatchOld); 381 Color.colorToHSV(mOriginalColor, hsv); 382 //if (UberColorPickerDialog.isGray(mColor)) //Don't need this right here, but imp't to note 383 // hsv[1] = 0; 384 if (hsv[2] > .5) 385 mText.setColor(Color.BLACK); 386 canvas.drawText("Revert", mOldSwatchRect.left + SWATCH_WIDTH / 2 - mText.measureText("Revert") / 2, mOldSwatchRect.top + 16, mText); 387 mText.setColor(Color.WHITE); 388 389 //Draw the new swatch 390 canvas.drawRect(mNewSwatchRect, mSwatchNew); 391 if (mHSV[2] > .5) 392 mText.setColor(Color.BLACK); 393 canvas.drawText("Accept", mNewSwatchRect.left + SWATCH_WIDTH / 2 - mText.measureText("Accept") / 2, mNewSwatchRect.top + 16, mText); 394 mText.setColor(Color.WHITE); 395 396 mText.setTextSize(TEXT_SIZE); 397 } 398 399 /** 400 * Write the color parametes (HSV, RGB, YUV, Hex, etc.). 401 * @param canvas 402 */ 403 private void writeColorParams(Canvas canvas) { 404 if (mHSVenabled) { 405 canvas.drawText("H: " + Integer.toString((int)(mHSV[0] / 360.0f * 255)), TEXT_HSV_POS[0], TEXT_HSV_POS[1] + TEXT_SIZE, mText); 406 canvas.drawText("S: " + Integer.toString((int)(mHSV[1] * 255)), TEXT_HSV_POS[0], TEXT_HSV_POS[1] + TEXT_SIZE * 2, mText); 407 canvas.drawText("V: " + Integer.toString((int)(mHSV[2] * 255)), TEXT_HSV_POS[0], TEXT_HSV_POS[1] + TEXT_SIZE * 3, mText); 408 } 409 410 if (mRGBenabled) { 411 canvas.drawText("R: " + mRGB[0], TEXT_RGB_POS[0], TEXT_RGB_POS[1] + TEXT_SIZE, mText); 412 canvas.drawText("G: " + mRGB[1], TEXT_RGB_POS[0], TEXT_RGB_POS[1] + TEXT_SIZE * 2, mText); 413 canvas.drawText("B: " + mRGB[2], TEXT_RGB_POS[0], TEXT_RGB_POS[1] + TEXT_SIZE * 3, mText); 414 } 415 416 if (mYUVenabled) { 417 canvas.drawText("Y: " + Integer.toString((int)(mYUV[0] * 255)), TEXT_YUV_POS[0], TEXT_YUV_POS[1] + TEXT_SIZE, mText); 418 canvas.drawText("U: " + Integer.toString((int)((mYUV[1] + .5f) * 255)), TEXT_YUV_POS[0], TEXT_YUV_POS[1] + TEXT_SIZE * 2, mText); 419 canvas.drawText("V: " + Integer.toString((int)((mYUV[2] + .5f) * 255)), TEXT_YUV_POS[0], TEXT_YUV_POS[1] + TEXT_SIZE * 3, mText); 420 } 421 422 if (mHexenabled) 423 canvas.drawText("#" + mHexStr, TEXT_HEX_POS[0], TEXT_HEX_POS[1] + TEXT_SIZE, mText); 424 } 425 426 /** 427 * Place a small circle on the 2D palette to indicate the current values. 428 * @param canvas 429 * @param markerPosX 430 * @param markerPosY 431 */ 432 private void mark2DPalette(Canvas canvas, int markerPosX, int markerPosY) { 433 mPosMarker.setColor(Color.BLACK); 434 canvas.drawOval(new RectF(markerPosX - 5, markerPosY - 5, markerPosX + 5, markerPosY + 5), mPosMarker); 435 mPosMarker.setColor(Color.WHITE); 436 canvas.drawOval(new RectF(markerPosX - 3, markerPosY - 3, markerPosX + 3, markerPosY + 3), mPosMarker); 437 } 438 439 /** 440 * Draw a line across the slider to indicate its current value. 441 * @param canvas 442 * @param markerPos 443 */ 444 private void markVerSlider(Canvas canvas, int markerPos) { 445 mPosMarker.setColor(Color.BLACK); 446 canvas.drawRect(new Rect(0, markerPos - 2, SLIDER_THICKNESS, markerPos + 3), mPosMarker); 447 mPosMarker.setColor(Color.WHITE); 448 canvas.drawRect(new Rect(0, markerPos, SLIDER_THICKNESS, markerPos + 1), mPosMarker); 449 } 450 451 /** 452 * Frame the slider to indicate that it has trackball focus. 453 * @param canvas 454 */ 455 private void hilightFocusedVerSlider(Canvas canvas) { 456 mPosMarker.setColor(Color.WHITE); 457 canvas.drawRect(new Rect(0, 0, SLIDER_THICKNESS, PALETTE_DIM), mPosMarker); 458 mPosMarker.setColor(Color.BLACK); 459 canvas.drawRect(new Rect(2, 2, SLIDER_THICKNESS - 2, PALETTE_DIM - 2), mPosMarker); 460 } 461 462 /** 463 * Frame the 2D palette to indicate that it has trackball focus. 464 * @param canvas 465 */ 466 private void hilightFocusedOvalPalette(Canvas canvas) { 467 mPosMarker.setColor(Color.WHITE); 468 canvas.drawOval(new RectF(-PALETTE_RADIUS, -PALETTE_RADIUS, PALETTE_RADIUS, PALETTE_RADIUS), mPosMarker); 469 mPosMarker.setColor(Color.BLACK); 470 canvas.drawOval(new RectF(-PALETTE_RADIUS + 2, -PALETTE_RADIUS + 2, PALETTE_RADIUS - 2, PALETTE_RADIUS - 2), mPosMarker); 471 } 472 473 //NEW_METHOD_WORK_NEEDED_HERE 474 //To add a new method, replicate the basic draw functions here. Use the 2D palette or 1D sliders as templates for the new method. 475 /** 476 * Draw the UI for HSV with angular H and radial S combined in 2D and a 1D V slider. 477 * @param canvas 478 */ 479 private void drawHSV1Palette(Canvas canvas) { 480 canvas.save(); 481 482 canvas.translate(PALETTE_POS_X, PALETTE_POS_Y); 483 484 //Draw the 2D palette 485 canvas.translate(PALETTE_CENTER_X, PALETTE_CENTER_Y); 486 canvas.drawOval(new RectF(-PALETTE_RADIUS, -PALETTE_RADIUS, PALETTE_RADIUS, PALETTE_RADIUS), mOvalHueSat); 487 canvas.drawOval(new RectF(-PALETTE_RADIUS, -PALETTE_RADIUS, PALETTE_RADIUS, PALETTE_RADIUS), mValDimmer); 488 if (mFocusedControl == 0) 489 hilightFocusedOvalPalette(canvas); 490 mark2DPalette(canvas, mCoord[0], mCoord[1]); 491 canvas.translate(-PALETTE_CENTER_X, -PALETTE_CENTER_Y); 492 493 //Draw the 1D slider 494 canvas.translate(PALETTE_DIM, 0); 495 canvas.drawBitmap(mVerSliderBM, 0, 0, null); 496 if (mFocusedControl == 1) 497 hilightFocusedVerSlider(canvas); 498 markVerSlider(canvas, mCoord[2]); 499 500 canvas.restore(); 501 } 502 503 /** 504 * Initialize the current color chooser's UI (set its color parameters and set its palette and slider values accordingly). 505 */ 506 private void initUI() { 507 initHSV1Palette(); 508 509 //Focus on the first controller (arbitrary). 510 mFocusedControl = 0; 511 } 512 513 //NEW_METHOD_WORK_NEEDED_HERE 514 //To add a new method, replicate and extend the last init function shown below 515 /** 516 * Initialize a color chooser. 517 */ 518 private void initHSV1Palette() { 519 setOvalValDimmer(); 520 setVerValSlider(); 521 522 float angle = 2*PI - mHSV[0] / (180 / 3.1415927f); 523 float radius = mHSV[1] * PALETTE_RADIUS; 524 mCoord[0] = (int)(Math.cos(angle) * radius); 525 mCoord[1] = (int)(Math.sin(angle) * radius); 526 527 mCoord[2] = PALETTE_DIM - (int)(mHSV[2] * PALETTE_DIM); 528 } 529 530 //NEW_METHOD_WORK_NEEDED_HERE 531 //To add a new method, replicate and extend the set functions below, one per UI controller in the new method 532 /** 533 * Adjust a Paint which, when painted, dims its underlying object to show the effects of varying value (brightness). 534 */ 535 private void setOvalValDimmer() { 536 float[] hsv = new float[3]; 537 hsv[0] = mHSV[0]; 538 hsv[1] = 0; 539 hsv[2] = mHSV[2]; 540 int gray = Color.HSVToColor(hsv); 541 mValDimmer.setColor(gray); 542 } 543 544 /** 545 * Create a linear gradient shader to show variations in value. 546 */ 547 private void setVerValSlider() { 548 float[] hsv = new float[3]; 549 hsv[0] = mHSV[0]; 550 hsv[1] = mHSV[1]; 551 hsv[2] = 1; 552 int col = Color.HSVToColor(hsv); 553 554 int colors[] = new int[2]; 555 colors[0] = col; 556 colors[1] = 0xFF000000; 557 GradientDrawable gradDraw = new GradientDrawable(Orientation.TOP_BOTTOM, colors); 558 gradDraw.setDither(true); 559 gradDraw.setLevel(10000); 560 gradDraw.setBounds(0, 0, SLIDER_THICKNESS, PALETTE_DIM); 561 gradDraw.draw(mVerSliderCv); 562 } 563 564 /** 565 * Report the correct tightly bounded dimensions of the view. 566 */ 567 @Override 568 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 569 setMeasuredDimension(VIEW_DIM_X, VIEW_DIM_Y); 570 } 571 572 /** 573 * Wrap Math.round(). I'm not a Java expert. Is this the only way to avoid writing "(int)Math.round" everywhere? 574 * @param x 575 * @return 576 */ 577 private int round(double x) { 578 return (int)Math.round(x); 579 } 580 581 /** 582 * Limit a value to the range [0,1]. 583 * @param n 584 * @return 585 */ 586 private float pinToUnit(float n) { 587 if (n < 0) { 588 n = 0; 589 } else if (n > 1) { 590 n = 1; 591 } 592 return n; 593 } 594 595 /** 596 * Limit a value to the range [0,max]. 597 * @param n 598 * @param max 599 * @return 600 */ 601 private float pin(float n, float max) { 602 if (n < 0) { 603 n = 0; 604 } else if (n > max) { 605 n = max; 606 } 607 return n; 608 } 609 610 /** 611 * Limit a value to the range [min,max]. 612 * @param n 613 * @param min 614 * @param max 615 * @return 616 */ 617 private float pin(float n, float min, float max) { 618 if (n < min) { 619 n = min; 620 } else if (n > max) { 621 n = max; 622 } 623 return n; 624 } 625 626 /** 627 * No clue what this does (some sort of average/mean I presume). It came with the original UberColorPickerDialog 628 * in the API Demos and wasn't documented. I don't feel like spending any time figuring it out, I haven't looked at it at all. 629 * @param s 630 * @param d 631 * @param p 632 * @return 633 */ 634 private int ave(int s, int d, float p) { 635 return s + round(p * (d - s)); 636 } 637 638 /** 639 * Came with the original UberColorPickerDialog in the API Demos, wasn't documented. I believe it takes an array of 640 * colors and a value in the range [0,1] and interpolates a resulting color in a seemingly predictable manner. 641 * I haven't looked at it at all. 642 * @param colors 643 * @param unit 644 * @return 645 */ 646 private int interpColor(int colors[], float unit) { 647 if (unit <= 0) { 648 return colors[0]; 649 } 650 if (unit >= 1) { 651 return colors[colors.length - 1]; 652 } 653 654 float p = unit * (colors.length - 1); 655 int i = (int)p; 656 p -= i; 657 658 // now p is just the fractional part [0...1) and i is the index 659 int c0 = colors[i]; 660 int c1 = colors[i+1]; 661 int a = ave(Color.alpha(c0), Color.alpha(c1), p); 662 int r = ave(Color.red(c0), Color.red(c1), p); 663 int g = ave(Color.green(c0), Color.green(c1), p); 664 int b = ave(Color.blue(c0), Color.blue(c1), p); 665 666 return Color.argb(a, r, g, b); 667 } 668 669 /** 670 * A standard point-in-rect routine. 671 * @param x 672 * @param y 673 * @param r 674 * @return true if point x,y is in rect r 675 */ 676 public boolean ptInRect(int x, int y, Rect r) { 677 return x > r.left && x < r.right && y > r.top && y < r.bottom; 678 } 679 680 /** 681 * Process trackball events. Used mainly for fine-tuned color adjustment, or alternatively to switch between slider controls. 682 */ 683 @Override 684 public boolean dispatchTrackballEvent(MotionEvent event) { 685 float x = event.getX(); 686 float y = event.getY(); 687 688 //A longer event history implies faster trackball movement. 689 //Use it to infer a larger jump and therefore faster palette/slider adjustment. 690 int jump = event.getHistorySize() + 1; 691 692 switch (event.getAction()) { 693 case MotionEvent.ACTION_DOWN: { 694 } 695 break; 696 case MotionEvent.ACTION_MOVE: { 697 //NEW_METHOD_WORK_NEEDED_HERE 698 //To add a new method, replicate and extend the appropriate entry in this list, 699 //depending on whether you use 1D or 2D controllers 700 switch (mMethod) { 701 case METHOD_HS_V_PALETTE: 702 if (mFocusedControl == 0) { 703 changeHSPalette(x, y, jump); 704 } 705 else if (mFocusedControl == 1) { 706 if (y < 0) 707 changeSlider(mFocusedControl, true, jump); 708 else if (y > 0) 709 changeSlider(mFocusedControl, false, jump); 710 } 711 break; 712 } 713 } 714 break; 715 case MotionEvent.ACTION_UP: { 716 } 717 break; 718 } 719 720 return true; 721 } 722 723 //NEW_METHOD_WORK_NEEDED_HERE 724 //To add a new method, replicate and extend the appropriate functions below, 725 //one per UI controller in the new method 726 /** 727 * Effect a trackball change to a 2D palette. 728 * @param x -1: negative x change, 0: no x change, +1: positive x change. 729 * @param y -1: negative y change, 0, no y change, +1: positive y change. 730 * @param jump the amount by which to change. 731 */ 732 private void changeHSPalette(float x, float y, int jump) { 733 int x2 = 0, y2 = 0; 734 if (x < 0) 735 x2 = -jump; 736 else if (x > 0) 737 x2 = jump; 738 if (y < 0) 739 y2 = -jump; 740 else if (y > 0) 741 y2 = jump; 742 743 mCoord[0] += x2; 744 mCoord[1] += y2; 745 746 if (mCoord[0] < -PALETTE_RADIUS) 747 mCoord[0] = -PALETTE_RADIUS; 748 else if (mCoord[0] > PALETTE_RADIUS) 749 mCoord[0] = PALETTE_RADIUS; 750 if (mCoord[1] < -PALETTE_RADIUS) 751 mCoord[1] = -PALETTE_RADIUS; 752 else if (mCoord[1] > PALETTE_RADIUS) 753 mCoord[1] = PALETTE_RADIUS; 754 755 float radius = (float)java.lang.Math.sqrt(mCoord[0] * mCoord[0] + mCoord[1] * mCoord[1]); 756 if (radius > PALETTE_RADIUS) 757 radius = PALETTE_RADIUS; 758 759 float angle = (float)java.lang.Math.atan2(mCoord[1], mCoord[0]); 760 // need to turn angle [-PI ... PI] into unit [0....1] 761 float unit = angle/(2*PI); 762 if (unit < 0) { 763 unit += 1; 764 } 765 766 mCoord[0] = round(Math.cos(angle) * radius); 767 mCoord[1] = round(Math.sin(angle) * radius); 768 769 int c = interpColor(mSpectrumColorsRev, unit); 770 float[] hsv = new float[3]; 771 Color.colorToHSV(c, hsv); 772 mHSV[0] = hsv[0]; 773 mHSV[1] = radius / PALETTE_RADIUS; 774 updateAllFromHSV(); 775 mSwatchNew.setColor(Color.HSVToColor(mHSV)); 776 777 setVerValSlider(); 778 779 invalidate(); 780 } 781 782 /** 783 * Effect a trackball change to a 1D slider. 784 * @param slider id of the slider to be effected 785 * @param increase true if the change is an increase, false if a decrease 786 * @param jump the amount by which to change in units of the range [0,255] 787 */ 788 private void changeSlider(int slider, boolean increase, int jump) { 789 //NEW_METHOD_WORK_NEEDED_HERE 790 //It is only necessary to add an entry here for a new method if the new method uses a 1D slider. 791 //Note, some sliders are horizontal and others are vertical. 792 //They differ a bit, especially in a sign flip on the vertical axis. 793 if (mMethod == METHOD_HS_V_PALETTE) { 794 //slider *must* equal 1 795 796 mHSV[2] += (increase ? jump : -jump) / 256.0f; 797 mHSV[2] = pinToUnit(mHSV[2]); 798 updateAllFromHSV(); 799 mCoord[2] = PALETTE_DIM - (int)(mHSV[2] * PALETTE_DIM); 800 801 mSwatchNew.setColor(Color.HSVToColor(mHSV)); 802 803 setOvalValDimmer(); 804 805 invalidate(); 806 } 807 } 808 809 /** 810 * Keep all colorspace representations in sync. 811 */ 812 private void updateRGBfromHSV() { 813 int color = Color.HSVToColor(mHSV); 814 mRGB[0] = Color.red(color); 815 mRGB[1] = Color.green(color); 816 mRGB[2] = Color.blue(color); 817 } 818 819 /** 820 * Keep all colorspace representations in sync. 821 */ 822 private void updateYUVfromRGB() { 823 float r = mRGB[0] / 255.0f; 824 float g = mRGB[1] / 255.0f; 825 float b = mRGB[2] / 255.0f; 826 827 ColorMatrix cm = new ColorMatrix(); 828 cm.setRGB2YUV(); 829 final float[] a = cm.getArray(); 830 831 mYUV[0] = a[0] * r + a[1] * g + a[2] * b; 832 mYUV[0] = pinToUnit(mYUV[0]); 833 mYUV[1] = a[5] * r + a[6] * g + a[7] * b; 834 mYUV[1] = pin(mYUV[1], -.5f, .5f); 835 mYUV[2] = a[10] * r + a[11] * g + a[12] * b; 836 mYUV[2] = pin(mYUV[2], -.5f, .5f); 837 } 838 839 /** 840 * Keep all colorspace representations in sync. 841 */ 842 private void updateHexFromHSV() { 843 //For now, assume 100% opacity 844 mHexStr = Integer.toHexString(Color.HSVToColor(mHSV)).toUpperCase(); 845 mHexStr = mHexStr.substring(2, mHexStr.length()); 846 } 847 848 /** 849 * Keep all colorspace representations in sync. 850 */ 851 private void updateAllFromHSV() { 852 //Update mRGB 853 if (mRGBenabled || mYUVenabled) 854 updateRGBfromHSV(); 855 856 //Update mYUV 857 if (mYUVenabled) 858 updateYUVfromRGB(); 859 860 //Update mHexStr 861 if (mRGBenabled) 862 updateHexFromHSV(); 863 } 864 865 /** 866 * Process touch events: down, move, and up 867 */ 868 @Override 869 public boolean onTouchEvent(MotionEvent event) { 870 float x = event.getX(); 871 float y = event.getY(); 872 873 //Generate coordinates which are palette=local with the origin at the upper left of the main 2D palette 874 int y2 = (int)(pin(round(y - PALETTE_POS_Y), PALETTE_DIM)); 875 876 //Generate coordinates which are palette-local with the origin at the center of the main 2D palette 877 float circlePinnedX = x - PALETTE_POS_X - PALETTE_CENTER_X; 878 float circlePinnedY = y - PALETTE_POS_Y - PALETTE_CENTER_Y; 879 880 //Is the event in a swatch? 881 boolean inSwatchOld = ptInRect(round(x), round(y), mOldSwatchRect); 882 boolean inSwatchNew = ptInRect(round(x), round(y), mNewSwatchRect); 883 884 //Get the event's distance from the center of the main 2D palette 885 float radius = (float)java.lang.Math.sqrt(circlePinnedX * circlePinnedX + circlePinnedY * circlePinnedY); 886 887 //Is the event in a circle-pinned 2D palette? 888 boolean inOvalPalette = radius <= PALETTE_RADIUS; 889 890 //Pin the radius 891 if (radius > PALETTE_RADIUS) 892 radius = PALETTE_RADIUS; 893 894 //Is the event in a vertical slider to the right of the main 2D palette 895 boolean inVerSlider = ptInRect(round(x), round(y), mVerSliderRect); 896 897 switch (event.getAction()) { 898 case MotionEvent.ACTION_DOWN: 899 mTracking = TRACKED_NONE; 900 901 if (inSwatchOld) 902 mTracking = TRACK_SWATCH_OLD; 903 else if (inSwatchNew) 904 mTracking = TRACK_SWATCH_NEW; 905 906 //NEW_METHOD_WORK_NEEDED_HERE 907 //To add a new method, replicate and extend the last entry in this list 908 else if (mMethod == METHOD_HS_V_PALETTE) { 909 if (inOvalPalette) { 910 mTracking = TRACK_HS_PALETTE; 911 mFocusedControl = 0; 912 } 913 else if (inVerSlider) { 914 mTracking = TRACK_VER_VALUE_SLIDER; 915 mFocusedControl = 1; 916 } 917 } 918 case MotionEvent.ACTION_MOVE: 919 //NEW_METHOD_WORK_NEEDED_HERE 920 //To add a new method, replicate and extend the entries in this list, 921 //one per UI controller the new method requires. 922 if (mTracking == TRACK_HS_PALETTE) { 923 float angle = (float)java.lang.Math.atan2(circlePinnedY, circlePinnedX); 924 // need to turn angle [-PI ... PI] into unit [0....1] 925 float unit = angle/(2*PI); 926 if (unit < 0) { 927 unit += 1; 928 } 929 930 mCoord[0] = round(Math.cos(angle) * radius); 931 mCoord[1] = round(Math.sin(angle) * radius); 932 933 int c = interpColor(mSpectrumColorsRev, unit); 934 float[] hsv = new float[3]; 935 Color.colorToHSV(c, hsv); 936 mHSV[0] = hsv[0]; 937 mHSV[1] = radius / PALETTE_RADIUS; 938 updateAllFromHSV(); 939 mSwatchNew.setColor(Color.HSVToColor(mHSV)); 940 941 setVerValSlider(); 942 943 invalidate(); 944 } 945 else if (mTracking == TRACK_VER_VALUE_SLIDER) { 946 if (mCoord[2] != y2) { 947 mCoord[2] = y2; 948 float value = 1.0f - (float)y2 / (float)PALETTE_DIM; 949 950 mHSV[2] = value; 951 updateAllFromHSV(); 952 mSwatchNew.setColor(Color.HSVToColor(mHSV)); 953 954 setOvalValDimmer(); 955 956 invalidate(); 957 } 958 } 959 break; 960 case MotionEvent.ACTION_UP: 961 //NEW_METHOD_WORK_NEEDED_HERE 962 //To add a new method, replicate and extend the last entry in this list. 963 if (mTracking == TRACK_SWATCH_OLD && inSwatchOld) { 964 Color.colorToHSV(mOriginalColor, mHSV); 965 mSwatchNew.setColor(mOriginalColor); 966 initUI(); 967 invalidate(); 968 } 969 else if (mTracking == TRACK_SWATCH_NEW && inSwatchNew) { 970 mListener.colorChanged(mSwatchNew.getColor()); 971 invalidate(); 972 } 973 974 mTracking= TRACKED_NONE; 975 break; 976 } 977 978 return true; 979 } 980 } 981 } 982