Skip to content

Commit

Permalink
Add mix-blend-mode effects to iOS (#45304)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #45304

Add support for most keyword values of mix-blend-mode on iOS and added RNTester Example
Missing compositing operators and global values

Changelog: [Internal]

Reviewed By: NickGerleman

Differential Revision: D59402969

fbshipit-source-id: b7e1aaed01fbf8f80e04ad0fa73d2ef63b5ad933
  • Loading branch information
joevilches authored and facebook-github-bot committed Jul 16, 2024
1 parent a96272e commit 944d40f
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#import <react/renderer/components/view/ViewEventEmitter.h>
#import <react/renderer/components/view/ViewProps.h>
#import <react/renderer/components/view/accessibilityPropsConversions.h>
#import <react/renderer/graphics/BlendMode.h>

#ifdef RCT_DYNAMIC_FRAMEWORKS
#import <React/RCTComponentViewFactory.h>
Expand Down Expand Up @@ -403,6 +404,11 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
_needsInvalidateLayer = YES;
}

// `mixBlendMode`
if (oldViewProps.mixBlendMode != newViewProps.mixBlendMode) {
_needsInvalidateLayer = YES;
}

// `boxShadow`
if (oldViewProps.boxShadow != newViewProps.boxShadow) {
_needsInvalidateLayer = YES;
Expand Down Expand Up @@ -776,6 +782,57 @@ - (void)invalidateLayer
[self.layer addSublayer:_filterLayer];
}

switch (_props->mixBlendMode) {
case BlendMode::Multiply:
layer.compositingFilter = @"multiplyBlendMode";
break;
case BlendMode::Screen:
layer.compositingFilter = @"screenBlendMode";
break;
case BlendMode::Overlay:
layer.compositingFilter = @"overlayBlendMode";
break;
case BlendMode::Darken:
layer.compositingFilter = @"darkenBlendMode";
break;
case BlendMode::Lighten:
layer.compositingFilter = @"lightenBlendMode";
break;
case BlendMode::ColorDodge:
layer.compositingFilter = @"colorDodgeBlendMode";
break;
case BlendMode::ColorBurn:
layer.compositingFilter = @"colorBurnBlendMode";
break;
case BlendMode::HardLight:
layer.compositingFilter = @"hardLightBlendMode";
break;
case BlendMode::SoftLight:
layer.compositingFilter = @"softLightBlendMode";
break;
case BlendMode::Difference:
layer.compositingFilter = @"differenceBlendMode";
break;
case BlendMode::Exclusion:
layer.compositingFilter = @"exclusionBlendMode";
break;
case BlendMode::Hue:
layer.compositingFilter = @"hueBlendMode";
break;
case BlendMode::Saturation:
layer.compositingFilter = @"saturationBlendMode";
break;
case BlendMode::Color:
layer.compositingFilter = @"colorBlendMode";
break;
case BlendMode::Luminosity:
layer.compositingFilter = @"luminosityBlendMode";
break;
case BlendMode::Normal:
layer.compositingFilter = nil;
break;
}

_boxShadowLayer = nil;
if (!_props->boxShadow.empty()) {
_boxShadowLayer = [CALayer layer];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <react/renderer/core/LayoutMetrics.h>
#include <react/renderer/core/Props.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/graphics/BlendMode.h>
#include <react/renderer/graphics/BoxShadow.h>
#include <react/renderer/graphics/Color.h>
#include <react/renderer/graphics/Filter.h>
Expand Down Expand Up @@ -63,7 +64,7 @@ class BaseViewProps : public YogaStylableProps, public AccessibilityProps {
std::vector<FilterPrimitive> filter{};

// MixBlendMode
std::string mixBlendMode;
BlendMode mixBlendMode;

// Transform
Transform transform{};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ void ViewShadowNode::initialize() noexcept {
viewProps.accessibilityViewIsModal ||
viewProps.importantForAccessibility != ImportantForAccessibility::Auto ||
viewProps.removeClippedSubviews || viewProps.cursor != Cursor::Auto ||
!viewProps.filter.empty() || !viewProps.mixBlendMode.empty() ||
!viewProps.filter.empty() ||
viewProps.mixBlendMode != BlendMode::Normal ||
HostPlatformViewTraitsInitializer::formsStackingContext(viewProps);

bool formsView = formsStackingContext ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <react/renderer/core/LayoutMetrics.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/RawProps.h>
#include <react/renderer/graphics/BlendMode.h>
#include <react/renderer/graphics/BoxShadow.h>
#include <react/renderer/graphics/Filter.h>
#include <react/renderer/graphics/PlatformColorParser.h>
Expand Down Expand Up @@ -1069,6 +1070,27 @@ inline void fromRawValue(
result = filter;
}

inline void fromRawValue(
const PropsParserContext& /*context*/,
const RawValue& value,
BlendMode& result) {
react_native_expect(value.hasType<std::string>());
result = BlendMode::Normal;
if (!value.hasType<std::string>()) {
return;
}

auto rawBlendMode = static_cast<std::string>(value);
std::optional<BlendMode> blendMode = blendModeFromString(rawBlendMode);

if (!blendMode) {
LOG(ERROR) << "Could not parse blend mode: " << rawBlendMode;
return;
}

result = blendMode.value();
}

template <size_t N>
inline std::string toString(const std::array<float, N> vec) {
std::string s;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#pragma once

#include <react/renderer/graphics/Float.h>

#include <optional>
#include <string>
#include <string_view>
#include <vector>

namespace facebook::react {

enum class BlendMode {
Normal,
Multiply,
Screen,
Overlay,
Darken,
Lighten,
ColorDodge,
ColorBurn,
HardLight,
SoftLight,
Difference,
Exclusion,
Hue,
Saturation,
Color,
Luminosity,
};

inline std::optional<BlendMode> blendModeFromString(
std::string_view blendModeName) {
if (blendModeName == "normal") {
return BlendMode::Normal;
} else if (blendModeName == "multiply") {
return BlendMode::Multiply;
} else if (blendModeName == "screen") {
return BlendMode::Screen;
} else if (blendModeName == "overlay") {
return BlendMode::Overlay;
} else if (blendModeName == "darken") {
return BlendMode::Darken;
} else if (blendModeName == "lighten") {
return BlendMode::Lighten;
} else if (blendModeName == "color-dodge") {
return BlendMode::ColorDodge;
} else if (blendModeName == "color-burn") {
return BlendMode::ColorBurn;
} else if (blendModeName == "hard-light") {
return BlendMode::HardLight;
} else if (blendModeName == "soft-light") {
return BlendMode::SoftLight;
} else if (blendModeName == "difference") {
return BlendMode::Difference;
} else if (blendModeName == "exclusion") {
return BlendMode::Exclusion;
} else if (blendModeName == "hue") {
return BlendMode::Hue;
} else if (blendModeName == "saturation") {
return BlendMode::Saturation;
} else if (blendModeName == "color") {
return BlendMode::Color;
} else if (blendModeName == "luminosity") {
return BlendMode::Luminosity;
} else {
return std::nullopt;
}
}
} // namespace facebook::react
4 changes: 4 additions & 0 deletions packages/rn-tester/js/utils/RNTesterList.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,10 @@ const APIs: Array<RNTesterModuleInfo> = ([
key: 'FilterExample',
module: require('../examples/Filter/FilterExample'),
},
{
key: 'MixBlendModeExample',
module: require('../examples/MixBlendMode/MixBlendModeExample'),
},
{
key: 'TurboModuleExample',
module: require('../examples/TurboModule/TurboModuleExample'),
Expand Down

0 comments on commit 944d40f

Please sign in to comment.