Android 不规则封闭区域填充 手指秒变油漆桶

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

                       
 

转载请标明出处:
  http://blog.csdn.net/lmj623565791/article/details/45954255
  本文出自:【张鸿洋的博客】

一、概述

在上一篇的叙述中,我们通过图层的方式完成了图片颜色的填充(详情请戳:Android 不规则图像填充 小玩着色游戏),不过在着色游戏中更多的还是基于边界的图像的填充。本篇博客将详细描述。

图像的填充有2种经典算法。

  • 一种是种子填充法。种子填充法理论上能够填充任意区域和图形,但是这种算法存在大量的反复入栈和大规模的递归,降低了填充效率。
  • 另一种是扫描线填充法。

注意:实际上图像填充的算法还是很多的,有兴趣可以去Google学术上去搜一搜。
ok,下面先看看今天的效果图:

ok,可以看到这样的颜色填充比上一篇的基于层的在素材的准备上要easy 很多~~~


二、原理分析

首先我们简述下原理,我们在点击的时候拿到点击点的”颜色”,然后按照我们选择的算法进行填色即可。

算法1:种子填充法,四联通/八联通

 

详细介绍,可以参考多边形区域填充算法--递归种子填充算法

算法简介:假设要将某个区域填充成红色。

从用户点击点的像素开始,上下左右(八联通还有左上,左下,右上,右下)去判断颜色,如果四个方向上的颜色与当前点击点的像素一致,则改变颜色至目标色。然后继续上述这个过程。

ok,可以看到这是一个递归的过程,1个点到4个,4个到16个不断的去延伸。如果按照这种算法,你会写出类似这样的代码:

/**     * @param pixels   像素数组     * @param w        宽度     * @param h        高度     * @param pixel    当前点的颜色     * @param newColor 填充色     * @param i        横坐标     * @param j        纵坐标     */    private void fillColor01(int[] pixels, int w, int h, int pixel, 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;        //上        fillColor01(pixels, w, h, pixel, newColor, i, j - 1);        //右        fillColor01(pixels, w, h, pixel, newColor, i + 1, j);        //下        fillColor01(pixels, w, h, pixel, newColor, i, j + 1);        //左        fillColor01(pixels, w, h, pixel, newColor, i - 1, j);    }
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

代码很简单,但是如果你去运行,会发生StackOverflowException异常,这个异常主要是因为大量的递归造成的。虽然简单,但是在移动设备上使用该方法不行。

于是,我就想,这个方法不是递归深度过多么,那么我可以使用一个Stack去存像素点,减少递归的深度和次数,于是我把代码改成如下的方式:

/**     * @param pixels   像素数组     * @param w        宽度     * @param h        高度     * @param pixel    当前点的颜色     * @param newColor 填充色     * @param i        横坐标     * @param j        纵坐标     */    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));                }            }        }    }
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

方法的思想也比较简单,将当前像素点入栈,然后出栈着色,接下来分别判断四个方向的,如果符合条件也进行入栈(只要栈不为空持续运行)。ok,这个方法我也尝试跑了下,恩,这次不会报错了,但是速度特别的慢~~~~慢得我是不可接受的。(有兴趣可以尝试,记得如果ANR,点击等待)。

这样来看,第一种算法,我们是不考虑了,没有办法使用,主要原因是假设对于矩形同色区域,都是需要填充的,而算法一依然是各种入栈。于是考虑第二种算法

扫描线填充法

 

详细可参考 扫描线种子填充算法的解析扫描线种子填充算法

算法思想[4]:

 
  1.  
  2. 初始化一个空的栈用于存放种子点,将种子点(x, y)入栈;
  3.  
  4. 判断栈是否为空,如果栈为空则结束算法,否则取出栈顶元素作为当前扫描线的种子点(x, y),y是当前的扫描线;
  5.  
  6. 从种子点(x, y)出发,沿当前扫描线向左、右两个方向填充,直到边界。分别标记区段的左、右端点坐标为xLeft和xRight;
  7.  
  8. 分别检查与当前扫描线相邻的y - 1和y + 1两条扫描线在区间[xLeft, xRight]中的像素,从xRight开始向xLeft方向搜索,假设扫描的区间为AAABAAC(A为种子点颜色),那么将B和C前面的A作为种子点压入栈中,然后返回第(2)步;
  9.  

上述参考自参考文献[4],做了些修改,文章[4]中描述算法,测试有一点问题,所以做了修改.

可以看到该算法,基本上是一行一行着色的,这样的话在大块需要着色区域的效率比算法一要高很多。

ok,关于算法的步骤大家目前觉得模糊,一会可以参照我们的代码。选定了算法以后,接下来就开始编码了。


三、编码实现

