#import "IMCCanvasView.h" @interface IMCCanvasView () @property (nonatomic, assign) NSInteger clearColor; @property (nonatomic, strong) NSMutableArray *commands; @property (nonatomic, strong) UIImage *streamImage; @end @implementation IMCCanvasView - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { _clearColor = 0; _commands = [NSMutableArray array]; self.opaque = YES; } return self; } - (void)clearWithColor:(NSInteger)color { self.clearColor = color == 1 ? 1 : 0; self.streamImage = nil; [self.commands removeAllObjects]; [self setNeedsDisplay]; } - (void)drawText:(NSString *)text x:(CGFloat)x y:(CGFloat)y size:(NSInteger)size { if (!text) { text = @""; } self.streamImage = nil; [self.commands addObject:@{ @"type": @"text", @"text": text, @"x": @(x), @"y": @(y), @"size": @(size == 2 ? 2 : 1) }]; [self setNeedsDisplay]; } - (void)drawImageData:(NSData *)data x:(CGFloat)x y:(CGFloat)y { UIImage *image = [UIImage imageWithData:data]; if (!image) { return; } self.streamImage = nil; [self.commands addObject:@{ @"type": @"image", @"image": image, @"x": @(x), @"y": @(y) }]; [self setNeedsDisplay]; } - (void)displayMonochromeFrameBuffer:(NSData *)frameData { if (frameData.length < 15000) { return; } const NSUInteger width = 400; const NSUInteger height = 300; NSMutableData *rgba = [NSMutableData dataWithLength:width * height * 4]; const uint8_t *source = frameData.bytes; uint8_t *pixels = rgba.mutableBytes; for (NSUInteger y = 0; y < height; y++) { NSUInteger invY = height - 1 - y; for (NSUInteger x = 0; x < width; x++) { NSUInteger bx = x / 2; NSUInteger by = invY / 4; NSUInteger index = bx * 75 + by; NSUInteger bit = 7 - ((invY % 4) * 2 + (x % 2)); BOOL on = (source[index] & (1 << bit)) != 0; uint8_t value = on ? 255 : 0; NSUInteger pixelIndex = (y * width + x) * 4; pixels[pixelIndex + 0] = value; pixels[pixelIndex + 1] = value; pixels[pixelIndex + 2] = value; pixels[pixelIndex + 3] = 255; } } CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)rgba); CGImageRef cgImage = CGImageCreate(width, height, 8, 32, width * 4, colorSpace, kCGImageAlphaLast | kCGBitmapByteOrderDefault, provider, NULL, false, kCGRenderingIntentDefault); if (cgImage) { self.streamImage = [UIImage imageWithCGImage:cgImage]; [self.commands removeAllObjects]; [self setNeedsDisplay]; CGImageRelease(cgImage); } CGDataProviderRelease(provider); CGColorSpaceRelease(colorSpace); } - (BOOL)displayRGB565FrameBuffer:(NSData *)frameData width:(NSUInteger)width height:(NSUInteger)height { if (width == 0 || height == 0 || width > 2048 || height > 2048 || frameData.length != width * height * 2) { return NO; } const uint8_t *source = frameData.bytes; NSMutableData *rgba = [NSMutableData dataWithLength:width * height * 4]; uint8_t *pixels = rgba.mutableBytes; for (NSUInteger i = 0; i < width * height; i++) { // Network color frames use big-endian RGB565. uint16_t value = ((uint16_t)source[i * 2] << 8) | source[i * 2 + 1]; uint8_t red5 = (value >> 11) & 0x1f; uint8_t green6 = (value >> 5) & 0x3f; uint8_t blue5 = value & 0x1f; pixels[i * 4 + 0] = (red5 << 3) | (red5 >> 2); pixels[i * 4 + 1] = (green6 << 2) | (green6 >> 4); pixels[i * 4 + 2] = (blue5 << 3) | (blue5 >> 2); pixels[i * 4 + 3] = 255; } CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)rgba); CGImageRef cgImage = CGImageCreate(width, height, 8, 32, width * 4, colorSpace, kCGImageAlphaLast | kCGBitmapByteOrderDefault, provider, NULL, false, kCGRenderingIntentDefault); if (cgImage) { self.streamImage = [UIImage imageWithCGImage:cgImage]; [self.commands removeAllObjects]; [self setNeedsDisplay]; CGImageRelease(cgImage); } CGDataProviderRelease(provider); CGColorSpaceRelease(colorSpace); return cgImage != NULL; } - (NSString *)snapshotPNGBase64 { UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, 0.0); [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES]; UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); NSData *pngData = UIImagePNGRepresentation(image); return [pngData base64EncodedStringWithOptions:0]; } - (CGPoint)pointForDeviceX:(CGFloat)x y:(CGFloat)y { CGFloat scaleX = self.bounds.size.width / 400.0; CGFloat scaleY = self.bounds.size.height / 300.0; return CGPointMake(x * scaleX, y * scaleY); } - (void)drawRect:(CGRect)rect { UIColor *background = self.clearColor == 1 ? [UIColor blackColor] : [UIColor whiteColor]; UIColor *foreground = self.clearColor == 1 ? [UIColor whiteColor] : [UIColor blackColor]; [background setFill]; UIRectFill(self.bounds); if (self.streamImage) { [self.streamImage drawInRect:self.bounds]; return; } CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetStrokeColorWithColor(context, foreground.CGColor); CGContextSetLineWidth(context, 2.0); CGContextStrokeRect(context, CGRectInset(self.bounds, 1.0, 1.0)); for (NSDictionary *command in self.commands) { NSString *type = command[@"type"]; CGFloat x = [command[@"x"] doubleValue]; CGFloat y = [command[@"y"] doubleValue]; CGPoint point = [self pointForDeviceX:x y:y]; if ([type isEqualToString:@"text"]) { NSInteger size = [command[@"size"] integerValue]; UIFont *font = [UIFont boldSystemFontOfSize:size == 2 ? 26.0 : 15.0]; NSDictionary *attrs = @{ NSFontAttributeName: font, NSForegroundColorAttributeName: foreground }; [command[@"text"] drawAtPoint:point withAttributes:attrs]; } else if ([type isEqualToString:@"image"]) { UIImage *image = command[@"image"]; CGFloat maxWidth = self.bounds.size.width - point.x; CGFloat maxHeight = self.bounds.size.height - point.y; CGSize imageSize = image.size; CGFloat scale = MIN(maxWidth / MAX(imageSize.width, 1.0), maxHeight / MAX(imageSize.height, 1.0)); scale = MIN(scale, 1.0); CGRect imageRect = CGRectMake(point.x, point.y, imageSize.width * scale, imageSize.height * scale); [image drawInRect:imageRect]; } } } @end