在Android中用ndk层egl/opengl es显示java层打开的图像

本文介绍了如何在Android的java层打开一张图片,并在native层进行渲染的方法。可以分为两个部分:
1. 在java层打开图片并通过jni接口将图像数据传给native层;
2. 在ndk层通过egl和opengl es来渲染图像;

首先,我们建立一个空的工程,并在activity_main.xml文件中添加SurfaceView:

<SurfaceView android:id="@+id/SurfaceView01"
      android:layout_width="match_parent"
      android:layout_height="match_parent"/>
  • 1
  • 2
  • 3

然后,在工程目录下创建jni目录,并添加如下文件:
Application.mk

APP_ABI := all
  • 1

Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := eglbitmapdemo
LOCAL_SRC_FILES := eglbitmapdemo-jni.cpp \
                   EGLEngine.cpp \
                   GLRender.cpp

LOCAL_LDLIBS    := -llog -lGLESv2 -lEGL -landroid -ljnigraphics

include $(BUILD_SHARED_LIBRARY) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

eglbitmapdemo-jni.cpp

#include <jni.h>
#include <android/bitmap.h>
#include "EGLEngine.h"
#include "JNILog.h"

EGLEngine *eglEngine = NULL;

#ifdef __cplusplus
extern "C" {
#endif

int
Java_com_example_eglbitmapdemo_MainActivity_setupGraphic( JNIEnv* env, jobject thiz, jobject jsurface)
{
    int ret = 0;
    ANativeWindow *nativeWindow = NULL;

    LOGI("Java_com_example_hellojni_HelloJni_setupGraphic enter");

    nativeWindow = ANativeWindow_fromSurface(env, jsurface);

    if (eglEngine) {
        return 0;
    }

    eglEngine = new EGLEngine();
    if (!eglEngine) {
        goto failed;
    }

    ret = eglEngine->init(nativeWindow);
    if (ret) {
        goto failed;
    }

    return 0;
failed:
    if (eglEngine)
        delete eglEngine;
    return -1;
}


int
Java_com_example_eglbitmapdemo_MainActivity_showBitmap(JNIEnv* env, jobject thiz, jobject jbitmap)
{
    int ret = 0;
    EGLEngine *egl = NULL;
    AndroidBitmapInfo bitmapInfo;
    void *pixels = NULL;
    int imgWidth = 2;
    int imgHeight = 2;

    LOGI("Java_com_example_hellojni_HelloJni_showBitmap enter");

    if ((ret = AndroidBitmap_getInfo(env, jbitmap, &bitmapInfo)) < 0) {
        LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
        return -1;
    }

    LOGI("Java_com_example_hellojni_HelloJni_showBitmap width %d, height %d, format %d",
            bitmapInfo.width, bitmapInfo.height, bitmapInfo.format);
    imgWidth = bitmapInfo.width;
    imgHeight = bitmapInfo.height;

    if (bitmapInfo.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
        LOGE("Java_com_example_hellojni_HelloJni_showBitmap invalid rgb format");
        return -1;
    }

    if ((ret = AndroidBitmap_lockPixels(env, jbitmap, &pixels)) < 0) {
        LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
    }

    eglEngine->loadImage(pixels, imgWidth, imgHeight);
    eglEngine->draw();

    AndroidBitmap_unlockPixels(env, jbitmap);

    return 0;
}

#ifdef __cplusplus
}
#endif
  • 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

EGLEngine.cpp

/*
 * EGLEngine.cpp
 *
 *  Created on: 2015103日
 *      Author: leon
 */
#include "EGLEngine.h"


EGLEngine::EGLEngine() {
    mWin = NULL;
}

EGLEngine::~EGLEngine() {

}

