This article mainly introduces the use of OpenGL ES in iOS to achieve the method of drawing board, the article through the example code is very detailed, for everyone’s learning or work has a certain reference learning value, interested partners can understand
Today we use OpenGL ES to achieve a drawing board, mainly introduced in OpenGL ES drawing smooth curve implementation scheme.First, take a look at the final result:
In iOS, there are many ways to implement a drawing board, for example, my other project MFPaintView is based on CoreGraphics.
However, using OpenGL ES allows for more flexibility, such as customizing the shape of the stroke, which is not possible with other implementations.
We know that there are only three primitives in OpenGL ES: points, lines, and triangles. So, how to draw curves in OpenGL ES is the first problem that we’re going to solve, and it’s also the most complicated problem.
We’re going to talk about this in a lot of space. As for the realization of other functions of the drawing board, it is not to say that it is not important, but that other ways of realizing the drawing board will have similar logic, so this chapter will be briefly introduced at the end.
How to draw a curve
The way to draw a curve in OpenGL ES, is to break it up into sequences of points. Because we want to draw points, we use point primitives. That is, we draw the vertex data as points, and each point is textured as a stroke. The key steps are as follows:
Specify primitive type:
1|glDrawArrays(GL_POINTS, 0, self.vertexCount);
Copy the code
Vertex shaders:
attribute vec4 Position;
uniform float Size;
void main (void) {
gl_Position = Position;
gl_PointSize = Size;
}
Copy the code
Fragment shader:
precision highp float; uniform float R; uniform float G; uniform float B; uniform float A; uniform sampler2D Texture; Void main (void) {Texture = texture2D(gl_pointcoord.x, 1.0-gl_pointcoord.y)); Gl_FragColor = A * vec4(R, G, B, 1.0) * mask; }Copy the code
The key point here is the built-in variable gl_PointCoord, which allows us to get the normalized coordinates of the current pixel in the point pixel when we use the point pixel.
But the origin of this coordinate is in the upper left corner, which is the opposite of the texture coordinate in the vertical direction. So when you read the color from the texture, you need to do a y coordinate conversion.
Next, we use the UITouch to get the position of the touch points and then calculate normalized vertex coordinates.
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[super touchesMoved:touches withEvent:event];
[self addPointWithTouches:touches];
}
Copy the code
However, due to the limited distribution frequency of touch events in iOS system, we can only get sparse points in the end. As shown in the figure below, the distance between each touch point will be large.
How to draw dense points
It is easy to imagine that a continuous trajectory could be plotted by interpolating between two points at a certain density.
But obviously, our result is a broken line, not a smooth one.
Three, how to make the curve smooth
Bessel curve is usually used to solve the problem of non-smooth point connection. This scheme is also well applied in MFPaintView. The method is to construct a Bessel curve using the midpoint and one vertex between two vertices. The three red dots in the figure below are used to construct a Bessel curve.
So, our problem becomes how to draw bezier curves in OpenGL ES. It is equivalent to finding the sequence of points on the Bessel curve inversely when three key points are known.
We know that the equation of the Bezier curve is P = (1-t)^2 * P0 + 2 * t * (1-t) * P1 + t^2 * P2, where t is the only variable, and it ranges from 0 to 1.
So we can take linear values, n points for each Bezier curve (n is a certain constant). Just plug in 1 / n, 2 / n,… N over n, you get a sequence of points.
Let’s take n to a small value, so it’s easier to see the problem. We find that the sequence of points is not evenly spaced. There are two reasons:
1. Different Lengths of Bessel curves are different, and the density of points calculated by using the same N value must be different.
2. Since the Bessel curve increases with t, the length of the curve does not increase linearly. According to our algorithm above, the result is sparse at both ends and dense in the middle.
How to generate a uniform sequence of points
Bessel curve generates a uniform sequence of points, which involves a classical problem of “Bessel curve uniform motion”. In simple terms, we encapsulated a method through a series of operations, just need to pass in three key points of bezier curve and stroke size, can obtain a uniform sequence of points.
+ (NSArray <NSValue *>*)pointsWithFrom:(CGPoint)from
to:(CGPoint)to
control:(CGPoint)control
pointSize:(CGFloat)pointSize;
Copy the code
Now let’s fix the starting point and control point of the Bezier curve and only move the ending point to verify whether this method is reliable.
It can be seen that in the process of movement, the distance between points is basically the same and uniform. Through this “magic” method, we finally drew a smooth and uniform curve.
Five, painting board function realization
Finally finished talking about the most troublesome part, the next simple introduction to the realization of the basic function of the painting board.
1, color mix
In previous examples, we used to call glClear(GL_COLOR_BUFFER_BIT) to clear the canvas before starting a render, because we didn’t want to keep the last render.
But for a drawing board, we’re constantly painting on the canvas, so we want to keep the last result. Therefore, no cleanup operation can be performed before drawing.
Also, since our brushes may be translucent, the newly drawn colors need to be blended with the existing colors on the canvas. So before you start drawing, you need to turn on the blending option.
1|glEnable(GL_BLEND);
2|glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
Copy the code
2, brush adjustment
The stroke has three properties that can be adjusted: color, size and shape. They are all essentially adjustments to point primitives, passing colors, sizes, and textures into shaders and applying them in the form of uniform variables.
3. Eraser
When GLPaintView is initialized, it needs to pass in a background color parameter. When the user switches to the eraser function, it simply switches the brush color to the background color, thus creating the eraser effect.
4. Undo redo
The undo redo function relies on two stacks. We define the data generated by the user’s finger from pressing down the screen to leaving the screen as an action object, which holds the normalized sequence of points and the properties of the points.
@interface MFPaintModel: NSObject @property (nonatomic, assign) CGFloat brushSize; @property (nonatomic, strong) UIColor *brushColor; @property (nonatomic, assign) GLPaintViewBrushMode brushMode; @property (nonatomic, copy) NSString *brushImageName; @property (nonatomic, copy) NSArray<NSValue *> *points; @endCopy the code
The undo redo code implementation might look something like this:
- (void)undo {
if ([self.operationStack isEmpty]) {
return;
}
MFPaintModel *model = self.operationStack.topModel;
[self.operationStack popModel];
[self.undoOperationStack pushModel:model];
[self reDraw];
}
- (void)redo {
if ([self.undoOperationStack isEmpty]) {
return;
}
MFPaintModel *model = self.undoOperationStack.topModel;
[self.undoOperationStack popModel];
[self.operationStack pushModel:model];
[self drawModel:model];
}
Copy the code
Note that since the undo operation requires the canvas to be cleaned first, it needs to be redrawn each time. Redo operations can take advantage of the results of the last drawing, so only one step is required at a time. The above is all the content of this article, I hope to help you
The original address: www.lymanli.com/2020/01/04/…