我们代码中引入了一个边界颜色,如果设置的话,着色的边界参考为该边界颜色,否则会只要与种子颜色不一致为边界。

(一)构造方法与测量
public class ColourImageView extends ImageView{    private Bitmap mBitmap;    /**     * 边界的颜色     */    private int mBorderColor = -1;    private boolean hasBorderColor = false;    private Stack<Point> mStacks = new Stack<Point>();    public ColourImageView(Context 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();        //以宽度为标准,等比例缩放view的高度        setMeasuredDimension(viewWidth,                getDrawable().getIntrinsicHeight() * viewWidth / getDrawable().getIntrinsicWidth());        L.e("view's width = " + getMeasuredWidth() + " , view's height = " + getMeasuredHeight());        //根据drawable,去得到一个和view一样大小的bitmap        BitmapDrawable drawable = (BitmapDrawable) getDrawable();        Bitmap bm = drawable.getBitmap();        mBitmap = Bitmap.createScaledBitmap(bm, getMeasuredWidth(), getMeasuredHeight(), false);    }
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

可以看到我们选择的是继承ImageView,这样只需要将图片设为src即可。
构造方法中获取我们的自定义边界颜色,当然可以不设置~~
重写测量的目的是为了获取一个和View一样大小的Bitmap便于我们操作。

接下来就是点击啦~

(二)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)        {            //填色            fillColorToSameArea(x, y);        }        return super.onTouchEvent(event);    }    /**     * 根据x,y获得改点颜色,进行填充     *     * @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();        //拿到该bitmap的颜色数组        int[] pixels = new int[w * h];        bm.getPixels(pixels, 0, w, 0, 0, w, h);        //填色        fillColor(pixels, w, h, pixel, newColor, x, y);        //重新设置bitmap        bm.setPixels(pixels, 0, w, 0, 0, w, h);        setImageDrawable(new BitmapDrawable(bm));    }
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

可以看到,我们在onTouchEvent中获取(x,y),然后拿到改点坐标:

  • 获得点击点颜色,获得整个bitmap的像素数组
  • 改变这个数组中的颜色
  • 然后重新设置给bitmap,重新设置给ImageView

重点就是通过fillColor去改变数组中的颜色

/**     * @param pixels   像素数组     * @param w        宽度     * @param h        高度     * @param pixel    当前点的颜色     * @param newColor 填充色     * @param i        横坐标     * @param j        纵坐标     */    private void fillColor(int[] pixels, int w, int h, int pixel, int newColor, int i, int j)    {        //步骤1:将种子点(x, y)入栈;        mStacks.push(new Point(i, j));        //步骤2:判断栈是否为空,        // 如果栈为空则结束算法,否则取出栈顶元素作为当前扫描线的种子点(x, y),        // y是当前的扫描线;        while (!mStacks.isEmpty())        {            /**             * 步骤3:从种子点(x, y)出发,沿当前扫描线向左、右两个方向填充,             * 直到边界。分别标记区段的左、右端点坐标为xLeft和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;            /**             * 步骤4:             * 分别检查与当前扫描线相邻的y - 1和y + 1两条扫描线在区间[xLeft, xRight]中的像素,             * 从xRight开始向xLeft方向搜索,假设扫描的区间为AAABAAC(A为种子点颜色),             * 那么将B和C前面的A作为种子点压入栈中,然后返回第(2)步;             */            //从y-1找种子            if (seed.y - 1 >= 0)                findSeedInNewLine(pixels, pixel, w, h, seed.y - 1, left, right);            //从y+1找种子            if (seed.y + 1 < h)                findSeedInNewLine(pixels, pixel, w, h, seed.y + 1, left, right);        }    }
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

可以看到我已经很清楚的将该算法的四个步骤标识到该方法中。好了,最后就是一些依赖的细节上的方法:

 /**     * 在新行找种子节点     *     * @param pixels     * @param pixel     * @param w     * @param h     * @param i     * @param left     * @param right     */    private void findSeedInNewLine(int[] pixels, int pixel, int w, int h, int i, int left, int right)    {        /**         * 获得该行的开始索引         */        int begin = i * w + left;        /**         * 获得该行的结束索引         */        int end = i * w + right;        boolean hasSeed = false;        int rx = -1, ry = -1;        ry = i;        /**         * 从end到begin,找到种子节点入栈(AAABAAAB,则B前的A为种子节点)         */        while (end >= begin)        {            if (pixels[end] == pixel)            {                if (!hasSeed)                {                    rx = end % w;                    mStacks.push(new Point(rx, ry));                    hasSeed = true;                }            } else            {                hasSeed = false;            }            end--;        }    }    /**     * 往右填色,返回填充的个数     *     * @return     */    private int fillLineRight(int[] pixels, int pixel, int w, int h, int newColor, int x, int y)    {        int count = 0;        while (x < w)        {            //拿到索引            int index = y * w + x;            if (needFillPixel(pixels, pixel, index))            {                pixels[index] = newColor;                count++;                x++;            } else            {                break;            }        }        return count;    }    /**     * 往左填色,返回填色的数量值     *     * @return     */    private int fillLineLeft(int[] pixels, int pixel, int w, int h, int newColor, int x, int y)    {        int count = 0;        while (x >= 0)        {            //计算出索引            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;        }    }    /**     * 返回一个随机颜色     *     * @return     */    private int randomColor()    {        Random random = new Random();        int color = Color.argb(255, random.nextInt(256), random.nextInt(256), random.nextInt(256));        return color;    }
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127

ok,到此,代码就介绍完毕了~~~

最后贴下布局文件~~

<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: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.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|reference"></attr>    </declare-styleable></resources>
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

参考链接

ok~

源码点击下载,欢迎star or fork ~~~

 

群号:264950424,欢迎入群

   

微信公众号:hongyangAndroid
  (欢迎关注,第一时间推送博文信息)
 

           

给我老师的人工智能教程打call!http://blog.csdn.net/jiangjunshow
这里写图片描述
你好! 这是你第一次使用 **Markdown编辑器** 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。

新的改变

我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:

  1. 全新的界面设计 ,将会带来全新的写作体验;
  2. 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
  3. 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
  4. 全新的 KaTeX数学公式 语法;
  5. 增加了支持甘特图的mermaid语法1 功能;
  6. 增加了 多屏幕编辑 Markdown文章功能;
  7. 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
  8. 增加了 检查列表 功能。

功能快捷键

撤销:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G

合理的创建标题,有助于目录的生成

直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC语法后生成一个完美的目录。

如何改变文本的样式

强调文本 强调文本

加粗文本 加粗文本

标记文本

删除文本

引用文本

H2O is是液体。

210 运算结果是 1024.

插入链接与图片

链接: link.

图片: Alt

带尺寸的图片: Alt

当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。

如何插入一段漂亮的代码片

博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片.

// An highlighted block var foo = 'bar'; 

生成一个适合你的列表

  • 项目
    • 项目
      • 项目
  1. 项目1
  2. 项目2
  3. 项目3
  • 计划任务
  • 完成任务

创建一个表格

一个简单的表格是这么创建的:

项目Value
电脑$1600
手机$12
导管$1

设定内容居中、居左、居右

使用:---------:居中
使用:----------居左
使用----------:居右

第一列第二列第三列
第一列文本居中第二列文本居右第三列文本居左

SmartyPants

SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:

TYPEASCIIHTML
Single backticks'Isn't this fun?'‘Isn’t this fun?’
Quotes"Isn't this fun?"“Isn’t this fun?”
Dashes-- is en-dash, --- is em-dash– is en-dash, — is em-dash

创建一个自定义列表

Markdown
Text-to- HTML conversion tool
Authors
John
Luke

如何创建一个注脚

一个具有注脚的文本。2

注释也是必不可少的

Markdown将文本转换为 HTML

KaTeX数学公式

您可以使用渲染LaTeX数学表达式 KaTeX:

Gamma公式展示 Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n1)!nN 是通过欧拉积分

Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t &ThinSpace; . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=0tz1etdt.

你可以找到更多关于的信息 LaTeX 数学表达式here.

新的甘特图功能,丰富你的文章

gantt
        dateFormat  YYYY-MM-DD
        title Adding GANTT diagram functionality to mermaid
        section 现有任务
        已完成               :done,    des1, 2014-01-06,2014-01-08
        进行中               :active,  des2, 2014-01-09, 3d
        计划一               :         des3, after des2, 5d
        计划二               :         des4, after des3, 5d
  • 关于 甘特图 语法,参考 这儿,

UML 图表

可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图::

张三 李四 王五 你好!李四, 最近怎么样? 你最近怎么样,王五? 我很好,谢谢! 我很好,谢谢! 李四想了很长时间, 文字太长了 不适合放在一行. 打量着王五... 很好... 王五, 你怎么样? 张三 李四 王五

这将产生一个流程图。:

链接
长方形
圆角长方形
菱形
  • 关于 Mermaid 语法,参考 这儿,

FLowchart流程图

我们依旧会支持flowchart的流程图:

  • 关于 Flowchart流程图 语法,参考 这儿.

导出与导入

导出

如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。

导入

如果你想加载一篇你写过的.md文件或者.html文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。


  1. mermaid语法说明 ↩︎

  2. 注脚的解释 ↩︎

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值