rui_orig/filter.go

358 lines
11 KiB
Go
Raw Normal View History

2021-09-07 17:36:50 +03:00
package rui
import (
"fmt"
"strings"
)
// Constants for [FilterProperty] specific properties and events
2021-09-07 17:36:50 +03:00
const (
// Blur is the constant for "blur" property tag.
//
// Used by FilterProperty.
2024-11-13 12:56:39 +03:00
// Applies a Gaussian blur. The value of radius defines the value of the standard deviation to the Gaussian function, or
// how many pixels on the screen blend into each other, so a larger value will create more blur. The lacuna value for
// interpolation is 0. The parameter is specified as a length in pixels.
//
2024-12-05 20:15:39 +03:00
// Supported types: float, int, string.
//
2024-12-05 20:15:39 +03:00
// Internal type is float, other types converted to it during assignment.
2024-11-13 12:56:39 +03:00
Blur PropertyName = "blur"
2021-09-07 17:36:50 +03:00
// Brightness is the constant for "brightness" property tag.
//
// Used by FilterProperty.
2024-11-13 12:56:39 +03:00
// Applies a linear multiplier to input image, making it appear more or less bright. A value of 0% will create an image
// that is completely black. A value of 100% leaves the input unchanged. Other values are linear multipliers on the
// effect. Values of an amount over 100% are allowed, providing brighter results.
//
2024-12-05 20:15:39 +03:00
// Supported types: float, int, string.
//
2024-12-05 20:15:39 +03:00
// Internal type is float, other types converted to it during assignment.
2024-11-13 12:56:39 +03:00
Brightness PropertyName = "brightness"
2021-09-07 17:36:50 +03:00
// Contrast is the constant for "contrast" property tag.
//
// Used by FilterProperty.
2024-11-13 12:56:39 +03:00
// Adjusts the contrast of the input. A value of 0% will create an image that is completely black. A value of 100% leaves
// the input unchanged. Values of amount over 100% are allowed, providing results with less contrast.
//
2024-12-05 20:15:39 +03:00
// Supported types: float, int, string.
//
2024-12-05 20:15:39 +03:00
// Internal type is float, other types converted to it during assignment.
2024-11-13 12:56:39 +03:00
Contrast PropertyName = "contrast"
2021-09-07 17:36:50 +03:00
// DropShadow is the constant for "drop-shadow" property tag.
//
// Used by FilterProperty.
2024-11-13 12:56:39 +03:00
// Applies a drop shadow effect to the input image. A drop shadow is effectively a blurred, offset version of the input
// image's alpha mask drawn in a particular color, composited below the image. Shadow parameters are set using the
2024-12-05 20:15:39 +03:00
// ShadowProperty interface.
//
2024-12-05 20:15:39 +03:00
// Supported types: []ShadowProperty, ShadowProperty, string.
//
2024-12-05 20:15:39 +03:00
// Internal type is []ShadowProperty, other types converted to it during assignment.
// See ShadowProperty description for more details.
//
// Conversion rules:
2024-12-05 20:15:39 +03:00
// - []ShadowProperty - stored as is, no conversion performed.
// - ShadowProperty - converted to []ShadowProperty.
// - string - string representation of ShadowProperty. Example: "_{blur = 1em, color = black, spread-radius = 0.5em}".
2024-11-13 12:56:39 +03:00
DropShadow PropertyName = "drop-shadow"
2021-09-07 17:36:50 +03:00
// Grayscale is the constant for "grayscale" property tag.
//
// Used by FilterProperty.
2024-11-13 12:56:39 +03:00
// Converts the input image to grayscale. The value of amount defines the proportion of the conversion. A value of 100%
// is completely grayscale. A value of 0% leaves the input unchanged. Values between 0% and 100% are linear multipliers on
// the effect.
//
2024-12-05 20:15:39 +03:00
// Supported types: float, int, string.
//
2024-12-05 20:15:39 +03:00
// Internal type is float, other types converted to it during assignment.
2024-11-13 12:56:39 +03:00
Grayscale PropertyName = "grayscale"
2021-09-07 17:36:50 +03:00
// HueRotate is the constant for "hue-rotate" property tag.
//
// Used by FilterProperty.
2024-11-13 12:56:39 +03:00
// Applies a hue rotation on the input image. The value of angle defines the number of degrees around the color circle
// the input samples will be adjusted. A value of 0deg leaves the input unchanged. If the angle parameter is missing, a
// value of 0deg is used. Though there is no maximum value, the effect of values above 360deg wraps around.
//
2024-12-05 20:15:39 +03:00
// Supported types: AngleUnit, string, float, int.
//
2024-12-05 20:15:39 +03:00
// Internal type is AngleUnit, other types will be converted to it during assignment.
// See AngleUnit description for more details.
//
// Conversion rules:
2024-12-05 20:15:39 +03:00
// - AngleUnit - stored as is, no conversion performed.
// - string - must contain string representation of AngleUnit. If numeric value will be provided without any suffix then AngleUnit with value and Radian value type will be created.
// - float - a new AngleUnit value will be created with Radian as a type.
// - int - a new AngleUnit value will be created with Radian as a type.
2024-11-13 12:56:39 +03:00
HueRotate PropertyName = "hue-rotate"
2021-09-07 17:36:50 +03:00
// Invert is the constant for "invert" property tag.
//
// Used by FilterProperty.
2024-11-13 12:56:39 +03:00
// Inverts the samples in the input image. The value of amount defines the proportion of the conversion. A value of 100%
// is completely inverted. A value of 0% leaves the input unchanged. Values between 0% and 100% are linear multipliers on
// the effect.
//
2024-12-05 20:15:39 +03:00
// Supported types: float64, int, string.
//
2024-12-05 20:15:39 +03:00
// Internal type is float, other types converted to it during assignment.
2024-11-13 12:56:39 +03:00
Invert PropertyName = "invert"
2021-09-07 17:36:50 +03:00
// Saturate is the constant for "saturate" property tag.
//
// Used by FilterProperty.
2024-11-13 12:56:39 +03:00
// Saturates the input image. The value of amount defines the proportion of the conversion. A value of 0% is completely
// un-saturated. A value of 100% leaves the input unchanged. Other values are linear multipliers on the effect. Values of
// amount over 100% are allowed, providing super-saturated results.
//
2024-12-05 20:15:39 +03:00
// Supported types: float, int, string.
//
2024-12-05 20:15:39 +03:00
// Internal type is float, other types converted to it during assignment.
2024-11-13 12:56:39 +03:00
Saturate PropertyName = "saturate"
2021-09-07 17:36:50 +03:00
// Sepia is the constant for "sepia" property tag.
//
// Used by FilterProperty.
2024-11-13 12:56:39 +03:00
// Converts the input image to sepia. The value of amount defines the proportion of the conversion. A value of 100% is
// completely sepia. A value of 0% leaves the input unchanged. Values between 0% and 100% are linear multipliers on the
// effect.
//
2024-12-05 20:15:39 +03:00
// Supported types: float, int, string.
//
2024-12-05 20:15:39 +03:00
// Internal type is float, other types converted to it during assignment.
2024-11-13 12:56:39 +03:00
Sepia PropertyName = "sepia"
2021-09-07 17:36:50 +03:00
)
// FilterProperty defines an applied to a View a graphical effects like blur or color shift.
2021-09-07 17:36:50 +03:00
// Allowable properties are Blur, Brightness, Contrast, DropShadow, Grayscale, HueRotate, Invert, Opacity, Saturate, and Sepia
type FilterProperty interface {
2021-09-07 17:36:50 +03:00
Properties
fmt.Stringer
2022-05-22 12:54:02 +03:00
stringWriter
2021-09-07 17:36:50 +03:00
cssStyle(session Session) string
}
type filterData struct {
2024-11-13 12:56:39 +03:00
dataProperty
2021-09-07 17:36:50 +03:00
}
// NewFilterProperty creates the new FilterProperty
func NewFilterProperty(params Params) FilterProperty {
2024-11-13 12:56:39 +03:00
if len(params) > 0 {
filter := new(filterData)
2022-05-05 17:18:08 +03:00
filter.init()
for tag, value := range params {
2024-11-13 12:56:39 +03:00
if !filter.Set(tag, value) {
return nil
}
2022-05-05 17:18:08 +03:00
}
2024-11-13 12:56:39 +03:00
return filter
2021-09-07 17:36:50 +03:00
}
return nil
}
func newFilterProperty(obj DataObject) FilterProperty {
filter := new(filterData)
2021-09-07 17:36:50 +03:00
filter.init()
for i := 0; i < obj.PropertyCount(); i++ {
if node := obj.Property(i); node != nil {
tag := node.Tag()
switch node.Type() {
case TextNode:
2024-11-13 12:56:39 +03:00
filter.Set(PropertyName(tag), node.Text())
2021-09-07 17:36:50 +03:00
case ObjectNode:
2024-11-13 12:56:39 +03:00
if tag == string(HueRotate) {
2021-09-07 17:36:50 +03:00
// TODO
} else {
ErrorLog(`Invalid value of "` + tag + `"`)
}
default:
ErrorLog(`Invalid value of "` + tag + `"`)
}
}
}
if len(filter.properties) > 0 {
return filter
}
ErrorLog("Empty view filter")
return nil
}
func (filter *filterData) init() {
2024-11-13 12:56:39 +03:00
filter.dataProperty.init()
filter.set = filterDataSet
2024-11-13 12:56:39 +03:00
filter.supportedProperties = []PropertyName{Blur, Brightness, Contrast, Saturate, Grayscale, Invert, Opacity, Sepia, HueRotate, DropShadow}
}
2021-09-07 17:36:50 +03:00
func filterDataSet(properties Properties, tag PropertyName, value any) []PropertyName {
2024-11-13 12:56:39 +03:00
switch tag {
2021-09-07 17:36:50 +03:00
case Blur, Brightness, Contrast, Saturate:
2024-11-13 12:56:39 +03:00
return setFloatProperty(properties, tag, value, 0, 10000)
2021-09-07 17:36:50 +03:00
case Grayscale, Invert, Opacity, Sepia:
2024-11-13 12:56:39 +03:00
return setFloatProperty(properties, tag, value, 0, 100)
2021-09-07 17:36:50 +03:00
case HueRotate:
2024-11-13 12:56:39 +03:00
return setAngleProperty(properties, tag, value)
2021-09-07 17:36:50 +03:00
case DropShadow:
2024-11-13 12:56:39 +03:00
if setShadowProperty(properties, tag, value) {
return []PropertyName{tag}
}
2021-09-07 17:36:50 +03:00
}
ErrorLogF(`"%s" property is not supported by the view filter`, tag)
2024-11-13 12:56:39 +03:00
return nil
2021-09-07 17:36:50 +03:00
}
func (filter *filterData) String() string {
2022-05-22 12:54:02 +03:00
return runStringWriter(filter)
2021-09-07 17:36:50 +03:00
}
func (filter *filterData) writeString(buffer *strings.Builder, indent string) {
2022-05-22 12:54:02 +03:00
buffer.WriteString("filter { ")
comma := false
tags := filter.AllTags()
for _, tag := range tags {
if value, ok := filter.properties[tag]; ok {
if comma {
buffer.WriteString(", ")
}
2024-11-13 12:56:39 +03:00
buffer.WriteString(string(tag))
2022-05-22 12:54:02 +03:00
buffer.WriteString(" = ")
writePropertyValue(buffer, tag, value, indent)
comma = true
}
2021-09-07 17:36:50 +03:00
}
2022-05-22 12:54:02 +03:00
buffer.WriteString(" }")
2021-09-07 17:36:50 +03:00
}
func (filter *filterData) cssStyle(session Session) string {
2021-09-07 17:36:50 +03:00
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
2022-08-18 18:18:36 +03:00
if value, ok := floatTextProperty(filter, Blur, session, 0); ok {
2024-11-13 12:56:39 +03:00
buffer.WriteString(string(Blur))
2021-09-07 17:36:50 +03:00
buffer.WriteRune('(')
2022-08-18 18:18:36 +03:00
buffer.WriteString(value)
buffer.WriteString("px)")
2021-09-07 17:36:50 +03:00
}
2024-11-13 12:56:39 +03:00
for _, tag := range []PropertyName{Brightness, Contrast, Saturate, Grayscale, Invert, Opacity, Sepia} {
2022-08-18 18:18:36 +03:00
if value, ok := floatTextProperty(filter, tag, session, 0); ok {
2021-09-07 17:36:50 +03:00
if buffer.Len() > 0 {
buffer.WriteRune(' ')
}
2024-11-13 12:56:39 +03:00
buffer.WriteString(string(tag))
2022-08-18 18:18:36 +03:00
buffer.WriteRune('(')
buffer.WriteString(value)
buffer.WriteString("%)")
2021-09-07 17:36:50 +03:00
}
}
if value, ok := angleProperty(filter, HueRotate, session); ok {
if buffer.Len() > 0 {
buffer.WriteRune(' ')
}
2024-11-13 12:56:39 +03:00
buffer.WriteString(string(HueRotate))
2021-09-07 17:36:50 +03:00
buffer.WriteRune('(')
buffer.WriteString(value.cssString())
buffer.WriteRune(')')
}
var lead string
if buffer.Len() > 0 {
lead = " drop-shadow("
} else {
lead = "drop-shadow("
}
for _, shadow := range getShadows(filter, DropShadow) {
if shadow.cssTextStyle(buffer, session, lead) {
buffer.WriteRune(')')
lead = " drop-shadow("
}
}
return buffer.String()
}
2024-11-13 12:56:39 +03:00
func setFilterProperty(properties Properties, tag PropertyName, value any) []PropertyName {
2021-09-07 17:36:50 +03:00
switch value := value.(type) {
case FilterProperty:
2024-11-13 12:56:39 +03:00
properties.setRaw(tag, value)
return []PropertyName{tag}
2021-09-07 17:36:50 +03:00
case string:
if obj := NewDataObject(value); obj == nil {
if filter := newFilterProperty(obj); filter != nil {
2024-11-13 12:56:39 +03:00
properties.setRaw(tag, filter)
return []PropertyName{tag}
2021-09-07 17:36:50 +03:00
}
}
2024-11-13 12:56:39 +03:00
2021-09-07 17:36:50 +03:00
case DataObject:
if filter := newFilterProperty(value); filter != nil {
2024-11-13 12:56:39 +03:00
properties.setRaw(tag, filter)
return []PropertyName{tag}
2021-09-07 17:36:50 +03:00
}
case DataValue:
if value.IsObject() {
if filter := newFilterProperty(value.Object()); filter != nil {
2024-11-13 12:56:39 +03:00
properties.setRaw(tag, filter)
return []PropertyName{tag}
2021-09-07 17:36:50 +03:00
}
}
}
notCompatibleType(tag, value)
2024-11-13 12:56:39 +03:00
return nil
2021-09-07 17:36:50 +03:00
}
// GetFilter returns a View graphical effects like blur or color shift.
// If the second argument (subviewID) is not specified or it is "" then a top position of the first argument (view) is returned
func GetFilter(view View, subviewID ...string) FilterProperty {
2024-11-24 16:43:31 +03:00
if view = getSubview(view, subviewID); view != nil {
2021-09-07 17:36:50 +03:00
if value := view.getRaw(Filter); value != nil {
if filter, ok := value.(FilterProperty); ok {
2021-09-07 17:36:50 +03:00
return filter
}
}
if value := valueFromStyle(view, Filter); value != nil {
if filter, ok := value.(FilterProperty); ok {
return filter
}
}
2021-09-07 17:36:50 +03:00
}
return nil
}
// GetBackdropFilter returns the area behind a View graphical effects like blur or color shift.
// If the second argument (subviewID) is not specified or it is "" then a top position of the first argument (view) is returned
func GetBackdropFilter(view View, subviewID ...string) FilterProperty {
2024-11-24 16:43:31 +03:00
if view = getSubview(view, subviewID); view != nil {
if value := view.getRaw(BackdropFilter); value != nil {
if filter, ok := value.(FilterProperty); ok {
return filter
}
}
if value := valueFromStyle(view, BackdropFilter); value != nil {
if filter, ok := value.(FilterProperty); ok {
return filter
}
}
}
return nil
}