Programmation Android/Exemples/miniPainter

Programmation Android
Plan
Modifier ce modèle

Objectif modifier

Le but est de réaliser un mini logiciel de dessin pour Android. Le code est celui de l'ancien fingerpaint pour android 1.1.

Réalisation modifier

MainActivity.java modifier

Pour dessiner on a besoin de 4 éléments :

  • Bitmap qui regroupe les pixels définissant le trait
  • Canvas qui doit contenir tous les traits
  • Path : une Primitive (le trait lui même)
  • Paint pour décrire la couleur et le style du dessin

Le path est une courbe de bézier qui trace le bitmap.

/*
 * 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.
 */

package com.example.fingerpaint;

import android.app.*;
import android.content.Context;
import android.graphics.*;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;

public class MainActivity extends Activity
        implements ColorPickerDialog.OnColorChangedListener {

    private Paint mPaint;
    private MaskFilter  mEmboss;
    private MaskFilter  mBlur;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new MyView(this));

        // creation d'un objet paint
        mPaint = new Paint();
        // customization du paint
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setColor(0xFFFF0000);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeWidth(12);

        // modification des propriétés sur la brosse
        mEmboss = new EmbossMaskFilter(new float[] { 1, 1, 1 },
                                       0.4f, 6, 3.5f);
        mBlur = new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL);
    }

    // permet d'assigner la couleur à l'objet paint
    public void colorChanged(int color) {
        mPaint.setColor(color);
    }

    public class MyView extends View {

        // 
        private static final float MINP = 0.25f;
        private static final float MAXP = 0.75f;

        // les 4 éléments de bases pour permettre le dessin
        private Bitmap  mBitmap;
        private Canvas  mCanvas;
        private Path    mPath;
        private Paint   mBitmapPaint;

        public MyView(Context c) {
            super(c);
            // instanciation du path
            mPath = new Path();
            // instanciation du paint avec tramage à on.
            mBitmapPaint = new Paint(Paint.DITHER_FLAG);
        }

        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            // creation du bitmap ou objet du trait
            mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
            // instanciation du canvas
            mCanvas = new Canvas(mBitmap);
        }

        // methode appelée dynamiquement quand on dessine
        @Override
        protected void onDraw(Canvas canvas) {
            // assignation de la couleur au canvas
            canvas.drawColor(0xFFAAAAAA);
            // assignation du bitmap au canvas
            canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
            // assignation du chemin au canvas
            canvas.drawPath(mPath, mPaint);
        }

        private float mX, mY;
        // tolérance de la touche
        private static final float TOUCH_TOLERANCE = 4;

        // à la touche posée
        private void touch_start(float x, float y) {
            mPath.reset();
            // deplacement du path et positionnement en x,y
            mPath.moveTo(x, y);
            mX = x;
            mY = y;
        }

        // à la touche en déplacement
        private void touch_move(float x, float y) {
            // approximation du bezier au deplacement
            float dx = Math.abs(x - mX);
            float dy = Math.abs(y - mY);
            if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
                mPath.quadTo(mX, mY, (x + mX)/2, (y + mY)/2);
                mX = x;
                mY = y;
            }
        }

        // a la touche levée
        private void touch_up() {
            mPath.lineTo(mX, mY);
            // dessin du path sur l'objet paint
            mCanvas.drawPath(mPath, mPaint);
            // reset du path pour ne pas avoir le chemin précédent
            mPath.reset();
        }

        // lancement des methodes de touch de l'écran tactile du smartphone
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            // prend l'axe des X
            float x = event.getX();
            // prend l'axe des Y
            float y = event.getY();

            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    touch_start(x, y);
                    invalidate();
                    break;
                case MotionEvent.ACTION_MOVE:
                    touch_move(x, y);
                    invalidate();
                    break;
                case MotionEvent.ACTION_UP:
                    touch_up();
                    invalidate();
                    break;
            }
            return true;
        }
    }

    // assignation des ids du menu
    private static final int COLOR_MENU_ID = Menu.FIRST;
    private static final int EMBOSS_MENU_ID = Menu.FIRST + 1;
    private static final int BLUR_MENU_ID = Menu.FIRST + 2;
    private static final int ERASE_MENU_ID = Menu.FIRST + 3;
    private static final int SRCATOP_MENU_ID = Menu.FIRST + 4;

    // assignation des éléments du menu
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);

        menu.add(0, COLOR_MENU_ID, 0, "Color").setShortcut('3', 'c');
        menu.add(0, EMBOSS_MENU_ID, 0, "Emboss").setShortcut('4', 's');
        menu.add(0, BLUR_MENU_ID, 0, "Blur").setShortcut('5', 'z');
        menu.add(0, ERASE_MENU_ID, 0, "Erase").setShortcut('5', 'z');
        menu.add(0, SRCATOP_MENU_ID, 0, "SrcATop").setShortcut('5', 'z');

        /****   Is this the mechanism to extend with filter effects?
        Intent intent = new Intent(null, getIntent().getData());
        intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
        menu.addIntentOptions(
                              Menu.ALTERNATIVE, 0,
                              new ComponentName(this, NotesList.class),
                              null, intent, 0, null);
        *****/
        return true;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);
        return true;
    }

    // événements lancés selon les éléments du menu appelés
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        mPaint.setXfermode(null);
        mPaint.setAlpha(0xFF);

        switch (item.getItemId()) {
            case COLOR_MENU_ID:
                // ouverture d'un colorpicker
                new ColorPickerDialog(this, this, mPaint.getColor()).show();
                return true;
            case EMBOSS_MENU_ID:
                // application d'un filtre emboss sur les traits de la brosse
                if (mPaint.getMaskFilter() != mEmboss) {
                    mPaint.setMaskFilter(mEmboss);
                } else {
                    mPaint.setMaskFilter(null);
                }
                return true;
            case BLUR_MENU_ID:
                // application d'un filtre blur sur les traits de la brosse
                if (mPaint.getMaskFilter() != mBlur) {
                    mPaint.setMaskFilter(mBlur);
                } else {
                    mPaint.setMaskFilter(null);
                }
                return true;
            case ERASE_MENU_ID:
                // effacement du contenu de l'objet paint
                mPaint.setXfermode(new PorterDuffXfermode(
                                                        PorterDuff.Mode.CLEAR));
                return true;
            case SRCATOP_MENU_ID:
                mPaint.setXfermode(new PorterDuffXfermode(
                                                    PorterDuff.Mode.SRC_ATOP));
                mPaint.setAlpha(0x80);
                return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

ColorPickerDialog.java modifier

package com.example.fingerpaint;

import android.os.Bundle;
import android.app.Dialog;
import android.content.Context;
import android.graphics.*;
import android.view.MotionEvent;
import android.view.View;

 public class ColorPickerDialog extends Dialog {

  public interface OnColorChangedListener {
     void colorChanged(int color);
  }

   private OnColorChangedListener mListener;
  private int mInitialColor;

   private static class ColorPickerView extends View {
  private Paint mPaint;
  private Paint mCenterPaint;
  private final int[] mColors;
  private OnColorChangedListener mListener;

  ColorPickerView(Context c, OnColorChangedListener l, int color) {
      super(c);
      mListener = l;
      mColors = new int[] {
          0xFFFF0000, 0xFFFF00FF, 0xFF0000FF, 0xFF00FFFF, 0xFF00FF00,
          0xFFFFFF00, 0xFFFF0000
      };
      Shader s = new SweepGradient(0, 0, mColors, null);

      mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
      mPaint.setShader(s);
      mPaint.setStyle(Paint.Style.STROKE);
      mPaint.setStrokeWidth(32);

      mCenterPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
      mCenterPaint.setColor(color);
      mCenterPaint.setStrokeWidth(5);
  }

  private boolean mTrackingCenter;
  private boolean mHighlightCenter;

  @Override
  protected void onDraw(Canvas canvas) {
      float r = CENTER_X - mPaint.getStrokeWidth()*0.5f;

      canvas.translate(CENTER_X, CENTER_X);

      canvas.drawOval(new RectF(-r, -r, r, r), mPaint);
      canvas.drawCircle(0, 0, CENTER_RADIUS, mCenterPaint);

      if (mTrackingCenter) {
          int c = mCenterPaint.getColor();
          mCenterPaint.setStyle(Paint.Style.STROKE);

          if (mHighlightCenter) {
              mCenterPaint.setAlpha(0xFF);
          } else {
              mCenterPaint.setAlpha(0x80);
          }
          canvas.drawCircle(0, 0,
                            CENTER_RADIUS + mCenterPaint.getStrokeWidth(),
                            mCenterPaint);

          mCenterPaint.setStyle(Paint.Style.FILL);
          mCenterPaint.setColor(c);
      }
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      setMeasuredDimension(CENTER_X*2, CENTER_Y*2);
  }

  private static final int CENTER_X = 100;
  private static final int CENTER_Y = 100;
  private static final int CENTER_RADIUS = 32;

  private int floatToByte(float x) {
      int n = java.lang.Math.round(x);
      return n;
  }
  private int pinToByte(int n) {
      if (n < 0) {
          n = 0;
      } else if (n > 255) {
          n = 255;
      }
      return n;
  }

  private int ave(int s, int d, float p) {
      return s + java.lang.Math.round(p * (d - s));
  }

  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);
  }

  private int rotateColor(int color, float rad) {
      float deg = rad * 180 / 3.1415927f;
      int r = Color.red(color);
      int g = Color.green(color);
      int b = Color.blue(color);

      ColorMatrix cm = new ColorMatrix();
      ColorMatrix tmp = new ColorMatrix();

      cm.setRGB2YUV();
      tmp.setRotate(0, deg);
      cm.postConcat(tmp);
      tmp.setYUV2RGB();
      cm.postConcat(tmp);

      final float[] a = cm.getArray();

      int ir = floatToByte(a[0] * r +  a[1] * g +  a[2] * b);
      int ig = floatToByte(a[5] * r +  a[6] * g +  a[7] * b);
      int ib = floatToByte(a[10] * r + a[11] * g + a[12] * b);

      return Color.argb(Color.alpha(color), pinToByte(ir),
                        pinToByte(ig), pinToByte(ib));
  }

  private static final float PI = 3.1415926f;

  @Override
  public boolean onTouchEvent(MotionEvent event) {
      float x = event.getX() - CENTER_X;
      float y = event.getY() - CENTER_Y;
      boolean inCenter = java.lang.Math.sqrt(x*x + y*y) <= CENTER_RADIUS;

      switch (event.getAction()) {
          case MotionEvent.ACTION_DOWN:
              mTrackingCenter = inCenter;
              if (inCenter) {
                  mHighlightCenter = true;
                  invalidate();
                  break;
              }
          case MotionEvent.ACTION_MOVE:
              if (mTrackingCenter) {
                  if (mHighlightCenter != inCenter) {
                      mHighlightCenter = inCenter;
                      invalidate();
                  }
              } else {
                  float angle = (float)java.lang.Math.atan2(y, x);
                  // need to turn angle [-PI ... PI] into unit [0....1]
                  float unit = angle/(2*PI);
                  if (unit < 0) {
                      unit += 1;
                  }
                  mCenterPaint.setColor(interpColor(mColors, unit));
                  invalidate();
              }
              break;
          case MotionEvent.ACTION_UP:
              if (mTrackingCenter) {
                  if (inCenter) {
                      mListener.colorChanged(mCenterPaint.getColor());
                  }
                  mTrackingCenter = false;    // so we draw w/o halo
                  invalidate();
              }
              break;
      }
      return true;
     }
   }

    public ColorPickerDialog(Context context,
                       OnColorChangedListener listener,
                       int initialColor) {
  super(context);

  mListener = listener;
  mInitialColor = initialColor;
 }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    OnColorChangedListener l = new OnColorChangedListener() {
      public void colorChanged(int color) {
          mListener.colorChanged(color);
          dismiss();
      }
  };

  setContentView(new ColorPickerView(getContext(), l, mInitialColor));
   setTitle("Pick a Color");
 }
}