Start with a coloring game to explain the method of color filling in the area of Android _android

Source: Internet
Author: User
Tags gety

A, Coloring game overview

The recent group accidentally saw a friend in the group chat irregular image fill what four unicom, eight unicom What, on its own studious pragmatic attitude to consult the relevant information. For this kind of coloring information, the best thing is to search some related app, according to my observation, irregular image filling in the coloring game mostly, but roughly can be divided into two kinds:

    • Layer-based padding
    • Boundary-based padding

So for the above two, we will pass two Bovinlai to explain, this article is to narrate based on the layer fill way, then what is based on the layer of filling method? In fact, a picture is actually composed of several layers, each layer shows part of the image (no image part of the transparent), multi-layer overlay after the formation of a complete pattern, the layer is superimposed relationship, similar to the following figure.

I believe that if you have studied PS, you will know more about the above. For example, you want to draw a sky, you can at the lowest level to draw the blue sky, on the top of the white clouds, and then the upper level will execute the birds. Then the three-storey overlay is a picture of a bird flying in the sky.

Second, the effect and analysis

Well, let's look at the effect of today.

OK, you can see a simple coloring effect, in fact, the principle is very simple, first of all, the figure is actually composed of 7 layers:

For example, the following figure.

So if we need to color a place in this picture, it's actually coloring a layer of non-transparent area. It actually translates into:

The user-clicked (x,y)-> determines which layer of opaque area is-> and then shades the opaque area of the layer.

OK, so the principle is clear, is actually very simple, based on this principle, we can customize a view, and then one to draw the layer, and finally follow the above steps to write code. However, we still have a place to be lazy, in fact, we do not have to go to a layer of a layer of painting, we can use drawable to complete the layer overlay work, we have a class of drawable called Layerdrawable, the corresponding XML for Layer-list, We can greatly simplify our work by using layerdrawable.

III. Coding and implementation

The above has been described very clearly, I will give you a further refinement:

To define our drawable in the layer-list.
And then take the drawable as the background to our view.
Replication Ontouchevent Method
To determine which level the user clicks the location of the opacity of the position, change the layer of opaque area color
(i) Layer-list

 <?xml version= "1.0" encoding= "Utf-8"?> <layer-list xmlns:android=
"Http://schemas.android.com/apk/res" /android ">
 <item
  android:drawable=" @drawable/eel_mask1 "/> <item android:drawable=
  " @drawable/eel_mask2 "/>
 <item
  android:drawable=" @drawable/eel_mask3/> "<item
  android:drawable= "@drawable/eel_mask4"/>
 <item
  android:drawable= "@drawable/eel_mask5"/>
 <item
  android:drawable= "@drawable/eel_mask6"/>
 <item android:drawable=
  "@drawable /eel_mask7 "/>
</layer-list>

OK, so our drawable OK ~ ~ did not say, but layer-list can do a lot of things, we can pay attention to.

(ii) View Code

Package com.zhy.colour_app_01;
Import Android.content.Context;
Import Android.graphics.Bitmap;
Import Android.graphics.Color;
Import Android.graphics.PorterDuff;
Import android.graphics.drawable.BitmapDrawable;
Import android.graphics.drawable.Drawable;
Import android.graphics.drawable.LayerDrawable;
Import Android.util.AttributeSet;
Import Android.util.Log;
Import android.view.MotionEvent;

Import Android.view.View;