int EGLEngine::init(ANativeWindow * surface) {
    if (!surface) {
        return -1;
    }

    mWin = surface;

    GLint majorVersion;
    GLint minorVersion;

    mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if(EGL_NO_DISPLAY == mEglDisplay) {
        return -1;
    }

    if(!eglInitialize(mEglDisplay, &majorVersion, &minorVersion)) {
        return -1;
    }

    EGLint config_attribs[] = {
        EGL_BLUE_SIZE, 8,
        EGL_GREEN_SIZE, 8,
        EGL_RED_SIZE, 8,
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
        EGL_NONE
    };

    int num_configs = 0;
    EGLConfig   eglConfig;
    if(!eglChooseConfig(mEglDisplay, config_attribs, &eglConfig, 1, &num_configs))
    {
        return -1;
    }

    mEglSurface = eglCreateWindowSurface(mEglDisplay, eglConfig, mWin, NULL);
    if(EGL_NO_SURFACE == mEglSurface) {
        return -1;
    }

    if(!eglQuerySurface(mEglDisplay, mEglSurface, EGL_WIDTH, &mSurfaceWidth) ||
       !eglQuerySurface(mEglDisplay, mEglSurface, EGL_HEIGHT, &mSurfaceHeight)) {
        return -1;
    }

    EGLint context_attrib[] = {
        EGL_CONTEXT_CLIENT_VERSION, 2,
        EGL_NONE
    };

    mEglContext = eglCreateContext(mEglDisplay, eglConfig, EGL_NO_CONTEXT, context_attrib);
    if(EGL_NO_CONTEXT == mEglContext) {
        return -1;
    }

    if(!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
        return -1;
    }

    glViewport(0, 0, mSurfaceWidth, mSurfaceHeight);
    glClearColor(1.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);
    glDisable(GL_CULL_FACE);

    LOGI("EGLEngine init success: surface width %d, surface height %d", mSurfaceWidth, mSurfaceHeight);

    imageRender = new GLRender();

    return 0;
}

void EGLEngine::release() {
    if (mEglDisplay != EGL_NO_DISPLAY) {
        eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
        if (mEglContext != EGL_NO_CONTEXT) {
            eglDestroyContext(mEglDisplay, mEglContext);
        }
        if (mEglSurface != EGL_NO_SURFACE) {
            eglDestroySurface(mEglDisplay, mEglSurface);

        }

        if (!eglTerminate(mEglDisplay)) {

        }
    }

    mEglDisplay = EGL_NO_DISPLAY;
    mEglContext = EGL_NO_CONTEXT;
    mEglSurface = EGL_NO_SURFACE;
}

int EGLEngine::loadImage(void *pixels, int width, int height) {
    int ret = 0;

    LOGI("EGLEngine::loadImage width %d, height %d", width, height);

    ret = imageRender->setup(pixels, width, height);
    if (ret) {
        LOGE("EGLEngine::loadImage failed");
        return -1;
    }

    LOGI("EGLEngine::loadImage done");

    return 0;
}

int EGLEngine::draw() {
    int ret = 0;

    ret = imageRender->draw();
    if (ret) {
        LOGE("EGLEngine::draw failed");
        return -1;
    }

    eglSwapBuffers(mEglDisplay, mEglSurface);

    LOGI("EGLEngine::draw done");

    return 0;
}
  • 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
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140

GLRender.cpp

/*
 * GLRender.cpp
 *
 *  Created on: 2015年10月4日
 *      Author: leon
 */

#include "GLRender.h"


GLRender::GLRender():
mIndices(NULL),
mVertex(NULL),
mVertexSize(0),
mIndicesSize(0){
    mVertexShader = STRINGIZE(
        attribute vec4 a_position;
        attribute vec2 a_tex_coord_in;
        varying vec2 v_tex_coord_out;

        void main(void) {
            v_tex_coord_out = a_tex_coord_in;
            gl_Position = a_position;
        }
    );

    mFragmentShader = STRINGIZE(
        precision highp float;
        varying highp vec2 v_tex_coord_out;
        uniform sampler2D s_texture;
        void main() {
            vec4 mzs = texture2D(s_texture, v_tex_coord_out);
            gl_FragColor = vec4(mzs.rgb, 0.0);
        }
    );
}

