Recently a little time, look at GPUImageView source code, here mainly static for GPUImageView set a Bitmap and add filter process based on the analysis of the working principle.
Simple to use
GPUImageView is simple to use:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<jp.co.cyberagent.android.gpuimage.GPUImageView
android:id="@+id/gpuImageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
app:gpuimage_surface_type="texture_view" />
</RelativeLayout>
Copy the code
Simply add GPUImageView to the XML file with a property app:gpuimage_surface_type=”texture_view”, which means to draw the image using textureView as a proxy, and then in the code:
val bitmap = xxx// Get the bitmap object
val ratio = bitmap.width.toFloat() / bitmap.height.toFloat()// This step is to set the fixed aspect ratio
gpuImageView.setImage(bitmap)// Set the image resource
gpuImageView.filter = GPUImageFilter()// Set the filter
gpuImageView.requestRender()// this is called whenever you need to update the drawing
Copy the code
Ok, the process is simple, add a Bitmap image and add a default filter (the original image).
GPUImageView
Let’s look at the definition of GPUImageView:
public class GPUImageView extends FrameLayout {
private int surfaceType = SURFACE_TYPE_SURFACE_VIEW;
private View surfaceView;
private View coverView;
private GPUImage gpuImage;
private boolean isShowLoading = true;
private GPUImageFilter filter;
public Size forceSize = null;
private float ratio = 0.0 f;
public final static int RENDERMODE_WHEN_DIRTY = 0;
public final static int RENDERMODE_CONTINUOUSLY = 1;
public GPUImageView(Context context) {
super(context);
init(context, null);
}
public GPUImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
GLUtil.init(context);// Save a reference to the current Context
if(attrs ! =null) {// Parse the XML to write some attributes are not more than two
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.GPUImageView, 0.0);
try {
surfaceType = a.getInt(R.styleable.GPUImageView_gpuimage_surface_type, surfaceType);// Surface type SurfaceView or TextureView
isShowLoading = a.getBoolean(R.styleable.GPUImageView_gpuimage_show_loading, isShowLoading);// Whether to display loading is useless
} finally {
a.recycle();
}
}
gpuImage = new GPUImage(context);// Create a GPUImage object and hold a reference to it
if (surfaceType == SURFACE_TYPE_TEXTURE_VIEW) {
surfaceView = new GPUImageGLTextureView(context, attrs);//GPUImageGLTextureView and holds its reference
gpuImage.setGLTextureView((GLTextureView) surfaceView);// Let GPUImage also hold a copy of its reference
} else {
surfaceView = new GPUImageGLSurfaceView(context, attrs);//GPUImageGLSurfaceView and holds its reference
gpuImage.setGLSurfaceView((GLSurfaceView) surfaceView);// Let GPUImage also hold a copy of its reference
}
addView(surfaceView);// Add the created surfaceView to your FrameLayout
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(ratio ! =0.0 f) {// Adjust width and height according to ratio
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int newHeight;
int newWidth;
if (width / ratio < height) {
newWidth = width;
newHeight = Math.round(width / ratio);
} else {
newHeight = height;
newWidth = Math.round(height * ratio);
}
int newWidthSpec = MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY);
int newHeightSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY);
super.onMeasure(newWidthSpec, newHeightSpec);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec); }}}Copy the code
The GPUImageView itself is a FrameLayout that adds a SurfaceView inside and then holds a reference to the GPUImage object. If ratio is set, the width and height will change according to ratio. When you initialize it, you’re going to initialize the OpenGL utility class, which is essentially saving a reference to the context; In addition, the surfaceView implementation is selected based on the familiar configuration of the current XML.
Then look at GPUImage:
public class GPUImage {
private final Context context;
private final GPUImageRenderer renderer;
/**
* Instantiates a new GPUImage object.
*
* @param context the context
*/
public GPUImage(final Context context) {
if(! supportsOpenGLES2(context)) {throw new IllegalStateException("OpenGL ES 2.0 is not supported on this phone.");
}
this.context = context;
filter = new GPUImageFilter();// The default filter is GPUImageFilter
renderer = new GPUImageRenderer(filter);// Create a GPUImageRender object and hold its reference}}Copy the code
The GPUImage is initialized with the original image filter, which is the GPUImageFilter, and then used to create a GPUImageRenderer object. Let’s look at the filter implementation later. Here’s the Renderer:
public class GPUImageRenderer implements GLSurfaceView.Renderer.GLTextureView.Renderer.PreviewCallback {
public static final float CUBE[] = {
-1.0 f, -1.0 f.1.0 f, -1.0 f,
-1.0 f.1.0 f.1.0 f.1.0 f};private GPUImageFilter filter;
private final FloatBuffer glCubeBuffer;
private final FloatBuffer glTextureBuffer;
private final Queue<Runnable> runOnDraw;
private final Queue<Runnable> runOnDrawEnd;
public GPUImageRenderer(final GPUImageFilter filter) {
this.filter = filter;
// Two Runnable queues, namely task queues
runOnDraw = new LinkedList<>();
runOnDrawEnd = new LinkedList<>();
// Vertex array and texture array
glCubeBuffer = ByteBuffer.allocateDirect(CUBE.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
glCubeBuffer.put(CUBE).position(0);
glTextureBuffer = ByteBuffer.allocateDirect(TEXTURE_NO_ROTATION.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
// Coordinate rotation
setRotation(Rotation.NORMAL, false.false);
}
/ /...
}
Copy the code
This Render will hold a reference to the filter with two task queues inside, as the name suggests. The other thing we’re going to do here is create two coordinate arrays using FloatBuffer. Buffer Here is the content of Java NIO, the coordinate array is a bit complex, involving a variety of coordinate transformations. First of all, we know that the screen coordinates in Android are the origin of the top left corner, the x axis to the right, and the Y axis to the bottom, as well as the coordinates of every View except the screen. In OpenGL, there are many, many coordinate concepts, and the main one is the world coordinate system, where the origin is right in the middle, the x is positive to the right, and the y is positive to the right, which is the same idea as the plane coordinate system that we use in mathematics. In addition, there is a texture coordinate system, the origin is the lower left corner of the texture, the right is the positive x direction, the upward is the positive Y direction, and the OpenGL coordinates are normalized values of -1 to 1 or 0-1. For example, if the screen width of the phone is 1080 and the abscess of a point on Android is 540, then in OpenGL it is 540/1080 = 0.5. If you want to display an image as an OpenGL texture, it involves a coordinate transformation or inversion, and I’m not sure what the logic is.
Back to GPUImageView, there are two implementations of the surfaceView, one is GPUImageGLTextureView, and the other is GPUImageGLSurfaceView. After the surfaceView object is created, GPUImage’s setGLTextureView or setGLSurfaceView is called to save a reference to the surfaceView:
/**
* Sets the GLSurfaceView which will display the preview.
*
* @param view the GLSurfaceView
*/
public void setGLSurfaceView(final GLSurfaceView view) {
surfaceType = SURFACE_TYPE_SURFACE_VIEW;
glSurfaceView = view;
// Do some initial configuration
glSurfaceView.setEGLContextClientVersion(2);
glSurfaceView.setEGLConfigChooser(8.8.8.8.16.0);
glSurfaceView.getHolder().setFormat(PixelFormat.RGBA_8888);
glSurfaceView.setRenderer(renderer);
glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
glSurfaceView.requestRender();
}
/**
* Sets the GLTextureView which will display the preview.
*
* @param view the GLTextureView
*/
public void setGLTextureView(final GLTextureView view) {
surfaceType = SURFACE_TYPE_TEXTURE_VIEW;
glTextureView = view;
// Do some initial configuration
glTextureView.setEGLContextClientVersion(2);
glTextureView.setEGLConfigChooser(8.8.8.8.16.0);
glTextureView.setOpaque(false);
glTextureView.setRenderer(renderer);// Also similar to SurfaceView, holds a Render reference
glTextureView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
glTextureView.requestRender();
}
Copy the code
It saves a reference to the View, and then sets up some OpenGL configuration, like version 2, color and depth, and then calls setRenderer() to make the view save a reference to the Renderer object in the current GPUImage object. Finally, set the render mode to active refresh (literally, refresh when the canvas is dirty, as opposed to passive refresh, which is automatically and repeatedly refreshed). Then actively call requestRender() to render once.
SurfaceView (surfaceView, surfaceView, surfaceView, surfaceView, surfaceView, surfaceView, surfaceView)
private class GPUImageGLSurfaceView extends GLSurfaceView {
public GPUImageGLSurfaceView(Context context) {
super(context);
}
public GPUImageGLSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(forceSize ! =null) {
super.onMeasure(MeasureSpec.makeMeasureSpec(forceSize.width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(forceSize.height, MeasureSpec.EXACTLY));
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec); }}}private class GPUImageGLTextureView extends GLTextureView {
public GPUImageGLTextureView(Context context) {
super(context);
}
public GPUImageGLTextureView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(forceSize ! =null) {
super.onMeasure(MeasureSpec.makeMeasureSpec(forceSize.width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(forceSize.height, MeasureSpec.EXACTLY));
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec); }}}Copy the code
GPUImageGLSurfaceView is directly inherited from the Android native GLSurfaceView. GPUImageGLTextureView is inherited from the GLTextureView class of GPUImage. But this GLTextureView is inherited from Android native TextureView, and GLTextureView implementation is very interesting, in fact, is completely according to the GLSurfaceView design. Let’s go back to where we set render for GPUImage. Using setGLTextureView as an example, we can see the setRenderer() method that calls textureView:
public void setRenderer(Renderer renderer) {
checkRenderThreadState();
if (eglConfigChooser == null) {
eglConfigChooser = new SimpleEGLConfigChooser(true);
}
if (eglContextFactory == null) {
eglContextFactory = new DefaultContextFactory();
}
if (eglWindowSurfaceFactory == null) {
eglWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
}
this.renderer = renderer;
// Create a GLThread, which is a thread, and call the start() method to start running
glThread = new GLThread(mThisWeakRef);
glThread.start();
}
Copy the code
If the configuration is correct, a GLThread object is created and the start() method is called. This thread is called GL and handles OpenGL ES calls. Look at the implementation:
static class GLThread extends Thread {
GLThread(WeakReference<GLTextureView> glTextureViewWeakRef) {
super(a); width =0;
height = 0;
requestRender = true;
renderMode = RENDERMODE_CONTINUOUSLY;
this.glTextureViewWeakRef = glTextureViewWeakRef;
}
@Override
public void run(a) {
setName("GLThread " + getId());
if (LOG_THREADS) {
Log.i("GLThread"."starting tid=" + getId());
}
try {
guardedRun();// What is guarded when run() checked?
} catch (InterruptedException e) {
// fall thru and exit normally
} finally {
glThreadManager.threadExiting(this); }}}Copy the code
MThisWeakRef is a weak reference to View itself, thread run() method will use try catch to safer call guardeRun() method:
private void guardedRun(a) throws InterruptedException {
eglHelper = new EglHelper(glTextureViewWeakRef);
haveEglContext = false;
haveEglSurface = false;
try {
GL10 gl = null;
boolean createEglContext = false;
boolean createEglSurface = false;
boolean createGlInterface = false;
boolean lostEglContext = false;
boolean sizeChanged = false;
boolean wantRenderNotification = false;
boolean doRenderNotification = false;
boolean askedToReleaseEglContext = false;
int w = 0;
int h = 0;
Runnable event = null;
while (true) {
synchronized (glThreadManager) {
while (true) {
if (shouldExit) {
return;
}
if(! eventQueue.isEmpty()) { event = eventQueue.remove(0);
break;
}
// Update the pause state.
boolean pausing = false;
if(paused ! = requestPaused) { pausing = requestPaused; paused = requestPaused; glThreadManager.notifyAll();if (LOG_PAUSE_RESUME) {
Log.i("GLThread"."paused is now " + paused + " tid="+ getId()); }}// Do we need to give up the EGL context?
if (shouldReleaseEglContext) {
if (LOG_SURFACE) {
Log.i("GLThread"."releasing EGL context because asked to tid=" + getId());
}
stopEglSurfaceLocked();
stopEglContextLocked();
shouldReleaseEglContext = false;
askedToReleaseEglContext = true;
}
// Have we lost the EGL context?
if (lostEglContext) {
stopEglSurfaceLocked();
stopEglContextLocked();
lostEglContext = false;
}
// When pausing, release the EGL surface:
if (pausing && haveEglSurface) {
if (LOG_SURFACE) {
Log.i("GLThread"."releasing EGL surface because paused tid=" + getId());
}
stopEglSurfaceLocked();
}
// When pausing, optionally release the EGL Context:
if (pausing && haveEglContext) {
GLTextureView view = glTextureViewWeakRef.get();
boolean preserveEglContextOnPause =
view == null ? false : view.preserveEGLContextOnPause;
if(! preserveEglContextOnPause || glThreadManager.shouldReleaseEGLContextWhenPausing()) { stopEglContextLocked();if (LOG_SURFACE) {
Log.i("GLThread"."releasing EGL context because paused tid="+ getId()); }}}// When pausing, optionally terminate EGL:
if (pausing) {
if (glThreadManager.shouldTerminateEGLWhenPausing()) {
eglHelper.finish();
if (LOG_SURFACE) {
Log.i("GLThread"."terminating EGL because paused tid="+ getId()); }}}// Have we lost the TextureView surface?
if((! hasSurface) && (! waitingForSurface)) {if (LOG_SURFACE) {
Log.i("GLThread"."noticed textureView surface lost tid=" + getId());
}
if (haveEglSurface) {
stopEglSurfaceLocked();
}
waitingForSurface = true;
surfaceIsBad = false;
glThreadManager.notifyAll();
}
// Have we acquired the surface view surface?
if (hasSurface && waitingForSurface) {
if (LOG_SURFACE) {
Log.i("GLThread"."noticed textureView surface acquired tid=" + getId());
}
waitingForSurface = false;
glThreadManager.notifyAll();
}
if (doRenderNotification) {
if (LOG_SURFACE) {
Log.i("GLThread"."sending render notification tid=" + getId());
}
wantRenderNotification = false;
doRenderNotification = false;
renderComplete = true;
glThreadManager.notifyAll();
}
// Ready to draw?
if (readyToDraw()) {
// If we don't have an EGL context, try to acquire one.
if(! haveEglContext) {if (askedToReleaseEglContext) {
askedToReleaseEglContext = false;
} else if (glThreadManager.tryAcquireEglContextLocked(this)) {
try {
eglHelper.start();
} catch (RuntimeException t) {
glThreadManager.releaseEglContextLocked(this);
throw t;
}
haveEglContext = true;
createEglContext = true; glThreadManager.notifyAll(); }}if(haveEglContext && ! haveEglSurface) { haveEglSurface =true;
createEglSurface = true;
createGlInterface = true;
sizeChanged = true;
}
if (haveEglSurface) {
if (this.sizeChanged) {
sizeChanged = true;
w = width;
h = height;
wantRenderNotification = true;
if (LOG_SURFACE) {
Log.i("GLThread"."noticing that we want render notification tid=" + getId());
}
// Destroy and recreate the EGL surface.
createEglSurface = true;
this.sizeChanged = false;
}
requestRender = false;
glThreadManager.notifyAll();
break; }}// By design, this is the only place in a GLThread thread where we wait().
if (LOG_THREADS) {
Log.i("GLThread"."waiting tid=" + getId() + " haveEglContext: " + haveEglContext
+ " haveEglSurface: " + haveEglSurface + " paused: " + paused + " hasSurface: "
+ hasSurface + " surfaceIsBad: " + surfaceIsBad + " waitingForSurface: "
+ waitingForSurface + " width: " + width + " height: " + height
+ " requestRender: " + requestRender + " renderMode: "+ renderMode); } glThreadManager.wait(); }}// end of synchronized(glThreadManager)
if(event ! =null) {
event.run();
event = null;
continue;
}
if (createEglSurface) {
if (LOG_SURFACE) {
Log.w("GLThread"."egl createSurface");
}
if(! eglHelper.createSurface()) {synchronized (glThreadManager) {
surfaceIsBad = true;
glThreadManager.notifyAll();
}
continue;
}
createEglSurface = false;
}
if (createGlInterface) {
gl = (GL10) eglHelper.createGL();
glThreadManager.checkGLDriver(gl);
createGlInterface = false;
}
if (createEglContext) {
if (LOG_RENDERER) {
Log.w("GLThread"."onSurfaceCreated");
}
GLTextureView view = glTextureViewWeakRef.get();
if(view ! =null) {
view.renderer.onSurfaceCreated(gl, eglHelper.eglConfig);
}
createEglContext = false;
}
if (sizeChanged) {
if (LOG_RENDERER) {
Log.w("GLThread"."onSurfaceChanged(" + w + "," + h + ")");
}
GLTextureView view = glTextureViewWeakRef.get();
if(view ! =null) {
view.renderer.onSurfaceChanged(gl, w, h);
}
sizeChanged = false;
}
if (LOG_RENDERER_DRAW_FRAME) {
Log.w("GLThread"."onDrawFrame tid=" + getId());
}
{
GLTextureView view = glTextureViewWeakRef.get();
if(view ! =null) { view.renderer.onDrawFrame(gl); }}int swapError = eglHelper.swap();
switch (swapError) {
case EGL10.EGL_SUCCESS:
break;
case EGL11.EGL_CONTEXT_LOST:
if (LOG_SURFACE) {
Log.i("GLThread"."egl context lost tid=" + getId());
}
lostEglContext = true;
break;
default:
// Other errors typically mean that the current surface is bad,
// probably because the TextureView surface has been destroyed,
// but we haven't been notified yet.
// Log the error to help developers understand why rendering stopped.
EglHelper.logEglErrorAsWarning("GLThread"."eglSwapBuffers", swapError);
synchronized (glThreadManager) {
surfaceIsBad = true;
glThreadManager.notifyAll();
}
break;
}
if (wantRenderNotification) {
doRenderNotification = true; }}}finally {
/* * clean-up everything... * /
synchronized(glThreadManager) { stopEglSurfaceLocked(); stopEglContextLocked(); }}}Copy the code
In the process of continuous loop execution, first set the environment variables, including the context, surface, etc., and then perform the drawing operation; Wait () will be blocked after glthreadManager.wait (), and the task will be executed when the refresh request is made. In passive refresh mode, tasks are executed in a continuous loop. GLTextureView view = glTextureViewWeakRef.get(); Get a reference to the current GLTextureView object and call its internal Render onSurfaceCreated onSurfaceChanged onDrawFrame method. The implementation of these three methods in Render looks like this:
@Override
public void onSurfaceCreated(final GL10 unused, final EGLConfig config) {
GLES20.glClearColor(backgroundRed, backgroundGreen, backgroundBlue, 1);
GLES20.glDisable(GLES20.GL_DEPTH_TEST);
filter.ifNeedInit();// Call the filter's initialization method
}
@Override
public void onSurfaceChanged(final GL10 gl, final int width, final int height) {
outputWidth = width;
outputHeight = height;
GLES20.glViewport(0.0, width, height);// Controls the drawing area of the current SurfaceView
GLES20.glUseProgram(filter.getProgram());
filter.onOutputSizeChanged(width, height);// Set the current width and height for the filter
adjustImageScaling();// Adjust the width and height of the texture (image) by modifying the texture coordinates
synchronized(surfaceChangedWaiter) { surfaceChangedWaiter.notifyAll(); }}@Override
public void onDrawFrame(final GL10 gl) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
runAll(runOnDraw);// The tasks are taken out of the queue from the loop and executed in sequence
generateOesTexture();// Get the current glTextureId, glCubeBuffer, glTextureBuffer
filter.onDraw(glTextureId, glCubeBuffer, glTextureBuffer);// Apply the filter
runAll(runOnDrawEnd);// Finally execute the second task queue
}
private void runAll(Queue<Runnable> queue) {
synchronized (queue) {
while(! queue.isEmpty()) { queue.poll().run(); }}}Copy the code
OnDrawFrame () = onDrawFrame(); onDrawFrame() = onDrawFrame(); onDrawFrame() = onDrawFrame();
setImage()
Next, look at the process of setting up an image resource and rendering it.
/**
* Sets the image on which the filter should be applied.
*
* @param bitmap the new image
*/
public void setImage(final Bitmap bitmap) {
gpuImage.setImage(bitmap);
}
/**
* Sets the image on which the filter should be applied from a Uri.
*
* @param uri the uri of the new image
*/
public void setImage(final Uri uri) {
gpuImage.setImage(uri);
}
/**
* Sets the image on which the filter should be applied from a File.
*
* @param file the file of the new image
*/
public void setImage(final File file) {
gpuImage.setImage(file);
}
Copy the code
The GPUImageView setImage() calls the GPUImage setImage() method and has multiple overloaded methods that can receive different types of input, bitmap URIs and files, respectively. The last two inputs will either build an image loading process themselves, or eventually get a bitmap:
/**
* Sets the image on which the filter should be applied from a Uri.
*
* @param uri the uri of the new image
*/
public void setImage(final Uri uri) {
new LoadImageUriTask(this, uri).execute();
}
/**
* Sets the image on which the filter should be applied from a File.
*
* @param file the file of the new image
*/
public void setImage(final File file) {
new LoadImageFileTask(this, file).execute();
}
Copy the code
Let’s take bitmap as an example:
/**
* Sets the image on which the filter should be applied.
*
* @param bitmap the new image
*/
public void setImage(final Bitmap bitmap) {
currentBitmap = bitmap;
renderer.setImageBitmap(bitmap, false);
requestRender();
}
Copy the code
Save a reference to a Bitmap, pass the bitmap to Render, and then actively request a render. Let’s see what render did:
public void setImageBitmap(final Bitmap bitmap, final boolean recycle) {
if (bitmap == null) {
return;
}
runOnDraw(new Runnable() {
@Override
public void run(a) {
Bitmap resizedBitmap = null;
if (bitmap.getWidth() % 2= =1) {
resizedBitmap = Bitmap.createBitmap(bitmap.getWidth() + 1, bitmap.getHeight(),
Bitmap.Config.ARGB_8888);
resizedBitmap.setDensity(bitmap.getDensity());
Canvas can = new Canvas(resizedBitmap);
can.drawARGB(0x00.0x00.0x00.0x00);
can.drawBitmap(bitmap, 0.0.null);
addedPadding = 1;
} else {
addedPadding = 0; } glTextureId = OpenGlUtils.loadTexture( resizedBitmap ! =null ? resizedBitmap : bitmap, glTextureId, recycle);
if(resizedBitmap ! =null) { resizedBitmap.recycle(); } imageWidth = bitmap.getWidth(); imageHeight = bitmap.getHeight(); adjustImageScaling(); }}); }protected void runOnDraw(final Runnable runnable) {
synchronized(runOnDraw) { runOnDraw.add(runnable); }}Copy the code
RunOnDraw () inserts the current operation as a Runnable task at the end of the draw task queue. As for the specific operation content, the first step is to detect the width of the picture. If it is odd, the first step is to create a picture with the width +1 of the original picture and the height equal to that of the original picture. A new picture is drawn in the way of Canvas. The adjustImageScaleing() method is then called. This method is used to make width/height adjustments and rotations by transforming texture coordinates:
private void adjustImageScaling(a) {
float outputWidth = this.outputWidth;
float outputHeight = this.outputHeight;
if (rotation == Rotation.ROTATION_270 || rotation == Rotation.ROTATION_90) {
outputWidth = this.outputHeight;
outputHeight = this.outputWidth;
}
float ratio1 = outputWidth / imageWidth;
float ratio2 = outputHeight / imageHeight;
float ratioMax = Math.max(ratio1, ratio2);
int imageWidthNew = Math.round(imageWidth * ratioMax);
int imageHeightNew = Math.round(imageHeight * ratioMax);
float ratioWidth = imageWidthNew / outputWidth;
float ratioHeight = imageHeightNew / outputHeight;
float[] cube = CUBE;
float[] textureCords = TextureRotationUtil.getRotation(rotation, flipHorizontal, flipVertical);
if (scaleType == GPUImage.ScaleType.CENTER_CROP) {
float distHorizontal = (1 - 1 / ratioWidth) / 2;
float distVertical = (1 - 1 / ratioHeight) / 2;
textureCords = new float[]{
addDistance(textureCords[0], distHorizontal), addDistance(textureCords[1], distVertical),
addDistance(textureCords[2], distHorizontal), addDistance(textureCords[3], distVertical),
addDistance(textureCords[4], distHorizontal), addDistance(textureCords[5], distVertical),
addDistance(textureCords[6], distHorizontal), addDistance(textureCords[7], distVertical),
};
} else {
cube = new float[]{
CUBE[0] / ratioHeight, CUBE[1] / ratioWidth,
CUBE[2] / ratioHeight, CUBE[3] / ratioWidth,
CUBE[4] / ratioHeight, CUBE[5] / ratioWidth,
CUBE[6] / ratioHeight, CUBE[7] / ratioWidth,
};
}
glCubeBuffer.clear();
glCubeBuffer.put(cube).position(0);
glTextureBuffer.clear();
glTextureBuffer.put(textureCords).position(0);
}
Copy the code
setFilter()
Next, add a filter:
/**
* Set the filter to be applied on the image.
*
* @param filter Filter that should be applied on the image.
*/
public void setFilter(GPUImageFilter filter) {
this.filter = filter;
gpuImage.setFilter(filter);
requestRender();
}
Copy the code
First save a reference to filter, then call the GPUImage setFilter() method to pass the filter, and finally actively refresh the filter. Look at the setFilter() implementation of GPUImage:
/** * Sets the filter which should be applied to the image which was (or will * be) set by setImage(...) . * *@param filter the new filter
*/
public void setFilter(final GPUImageFilter filter) {
this.filter = filter;
renderer.setFilter(this.filter);
requestRender();
}
Copy the code
Similarly, we end up in Render:
public void setFilter(final GPUImageFilter filter) {
runOnDraw(new Runnable() {
@Override
public void run(a) {
final GPUImageFilter oldFilter = GPUImageRenderer.this.filter;
GPUImageRenderer.this.filter = filter;
if(oldFilter ! =null) {
oldFilter.destroy();
}
GPUImageRenderer.this.filter.ifNeedInit();
GLES20.glUseProgram(GPUImageRenderer.this.filter.getProgram());
GPUImageRenderer.this.filter.onOutputSizeChanged(outputWidth, outputHeight); }}); }Copy the code
Create a new drawing task that first gets the Filter saved in Render, which was drawn last time. After the Filter is updated, call the destroy() method of the old Filter to clean up some resources, and then call the initialization method of the new Filter to pass the current width and height data to the new Filter. To summarize, all subsequent Settings, including images and filters, are drawn in the GLTread onDrawFrame() method.