Image verification code control implementation (Code tutorial), verification code control
Currently, there are a variety of verification methods, from the initial digital verification, to the subsequent digital image into a dynamic image, and then to the image verification, there are still 12306 that are terrible:
No matter how the method changes, it is to ensure that someone uses machines for malicious registration and login, and increase the burden and consumption of servers. Recently, we implemented a verification code. After optimization, we decided to record its implementation principles.
The final result is as follows:
1. Train of Thought
First, we need to process the image and extract a verification block from it. The image waiting for verification is the image after the source image is extracted from the block, then, we can drag the progress bar to change the position of the puzzle block, and finally determine whether the position of the puzzle block is near the position where the source image is dug.
2. Dig for a tile
In this case, we must use a custom View. First, we load an image, dig out a tile, and then draw a progress bar at the bottom:
Public class ImageCheckCodeView extends View {private Paint mPaint; // Paint brush, drawing progress bar of the rounded rectangle, drawing progress cursor private Bitmap mBitmap; // source image private Bitmap waitCheckBitmap for image verification; // The image waiting for verification is the image private Puzzle puzzle after the original image is extracted from a Puzzle block; // The private RectF roundRectF of the puzzle block to be verified; // The rounded corner rectangle of the progress bar private float progress = 0; // The sliding progress of the current mobile phone private int seekBarHeight; // The height of the progress bar private boolean startImageCheck = false; // private Rect puzzleSrc; // private Rect puzzleDst; // public void setBitmap (Bitmap bitmap) {this. mBitmap = bitmap; if (getMeasuredWidth () = 0 | getMeasuredHeight () = 0) {getViewTreeObserver (). addOnGlobalLayoutListener (new ViewTreeObserver. onGlobalLayoutListener () {@ Override public void onGlobalLayout () {initBitmap () ;}}) ;} else {initBitmap () ;}} public ImageCheckCodeView (Context context) {this (context, null);} public ImageCheckCodeView (Context context, AttributeSet attrs) {this (context, attrs, 0);} public ImageCheckCodeView (Context context, AttributeSet attrs, int defStyleAttr) {super (context, attrs, defStyleAttr); // initialize the Paint brush mPaint = new Paint (); // create a roundRectF = new RectF (); // create a puzzle block object Puzzle = new puzzle (); // The draw area and display area of the Puzzle block puzzleSrc = new Rect (); puzzleDst = new Rect ();} @ Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {int widthMode = MeasureSpec. getMode (widthMeasureSpec); int widthSize = MeasureSpec. getSize (widthMeasureSpec); int heightMode = MeasureSpec. getMode (heightMeasureSpec); int heightSize = MeasureSpec. getSize (heightMeasureSpec); int width; int height; if (widthMode = MeasureSpec. EXACTLY) {width = widthSize;} else {// if no width is specified, the default width is screen width = getContext (). getResources (). getDisplayMetrics (). widthPixels;} if (heightMode = MeasureSpec. EXACTLY) {height = heightSize;} else {// if no height is specified, the default height is 1/3 height = getContext (). getResources (). getDisplayMetrics (). heightPixels/3;} setMeasuredDimension (width, height); initBitmap ();}/*** Description: Initialize the image, obtain the image that is waiting for verification after the puzzle block and after the puzzle block is dug * Date: 2018/2/7 */private void initBitmap () {if (mBitmap = null) {mBitmap = BitmapFactory. decodeResource (getContext (). getResources (), R. drawable. a);} // set the height of the progress bar to 1/10 seekBarHeight = getMeasuredHeight ()/10; // set the progress bar to the bottom of the control, the distance from the bottom is also 1/10 roundRectF of the control height. set (0, getMeasuredHeight ()-seekBarHeight * 2, getMeasuredWidth (), getMeasuredHeight ()-seekBarHeight); // pass the width and height of the control to the puzzle object puzzle. setContainerWidth (getMeasuredWidth (); puzzle. setContainerHeight (getMeasuredHeight (); // transmits the source image to the puzzle object. The puzzle internally processes the image into a puzzle block. setBitmap (mBitmap); // obtain the image after the puzzle block is dug based on the source image and puzzle object, that is, the image waiting for verification waitCheckBitmap = BitmapUtil. getWaitCheckBitmap (mBitmap, puzzle, getMeasuredWidth (), getMeasuredHeight () ;}@ Override protected void onDraw (Canvas canvas) {super. onDraw (canvas); canvas. drawBitmap (waitCheckBitmap, 0, 0, null); // paint the indicator bar of the rounded rectangle with a opacity of 50% mPaint. setColor (Color. argb (128,128,128,128); canvas. drawRoundRect (roundRectF, 45, 45, mPaint );}}
The Puzzle object is as follows. After the parent container measures the width and height, the object is passed. The width and height of the parent container are determined based on the width and height of the parent container, width: 1/5 of the parent container, and the height is 1/4 of the parent container. Then, the position of the puzzle block is randomly generated. x and y are the horizontal and vertical coordinates in the upper left corner of the puzzle block:
Public class Puzzle {private int containerWidth; // container width private int containerHeight; // container height private int x; // The horizontal coordinate private int y in the upper left corner of the Puzzle block; // private int width in the upper left corner of the Puzzle block; // private int height in the upper left corner of the Puzzle block; // high private Bitmap bitmap of the Puzzle block; // public Puzzle () {} public void setContainerWidth (int containerWidth) {this. containerWidth = containerWidth; x = new Random (). nextInt (containerWidth-containerWidth/5 // minus the tile width to ensure that all the blocks are displayed); width = containerWidth/5;} public void setContainerHeight (int containerHeight) {this. containerHeight = containerHeight; y = new Random (). nextInt (containerHeight-containerHeight/5 // minus the height of the tile to ensure that all the blocks are displayed); height = containerHeight/4;} public int getX () {return x ;} public int getY () {return y;} public int getWidth () {return width;} public int getHeight () {return height;} public Bitmap getBitmap () {return bitmap ;} public void setBitmap (Bitmap bitmap) {this. bitmap = BitmapUtil. getPuzzleBitmap (bitmap, this, containerWidth, containerHeight );}}
BitmapUtil has two image processing methods, which implement almost the same internal implementation. The main difference is serXfermode () to determine whether to display the intersection or non-intersection, the drawing of the puzzle block mainly involves the calculation of the beiser curve. If you do not know it, you can search for the beiser curve.
Public class BitmapUtil {/*** Description: Get the tile * Date: 2018/2/7 */public static Bitmap getPuzzleBitmap (Bitmap bitmap, Puzzle puzzle, int width, int height) {// create an image Bitmap mBitmap = Bitmap of the size of a tile. createBitmap (puzzle. getWidth (), puzzle. getHeight (), Bitmap. config. ARGB_8888); Canvas mCanvas = new Canvas (mBitmap); Paint mPaint = new Paint (); mPaint. setAntiAlias (true); // draw the Path of the tile. Path mPath = new Path (); mPath. moveTo (0, puzzle. getHeight ()/4); mPath. lineTo (puzzle. getWidth ()/3, puzzle. getHeight ()/4); mPath. cubicTo (puzzle. getWidth ()/6, 0, puzzle. getWidth ()-puzzle. getWidth ()/6, 0, puzzle. getWidth ()-puzzle. getWidth ()/3, puzzle. getHeight ()/4); mPath. lineTo (puzzle. getWidth (), puzzle. getHeight ()/4); mPath. lineTo (puzzle. getWidth (), puzzle. getHeight (); mPath. lineTo (0, puzzle. getHeight (); mPath. lineTo (0, puzzle. getHeight ()-puzzle. getHeight ()/4); mPath. cubicTo (puzzle. getWidth ()/3, puzzle. getHeight ()-puzzle. getHeight ()/8, puzzle. getWidth ()/3, puzzle. getHeight ()/4 + puzzle. getHeight ()/8, 0, puzzle. getHeight ()/2); mPath. lineTo (0, puzzle. getHeight ()/4); mCanvas. drawPath (mPath, mPaint); // draw the image mPaint of the tile. setXfermode (new porterduxfermode (PorterDuff. mode. SRC_IN); // The position displayed by the tile is determined based on the ratio of the image to the control. Rect src = new Rect (int) (double) bitmap. getWidth ()/(double) width * puzzle. getX (), (int) (double) bitmap. getHeight ()/(double) height * puzzle. getY (), (int) (double) bitmap. getWidth ()/(double) width * (puzzle. getX () + puzzle. getWidth (), (int) (double) bitmap. getHeight ()/(double) height * (puzzle. getY () + puzzle. getHeight (); Rect dst = new Rect (0, 0, puzzle. getWidth (), puzzle. getHeight (); mCanvas. drawBitmap (bitmap, src, dst, mPaint); // draw an outline on the periphery of the puzzle block to highlight the mPaint. setColor (Color. RED); mPaint. setStrokeWidth (5f); mPaint. setStyle (Paint. style. STROKE); mPaint. setXfermode (null); mCanvas. drawPath (mPath, mPaint); return mBitmap;}/*** Description: Get the image waiting for verification * Date: 88/2/7 */public static Bitmap getWaitCheckBitmap (Bitmap bitmap, Puzzle puzzle, int width, int height) {// create an image with the same width and height as the control. Bitmap mBitmap = Bitmap. createBitmap (width, height, Bitmap. config. ARGB_8888); Canvas mCanvas = new Canvas (mBitmap); Paint mPaint = new Paint (); mPaint. setAntiAlias (true); Path mPath = new Path (); // truncate the tile mPath. moveTo (puzzle. getX (), puzzle. getY () + puzzle. getHeight ()/4); mPath. lineTo (puzzle. getX () + puzzle. getWidth ()/3, puzzle. getY () + puzzle. getHeight ()/4); mPath. cubicTo (puzzle. getX () + puzzle. getWidth ()/6, puzzle. getY (), puzzle. getX () + puzzle. getWidth ()-puzzle. getWidth ()/6, puzzle. getY (), puzzle. getX () + puzzle. getWidth ()-puzzle. getWidth ()/3, puzzle. getY () + puzzle. getHeight ()/4); mPath. lineTo (puzzle. getX () + puzzle. getWidth (), puzzle. getY () + puzzle. getHeight ()/4); mPath. lineTo (puzzle. getX () + puzzle. getWidth (), puzzle. getY () + puzzle. getHeight (); mPath. lineTo (puzzle. getX (), puzzle. getY () + puzzle. getHeight (); mPath. lineTo (puzzle. getX (), puzzle. getY () + puzzle. getHeight ()-puzzle. getHeight ()/4); mPath. cubicTo (puzzle. getX () + puzzle. getWidth ()/3, puzzle. getY () + puzzle. getHeight ()-puzzle. getHeight ()/8, puzzle. getX () + puzzle. getWidth ()/3, puzzle. getY () + puzzle. getHeight ()/4 + puzzle. getHeight ()/8, puzzle. getX (), puzzle. getY () + puzzle. getHeight ()/2); mPath. lineTo (puzzle. getX (), puzzle. getY () + puzzle. getHeight ()/4); mCanvas. drawPath (mPath, mPaint); // The image is displayed in the control range mPaint. setXfermode (new porterduxfermode (PorterDuff. mode. SRC_OUT); Rect src = new Rect (0, 0, bitmap. getWidth (), bitmap. getHeight (); Rect dst = new Rect (0, 0, width, height); mCanvas. drawBitmap (bitmap, src, dst, mPaint); return mBitmap ;}}
The current results are as follows:
You have successfully obtained the picture waiting for verification after the puzzle block is dug, and there is a progress bar. The Puzzle block has not been drawn yet. Don't worry. It usually appears when it slides, next we will handle the slide.
3 Processing slide
Start to slide when you press it (it is valid when you press it from the place on the left that is less than the control width 1/10 ),
@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (event.getRawX() < getMeasuredWidth() / 10) { startImageCheck = true; progress = event.getRawX() < seekBarHeight / 2 ? seekBarHeight / 2 : event.getRawX() > getMeasuredWidth() - seekBarHeight / 2 ? getMeasuredWidth() - seekBarHeight / 2 : event.getRawX(); } invalidate(); return true; case MotionEvent.ACTION_MOVE: if (!startImageCheck) { break; } progress = event.getRawX() < seekBarHeight / 2 ? seekBarHeight / 2 : event.getRawX() > getMeasuredWidth() - seekBarHeight / 2 ? getMeasuredWidth() - seekBarHeight / 2 : event.getRawX(); invalidate(); break; case MotionEvent.ACTION_UP: if (!startImageCheck) { break; } progress = 0; startImageCheck = false; invalidate(); break; } return false; }
Add the following code in onDraw:
// After verification starts, redraw the position of the touch point and the tile if (startImageCheck) {puzzleSrc. set (0, 0, puzzle. getWidth (), puzzle. getHeight (); puzzleDst. set (int) progress-puzzle. getWidth ()/2, puzzle. getY (), (int) progress + puzzle. getWidth ()-puzzle. getWidth ()/2, puzzle. getY () + puzzle. getHeight (); canvas. drawBitmap (puzzle. getBitmap (), puzzleSrc, puzzleDst, null); // draw a touch point mPaint. setColor (Color. BLACK); canvas. drawCircle (progress, getMeasuredHeight ()-seekBarHeight/2, seekBarHeight/2, mPaint );}
Now, if verification is started during each re-painting, the points in the tile and progress bar will be drawn:
4. Verification Result callback
Finally, we need to provide an external interface to check whether the verification is successful.
public interface OnCheckResultCallback { void onSuccess(); void onFailure(); }
Define a variable for storing the callback interface and provide a method for setting the callback interface:
private OnCheckResultCallback onCheckResultCallback; public void setOnCheckResultCallback(OnCheckResultCallback onCheckResultCallback) { this.onCheckResultCallback = onCheckResultCallback; }
When your fingers are raised, check the verification result and call back the result (the condition here is that when the finger is raised, the error between the left and right sides of the tile center does not exceed 1/20 of the width of the tile ):
If (onCheckResultCallback = null) {break;} if (event. getRawX ()> puzzle. getX () + puzzle. getWidth ()/2-puzzle. getWidth ()/20 & event. getRawX () <puzzle. getX () + puzzle. getWidth ()/2 + puzzle. getWidth ()/20) {// if the deviation between the left and right coordinates of the touch point in the center of the block does not exceed 1/20 of the width of the block, onCheckResultCallback is verified. onSuccess ();} else {onCheckResultCallback. onFailure ();}
In this way, the effect at the beginning of the article is achieved.
5. Summary
This image verification code control was very troublesome when I was preparing to do it at the beginning, but it was not troublesome when I finally realized it. It mainly involved some calculations, including the calculation of the size of the puzzle block, the calculation of the contour path of the puzzle block and the calculation of the touch. This is the optimized version. The initial computation looks complicated. Finally, the puzzle block is encapsulated into an object, which is easy to understand. This is also the idea we should use when writing code, it is necessary to encapsulate the Code if it is hard to understand. Complete code and usage can be found at the Github portal. If you have any questions, please leave a message.