/*
* This file is part of Oekaki Mobile.
* Copyright (C) 2013 Jeremy Lam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package idv.jlchntoz.oekakimobile;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.*;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Shader.TileMode;
import android.os.Build;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.*;
public class ColorPickerView extends View {
Bitmap _hsvColorWheel;
Paint _p, _ps;
int width, height, downMode, selectMode, selColor;
float radius, innerRadius, centerX, centerY, SVSize;
int SVX1, SVY1, SVX2, SVY2, paddingSize;
int[] _px;
float[] selHSV, selHSV1;
LinearGradient sh1, sh2;
ComposeShader sh3;
colorslider c_r, c_g, c_b;
colorslider[] sliders;
public ColorPickerView(Context c) {
super(c);
initialize();
}
public ColorPickerView(Context c, AttributeSet a) {
super(c, a);
initialize();
}
private void initialize() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
selHSV = new float[] { 0, 0, 1 };
selHSV1 = new float[] { 0, 1, 1 };
downMode = 0;
selectMode = 0;
_ps = new Paint();
_p = new Paint();
_ps.setStyle(Style.FILL);
_p.setStyle(Style.STROKE);
_p.setStrokeWidth(3);
_ps.setTypeface(Typeface.SANS_SERIF);
_ps.setTextAlign(Align.CENTER);
_ps.setTextSize(20);
selColor = Color.TRANSPARENT;
sliders = new colorslider[3];
sliders[0] = c_r = new colorslider();
sliders[1] = c_g = new colorslider();
sliders[2] = c_b = new colorslider();
paddingSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
getContext().getResources().getDimension(R.dimen.dialpgpadding),
getContext().getResources().getDisplayMetrics());
}
public int getColor() {
if (selectMode == 0)
selColor = Color.HSVToColor(selHSV);
return selColor;
}
public void setColor(int color) {
selColor = color;
Color.colorToHSV(color, selHSV);
selHSV1[0] = selHSV[0];
c_r.setValue(Color.red(color) / 255F);
c_g.setValue(Color.green(color) / 255F);
c_b.setValue(Color.blue(color) / 255F);
updateShaders();
}
@Override
protected void onSizeChanged(int w, int h, int ow, int oh) {
width = w;
height = h;
measuseValues(w, h);
invalidate();
}
@SuppressLint("WrongCall")
@Override
protected void onDraw(Canvas c) {
c.drawColor(Color.TRANSPARENT);
_ps.setShader(null);
_ps.setColor(selColor);
c.drawRect(width - paddingSize * 3, paddingSize, width - paddingSize,
paddingSize * 3, _ps);
if (selectMode == 1) {
_ps.setColor(Color.WHITE);
c.drawText("HSV", paddingSize * 2, paddingSize * 2, _ps);
for (colorslider s : sliders)
s.onDraw(c);
} else {
_ps.setColor(Color.WHITE);
c.drawText("RGB", paddingSize * 2, paddingSize * 2, _ps);
if (_hsvColorWheel != null)
c.drawBitmap(_hsvColorWheel, 0, 0, null);
_ps.setShader(sh3);
c.drawRect(SVX1, SVY1, SVX2, SVY2, _ps);
_p.setColor(fastCompleColor(Color.HSVToColor(selHSV1)));
float r = getRadian(selHSV[0]), cr = (float) Math.cos(r), sr = (float) Math
.sin(r);
c.drawLine(cr * radius + centerX, sr * radius + centerY, cr * innerRadius
+ centerX, sr * innerRadius + centerY, _p);
_p.setColor(fastCompleColor(getColor()));
c.drawCircle(SVX1 + selHSV[1] * SVSize, SVY1 + selHSV[2] * SVSize, 5, _p);
}
}
@Override
public boolean onTouchEvent(MotionEvent e) {
float ex = e.getX(), ey = e.getY(), d = getDistance(ex, ey, centerX,
centerY);
if (e.getAction() == MotionEvent.ACTION_DOWN && ex < paddingSize * 3
&& ey < paddingSize * 3) {
selectMode = (selectMode + 1) % 2;
invalidate();
return true;
} else if (selectMode == 1) {
boolean ret = false;
for (colorslider s : sliders)
if (s.onTouchEvent(e)) {
ret = true;
break;
}
updateShaders();
Color.colorToHSV(selColor, selHSV);
selHSV1[0] = selHSV[0]; // Sync the color to HSV mode.
return ret;
} else {
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
if (d < radius && d > innerRadius)
downMode = 1;
else if (isIn(SVX1, SVY1, SVX2, SVY2, ex, ey))
downMode = 2;
break;
case MotionEvent.ACTION_UP:
downMode = 0;
break;
}
switch (downMode) {
case 1:
selHSV1[0] = selHSV[0] = getAngle(ex - centerX, ey - centerY);
updateShaders();
break;
case 2:
selHSV[1] = getPercentage(SVX1, SVX2, ex);
selHSV[2] = getPercentage(SVY1, SVY2, ey);
invalidate();
break;
}
selColor = Color.HSVToColor(selHSV); // Sync the color to RGB mode.
c_r.setValue(Color.red(selColor) / 255F);
c_g.setValue(Color.green(selColor) / 255F);
c_b.setValue(Color.blue(selColor) / 255F);
return downMode != 0;
}
}
@Override
protected void onMeasure(int w, int h) {
int widthSize = MeasureSpec.getSize(w);
int heightSize = MeasureSpec.getSize(h);
int height = Math.min(widthSize, heightSize);
int mh;
switch (MeasureSpec.getMode(h)) {
case MeasureSpec.EXACTLY:
mh = heightSize;
case MeasureSpec.AT_MOST:
mh = Math.min(height, heightSize);
break;
default:
mh = height;
break;
}
setMeasuredDimension(widthSize, mh);
}
private void measuseValues(int w, int h) {
width = w;
height = h;
radius = Math.min(w, h) / 2 - paddingSize;
innerRadius = radius * 0.8F;
centerX = w / 2;
centerY = h / 2;
float d = innerRadius * (float) Math.sqrt(2) / 2;
SVX1 = Math.round(centerX - d);
SVY1 = Math.round(centerY - d);
SVX2 = Math.round(centerX + d);
SVY2 = Math.round(centerY + d);
SVSize = d * 2;
sh1 = new LinearGradient(SVX1, SVY1, SVX1, SVY2, Color.BLACK, Color.WHITE,
TileMode.CLAMP);
for (int i = 0; i < sliders.length; i++)
sliders[i].setPosition(paddingSize, paddingSize * 4 * (i + 1), w
- paddingSize * 2, paddingSize * 4);
updateShaders();
updateBitmap();
}
private void updateShaders() {
int _r = Math.round(c_r.getValue() * 255);
int _g = Math.round(c_g.getValue() * 255);
int _b = Math.round(c_b.getValue() * 255);
c_r.setColor(Color.argb(255, 0, _g, _b), Color.argb(255, 255, _g, _b));
c_g.setColor(Color.argb(255, _r, 0, _b), Color.argb(255, _r, 255, _b));
c_b.setColor(Color.argb(255, _r, _g, 0), Color.argb(255, _r, _g, 255));
if (selectMode == 1)
selColor = Color.argb(255, _r, _g, _b);
if (sh1 == null)
return;
sh2 = new LinearGradient(SVX1, SVY1, SVX2, SVY1, Color.WHITE,
Color.HSVToColor(selHSV1), TileMode.CLAMP);
sh3 = new ComposeShader(sh1, sh2, PorterDuff.Mode.MULTIPLY);
if (_hsvColorWheel == null)
updateBitmap();
invalidate();
}
private Bitmap updateBitmap() {
float[] hsv = new float[] { 0, 1, 1 };
if (width < 1 || height < 1)
return null;
boolean updateAll = _hsvColorWheel == null
|| width != _hsvColorWheel.getWidth()
|| height != _hsvColorWheel.getHeight();
if (updateAll) {
_hsvColorWheel = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
_px = new int[width * height];
int x1 = (int) (centerX - radius), x2 = (int) (centerX + radius);
int y1 = (int) (centerY - radius), y2 = (int) (centerY + radius);
float d = 0;
for (int y = y1; y < y2; y++)
for (int x = x1; x < x2; x++) {
d = getDistance(x, y, centerX, centerY);
if (d < radius && d > innerRadius) {
hsv[0] = getAngle(x - centerX, y - centerY);
_px[x + width * y] = Color.HSVToColor(hsv);
}
}
}
_hsvColorWheel.setPixels(_px, 0, width, 0, 0, width, height);
return _hsvColorWheel;
}
private float getDistance(float x1, float y1, float x2, float y2) {
return (float) Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
}
private float getAngle(float x, float y) {
return (float) (Math.atan2(y, x) / Math.PI * 180) + 180;
}
private float getRadian(float a) {
return (float) ((a - 180) / 180f * Math.PI);
}
private boolean isIn(float x1, float y1, float x2, float y2, float targetX,
float targetY) {
if (x1 > x2) {
float t = x2;
x2 = x1;
x1 = t;
}
if (y1 > y2) {
float t = y2;
y2 = y1;
y1 = t;
}
return targetX > x1 && targetX < x2 && targetY > y1 && targetY < y2;
}
private int mixValue(int v1, int v2, float p) {
return v1 + Math.round((v2 - v1) * p);
}
private float mixValue(float v1, float v2, float p) {
return v1 + (v2 - v1) * p;
}
private float getPercentage(float start, float end, float mid) {
return Math.max(0, Math.min(1, (mid - start) / (end - start)));
}
private int fastCompleColor(int color) {
return 0xFF << 24 | ~color;
}
private class colorslider {
float x1, y1, x2, y2, value;
boolean isDown;
int color1, color2;
Paint _p, _p2;
LinearGradient _lg;
public colorslider() {
value = 0;
isDown = false;
color1 = Color.TRANSPARENT;
color2 = Color.TRANSPARENT;
_p = new Paint();
_p2 = new Paint();
_p.setStyle(Style.STROKE);
_p.setStrokeWidth(3);
_p2.setStyle(Style.FILL);
setPosition(0, 0, 0, 0);
}
public void setPosition(float x, float y, float w, float h) {
x1 = x;
x2 = x + w;
y1 = y;
y2 = y + h;
if (_lg != null)
setColor(color1, color2);
}
public void setColor(int start, int end) {
color1 = start;
color2 = end;
_lg = new LinearGradient(x1, y1, x2, y1, start, end, TileMode.CLAMP);
_p2.setShader(_lg);
}
public int getSelectedColor() {
int _a = mixValue(Color.alpha(color1), Color.alpha(color2), value);
int _r = mixValue(Color.red(color1), Color.red(color2), value);
int _g = mixValue(Color.green(color1), Color.green(color2), value);
int _b = mixValue(Color.blue(color1), Color.blue(color2), value);
return Color.argb(_a, _r, _g, _b);
}
public void setValue(float value) {
this.value = value;
}
public float getValue() {
return value;
}
public void onDraw(Canvas c) {
c.drawRect(x1, y1, x2, y2, _p2);
float _x = mixValue(x1, x2, value);
_p.setColor(fastCompleColor(getSelectedColor()));
c.drawLine(_x, y1, _x, y2, _p);
}
public boolean onTouchEvent(MotionEvent e) {
float _x = e.getX(), _y = e.getY();
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
if (isIn(x1, y1, x2, y2, _x, _y))
isDown = true;
break;
case MotionEvent.ACTION_UP:
isDown = false;
break;
}
if (isDown)
value = getPercentage(x1, x2, _x);
return isDown;
}
}
}