Index: ui/accelerated_widget_mac/ca_renderer_layer_tree.mm |
diff --git a/ui/accelerated_widget_mac/ca_renderer_layer_tree.mm b/ui/accelerated_widget_mac/ca_renderer_layer_tree.mm |
index d5b3734a72676c4e534753b54e55c877b84bdc01..7159a885416d98cc52a745c7f6ff3dae6e2ddd9e 100644 |
--- a/ui/accelerated_widget_mac/ca_renderer_layer_tree.mm |
+++ b/ui/accelerated_widget_mac/ca_renderer_layer_tree.mm |
@@ -16,7 +16,6 @@ |
#include "ui/base/cocoa/animation_utils.h" |
#include "ui/base/ui_base_switches.h" |
#include "ui/gfx/geometry/dip_util.h" |
-#include "ui/gl/ca_renderer_layer_params.h" |
#include "ui/gl/gl_image_io_surface.h" |
#if !defined(MAC_OS_X_VERSION_10_8) || \ |
@@ -46,10 +45,61 @@ extern CFStringRef const kCMSampleAttachmentKey_DisplayImmediately; |
extern const CMTime kCMTimeInvalid; |
#endif // MAC_OS_X_VERSION_10_8 |
+// CAFilter and CAColorMatrix are QuartzCore SPI. |
+@interface CAFilter : NSObject<NSCopying, NSMutableCopying, NSCoding> |
+@end |
+ |
+@interface CAFilter (QuartzCoreSPI) |
++ (CAFilter*)filterWithType:(NSString*)type; |
+@end |
+ |
+// TODO(erikchen): Test out the named filter kCAFilterColorInvert. |
+// https://crbug.com/581526. |
+extern NSString* const kCAFilterColorMatrix; |
+extern NSString* const kCAFilterColorMonochrome; |
+extern NSString* const kCAFilterColorHueRotate; |
+extern NSString* const kCAFilterColorSaturate; |
+extern NSString* const kCAFilterGaussianBlur; |
+ |
+struct CAColorMatrix { |
+ float m11, m12, m13, m14, m15; |
+ float m21, m22, m23, m24, m25; |
+ float m31, m32, m33, m34, m35; |
+ float m41, m42, m43, m44, m45; |
+}; |
+typedef struct CAColorMatrix CAColorMatrix; |
+ |
+@interface NSValue (QuartzCoreSPI) |
++ (NSValue*)valueWithCAColorMatrix:(CAColorMatrix)t; |
+@end |
+ |
namespace ui { |
namespace { |
+float Blend(float from, float to, float progress) { |
+ return from + (to - from) * progress; |
+} |
+ |
+#ifndef M_PI |
+#define M_PI 3.14159265358979323846 |
+#endif |
+ |
+double DegreeToRadians(double degree) { return degree * M_PI / 180.0; } |
+ |
+// These values were obtained from https://www.w3.org/TR/filter-effects/. |
+static const double kSepiaFullConstants[3][3] = { |
+ { 0.393, 0.769, 0.189 }, |
+ { 0.349, 0.686, 0.168 }, |
+ { 0.272, 0.534, 0.131 } |
+}; |
+ |
+static const double kSepiaNoneConstants[3][3] = { |
+ { 1, 0, 0 }, |
+ { 0, 1, 0 }, |
+ { 0, 0, 1 } |
+}; |
+ |
// This will enqueue |io_surface| to be drawn by |av_layer|. This will |
// retain |cv_pixel_buffer| until it is no longer being displayed. |
bool AVSampleBufferDisplayLayerEnqueueCVPixelBuffer( |
@@ -127,6 +177,161 @@ bool AVSampleBufferDisplayLayerEnqueueIOSurface( |
cv_pixel_buffer); |
} |
+// If the filter effect can be represented as a named filter, return it. |
+// Otherwise, return nil. |
+CAFilter* NamedFilterForType(CARendererLayerParams::FilterEffectType type, |
+ float amount) { |
+ CAFilter* filter = nil; |
+ switch (type) { |
+ case CARendererLayerParams::FilterEffectType::GRAYSCALE: |
+ filter = [CAFilter filterWithType:kCAFilterColorMonochrome]; |
+ [filter setValue:@(amount) forKey:@"inputAmount"]; |
+ break; |
+ case CARendererLayerParams::FilterEffectType::SATURATE: |
+ filter = [CAFilter filterWithType:kCAFilterColorSaturate]; |
+ [filter setValue:@(amount) forKey:@"inputAmount"]; |
+ break; |
+ case CARendererLayerParams::FilterEffectType::HUE_ROTATE: |
+ filter = [CAFilter filterWithType:kCAFilterColorHueRotate]; |
+ [filter setValue:@(DegreeToRadians(amount)) forKey:@"inputAngle"]; |
+ break; |
+ case CARendererLayerParams::FilterEffectType::BLUR: |
+ filter = [CAFilter filterWithType:kCAFilterGaussianBlur]; |
+ [filter setValue:@(amount) forKey:@"inputRadius"]; |
+ break; |
+ default: |
+ break; |
+ } |
+ return filter; |
+} |
+ |
+// If the filter effect has a corresponding color matrix, return it. Otherwise, |
+// return nil. |
+NSValue* ColorMatrixForType(CARendererLayerParams::FilterEffectType type, |
+ float amount) { |
+ switch (type) { |
+ case CARendererLayerParams::FilterEffectType::SEPIA: |
+ { |
+ float t = std::min(std::max(0.0f, amount), 1.0f); |
+ CAColorMatrix colorMatrix = { |
+ Blend(kSepiaNoneConstants[0][0], kSepiaFullConstants[0][0], t), |
+ Blend(kSepiaNoneConstants[0][1], kSepiaFullConstants[0][1], t), |
+ Blend(kSepiaNoneConstants[0][2], kSepiaFullConstants[0][2], t), |
+ 0, 0, |
+ |
+ Blend(kSepiaNoneConstants[1][0], kSepiaFullConstants[1][0], t), |
+ Blend(kSepiaNoneConstants[1][1], kSepiaFullConstants[1][1], t), |
+ Blend(kSepiaNoneConstants[1][2], kSepiaFullConstants[1][2], t), |
+ 0, 0, |
+ |
+ Blend(kSepiaNoneConstants[2][0], kSepiaFullConstants[2][0], t), |
+ Blend(kSepiaNoneConstants[2][1], kSepiaFullConstants[2][1], t), |
+ Blend(kSepiaNoneConstants[2][2], kSepiaFullConstants[2][2], t), |
+ 0, 0, 0, 0, 0, 1, 0 |
+ }; |
+ return [NSValue valueWithCAColorMatrix:colorMatrix]; |
+ } |
+ case CARendererLayerParams::FilterEffectType::INVERT: |
+ { |
+ float multiplier = 1 - amount * 2; |
+ CAColorMatrix colorMatrix = { |
+ multiplier, 0, 0, 0, amount, |
+ 0, multiplier, 0, 0, amount, |
+ 0, 0, multiplier, 0, amount, |
+ 0, 0, 0, 1, 0 |
+ }; |
+ return [NSValue valueWithCAColorMatrix:colorMatrix]; |
+ } |
+ case CARendererLayerParams::FilterEffectType::BRIGHTNESS: |
+ { |
+ CAColorMatrix colorMatrix = { |
+ amount, 0, 0, 0, 0, |
+ 0, amount, 0, 0, 0, |
+ 0, 0, amount, 0, 0, |
+ 0, 0, 0, 1, 0 |
+ }; |
+ return [NSValue valueWithCAColorMatrix:colorMatrix]; |
+ } |
+ case CARendererLayerParams::FilterEffectType::CONTRAST: |
+ { |
+ float intercept = -0.5 * amount + 0.5; |
+ CAColorMatrix colorMatrix = { |
+ amount, 0, 0, 0, intercept, |
+ 0, amount, 0, 0, intercept, |
+ 0, 0, amount, 0, intercept, |
+ 0, 0, 0, 1, 0 |
+ }; |
+ return [NSValue valueWithCAColorMatrix:colorMatrix]; |
+ } |
+ case CARendererLayerParams::FilterEffectType::OPACITY: |
+ { |
+ CAColorMatrix colorMatrix = { |
+ 1, 0, 0, 0, 0, |
+ 0, 1, 0, 0, 0, |
+ 0, 0, 1, 0, 0, |
+ 0, 0, 0, amount, 0 |
+ }; |
+ return [NSValue valueWithCAColorMatrix:colorMatrix]; |
+ } |
+ default: |
+ return nil; |
+ } |
+} |
+ |
+// Updates the CALayer to accurately represent the given |filter_effects|. |
+void UpdateFiltersOnCALayer( |
+ const CARendererLayerParams::FilterEffects& filter_effects, |
+ CALayer* layer) { |
+ if (filter_effects.empty()) { |
+ // It's possible that this enables shadow properties, even if there were |
+ // none before. That's an implementation detail of Core Animation. |
+ [layer setShadowOffset:CGSizeZero]; |
+ [layer setShadowColor:nil]; |
+ [layer setShadowRadius:0]; |
+ [layer setShadowOpacity:0]; |
+ layer.filters = @[]; |
+ return; |
+ } |
+ |
+ NSMutableArray* filters = [NSMutableArray array]; |
+ for (const CARendererLayerParams::FilterEffect& filter_effect : |
+ filter_effects) { |
+ CAFilter* filter = |
+ NamedFilterForType(filter_effect.type, filter_effect.amount); |
+ if (filter) { |
+ [filters addObject:filter]; |
+ continue; |
+ } |
+ |
+ NSValue* color_matrix = |
+ ColorMatrixForType(filter_effect.type, filter_effect.amount); |
+ if (color_matrix) { |
+ CAFilter* filter = [CAFilter filterWithType:kCAFilterColorMatrix]; |
+ [filter setValue:color_matrix forKey:@"inputColorMatrix"]; |
+ [filters addObject:filter]; |
+ continue; |
+ } |
+ |
+ DCHECK_EQ(CARendererLayerParams::FilterEffectType::DROP_SHADOW, |
+ filter_effect.type); |
+ [layer setShadowOffset:CGSizeMake(filter_effect.drop_shadow_offset.x(), |
+ filter_effect.drop_shadow_offset.y())]; |
+ |
+ CGFloat rgba_color_components[4] = { |
+ SkColorGetR(filter_effect.drop_shadow_color) / 255., |
+ SkColorGetG(filter_effect.drop_shadow_color) / 255., |
+ SkColorGetB(filter_effect.drop_shadow_color) / 255., |
+ SkColorGetA(filter_effect.drop_shadow_color) / 255., |
+ }; |
+ base::ScopedCFTypeRef<CGColorRef> srgb_color(CGColorCreate( |
+ CGColorSpaceCreateWithName(kCGColorSpaceSRGB), rgba_color_components)); |
+ [layer setShadowColor:srgb_color.get()]; |
+ [layer setShadowRadius:filter_effect.amount]; |
+ [layer setShadowOpacity:1]; |
+ } |
+ layer.filters = filters; |
+} |
+ |
} // namespace |
CARendererLayerTree::CARendererLayerTree() {} |
@@ -274,7 +479,8 @@ CARendererLayerTree::ContentLayer::ContentLayer( |
unsigned background_color, |
unsigned edge_aa_mask, |
float opacity, |
- unsigned filter) |
+ unsigned filter, |
+ const CARendererLayerParams::FilterEffects& filter_effects) |
: io_surface(io_surface), |
cv_pixel_buffer(cv_pixel_buffer), |
contents_rect(contents_rect), |
@@ -282,7 +488,8 @@ CARendererLayerTree::ContentLayer::ContentLayer( |
background_color(background_color), |
ca_edge_aa_mask(0), |
opacity(opacity), |
- ca_filter(filter == GL_LINEAR ? kCAFilterLinear : kCAFilterNearest) { |
+ ca_filter(filter == GL_LINEAR ? kCAFilterLinear : kCAFilterNearest), |
+ filter_effects(filter_effects) { |
DCHECK(filter == GL_LINEAR || filter == GL_NEAREST); |
// Because the root layer has setGeometryFlipped:YES, there is some ambiguity |
@@ -329,7 +536,8 @@ CARendererLayerTree::ContentLayer::ContentLayer(ContentLayer&& layer) |
ca_filter(layer.ca_filter), |
ca_layer(std::move(layer.ca_layer)), |
av_layer(std::move(layer.av_layer)), |
- use_av_layer(layer.use_av_layer) { |
+ use_av_layer(layer.use_av_layer), |
+ filter_effects(layer.filter_effects) { |
DCHECK(!layer.ca_layer); |
DCHECK(!layer.av_layer); |
} |
@@ -407,7 +615,7 @@ void CARendererLayerTree::TransformLayer::AddContentLayer( |
content_layers.push_back( |
ContentLayer(io_surface, cv_pixel_buffer, params.contents_rect, |
params.rect, params.background_color, params.edge_aa_mask, |
- params.opacity, params.filter)); |
+ params.opacity, params.filter, params.filter_effects)); |
} |
void CARendererLayerTree::RootLayer::CommitToCA(CALayer* superlayer, |
@@ -535,6 +743,9 @@ void CARendererLayerTree::ContentLayer::CommitToCA(CALayer* superlayer, |
bool update_ca_edge_aa_mask = true; |
bool update_opacity = true; |
bool update_ca_filter = true; |
+ bool update_filter_effects = |
+ (old_layer && !old_layer->filter_effects.empty()) || |
+ !filter_effects.empty(); |
if (old_layer && old_layer->use_av_layer == use_av_layer) { |
DCHECK(old_layer->ca_layer); |
std::swap(ca_layer, old_layer->ca_layer); |
@@ -547,6 +758,7 @@ void CARendererLayerTree::ContentLayer::CommitToCA(CALayer* superlayer, |
update_ca_edge_aa_mask = old_layer->ca_edge_aa_mask != ca_edge_aa_mask; |
update_opacity = old_layer->opacity != opacity; |
update_ca_filter = old_layer->ca_filter != ca_filter; |
+ update_filter_effects = old_layer->filter_effects != filter_effects; |
} else { |
if (use_av_layer) { |
av_layer.reset([[AVSampleBufferDisplayLayer alloc] init]); |
@@ -562,7 +774,7 @@ void CARendererLayerTree::ContentLayer::CommitToCA(CALayer* superlayer, |
bool update_anything = update_contents || update_contents_rect || |
update_rect || update_background_color || |
update_ca_edge_aa_mask || update_opacity || |
- update_ca_filter; |
+ update_ca_filter || update_filter_effects; |
if (use_av_layer) { |
if (update_contents) { |
if (cv_pixel_buffer) { |
@@ -606,6 +818,9 @@ void CARendererLayerTree::ContentLayer::CommitToCA(CALayer* superlayer, |
[ca_layer setMagnificationFilter:ca_filter]; |
[ca_layer setMinificationFilter:ca_filter]; |
} |
+ if (update_filter_effects) { |
+ UpdateFiltersOnCALayer(filter_effects, ca_layer.get()); |
+ } |
static bool show_borders = base::CommandLine::ForCurrentProcess()->HasSwitch( |
switches::kShowMacOverlayBorders); |