blur uses radius instead of intensity
This commit is contained in:
119
modules/Blur.lua
119
modules/Blur.lua
@@ -12,7 +12,7 @@ local Cache = {
|
||||
MAX_CANVAS_SIZE = 20,
|
||||
MAX_QUAD_SIZE = 20,
|
||||
MAX_BLURRED_CANVAS_CACHE = 50, -- Maximum cached blurred canvases
|
||||
INTENSITY_THRESHOLD = 5, -- Skip blur below this intensity
|
||||
RADIUS_THRESHOLD = 0.5, -- Skip blur below this radius
|
||||
LARGE_BLUR_THRESHOLD = 250 * 250, -- Warn if blur area exceeds this (250x250px)
|
||||
}
|
||||
|
||||
@@ -133,12 +133,12 @@ end
|
||||
---@param y number Y position
|
||||
---@param width number Width
|
||||
---@param height number Height
|
||||
---@param intensity number Blur intensity
|
||||
---@param radius number Blur radius
|
||||
---@param quality number Blur quality
|
||||
---@param isBackdrop boolean Whether this is backdrop blur
|
||||
---@return string key Cache key
|
||||
function Cache.generateBlurCacheKey(elementId, x, y, width, height, intensity, quality, isBackdrop)
|
||||
return string.format("%s:%d:%d:%d:%d:%d:%d:%s", elementId, x, y, width, height, intensity, quality, tostring(isBackdrop))
|
||||
function Cache.generateBlurCacheKey(elementId, x, y, width, height, radius, quality, isBackdrop)
|
||||
return string.format("%s:%d:%d:%d:%d:%.1f:%d:%s", elementId, x, y, width, height, radius, quality, tostring(isBackdrop))
|
||||
end
|
||||
|
||||
--- Get cached blurred canvas
|
||||
@@ -381,13 +381,13 @@ function Blur.new(props)
|
||||
end
|
||||
|
||||
--- Apply blur to a region of the screen
|
||||
---@param intensity number Blur intensity (0-100)
|
||||
---@param radius number Blur radius in pixels
|
||||
---@param x number X position
|
||||
---@param y number Y position
|
||||
---@param width number Width of region
|
||||
---@param height number Height of region
|
||||
---@param drawFunc function Function to draw content to be blurred
|
||||
function Blur:applyToRegion(intensity, x, y, width, height, drawFunc)
|
||||
function Blur:applyToRegion(radius, x, y, width, height, drawFunc)
|
||||
if type(drawFunc) ~= "function" then
|
||||
if Blur._ErrorHandler then
|
||||
Blur._ErrorHandler:warn("Blur", "BLUR_001")
|
||||
@@ -395,13 +395,13 @@ function Blur:applyToRegion(intensity, x, y, width, height, drawFunc)
|
||||
return
|
||||
end
|
||||
|
||||
if intensity <= 0 or width <= 0 or height <= 0 then
|
||||
if radius <= 0 or width <= 0 or height <= 0 then
|
||||
drawFunc()
|
||||
return
|
||||
end
|
||||
|
||||
-- Early exit for very low intensity (optimization)
|
||||
if intensity < Cache.INTENSITY_THRESHOLD then
|
||||
-- Early exit for very low radius (optimization)
|
||||
if radius < Cache.RADIUS_THRESHOLD then
|
||||
drawFunc()
|
||||
return
|
||||
end
|
||||
@@ -409,11 +409,9 @@ function Blur:applyToRegion(intensity, x, y, width, height, drawFunc)
|
||||
-- Check for large blur area in immediate mode
|
||||
checkLargeBlurWarning(nil, width, height, "content")
|
||||
|
||||
intensity = math.max(0, math.min(100, intensity))
|
||||
|
||||
-- Intensity 0-100 maps to 0-5 passes
|
||||
local passes = math.ceil(intensity / 20)
|
||||
passes = math.max(1, math.min(5, passes))
|
||||
-- Calculate offset multiplier based on radius and quality
|
||||
-- Higher quality = more samples = smaller steps for same radius
|
||||
local offsetMultiplier = radius / self.quality
|
||||
|
||||
local canvas1 = Cache.getCanvas(width, height)
|
||||
local canvas2 = Cache.getCanvas(width, height)
|
||||
@@ -435,17 +433,16 @@ function Blur:applyToRegion(intensity, x, y, width, height, drawFunc)
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
love.graphics.setBlendMode("alpha", "premultiplied")
|
||||
|
||||
for i = 1, passes do
|
||||
love.graphics.setCanvas(canvas2)
|
||||
love.graphics.clear()
|
||||
self.shader:send("direction", { 1 / width, 0 })
|
||||
love.graphics.draw(canvas1, 0, 0)
|
||||
-- Single pass with radius-controlled offset
|
||||
love.graphics.setCanvas(canvas2)
|
||||
love.graphics.clear()
|
||||
self.shader:send("direction", { offsetMultiplier / width, 0 })
|
||||
love.graphics.draw(canvas1, 0, 0)
|
||||
|
||||
love.graphics.setCanvas(canvas1)
|
||||
love.graphics.clear()
|
||||
self.shader:send("direction", { 0, 1 / height })
|
||||
love.graphics.draw(canvas2, 0, 0)
|
||||
end
|
||||
love.graphics.setCanvas(canvas1)
|
||||
love.graphics.clear()
|
||||
self.shader:send("direction", { 0, offsetMultiplier / height })
|
||||
love.graphics.draw(canvas2, 0, 0)
|
||||
|
||||
love.graphics.setCanvas(prevCanvas)
|
||||
love.graphics.setShader()
|
||||
@@ -460,13 +457,13 @@ function Blur:applyToRegion(intensity, x, y, width, height, drawFunc)
|
||||
end
|
||||
|
||||
--- Apply backdrop blur effect (blur content behind a region)
|
||||
---@param intensity number Blur intensity (0-100)
|
||||
---@param radius number Blur radius in pixels
|
||||
---@param x number X position
|
||||
---@param y number Y position
|
||||
---@param width number Width of region
|
||||
---@param height number Height of region
|
||||
---@param backdropCanvas love.Canvas Canvas containing the backdrop content
|
||||
function Blur:applyBackdrop(intensity, x, y, width, height, backdropCanvas)
|
||||
function Blur:applyBackdrop(radius, x, y, width, height, backdropCanvas)
|
||||
if not backdropCanvas then
|
||||
if Blur._ErrorHandler then
|
||||
Blur._ErrorHandler:warn("Blur", "BLUR_002")
|
||||
@@ -474,19 +471,17 @@ function Blur:applyBackdrop(intensity, x, y, width, height, backdropCanvas)
|
||||
return
|
||||
end
|
||||
|
||||
if intensity <= 0 or width <= 0 or height <= 0 then
|
||||
if radius <= 0 or width <= 0 or height <= 0 then
|
||||
return
|
||||
end
|
||||
|
||||
-- Early exit for very low intensity (optimization)
|
||||
if intensity < Cache.INTENSITY_THRESHOLD then
|
||||
-- Early exit for very low radius (optimization)
|
||||
if radius < Cache.RADIUS_THRESHOLD then
|
||||
return
|
||||
end
|
||||
|
||||
intensity = math.max(0, math.min(100, intensity))
|
||||
|
||||
local passes = math.ceil(intensity / 20)
|
||||
passes = math.max(1, math.min(5, passes))
|
||||
-- Calculate offset multiplier based on radius and quality
|
||||
local offsetMultiplier = radius / self.quality
|
||||
|
||||
local canvas1 = Cache.getCanvas(width, height)
|
||||
local canvas2 = Cache.getCanvas(width, height)
|
||||
@@ -507,17 +502,16 @@ function Blur:applyBackdrop(intensity, x, y, width, height, backdropCanvas)
|
||||
|
||||
love.graphics.setShader(self.shader)
|
||||
|
||||
for i = 1, passes do
|
||||
love.graphics.setCanvas(canvas2)
|
||||
love.graphics.clear()
|
||||
self.shader:send("direction", { 1 / width, 0 })
|
||||
love.graphics.draw(canvas1, 0, 0)
|
||||
-- Single pass with radius-controlled offset
|
||||
love.graphics.setCanvas(canvas2)
|
||||
love.graphics.clear()
|
||||
self.shader:send("direction", { offsetMultiplier / width, 0 })
|
||||
love.graphics.draw(canvas1, 0, 0)
|
||||
|
||||
love.graphics.setCanvas(canvas1)
|
||||
love.graphics.clear()
|
||||
self.shader:send("direction", { 0, 1 / height })
|
||||
love.graphics.draw(canvas2, 0, 0)
|
||||
end
|
||||
love.graphics.setCanvas(canvas1)
|
||||
love.graphics.clear()
|
||||
self.shader:send("direction", { 0, offsetMultiplier / height })
|
||||
love.graphics.draw(canvas2, 0, 0)
|
||||
|
||||
love.graphics.setCanvas(prevCanvas)
|
||||
love.graphics.setShader()
|
||||
@@ -550,21 +544,21 @@ function Blur.clearCache()
|
||||
end
|
||||
|
||||
--- Apply backdrop blur with caching support
|
||||
---@param intensity number Blur intensity (0-100)
|
||||
---@param radius number Blur radius in pixels
|
||||
---@param x number X position
|
||||
---@param y number Y position
|
||||
---@param width number Width of region
|
||||
---@param height number Height of region
|
||||
---@param backdropCanvas love.Canvas Canvas containing the backdrop content
|
||||
---@param elementId string|nil Element ID for caching (nil disables caching)
|
||||
function Blur:applyBackdropCached(intensity, x, y, width, height, backdropCanvas, elementId)
|
||||
function Blur:applyBackdropCached(radius, x, y, width, height, backdropCanvas, elementId)
|
||||
-- If caching is disabled or no element ID, fall back to regular apply
|
||||
if not Blur._immediateModeOptimizations or not elementId then
|
||||
return self:applyBackdrop(intensity, x, y, width, height, backdropCanvas)
|
||||
return self:applyBackdrop(radius, x, y, width, height, backdropCanvas)
|
||||
end
|
||||
|
||||
-- Generate cache key
|
||||
local cacheKey = Cache.generateBlurCacheKey(elementId, x, y, width, height, intensity, self.quality, true)
|
||||
local cacheKey = Cache.generateBlurCacheKey(elementId, x, y, width, height, radius, self.quality, true)
|
||||
|
||||
-- Check cache
|
||||
local cachedCanvas = Cache.getBlurredCanvas(cacheKey)
|
||||
@@ -593,22 +587,20 @@ function Blur:applyBackdropCached(intensity, x, y, width, height, backdropCanvas
|
||||
return
|
||||
end
|
||||
|
||||
if intensity <= 0 or width <= 0 or height <= 0 then
|
||||
if radius <= 0 or width <= 0 or height <= 0 then
|
||||
return
|
||||
end
|
||||
|
||||
-- Early exit for very low intensity (optimization)
|
||||
if intensity < Cache.INTENSITY_THRESHOLD then
|
||||
-- Early exit for very low radius (optimization)
|
||||
if radius < Cache.RADIUS_THRESHOLD then
|
||||
return
|
||||
end
|
||||
|
||||
-- Check for large blur area in immediate mode
|
||||
checkLargeBlurWarning(elementId, width, height, "backdrop")
|
||||
|
||||
intensity = math.max(0, math.min(100, intensity))
|
||||
|
||||
local passes = math.ceil(intensity / 20)
|
||||
passes = math.max(1, math.min(5, passes))
|
||||
-- Calculate offset multiplier based on radius and quality
|
||||
local offsetMultiplier = radius / self.quality
|
||||
|
||||
local canvas1 = Cache.getCanvas(width, height)
|
||||
local canvas2 = Cache.getCanvas(width, height)
|
||||
@@ -629,17 +621,16 @@ function Blur:applyBackdropCached(intensity, x, y, width, height, backdropCanvas
|
||||
|
||||
love.graphics.setShader(self.shader)
|
||||
|
||||
for i = 1, passes do
|
||||
love.graphics.setCanvas(canvas2)
|
||||
love.graphics.clear()
|
||||
self.shader:send("direction", { 1 / width, 0 })
|
||||
love.graphics.draw(canvas1, 0, 0)
|
||||
-- Single pass with radius-controlled offset
|
||||
love.graphics.setCanvas(canvas2)
|
||||
love.graphics.clear()
|
||||
self.shader:send("direction", { offsetMultiplier / width, 0 })
|
||||
love.graphics.draw(canvas1, 0, 0)
|
||||
|
||||
love.graphics.setCanvas(canvas1)
|
||||
love.graphics.clear()
|
||||
self.shader:send("direction", { 0, 1 / height })
|
||||
love.graphics.draw(canvas2, 0, 0)
|
||||
end
|
||||
love.graphics.setCanvas(canvas1)
|
||||
love.graphics.clear()
|
||||
self.shader:send("direction", { 0, offsetMultiplier / height })
|
||||
love.graphics.draw(canvas2, 0, 0)
|
||||
|
||||
-- Cache the result
|
||||
local cachedResult = love.graphics.newCanvas(width, height)
|
||||
|
||||
@@ -70,8 +70,8 @@
|
||||
---@field contentAutoSizingMultiplier {width:number?, height:number?}? -- Multiplier for auto-sized content dimensions
|
||||
---@field scaleCorners number? -- Scale multiplier for 9-patch corners/edges. E.g., 2 = 2x size (overrides theme setting)
|
||||
---@field scalingAlgorithm "nearest"|"bilinear"? -- Scaling algorithm for 9-patch corners: "nearest" (sharp/pixelated) or "bilinear" (smooth) (overrides theme setting)
|
||||
---@field contentBlur {intensity:number, quality:number}? -- Blur the element's content including children (intensity: 0-100, quality: 1-10)
|
||||
---@field backdropBlur {intensity:number, quality:number}? -- Blur content behind the element (intensity: 0-100, quality: 1-10)
|
||||
---@field contentBlur {radius:number, quality:number?}? -- Blur the element's content including children (radius: pixels, quality: 1-10, default: 5)
|
||||
---@field backdropBlur {radius:number, quality:number?}? -- Blur content behind the element (radius: pixels, quality: 1-10, default: 5)
|
||||
---@field _blurInstance table? -- Internal: cached blur effect instance
|
||||
---@field editable boolean -- Whether the element is editable (default: false)
|
||||
---@field multiline boolean -- Whether the element supports multiple lines (default: false)
|
||||
@@ -2123,10 +2123,10 @@ function Element:draw(backdropCanvas)
|
||||
end
|
||||
|
||||
-- Apply content blur if configured
|
||||
if self.contentBlur and self.contentBlur.intensity > 0 and #sortedChildren > 0 then
|
||||
if self.contentBlur and self.contentBlur.radius > 0 and #sortedChildren > 0 then
|
||||
local blurInstance = self:getBlurInstance()
|
||||
if blurInstance then
|
||||
Element._Blur.applyToRegion(blurInstance, self.contentBlur.intensity, self.x, self.y, borderBoxWidth, borderBoxHeight, drawChildren)
|
||||
Element._Blur.applyToRegion(blurInstance, self.contentBlur.radius, self.x, self.y, borderBoxWidth, borderBoxHeight, drawChildren)
|
||||
else
|
||||
drawChildren()
|
||||
end
|
||||
@@ -3190,13 +3190,13 @@ function Element:saveState()
|
||||
}
|
||||
|
||||
if self.backdropBlur then
|
||||
state.blur._backdropBlurIntensity = self.backdropBlur.intensity
|
||||
state.blur._backdropBlurQuality = self.backdropBlur.quality
|
||||
state.blur._backdropBlurRadius = self.backdropBlur.radius
|
||||
state.blur._backdropBlurQuality = self.backdropBlur.quality or 5
|
||||
end
|
||||
|
||||
if self.contentBlur then
|
||||
state.blur._contentBlurIntensity = self.contentBlur.intensity
|
||||
state.blur._contentBlurQuality = self.contentBlur.quality
|
||||
state.blur._contentBlurRadius = self.contentBlur.radius
|
||||
state.blur._contentBlurQuality = self.contentBlur.quality or 5
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3252,9 +3252,9 @@ function Element:shouldInvalidateBlurCache(oldState, newState)
|
||||
or old._blurY ~= new._blurY
|
||||
or old._blurWidth ~= new._blurWidth
|
||||
or old._blurHeight ~= new._blurHeight
|
||||
or old._backdropBlurIntensity ~= new._backdropBlurIntensity
|
||||
or old._backdropBlurRadius ~= new._backdropBlurRadius
|
||||
or old._backdropBlurQuality ~= new._backdropBlurQuality
|
||||
or old._contentBlurIntensity ~= new._contentBlurIntensity
|
||||
or old._contentBlurRadius ~= new._contentBlurRadius
|
||||
or old._contentBlurQuality ~= new._contentBlurQuality
|
||||
end
|
||||
|
||||
|
||||
@@ -417,12 +417,12 @@ function Renderer:draw(element, backdropCanvas)
|
||||
end
|
||||
|
||||
-- LAYER 0.5: Draw backdrop blur if configured (before background)
|
||||
if self.backdropBlur and self.backdropBlur.intensity > 0 and backdropCanvas then
|
||||
if self.backdropBlur and self.backdropBlur.radius > 0 and backdropCanvas then
|
||||
local blurInstance = self:getBlurInstance()
|
||||
if blurInstance then
|
||||
-- Use cached blur in immediate mode if element has an ID
|
||||
local elementId = element.id and element.id ~= "" and element.id or nil
|
||||
blurInstance:applyBackdropCached(self.backdropBlur.intensity, element.x, element.y, borderBoxWidth, borderBoxHeight, backdropCanvas, elementId)
|
||||
blurInstance:applyBackdropCached(self.backdropBlur.radius, element.x, element.y, borderBoxWidth, borderBoxHeight, backdropCanvas, elementId)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -95,8 +95,8 @@ local AnimationProps = {}
|
||||
---@field contentAutoSizingMultiplier {width:number?, height:number?}? -- Multiplier for auto-sized content dimensions (default: sourced from theme or {1, 1})
|
||||
---@field scaleCorners number? -- Scale multiplier for 9-patch corners/edges. E.g., 2 = 2x size (overrides theme setting)
|
||||
---@field scalingAlgorithm "nearest"|"bilinear"? -- Scaling algorithm for 9-patch corners: "nearest" (sharp/pixelated) or "bilinear" (smooth) (overrides theme setting)
|
||||
---@field contentBlur {intensity:number, quality:number}? -- Blur the element's content including children (intensity: 0-100, quality: 1-10, default: nil)
|
||||
---@field backdropBlur {intensity:number, quality:number}? -- Blur content behind the element (intensity: 0-100, quality: 1-10, default: nil)
|
||||
---@field contentBlur {radius:number, quality:number?}? -- Blur the element's content including children (radius: pixels, quality: 1-10, default(quality): 5)
|
||||
---@field backdropBlur {radius:number, quality:number?}? -- Blur content behind the element (radius: pixels, quality: 1-10, default(quality): 5)
|
||||
---@field editable boolean? -- Whether the element is editable (default: false)
|
||||
---@field multiline boolean? -- Whether the element supports multiple lines (default: false)
|
||||
---@field textWrap boolean|"word"|"char"? -- Text wrapping mode (default: false for single-line, "word" for multi-line)
|
||||
@@ -204,7 +204,7 @@ local FlexLoveConfig = {}
|
||||
---@field _blurY number
|
||||
---@field _blurWidth number
|
||||
---@field _blurHeight number
|
||||
---@field _backdropBlurIntensity number?
|
||||
---@field _backdropBlurQuality string?
|
||||
---@field _contentBlurIntensity number?
|
||||
---@field _contentBlurQuality string?
|
||||
---@field _backdropBlurRadius number?
|
||||
---@field _backdropBlurQuality number?
|
||||
---@field _contentBlurRadius number?
|
||||
---@field _contentBlurQuality number?
|
||||
|
||||
Reference in New Issue
Block a user