#import "TGPainting.h" #import "LegacyComponentsInternal.h" #import #import #import #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 #import @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