GLRender::~GLRender() {
    if (mGlVShader) {
        glDetachShader(mGlProgram, mGlVShader);
        glDeleteShader(mGlVShader);
        mGlVShader = 0;
    }
    if (mGlFShader) {
        glDetachShader(mGlProgram, mGlFShader);
        glDeleteShader(mGlFShader);
        mGlFShader = 0;
    }
    if (mGlProgram) {
        glDeleteProgram(mGlProgram);
        mGlProgram = 0;
    }
}

int GLRender::setup(void *pixels, int width, int height) {
    LOGI("GLRender::setup enter, pixels %x, width %d, height %d", pixels, width, height);

    mGlProgram = createProgram(mVertexShader, mFragmentShader);
    if (!mGlProgram) {
        LOGE("GLRender::setup createProgram failed");
        return -1;
    }

    glUseProgram(mGlProgram);

    if (setupVertex()) {
        LOGE("GLRender::setup setupVertex failed");
        return -1;
    }

    if (setupTexture()) {
        LOGE("GLRender::setup setupTexture failed");
        return -1;
    }

    if (loadImage((uint8_t *)pixels, width, height)) {
        LOGE("GLRender::setup loadImage failed");
        return -1;
    }

    LOGI("GLRender::setup done");

    return 0;
}

GLuint GLRender::createProgram(const char *vshaderSrc, const char *fshaderSrc) {
    GLuint programObject;
    GLint  linked;

    mGlVShader = loadShader(GL_VERTEX_SHADER, vshaderSrc);
    if (!mGlVShader) {
        LOGE("vertex shader initialize wrong. ");
        return (0);
    }
    mGlFShader = loadShader(GL_FRAGMENT_SHADER, fshaderSrc);
    if (!mGlFShader)
    {
        LOGE("fragment shader initialize wrong. ");
        return (0);
    }

    //Create the program object
    programObject = glCreateProgram();

    if (programObject == 0)
        return (0);

    glAttachShader(programObject, mGlVShader);
    glAttachShader(programObject, mGlFShader);

    //Link the program
    glLinkProgram(programObject);

    //Check the link status
    glGetProgramiv(programObject, GL_LINK_STATUS, &linked);

    if(!linked)
    {
        GLint infoLen;

        glGetProgramiv(programObject, GL_INFO_LOG_LENGTH, &infoLen);
        LOGI("Error linking length: %d", infoLen);

        if(infoLen > 1)
        {
            char* infoLog = (char*)malloc(infoLen);

            glGetProgramInfoLog(programObject, infoLen, NULL, infoLog);
            LOGE("Error linking program: %s, %s", infoLog, glGetString(glGetError()));


            free(infoLog);
        }

        glDeleteProgram(programObject);
        return (0);
    }

    //Store the program object

    return (programObject);
}

GLuint GLRender::loadShader(GLenum type, const char *shaderSrc) {
    GLuint shader;
    shader = glCreateShader(type);
    if (shader) {
        glShaderSource(shader,1, &shaderSrc, NULL);
        glCompileShader(shader);
        GLint compile;
        compile = 0;
        glGetShaderiv(shader, GL_COMPILE_STATUS, &compile);
        if (!compile) {
            GLint infoLen = 0;
            glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
            if(infoLen > 1)
            {
                char* infoLog = (char*)malloc(infoLen);

                glGetShaderInfoLog(shader, infoLen, NULL, infoLog);
                LOGI( "Error compiling shader: %s", infoLog);
                free(infoLog);
            }

            glDeleteShader(shader);
            return (0);
        }
    } else
    {
        return (0);
    }
    return (shader);
}

