2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
20 * kwiley@keithwiley.com
21 * http://keithwiley.com
23 * UberColorPickerDialog v1.1
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.
34 package org.connectbot.util;
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;
59 * UberColorPickerDialog is a seriously enhanced version of the UberColorPickerDialog
60 * class provided in the Android API Demos.<p>
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>
65 * @author Keith Wiley, kwiley@keithwiley.com, http://keithwiley.com
67 public class UberColorPickerDialog extends Dialog {
68 private OnColorChangedListener mListener;
69 private int mInitialColor;
72 * Callback to the creator of the dialog, informing the creator of a new color and notifying that the dialog is about to dismiss.
74 public interface OnColorChangedListener {
75 void colorChanged(int color);
83 * @param showTitle If true, a title is shown across the top of the dialog. If false a toast is shown instead.
85 public UberColorPickerDialog(Context context,
86 OnColorChangedListener listener,
91 mInitialColor = initialColor;
95 * Activity entry point
98 protected void onCreate(Bundle savedInstanceState) {
99 super.onCreate(savedInstanceState);
100 OnColorChangedListener l = new OnColorChangedListener() {
101 public void colorChanged(int color) {
102 mListener.colorChanged(color);
107 DisplayMetrics dm = new DisplayMetrics();
108 getWindow().getWindowManager().getDefaultDisplay().getMetrics(dm);
109 int screenWidth = dm.widthPixels;
110 int screenHeight = dm.heightPixels;
112 setTitle("Pick a color (try the trackball)");
115 setContentView(new ColorPickerView(getContext(), l, screenWidth, screenHeight, mInitialColor));
117 catch (Exception e) {
118 //There is currently only one kind of ctor exception, that where no methods are enabled.
119 dismiss(); //This doesn't work! The dialog is still shown (its title at least, the layout is empty from the exception being thrown). <sigh>
124 * ColorPickerView is the meat of this color picker (as opposed to the enclosing class).
125 * All the heavy lifting is done directly by this View subclass.
127 * You can enable/disable whichever color chooser methods you want by modifying the ENABLED_METHODS switches. They *should*
128 * do all the work required to properly enable/disable methods without losing track of what goes with what and what maps to what.
130 * If you add a new color chooser method, do a text search for "NEW_METHOD_WORK_NEEDED_HERE". That tag indicates all
131 * the locations in the code that will have to be amended in order to properly add a new color chooser method.
132 * 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.
134 private static class ColorPickerView extends View {
135 private static int SWATCH_WIDTH = 95;
136 private static final int SWATCH_HEIGHT = 60;
138 private static int PALETTE_POS_X = 0;
139 private static int PALETTE_POS_Y = SWATCH_HEIGHT;
140 private static final int PALETTE_DIM = SWATCH_WIDTH * 2;
141 private static final int PALETTE_RADIUS = PALETTE_DIM / 2;
142 private static final int PALETTE_CENTER_X = PALETTE_RADIUS;
143 private static final int PALETTE_CENTER_Y = PALETTE_RADIUS;
145 private static final int SLIDER_THICKNESS = 40;
147 private static int VIEW_DIM_X = PALETTE_DIM;
148 private static int VIEW_DIM_Y = SWATCH_HEIGHT;
150 //NEW_METHOD_WORK_NEEDED_HERE
151 private static final int METHOD_HS_V_PALETTE = 0;
153 //NEW_METHOD_WORK_NEEDED_HERE
154 //Add a new entry to the list for each controller in the new method
155 private static final int TRACKED_NONE = -1; //No object on screen is currently being tracked
156 private static final int TRACK_SWATCH_OLD = 10;
157 private static final int TRACK_SWATCH_NEW = 11;
158 private static final int TRACK_HS_PALETTE = 30;
159 private static final int TRACK_VER_VALUE_SLIDER = 31;
161 private static final int TEXT_SIZE = 12;
162 private static int[] TEXT_HSV_POS = new int[2];
163 private static int[] TEXT_RGB_POS = new int[2];
164 private static int[] TEXT_YUV_POS = new int[2];
165 private static int[] TEXT_HEX_POS = new int[2];
167 private static final float PI = 3.141592653589793f;
169 private int mMethod = METHOD_HS_V_PALETTE;
170 private int mTracking = TRACKED_NONE; //What object on screen is currently being tracked for movement
172 //Zillions of persistant Paint objecs for drawing the View
174 private Paint mSwatchOld, mSwatchNew;
176 //NEW_METHOD_WORK_NEEDED_HERE
177 //Add Paints to represent the palettes of the new method's UI controllers
178 private Paint mOvalHueSat;
180 private Bitmap mVerSliderBM;
181 private Canvas mVerSliderCv;
183 private Bitmap[] mHorSlidersBM = new Bitmap[3];
184 private Canvas[] mHorSlidersCv = new Canvas[3];
186 private Paint mValDimmer;
188 //NEW_METHOD_WORK_NEEDED_HERE
189 //Add Paints to represent the icon for the new method
190 private Paint mOvalHueSatSmall;
192 private Paint mPosMarker;
195 private Rect mOldSwatchRect = new Rect();
196 private Rect mNewSwatchRect = new Rect();
197 private Rect mPaletteRect = new Rect();
198 private Rect mVerSliderRect = new Rect();
200 private int[] mSpectrumColorsRev;
201 private int mOriginalColor = 0; //The color passed in at the beginning, which can be reverted to at any time by tapping the old swatch.
202 private float[] mHSV = new float[3];
203 private int[] mRGB = new int[3];
204 private float[] mYUV = new float[3];
205 private String mHexStr = "";
206 private boolean mHSVenabled = true; //Only true if an HSV method is enabled
207 private boolean mRGBenabled = true; //Only true if an RGB method is enabled
208 private boolean mYUVenabled = true; //Only true if a YUV method is enabled
209 private boolean mHexenabled = true; //Only true if an RGB method is enabled
210 private int[] mCoord = new int[3]; //For drawing slider/palette markers
211 private int mFocusedControl = -1; //Which control receives trackball events.
212 private OnColorChangedListener mListener;
218 * @param width Used to determine orientation and adjust layout accordingly
219 * @param height Used to determine orientation and adjust layout accordingly
220 * @param color The initial color
223 ColorPickerView(Context c, OnColorChangedListener l, int width, int height, int color)
227 //We need to make the dialog focusable to retrieve trackball events.
232 mOriginalColor = color;
234 Color.colorToHSV(color, mHSV);
238 //Setup the layout based on whether this is a portrait or landscape orientation.
239 if (width <= height) { //Portrait layout
240 SWATCH_WIDTH = (PALETTE_DIM + SLIDER_THICKNESS) / 2;
243 PALETTE_POS_Y = TEXT_SIZE * 4 + SWATCH_HEIGHT;
245 //Set more rects, lots of rects
246 mOldSwatchRect.set(0, TEXT_SIZE * 4, SWATCH_WIDTH, TEXT_SIZE * 4 + SWATCH_HEIGHT);
247 mNewSwatchRect.set(SWATCH_WIDTH, TEXT_SIZE * 4, SWATCH_WIDTH * 2, TEXT_SIZE * 4 + SWATCH_HEIGHT);
248 mPaletteRect.set(0, PALETTE_POS_Y, PALETTE_DIM, PALETTE_POS_Y + PALETTE_DIM);
249 mVerSliderRect.set(PALETTE_DIM, PALETTE_POS_Y, PALETTE_DIM + SLIDER_THICKNESS, PALETTE_POS_Y + PALETTE_DIM);
253 TEXT_RGB_POS[0] = TEXT_HSV_POS[0] + 50;
254 TEXT_RGB_POS[1] = TEXT_HSV_POS[1];
255 TEXT_YUV_POS[0] = TEXT_HSV_POS[0] + 100;
256 TEXT_YUV_POS[1] = TEXT_HSV_POS[1];
257 TEXT_HEX_POS[0] = TEXT_HSV_POS[0] + 150;
258 TEXT_HEX_POS[1] = TEXT_HSV_POS[1];
260 VIEW_DIM_X = PALETTE_DIM + SLIDER_THICKNESS;
261 VIEW_DIM_Y = SWATCH_HEIGHT + PALETTE_DIM + TEXT_SIZE * 4;
263 else { //Landscape layout
266 PALETTE_POS_X = SWATCH_WIDTH;
269 //Set more rects, lots of rects
270 mOldSwatchRect.set(0, TEXT_SIZE * 7, SWATCH_WIDTH, TEXT_SIZE * 7 + SWATCH_HEIGHT);
271 mNewSwatchRect.set(0, TEXT_SIZE * 7 + SWATCH_HEIGHT, SWATCH_WIDTH, TEXT_SIZE * 7 + SWATCH_HEIGHT * 2);
272 mPaletteRect.set(SWATCH_WIDTH, PALETTE_POS_Y, SWATCH_WIDTH + PALETTE_DIM, PALETTE_POS_Y + PALETTE_DIM);
273 mVerSliderRect.set(SWATCH_WIDTH + PALETTE_DIM, PALETTE_POS_Y, SWATCH_WIDTH + PALETTE_DIM + SLIDER_THICKNESS, PALETTE_POS_Y + PALETTE_DIM);
277 TEXT_RGB_POS[0] = TEXT_HSV_POS[0];
278 TEXT_RGB_POS[1] = (int)(TEXT_HSV_POS[1] + TEXT_SIZE * 3.5);
279 TEXT_YUV_POS[0] = TEXT_HSV_POS[0] + 50;
280 TEXT_YUV_POS[1] = (int)(TEXT_HSV_POS[1] + TEXT_SIZE * 3.5);
281 TEXT_HEX_POS[0] = TEXT_HSV_POS[0] + 50;
282 TEXT_HEX_POS[1] = TEXT_HSV_POS[1];
284 VIEW_DIM_X = PALETTE_POS_X + PALETTE_DIM + SLIDER_THICKNESS;
285 VIEW_DIM_Y = Math.max(mNewSwatchRect.bottom, PALETTE_DIM);
288 //Rainbows make everybody happy!
289 mSpectrumColorsRev = new int[] {
290 0xFFFF0000, 0xFFFF00FF, 0xFF0000FF, 0xFF00FFFF,
291 0xFF00FF00, 0xFFFFFF00, 0xFFFF0000,
294 //Setup all the Paint and Shader objects. There are lots of them!
296 //NEW_METHOD_WORK_NEEDED_HERE
297 //Add Paints to represent the palettes of the new method's UI controllers
299 mSwatchOld = new Paint(Paint.ANTI_ALIAS_FLAG);
300 mSwatchOld.setStyle(Paint.Style.FILL);
301 mSwatchOld.setColor(Color.HSVToColor(mHSV));
303 mSwatchNew = new Paint(Paint.ANTI_ALIAS_FLAG);
304 mSwatchNew.setStyle(Paint.Style.FILL);
305 mSwatchNew.setColor(Color.HSVToColor(mHSV));
307 Shader shaderA = new SweepGradient(0, 0, mSpectrumColorsRev, null);
308 Shader shaderB = new RadialGradient(0, 0, PALETTE_CENTER_X, 0xFFFFFFFF, 0xFF000000, Shader.TileMode.CLAMP);
309 Shader shader = new ComposeShader(shaderA, shaderB, PorterDuff.Mode.SCREEN);
310 mOvalHueSat = new Paint(Paint.ANTI_ALIAS_FLAG);
311 mOvalHueSat.setShader(shader);
312 mOvalHueSat.setStyle(Paint.Style.FILL);
313 mOvalHueSat.setDither(true);
315 mVerSliderBM = Bitmap.createBitmap(SLIDER_THICKNESS, PALETTE_DIM, Bitmap.Config.RGB_565);
316 mVerSliderCv = new Canvas(mVerSliderBM);
318 for (int i = 0; i < 3; i++) {
319 mHorSlidersBM[i] = Bitmap.createBitmap(PALETTE_DIM, SLIDER_THICKNESS, Bitmap.Config.RGB_565);
320 mHorSlidersCv[i] = new Canvas(mHorSlidersBM[i]);
323 mValDimmer = new Paint(Paint.ANTI_ALIAS_FLAG);
324 mValDimmer.setStyle(Paint.Style.FILL);
325 mValDimmer.setDither(true);
326 mValDimmer.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
328 //Whew, we're done making the big Paints and Shaders for the swatches, palettes, and sliders.
329 //Now we need to make the Paints and Shaders that will draw the little method icons in the method selector list.
331 //NEW_METHOD_WORK_NEEDED_HERE
332 //Add Paints to represent the icon for the new method
334 shaderA = new SweepGradient(0, 0, mSpectrumColorsRev, null);
335 shaderB = new RadialGradient(0, 0, PALETTE_DIM / 2, 0xFFFFFFFF, 0xFF000000, Shader.TileMode.CLAMP);
336 shader = new ComposeShader(shaderA, shaderB, PorterDuff.Mode.SCREEN);
337 mOvalHueSatSmall = new Paint(Paint.ANTI_ALIAS_FLAG);
338 mOvalHueSatSmall.setShader(shader);
339 mOvalHueSatSmall.setStyle(Paint.Style.FILL);
341 //Make a simple stroking Paint for drawing markers and borders and stuff like that.
342 mPosMarker = new Paint(Paint.ANTI_ALIAS_FLAG);
343 mPosMarker.setStyle(Paint.Style.STROKE);
344 mPosMarker.setStrokeWidth(2);
346 //Make a basic text Paint.
347 mText = new Paint(Paint.ANTI_ALIAS_FLAG);
348 mText.setTextSize(TEXT_SIZE);
349 mText.setColor(Color.WHITE);
356 * Draw the entire view (the entire dialog).
359 protected void onDraw(Canvas canvas) {
360 //Draw the old and new swatches
361 drawSwatches(canvas);
364 writeColorParams(canvas);
366 //Draw the palette and sliders (the UI)
367 if (mMethod == METHOD_HS_V_PALETTE)
368 drawHSV1Palette(canvas);
372 * Draw the old and new swatches.
375 private void drawSwatches(Canvas canvas) {
376 float[] hsv = new float[3];
378 mText.setTextSize(16);
380 //Draw the original swatch
381 canvas.drawRect(mOldSwatchRect, mSwatchOld);
382 Color.colorToHSV(mOriginalColor, hsv);
383 //if (UberColorPickerDialog.isGray(mColor)) //Don't need this right here, but imp't to note
386 mText.setColor(Color.BLACK);
387 canvas.drawText("Revert", mOldSwatchRect.left + SWATCH_WIDTH / 2 - mText.measureText("Revert") / 2, mOldSwatchRect.top + 16, mText);
388 mText.setColor(Color.WHITE);
390 //Draw the new swatch
391 canvas.drawRect(mNewSwatchRect, mSwatchNew);
393 mText.setColor(Color.BLACK);
394 canvas.drawText("Accept", mNewSwatchRect.left + SWATCH_WIDTH / 2 - mText.measureText("Accept") / 2, mNewSwatchRect.top + 16, mText);
395 mText.setColor(Color.WHITE);
397 mText.setTextSize(TEXT_SIZE);
401 * Write the color parametes (HSV, RGB, YUV, Hex, etc.).
404 private void writeColorParams(Canvas canvas) {
406 canvas.drawText("H: " + Integer.toString((int)(mHSV[0] / 360.0f * 255)), TEXT_HSV_POS[0], TEXT_HSV_POS[1] + TEXT_SIZE, mText);
407 canvas.drawText("S: " + Integer.toString((int)(mHSV[1] * 255)), TEXT_HSV_POS[0], TEXT_HSV_POS[1] + TEXT_SIZE * 2, mText);
408 canvas.drawText("V: " + Integer.toString((int)(mHSV[2] * 255)), TEXT_HSV_POS[0], TEXT_HSV_POS[1] + TEXT_SIZE * 3, mText);
412 canvas.drawText("R: " + mRGB[0], TEXT_RGB_POS[0], TEXT_RGB_POS[1] + TEXT_SIZE, mText);
413 canvas.drawText("G: " + mRGB[1], TEXT_RGB_POS[0], TEXT_RGB_POS[1] + TEXT_SIZE * 2, mText);
414 canvas.drawText("B: " + mRGB[2], TEXT_RGB_POS[0], TEXT_RGB_POS[1] + TEXT_SIZE * 3, mText);
418 canvas.drawText("Y: " + Integer.toString((int)(mYUV[0] * 255)), TEXT_YUV_POS[0], TEXT_YUV_POS[1] + TEXT_SIZE, mText);
419 canvas.drawText("U: " + Integer.toString((int)((mYUV[1] + .5f) * 255)), TEXT_YUV_POS[0], TEXT_YUV_POS[1] + TEXT_SIZE * 2, mText);
420 canvas.drawText("V: " + Integer.toString((int)((mYUV[2] + .5f) * 255)), TEXT_YUV_POS[0], TEXT_YUV_POS[1] + TEXT_SIZE * 3, mText);
424 canvas.drawText("#" + mHexStr, TEXT_HEX_POS[0], TEXT_HEX_POS[1] + TEXT_SIZE, mText);
428 * Place a small circle on the 2D palette to indicate the current values.
433 private void mark2DPalette(Canvas canvas, int markerPosX, int markerPosY) {
434 mPosMarker.setColor(Color.BLACK);
435 canvas.drawOval(new RectF(markerPosX - 5, markerPosY - 5, markerPosX + 5, markerPosY + 5), mPosMarker);
436 mPosMarker.setColor(Color.WHITE);
437 canvas.drawOval(new RectF(markerPosX - 3, markerPosY - 3, markerPosX + 3, markerPosY + 3), mPosMarker);
441 * Draw a line across the slider to indicate its current value.
445 private void markVerSlider(Canvas canvas, int markerPos) {
446 mPosMarker.setColor(Color.BLACK);
447 canvas.drawRect(new Rect(0, markerPos - 2, SLIDER_THICKNESS, markerPos + 3), mPosMarker);
448 mPosMarker.setColor(Color.WHITE);
449 canvas.drawRect(new Rect(0, markerPos, SLIDER_THICKNESS, markerPos + 1), mPosMarker);
453 * Frame the slider to indicate that it has trackball focus.
456 private void hilightFocusedVerSlider(Canvas canvas) {
457 mPosMarker.setColor(Color.WHITE);
458 canvas.drawRect(new Rect(0, 0, SLIDER_THICKNESS, PALETTE_DIM), mPosMarker);
459 mPosMarker.setColor(Color.BLACK);
460 canvas.drawRect(new Rect(2, 2, SLIDER_THICKNESS - 2, PALETTE_DIM - 2), mPosMarker);
464 * Frame the 2D palette to indicate that it has trackball focus.
467 private void hilightFocusedOvalPalette(Canvas canvas) {
468 mPosMarker.setColor(Color.WHITE);
469 canvas.drawOval(new RectF(-PALETTE_RADIUS, -PALETTE_RADIUS, PALETTE_RADIUS, PALETTE_RADIUS), mPosMarker);
470 mPosMarker.setColor(Color.BLACK);
471 canvas.drawOval(new RectF(-PALETTE_RADIUS + 2, -PALETTE_RADIUS + 2, PALETTE_RADIUS - 2, PALETTE_RADIUS - 2), mPosMarker);
474 //NEW_METHOD_WORK_NEEDED_HERE
475 //To add a new method, replicate the basic draw functions here. Use the 2D palette or 1D sliders as templates for the new method.
477 * Draw the UI for HSV with angular H and radial S combined in 2D and a 1D V slider.
480 private void drawHSV1Palette(Canvas canvas) {
483 canvas.translate(PALETTE_POS_X, PALETTE_POS_Y);
485 //Draw the 2D palette
486 canvas.translate(PALETTE_CENTER_X, PALETTE_CENTER_Y);
487 canvas.drawOval(new RectF(-PALETTE_RADIUS, -PALETTE_RADIUS, PALETTE_RADIUS, PALETTE_RADIUS), mOvalHueSat);
488 canvas.drawOval(new RectF(-PALETTE_RADIUS, -PALETTE_RADIUS, PALETTE_RADIUS, PALETTE_RADIUS), mValDimmer);
489 if (mFocusedControl == 0)
490 hilightFocusedOvalPalette(canvas);
491 mark2DPalette(canvas, mCoord[0], mCoord[1]);
492 canvas.translate(-PALETTE_CENTER_X, -PALETTE_CENTER_Y);
495 canvas.translate(PALETTE_DIM, 0);
496 canvas.drawBitmap(mVerSliderBM, 0, 0, null);
497 if (mFocusedControl == 1)
498 hilightFocusedVerSlider(canvas);
499 markVerSlider(canvas, mCoord[2]);
505 * Initialize the current color chooser's UI (set its color parameters and set its palette and slider values accordingly).
507 private void initUI() {
510 //Focus on the first controller (arbitrary).
514 //NEW_METHOD_WORK_NEEDED_HERE
515 //To add a new method, replicate and extend the last init function shown below
517 * Initialize a color chooser.
519 private void initHSV1Palette() {
523 float angle = 2*PI - mHSV[0] / (180 / 3.1415927f);
524 float radius = mHSV[1] * PALETTE_RADIUS;
525 mCoord[0] = (int)(Math.cos(angle) * radius);
526 mCoord[1] = (int)(Math.sin(angle) * radius);
528 mCoord[2] = PALETTE_DIM - (int)(mHSV[2] * PALETTE_DIM);
531 //NEW_METHOD_WORK_NEEDED_HERE
532 //To add a new method, replicate and extend the set functions below, one per UI controller in the new method
534 * Adjust a Paint which, when painted, dims its underlying object to show the effects of varying value (brightness).
536 private void setOvalValDimmer() {
537 float[] hsv = new float[3];
541 int gray = Color.HSVToColor(hsv);
542 mValDimmer.setColor(gray);
546 * Create a linear gradient shader to show variations in value.
548 private void setVerValSlider() {
549 float[] hsv = new float[3];
553 int col = Color.HSVToColor(hsv);
555 int colors[] = new int[2];
557 colors[1] = 0xFF000000;
558 GradientDrawable gradDraw = new GradientDrawable(Orientation.TOP_BOTTOM, colors);
559 gradDraw.setDither(true);
560 gradDraw.setLevel(10000);
561 gradDraw.setBounds(0, 0, SLIDER_THICKNESS, PALETTE_DIM);
562 gradDraw.draw(mVerSliderCv);
566 * Report the correct tightly bounded dimensions of the view.
569 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
570 setMeasuredDimension(VIEW_DIM_X, VIEW_DIM_Y);
574 * Convert a slider position in the range [0,PALETTE_DIM] to a byte value in the range [0,255].
575 * @param sliderPos in the range [0,PALETTE_DIM].
578 public int sliderPosTo255(int sliderPos) {
579 int int255 = (int)(255.0f * ((float)sliderPos / (float)PALETTE_DIM));
580 int255 = pinToByte(int255);
585 * Wrap Math.round(). I'm not a Java expert. Is this the only way to avoid writing "(int)Math.round" everywhere?
589 private int round(double x) {
590 return (int)Math.round(x);
594 * Limit a value to a min and max range.
598 private int pinToByte(int n) {
601 } else if (n > 255) {
608 * Limit a value to the range [0,1].
612 private float pinToUnit(float n) {
622 * Limit a value to the range [0,max].
627 private float pin(float n, float max) {
630 } else if (n > max) {
637 * Limit a value to the range [min,max].
643 private float pin(float n, float min, float max) {
646 } else if (n > max) {
653 * No clue what this does (some sort of average/mean I presume). It came with the original UberColorPickerDialog
654 * 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.
660 private int ave(int s, int d, float p) {
661 return s + round(p * (d - s));
665 * Came with the original UberColorPickerDialog in the API Demos, wasn't documented. I believe it takes an array of
666 * colors and a value in the range [0,1] and interpolates a resulting color in a seemingly predictable manner.
667 * I haven't looked at it at all.
672 private int interpColor(int colors[], float unit) {
677 return colors[colors.length - 1];
680 float p = unit * (colors.length - 1);
684 // now p is just the fractional part [0...1) and i is the index
686 int c1 = colors[i+1];
687 int a = ave(Color.alpha(c0), Color.alpha(c1), p);
688 int r = ave(Color.red(c0), Color.red(c1), p);
689 int g = ave(Color.green(c0), Color.green(c1), p);
690 int b = ave(Color.blue(c0), Color.blue(c1), p);
692 return Color.argb(a, r, g, b);
696 * A standard point-in-rect routine.
700 * @return true if point x,y is in rect r
702 public boolean ptInRect(int x, int y, Rect r) {
703 return x > r.left && x < r.right && y > r.top && y < r.bottom;
707 * Process trackball events. Used mainly for fine-tuned color adjustment, or alternatively to switch between slider controls.
710 public boolean dispatchTrackballEvent(MotionEvent event) {
711 float x = event.getX();
712 float y = event.getY();
714 //A longer event history implies faster trackball movement.
715 //Use it to infer a larger jump and therefore faster palette/slider adjustment.
716 int jump = event.getHistorySize() + 1;
718 switch (event.getAction()) {
719 case MotionEvent.ACTION_DOWN: {
722 case MotionEvent.ACTION_MOVE: {
723 //NEW_METHOD_WORK_NEEDED_HERE
724 //To add a new method, replicate and extend the appropriate entry in this list,
725 //depending on whether you use 1D or 2D controllers
727 case METHOD_HS_V_PALETTE:
728 if (mFocusedControl == 0) {
729 changeHSPalette(x, y, jump);
731 else if (mFocusedControl == 1) {
733 changeSlider(mFocusedControl, true, jump);
735 changeSlider(mFocusedControl, false, jump);
741 case MotionEvent.ACTION_UP: {
749 //NEW_METHOD_WORK_NEEDED_HERE
750 //To add a new method, replicate and extend the appropriate functions below,
751 //one per UI controller in the new method
753 * Effect a trackball change to a 2D palette.
754 * @param x -1: negative x change, 0: no x change, +1: positive x change.
755 * @param y -1: negative y change, 0, no y change, +1: positive y change.
756 * @param jump the amount by which to change.
758 private void changeHSPalette(float x, float y, int jump) {
772 if (mCoord[0] < -PALETTE_RADIUS)
773 mCoord[0] = -PALETTE_RADIUS;
774 else if (mCoord[0] > PALETTE_RADIUS)
775 mCoord[0] = PALETTE_RADIUS;
776 if (mCoord[1] < -PALETTE_RADIUS)
777 mCoord[1] = -PALETTE_RADIUS;
778 else if (mCoord[1] > PALETTE_RADIUS)
779 mCoord[1] = PALETTE_RADIUS;
781 float radius = (float)java.lang.Math.sqrt(mCoord[0] * mCoord[0] + mCoord[1] * mCoord[1]);
782 if (radius > PALETTE_RADIUS)
783 radius = PALETTE_RADIUS;
785 float angle = (float)java.lang.Math.atan2(mCoord[1], mCoord[0]);
786 // need to turn angle [-PI ... PI] into unit [0....1]
787 float unit = angle/(2*PI);
792 mCoord[0] = round(Math.cos(angle) * radius);
793 mCoord[1] = round(Math.sin(angle) * radius);
795 int c = interpColor(mSpectrumColorsRev, unit);
796 float[] hsv = new float[3];
797 Color.colorToHSV(c, hsv);
799 mHSV[1] = radius / PALETTE_RADIUS;
801 mSwatchNew.setColor(Color.HSVToColor(mHSV));
809 * Effect a trackball change to a 1D slider.
810 * @param slider id of the slider to be effected
811 * @param increase true if the change is an increase, false if a decrease
812 * @param jump the amount by which to change in units of the range [0,255]
814 private void changeSlider(int slider, boolean increase, int jump) {
815 //NEW_METHOD_WORK_NEEDED_HERE
816 //It is only necessary to add an entry here for a new method if the new method uses a 1D slider.
817 //Note, some sliders are horizontal and others are vertical.
818 //They differ a bit, especially in a sign flip on the vertical axis.
819 if (mMethod == METHOD_HS_V_PALETTE) {
820 //slider *must* equal 1
822 mHSV[2] += (increase ? jump : -jump) / 256.0f;
823 mHSV[2] = pinToUnit(mHSV[2]);
825 mCoord[2] = PALETTE_DIM - (int)(mHSV[2] * PALETTE_DIM);
827 mSwatchNew.setColor(Color.HSVToColor(mHSV));
836 * Keep all colorspace representations in sync.
838 private void updateRGBfromHSV() {
839 int color = Color.HSVToColor(mHSV);
840 mRGB[0] = Color.red(color);
841 mRGB[1] = Color.green(color);
842 mRGB[2] = Color.blue(color);
846 * Keep all colorspace representations in sync.
848 private void updateYUVfromRGB() {
849 float r = mRGB[0] / 255.0f;
850 float g = mRGB[1] / 255.0f;
851 float b = mRGB[2] / 255.0f;
853 ColorMatrix cm = new ColorMatrix();
855 final float[] a = cm.getArray();
857 mYUV[0] = a[0] * r + a[1] * g + a[2] * b;
858 mYUV[0] = pinToUnit(mYUV[0]);
859 mYUV[1] = a[5] * r + a[6] * g + a[7] * b;
860 mYUV[1] = pin(mYUV[1], -.5f, .5f);
861 mYUV[2] = a[10] * r + a[11] * g + a[12] * b;
862 mYUV[2] = pin(mYUV[2], -.5f, .5f);
866 * Keep all colorspace representations in sync.
868 private void updateHexFromHSV() {
869 //For now, assume 100% opacity
870 mHexStr = Integer.toHexString(Color.HSVToColor(mHSV)).toUpperCase();
871 mHexStr = mHexStr.substring(2, mHexStr.length());
875 * Keep all colorspace representations in sync.
877 private void updateAllFromHSV() {
879 if (mRGBenabled || mYUVenabled)
892 * Process touch events: down, move, and up
895 public boolean onTouchEvent(MotionEvent event) {
896 float x = event.getX();
897 float y = event.getY();
899 //Generate coordinates which are palette=local with the origin at the upper left of the main 2D palette
900 int y2 = (int)(pin(round(y - PALETTE_POS_Y), PALETTE_DIM));
902 //Generate coordinates which are palette-local with the origin at the center of the main 2D palette
903 float circlePinnedX = x - PALETTE_POS_X - PALETTE_CENTER_X;
904 float circlePinnedY = y - PALETTE_POS_Y - PALETTE_CENTER_Y;
906 //Is the event in a swatch?
907 boolean inSwatchOld = ptInRect(round(x), round(y), mOldSwatchRect);
908 boolean inSwatchNew = ptInRect(round(x), round(y), mNewSwatchRect);
910 //Get the event's distance from the center of the main 2D palette
911 float radius = (float)java.lang.Math.sqrt(circlePinnedX * circlePinnedX + circlePinnedY * circlePinnedY);
913 //Is the event in a circle-pinned 2D palette?
914 boolean inOvalPalette = radius <= PALETTE_RADIUS;
917 if (radius > PALETTE_RADIUS)
918 radius = PALETTE_RADIUS;
920 //Is the event in a vertical slider to the right of the main 2D palette
921 boolean inVerSlider = ptInRect(round(x), round(y), mVerSliderRect);
923 switch (event.getAction()) {
924 case MotionEvent.ACTION_DOWN:
925 mTracking = TRACKED_NONE;
928 mTracking = TRACK_SWATCH_OLD;
929 else if (inSwatchNew)
930 mTracking = TRACK_SWATCH_NEW;
932 //NEW_METHOD_WORK_NEEDED_HERE
933 //To add a new method, replicate and extend the last entry in this list
934 else if (mMethod == METHOD_HS_V_PALETTE) {
936 mTracking = TRACK_HS_PALETTE;
939 else if (inVerSlider) {
940 mTracking = TRACK_VER_VALUE_SLIDER;
944 case MotionEvent.ACTION_MOVE:
945 //NEW_METHOD_WORK_NEEDED_HERE
946 //To add a new method, replicate and extend the entries in this list,
947 //one per UI controller the new method requires.
948 if (mTracking == TRACK_HS_PALETTE) {
949 float angle = (float)java.lang.Math.atan2(circlePinnedY, circlePinnedX);
950 // need to turn angle [-PI ... PI] into unit [0....1]
951 float unit = angle/(2*PI);
956 mCoord[0] = round(Math.cos(angle) * radius);
957 mCoord[1] = round(Math.sin(angle) * radius);
959 int c = interpColor(mSpectrumColorsRev, unit);
960 float[] hsv = new float[3];
961 Color.colorToHSV(c, hsv);
963 mHSV[1] = radius / PALETTE_RADIUS;
965 mSwatchNew.setColor(Color.HSVToColor(mHSV));
971 else if (mTracking == TRACK_VER_VALUE_SLIDER) {
972 if (mCoord[2] != y2) {
974 float value = 1.0f - (float)y2 / (float)PALETTE_DIM;
978 mSwatchNew.setColor(Color.HSVToColor(mHSV));
986 case MotionEvent.ACTION_UP:
987 //NEW_METHOD_WORK_NEEDED_HERE
988 //To add a new method, replicate and extend the last entry in this list.
989 if (mTracking == TRACK_SWATCH_OLD && inSwatchOld) {
990 Color.colorToHSV(mOriginalColor, mHSV);
991 mSwatchNew.setColor(mOriginalColor);
995 else if (mTracking == TRACK_SWATCH_NEW && inSwatchNew) {
996 mListener.colorChanged(mSwatchNew.getColor());
1000 mTracking= TRACKED_NONE;