Import Java.util.Random;
 /** * Created by Zhy on 15/5/14.

 * * public class Colourimagebaselayerview extends View {private layerdrawable mdrawables;
  Public Colourimagebaselayerview (context, AttributeSet attrs) {Super (context, attrs);

 Mdrawables = (layerdrawable) getbackground (); @Override protected void onmeasure (int widthmeasurespec, int heightmeasurespec) {setmeasureddimension (mdrawables.
 Getintrinsicwidth (), Mdrawables.getintrinsicheight ());
@Override public boolean ontouchevent (Motionevent event) {final float x = Event.getx ();  Final float y = event.gety ();
   if (event.getaction () = = Motionevent.action_down) {drawable drawable = finddrawable (x, y);
  if (drawable!= null) drawable.setcolorfilter (Randomcolor (), PorterDuff.Mode.SRC_IN);
 Return Super.ontouchevent (event);
  private int Randomcolor () {Random Random = new Random ();
  int color = COLOR.ARGB (255, Random.nextint (256), Random.nextint (256), Random.nextint (256));
 return color;
  Private drawable finddrawable (float x, float y) {final int numberoflayers = Mdrawables.getnumberoflayers ();
  Drawable drawable = null;
  Bitmap Bitmap = null;
   for (int i = numberOfLayers-1 i >= 0; i--) {drawable = mdrawables.getdrawable (i);
   Bitmap = ((bitmapdrawable) drawable). Getbitmap ();
    try {int pixel = bitmap.getpixel ((int) x, (int) y);
    if (pixel = = color.transparent) {continue;
   The catch (Exception e) {continue;
  return drawable;
 return null;

 }

}

OK, the code is also relatively simple, first we take drawable as the background of the view, and then get Drawable (layerdrawable) in the construction. Next, the replication ontouchevent, capture the user clicks (x,y), according to (X,y) to find out which layer is currently clicked (must be clicked in the Non-transparent area), and finally by setting the Setcolorfilter to change the color can ~ very easy bar finally put the layout file:

(iii) Layout documents

<relativelayout xmlns:android= "http://schemas.android.com/apk/res/android"
    xmlns:tools= "http:// Schemas.android.com/tools "
    android:layout_width=" match_parent "
    android:layout_height=" Match_parent "
    android:paddingleft= "@dimen/activity_horizontal_margin"
    android:paddingright= "@dimen/activity_ Horizontal_margin "
    android:paddingtop=" @dimen/activity_vertical_margin "
    android:paddingbottom=" @dimen Activity_vertical_margin "
    tools:context=". Mainactivity ">

 <com.zhy.colour_app_01.colourimagebaselayerview
  android:background=" @drawable Eel "
  android:layout_width=" match_parent "
  android:layout_centerinparent=" true "
  android:layout_ height= "Match_parent"/>

</RelativeLayout>

Iv. Filling of boundaries
1. There are 2 kinds of classical algorithms for image filling.

One is the seed filling method. The seed filling method can be used to fill arbitrary regions and graphs theoretically, but the algorithm has a lot of iterative stack and large recursion, which reduces the filling efficiency.
Another is the scanning line filling method.
Note: In fact, the image filling algorithm is still a lot of interest can go to Google Academic search.
OK, let's take a look at the effect chart below:

OK, you can see such a color fill than the one based on the layer on the preparation of the material to easy a lot ~ ~ ~

2. Principle Analysis

First we briefly describe the principle, we click on the point of the click to get the "color", and then follow the algorithm we choose to fill the color can be.

Algorithm 1: Seed filling method, four Unicom/eight Unicom
Introduction to the algorithm: Suppose you want to fill a range with red.

From the point of the user clicks the pixel start, up and down (eight unicom and left, lower left, right, right) to judge the color, if the color of the four direction and the current point of view of the pixel consistent, then change the color to the target color. Then proceed to the above process.

OK, you can see this is a recursive process, 1 points to 4, 4 to 16 continue to extend. If you follow this algorithm, you'll write code like this:

/**
  * @param pixels pixel Group
  * @param w  width
  * @param h  Height
  * @param pixel the color of the current point *
  @param newcolor Fill Color-filled
  * @param i  horizontal axis
  * @param j  ordinate
 /private void FillColor01 (int[] pixels, int w, int h, int p Ixel, int newcolor, int i, int j)
 {
  int index = j * W + i;
  if (Pixels[index]!= pixel | | I >= w | | I < 0 | | J < 0 | | J >= h) return
   ;
  Pixels[index] = Newcolor;
  Upper
  FillColor01 (Pixels, W, h, Pixel, Newcolor, I, j-1);
  Right
  FillColor01 (pixels, W, h, Pixel, Newcolor, i + 1, j);
  Lower
  FillColor01 (pixels, W, h, Pixel, Newcolor, I, j + 1);
  Left
  FillColor01 (pixels, W, h, Pixel, Newcolor, I-1, j);
 

The code is simple, but if you run, a StackOverflowException exception occurs, which is largely due to a large number of recursion. Although simple, it is not possible to use this method on mobile devices.

So, I thought, this method is not recursive depth too much, then I can use a stack to save pixels, reduce recursion depth and number of times, so I changed the code to the following way:

/** * @param pixels Pixel Group * @param w Width * @param h height * @param pixel the color of the current point * @param newcolor fill Color * @param i 
  Horizontal coordinates * @param j ordinate * * private void FillColor (int[] pixels, int w, int h, int pixel, int newcolor, int i, int j) {

  Mstacks.push (new Point (I, j));
   while (!mstacks.isempty ()) {Point seed = Mstacks.pop ();

   LOG.E ("TAG", "seed =" + seed.x + ", seed =" + seed.y);

   int index = SEED.Y * W + seed.x;
   Pixels[index] = Newcolor;
    if (Seed.y > 0) {int top = INDEX-W;
    if (pixels[top] = = pixel) {Mstacks.push (new Point (Seed.x, seed.y-1));
    } if (Seed.y < h-1) {int bottom = index + W;
    if (pixels[bottom] = = pixel) {Mstacks.push (new Point (seed.x, SEED.Y + 1));
    } if (Seed.x > 0) {int left = index-1;
    if (pixels[left] = = pixel) {Mstacks.push (new Point (Seed.x-1, seed.y));
    } if (Seed.x < w-1) {int right = index + 1; if (Pixels[right] = = pixel) {Mstacks.push (new Point (seed.x + 1, seed.y));

 }
   }

  }


 }

The idea of the method is also relatively simple, the current pixel point into the stack, and then the stack coloring, and then judge the four directions respectively, if the conditions are also carried into the stack (as long as the stack is not empty continuous operation). Ok, this method I also tried to run under, well, this will not be an error, but the speed of special slow ~ ~ ~ ~ I was not acceptable. (Interested can try, remember if ANR, click Wait).

In this way, the first algorithm, we do not consider, there is no way to use, the main reason is to assume for the rectangular color region, are required to fill, and the algorithm is still a variety of stack. Then consider the second algorithm

Scanning line Filling method

Detailed reference to the scanning line seed filling algorithm and scanning line seed filling algorithm.
Algorithm idea:

Initializes an empty stack for storing seed points, and putting seed points (x, y) into the stack;
To determine whether the stack is empty, if the stack is null to end the algorithm, otherwise take out the top of the stack as the current scan line of the seed point (x, y), Y is the current scan line;
From the seed point (x, y), fill the left and right two directions along the current scan line until the boundary. The left and right endpoint coordinates of the marked section are Xleft and xright respectively;
Check the pixels in the interval [Xleft, xright] of the y-1 and Y + 12 scan lines adjacent to the current scan line, and search from the xright to the xleft direction, assuming that the scan interval is AAABAAC (A is a seed-point color), Then press B and C in front of a as the seed point into the stack, then return to step (2);
The above reference from reference [4], made some changes, the article [4] describes the algorithm, the test has a little problem, so did the modification.

You can see that the algorithm is basically a line of coloring, so that in large areas of the need to color the efficiency of the algorithm is much higher.

OK, about the steps of the algorithm we now feel vague, we can refer to our code. After the algorithm is selected, the code is then started.

3. Code implementation

We introduced a boundary color in our code, and if set, the shaded boundary reference is the boundary color, otherwise it will be inconsistent with the seed color as the boundary.

(i) Construction methods and measurements

public class Colourimageview extends ImageView {private Bitmap mbitmap;

 /** * boundary color/private int mbordercolor =-1;

 Private Boolean hasbordercolor = false;

 Private stack<point> mstacks = new stack<point> ();

  Public Colourimageview (context, AttributeSet attrs) {Super (context, attrs);
  TypedArray ta = context.obtainstyledattributes (attrs, R.styleable.colourimageview);
  Mbordercolor = Ta.getcolor (R.styleable.colourimageview_border_color,-1);

  Hasbordercolor = (Mbordercolor!=-1);

  L.E ("Hasbordercolor =" + Hasbordercolor + ", Mbordercolor =" + Mbordercolor);

 Ta.recycle (); @Override protected void onmeasure (int widthmeasurespec, int heightmeasurespec) {super.onmeasure (widthmeasurespec

  , Heightmeasurespec);
  int viewwidth = Getmeasuredwidth ();

  int viewheight = Getmeasuredheight (); The height of the view is Setmeasureddimension (Viewwidth, getdrawable () with the width as the standard, equal scale. Getintrinsicheight () * viewwidth/getdrawable () . Getintrinsicwidth ());

  L.E ("View ' s width =" + getmeasuredwidth () + ", view ' s height =" + getmeasuredheight ());
  According to Drawable, a bitmap bitmapdrawable drawable = (bitmapdrawable) getdrawable () with the same size as the view is obtained;
  Bitmap BM = Drawable.getbitmap ();
 Mbitmap = Bitmap.createscaledbitmap (BM, Getmeasuredwidth (), Getmeasuredheight (), false);

 }

We can see that we have chosen to inherit ImageView, so we just need to set the picture to SRC.
Construct method to get our custom boundary color, of course you can not set ~ ~
The purpose of the rewrite measurement is to get a bitmap as large as the view to facilitate our operation.

The next step is the Click ~

4.onTouchEvent

@Override Public
 Boolean ontouchevent (Motionevent event)
 {
  final int x = (int) event.getx ();
  Final int y = (int) event.gety ();
  if (event.getaction () = = Motionevent.action_down)
  {
   //Fill
   fillcolortosamearea (x, y);
  }

  Return Super.ontouchevent (event);
 }

 /**
  * Obtain a pity Dorado color based on x,y, fill
  *
  @param x
  * @param y
 /private void Fillcolortosamearea (int x , int y)
 {
  Bitmap bm = mbitmap;

  int pixel = Bm.getpixel (x, y);
  if (pixel = = Color.transparent | | (hasbordercolor && mbordercolor = pixel))
  {return
   ;
  }
  int newcolor = Randomcolor ();

  int w = bm.getwidth ();
  int h = bm.getheight ();
  Get the color array of the bitmap
  int[] pixels = new int[w * h];
  Bm.getpixels (pixels, 0, W, 0, 0, W, h);
  Colouring
  fillcolor (pixels, W, h, Pixel, Newcolor, x, y);
  Reset Bitmap
  bm.setpixels (pixels, 0, W, 0, 0, W, h);
  Setimagedrawable (New bitmapdrawable (BM));

 

As you can see, we get (x,y) in the ontouchevent, and we get the A pity dorado coordinates:

Get the click Color, get the entire bitmap pixel array
Change the color in this array
and then reset to bitmap, reset to ImageView
The key is to change the colors in the array by FillColor

/** * @param pixels Pixel Group * @param w Width * @param h height * @param pixel the color of the current point * @param newcolor fill Color * @param i 
  Horizontal coordinates * @param j ordinate * * private void FillColor (int[] pixels, int w, int h, int pixel, int newcolor, int i, int j) {

  Step 1: Place the seed points (x, y) into the stack; Mstacks.push (new Point (I, j)); Step 2: To determine whether the stack is empty,//if the stack is null to end the algorithm, or remove the top of the stack as the current scan line of the seed point (x, y),//Y is the current scan line; while (!mstacks.isempty ()) {/** * step Flash 3: From the seed point (x, y), fill the left and right two directions along the current scan line, * until the boundary.
   The left and right endpoint coordinates of the labeled section are Xleft and xright;/point seed = Mstacks.pop ();
   L.E ("seed =" + seed.x + ", seed =" + seed.y);
   int count = filllineleft (pixels, Pixel, W, H, Newcolor, seed.x, SEED.Y);
   int left = Seed.x-count + 1;
   Count = filllineright (pixels, Pixel, W, H, Newcolor, Seed.x + 1, seed.y);


   int right = seed.x + count; /** * Step 4: * Check respectively with the current scan line adjacent to the y-1 and Y + 12 scan lines in the interval [Xleft, xright] in the pixels, * from the xright to the xleft direction of the search, assuming that the scan interval is AAABAAC (a for the seed Point Yan
   color), * then press B and C in front of a as the seed point into the stack, and then return to step (2);///from Y-1 for seedif (seed.y-1 >= 0) findseedinnewline (pixels, Pixel, W, H, Seed.y-1, left, right);
  Find seeds from y+1 if (Seed.y + 1 < h) findseedinnewline (pixels, Pixel, W, H, Seed.y + 1, left, right);

 }


 }

The


can see that I have clearly identified four steps of the algorithm in this method. Well, finally there are some dependencies on the details on the method:

 /** * Find seed node in New line * * @param pixels * @param pixel * @param w * @param h * @param i * @param left * @para
   M right */private void findseedinnewline (int[] pixels, int pixel, int w, int h, int i, int left, int right) {/**
  * Get start index of the line/int begin = i * w + left;

  /** * Gets the end index of the line/int ending = i * w + right;

  Boolean hasseed = false;

  int rx =-1, ry =-1;

  ry = i;
    /** * from end to begin, find the seed node into the stack (Aaabaaab, then A is the seed node before b) * * (end >= begin) {if (pixels[end] = = pixel) {
     if (!hasseed) {Rx = end% W;
     Mstacks.push (New Point (Rx, ry));
    Hasseed = true;
   } else {hasseed = false;
  } end--; }/** * to the right color, return the number of fills * * @return/private int filllineright (int[] pixels, int pixel, int w, int h, int new

  Color, int x, int y) {int count = 0;
   while (x < W) {//Get index int index = y * w + x;
    if (needfillpixel (pixels, pixel, index)) {Pixels[index] = Newcolor;count++;
   x + +;
   } else {break;
 } return count; /** * to left color, return the number of fill color * * @return/private int filllineleft (int[] pixels, int pixel, int w, int h, int Newco
  lor, int x, int y) {int count = 0;

   while (x >= 0) {//computes the index int index = y * w + x;
    if (needfillpixel (pixels, pixel, index)) {Pixels[index] = Newcolor;
    count++;
   x--;
   } else {break;
 } return count;  Private Boolean needfillpixel (int[] pixels, int pixel, int index) {if (Hasbordercolor) {return Pixels[index]
  != Mbordercolor;
  else {return Pixels[index] = = pixel;
  }/** * Returns a random color * * @return/private int randomcolor () {Random Random = new Random ();
  int color = COLOR.ARGB (255, Random.nextint (256), Random.nextint (256), Random.nextint (256));
 return color;

 }

OK, to this, the code is introduced to complete ~ ~ ~

Finally paste the layout file ~ ~

<relativelayout xmlns:android= "http://schemas.android.com/apk/res/android" xmlns:tools= "http:// Schemas.android.com/tools "xmlns:zhy=" Http://schemas.android.com/apk/res-auto "android:layout_width=" Match_ Parent "android:layout_height=" match_parent "android:paddingleft=" "@dimen/activity_horizontal_margin" android:p addingright= "@dimen/activity_horizontal_margin" android:paddingtop= "@dimen/activity_vertical_margin" Android: paddingbottom= "@dimen/activity_vertical_margin" tools:context= ". Mainactivity "> <com.zhy.colour_app_01.colourimageview zhy:border_color=" #FF000000 "android:src=" @drawable image_007 "android:background=" #33ff0000 "android:layout_width=" Match_parent "android:layout_centerinparent=" true "android:layout_height=" match_parent "/> </RelativeLayout> <?xml version=" 1.0 "encoding=" Utf-8 "?>" ;resources> <declare-styleable name= "Colourimageview" > <attr name= "border_color" format= "Color|referenc"E "></attr> </declare-styleable> </resources>
 

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.