int GLRender::setupVertex() {
    Vertex vertices[4];
    GLubyte indices[] = {0, 1, 2, 2, 3, 0};
    GLuint a_position;
    GLuint a_tex_coord_in;

    mVertex = (Vertex *)calloc(sizeof(Vertex)*4, 1);
    if (!mVertex) {
        goto failed;
    }
    mIndices = (GLubyte *)calloc(sizeof(GLubyte)*6, 1);
    if (!mIndices) {
        goto failed;
    }

    vertices[0] = { {1.0f, -1.0f, 0.0f}, {1 ,1}};
    vertices[1] = { {1.0f, 1.0f, 0.0f}, {1,0}};
    vertices[2] = { {-1.0f, 1.0f, 0.0f}, {0,0}};
    vertices[3] = { {-1.0f, -1.0f, 0.0f}, {0,1}};

    memcpy(mVertex, vertices, sizeof(vertices));
    memcpy(mIndices, indices, sizeof(indices));
    mVertexSize = sizeof(vertices);
    mIndicesSize = sizeof(indices);

    glGenBuffers(1, &mVertexBuf);
    glGenBuffers(1, &mIndicesBuf);

    a_position = glGetAttribLocation(mGlProgram, "a_position");
    a_tex_coord_in = glGetAttribLocation(mGlProgram, "a_tex_coord_in");

    glEnableVertexAttribArray(a_position);
    glEnableVertexAttribArray(a_tex_coord_in);

    glBindBuffer(GL_ARRAY_BUFFER, mVertexBuf);
    glBufferData(GL_ARRAY_BUFFER, mVertexSize, mVertex, GL_STATIC_DRAW);

    glVertexAttribPointer(a_position, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
    glVertexAttribPointer(a_tex_coord_in, 2, GL_FLOAT, GL_TRUE, sizeof(Vertex),
            (GLvoid*) (sizeof(float) * 3));

    return 0;
failed:
    if (mVertex) {
        free(mVertex);
        mVertex = NULL;
    }

    if (mIndices) {
        free(mIndices);
        mIndices = NULL;
    }

    return -1;
}

int GLRender::setupTexture() {
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    glActiveTexture(GL_TEXTURE0);

    glGenTextures(1, &mTextureId);
    glBindTexture(GL_TEXTURE_2D, mTextureId);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    return 0;
}

int GLRender::loadImage(uint8_t *pixels, int width, int height) {
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, mTextureId);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
                 GL_RGBA, GL_UNSIGNED_BYTE, pixels);
    GLuint location = glGetUniformLocation(mGlProgram, "s_texture");
    glUniform1i(location, 0);

    return 0;
}

int GLRender::draw() {
    glUseProgram(mGlProgram);

    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

    return 0;
}
  • 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
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263

用ndk-build编译出jni动态库文件。

将要显示的图像拷贝到assets目录下。

编辑MainActivity.java代码如下:

package com.example.eglbitmapdemo;

import java.io.IOException;
import java.io.InputStream;

import android.app.Activity;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        SurfaceView surfaceView = (SurfaceView) findViewById(R.id.SurfaceView01);

        SurfaceHolder surfaceHolder = surfaceView.getHolder();
        surfaceHolder.addCallback(new SurfaceHolder.Callback() {
            public void surfaceChanged(SurfaceHolder holder, int format,
                    int width, int height) {

            }

            public void surfaceCreated(SurfaceHolder holder) {
                AssetManager manager = getAssets();
                InputStream open;
                Bitmap bitmap;
                try {
                    open = manager.open("dog.jpg");
                    bitmap = BitmapFactory.decodeStream(open);
                    setupGraphic(holder.getSurface());                  
                    showBitmap(bitmap);
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }                           
            }

            public void surfaceDestroyed(SurfaceHolder holder) {

            }
        });
    }

    public native int setupGraphic(Surface surface);    
    public native int showBitmap(Bitmap bitmap);

    static {
        System.loadLibrary("eglbitmapdemo");
    }
}
  • 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

至此,我们的主要代码就结束了。
它的工作原理是,通过java代码生成图像的Bitmap对象,通过jni接口将SurfaceView对象和Bitmap对象传给native代码,native代码从传入的Bitmap对象中获取rgb数据,并通过egl和opengl es进行渲染。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值