Ilya Laktyushin 0d17a8989c Cleanup
2020-05-26 14:19:12 +03:00

724 lines
21 KiB
Objective-C

#import "TGPainting.h"
#import "LegacyComponentsInternal.h"
#import <SSignalKit/SSignalKit.h>
#import <OpenGLES/ES2/gl.h>
#import <OpenGLES/ES2/glext.h>
#import "matrix.h"
#import "TGPaintBrush.h"
#import "TGPaintPath.h"
#import "TGPaintRender.h"
#import "TGPaintSlice.h"
#import "TGPaintShader.h"
#import "TGPaintShaderSet.h"
#import "TGPaintTexture.h"
#import <LegacyComponents/TGPaintUtils.h>
#import <LegacyComponents/TGPaintUndoManager.h>
@interface TGPainting ()
{
EAGLContext *_context;
NSDictionary *_shaders;
NSData *_initialImageData;
GLuint _textureName;
GLuint _quadVAO;
GLuint _quadVBO;
GLfloat _projection[16];
CGRect _activeStrokeBounds;
GLuint _reusableFramebuffer;
GLuint _paintTextureName;
TGPaintTexture *_brushTexture;
TGPaintRenderState *_renderState;
NSInteger _suppressChangesCounter;
SQueue *_queue;
__weak TGPaintUndoManager *_undoManager;
NSUInteger _strokeCount;
}
@end
@implementation TGPainting
- (instancetype)initWithSize:(CGSize)size undoManager:(TGPaintUndoManager *)undoManager imageData:(NSData *)imageData
{
self = [super init];
if (self != nil)
{
_queue = [[SQueue alloc] init];
_undoManager = undoManager;
_initialImageData = imageData;
_renderState = [[TGPaintRenderState alloc] init];
if (_initialImageData.length > 0)
_strokeCount++;
[self setSize:size];
}
return self;
}
- (void)dealloc
{
if (_context == nil)
return;
[self performSynchronouslyInContext:^{
[EAGLContext setCurrentContext:_context];
if (_paintTextureName != 0)
glDeleteTextures(1, &_paintTextureName);
glDeleteBuffers(1, &_quadVBO);
glDeleteVertexArraysOES(1, &_quadVAO);
if (_reusableFramebuffer != 0)
glDeleteFramebuffers(1, &_reusableFramebuffer);
if (_textureName != 0)
glDeleteTextures(1, &_textureName);
[_brushTexture cleanResources];
TGPaintHasGLError();
[EAGLContext setCurrentContext:nil];
}];
}
- (void)setSize:(CGSize)size
{
_size = size;
mat4f_LoadOrtho(0, (GLint)size.width, 0, (GLint)size.height, -1.0f, 1.0f, _projection);
}
- (EAGLContext *)context
{
if (_context == nil)
{
_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
if (_context != nil && [EAGLContext setCurrentContext:_context])
{
glEnable(GL_BLEND);
glDisable(GL_DITHER);
glDisable(GL_STENCIL_TEST);
glDisable(GL_DEPTH_TEST);
}
[self _setupShaders];
}
return _context;
}
- (bool)isEmpty
{
return (_strokeCount == 0);
}
- (CGRect)bounds
{
return CGRectMake(0.0f, 0.0f, self.size.width, self.size.height);
}
#pragma mark -
- (void)beginSuppressingChanges
{
_suppressChangesCounter++;
}
- (void)endSuppressingChanges
{
_suppressChangesCounter--;
}
- (bool)isSuppressingChanges
{
return _suppressChangesCounter > 0;
}
#pragma mark -
- (void)performSynchronouslyInContext:(void (^)(void))block
{
[_queue dispatch:^
{
[EAGLContext setCurrentContext:self.context];
block();
} synchronous:true];
}
- (void)performAsynchronouslyInContext:(void (^)(void))block
{
[_queue dispatch:^
{
[EAGLContext setCurrentContext:self.context];
block();
}];
}
- (void)paintStroke:(TGPaintPath *)path clearBuffer:(bool)clearBuffer completion:(void (^)(void))completion
{
[self performAsynchronouslyInContext:^
{
_activePath = path;
CGRect bounds = CGRectZero;
glBindFramebuffer(GL_FRAMEBUFFER, [self _reusableFramebuffer]);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, [self _paintTextureName], 0);
GLuint status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status == GL_FRAMEBUFFER_COMPLETE)
{
glViewport(0, 0, (GLint)self.size.width, (GLint)self.size.height);
if (clearBuffer)
{
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
}
[self _setupBrush];
bounds = [TGPaintRender renderPath:path renderState:_renderState];
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
TGPaintHasGLError();
if (self.contentChanged != nil)
self.contentChanged(bounds);
_activeStrokeBounds = TGPaintUnionRect(_activeStrokeBounds, bounds);
if (completion != nil)
completion();
}];
}
- (void)commitStrokeWithColor:(UIColor *)color erase:(bool)erase
{
[self performAsynchronouslyInContext:^
{
[self registerUndoInRect:_activeStrokeBounds];
[self beginSuppressingChanges];
[self updateWithBlock:^
{
GLfloat proj[16];
mat4f_LoadOrtho(0, (GLint)self.size.width, 0, (GLint)self.size.height, -1.0f, 1.0f, proj);
TGPaintShader *shader = erase ? [self shaderForKey:@"compositeWithEraseMask"] : [self shaderForKey:@"compositeWithMask"];
if (_brush.lightSaber)
shader = [self shaderForKey:@"compositeWithMaskLight"];
glUseProgram(shader.program);
glUniformMatrix4fv([shader uniformForKey:@"mvpMatrix"], 1, GL_FALSE, proj);
glUniform1i([shader uniformForKey:@"texture"], 0);
glUniform1i([shader uniformForKey:@"mask"], 1);
if (!erase)
TGSetupColorUniform([shader uniformForKey:@"color"], color);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, self.textureName);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, self._paintTextureName);
glBlendFunc(GL_ONE, GL_ZERO);
glBindVertexArrayOES([self _quad]);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArrayOES(0);
glBindTexture(GL_TEXTURE_2D, self.textureName);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
} bounds:_activeStrokeBounds];
[self endSuppressingChanges];
[_renderState reset];
_activeStrokeBounds = CGRectZero;
_activePath = nil;
TGDispatchOnMainThread(^
{
if (self.strokeCommited != nil)
self.strokeCommited();
});
}];
}
- (void)setBrush:(TGPaintBrush *)brush
{
_brush = brush;
[self performAsynchronouslyInContext:^{
if (_brushTexture != nil)
{
// [_brushTexture cleanResources];
_brushTexture = nil;
}
}];
}
- (void)_setupBrush
{
TGPaintShader *shader = [self shaderForKey:_brush.lightSaber ? @"brushLight" : @"brush"];
glUseProgram(shader.program);
glActiveTexture(GL_TEXTURE0);
if (_brushTexture == nil)
_brushTexture = [[TGPaintTexture alloc] initWithCGImage:_brush.stampRef forceRGB:false];
glBindTexture(GL_TEXTURE_2D, _brushTexture.textureName);
glUniformMatrix4fv([shader uniformForKey:@"mvpMatrix"], 1, GL_FALSE, _projection);
glUniform1i([shader uniformForKey:@"texture"], 0);
TGPaintHasGLError();
}
#pragma mark -
- (void)updateWithBlock:(void (^)(void))updateBlock bounds:(CGRect)bounds
{
glBindFramebuffer(GL_FRAMEBUFFER, [self _reusableFramebuffer]);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self.textureName, 0);
GLuint status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status == GL_FRAMEBUFFER_COMPLETE)
{
glViewport(0, 0, (GLint)self.size.width, (GLint)self.size.height);
TGPaintHasGLError();
updateBlock();
TGPaintHasGLError();
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
if (![self isSuppressingChanges])
{
if (self.contentChanged != nil)
self.contentChanged(bounds);
}
}
- (void)clear
{
_strokeCount = 0;
[self performAsynchronouslyInContext:^
{
[self updateWithBlock:^
{
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
} bounds:[self bounds]];
}];
}
- (dispatch_queue_t)_queue
{
return _queue._dispatch_queue;
}
#pragma mark -
- (void)renderWithProjection:(GLfloat *)projection
{
if (_activePath != nil)
{
if (_activePath.action == TGPaintActionErase)
[self _renderWithProjection:projection mask:[self _paintTextureName] color:nil erase:true];
else
[self _renderWithProjection:projection mask:[self _paintTextureName] color:_activePath.color erase:false];
}
else
{
[self _renderWithProjection:projection];
}
}
- (void)_renderWithProjection:(GLfloat *)projection mask:(GLint)mask color:(UIColor *)color erase:(bool)erase
{
TGPaintShader *shader = erase ? [self shaderForKey:@"blitWithEraseMask"] : [self shaderForKey:@"blitWithMask"];
if (_brush.lightSaber)
shader = [self shaderForKey:@"blitWithMaskLight"];
glUseProgram(shader.program);
glUniformMatrix4fv([shader uniformForKey:@"mvpMatrix"], 1, GL_FALSE, projection);
glUniform1i([shader uniformForKey:@"texture"], 0);
glUniform1i([shader uniformForKey:@"mask"], 1);
if (!erase)
TGSetupColorUniform([shader uniformForKey:@"color"], color);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, self.textureName);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, mask);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glBindVertexArrayOES([self _quad]);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArrayOES(0);
}
- (void)_renderWithProjection:(GLfloat *)projection
{
TGPaintShader *shader = [self shaderForKey:@"blit"];
glUseProgram(shader.program);
glUniformMatrix4fv([shader uniformForKey:@"mvpMatrix"], 1, GL_FALSE, projection);
glUniform1i([shader uniformForKey:@"texture"], 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, self.textureName);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glBindVertexArrayOES([self _quad]);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArrayOES(0);
}
#pragma mark -
- (NSData *)imageDataForRect:(CGRect)rect resultPaintingData:(NSData **)resultPaintingData
{
[EAGLContext setCurrentContext:self.context];
TGPaintHasGLError();
GLint minX = (GLint) CGRectGetMinX(rect);
GLint minY = (GLint) CGRectGetMinY(rect);
GLint width = (GLint) CGRectGetWidth(rect);
GLint height = (GLint) CGRectGetHeight(rect);
GLuint framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
GLuint colorRenderbuffer;
glGenRenderbuffers(1, &colorRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8_OES, width, height);
GLuint textureName;
GLenum format = GL_RGBA;
GLenum type = GL_UNSIGNED_BYTE;
glGenTextures(1, &textureName);
glBindTexture(GL_TEXTURE_2D, textureName);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, textureName);
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureName, 0);
GLint status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE)
{
TGLegacyLog(@"ERROR: imageAndData: - Incomplete Framebuffer!");
TGPaintHasGLError();
return nil;
}
glViewport(0, 0, (GLint)self.size.width, (GLint)self.size.height);
TGPaintShader *blitShader = [self shaderForKey:@"nonPremultipliedBlit"];
glUseProgram(blitShader.program);
GLfloat proj[16], effectiveProj[16],final[16];
mat4f_LoadOrtho(0, (GLint)self.size.width, 0, (GLint)self.size.height, -1.0f, 1.0f, proj);
CGAffineTransform translate = CGAffineTransformMakeTranslation(-minX, -minY);
mat4f_LoadCGAffineTransform(effectiveProj, translate);
mat4f_MultiplyMat4f(proj, effectiveProj, final);
glUniformMatrix4fv([blitShader uniformForKey:@"mvpMatrix"], 1, GL_FALSE, final);
glUniform1i([blitShader uniformForKey:@"texture"], (GLuint)0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, self.textureName);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glBindVertexArrayOES([self _quad]);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArrayOES(0);
NSUInteger length = width * 4 * height;
GLubyte *pixels = malloc(sizeof(GLubyte) * length);
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
NSData *paintingResult = [NSData dataWithBytes:pixels length:length];
if (resultPaintingData != NULL)
*resultPaintingData = paintingResult;
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRenderbuffer);
status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE)
{
TGLegacyLog(@"ERROR: imageAndData: - Incomplete Framebuffer!");
TGPaintHasGLError();
return nil;
}
blitShader = [self shaderForKey:@"blit"];
glUseProgram(blitShader.program);
glUniformMatrix4fv([blitShader uniformForKey:@"mvpMatrix"], 1, GL_FALSE, final);
glUniform1i([blitShader uniformForKey:@"texture"], (GLuint)0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureName);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glBindVertexArrayOES([self _quad]);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArrayOES(0);
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
NSData *result = [NSData dataWithBytes:pixels length:length];
free(pixels);
glDeleteFramebuffers(1, &framebuffer);
glDeleteTextures(1, &textureName);
glDeleteRenderbuffers(1, &colorRenderbuffer);
TGPaintHasGLError();
return result;
}
- (UIImage *)imageWithSize:(CGSize)size andData:(NSData *__autoreleasing *)outData
{
NSData *paintingData = nil;
NSData *imageData = [self imageDataForRect:self.bounds resultPaintingData:&paintingData];
UIImage *image = [self imageForData:imageData size:self.size outputSize:size];
if (outData != NULL)
*outData = paintingData;
return image;
}
- (UIImage *)imageForData:(NSData *)data size:(CGSize)size outputSize:(CGSize)outputSize
{
size_t width = (size_t)size.width;
size_t height = (size_t)size.height;
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate((void *)data.bytes, width, height, 8, width * 4, colorSpaceRef, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast);
CGImageRef imageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
CGColorSpaceRelease(colorSpaceRef);
UIGraphicsBeginImageContext(outputSize);
[[UIImage imageWithCGImage:imageRef] drawInRect:CGRectMake(0.0f, 0.0f, outputSize.width, outputSize.height)];
CGImageRelease(imageRef);
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
}
#pragma mark -
- (void)registerUndoInRect:(CGRect)rect
{
rect = CGRectIntersection(rect, self.bounds);
NSData *paintingData = nil;
[self imageDataForRect:rect resultPaintingData:&paintingData];
TGPaintSlice *slice = [[TGPaintSlice alloc] initWithData:paintingData bounds:rect];
NSInteger uuid;
arc4random_buf(&uuid, sizeof(NSInteger));
[_undoManager registerUndoWithUUID:uuid block:^(TGPainting *painting, __unused TGPhotoEntitiesContainerView *entitiesContainer, __unused NSInteger uuid)
{
[painting restoreSlice:slice redo:false];
}];
_strokeCount++;
}
- (void)restoreSlice:(TGPaintSlice *)slice redo:(bool)redo
{
[self performAsynchronouslyInContext:^
{
if (!redo)
_strokeCount--;
NSData *data = slice.data;
glBindTexture(GL_TEXTURE_2D, self.textureName);
glTexSubImage2D(GL_TEXTURE_2D, 0, (GLint)slice.bounds.origin.x, (GLint)slice.bounds.origin.y, (GLint)slice.bounds.size.width, (GLint)slice.bounds.size.height, GL_RGBA, GL_UNSIGNED_BYTE, data.bytes);
if (![self isSuppressingChanges] && self.contentChanged != nil)
self.contentChanged(slice.bounds);
}];
}
#pragma mark -
- (GLuint)textureName
{
if (_textureName == 0)
{
_textureName = [self _generateTextureWithPixels:(GLubyte *)_initialImageData.bytes];
_initialImageData = nil;
}
return _textureName;
}
- (GLuint)_paintTextureName
{
if (_paintTextureName == 0)
_paintTextureName = [self _generateTextureWithPixels:nil];
return _paintTextureName;
}
- (GLuint)_reusableFramebuffer
{
if (_reusableFramebuffer == 0)
glGenFramebuffers(1, &_reusableFramebuffer);
return _reusableFramebuffer;
}
- (GLuint)_quad
{
if (_quadVAO == 0)
{
[EAGLContext setCurrentContext:self.context];
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGPoint corners[4];
corners[0] = rect.origin;
corners[1] = CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect));
corners[2] = CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect));
corners[3] = CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect));
const GLfloat vertices[] =
{
(GLfloat)corners[0].x, (GLfloat)corners[0].y, 0.0, 0.0,
(GLfloat)corners[1].x, (GLfloat)corners[1].y, 1.0, 0.0,
(GLfloat)corners[3].x, (GLfloat)corners[3].y, 0.0, 1.0,
(GLfloat)corners[2].x, (GLfloat)corners[2].y, 1.0, 1.0,
};
glGenVertexArraysOES(1, &_quadVAO);
glBindVertexArrayOES(_quadVAO);
glGenBuffers(1, &_quadVBO);
glBindBuffer(GL_ARRAY_BUFFER, _quadVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 16, vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 4, (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 4, (void*)8);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER,0);
glBindVertexArrayOES(0);
}
return _quadVAO;
}
- (GLfloat *)_projection
{
return _projection;
}
- (GLuint)_generateTextureWithPixels:(GLubyte *)pixels
{
[EAGLContext setCurrentContext:self.context];
TGPaintHasGLError();
GLuint textureName;
glGenTextures(1, &textureName);
glBindTexture(GL_TEXTURE_2D, textureName);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
GLuint width = (GLuint)self.size.width;
GLuint height = (GLuint)self.size.height;
GLenum format = GL_RGBA;
GLenum type = GL_UNSIGNED_BYTE;
NSUInteger bytesPerPixel = 4;
if (pixels == NULL)
{
pixels = calloc((size_t) (self.size.width * bytesPerPixel * self.size.height), sizeof(GLubyte));
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, pixels);
free(pixels);
}
else
{
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, pixels);
}
TGPaintHasGLError();
return textureName;
}
#pragma mark - Shaders
- (void)_setupShaders
{
if (_shaders != nil)
return;
_shaders = [TGPaintShaderSet setup];
}
- (TGPaintShader *)shaderForKey:(NSString *)key
{
return _shaders[key];
}
@end