NekoX/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Painting.java

545 lines
18 KiB
Java

package org.telegram.ui.Components.Paint;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.opengl.GLES20;
import org.telegram.messenger.DispatchQueue;
import org.telegram.ui.Components.Size;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.Map;
import java.util.UUID;
import javax.microedition.khronos.opengles.GL10;
public class Painting {
public interface PaintingDelegate {
void contentChanged();
void strokeCommited();
UndoStore requestUndoStore();
DispatchQueue requestDispatchQueue();
}
public static class PaintingData {
public Bitmap bitmap;
public ByteBuffer data;
PaintingData(Bitmap b, ByteBuffer buffer) {
bitmap = b;
data = buffer;
}
}
private PaintingDelegate delegate;
private Path activePath;
private RenderState renderState;
private RenderView renderView;
private Size size;
private RectF activeStrokeBounds;
private Brush brush;
private Texture brushTexture;
private Texture bitmapTexture;
private ByteBuffer vertexBuffer;
private ByteBuffer textureBuffer;
private int reusableFramebuffer;
private int paintTexture;
private Map<String, Shader> shaders;
private int suppressChangesCounter;
private int[] buffers = new int[1];
private ByteBuffer dataBuffer;
private boolean paused;
private Slice backupSlice;
private float[] projection;
private float[] renderProjection;
public Painting(Size sz) {
renderState = new RenderState();
size = sz;
dataBuffer = ByteBuffer.allocateDirect((int) size.width * (int) size.height * 4);
projection = GLMatrix.LoadOrtho(0, size.width, 0, size.height, -1.0f, 1.0f);
if (vertexBuffer == null) {
vertexBuffer = ByteBuffer.allocateDirect(8 * 4);
vertexBuffer.order(ByteOrder.nativeOrder());
}
vertexBuffer.putFloat(0.0f);
vertexBuffer.putFloat(0.0f);
vertexBuffer.putFloat(size.width);
vertexBuffer.putFloat(0.0f);
vertexBuffer.putFloat(0.0f);
vertexBuffer.putFloat(size.height);
vertexBuffer.putFloat(size.width);
vertexBuffer.putFloat(size.height);
vertexBuffer.rewind();
if (textureBuffer == null) {
textureBuffer = ByteBuffer.allocateDirect(8 * 4);
textureBuffer.order(ByteOrder.nativeOrder());
textureBuffer.putFloat(0.0f);
textureBuffer.putFloat(0.0f);
textureBuffer.putFloat(1.0f);
textureBuffer.putFloat(0.0f);
textureBuffer.putFloat(0.0f);
textureBuffer.putFloat(1.0f);
textureBuffer.putFloat(1.0f);
textureBuffer.putFloat(1.0f);
textureBuffer.rewind();
}
}
public void setDelegate(PaintingDelegate paintingDelegate) {
delegate = paintingDelegate;
}
public void setRenderView(RenderView view) {
renderView = view;
}
public Size getSize() {
return size;
}
public RectF getBounds() {
return new RectF(0.0f, 0.0f, size.width, size.height);
}
private boolean isSuppressingChanges() {
return suppressChangesCounter > 0;
}
private void beginSuppressingChanges() {
suppressChangesCounter++;
}
private void endSuppressingChanges() {
suppressChangesCounter--;
}
public void setBitmap(Bitmap bitmap) {
if (bitmapTexture != null) {
return;
}
bitmapTexture = new Texture(bitmap);
}
public void paintStroke(final Path path, final boolean clearBuffer, final Runnable action) {
renderView.performInContext(() -> {
activePath = path;
RectF bounds = null;
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, getReusableFramebuffer());
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, getPaintTexture(), 0);
Utils.HasGLError();
int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
if (status == GLES20.GL_FRAMEBUFFER_COMPLETE) {
GLES20.glViewport(0, 0, (int) size.width, (int) size.height);
if (clearBuffer) {
GLES20.glClearColor(0, 0, 0, 0);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
}
if (shaders == null) {
return;
}
Shader shader = shaders.get(brush.isLightSaber() ? "brushLight" : "brush");
if (shader == null) {
return;
}
GLES20.glUseProgram(shader.program);
if (brushTexture == null) {
brushTexture = new Texture(brush.getStamp());
}
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, brushTexture.texture());
GLES20.glUniformMatrix4fv(shader.getUniform("mvpMatrix"), 1, false, FloatBuffer.wrap(projection));
GLES20.glUniform1i(shader.getUniform("texture"), 0);
bounds = Render.RenderPath(path, renderState);
}
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
if (delegate != null) {
delegate.contentChanged();
}
if (activeStrokeBounds != null) {
activeStrokeBounds.union(bounds);
} else {
activeStrokeBounds = bounds;
}
if (action != null) {
action.run();
}
});
}
public void commitStroke(final int color) {
renderView.performInContext(() -> {
registerUndo(activeStrokeBounds);
beginSuppressingChanges();
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, getReusableFramebuffer());
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, getTexture(), 0);
GLES20.glViewport(0, 0, (int) size.width, (int) size.height);
Shader shader = shaders.get(brush.isLightSaber() ? "compositeWithMaskLight" : "compositeWithMask");
GLES20.glUseProgram(shader.program);
GLES20.glUniformMatrix4fv(shader.getUniform("mvpMatrix"), 1, false, FloatBuffer.wrap(projection));
GLES20.glUniform1i(shader.getUniform("texture"), 0);
GLES20.glUniform1i(shader.getUniform("mask"), 1);
Shader.SetColorUniform(shader.getUniform("color"), color);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTexture());
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getPaintTexture());
GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ZERO);
GLES20.glVertexAttribPointer(0, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer);
GLES20.glEnableVertexAttribArray(0);
GLES20.glVertexAttribPointer(1, 2, GLES20.GL_FLOAT, false, 8, textureBuffer);
GLES20.glEnableVertexAttribArray(1);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTexture());
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
if (!isSuppressingChanges() && delegate != null) {
delegate.contentChanged();
}
endSuppressingChanges();
renderState.reset();
activeStrokeBounds = null;
activePath = null;
});
}
public void clearStroke() {
renderView.performInContext(() -> {
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, getReusableFramebuffer());
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, getPaintTexture(), 0);
Utils.HasGLError();
int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
if (status == GLES20.GL_FRAMEBUFFER_COMPLETE) {
GLES20.glViewport(0, 0, (int) size.width, (int) size.height);
GLES20.glClearColor(0, 0, 0, 0);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
}
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
if (delegate != null) {
delegate.contentChanged();
}
renderState.reset();
activeStrokeBounds = null;
activePath = null;
});
}
private void registerUndo(RectF rect) {
if (rect == null) {
return;
}
boolean intersect = rect.setIntersect(rect, getBounds());
if (!intersect) {
return;
}
PaintingData paintingData = getPaintingData(rect, true);
ByteBuffer data = paintingData.data;
final Slice slice = new Slice(data, rect, delegate.requestDispatchQueue());
delegate.requestUndoStore().registerUndo(UUID.randomUUID(), () -> restoreSlice(slice));
}
private void restoreSlice(final Slice slice) {
renderView.performInContext(() -> {
ByteBuffer buffer = slice.getData();
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTexture());
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, slice.getX(), slice.getY(), slice.getWidth(), slice.getHeight(), GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buffer);
if (!isSuppressingChanges() && delegate != null) {
delegate.contentChanged();
}
slice.cleanResources();
});
}
public void setRenderProjection(float[] proj) {
renderProjection = proj;
}
public void render() {
if (shaders == null) {
return;
}
if (activePath != null) {
render(getPaintTexture(), activePath.getColor());
} else {
renderBlit();
}
}
private void render(int mask, int color) {
Shader shader = shaders.get(brush.isLightSaber() ? "blitWithMaskLight" : "blitWithMask");
if (shader == null) {
return;
}
GLES20.glUseProgram(shader.program);
GLES20.glUniformMatrix4fv(shader.getUniform("mvpMatrix"), 1, false, FloatBuffer.wrap(renderProjection));
GLES20.glUniform1i(shader.getUniform("texture"), 0);
GLES20.glUniform1i(shader.getUniform("mask"), 1);
Shader.SetColorUniform(shader.getUniform("color"), color);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTexture());
GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mask);
GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
GLES20.glVertexAttribPointer(0, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer);
GLES20.glEnableVertexAttribArray(0);
GLES20.glVertexAttribPointer(1, 2, GLES20.GL_FLOAT, false, 8, textureBuffer);
GLES20.glEnableVertexAttribArray(1);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
Utils.HasGLError();
}
private void renderBlit() {
Shader shader = shaders.get("blit");
if (shader == null) {
return;
}
GLES20.glUseProgram(shader.program);
GLES20.glUniformMatrix4fv(shader.getUniform("mvpMatrix"), 1, false, FloatBuffer.wrap(renderProjection));
GLES20.glUniform1i(shader.getUniform("texture"), 0);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTexture());
GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
GLES20.glVertexAttribPointer(0, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer);
GLES20.glEnableVertexAttribArray(0);
GLES20.glVertexAttribPointer(1, 2, GLES20.GL_FLOAT, false, 8, textureBuffer);
GLES20.glEnableVertexAttribArray(1);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
Utils.HasGLError();
}
public PaintingData getPaintingData(RectF rect, boolean undo) {
int minX = (int) rect.left;
int minY = (int) rect.top;
int width = (int) rect.width();
int height = (int) rect.height();
GLES20.glGenFramebuffers(1, buffers, 0);
int framebuffer = buffers[0];
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, framebuffer);
GLES20.glGenTextures(1, buffers, 0);
int texture = buffers[0];
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture);
GLES20.glTexParameteri(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
GLES20.glTexParameteri(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, texture, 0);
GLES20.glViewport(0, 0, (int) size.width, (int) size.height);
if (shaders == null) {
return null;
}
Shader shader = shaders.get(undo ? "nonPremultipliedBlit" : "blit");
if (shader == null) {
return null;
}
GLES20.glUseProgram(shader.program);
Matrix translate = new Matrix();
translate.preTranslate(-minX, -minY);
float[] effective = GLMatrix.LoadGraphicsMatrix(translate);
float[] finalProjection = GLMatrix.MultiplyMat4f(projection, effective);
GLES20.glUniformMatrix4fv(shader.getUniform("mvpMatrix"), 1, false, FloatBuffer.wrap(finalProjection));
GLES20.glUniform1i(shader.getUniform("texture"), 0);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTexture());
GLES20.glClearColor(0, 0, 0, 0);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ZERO);
GLES20.glVertexAttribPointer(0, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer);
GLES20.glEnableVertexAttribArray(0);
GLES20.glVertexAttribPointer(1, 2, GLES20.GL_FLOAT, false, 8, textureBuffer);
GLES20.glEnableVertexAttribArray(1);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
dataBuffer.limit(width * height * 4);
GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, dataBuffer);
PaintingData data;
if (undo) {
data = new PaintingData(null, dataBuffer);
} else {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(dataBuffer);
data = new PaintingData(bitmap, null);
}
buffers[0] = framebuffer;
GLES20.glDeleteFramebuffers(1, buffers, 0);
buffers[0] = texture;
GLES20.glDeleteTextures(1, buffers, 0);
return data;
}
public void setBrush(Brush value) {
brush = value;
if (brushTexture != null) {
brushTexture.cleanResources(true);
brushTexture = null;
}
}
public boolean isPaused() {
return paused;
}
public void onPause(final Runnable completionRunnable) {
renderView.performInContext(() -> {
paused = true;
PaintingData data = getPaintingData(getBounds(), true);
backupSlice = new Slice(data.data, getBounds(), delegate.requestDispatchQueue());
cleanResources(false);
if (completionRunnable != null)
completionRunnable.run();
});
}
public void onResume() {
restoreSlice(backupSlice);
backupSlice = null;
paused = false;
}
public void cleanResources(boolean recycle) {
if (reusableFramebuffer != 0) {
buffers[0] = reusableFramebuffer;
GLES20.glDeleteFramebuffers(1, buffers, 0);
reusableFramebuffer = 0;
}
bitmapTexture.cleanResources(recycle);
if (paintTexture != 0) {
buffers[0] = paintTexture;
GLES20.glDeleteTextures(1, buffers, 0);
paintTexture = 0;
}
if (brushTexture != null) {
brushTexture.cleanResources(true);
brushTexture = null;
}
if (shaders != null) {
for (Shader shader : shaders.values()) {
shader.cleanResources();
}
shaders = null;
}
}
private int getReusableFramebuffer() {
if (reusableFramebuffer == 0) {
int[] buffers = new int[1];
GLES20.glGenFramebuffers(1, buffers, 0);
reusableFramebuffer = buffers[0];
Utils.HasGLError();
}
return reusableFramebuffer;
}
private int getTexture() {
if (bitmapTexture != null) {
return bitmapTexture.texture();
}
return 0;
}
private int getPaintTexture() {
if (paintTexture == 0) {
paintTexture = Texture.generateTexture(size);
}
return paintTexture;
}
public void setupShaders() {
shaders = ShaderSet.setup();
}
}