vault backup: 2024-10-12 17:19:45

This commit is contained in:
2024-10-12 17:19:46 +08:00
parent ff94ddca61
commit 244c0c52f6
960 changed files with 31348 additions and 10 deletions

View File

@@ -0,0 +1,36 @@
# BlueprintAssignable
- **功能描述:** 在蓝图中可以为这个多播委托绑定事件
- **元数据类型:** bool
- **引擎模块:** Blueprint
- **限制类型:** Multicast Delegates
- **作用机制:** 在PropertyFlags中加入[CPF_BlueprintAssignable](../../../../Flags/EPropertyFlags/CPF_BlueprintAssignable.md)
- **常用程度:** ★★★
## C++的测试代码:
```cpp
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMyDynamicMulticastDelegate_One, int32, Value);
UPROPERTY(EditAnywhere, BlueprintReadWrite, BlueprintAssignable, BlueprintCallable)
FMyDynamicMulticastDelegate_One MyMulticastDelegateAssignAndCall;
UPROPERTY(EditAnywhere, BlueprintReadWrite, BlueprintCallable)
FMyDynamicMulticastDelegate_One MyMulticastDelegateCall;
UPROPERTY(EditAnywhere, BlueprintReadWrite, BlueprintAssignable)
FMyDynamicMulticastDelegate_One MyMulticastDelegateAssign;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FMyDynamicMulticastDelegate_One MyMulticastDelegate;
```
## 蓝图中的表现:
![Untitled](Untitled.png)
因此一般建议二者标记都加上:
![Untitled](Untitled%201.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,23 @@
# BlueprintAuthorityOnly
- **功能描述:** 只能绑定为BlueprintAuthorityOnly的事件让该多播委托只接受在服务端运行的事件
- **元数据类型:** bool
- **引擎模块:** Blueprint, Network
- **限制类型:** Multicast Delegates
- **作用机制:** 在PropertyFlags中加入[CPF_BlueprintAuthorityOnly](../../../../Flags/EPropertyFlags/CPF_BlueprintAuthorityOnly.md)
- **常用程度:** ★★★
## 测试代码:
```cpp
UPROPERTY(EditAnywhere, BlueprintReadWrite, BlueprintAssignable, BlueprintCallable)
FMyDynamicMulticastDelegate_One MyMulticastDelegateAssignAndCall;
UPROPERTY(EditAnywhere, BlueprintReadWrite, BlueprintAssignable, BlueprintCallable, BlueprintAuthorityOnly)
FMyDynamicMulticastDelegate_One MyMulticastDelegateAuthorityOnly;
```
## 蓝图中表现:
![Untitled](Untitled.png)

View File

@@ -0,0 +1,47 @@
# BlueprintCallable
- **功能描述:** 在蓝图中可以调用这个多播委托
- **元数据类型:** bool
- **引擎模块:** Blueprint
- **限制类型:** Multicast Delegates
- **作用机制:** 在PropertyFlags中加入[CPF_BlueprintCallable](../../../../Flags/EPropertyFlags/CPF_BlueprintCallable.md)
- **常用程度:** ★★★
在蓝图中可以调用这个多播委托。
## 示例代码:
```cpp
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMyDynamicMulticastDelegate_One, int32, Value);
UPROPERTY(EditAnywhere, BlueprintReadWrite, BlueprintAssignable, BlueprintCallable)
FMyDynamicMulticastDelegate_One MyMulticastDelegateAssignAndCall;
UPROPERTY(EditAnywhere, BlueprintReadWrite, BlueprintCallable)
FMyDynamicMulticastDelegate_One MyMulticastDelegateCall;
UPROPERTY(EditAnywhere, BlueprintReadWrite, BlueprintAssignable)
FMyDynamicMulticastDelegate_One MyMulticastDelegateAssign;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FMyDynamicMulticastDelegate_One MyMulticastDelegate;
```
## 示例效果:
![Untitled](Untitled.png)
注意BlueprintAssignable和BlueprintCallable只能用于多播委托
```cpp
DECLARE_DYNAMIC_DELEGATE_OneParam(FMyDynamicSinglecastDelegate_One, int32, Value);
//编译报错:'BlueprintCallable' is only allowed on a property when it is a multicast delegate
UPROPERTY(EditAnywhere, BlueprintReadWrite, BlueprintCallable)
FMyDynamicSinglecastDelegate_One MyMyDelegate4;
//编译报错:'BlueprintAssignable' is only allowed on a property when it is a multicast delegate
UPROPERTY(EditAnywhere, BlueprintReadWrite, BlueprintAssignable)
FMyDynamicSinglecastDelegate_One MyMyDelegate5;
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -0,0 +1,42 @@
# BlueprintGetter
- **功能描述:** 为属性定义一个自定义的Get函数来读取。
- **元数据类型:** string="abc"
- **引擎模块:** Blueprint
- **作用机制:** 在PropertyFlags中加入[CPF_BlueprintReadOnly](../../../../Flags/EPropertyFlags/CPF_BlueprintReadOnly.md)、[CPF_BlueprintVisible](../../../../Flags/EPropertyFlags/CPF_BlueprintVisible.md)在Meta中加入[BlueprintGetter](../../../../Meta/Blueprint/BlueprintGetter.md)
- **常用程度:** ★★★
为属性定义一个自定义的Get函数来读取。
如果没有设置BlueprintSetter或BlueprintReadWrite则会默认设置BlueprintReadOnly这个属性变成只读的。
## 示例代码:
```cpp
public:
//(BlueprintGetter = , Category = Blueprint, ModuleRelativePath = Property/MyProperty_Test.h)
UFUNCTION(BlueprintGetter, Category = Blueprint) //or BlueprintPure
int32 MyInt_Getter()const { return MyInt_WithGetter * 2; }
//(BlueprintSetter = , Category = Blueprint, ModuleRelativePath = Property/MyProperty_Test.h)
UFUNCTION(BlueprintSetter, Category = Blueprint) //or BlueprintCallable
void MyInt_Setter(int NewValue) { MyInt_WithSetter = NewValue / 4; }
private:
//(BlueprintGetter = MyInt_Getter, Category = Blueprint, ModuleRelativePath = Property/MyProperty_Test.h)
//PropertyFlags: CPF_BlueprintVisible | CPF_BlueprintReadOnly | CPF_ZeroConstructor | CPF_IsPlainOldData | CPF_NoDestructor | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierPrivate
UPROPERTY(BlueprintGetter = MyInt_Getter, Category = Blueprint)
int32 MyInt_WithGetter = 123;
//(BlueprintSetter = MyInt_Setter, Category = Blueprint, ModuleRelativePath = Property/MyProperty_Test.h)
//PropertyFlags: CPF_BlueprintVisible | CPF_ZeroConstructor | CPF_IsPlainOldData | CPF_NoDestructor | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierPrivate
UPROPERTY(BlueprintSetter = MyInt_Setter, Category = Blueprint)
int32 MyInt_WithSetter = 123;
```
## 示例效果:
可见MyInt_WithGetter是只读的。
而MyInt_WithSetter 是可读写的。
![Untitled](Untitled.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -0,0 +1,69 @@
# BlueprintReadOnly
- **功能描述:** 此属性可由蓝图读取,但不能被修改。
- **元数据类型:** bool
- **引擎模块:** Blueprint
- **作用机制:** 在PropertyFlags中加入[CPF_BlueprintVisible](../../../../Flags/EPropertyFlags/CPF_BlueprintVisible.md), [CPF_BlueprintReadOnly](../../../../Flags/EPropertyFlags/CPF_BlueprintReadOnly.md)
- **常用程度:** ★★★★★
此属性可由蓝图读取,但不能被修改。此说明符与 BlueprintReadWrite 说明符不兼容。
## 示例代码:
```cpp
public:
//PropertyFlags: CPF_BlueprintVisible | CPF_ZeroConstructor | CPF_IsPlainOldData | CPF_NoDestructor | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierPublic
UPROPERTY(BlueprintReadWrite, Category = Blueprint)
int32 MyInt_ReadWrite = 123;
//PropertyFlags: CPF_BlueprintVisible | CPF_BlueprintReadOnly | CPF_ZeroConstructor | CPF_IsPlainOldData | CPF_NoDestructor | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierPublic
UPROPERTY(BlueprintReadOnly, Category = Blueprint)
int32 MyInt_ReadOnly = 123;
```
## 示例效果:
指定蓝图中只读:
![Untitled](Untitled.png)
## 原理:
有CPF_BlueprintVisible 就可以Get
加上CPF_BlueprintReadOnly 后就不能修改。
```cpp
EPropertyAccessResultFlags PropertyAccessUtil::CanGetPropertyValue(const FProperty* InProp)
{
if (!InProp->HasAnyPropertyFlags(CPF_Edit | CPF_BlueprintVisible | CPF_BlueprintAssignable))
{
return EPropertyAccessResultFlags::PermissionDenied | EPropertyAccessResultFlags::AccessProtected;
}
return EPropertyAccessResultFlags::Success;
}
FBlueprintEditorUtils::EPropertyWritableState FBlueprintEditorUtils::IsPropertyWritableInBlueprint(const UBlueprint* Blueprint, const FProperty* Property)
{
if (Property)
{
if (!Property->HasAnyPropertyFlags(CPF_BlueprintVisible))
{
return EPropertyWritableState::NotBlueprintVisible;
}
if (Property->HasAnyPropertyFlags(CPF_BlueprintReadOnly))
{
return EPropertyWritableState::BlueprintReadOnly;
}
if (Property->GetBoolMetaData(FBlueprintMetadata::MD_Private))
{
const UClass* OwningClass = Property->GetOwnerChecked<UClass>();
if (OwningClass->ClassGeneratedBy.Get() != Blueprint)
{
return EPropertyWritableState::Private;
}
}
}
return EPropertyWritableState::Writable;
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -0,0 +1,47 @@
# BlueprintReadWrite
- **功能描述:** 可从蓝图读取或写入此属性。
- **元数据类型:** bool
- **引擎模块:** Blueprint
- **作用机制:** 在PropertyFlags中加入[CPF_BlueprintVisible](../../../../Flags/EPropertyFlags/CPF_BlueprintVisible.md)
- **常用程度:** ★★★★★
可从蓝图读取或写入此属性。
此说明符与 BlueprintReadOnly 说明符不兼容。
## 示例代码:
```cpp
public:
//PropertyFlags: CPF_BlueprintVisible | CPF_ZeroConstructor | CPF_IsPlainOldData | CPF_NoDestructor | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierPublic
UPROPERTY(BlueprintReadWrite, Category = Blueprint)
int32 MyInt_ReadWrite = 123;
//PropertyFlags: CPF_BlueprintVisible | CPF_BlueprintReadOnly | CPF_ZeroConstructor | CPF_IsPlainOldData | CPF_NoDestructor | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierPublic
UPROPERTY(BlueprintReadOnly, Category = Blueprint)
int32 MyInt_ReadOnly = 123;
```
## 示例效果:
蓝图中可读写:
![Untitled](Untitled.png)
## 原理:
如果有CPF_Edit | CPF_BlueprintVisible | CPF_BlueprintAssignable之一则可以Get属性。
```cpp
EPropertyAccessResultFlags PropertyAccessUtil::CanGetPropertyValue(const FProperty* InProp)
{
if (!InProp->HasAnyPropertyFlags(CPF_Edit | CPF_BlueprintVisible | CPF_BlueprintAssignable))
{
return EPropertyAccessResultFlags::PermissionDenied | EPropertyAccessResultFlags::AccessProtected;
}
return EPropertyAccessResultFlags::Success;
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -0,0 +1,58 @@
# BlueprintSetter
- **功能描述:** 采用一个自定义的set函数来读取。
- **元数据类型:** string="abc"
- **引擎模块:** Blueprint
- **作用机制:** 在PropertyFlags中加入[CPF_BlueprintVisible](../../../Flags/EPropertyFlags/CPF_BlueprintVisible.md)在Meta中加入[BlueprintSetter](../../../Meta/Blueprint/BlueprintSetter.md)
- **常用程度:** ★★★
采用一个自定义的set函数来读取。
会默认设置BlueprintReadWrite。
## 测试代码:
```cpp
public:
//(BlueprintGetter = , Category = Blueprint, ModuleRelativePath = Property/MyProperty_Test.h)
UFUNCTION(BlueprintGetter, Category = Blueprint) //or BlueprintPure
int32 MyInt_Getter()const { return MyInt_WithGetter * 2; }
//(BlueprintSetter = , Category = Blueprint, ModuleRelativePath = Property/MyProperty_Test.h)
UFUNCTION(BlueprintSetter, Category = Blueprint) //or BlueprintCallable
void MyInt_Setter(int NewValue) { MyInt_WithSetter = NewValue / 4; }
private:
//(BlueprintGetter = MyInt_Getter, Category = Blueprint, ModuleRelativePath = Property/MyProperty_Test.h)
//PropertyFlags: CPF_BlueprintVisible | CPF_BlueprintReadOnly | CPF_ZeroConstructor | CPF_IsPlainOldData | CPF_NoDestructor | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierPrivate
UPROPERTY(BlueprintGetter = MyInt_Getter, Category = Blueprint)
int32 MyInt_WithGetter = 123;
//(BlueprintSetter = MyInt_Setter, Category = Blueprint, ModuleRelativePath = Property/MyProperty_Test.h)
//PropertyFlags: CPF_BlueprintVisible | CPF_ZeroConstructor | CPF_IsPlainOldData | CPF_NoDestructor | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierPrivate
UPROPERTY(BlueprintSetter = MyInt_Setter, Category = Blueprint)
int32 MyInt_WithSetter = 123;
```
## 蓝图表现:
![Untitled](BlueprintGetter/Untitled.png)
## 原理:
如果有MD_PropertySetFunction则用它来作为Set的调用。
```cpp
void UK2Node_VariableSet::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
{
// If property has a BlueprintSetter accessor, then replace the variable get node with a call function
if (VariableProperty)
{
// todo check with BP team if we need to test if the variable has native Setter
const FString& SetFunctionName = VariableProperty->GetMetaData(FBlueprintMetadata::MD_PropertySetFunction);
if (!SetFunctionName.IsEmpty())
{
}
}
}
```

View File

@@ -0,0 +1,109 @@
# Getter
- **功能描述:** 为属性增加一个C++的Get函数只在C++层面应用。
- **元数据类型:** string="abc"
- **引擎模块:** Blueprint
- **常用程度:** ★★★
为属性增加一个C++的Get函数只在C++层面应用。
- Getter上如不提供函数名那就用默认的GetXXX的名字。也可以提供另外一个函数名。
- 这些Getter函数是不加UFUNCTION的这点要和BlueprintGetter区分。
- 感觉更好的名字是NativeGetter。
- GetXXX的函数必须自己手写否则UHT会报错。
- 我们当然也可以自己写GetSet函数不需要写Getter和Setter的元数据。但写上Getter和Settter的好处是万一在项目里别的地方用到了反射来获取和设置值这个时候如果没有标上Getter和Setter就会直接从属性上获取值从而跳过我们想要的自定义Get/Set流程。
## 测试代码:
```cpp
public:
//GetterFunc: Has Native Getter
UPROPERTY(BlueprintReadWrite, Getter)
float MyFloat = 1.0f;
//GetterFunc: Has Native Getter
UPROPERTY(BlueprintReadWrite, Getter = GetMyCustomFloat)
float MyFloat2 = 1.0f;
public:
float GetMyFloat()const { return MyFloat + 100.f; }
float GetMyCustomFloat()const { return MyFloat2 + 100.f; }
void UMyProperty_Get::RunTest()
{
float Value1=MyFloat;
FProperty* prop=GetClass()->FindPropertyByName(TEXT("MyFloat"));
float Value2=0.f;
prop->GetValue_InContainer(this,&Value2);
}
```
## 蓝图表现:
在测试的时候可见如果是用GetValue_InContainer这种反射的方式来获取值就会自动的调用到GetMyFloat从而返回不同的值。
在蓝图里直接Get MyFloat 是依然是1.
![Untitled](Setter/Untitled.png)
## 原理:
UHT在分析Getter标记后会在gen.cpp里生成相应的函数包装。在构建FProperty的时候就会创建TPropertyWithSetterAndGetter之后在GetSingleValue_InContainer的时候就会调用到CallGetter。
```cpp
void UMyProperty_Get::GetMyFloat_WrapperImpl(const void* Object, void* OutValue)
{
const UMyProperty_Get* Obj = (const UMyProperty_Get*)Object;
float& Result = *(float*)OutValue;
Result = (float)Obj->GetMyFloat();
}
const UECodeGen_Private::FFloatPropertyParams Z_Construct_UClass_UMyProperty_Get_Statics::NewProp_MyFloat = { "MyFloat", nullptr, (EPropertyFlags)0x0010000000000004, UECodeGen_Private::EPropertyGenFlags::Float, RF_Public|RF_Transient|RF_MarkAsNative, nullptr, &UMyProperty_Get::GetMyFloat_WrapperImpl, 1, STRUCT_OFFSET(UMyProperty_Get, MyFloat), METADATA_PARAMS(UE_ARRAY_COUNT(NewProp_MyFloat_MetaData), NewProp_MyFloat_MetaData) };
template <typename PropertyType, typename PropertyParamsType>
PropertyType* NewFProperty(FFieldVariant Outer, const FPropertyParamsBase& PropBase)
{
const PropertyParamsType& Prop = (const PropertyParamsType&)PropBase;
PropertyType* NewProp = nullptr;
if (Prop.SetterFunc || Prop.GetterFunc)
{
NewProp = new TPropertyWithSetterAndGetter<PropertyType>(Outer, Prop);
}
else
{
NewProp = new PropertyType(Outer, Prop);
}
}
void FProperty::GetSingleValue_InContainer(const void* InContainer, void* OutValue, int32 ArrayIndex) const
{
checkf(ArrayIndex <= ArrayDim, TEXT("ArrayIndex (%d) must be less than the property %s array size (%d)"), ArrayIndex, *GetFullName(), ArrayDim);
if (!HasGetter())
{
// Fast path - direct memory access
CopySingleValue(OutValue, ContainerVoidPtrToValuePtrInternal((void*)InContainer, ArrayIndex));
}
else
{
if (ArrayDim == 1)
{
// Slower but no mallocs. We can copy the value directly to the resulting param
CallGetter(InContainer, OutValue);
}
else
{
// Malloc a temp value that is the size of the array. Getter will then copy the entire array to the temp value
uint8* ValueArray = (uint8*)AllocateAndInitializeValue();
GetValue_InContainer(InContainer, ValueArray);
// Copy the item we care about and free the temp array
CopySingleValue(OutValue, ValueArray + ArrayIndex * ElementSize);
DestroyAndFreeValue(ValueArray);
}
}
}
```

View File

@@ -0,0 +1,114 @@
# Setter
- **功能描述:** 为属性增加一个C++的Set函数只在C++层面应用。
- **元数据类型:** string="abc"
- **引擎模块:** Blueprint
- **关联项:** [Getter](../Getter.md)
- **常用程度:** ★★★
为属性增加一个C++的Set函数只在C++层面应用。
- Getter上如不提供函数名那就用默认的SetXXX的名字。也可以提供另外一个函数名。
- 这些Getter函数是不加UFUNCTION的这点要和BlueprintGetter区分。
- 感觉更好的名字是NativeSetter。
- SetXXX的函数必须自己手写否则UHT会报错。
- 我们当然也可以自己写GetSet函数不需要写Getter和Setter的元数据。但写上Getter和Settter的好处是万一在项目里别的地方用到了反射来获取和设置值这个时候如果没有标上Getter和Setter就会直接从属性上获取值从而跳过我们想要的自定义Get/Set流程。
## 测试代码:
```cpp
public:
UPROPERTY(BlueprintReadWrite, Setter)
float MyFloat = 1.0f;
UPROPERTY(BlueprintReadWrite, Setter = SetMyCustomFloat)
float MyFloat2 = 1.0f;
public:
void SetMyFloat(float val) { MyFloat = val + 100.f; }
void SetMyCustomFloat(float val) { MyFloat2 = val + 100.f; }
public:
UFUNCTION(BlueprintCallable)
void RunTest();
};
void UMyProperty_Set::RunTest()
{
float OldValue=MyFloat;
FProperty* prop=GetClass()->FindPropertyByName(TEXT("MyFloat"));
const float Value2=20.f;
prop->SetValue_InContainer(this,&Value2);
float NewValue=MyFloat;
}
```
## 蓝图表现:
在测试的时候可见如果是用SetValue_InContainer这种反射的方式来获取值就会自动的调用到SetMyFloat从而实际上设置到不同的值。
![Untitled](Untitled.png)
![Untitled](Untitled%201.png)
## 原理:
UHT在分析Setter标记后会在gen.cpp里生成相应的函数包装。在构建FProperty的时候就会创建TPropertyWithSetterAndGetter之后在GetSingleValue_InContainer的时候就会调用到CallGetter。
```cpp
void UMyProperty_Set::SetMyFloat_WrapperImpl(void* Object, const void* InValue)
{
UMyProperty_Set* Obj = (UMyProperty_Set*)Object;
float& Value = *(float*)InValue;
Obj->SetMyFloat(Value);
}
const UECodeGen_Private::FFloatPropertyParams Z_Construct_UClass_UMyProperty_Set_Statics::NewProp_MyFloat = { "MyFloat", nullptr, (EPropertyFlags)0x0010000000000004, UECodeGen_Private::EPropertyGenFlags::Float, RF_Public|RF_Transient|RF_MarkAsNative, &UMyProperty_Set::SetMyFloat_WrapperImpl, nullptr, 1, STRUCT_OFFSET(UMyProperty_Set, MyFloat), METADATA_PARAMS(UE_ARRAY_COUNT(NewProp_MyFloat_MetaData), NewProp_MyFloat_MetaData) };
template <typename PropertyType, typename PropertyParamsType>
PropertyType* NewFProperty(FFieldVariant Outer, const FPropertyParamsBase& PropBase)
{
const PropertyParamsType& Prop = (const PropertyParamsType&)PropBase;
PropertyType* NewProp = nullptr;
if (Prop.SetterFunc || Prop.GetterFunc)
{
NewProp = new TPropertyWithSetterAndGetter<PropertyType>(Outer, Prop);
}
else
{
NewProp = new PropertyType(Outer, Prop);
}
}
void FProperty::SetSingleValue_InContainer(void* OutContainer, const void* InValue, int32 ArrayIndex) const
{
checkf(ArrayIndex <= ArrayDim, TEXT("ArrayIndex (%d) must be less than the property %s array size (%d)"), ArrayIndex, *GetFullName(), ArrayDim);
if (!HasSetter())
{
// Fast path - direct memory access
CopySingleValue(ContainerVoidPtrToValuePtrInternal((void*)OutContainer, ArrayIndex), InValue);
}
else
{
if (ArrayDim == 1)
{
// Slower but no mallocs. We can copy the value directly to the resulting param
CallSetter(OutContainer, InValue);
}
else
{
// Malloc a temp value that is the size of the array. We will then copy the entire array to the temp value
uint8* ValueArray = (uint8*)AllocateAndInitializeValue();
GetValue_InContainer(OutContainer, ValueArray);
// Replace the value at the specified index in the temp array with the InValue
CopySingleValue(ValueArray + ArrayIndex * ElementSize, InValue);
// Now call a setter to replace the entire array and then destroy the temp value
CallSetter(OutContainer, ValueArray);
DestroyAndFreeValue(ValueArray);
}
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB