--------------------------------------------------------------------------- -- @author Uli Schlachter -- @copyright 2012 Uli Schlachter -- @module gears.surface --------------------------------------------------------------------------- local setmetatable = setmetatable local type = type local capi = { awesome = awesome } local cairo = require("lgi").cairo local GdkPixbuf = require("lgi").GdkPixbuf local color = nil local gdebug = require("gears.debug") local hierarchy = require("wibox.hierarchy") -- 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)') if tonumber(ver_major) <= 0 and (tonumber(ver_minor) < 8 or (tonumber(ver_minor) == 8 and tonumber(ver_patch) < 0)) then error("lgi too old, need at least version 0.8.0") end local surface = { mt = {} } local surface_cache = setmetatable({}, { __mode = 'v' }) local function get_default(arg) if type(arg) == 'nil' then return cairo.ImageSurface(cairo.Format.ARGB32, 0, 0) end return arg end --- Try to convert the argument into an lgi cairo surface. -- This is usually needed for loading images by file name. -- @param _surface The surface to load or nil -- @param default The default value to return on error; when nil, then a surface -- in an error state is returned. -- @return The loaded surface, or the replacement default -- @return An error message, or nil on success. -- @staticfct load_uncached_silently function surface.load_uncached_silently(_surface, default) -- On nil, return some sane default if not _surface then return get_default(default) end -- lgi cairo surfaces don't get changed either if cairo.Surface:is_type_of(_surface) then return _surface end -- Strings are assumed to be file names and get loaded if type(_surface) == "string" then local pixbuf, err = GdkPixbuf.Pixbuf.new_from_file(_surface) if not pixbuf then return get_default(default), tostring(err) end _surface = capi.awesome.pixbuf_to_surface(pixbuf._native, _surface) -- The shims implement load_image() to return a surface directly, -- instead of a lightuserdatum. if cairo.Surface:is_type_of(_surface) then return _surface end end -- Everything else gets forced into a surface return cairo.Surface(_surface, true) end --- Try to convert the argument into an lgi cairo surface. -- This is usually needed for loading images by file name and uses a cache. -- In contrast to `load()`, errors are returned to the caller. -- @param _surface The surface to load or nil -- @param default The default value to return on error; when nil, then a surface -- in an error state is returned. -- @return The loaded surface, or the replacement default, or nil if called with -- nil. -- @return An error message, or nil on success. -- @staticfct load_silently function surface.load_silently(_surface, default) if type(_surface) == "string" then local cache = surface_cache[_surface] if cache then return cache end local result, err = surface.load_uncached_silently(_surface, default) if not err then -- Cache the file surface_cache[_surface] = result end return result, err end return surface.load_uncached_silently(_surface, default) end local function do_load_and_handle_errors(_surface, func) if type(_surface) == 'nil' then return get_default() end local result, err = func(_surface, false) if result then return result end gdebug.print_error(debug.traceback( "Failed to load '" .. tostring(_surface) .. "': " .. tostring(err))) return get_default() end --- Try to convert the argument into an lgi cairo surface. -- This is usually needed for loading images by file name. Errors are handled -- via `gears.debug.print_error`. -- @param _surface The surface to load or nil -- @return The loaded surface, or nil -- @staticfct load_uncached function surface.load_uncached(_surface) return do_load_and_handle_errors(_surface, surface.load_uncached_silently) end --- Try to convert the argument into an lgi cairo surface. -- This is usually needed for loading images by file name. Errors are handled -- via `gears.debug.print_error`. -- @param _surface The surface to load or nil -- @return The loaded surface, or nil. -- @staticfct gears.surface function surface.load(_surface) return do_load_and_handle_errors(_surface, surface.load_silently) end function surface.mt.__call(_, ...) return surface.load(...) end --- Get the size of a cairo surface -- @param surf The surface you are interested in -- @return The surface's width and height. -- @staticfct get_size function surface.get_size(surf) local cr = cairo.Context(surf) local x, y, w, h = cr:clip_extents() return w - x, h - y end --- Create a copy of a cairo surface. -- The surfaces returned by `surface.load` are cached and must not be -- modified to avoid unintended side-effects. This function allows to create -- a copy of a cairo surface. This copy can then be freely modified. -- The surface returned will be as compatible as possible to the input -- surface. For example, it will likely be of the same surface type as the -- input. The details are explained in the `create_similar` function on a cairo -- surface. -- @param s Source surface. -- @return The surface's duplicate. -- @staticfct duplicate_surface function surface.duplicate_surface(s) s = surface.load(s) -- Figure out surface size (this does NOT work for unbounded recording surfaces) local cr = cairo.Context(s) local x, y, w, h = cr:clip_extents() -- Create a copy local result = s:create_similar(s.content, w - x, h - y) cr = cairo.Context(result) cr:set_source_surface(s, 0, 0) cr.operator = cairo.Operator.SOURCE cr:paint() return result end --- Create a surface from a `gears.shape` -- Any additional parameters will be passed to the shape function -- @tparam number width The surface width -- @tparam number height The surface height -- @param shape A `gears.shape` compatible function -- @param[opt=white] shape_color The shape color or pattern -- @param[opt=transparent] bg_color The surface background color -- @treturn cairo.surface the new surface -- @staticfct load_from_shape function surface.load_from_shape(width, height, shape, shape_color, bg_color, ...) color = color or require("gears.color") local img = cairo.ImageSurface(cairo.Format.ARGB32, width, height) local cr = cairo.Context(img) cr:set_source(color(bg_color or "#00000000")) cr:paint() cr:set_source(color(shape_color or "#000000")) shape(cr, width, height, ...) cr:fill() return img end --- Apply a shape to a client or a wibox. -- -- If the wibox or client size change, this function need to be called -- again. -- @param draw A wibox or a client -- @param shape or gears.shape function or a custom function with a context, -- width and height as parameter. -- @param[opt] Any additional parameters will be passed to the shape function. -- @staticfct apply_shape_bounding function surface.apply_shape_bounding(draw, shape, ...) local geo = draw:geometry() local img = cairo.ImageSurface(cairo.Format.A1, geo.width, geo.height) local cr = cairo.Context(img) cr:set_operator(cairo.Operator.CLEAR) cr:set_source_rgba(0,0,0,1) cr:paint() cr:set_operator(cairo.Operator.SOURCE) cr:set_source_rgba(1,1,1,1) shape(cr, geo.width, geo.height, ...) cr:fill() draw.shape_bounding = img._native img:finish() end local function no_op() end local function run_in_hierarchy(self, cr, width, height) local context = {dpi=96} local h = hierarchy.new(context, self, width, height, no_op, no_op, {}) h:draw(context, cr) return h end --- Create an SVG file with this widget content. -- This is dynamic, so the SVG will be updated along with the widget content. -- because of this, the painting may happen hover multiple event loop cycles. -- @deprecated draw_to_svg_file -- @tparam widget widget A widget -- @tparam string path The output file path -- @tparam number width The surface width -- @tparam number height The surface height -- @return The cairo surface -- @return The hierarchy. -- @see wibox.widget.draw_to_svg_file -- @see wibox.widget.draw_to_image_surface function surface.widget_to_svg(widget, path, width, height) gdebug.deprecate("Use wibox.widget.draw_to_svg_file instead of ".. "gears.surface.render_to_svg", {deprecated_in=5}) local img = cairo.SvgSurface.create(path, width, height) local cr = cairo.Context(img) return img, run_in_hierarchy(widget, cr, width, height) end --- Create a cairo surface with this widget content. -- This is dynamic, so the SVG will be updated along with the widget content. -- because of this, the painting may happen hover multiple event loop cycles. -- @deprecated draw_to_image_surface -- @tparam widget widget A widget -- @tparam number width The surface width -- @tparam number height The surface height -- @param[opt=cairo.Format.ARGB32] format The surface format -- @return The cairo surface -- @return The hierarchy. -- @see wibox.widget.draw_to_svg_file -- @see wibox.widget.draw_to_image_surface function surface.widget_to_surface(widget, width, height, format) gdebug.deprecate("Use wibox.widget.draw_to_image_surface instead of ".. "gears.surface.render_to_surface", {deprecated_in=5}) local img = cairo.ImageSurface(format or cairo.Format.ARGB32, width, height) local cr = cairo.Context(img) return img, run_in_hierarchy(widget, cr, width, height) end return setmetatable(surface, surface.mt) -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80