| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* |
| * 090408 |
| * Keith Wiley |
| * kwiley@keithwiley.com |
| * http://keithwiley.com |
| * |
| * UberColorPickerDialog v1.1 |
| * |
| * This color picker was implemented as a (significant) extension of the |
| * ColorPickerDialog class provided in the Android API Demos. You are free |
| * to drop it unchanged into your own projects or to modify it as you see |
| * fit. I would appreciate it if this comment block were let intact, |
| * merely for credit's sake. |
| * |
| * Enjoy! |
| */ |
| |
| package org.connectbot.util; |
| |
| import android.app.Dialog; |
| import android.content.Context; |
| import android.graphics.Bitmap; |
| import android.graphics.Canvas; |
| import android.graphics.Color; |
| import android.graphics.ColorMatrix; |
| import android.graphics.ComposeShader; |
| import android.graphics.Paint; |
| import android.graphics.PorterDuff; |
| import android.graphics.PorterDuffXfermode; |
| import android.graphics.RadialGradient; |
| import android.graphics.Rect; |
| import android.graphics.RectF; |
| import android.graphics.Shader; |
| import android.graphics.SweepGradient; |
| import android.graphics.drawable.GradientDrawable; |
| import android.graphics.drawable.GradientDrawable.Orientation; |
| import android.os.Bundle; |
| import android.util.DisplayMetrics; |
| import android.view.MotionEvent; |
| import android.view.View; |
| |
| /** |
| * UberColorPickerDialog is a seriously enhanced version of the UberColorPickerDialog |
| * class provided in the Android API Demos.<p> |
| * |
| * NOTE (from Kenny Root): This is a VERY slimmed down version custom for ConnectBot. |
| * Visit Keith's site for the full version at the URL listed in the author line.<p> |
| * |
| * @author Keith Wiley, kwiley@keithwiley.com, http://keithwiley.com |
| */ |
| public class UberColorPickerDialog extends Dialog { |
| private OnColorChangedListener mListener; |
| private int mInitialColor; |
| |
| /** |
| * Callback to the creator of the dialog, informing the creator of a new color and notifying that the dialog is about to dismiss. |
| */ |
| public interface OnColorChangedListener { |
| void colorChanged(int color); |
| } |
| |
| /** |
| * Ctor |
| * @param context |
| * @param listener |
| * @param initialColor |
| * @param showTitle If true, a title is shown across the top of the dialog. If false a toast is shown instead. |
| */ |
| public UberColorPickerDialog(Context context, |
| OnColorChangedListener listener, |
| int initialColor) { |
| super(context); |
| |
| mListener = listener; |
| mInitialColor = initialColor; |
| } |
| |
| /** |
| * Activity entry point |
| */ |
| @Override |
| protected void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| OnColorChangedListener l = new OnColorChangedListener() { |
| public void colorChanged(int color) { |
| mListener.colorChanged(color); |
| dismiss(); |
| } |
| }; |
| |
| DisplayMetrics dm = new DisplayMetrics(); |
| getWindow().getWindowManager().getDefaultDisplay().getMetrics(dm); |
| int screenWidth = dm.widthPixels; |
| int screenHeight = dm.heightPixels; |
| |
| setTitle("Pick a color (try the trackball)"); |
| |
| try { |
| setContentView(new ColorPickerView(getContext(), l, screenWidth, screenHeight, mInitialColor)); |
| } |
| catch (Exception e) { |
| //There is currently only one kind of ctor exception, that where no methods are enabled. |
| dismiss(); //This doesn't work! The dialog is still shown (its title at least, the layout is empty from the exception being thrown). <sigh> |
| } |
| } |
| |
| /** |
| * ColorPickerView is the meat of this color picker (as opposed to the enclosing class). |
| * All the heavy lifting is done directly by this View subclass. |
| * <P> |
| * You can enable/disable whichever color chooser methods you want by modifying the ENABLED_METHODS switches. They *should* |
| * do all the work required to properly enable/disable methods without losing track of what goes with what and what maps to what. |
| * <P> |
| * If you add a new color chooser method, do a text search for "NEW_METHOD_WORK_NEEDED_HERE". That tag indicates all |
| * the locations in the code that will have to be amended in order to properly add a new color chooser method. |
| * 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. |
| */ |
| private static class ColorPickerView extends View { |
| private static int SWATCH_WIDTH = 95; |
| private static final int SWATCH_HEIGHT = 60; |
| |
| private static int PALETTE_POS_X = 0; |
| private static int PALETTE_POS_Y = SWATCH_HEIGHT; |
| private static final int PALETTE_DIM = SWATCH_WIDTH * 2; |
| private static final int PALETTE_RADIUS = PALETTE_DIM / 2; |
| private static final int PALETTE_CENTER_X = PALETTE_RADIUS; |
| private static final int PALETTE_CENTER_Y = PALETTE_RADIUS; |
| |
| private static final int SLIDER_THICKNESS = 40; |
| |
| private static int VIEW_DIM_X = PALETTE_DIM; |
| private static int VIEW_DIM_Y = SWATCH_HEIGHT; |
| |
| //NEW_METHOD_WORK_NEEDED_HERE |
| private static final int METHOD_HS_V_PALETTE = 0; |
| |
| //NEW_METHOD_WORK_NEEDED_HERE |
| //Add a new entry to the list for each controller in the new method |
| private static final int TRACKED_NONE = -1; //No object on screen is currently being tracked |
| private static final int TRACK_SWATCH_OLD = 10; |
| private static final int TRACK_SWATCH_NEW = 11; |
| private static final int TRACK_HS_PALETTE = 30; |
| private static final int TRACK_VER_VALUE_SLIDER = 31; |
| |
| private static final int TEXT_SIZE = 12; |
| private static int[] TEXT_HSV_POS = new int[2]; |
| private static int[] TEXT_RGB_POS = new int[2]; |
| private static int[] TEXT_YUV_POS = new int[2]; |
| private static int[] TEXT_HEX_POS = new int[2]; |
| |
| private static final float PI = 3.141592653589793f; |
| |
| private int mMethod = METHOD_HS_V_PALETTE; |
| private int mTracking = TRACKED_NONE; //What object on screen is currently being tracked for movement |
| |
| //Zillions of persistant Paint objecs for drawing the View |
| |
| private Paint mSwatchOld, mSwatchNew; |
| |
| //NEW_METHOD_WORK_NEEDED_HERE |
| //Add Paints to represent the palettes of the new method's UI controllers |
| private Paint mOvalHueSat; |
| |
| private Bitmap mVerSliderBM; |
| private Canvas mVerSliderCv; |
| |
| private Bitmap[] mHorSlidersBM = new Bitmap[3]; |
| private Canvas[] mHorSlidersCv = new Canvas[3]; |
| |
| private Paint mValDimmer; |
| |
| //NEW_METHOD_WORK_NEEDED_HERE |
| //Add Paints to represent the icon for the new method |
| private Paint mOvalHueSatSmall; |
| |
| private Paint mPosMarker; |
| private Paint mText; |
| |
| private Rect mOldSwatchRect = new Rect(); |
| private Rect mNewSwatchRect = new Rect(); |
| private Rect mPaletteRect = new Rect(); |
| private Rect mVerSliderRect = new Rect(); |
| |
| private int[] mSpectrumColorsRev; |
| private int mOriginalColor = 0; //The color passed in at the beginning, which can be reverted to at any time by tapping the old swatch. |
| private float[] mHSV = new float[3]; |
| private int[] mRGB = new int[3]; |
| private float[] mYUV = new float[3]; |
| private String mHexStr = ""; |
| private boolean mHSVenabled = true; //Only true if an HSV method is enabled |
| private boolean mRGBenabled = true; //Only true if an RGB method is enabled |
| private boolean mYUVenabled = true; //Only true if a YUV method is enabled |
| private boolean mHexenabled = true; //Only true if an RGB method is enabled |
| private int[] mCoord = new int[3]; //For drawing slider/palette markers |
| private int mFocusedControl = -1; //Which control receives trackball events. |
| private OnColorChangedListener mListener; |
| |
| /** |
| * Ctor. |
| * @param c |
| * @param l |
| * @param width Used to determine orientation and adjust layout accordingly |
| * @param height Used to determine orientation and adjust layout accordingly |
| * @param color The initial color |
| * @throws Exception |
| */ |
| ColorPickerView(Context c, OnColorChangedListener l, int width, int height, int color) |
| throws Exception { |
| super(c); |
| |
| //We need to make the dialog focusable to retrieve trackball events. |
| setFocusable(true); |
| |
| mListener = l; |
| |
| mOriginalColor = color; |
| |
| Color.colorToHSV(color, mHSV); |
| |
| updateAllFromHSV(); |
| |
| //Setup the layout based on whether this is a portrait or landscape orientation. |
| if (width <= height) { //Portrait layout |
| SWATCH_WIDTH = (PALETTE_DIM + SLIDER_THICKNESS) / 2; |
| |
| PALETTE_POS_X = 0; |
| PALETTE_POS_Y = TEXT_SIZE * 4 + SWATCH_HEIGHT; |
| |
| //Set more rects, lots of rects |
| mOldSwatchRect.set(0, TEXT_SIZE * 4, SWATCH_WIDTH, TEXT_SIZE * 4 + SWATCH_HEIGHT); |
| mNewSwatchRect.set(SWATCH_WIDTH, TEXT_SIZE * 4, SWATCH_WIDTH * 2, TEXT_SIZE * 4 + SWATCH_HEIGHT); |
| mPaletteRect.set(0, PALETTE_POS_Y, PALETTE_DIM, PALETTE_POS_Y + PALETTE_DIM); |
| mVerSliderRect.set(PALETTE_DIM, PALETTE_POS_Y, PALETTE_DIM + SLIDER_THICKNESS, PALETTE_POS_Y + PALETTE_DIM); |
| |
| TEXT_HSV_POS[0] = 3; |
| TEXT_HSV_POS[1] = 0; |
| TEXT_RGB_POS[0] = TEXT_HSV_POS[0] + 50; |
| TEXT_RGB_POS[1] = TEXT_HSV_POS[1]; |
| TEXT_YUV_POS[0] = TEXT_HSV_POS[0] + 100; |
| TEXT_YUV_POS[1] = TEXT_HSV_POS[1]; |
| TEXT_HEX_POS[0] = TEXT_HSV_POS[0] + 150; |
| TEXT_HEX_POS[1] = TEXT_HSV_POS[1]; |
| |
| VIEW_DIM_X = PALETTE_DIM + SLIDER_THICKNESS; |
| VIEW_DIM_Y = SWATCH_HEIGHT + PALETTE_DIM + TEXT_SIZE * 4; |
| } |
| else { //Landscape layout |
| SWATCH_WIDTH = 110; |
| |
| PALETTE_POS_X = SWATCH_WIDTH; |
| PALETTE_POS_Y = 0; |
| |
| //Set more rects, lots of rects |
| mOldSwatchRect.set(0, TEXT_SIZE * 7, SWATCH_WIDTH, TEXT_SIZE * 7 + SWATCH_HEIGHT); |
| mNewSwatchRect.set(0, TEXT_SIZE * 7 + SWATCH_HEIGHT, SWATCH_WIDTH, TEXT_SIZE * 7 + SWATCH_HEIGHT * 2); |
| mPaletteRect.set(SWATCH_WIDTH, PALETTE_POS_Y, SWATCH_WIDTH + PALETTE_DIM, PALETTE_POS_Y + PALETTE_DIM); |
| mVerSliderRect.set(SWATCH_WIDTH + PALETTE_DIM, PALETTE_POS_Y, SWATCH_WIDTH + PALETTE_DIM + SLIDER_THICKNESS, PALETTE_POS_Y + PALETTE_DIM); |
| |
| TEXT_HSV_POS[0] = 3; |
| TEXT_HSV_POS[1] = 0; |
| TEXT_RGB_POS[0] = TEXT_HSV_POS[0]; |
| TEXT_RGB_POS[1] = (int)(TEXT_HSV_POS[1] + TEXT_SIZE * 3.5); |
| TEXT_YUV_POS[0] = TEXT_HSV_POS[0] + 50; |
| TEXT_YUV_POS[1] = (int)(TEXT_HSV_POS[1] + TEXT_SIZE * 3.5); |
| TEXT_HEX_POS[0] = TEXT_HSV_POS[0] + 50; |
| TEXT_HEX_POS[1] = TEXT_HSV_POS[1]; |
| |
| VIEW_DIM_X = PALETTE_POS_X + PALETTE_DIM + SLIDER_THICKNESS; |
| VIEW_DIM_Y = Math.max(mNewSwatchRect.bottom, PALETTE_DIM); |
| } |
| |
| //Rainbows make everybody happy! |
| mSpectrumColorsRev = new int[] { |
| 0xFFFF0000, 0xFFFF00FF, 0xFF0000FF, 0xFF00FFFF, |
| 0xFF00FF00, 0xFFFFFF00, 0xFFFF0000, |
| }; |
| |
| //Setup all the Paint and Shader objects. There are lots of them! |
| |
| //NEW_METHOD_WORK_NEEDED_HERE |
| //Add Paints to represent the palettes of the new method's UI controllers |
| |
| mSwatchOld = new Paint(Paint.ANTI_ALIAS_FLAG); |
| mSwatchOld.setStyle(Paint.Style.FILL); |
| mSwatchOld.setColor(Color.HSVToColor(mHSV)); |
| |
| mSwatchNew = new Paint(Paint.ANTI_ALIAS_FLAG); |
| mSwatchNew.setStyle(Paint.Style.FILL); |
| mSwatchNew.setColor(Color.HSVToColor(mHSV)); |
| |
| Shader shaderA = new SweepGradient(0, 0, mSpectrumColorsRev, null); |
| Shader shaderB = new RadialGradient(0, 0, PALETTE_CENTER_X, 0xFFFFFFFF, 0xFF000000, Shader.TileMode.CLAMP); |
| Shader shader = new ComposeShader(shaderA, shaderB, PorterDuff.Mode.SCREEN); |
| mOvalHueSat = new Paint(Paint.ANTI_ALIAS_FLAG); |
| mOvalHueSat.setShader(shader); |
| mOvalHueSat.setStyle(Paint.Style.FILL); |
| mOvalHueSat.setDither(true); |
| |
| mVerSliderBM = Bitmap.createBitmap(SLIDER_THICKNESS, PALETTE_DIM, Bitmap.Config.RGB_565); |
| mVerSliderCv = new Canvas(mVerSliderBM); |
| |
| for (int i = 0; i < 3; i++) { |
| mHorSlidersBM[i] = Bitmap.createBitmap(PALETTE_DIM, SLIDER_THICKNESS, Bitmap.Config.RGB_565); |
| mHorSlidersCv[i] = new Canvas(mHorSlidersBM[i]); |
| } |
| |
| mValDimmer = new Paint(Paint.ANTI_ALIAS_FLAG); |
| mValDimmer.setStyle(Paint.Style.FILL); |
| mValDimmer.setDither(true); |
| mValDimmer.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)); |
| |
| //Whew, we're done making the big Paints and Shaders for the swatches, palettes, and sliders. |
| //Now we need to make the Paints and Shaders that will draw the little method icons in the method selector list. |
| |
| //NEW_METHOD_WORK_NEEDED_HERE |
| //Add Paints to represent the icon for the new method |
| |
| shaderA = new SweepGradient(0, 0, mSpectrumColorsRev, null); |
| shaderB = new RadialGradient(0, 0, PALETTE_DIM / 2, 0xFFFFFFFF, 0xFF000000, Shader.TileMode.CLAMP); |
| shader = new ComposeShader(shaderA, shaderB, PorterDuff.Mode.SCREEN); |
| mOvalHueSatSmall = new Paint(Paint.ANTI_ALIAS_FLAG); |
| mOvalHueSatSmall.setShader(shader); |
| mOvalHueSatSmall.setStyle(Paint.Style.FILL); |
| |
| //Make a simple stroking Paint for drawing markers and borders and stuff like that. |
| mPosMarker = new Paint(Paint.ANTI_ALIAS_FLAG); |
| mPosMarker.setStyle(Paint.Style.STROKE); |
| mPosMarker.setStrokeWidth(2); |
| |
| //Make a basic text Paint. |
| mText = new Paint(Paint.ANTI_ALIAS_FLAG); |
| mText.setTextSize(TEXT_SIZE); |
| mText.setColor(Color.WHITE); |
| |
| //Kickstart |
| initUI(); |
| } |
| |
| /** |
| * Draw the entire view (the entire dialog). |
| */ |
| @Override |
| protected void onDraw(Canvas canvas) { |
| //Draw the old and new swatches |
| drawSwatches(canvas); |
| |
| //Write the text |
| writeColorParams(canvas); |
| |
| //Draw the palette and sliders (the UI) |
| if (mMethod == METHOD_HS_V_PALETTE) |
| drawHSV1Palette(canvas); |
| } |
| |
| /** |
| * Draw the old and new swatches. |
| * @param canvas |
| */ |
| private void drawSwatches(Canvas canvas) { |
| float[] hsv = new float[3]; |
| |
| mText.setTextSize(16); |
| |
| //Draw the original swatch |
| canvas.drawRect(mOldSwatchRect, mSwatchOld); |
| Color.colorToHSV(mOriginalColor, hsv); |
| //if (UberColorPickerDialog.isGray(mColor)) //Don't need this right here, but imp't to note |
| // hsv[1] = 0; |
| if (hsv[2] > .5) |
| mText.setColor(Color.BLACK); |
| canvas.drawText("Revert", mOldSwatchRect.left + SWATCH_WIDTH / 2 - mText.measureText("Revert") / 2, mOldSwatchRect.top + 16, mText); |
| mText.setColor(Color.WHITE); |
| |
| //Draw the new swatch |
| canvas.drawRect(mNewSwatchRect, mSwatchNew); |
| if (mHSV[2] > .5) |
| mText.setColor(Color.BLACK); |
| canvas.drawText("Accept", mNewSwatchRect.left + SWATCH_WIDTH / 2 - mText.measureText("Accept") / 2, mNewSwatchRect.top + 16, mText); |
| mText.setColor(Color.WHITE); |
| |
| mText.setTextSize(TEXT_SIZE); |
| } |
| |
| /** |
| * Write the color parametes (HSV, RGB, YUV, Hex, etc.). |
| * @param canvas |
| */ |
| private void writeColorParams(Canvas canvas) { |
| if (mHSVenabled) { |
| canvas.drawText("H: " + Integer.toString((int)(mHSV[0] / 360.0f * 255)), TEXT_HSV_POS[0], TEXT_HSV_POS[1] + TEXT_SIZE, mText); |
| canvas.drawText("S: " + Integer.toString((int)(mHSV[1] * 255)), TEXT_HSV_POS[0], TEXT_HSV_POS[1] + TEXT_SIZE * 2, mText); |
| canvas.drawText("V: " + Integer.toString((int)(mHSV[2] * 255)), TEXT_HSV_POS[0], TEXT_HSV_POS[1] + TEXT_SIZE * 3, mText); |
| } |
| |
| if (mRGBenabled) { |
| canvas.drawText("R: " + mRGB[0], TEXT_RGB_POS[0], TEXT_RGB_POS[1] + TEXT_SIZE, mText); |
| canvas.drawText("G: " + mRGB[1], TEXT_RGB_POS[0], TEXT_RGB_POS[1] + TEXT_SIZE * 2, mText); |
| canvas.drawText("B: " + mRGB[2], TEXT_RGB_POS[0], TEXT_RGB_POS[1] + TEXT_SIZE * 3, mText); |
| } |
| |
| if (mYUVenabled) { |
| canvas.drawText("Y: " + Integer.toString((int)(mYUV[0] * 255)), TEXT_YUV_POS[0], TEXT_YUV_POS[1] + TEXT_SIZE, mText); |
| canvas.drawText("U: " + Integer.toString((int)((mYUV[1] + .5f) * 255)), TEXT_YUV_POS[0], TEXT_YUV_POS[1] + TEXT_SIZE * 2, mText); |
| canvas.drawText("V: " + Integer.toString((int)((mYUV[2] + .5f) * 255)), TEXT_YUV_POS[0], TEXT_YUV_POS[1] + TEXT_SIZE * 3, mText); |
| } |
| |
| if (mHexenabled) |
| canvas.drawText("#" + mHexStr, TEXT_HEX_POS[0], TEXT_HEX_POS[1] + TEXT_SIZE, mText); |
| } |
| |
| /** |
| * Place a small circle on the 2D palette to indicate the current values. |
| * @param canvas |
| * @param markerPosX |
| * @param markerPosY |
| */ |
| private void mark2DPalette(Canvas canvas, int markerPosX, int markerPosY) { |
| mPosMarker.setColor(Color.BLACK); |
| canvas.drawOval(new RectF(markerPosX - 5, markerPosY - 5, markerPosX + 5, markerPosY + 5), mPosMarker); |
| mPosMarker.setColor(Color.WHITE); |
| canvas.drawOval(new RectF(markerPosX - 3, markerPosY - 3, markerPosX + 3, markerPosY + 3), mPosMarker); |
| } |
| |
| /** |
| * Draw a line across the slider to indicate its current value. |
| * @param canvas |
| * @param markerPos |
| */ |
| private void markVerSlider(Canvas canvas, int markerPos) { |
| mPosMarker.setColor(Color.BLACK); |
| canvas.drawRect(new Rect(0, markerPos - 2, SLIDER_THICKNESS, markerPos + 3), mPosMarker); |
| mPosMarker.setColor(Color.WHITE); |
| canvas.drawRect(new Rect(0, markerPos, SLIDER_THICKNESS, markerPos + 1), mPosMarker); |
| } |
| |
| /** |
| * Frame the slider to indicate that it has trackball focus. |
| * @param canvas |
| */ |
| private void hilightFocusedVerSlider(Canvas canvas) { |
| mPosMarker.setColor(Color.WHITE); |
| canvas.drawRect(new Rect(0, 0, SLIDER_THICKNESS, PALETTE_DIM), mPosMarker); |
| mPosMarker.setColor(Color.BLACK); |
| canvas.drawRect(new Rect(2, 2, SLIDER_THICKNESS - 2, PALETTE_DIM - 2), mPosMarker); |
| } |
| |
| /** |
| * Frame the 2D palette to indicate that it has trackball focus. |
| * @param canvas |
| */ |
| private void hilightFocusedOvalPalette(Canvas canvas) { |
| mPosMarker.setColor(Color.WHITE); |
| canvas.drawOval(new RectF(-PALETTE_RADIUS, -PALETTE_RADIUS, PALETTE_RADIUS, PALETTE_RADIUS), mPosMarker); |
| mPosMarker.setColor(Color.BLACK); |
| canvas.drawOval(new RectF(-PALETTE_RADIUS + 2, -PALETTE_RADIUS + 2, PALETTE_RADIUS - 2, PALETTE_RADIUS - 2), mPosMarker); |
| } |
| |
| //NEW_METHOD_WORK_NEEDED_HERE |
| //To add a new method, replicate the basic draw functions here. Use the 2D palette or 1D sliders as templates for the new method. |
| /** |
| * Draw the UI for HSV with angular H and radial S combined in 2D and a 1D V slider. |
| * @param canvas |
| */ |
| private void drawHSV1Palette(Canvas canvas) { |
| canvas.save(); |
| |
| canvas.translate(PALETTE_POS_X, PALETTE_POS_Y); |
| |
| //Draw the 2D palette |
| canvas.translate(PALETTE_CENTER_X, PALETTE_CENTER_Y); |
| canvas.drawOval(new RectF(-PALETTE_RADIUS, -PALETTE_RADIUS, PALETTE_RADIUS, PALETTE_RADIUS), mOvalHueSat); |
| canvas.drawOval(new RectF(-PALETTE_RADIUS, -PALETTE_RADIUS, PALETTE_RADIUS, PALETTE_RADIUS), mValDimmer); |
| if (mFocusedControl == 0) |
| hilightFocusedOvalPalette(canvas); |
| mark2DPalette(canvas, mCoord[0], mCoord[1]); |
| canvas.translate(-PALETTE_CENTER_X, -PALETTE_CENTER_Y); |
| |
| //Draw the 1D slider |
| canvas.translate(PALETTE_DIM, 0); |
| canvas.drawBitmap(mVerSliderBM, 0, 0, null); |
| if (mFocusedControl == 1) |
| hilightFocusedVerSlider(canvas); |
| markVerSlider(canvas, mCoord[2]); |
| |
| canvas.restore(); |
| } |
| |
| /** |
| * Initialize the current color chooser's UI (set its color parameters and set its palette and slider values accordingly). |
| */ |
| private void initUI() { |
| initHSV1Palette(); |
| |
| //Focus on the first controller (arbitrary). |
| mFocusedControl = 0; |
| } |
| |
| //NEW_METHOD_WORK_NEEDED_HERE |
| //To add a new method, replicate and extend the last init function shown below |
| /** |
| * Initialize a color chooser. |
| */ |
| private void initHSV1Palette() { |
| setOvalValDimmer(); |
| setVerValSlider(); |
| |
| float angle = 2*PI - mHSV[0] / (180 / 3.1415927f); |
| float radius = mHSV[1] * PALETTE_RADIUS; |
| mCoord[0] = (int)(Math.cos(angle) * radius); |
| mCoord[1] = (int)(Math.sin(angle) * radius); |
| |
| mCoord[2] = PALETTE_DIM - (int)(mHSV[2] * PALETTE_DIM); |
| } |
| |
| //NEW_METHOD_WORK_NEEDED_HERE |
| //To add a new method, replicate and extend the set functions below, one per UI controller in the new method |
| /** |
| * Adjust a Paint which, when painted, dims its underlying object to show the effects of varying value (brightness). |
| */ |
| private void setOvalValDimmer() { |
| float[] hsv = new float[3]; |
| hsv[0] = mHSV[0]; |
| hsv[1] = 0; |
| hsv[2] = mHSV[2]; |
| int gray = Color.HSVToColor(hsv); |
| mValDimmer.setColor(gray); |
| } |
| |
| /** |
| * Create a linear gradient shader to show variations in value. |
| */ |
| private void setVerValSlider() { |
| float[] hsv = new float[3]; |
| hsv[0] = mHSV[0]; |
| hsv[1] = mHSV[1]; |
| hsv[2] = 1; |
| int col = Color.HSVToColor(hsv); |
| |
| int colors[] = new int[2]; |
| colors[0] = col; |
| colors[1] = 0xFF000000; |
| GradientDrawable gradDraw = new GradientDrawable(Orientation.TOP_BOTTOM, colors); |
| gradDraw.setDither(true); |
| gradDraw.setLevel(10000); |
| gradDraw.setBounds(0, 0, SLIDER_THICKNESS, PALETTE_DIM); |
| gradDraw.draw(mVerSliderCv); |
| } |
| |
| /** |
| * Report the correct tightly bounded dimensions of the view. |
| */ |
| @Override |
| protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| setMeasuredDimension(VIEW_DIM_X, VIEW_DIM_Y); |
| } |
| |
| /** |
| * Wrap Math.round(). I'm not a Java expert. Is this the only way to avoid writing "(int)Math.round" everywhere? |
| * @param x |
| * @return |
| */ |
| private int round(double x) { |
| return (int)Math.round(x); |
| } |
| |
| /** |
| * Limit a value to the range [0,1]. |
| * @param n |
| * @return |
| */ |
| private float pinToUnit(float n) { |
| if (n < 0) { |
| n = 0; |
| } else if (n > 1) { |
| n = 1; |
| } |
| return n; |
| } |
| |
| /** |
| * Limit a value to the range [0,max]. |
| * @param n |
| * @param max |
| * @return |
| */ |
| private float pin(float n, float max) { |
| if (n < 0) { |
| n = 0; |
| } else if (n > max) { |
| n = max; |
| } |
| return n; |
| } |
| |
| /** |
| * Limit a value to the range [min,max]. |
| * @param n |
| * @param min |
| * @param max |
| * @return |
| */ |
| private float pin(float n, float min, float max) { |
| if (n < min) { |
| n = min; |
| } else if (n > max) { |
| n = max; |
| } |
| return n; |
| } |
| |
| /** |
| * No clue what this does (some sort of average/mean I presume). It came with the original UberColorPickerDialog |
| * 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. |
| * @param s |
| * @param d |
| * @param p |
| * @return |
| */ |
| private int ave(int s, int d, float p) { |
| return s + round(p * (d - s)); |
| } |
| |
| /** |
| * Came with the original UberColorPickerDialog in the API Demos, wasn't documented. I believe it takes an array of |
| * colors and a value in the range [0,1] and interpolates a resulting color in a seemingly predictable manner. |
| * I haven't looked at it at all. |
| * @param colors |
| * @param unit |
| * @return |
| */ |
| private int interpColor(int colors[], float unit) { |
| if (unit <= 0) { |
| return colors[0]; |
| } |
| if (unit >= 1) { |
| return colors[colors.length - 1]; |
| } |
| |
| float p = unit * (colors.length - 1); |
| int i = (int)p; |
| p -= i; |
| |
| // now p is just the fractional part [0...1) and i is the index |
| int c0 = colors[i]; |
| int c1 = colors[i+1]; |
| int a = ave(Color.alpha(c0), Color.alpha(c1), p); |
| int r = ave(Color.red(c0), Color.red(c1), p); |
| int g = ave(Color.green(c0), Color.green(c1), p); |
| int b = ave(Color.blue(c0), Color.blue(c1), p); |
| |
| return Color.argb(a, r, g, b); |
| } |
| |
| /** |
| * A standard point-in-rect routine. |
| * @param x |
| * @param y |
| * @param r |
| * @return true if point x,y is in rect r |
| */ |
| public boolean ptInRect(int x, int y, Rect r) { |
| return x > r.left && x < r.right && y > r.top && y < r.bottom; |
| } |
| |
| /** |
| * Process trackball events. Used mainly for fine-tuned color adjustment, or alternatively to switch between slider controls. |
| */ |
| @Override |
| public boolean dispatchTrackballEvent(MotionEvent event) { |
| float x = event.getX(); |
| float y = event.getY(); |
| |
| //A longer event history implies faster trackball movement. |
| //Use it to infer a larger jump and therefore faster palette/slider adjustment. |
| int jump = event.getHistorySize() + 1; |
| |
| switch (event.getAction()) { |
| case MotionEvent.ACTION_DOWN: { |
| } |
| break; |
| case MotionEvent.ACTION_MOVE: { |
| //NEW_METHOD_WORK_NEEDED_HERE |
| //To add a new method, replicate and extend the appropriate entry in this list, |
| //depending on whether you use 1D or 2D controllers |
| switch (mMethod) { |
| case METHOD_HS_V_PALETTE: |
| if (mFocusedControl == 0) { |
| changeHSPalette(x, y, jump); |
| } |
| else if (mFocusedControl == 1) { |
| if (y < 0) |
| changeSlider(mFocusedControl, true, jump); |
| else if (y > 0) |
| changeSlider(mFocusedControl, false, jump); |
| } |
| break; |
| } |
| } |
| break; |
| case MotionEvent.ACTION_UP: { |
| } |
| break; |
| } |
| |
| return true; |
| } |
| |
| //NEW_METHOD_WORK_NEEDED_HERE |
| //To add a new method, replicate and extend the appropriate functions below, |
| //one per UI controller in the new method |
| /** |
| * Effect a trackball change to a 2D palette. |
| * @param x -1: negative x change, 0: no x change, +1: positive x change. |
| * @param y -1: negative y change, 0, no y change, +1: positive y change. |
| * @param jump the amount by which to change. |
| */ |
| private void changeHSPalette(float x, float y, int jump) { |
| int x2 = 0, y2 = 0; |
| if (x < 0) |
| x2 = -jump; |
| else if (x > 0) |
| x2 = jump; |
| if (y < 0) |
| y2 = -jump; |
| else if (y > 0) |
| y2 = jump; |
| |
| mCoord[0] += x2; |
| mCoord[1] += y2; |
| |
| if (mCoord[0] < -PALETTE_RADIUS) |
| mCoord[0] = -PALETTE_RADIUS; |
| else if (mCoord[0] > PALETTE_RADIUS) |
| mCoord[0] = PALETTE_RADIUS; |
| if (mCoord[1] < -PALETTE_RADIUS) |
| mCoord[1] = -PALETTE_RADIUS; |
| else if (mCoord[1] > PALETTE_RADIUS) |
| mCoord[1] = PALETTE_RADIUS; |
| |
| float radius = (float)java.lang.Math.sqrt(mCoord[0] * mCoord[0] + mCoord[1] * mCoord[1]); |
| if (radius > PALETTE_RADIUS) |
| radius = PALETTE_RADIUS; |
| |
| float angle = (float)java.lang.Math.atan2(mCoord[1], mCoord[0]); |
| // need to turn angle [-PI ... PI] into unit [0....1] |
| float unit = angle/(2*PI); |
| if (unit < 0) { |
| unit += 1; |
| } |
| |
| mCoord[0] = round(Math.cos(angle) * radius); |
| mCoord[1] = round(Math.sin(angle) * radius); |
| |
| int c = interpColor(mSpectrumColorsRev, unit); |
| float[] hsv = new float[3]; |
| Color.colorToHSV(c, hsv); |
| mHSV[0] = hsv[0]; |
| mHSV[1] = radius / PALETTE_RADIUS; |
| updateAllFromHSV(); |
| mSwatchNew.setColor(Color.HSVToColor(mHSV)); |
| |
| setVerValSlider(); |
| |
| invalidate(); |
| } |
| |
| /** |
| * Effect a trackball change to a 1D slider. |
| * @param slider id of the slider to be effected |
| * @param increase true if the change is an increase, false if a decrease |
| * @param jump the amount by which to change in units of the range [0,255] |
| */ |
| private void changeSlider(int slider, boolean increase, int jump) { |
| //NEW_METHOD_WORK_NEEDED_HERE |
| //It is only necessary to add an entry here for a new method if the new method uses a 1D slider. |
| //Note, some sliders are horizontal and others are vertical. |
| //They differ a bit, especially in a sign flip on the vertical axis. |
| if (mMethod == METHOD_HS_V_PALETTE) { |
| //slider *must* equal 1 |
| |
| mHSV[2] += (increase ? jump : -jump) / 256.0f; |
| mHSV[2] = pinToUnit(mHSV[2]); |
| updateAllFromHSV(); |
| mCoord[2] = PALETTE_DIM - (int)(mHSV[2] * PALETTE_DIM); |
| |
| mSwatchNew.setColor(Color.HSVToColor(mHSV)); |
| |
| setOvalValDimmer(); |
| |
| invalidate(); |
| } |
| } |
| |
| /** |
| * Keep all colorspace representations in sync. |
| */ |
| private void updateRGBfromHSV() { |
| int color = Color.HSVToColor(mHSV); |
| mRGB[0] = Color.red(color); |
| mRGB[1] = Color.green(color); |
| mRGB[2] = Color.blue(color); |
| } |
| |
| /** |
| * Keep all colorspace representations in sync. |
| */ |
| private void updateYUVfromRGB() { |
| float r = mRGB[0] / 255.0f; |
| float g = mRGB[1] / 255.0f; |
| float b = mRGB[2] / 255.0f; |
| |
| ColorMatrix cm = new ColorMatrix(); |
| cm.setRGB2YUV(); |
| final float[] a = cm.getArray(); |
| |
| mYUV[0] = a[0] * r + a[1] * g + a[2] * b; |
| mYUV[0] = pinToUnit(mYUV[0]); |
| mYUV[1] = a[5] * r + a[6] * g + a[7] * b; |
| mYUV[1] = pin(mYUV[1], -.5f, .5f); |
| mYUV[2] = a[10] * r + a[11] * g + a[12] * b; |
| mYUV[2] = pin(mYUV[2], -.5f, .5f); |
| } |
| |
| /** |
| * Keep all colorspace representations in sync. |
| */ |
| private void updateHexFromHSV() { |
| //For now, assume 100% opacity |
| mHexStr = Integer.toHexString(Color.HSVToColor(mHSV)).toUpperCase(); |
| mHexStr = mHexStr.substring(2, mHexStr.length()); |
| } |
| |
| /** |
| * Keep all colorspace representations in sync. |
| */ |
| private void updateAllFromHSV() { |
| //Update mRGB |
| if (mRGBenabled || mYUVenabled) |
| updateRGBfromHSV(); |
| |
| //Update mYUV |
| if (mYUVenabled) |
| updateYUVfromRGB(); |
| |
| //Update mHexStr |
| if (mRGBenabled) |
| updateHexFromHSV(); |
| } |
| |
| /** |
| * Process touch events: down, move, and up |
| */ |
| @Override |
| public boolean onTouchEvent(MotionEvent event) { |
| float x = event.getX(); |
| float y = event.getY(); |
| |
| //Generate coordinates which are palette=local with the origin at the upper left of the main 2D palette |
| int y2 = (int)(pin(round(y - PALETTE_POS_Y), PALETTE_DIM)); |
| |
| //Generate coordinates which are palette-local with the origin at the center of the main 2D palette |
| float circlePinnedX = x - PALETTE_POS_X - PALETTE_CENTER_X; |
| float circlePinnedY = y - PALETTE_POS_Y - PALETTE_CENTER_Y; |
| |
| //Is the event in a swatch? |
| boolean inSwatchOld = ptInRect(round(x), round(y), mOldSwatchRect); |
| boolean inSwatchNew = ptInRect(round(x), round(y), mNewSwatchRect); |
| |
| //Get the event's distance from the center of the main 2D palette |
| float radius = (float)java.lang.Math.sqrt(circlePinnedX * circlePinnedX + circlePinnedY * circlePinnedY); |
| |
| //Is the event in a circle-pinned 2D palette? |
| boolean inOvalPalette = radius <= PALETTE_RADIUS; |
| |
| //Pin the radius |
| if (radius > PALETTE_RADIUS) |
| radius = PALETTE_RADIUS; |
| |
| //Is the event in a vertical slider to the right of the main 2D palette |
| boolean inVerSlider = ptInRect(round(x), round(y), mVerSliderRect); |
| |
| switch (event.getAction()) { |
| case MotionEvent.ACTION_DOWN: |
| mTracking = TRACKED_NONE; |
| |
| if (inSwatchOld) |
| mTracking = TRACK_SWATCH_OLD; |
| else if (inSwatchNew) |
| mTracking = TRACK_SWATCH_NEW; |
| |
| //NEW_METHOD_WORK_NEEDED_HERE |
| //To add a new method, replicate and extend the last entry in this list |
| else if (mMethod == METHOD_HS_V_PALETTE) { |
| if (inOvalPalette) { |
| mTracking = TRACK_HS_PALETTE; |
| mFocusedControl = 0; |
| } |
| else if (inVerSlider) { |
| mTracking = TRACK_VER_VALUE_SLIDER; |
| mFocusedControl = 1; |
| } |
| } |
| case MotionEvent.ACTION_MOVE: |
| //NEW_METHOD_WORK_NEEDED_HERE |
| //To add a new method, replicate and extend the entries in this list, |
| //one per UI controller the new method requires. |
| if (mTracking == TRACK_HS_PALETTE) { |
| float angle = (float)java.lang.Math.atan2(circlePinnedY, circlePinnedX); |
| // need to turn angle [-PI ... PI] into unit [0....1] |
| float unit = angle/(2*PI); |
| if (unit < 0) { |
| unit += 1; |
| } |
| |
| mCoord[0] = round(Math.cos(angle) * radius); |
| mCoord[1] = round(Math.sin(angle) * radius); |
| |
| int c = interpColor(mSpectrumColorsRev, unit); |
| float[] hsv = new float[3]; |
| Color.colorToHSV(c, hsv); |
| mHSV[0] = hsv[0]; |
| mHSV[1] = radius / PALETTE_RADIUS; |
| updateAllFromHSV(); |
| mSwatchNew.setColor(Color.HSVToColor(mHSV)); |
| |
| setVerValSlider(); |
| |
| invalidate(); |
| } |
| else if (mTracking == TRACK_VER_VALUE_SLIDER) { |
| if (mCoord[2] != y2) { |
| mCoord[2] = y2; |
| float value = 1.0f - (float)y2 / (float)PALETTE_DIM; |
| |
| mHSV[2] = value; |
| updateAllFromHSV(); |
| mSwatchNew.setColor(Color.HSVToColor(mHSV)); |
| |
| setOvalValDimmer(); |
| |
| invalidate(); |
| } |
| } |
| break; |
| case MotionEvent.ACTION_UP: |
| //NEW_METHOD_WORK_NEEDED_HERE |
| //To add a new method, replicate and extend the last entry in this list. |
| if (mTracking == TRACK_SWATCH_OLD && inSwatchOld) { |
| Color.colorToHSV(mOriginalColor, mHSV); |
| mSwatchNew.setColor(mOriginalColor); |
| initUI(); |
| invalidate(); |
| } |
| else if (mTracking == TRACK_SWATCH_NEW && inSwatchNew) { |
| mListener.colorChanged(mSwatchNew.getColor()); |
| invalidate(); |
| } |
| |
| mTracking= TRACKED_NONE; |
| break; |
| } |
| |
| return true; |
| } |
| } |
| } |