flutter Display P3

Display P3

Display P3 是苹果手机相机使用的一种色域,查看图片信息可以看到, 然而在flutter中 渲染 Display P3格式的图片 bitmap颜色失真,不了解色域的参考下前面HDR相关的文章

失真效果:

修复效果:

原因就是Flutter 直接把Display P3社区的当做sRGB色域的图像处理了,而没有做色域转换

Flutter

Flutter 跟 Native 图片打通,常见有两种方式:bitmap传递 && 外接纹理,这边文章针对的是前者,外接纹理也会有这种问题的,无非就是pixelBuffer 转纹理,pixelBuffer一样也是有色域问题的,解决方案可以参考 Flutter HDR,原理是一样的。

针对图片的bitmap做色域转换,方案有很多,这里列出常见的两种:

ImageIO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
CGImageSourceRef src = CGImageSourceCreateWithData((__bridge CFDataRef) imageData, NULL);
NSUInteger frameCount = CGImageSourceGetCount(src);
if (frameCount > 0) {
NSDictionary *options = @{(__bridge NSString *)kCGImageSourceShouldCache : @YES,
(__bridge NSString *)kCGImageSourceShouldCacheImmediately : @NO
};
NSDictionary *props = (NSDictionary *) CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(src, (size_t) 0, (__bridge CFDictionaryRef)options));
NSString *profileName = [props objectForKey:(NSString *) kCGImagePropertyProfileName];
if ([profileName isEqualToString:@"Display P3"]) {

NSMutableData *data = [NSMutableData data];
CGImageDestinationRef destRef = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)data, kUTTypePNG, 1, NULL);

NSMutableDictionary *properties = [NSMutableDictionary dictionary];
properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = @(1);
properties[(__bridge NSString *)kCGImageDestinationEmbedThumbnail] = @(0);

properties[(__bridge NSString *)kCGImagePropertyNamedColorSpace] = (__bridge id _Nullable)(kCGColorSpaceSRGB);
properties[(__bridge NSString *)kCGImageDestinationOptimizeColorForSharing] = @(YES);

CGImageDestinationAddImageFromSource(destRef, src, 0, (__bridge CFDictionaryRef)properties);

CGImageDestinationFinalize(destRef);
CFRelease(destRef);
return data;

}
}

return imageData;

核心就是这个属性,很好理解吧

1
2
3
4
5
/* Create an image using a colorspace, that has is compatible with older devices
* The value should be kCFBooleanTrue or kCFBooleanFalse
* Defaults to kCFBooleanFalse = don't do any color conversion
*/
IMAGEIO_EXTERN const CFStringRef kCGImageDestinationOptimizeColorForSharing IMAGEIO_AVAILABLE_STARTING(10.12, 9.3);

重新Render一张图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
CGImageSourceRef src = CGImageSourceCreateWithData((__bridge CFDataRef) imageData, NULL);
NSUInteger frameCount = CGImageSourceGetCount(src);
if (frameCount > 0) {
NSDictionary *frameProperties = (NSDictionary *) CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(src, (size_t) 0, NULL));
NSString *profileName = [frameProperties objectForKey:(NSString *) kCGImagePropertyProfileName];
if ([profileName isEqualToString:@"Display P3"]) {
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(src, (size_t) 0, NULL);
CIImage *image = [CIImage imageWithCGImage:imageRef];

CIContext *context = [[CIContext alloc] init];


float w = image.extent.size.width;
float h = image.extent.size.height;
unsigned char *bitmap = malloc(w * h * 4);
CIRenderDestination *destination = [[CIRenderDestination alloc] initWithBitmapData:bitmap
width:w
height:h
bytesPerRow:w * 4
format:kCIFormatBGRA8];

NSError *error = nil;
[context startTaskToRender:image toDestination:destination error:&error];
if (error) {
CFRelease(src);
return imageData;
}

CFRelease(src);

UIImage *newImage = [FlutterImagePlugin imageFromBRGABytes:bitmap imageSize:image.extent.size];

free(bitmap);
CGImageRelease(imageRef);

if (newImage == nil) {
return imageData;
}


return UIImagePNGRepresentation(newImage);
}
}


+ (UIImage *)imageFromBRGABytes:(unsigned char *)imageBytes imageSize:(CGSize)imageSize {
CGImageRef imageRef = [self imageRefFromBGRABytes:imageBytes imageSize:imageSize];
if (imageRef == NULL) {
return nil;
}

UIImage *image = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
return image;
}

+ (CGImageRef)imageRefFromBGRABytes:(unsigned char *)imageBytes imageSize:(CGSize)imageSize {
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

CGContextRef context = CGBitmapContextCreate(imageBytes,
imageSize.width,
imageSize.height,
8,
imageSize.width * 4,
colorSpaceSDImage,
kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);

if (context == NULL) {
return NULL;
}

CGImageRef imageRef = CGBitmapContextCreateImage(context);
if (imageRef == NULL) {
CGContextRelease(context);
return NULL;
}
CGContextRelease(context);

return imageRef;
}

从代码量跟性能考量,无脑选前者 😝