Files
mcp_screen/iphone_app/IMCCanvasView.m
T
2026-06-21 21:27:06 -04:00

219 lines
7.8 KiB
Objective-C

#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