blob: 979b0e80595bd20184fbfae080db170bb6aa6af9 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#import "WXCanvasComponent.h"
#import "WXComponent_internal.h"
#import "WXUtility.h"
#import "WXConvert.h"
#import <Foundation/Foundation.h>
#import <OpenGLES/ES2/gl.h>
#import <OpenGLES/ES2/glext.h>
@interface WXCanvasComponent()
@property (nonatomic, strong) GLKView *glkview;
@property (nonatomic, strong) NSMutableDictionary *cacheMap;
@property (nonatomic, strong) NSMutableArray *drawActionList;
@property (nonatomic, strong) GLKBaseEffect *effect;
@property (nonatomic, strong) CADisplayLink *displayLink;
@property (nonatomic, assign) NSInteger fps;
@property (nonatomic, strong) WXCanvasModule *canvasModule;
@property (nonatomic, assign) GLKVector4 curFillColor;
@property (nonatomic, assign) GLKVector4 curStrokeColor;
@property (nonatomic, assign) GLfloat curLineWidth;
@property (nonatomic, assign) GLfloat curGlobalAlpha;
@end
@implementation WXCanvasComponent
- (instancetype)initWithRef:(NSString *)ref
type:(NSString *)type
styles:(NSDictionary *)styles
attributes:(NSDictionary *)attributes
events:(NSArray *)events
weexInstance:(WXSDKInstance *)weexInstance
{
if (self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]) {
_fps = [WXConvert NSInteger:attributes[@"fps"]];
if (!_fps) {
_fps = 30;
}
}
return self;
}
- (UIView *)loadView
{
_glkview = [[GLKView alloc] init];
EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
_glkview.context = context;
_glkview.delegate = self;
_glkview.enableSetNeedsDisplay = NO;
_cacheMap = [NSMutableDictionary new];
return _glkview;
}
- (void) viewDidLoad
{
[super viewDidLoad];
_drawActionList = [[NSMutableArray alloc] init];
_effect = [[GLKBaseEffect alloc] init];
CGSize size = _glkview.frame.size;
_effect.transform.projectionMatrix = GLKMatrix4MakeOrtho(0, size.width, 0, size.height, -1024, 1024);
_effect.transform.modelviewMatrix = GLKMatrix4Multiply(GLKMatrix4MakeTranslation(0, size.height, 0), GLKMatrix4MakeScale(1, -1, 1));
_curStrokeColor = GLKVector4Make(1, 1, 1, 1);
_curFillColor = GLKVector4Make(1, 1, 1, 1);
_curGlobalAlpha = 1;
_curLineWidth = 1;
}
- (void) viewDidUnload
{
[_displayLink invalidate];
_displayLink = nil;
[_drawActionList removeAllObjects];
_drawActionList = nil;
_effect = nil;
_canvasModule = nil;
[_cacheMap removeAllObjects];
_cacheMap = nil;
[super viewDidUnload];
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
NSInteger count = [_drawActionList count];
for (NSInteger i = 0; i < count; i++) {
NSArray *action = (NSArray *)_drawActionList[i];
NSInteger paramsCount = [action count];
SEL method;
if (paramsCount == 1) {
method = NSSelectorFromString(action[0]);
} else {
method = NSSelectorFromString([action[0] stringByAppendingString:@":"]);
}
if (![self respondsToSelector:method]) {
continue;
}
id params = nil;
if (paramsCount > 1) {
params = action[1];
}
[self performSelectorOnMainThread:method withObject:params waitUntilDone:YES];
}
[_drawActionList removeAllObjects];
}
// [
// [setFillStyle, ["black"]],
// [fillRect, [0, 0, 750, 750]],
// [drawImages, [imgURL, [[sx, sy, sw, sh, dx, dy, dw, dh], [sx, sy, sw, sh, dx, dy, dw, dh]]]],
// ]
- (void) addDrawActions:(NSArray *)actions canvasModule:(WXCanvasModule*)canvasModule
{
[_drawActionList removeAllObjects];
[_drawActionList addObjectsFromArray:actions];
_canvasModule = canvasModule;
[_glkview display];
}
-(GLKTextureInfo *) getTexture:(NSString *)imageURL
{
NSString *key = [NSString stringWithFormat:@"texture~%@", imageURL];
if ([_cacheMap objectForKey:key]) {
return (GLKTextureInfo *)[_cacheMap objectForKey:key];
}
// Image can cache by weexModule, so use it to get the UIImage
UIImage *image = [_canvasModule getImage:imageURL];
NSError *error;
NSDictionary *options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES]
forKey:GLKTextureLoaderApplyPremultiplication];
GLKTextureInfo *texture = [GLKTextureLoader textureWithCGImage:image.CGImage options:options error:&error];
if (texture != nil) {
[_cacheMap setObject:texture forKey:key];
}
return texture;
}
-(void) handleDisplayLink
{
[self fireEvent:@"tick" params:nil];
}
- (void)addEvent:(NSString *)eventName
{
if ([eventName isEqualToString:@"tick"]) {
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink)];
float i = 60.0 / _fps;
_displayLink.frameInterval = i;
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
}
// OpenGL ES methods map
#define WX_CANVAS_PIXEL(name, index) WXPixelType name = [WXConvert WXPixelType:params[index] scaleFactor:self.weexInstance.pixelScaleFactor];
#define WX_CANVAS_CGFLOAT(name, index) CGFloat name = [WXConvert CGFloat:params[index]];
#define WX_CANVAS_PARAMS_CHECK(num) if ([params count] < num) return;
// [imageURL, [[sx, sy, sw, sh, dx, dy, dw, dh], [sx, sy, sw, sh, dx, dy, dw, dh, m00, m01, m02, m10, m11, m12, m20, m21, m22]]]
- (void) drawImages:(NSArray *)params
{
WX_CANVAS_PARAMS_CHECK(2);
GLKTextureInfo *textureInfo = [self getTexture:[WXConvert NSString:params[0]]];
CGFloat textureWidth = textureInfo.width;
CGFloat textureHeight = textureInfo.height;
NSArray *drawParams = (NSArray *)params[1];
const GLint count = (GLint)[drawParams count] * 4;
GLKVector3 geometrys[count];
GLKVector3 textures[count];
const GLint indexLen = (GLint)drawParams.count * 6;
GLuint indices[indexLen];
for (GLint i = 0; i < drawParams.count; i++) {
NSArray *curParams = (NSArray *)drawParams[i];
if (curParams.count < 8) {
continue;
}
CGFloat sx = [WXConvert CGFloat:curParams[0]];
CGFloat sy = [WXConvert CGFloat:curParams[1]];
CGFloat sw = [WXConvert CGFloat:curParams[2]];
CGFloat sh = [WXConvert CGFloat:curParams[3]];
CGFloat dx = [WXConvert WXPixelType:curParams[4] scaleFactor:self.weexInstance.pixelScaleFactor];
CGFloat dy = [WXConvert WXPixelType:curParams[5] scaleFactor:self.weexInstance.pixelScaleFactor];
CGFloat dw = [WXConvert WXPixelType:curParams[6] scaleFactor:self.weexInstance.pixelScaleFactor];
CGFloat dh = [WXConvert WXPixelType:curParams[7] scaleFactor:self.weexInstance.pixelScaleFactor];
geometrys[i * 4] = GLKVector3Make(dx, dh + dy, 1);
geometrys[i * 4 + 1] = GLKVector3Make(dw + dx, dh + dy, 1);
geometrys[i * 4 + 2] = GLKVector3Make(dx, dy, 1);
geometrys[i * 4 + 3] = GLKVector3Make(dw + dx, dy, 1);
if (curParams.count == 17) {
// If the length of curParams is 17, the tail of curParams is the transform matrix of this draw
// m00, m01, m02, m10, m11, m12, m20, m21, m22
GLKMatrix3 matrix = GLKMatrix3Make(
[WXConvert CGFloat:curParams[8]],
[WXConvert CGFloat:curParams[9]],
[WXConvert CGFloat:curParams[10]],
[WXConvert CGFloat:curParams[11]],
[WXConvert CGFloat:curParams[12]],
[WXConvert CGFloat:curParams[13]],
[WXConvert CGFloat:curParams[14]],
[WXConvert CGFloat:curParams[15]],
[WXConvert CGFloat:curParams[16]]
);
// Because the params are base on 750, so we should make scale for the transform matrix
matrix = GLKMatrix3Scale(matrix, 1, 1, self.weexInstance.pixelScaleFactor);
geometrys[i * 4] = GLKMatrix3MultiplyVector3(matrix, geometrys[i * 4]);
geometrys[i * 4 + 1] = GLKMatrix3MultiplyVector3(matrix, geometrys[i * 4 + 1]);
geometrys[i * 4 + 2] = GLKMatrix3MultiplyVector3(matrix, geometrys[i * 4 + 2]);
geometrys[i * 4 + 3] = GLKMatrix3MultiplyVector3(matrix, geometrys[i * 4 + 3]);
}
textures[i * 4] = GLKVector3Make(sx / textureWidth, (sy + sh) / textureHeight, 1);
textures[i * 4 + 1] = GLKVector3Make((sx + sw) / textureWidth, (sy + sh) / textureHeight, 1);
textures[i * 4 + 2] = GLKVector3Make(sx / textureWidth, sy / textureHeight, 1);
textures[i * 4 + 3] = GLKVector3Make((sx + sw) / textureWidth, sy / textureHeight, 1);
// two triangles 0,1,2 and 1,2,3 make a rectangle
indices[i * 6] = i * 4;
indices[i * 6 + 1] = i * 4 + 1;
indices[i * 6 + 2] = i * 4 + 2;
indices[i * 6 + 3] = i * 4 + 1;
indices[i * 6 + 4] = i * 4 + 2;
indices[i * 6 + 5] = i * 4 + 3;
}
glEnable(GL_BLEND);
if (_curGlobalAlpha != 1) {
glBlendColor(1, 1, 1, _curGlobalAlpha);
glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE);
} else {
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
}
_effect.useConstantColor = GL_FALSE;
_effect.texture2d0.enabled = YES;
_effect.texture2d0.name = textureInfo.name;
[_effect prepareToDraw];
glEnableVertexAttribArray(GLKVertexAttribPosition);
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 0, &geometrys);
glVertexAttribPointer(GLKVertexAttribTexCoord0, 3, GL_FLOAT, GL_FALSE, 0, &textures);
glDrawElements(GL_TRIANGLES, indexLen, GL_UNSIGNED_INT, indices);
}
- (void) setGlobalAlpha:(NSArray *)params
{
WX_CANVAS_PARAMS_CHECK(1);
_curGlobalAlpha = [WXConvert CGFloat:params[0]];
}
// use rgba(0, 0, 0, 0) to fill the rect
- (void) clearRect:(NSArray *)params
{
WX_CANVAS_PARAMS_CHECK(4);
WX_CANVAS_PIXEL(x, 0)
WX_CANVAS_PIXEL(y, 1)
WX_CANVAS_PIXEL(w, 2)
WX_CANVAS_PIXEL(h, 3)
_effect.useConstantColor = GL_TRUE;
_effect.constantColor = GLKVector4Make(0, 0, 0, 0);
_effect.texture2d0.enabled = NO;
[_effect prepareToDraw];
glBlendFunc(GL_ONE, GL_ZERO);
glEnableVertexAttribArray(GLKVertexAttribPosition);
GLfloat points[] = {
x, y,
x, y + h,
x + w, y,
x + w, y + h
};
glVertexAttribPointer(GLKVertexAttribPosition, 2, GL_FLOAT, GL_FALSE, 0, &points);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
- (void) setStrokeStyle:(NSArray *)params
{
WX_CANVAS_PARAMS_CHECK(1);
UIColor *color = [WXConvert UIColor:params[0]];
CGFloat red = 0.0, green = 0.0, blue = 0.0, alpha =0.0;
[color getRed:&red green:&green blue:&blue alpha:&alpha];
_curStrokeColor = GLKVector4Make(red, green, blue, alpha);
}
- (void) setLineWidth:(NSArray *)params
{
WX_CANVAS_PARAMS_CHECK(1);
_curLineWidth = [WXConvert WXPixelType:params[0] scaleFactor:self.weexInstance.pixelScaleFactor];
}
// ["M", 0, 0, "L", 100, 100, "L", 0, 100, "L", 0, 0] it made a triangle
- (void) strokeLines:(NSArray *)params
{
WX_CANVAS_PARAMS_CHECK(1);
const NSInteger pointsCount = floor([params count] / 3);
GLKVector2 points[pointsCount];
GLint indices[pointsCount * 2];
GLint pointIndex = 0;
GLint lineIndex = 0;
BOOL isLastMove = YES;
for (GLint i = 0; i < params.count; i += 3) {
NSString *type = [WXConvert NSString:params[i]];
points[pointIndex] = GLKVector2Make([WXConvert WXPixelType:params[i + 1] scaleFactor:self.weexInstance.pixelScaleFactor], [WXConvert WXPixelType:params[i + 2] scaleFactor:self.weexInstance.pixelScaleFactor]);
if ([type isEqualToString:@"L"]) {
if (isLastMove) {
indices[lineIndex++] = pointIndex;
if (lineIndex > 1) {
isLastMove = NO;
}
} else {
indices[lineIndex] = indices[lineIndex - 1];
lineIndex += 1;
indices[lineIndex++] = pointIndex;
}
} else if (isLastMove && lineIndex > 0) {
isLastMove = YES;
indices[lineIndex - 1] = pointIndex;
} else {
isLastMove = YES;
indices[lineIndex++] = pointIndex;
}
pointIndex += 1;
}
_effect.useConstantColor = GL_TRUE;
_effect.constantColor = _curStrokeColor;
_effect.texture2d0.enabled = NO;
[_effect prepareToDraw];
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnableVertexAttribArray(GLKVertexAttribPosition);
glLineWidth(_curLineWidth);
glVertexAttribPointer(GLKVertexAttribPosition, 2, GL_FLOAT, GL_FALSE, 0, &points);
glDrawElements(GL_LINES, lineIndex, GL_UNSIGNED_INT, indices);
}
- (void) strokeRect:(NSArray *)params
{
WX_CANVAS_PARAMS_CHECK(4);
WX_CANVAS_PIXEL(x, 0)
WX_CANVAS_PIXEL(y, 1)
WX_CANVAS_PIXEL(w, 2)
WX_CANVAS_PIXEL(h, 3)
_effect.useConstantColor = GL_TRUE;
_effect.constantColor = _curStrokeColor;
_effect.texture2d0.enabled = NO;
[_effect prepareToDraw];
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnableVertexAttribArray(GLKVertexAttribPosition);
GLfloat points[] = {
x, y,
x, y + h,
x + w, y + h,
x + w, y,
x, y
};
glLineWidth(_curLineWidth);
glVertexAttribPointer(GLKVertexAttribPosition, 2, GL_FLOAT, GL_FALSE, 0, &points);
glDrawArrays(GL_LINE_STRIP, 0, 5);
}
// only support color, such as red, rgb(100, 100, 100), rgba(0, 0, 0, 1), #cccccc
-(void) setFillStyle:(NSArray *)params
{
WX_CANVAS_PARAMS_CHECK(1);
UIColor *color = [WXConvert UIColor:params[0]];
CGFloat red = 0.0, green = 0.0, blue = 0.0, alpha =0.0;
[color getRed:&red green:&green blue:&blue alpha:&alpha];
_curFillColor = GLKVector4Make(red, green, blue, alpha);
}
- (void) fillRect:(NSArray *)params
{
WX_CANVAS_PARAMS_CHECK(4);
WX_CANVAS_PIXEL(x, 0)
WX_CANVAS_PIXEL(y, 1)
WX_CANVAS_PIXEL(w, 2)
WX_CANVAS_PIXEL(h, 3)
_effect.useConstantColor = GL_TRUE;
_effect.constantColor = _curFillColor;
_effect.texture2d0.enabled = NO;
[_effect prepareToDraw];
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnableVertexAttribArray(GLKVertexAttribPosition);
GLfloat points[] = {
x, y,
x, y + h,
x + w, y,
x + w, y + h
};
glVertexAttribPointer(GLKVertexAttribPosition, 2, GL_FLOAT, GL_FALSE, 0, &points);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
@end