使用过PS的朋友应该都知道,PS利用滤镜可以非常方便的更改图片整体色彩。用过诸如美图秀秀之类的傻瓜式图片处理工具也应该记得,他们可以直接选择图片的风格改为冷色调、暖色调、复古、黑白等等。这些是怎么实现的呢?
在Android应用当中,我们设置文字的颜色,控件的背景色,会在资源文件中进行诸如<color name="bg_color">#FF88269F</color>之类的定义。这个#FF88269F就是我们期望的色彩,我们可以把这个色彩分为4部分,每两位组成一部分,分别表示A(透明通道)、R(红色通道)、G(绿色通道)、B(蓝色通道)。每一部分都由两位十六进制的数表示,也就是取值范围都是0-255,根据各个通道的比率不同,显示出来的最终色彩也就不同了。
在其他的编程语言中,也是和这种方式大同小异。在GLSL中,颜色是用包含四个浮点的向量vec4表示,四个浮点分别代表RGBA四个通道,取值范围为0.0-1.0。我们先读取图片每个像素的色彩值,再对读取的色彩纸进行调整,这样可以完成对图片的色彩处理了。
我们应该都知道,黑白图片上,每个像素的RGB三个通道值应该是相等的。知道这个,将彩色图片处理成黑白图片就非常简单了。我们直接算出像素点的RGB三个通道,相加后除以3作为处理后每个通道的值就可以得到一个黑白图片了。这均值的方式是常见黑白图片处理的一种方法。类似的还有权值方法(给予RGB三个通道不同的比例)、只取绿色通道等等方式。
与之类似的,冷色调的处理就是好单一增加蓝色通道的值,暖色调的处理可以增加红绿通道的值。还有其他复古、浮雕等处理也差不多。
黑白
我们计算出像素点RGB三个通道,然后对应相乘再相加。如下的片元着色器代码:
顶点着色器
private final String vertextShaderCode =
"attribute vec4 vPosition;" +
"uniform mat4 vMatrix;" +
"attribute vec2 vCoordinate;" +
"varying vec2 aCoordinate;" +
"void main() {" +
" gl_Position = vMatrix * vPosition;" +
" aCoordinate=vCoordinate;" +
"}";
片元着色器
计算出像素点RGB三个通道,然后对应相乘再相加
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform sampler2D vTexture;" +
"uniform vec3 vChangeColor;" +
"varying vec2 aCoordinate;" +
"void main() {" +
" vec4 nColor=texture2D(vTexture,aCoordinate);" +
" float c=nColor.r*vChangeColor.r+nColor.g*vChangeColor.g+nColor.b*vChangeColor.b;" +
" gl_FragColor=vec4(c,c,c,nColor.a);" +
"}";
顶点坐标
private float[] shapePos = {
-1.0f, 1.0f, //左上角
-1.0f, -1.0f, //左下角
1.0f, 1.0f, //右上角
1.0f, -1.0f //右下角
};
纹理坐标
private float[] sCoord = {
0.0f, 0.0f,
0.0f, 1.0f,
1.0f, 0.0f,
1.0f, 1.0f
};
完整代码:
public class MyTenthRender implements GLSurfaceView.Renderer
{
private final String vertextShaderCode =
"attribute vec4 vPosition;" +
"uniform mat4 vMatrix;" +
"attribute vec2 vCoordinate;" +
"varying vec2 aCoordinate;" +
"void main() {" +
" gl_Position = vMatrix * vPosition;" +
" aCoordinate=vCoordinate;" +
"}";
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform sampler2D vTexture;" +
"uniform vec3 vChangeColor;" +
"varying vec2 aCoordinate;" +
"void main() {" +
" vec4 nColor=texture2D(vTexture,aCoordinate);" +
" float c=nColor.r*vChangeColor.r+nColor.g*vChangeColor.g+nColor.b*vChangeColor.b;" +
" gl_FragColor=vec4(c,c,c,nColor.a);" +
"}";
private final Context context;
private int program;
private FloatBuffer vertexBuffer;
//正交矩阵
private final float[] mProjectMatrix = new float[16];
//相机位置矩阵
private final float[] mViewMatrix = new float[16];
//计算变换矩阵
private final float[] mMVPMatrix = new float[16];
private float[] shapePos = {
-1.0f, 1.0f, //左上角
-1.0f, -1.0f, //左下角
1.0f, 1.0f, //右上角
1.0f, -1.0f //右下角
};
private float[] sCoord = {
0.0f, 0.0f,
0.0f, 1.0f,
1.0f, 0.0f,
1.0f, 1.0f
};
private Bitmap mBitmap;
private FloatBuffer coordBuffer;
private int[] texture;
private float gray[] = new float[]{0.299f,0.587f,0.114f};
public MyTenthRender(Context context)
{
this.context = context;
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config)
{
// GLES20.glEnable(GLES20.GL_DEPTH_TEST);
//将背景设置为灰色
GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
//申请底层空间
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(shapePos.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
//将坐标数据转换为FloatBuffer,用以传入给OpenGL ES程序
vertexBuffer = byteBuffer.asFloatBuffer();
//将三角形坐标传入FloatBuffer
vertexBuffer.put(shapePos);
vertexBuffer.position(0);
//申请底层空间
ByteBuffer coordByteBuffer = ByteBuffer.allocateDirect(sCoord.length * 4);
coordByteBuffer.order(ByteOrder.nativeOrder());
//将坐标数据转换为FloatBuffer,用以传入给OpenGL ES程序
coordBuffer = coordByteBuffer.asFloatBuffer();
//将纹理坐标传入FloatBuffer
coordBuffer.put(sCoord);
coordBuffer.position(0);
//创建顶点着色器程序
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertextShaderCode);
//创建片元着色器程序
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
if (vertexShader == 0 || fragmentShader == 0)
{
return;
}
//创建一个空的OpenGL ES程序
program = GLES20.glCreateProgram();
//将顶点着色器加入程序
GLES20.glAttachShader(program, vertexShader);
//将片元着色器加入程序
GLES20.glAttachShader(program, fragmentShader);
//连接到着色器程序中
GLES20.glLinkProgram(program);
//使用程序
GLES20.glUseProgram(program);
texture = new int[1];
//生成一个纹理,纹理ID放在int[] texture
GLES20.glGenTextures(1, texture, 0);
mBitmap = BitmapFactory.decodeResource(context.getResources(), R.mipmap.bg);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height)
{
GLES20.glViewport(0, 0, width, height);
int w = mBitmap.getWidth();
int h = mBitmap.getHeight();
float sWH = w / (float) h;
float sWidthHeight = width / (float) height;
if (width > height)
{
if (sWH > sWidthHeight)
{
//设置正交矩阵
Matrix.orthoM(mProjectMatrix, 0, -sWidthHeight * sWH, sWidthHeight * sWH, -1, 1, 3, 7);
} else
{
//设置正交矩阵
Matrix.orthoM(mProjectMatrix, 0, -sWidthHeight / sWH, sWidthHeight / sWH, -1, 1, 3, 7);
}
} else
{
if (sWH > sWidthHeight)
{
//设置正交矩阵
Matrix.orthoM(mProjectMatrix, 0, -1, 1, -1 / sWidthHeight * sWH, 1 / sWidthHeight * sWH, 3, 7);
} else
{
//设置正交矩阵
Matrix.orthoM(mProjectMatrix, 0, -1, 1, -sWH / sWidthHeight, sWH / sWidthHeight, 3, 7);
}
}
//设置相机位置
Matrix.setLookAtM(mViewMatrix, 0, 0, 0, 7.0f, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
//计算变换矩阵
Matrix.multiplyMM(mMVPMatrix, 0, mProjectMatrix, 0, mViewMatrix, 0);
}
@Override
public void onDrawFrame(GL10 gl)
{
if (program == 0)
return;
//获取变换矩阵vMatrix成员句柄
int vMatrix = GLES20.glGetUniformLocation(program, "vMatrix");
//设置vMatrix的值
GLES20.glUniformMatrix4fv(vMatrix, 1, false, mMVPMatrix, 0);
//获取顶点着色器的vPosition成员句柄
int vPosition = GLES20.glGetAttribLocation(program, "vPosition");
//启用vPosition句柄
GLES20.glEnableVertexAttribArray(vPosition);
//传的圆筒面的所有坐标数据
GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 2 * 4, vertexBuffer);
//获取顶点着色器的vCoordinate成员句柄
int vCoordinate = GLES20.glGetAttribLocation(program, "vCoordinate");
//启用vPosition句柄
GLES20.glEnableVertexAttribArray(vCoordinate);
//传的纹理坐标数据
GLES20.glVertexAttribPointer(vCoordinate, 2, GLES20.GL_FLOAT, false, 2 * 4, coordBuffer);
//传入rgb颜色用于与图片的rgb计算
int vChangeColor = GLES20.glGetUniformLocation(program,"vChangeColor");
GLES20.glUniform3fv(vChangeColor,1,gray,0);
//将已经创建并命名的纹理绑定到一个纹理目标(GL_TEXTURE_2D)上
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture[0]);
//设置缩小过滤为使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
//设置放大过滤为使用纹理中坐标最接近的若干个颜色,通过加权平均算法得到需要绘制的像素颜色
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
//设置环绕方向S:指定横向模式为GL_CLAMP_TO_EDGE
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
//设置环绕方向T:指定横向模式为GL_CLAMP_TO_EDGE
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
//将图片数据传到上面创建的纹理中
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, mBitmap, 0);
//绘制
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
//禁止顶点数组的句柄
GLES20.glDisableVertexAttribArray(vPosition);
}
public int loadShader(int type, String shaderCode)
{
//创建空的着色器
int shader = GLES20.glCreateShader(type);
//将着色器程序加到着色器中
GLES20.glShaderSource(shader, shaderCode);
//编译色器程序
GLES20.glCompileShader(shader);
return shader;
}
}