App

- Main Page
- Related Pages
- Namespaces
- Classes
- Files

- File List
- File Members

CurvesView.java
Go to the documentation of this file.
1 package mhr.appandroid.views;
2
3 import java.util.ArrayList;
4
5 import mhr.appandroid.R;
6 import android.content.Context;
7 import android.graphics.Bitmap;
8 import android.graphics.BitmapFactory;
9 import android.graphics.Canvas;
10 import android.graphics.Paint;
11 import android.graphics.Paint.Style;
12 import android.graphics.Path;
13 import android.graphics.Rect;
14 import android.graphics.RectF;
15 import android.graphics.drawable.ShapeDrawable;
16 import android.graphics.drawable.shapes.PathShape;
17 import android.util.AttributeSet;
18 import android.view.GestureDetector;
19 import android.view.MotionEvent;
20 import android.view.View;
21 import android.view.ViewParent;
22
23 public class CurvesView extends View {
24 //===== INTERFACES, CLASSES, ENUMS ==========================================================================================================//
25 //----- NON-PUBLIC --------------------------------------------------------------------------------------------------------------------------//
26 protected class GestureListener extends GestureDetector.SimpleOnGestureListener {
27 @Override
28 public boolean onDown(MotionEvent e) {
29
30 int X = (int) e.getX();
31 int Y = (int) e.getY();
32
33 for (int i = 0; i < x.length; i++) {
34 cRect.offsetTo(xToGlob(x[i]) - cXCenter, yToGlob(y[i]) - cYCenter);
35 if (cRect.contains(X, Y)) {
36 selIndex = i;
37 invalidate();
38 if (listener != null) {
39 listener.onCurvesChangeStart(CurvesView.this);
40 }
41 return true;
42 }
43 }
44
45 int pos = addPoint(xToNorm(X), yToNorm(Y));
46 if (pos >= 0) {
47 selIndex = pos;
48 prepareCurve();
49 invalidate();
50 if (listener != null) {
51 listener.onCurvesChangeStart(CurvesView.this);
52 listener.onCurvesChange(CurvesView.this);
53 }
54 return true;
55 }
56 return true;
57 }
58
59 @Override
60 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
61 if (selIndex < 0) {
62 return true;
63 } else {
64 float tmpx = x[selIndex] - (distanceX / xLength);
65 float tmpy = y[selIndex] - (distanceY / yLength);
66 if (selIndex == 0) {
67 x[selIndex] = 0;
68 y[selIndex] = (tmpy < 0) ? 0 : (tmpy > 1) ? 1 : tmpy;
69 } else if (selIndex == x.length - 1) {
70 x[selIndex] = 1;
71 y[selIndex] = (tmpy < 0) ? 0 : (tmpy > 1) ? 1 : tmpy;
72 } else {
73 tmpx = (tmpx < 0) ? 0 : (tmpx > 1) ? 1 : tmpx;
74 x[selIndex] = (tmpx - x[selIndex - 1]) < minDelta ? x[selIndex - 1] + minDelta : (x[selIndex + 1] - tmpx) < minDelta ? x[selIndex + 1] - minDelta : tmpx;
75 y[selIndex] = (tmpy < 0) ? 0 : (tmpy > 1) ? 1 : tmpy;
76 }
77 prepareCurve();
78 invalidate();
79 if (listener != null) {
80 listener.onCurvesChange(CurvesView.this);
81 }
82 }
83
84 return true;
85 }
86
87 @Override
88 public boolean onDoubleTapEvent (MotionEvent e) {
89 int X = (int) e.getX();
90 int Y = (int) e.getY();
91
92 for (int i = 0; i < x.length; i++) {
93 cRect.offsetTo(xToGlob(x[i]) - cXCenter, yToGlob(y[i]) - cYCenter);
94 if (cRect.contains(X, Y)) {
95 selIndex = i;
96 if (removeSelPoint()) {
97 prepareCurve();
98 invalidate();
99 if (listener != null) {
100 listener.onCurvesChange(CurvesView.this);
101 listener.onCurvesChangeStop(CurvesView.this);
102 }
103 }
104 return true;
105 }
106 }
107 return true;
108 }
109 }
110
111 protected boolean removeSelPoint() {
112 if (selIndex < 1 || selIndex > x.length - 2) {
113 selIndex = -1;
114 return false;
115 }
116 int ptsCount = x.length;
117 float[] tx = new float[ptsCount - 1];
118 float[] ty = new float[ptsCount - 1];
119 int i = 0;
120 while (i < selIndex) {
121 tx[i] = x[i];
122 ty[i] = y[i];
123 i++;
124 }
125 i++;
126 while (i < x.length) {
127 tx[i - 1] = x[i];
128 ty[i - 1] = y[i];
129 i++;
130 }
131 x = tx;
132 y = ty;
133 y2 = new float[ptsCount - 1];
134 xc1 = new float[ptsCount - 2];
135 xc2 = new float[ptsCount - 2];
136 yc1 = new float[ptsCount - 2];
137 yc2 = new float[ptsCount - 2];
138 selIndex = -1;
139 return true;
140 }
141
142
143 protected int addPoint(float xx, float yy) {
144 int pos = -1;
145 if (xx < 0 || xx > 1 || yy < 0 || yy > 1) {
146 return pos;
147 }
148
149 for (int i = 1; i < x.length; i++) { // Nula nema cenu, mensi by znamenalo BUG
150 if (x[i] > xx) {
151 pos = i;
152 break;
153 }
154 }
155
156 if (pos < 0) {
157 return pos;
158 }
159
160 xx = (xx - x[pos - 1]) < minDelta ? x[pos - 1] + minDelta : (x[pos] - xx) < minDelta ? x[pos] - minDelta : xx; // Korekce pokud nahodou koliduje
161 if (((x[pos] - xx) >= minDelta) && ((xx - x[pos - 1]) >= minDelta)) { // Na novy bod je misto..
162 int ptsCount = x.length;
163 float[] tx = new float[ptsCount + 1];
164 float[] ty = new float[ptsCount + 1];
165 int i = 0;
166 while (i < pos) {
167 tx[i] = x[i];
168 ty[i] = y[i];
169 i++;
170 }
171 tx[i] = xx;
172 ty[i] = yy;
173 while (i < x.length) {
174 tx[i+1] = x[i];
175 ty[i+1] = y[i];
176 i++;
177 }
178 x = tx;
179 y = ty;
180 y2 = new float[ptsCount + 1];
181 xc1 = new float[ptsCount];
182 xc2 = new float[ptsCount];
183 yc1 = new float[ptsCount];
184 yc2 = new float[ptsCount];
185 }
186 return pos;
187 }
188
189
190 //----- PUBLIC ------------------------------------------------------------------------------------------------------------------------------//
191 public interface CurvesViewChangeListener {
192 public void onCurvesChange(CurvesView v);
193 public void onCurvesChangeStart(CurvesView v);
194 public void onCurvesChangeStop(CurvesView v);
195 }
196
197
198 //===== FIELDS ==============================================================================================================================//
199 //----- NON-PUBLIC --------------------------------------------------------------------------------------------------------------------------//
200 protected GestureDetector detector;
201
202 protected Bitmap cPressed;
203 protected Bitmap cNormal;
204
205 protected Rect boundingRect;
206 protected Rect curvesRect;
207 protected Rect cRect;
208
209 protected int xLength;
210 protected int yLength;
211
212 protected float[] x;
213 protected float[] xc1;
214 protected float[] xc2;
215
216 protected float[] y;
217 protected float[] y2;
218 protected float[] yc1;
219 protected float[] yc2;
220
221 protected int cWidth;
222 protected int cHeight;
223 protected int cXCenter;
224 protected int cYCenter;
225
226 protected int xOffset;
227 protected int yOffset;
228
229 protected int selIndex = -1;
230
231 protected Paint frame;
232 protected RectF marginRect;
233 protected Paint marginRectPaint;
234
235 protected CurvesViewChangeListener listener = null;
236
237 protected ShapeDrawable curve;
238
239 protected float minDelta = 0.001f; // ptz pokud bude bliz, numericka nestabilita atd...
240 //----- PUBLIC ------------------------------------------------------------------------------------------------------------------------------//
241
242 //===== CONSTRUCTORS, DESTRUCTORS, RELATED METHODS ==========================================================================================//
243 //----- NON-PUBLIC --------------------------------------------------------------------------------------------------------------------------//
244 protected void init() {
245 setLayerType(View.LAYER_TYPE_SOFTWARE, null);
246 cPressed = BitmapFactory.decodeResource(getResources(), R.drawable.scrubber_control_pressed_bw);
247 cNormal = BitmapFactory.decodeResource(getResources(), R.drawable.scrubber_control_normal_bw);
248 detector = new GestureDetector(getContext(), new GestureListener());
249
250 int ptsCount = 2;
251
252 x = new float[] {0.0f, 1.0f};
253 y = new float[] {1.0f, 0.0f};
254 y2 = new float[ptsCount];
255
256
257 xc1 = new float[ptsCount - 1];
258 xc2 = new float[ptsCount - 1];
259 yc1 = new float[ptsCount - 1];
260 yc2 = new float[ptsCount - 1];
261
262
263 cWidth = cNormal.getWidth();
264 cHeight = cNormal.getHeight();
265
266 cXCenter = cWidth / 2;
267 cYCenter = cHeight / 2;
268
269 xOffset = cXCenter;
270 yOffset = cYCenter;
271
272 cRect = new Rect(0, 0, cWidth, cHeight);
273
274 frame = new Paint();
275 frame.setColor(0xFFFFFFFF);
276 frame.setStyle(Style.STROKE);
277 frame.setStrokeWidth(2);
278 frame.setAntiAlias(true);
279
280 marginRectPaint = new Paint();
281 marginRectPaint.setColor(0xFF000000);
282 marginRectPaint.setStyle(Style.STROKE);
283
284 prepareCurve();
285 }
286 //----- PUBLIC ------------------------------------------------------------------------------------------------------------------------------//
287 public CurvesView(Context context) {
288 super(context);
289 init();
290 }
291
292 public CurvesView(Context context, AttributeSet attrs) {
293 super(context, attrs);
294 init();
295 }
296
297 public CurvesView(Context context, AttributeSet attrs, int defStyle) {
298 super(context, attrs, defStyle);
299 init();
300 }
301
302 //===== METHODS =============================================================================================================================//
303 //----- NON-PUBLIC --------------------------------------------------------------------------------------------------------------------------//
304 protected int xToGlob(float xVal) {
305 return (int) (xVal * xLength + xOffset);
306 }
307
308 protected int yToGlob(float yVal) {
309 return (int) (yVal * yLength + yOffset);
310 }
311
312 protected float xToNorm(float xVal) {
313 return (xVal - xOffset) / xLength;
314 }
315
316 protected float yToNorm(float yVal) {
317 return (yVal - yOffset) / yLength;
318 }
319
320 protected void prepareCurve() {
321 cpInit();
322
323 Path p = new Path();
324 for (int i = 0; i < x.length - 1; i++) {
325 p.moveTo(x[i], y[i]);
326 p.cubicTo(xc1[i], yc1[i], xc2[i], yc2[i], x[i+1], y[i+1]);
327 }
328 curve = new ShapeDrawable(new PathShape(p, 1, 1));
329 Paint paint = curve.getPaint();
330 paint.setStyle(Style.STROKE);
331 paint.setColor(0xFFFFFFFF);
332 paint.setAntiAlias(true);
333
334 }
335
336 // Vypocita hodnoty druhych derivaci v interpolovanych bodech tak, aby odpovidaly interpolaci kubickym splajnem, shodne s C++ knihovnou
337 protected void y2ValsInit() {
338 int i, k;
339 double p, qn, sig, un;
340 double[] u = new double[x.length];
341 int valsCount = x.length;
342 int lastVal = valsCount - 1;
343 y2[0] = 0.0f;
344 u[0] = 0.0;
345 for (i = 1; i < lastVal; i++) {
346 sig = (x[i] - x[i - 1]) / (x[i + 1] - x[i - 1]);
347 p = sig * y2[i - 1] + 2.0;
348 y2[i] =(float)((sig - 1.0) / p);
349 u[i] = (y[i + 1] - y[i]) / (x[i + 1] - x[i])
350 - (y[i] - y[i - 1]) / (x[i] - x[i - 1]);
351 u[i] = (6.0 * u[i] / (x[i + 1] - x[i - 1]) - sig * u[i - 1]) / p;
352 }
353 qn = un = 0.0;
354 y2[lastVal] =(float) ( (un - qn * u[valsCount - 2]) / (qn * y2[valsCount - 2] + 1.0));
355 for (k = valsCount - 2; k >= 0; k--) {
356 y2[k] = (float) (y2[k] * y2[k + 1] + u[k]);
357 }
358 }
359
365 protected void cpInit() {
366
367 y2ValsInit();
368
369 int n = x.length - 1;
370
371 double d0;
372 double d1;
373 double delta;
374
375 for (int i = 0; i < n; i++) {
376
377 d0 = (y[i+1] - y[i]) / (x[i+1] - x[i]) - 2.0/6.0 * (x[i+1] - x[i]) * y2[i] - 1.0/6.0 * (x[i+1] - x[i]) * y2[i+1];
378 d1 = (y[i+1] - y[i]) / (x[i+1] - x[i]) + 1.0/6.0 * (x[i+1] - x[i]) * y2[i] + 2.0/6.0 * (x[i+1] - x[i]) * y2[i+1];
379
380 delta = x[i + 1] - x[i];
381
382 yc1[i] = (float) (y[i] + d0 * (delta) / 3.0);
383 yc2[i] = (float) (y[i+1] - d1 * (delta) / 3.0);
384
385 xc1[i] = (float) (x[i] + delta / 3.0);
386 xc2[i] = (float) (x[i+1] - delta / 3.0);
387
388 }
389 }
390 //----- PUBLIC ------------------------------------------------------------------------------------------------------------------------------//
391 public void setCurvesChangeListener(CurvesViewChangeListener l) {
392 listener = l;
393 }
394
395 public float[][] getPoints() {
396 float[][] retVal = new float[2][x.length];
397
398 for (int i = 0; i < x.length; i++) {
399 retVal[0][i] = x[i];
400 retVal[1][i] = 1.0f - y[i];
401 }
402
403 return retVal;
404 }
405
406 public void setPoints(float[][] pts) {
407 int ptsCount = pts[0].length;
408 x = new float[ptsCount];
409 y = new float[ptsCount];
410 for (int i = 0; i < ptsCount; i++) {
411 x[i] = pts[0][i];
412 y[i] = 1 - pts[1][i];
413 }
414
415 y2 = new float[ptsCount];
416
417 xc1 = new float[ptsCount - 1];
418 xc2 = new float[ptsCount - 1];
419 yc1 = new float[ptsCount - 1];
420 yc2 = new float[ptsCount - 1];
421
422 prepareCurve();
423 invalidate();
424 }
425
426 //===== CALLBACKS ===========================================================================================================================//
427
428 @Override
429 public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
430 int width = MeasureSpec.getSize(widthMeasureSpec);
431 int height = MeasureSpec.getSize(heightMeasureSpec);
432 // Abychom získaly největší možný čtverec, je nutné nastavit řídící rozměr a ten co se má přizpůsobit na 0. Obrácený postup s hledáním minima nefunguje...
433 if (width < height) {
434 width = height;
435 } else {
436 height = width;
437 }
438 setMeasuredDimension(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
439 }
440
442 @Override
443 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
444 super.onSizeChanged(w, h, oldw, oldh);
445
446 xLength = w - 2 * xOffset;
447 yLength = h - 2 * yOffset;
448
449 boundingRect = new Rect(0, 0, w, h);
450 curvesRect = new Rect(xOffset, yOffset, w - xOffset, h - yOffset);
451
452 marginRect = new RectF(
453 xOffset / 2.0f,
454 yOffset / 2.0f,
455 w - xOffset / 2.0f,
456 h - yOffset / 2.0f);
457 marginRectPaint.setStrokeWidth(xOffset);
458 }
459
460 @Override
461 protected void onDraw(Canvas canvas) {
462 canvas.drawRect(curvesRect, frame);
463 Paint paint = curve.getPaint();
464 paint.setStrokeWidth(2.0f / xLength);
465 curve.setBounds(curvesRect);
466 curve.draw(canvas);
467 canvas.drawRect(marginRect, marginRectPaint);
468 for (int i = 0; i < x.length; i++) {
469 if (i == selIndex) {
470 canvas.drawBitmap(cPressed, x[i] * xLength + xOffset - cXCenter, y[i] * yLength + yOffset - cYCenter, null);
471 } else {
472 canvas.drawBitmap(cNormal, x[i] * xLength + xOffset - cXCenter, y[i] * yLength + yOffset - cYCenter, null);
473 }
474 }
475 }
476
477 @Override
478 public boolean onTouchEvent(MotionEvent ev) {
479 detector.onTouchEvent(ev);
480 ViewParent parent = getParent();
481 if (parent != null) {
482 parent.requestDisallowInterceptTouchEvent(true);
483 }
484 if (ev.getAction() == MotionEvent.ACTION_UP) {
485 selIndex = -1;
486 invalidate();
487 if (listener != null) {
488 listener.onCurvesChangeStop(CurvesView.this);
489 }
490 }
491 return true;
492 }
493 }