gears.surface: Add crop (#3882)
* added surface crop function
* added busted tests
* added devShell Flake
* Revert "added devShell Flake"
This reverts commit 0acb3d4e43
.
* general purpose crop added
* cleanup
* merged functions, added tests
* replaced gdebug.print_error with error
* clearer error message for crop
Co-authored-by: Lucas Schwiderski <4508454+sclu1034@users.noreply.github.com>
* make error message shorter
* surface.crop: Fix doc warnings.
---------
Co-authored-by: Paul Schneider <74120050+paulhersch@users.noreply.github.com>
Co-authored-by: Paul Schneider <paul.schneider2@student.uni-halle.de>
Co-authored-by: Lucas Schwiderski <4508454+sclu1034@users.noreply.github.com>
This commit is contained in:
parent
2f8b334234
commit
00ba63b44e
|
@ -14,6 +14,7 @@ local GdkPixbuf = require("lgi").GdkPixbuf
|
|||
local color, beautiful = nil, nil
|
||||
local gdebug = require("gears.debug")
|
||||
local hierarchy = require("wibox.hierarchy")
|
||||
local ceil = math.ceil
|
||||
|
||||
-- Keep this in sync with build-utils/lgi-check.c!
|
||||
local ver_major, ver_minor, ver_patch = string.match(require('lgi.version'), '(%d)%.(%d)%.(%d)')
|
||||
|
@ -283,6 +284,82 @@ function surface.widget_to_surface(widget, width, height, format)
|
|||
return img, run_in_hierarchy(widget, cr, width, height)
|
||||
end
|
||||
|
||||
--- Crop a surface on its edges.
|
||||
-- @tparam[opt=nil] table args
|
||||
-- @tparam[opt=0] integer args.left Left cutoff, cannot be negative
|
||||
-- @tparam[opt=0] integer args.right Right cutoff, cannot be negative
|
||||
-- @tparam[opt=0] integer args.top Top cutoff, cannot be negative
|
||||
-- @tparam[opt=0] integer args.bottom Bottom cutoff, cannot be negative
|
||||
-- @tparam[opt=nil] number|nil args.ratio Ratio to crop the image to. If edge cutoffs and
|
||||
-- ratio are given, the edge cutoffs are computed first. Using ratio will crop
|
||||
-- the center out of an image, similar to what "zoomed-fill" does in wallpaper
|
||||
-- setter programs. Cannot be negative
|
||||
-- @tparam[opt=nil] surface args.surface The surface to crop
|
||||
-- @return The cropped surface
|
||||
-- @staticfct crop_surface
|
||||
function surface.crop_surface(args)
|
||||
args = args or {}
|
||||
|
||||
if not args.surface then
|
||||
error("No surface to crop_surface supplied")
|
||||
return nil
|
||||
end
|
||||
|
||||
local surf = args.surface
|
||||
local target_ratio = args.ratio
|
||||
|
||||
local w, h = surface.get_size(surf)
|
||||
local offset_w, offset_h = 0, 0
|
||||
|
||||
if (args.top or args.right or args.bottom or args.left) then
|
||||
local left = args.left or 0
|
||||
local right = args.right or 0
|
||||
local top = args.top or 0
|
||||
local bottom = args.bottom or 0
|
||||
|
||||
if (top < 0 or right < 0 or bottom < 0 or left < 0) then
|
||||
error("negative offsets are not supported for crop_surface")
|
||||
end
|
||||
|
||||
w = w - left - right
|
||||
h = h - top - bottom
|
||||
|
||||
-- the offset needs to be negative
|
||||
offset_w = - left
|
||||
offset_h = - top
|
||||
|
||||
-- breaking stuff with cairo crashes awesome with no way to restart in place
|
||||
-- so here are checks for user error
|
||||
if w <= 0 or h <= 0 then
|
||||
error("Area to remove cannot be larger than the image size")
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
if target_ratio and target_ratio > 0 then
|
||||
local prev_ratio = w/h
|
||||
if prev_ratio ~= target_ratio then
|
||||
if (prev_ratio < target_ratio) then
|
||||
local old_h = h
|
||||
h = ceil(w * (1/target_ratio))
|
||||
offset_h = offset_h - ceil((old_h - h)/2)
|
||||
else
|
||||
local old_w = w
|
||||
w = ceil(h * target_ratio)
|
||||
offset_w = offset_w - ceil((old_w - w)/2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local ret = cairo.ImageSurface(cairo.Format.ARGB32, w, h)
|
||||
local cr = cairo.Context(ret)
|
||||
cr:set_source_surface(surf, offset_w, offset_h)
|
||||
cr.operator = cairo.Operator.SOURCE
|
||||
cr:paint()
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
return setmetatable(surface, surface.mt)
|
||||
|
||||
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
local surface = require("gears.surface")
|
||||
local color = require("gears.color")
|
||||
local lgi = require("lgi")
|
||||
local cairo = lgi.cairo
|
||||
local gdk = lgi.Gdk
|
||||
|
||||
describe("gears.surface", function ()
|
||||
describe("crop_surface", function ()
|
||||
local function test_square()
|
||||
local surf = cairo.ImageSurface(cairo.Format.ARGB32, 50, 50)
|
||||
local ctx = cairo.Context(surf)
|
||||
-- creates complicated pattern, to make sure each pixel has a
|
||||
-- different color value
|
||||
ctx:rectangle(0,0,50,50)
|
||||
local pattern = cairo.Pattern.create_linear(0, 0, 50, 50)
|
||||
pattern:add_color_stop_rgba(0, color.parse_color("#00000000"))
|
||||
pattern:add_color_stop_rgba(1, color.parse_color("#FF00FF55"))
|
||||
ctx:set_source(pattern)
|
||||
ctx:fill()
|
||||
local ctx2 = cairo.Context(surf)
|
||||
ctx2:rectangle(0,0,50,50)
|
||||
local pattern2 = cairo.Pattern.create_linear(0, 50, 50, 0)
|
||||
pattern2:add_color_stop_rgba(0, color.parse_color("#00FF0088"))
|
||||
pattern2:add_color_stop_rgba(1, color.parse_color("#00000000"))
|
||||
ctx2:set_source(pattern2)
|
||||
ctx2:fill()
|
||||
|
||||
return surf
|
||||
end
|
||||
|
||||
-- if cutoff + ratio are tested only right and bottom cutoff can be
|
||||
-- used because the test should not rebuild the math logic of the actual
|
||||
-- function
|
||||
---@param args table
|
||||
---@param target_heigth integer
|
||||
---@param target_width integer
|
||||
local function test(args, target_width, target_heigth)
|
||||
args.surface = test_square()
|
||||
local out = surface.crop_surface(args)
|
||||
|
||||
-- check size
|
||||
local w, h = surface.get_size(out)
|
||||
assert.is_equal(w, target_width)
|
||||
assert.is_equal(h, target_heigth)
|
||||
|
||||
-- check if area in img and cropped img are the same
|
||||
local calc_w_offset = math.ceil((50 - w - (args.right or 0))/2)
|
||||
local calc_h_offset = math.ceil((50 - h - (args.bottom or 0))/2)
|
||||
|
||||
local pbuf1_in = gdk.pixbuf_get_from_surface(
|
||||
args.surface,
|
||||
args.left or calc_w_offset,
|
||||
args.top or calc_h_offset,
|
||||
w,
|
||||
h
|
||||
)
|
||||
local pbuf1_out = gdk.pixbuf_get_from_surface(out, 0, 0, w, h)
|
||||
assert.is_equal(pbuf1_in:get_pixels(), pbuf1_out:get_pixels())
|
||||
end
|
||||
|
||||
it("keep size using offsets", function ()
|
||||
test({
|
||||
left = 0,
|
||||
top = 0,
|
||||
}, 50, 50)
|
||||
end)
|
||||
|
||||
it("keep size using ratio", function ()
|
||||
test({
|
||||
ratio = 1,
|
||||
left = 0,
|
||||
top = 0,
|
||||
}, 50, 50)
|
||||
end)
|
||||
|
||||
it("crop to 50x25 with offsets", function ()
|
||||
test({
|
||||
top = 12,
|
||||
bottom = 13,
|
||||
left = 0,
|
||||
}, 50, 25)
|
||||
end)
|
||||
|
||||
it("crop to 25x50 with offsets", function ()
|
||||
test({
|
||||
left = 12,
|
||||
right = 13,
|
||||
top = 0,
|
||||
}, 25, 50)
|
||||
end)
|
||||
|
||||
it("crop all edges", function ()
|
||||
test({
|
||||
left = 7,
|
||||
right = 13,
|
||||
top = 9,
|
||||
bottom = 16
|
||||
}, 30, 25)
|
||||
end)
|
||||
|
||||
it("use ratio to crop width", function ()
|
||||
test({
|
||||
ratio = 3/5,
|
||||
}, 30, 50)
|
||||
end)
|
||||
|
||||
it("use ratio to crop height", function ()
|
||||
test({
|
||||
ratio = 5/3,
|
||||
}, 50, 30)
|
||||
end)
|
||||
|
||||
it("use very large ratio", function ()
|
||||
test({
|
||||
ratio = 10000
|
||||
}, 50, 1)
|
||||
end)
|
||||
|
||||
it("use very small ratio", function ()
|
||||
test({
|
||||
ratio = 0.0001
|
||||
}, 1, 50)
|
||||
end)
|
||||
|
||||
it("use ratio and offset", function ()
|
||||
test({
|
||||
bottom = 20,
|
||||
ratio = 1
|
||||
}, 30, 30)
|
||||
end)
|
||||
|
||||
it("use too large offset", function ()
|
||||
assert.has.errors(function ()
|
||||
test({
|
||||
left = 55,
|
||||
top = 0,
|
||||
}, 0, 0)
|
||||
end)
|
||||
end)
|
||||
|
||||
it("use negative offset", function ()
|
||||
assert.has.errors(function ()
|
||||
test({
|
||||
left = -1,
|
||||
top = 0
|
||||
}, 0, 0)
|
||||
end)
|
||||
end)
|
||||
|
||||
it("use negative ratio", function ()
|
||||
assert.has.errors(function ()
|
||||
test({
|
||||
ratio = - 1,
|
||||
}, 0, 0)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|
Loading…
Reference in New Issue