tag: Improve tag property::index support (FS#1229)
[awesome.git] / lib / gears / color.lua.in
blobfe5ac2a147e189d2e020824dec3523463aaf2246
1 ---------------------------------------------------------------------------
2 -- @author Uli Schlachter
3 -- @copyright 2010 Uli Schlachter
4 -- @release @AWESOME_VERSION@
5 ---------------------------------------------------------------------------
7 local setmetatable = setmetatable
8 local string = string
9 local table = table
10 local unpack = unpack or table.unpack -- v5.1: unpack, v5.2: table.unpack
11 local tonumber = tonumber
12 local ipairs = ipairs
13 local pairs = pairs
14 local type = type
15 local cairo = require("lgi").cairo
16 local surface = require("gears.surface")
18 local color = { mt = {} }
19 local pattern_cache = setmetatable({}, { __mode = 'v' })
21 --- Parse a HTML-color.
22 -- This function can parse colors like #rrggbb and #rrggbbaa.
23 -- For example, parse_color("#00ff00ff") would return 0, 1, 0, 1.
24 -- Thanks to #lua for this. :)
25 -- @param col The color to parse
26 -- @return 4 values which each are in the range [0, 1].
27 function color.parse_color(col)
28 local rgb = {}
29 for pair in string.gmatch(col, "[^#].") do
30 local i = tonumber(pair, 16)
31 if i then
32 table.insert(rgb, i / 255)
33 end
34 end
35 while #rgb < 4 do
36 table.insert(rgb, 1)
37 end
38 return unpack(rgb)
39 end
41 --- Find all numbers in a string
42 -- @param s The string to parse
43 -- @return Each number found as a separate value
44 local function parse_numbers(s)
45 local res = {}
46 for k in string.gmatch(s, "-?[0-9]+[.]?[0-9]*") do
47 table.insert(res, tonumber(k))
48 end
49 return unpack(res)
50 end
52 --- Create a solid pattern
53 -- @param col The color for the pattern
54 -- @return A cairo pattern object
55 function color.create_solid_pattern(col)
56 local col = col
57 if col == nil then
58 col = "#000000"
59 elseif type(col) == "table" then
60 col = col.color
61 end
62 return cairo.Pattern.create_rgba(color.parse_color(col))
63 end
65 --- Create an image pattern from a png file
66 -- @param file The filename of the file
67 -- @return a cairo pattern object
68 function color.create_png_pattern(file)
69 local file = file
70 if type(file) == "table" then
71 file = file.file
72 end
73 local image = surface.load(file)
74 local pattern = cairo.Pattern.create_for_surface(image)
75 pattern:set_extend(cairo.Extend.REPEAT)
76 return pattern
77 end
79 -- Add stops to the given pattern.
80 -- @param p The cairo pattern to add stops to
81 -- @param iterator An iterator that returns strings. Each of those strings
82 -- should be in the form place,color where place is in [0, 1].
83 local function add_iterator_stops(p, iterator)
84 for k in iterator do
85 local sub = string.gmatch(k, "[^,]+")
86 local point, clr = sub(), sub()
87 p:add_color_stop_rgba(point, color.parse_color(clr))
88 end
89 end
91 -- Add a list of stops to a given pattern
92 local function add_stops_table(pat, arg)
93 for _, stop in ipairs(arg) do
94 pat:add_color_stop_rgba(stop[1], color.parse_color(stop[2]))
95 end
96 end
98 -- Create a pattern from a string
99 local function string_pattern(creator, arg)
100 local iterator = string.gmatch(arg, "[^:]+")
101 -- Create a table where each entry is a number from the original string
102 local args = { parse_numbers(iterator()) }
103 local to = { parse_numbers(iterator()) }
104 -- Now merge those two tables
105 for k, v in pairs(to) do
106 table.insert(args, v)
108 -- And call our creator function with the values
109 local p = creator(unpack(args))
111 add_iterator_stops(p, iterator)
112 return p
115 --- Create a linear pattern object.
116 -- The pattern is created from a string. This string should have the following
117 -- form: "x0,y0:x1,y1:&#60;stops&#62;"
118 -- Alternatively, the pattern can be specified as a table:
119 -- { type = "linear", from = { x0, y0 }, to = { x1, y1 },
120 -- stops = { &#60stops&#62 } }
121 -- x0,y0 and x1,y1 are the start and stop point of the pattern.
122 -- For the explanation of "&#60;stops&#62;", see @{create_pattern}.
123 -- @param arg The argument describing the pattern
124 -- @return a cairo pattern object
125 function color.create_linear_pattern(arg)
126 local pat
128 if type(arg) == "string" then
129 return string_pattern(cairo.Pattern.create_linear, arg)
130 elseif type(arg) ~= "table" then
131 error("Wrong argument type: " .. type(arg))
134 pat = cairo.Pattern.create_linear(arg.from[1], arg.from[2], arg.to[1], arg.to[2])
135 add_stops_table(pat, arg.stops)
136 return pat
139 --- Create a radial pattern object.
140 -- The pattern is created from a string. This string should have the following
141 -- form: "x0,y0,r0:x1,y1,r1:&#60stops&#62"
142 -- Alternatively, the pattern can be specified as a table:
143 -- { type = "radial", from = { x0, y0, r0 }, to = { x1, y1, r1 },
144 -- stops = { &#60stops&#62 } }
145 -- x0,y0 and x1,y1 are the start and stop point of the pattern.
146 -- r0 and r1 are the radii of the start / stop circle.
147 -- For the explanation of "&#60;stops&#62;", see @{create_pattern}.
148 -- @param arg The argument describing the pattern
149 -- @return a cairo pattern object
150 function color.create_radial_pattern(arg)
151 local pat
153 if type(arg) == "string" then
154 return string_pattern(cairo.Pattern.create_radial, arg)
155 elseif type(arg) ~= "table" then
156 error("Wrong argument type: " .. type(arg))
159 pat = cairo.Pattern.create_radial(arg.from[1], arg.from[2], arg.from[3],
160 arg.to[1], arg.to[2], arg.to[3])
161 add_stops_table(pat, arg.stops)
162 return pat
165 --- Mapping of all supported color types. New entries can be added.
166 color.types = {
167 solid = color.create_solid_pattern,
168 png = color.create_png_pattern,
169 linear = color.create_linear_pattern,
170 radial = color.create_radial_pattern
173 --- Create a pattern from a given string.
174 -- For full documentation of this function, please refer to create_pattern().
175 -- This difference between @{create_pattern} and this function is that this
176 -- function does not insert the generated objects into the pattern cache. Thus,
177 -- you are allowed to modify the returned object.
178 -- @see create_pattern
179 -- @param col The string describing the pattern.
180 -- @return a cairo pattern object
181 function color.create_pattern_uncached(col)
182 -- If it already is a cairo pattern, just leave it as that
183 if cairo.Pattern:is_type_of(col) then
184 return col
186 local col = col or "#000000"
187 if type(col) == "string" then
188 local t = string.match(col, "[^:]+")
189 if color.types[t] then
190 local pos = string.len(t)
191 local arg = string.sub(col, pos + 2)
192 return color.types[t](arg)
194 elseif type(col) == "table" then
195 local t = col.type
196 if color.types[t] then
197 return color.types[t](col)
200 return color.create_solid_pattern(col)
203 --- Create a pattern from a given string.
204 -- This function can create solid, linear, radial and png patterns. In general,
205 -- patterns are specified as strings formatted as"type:arguments". "arguments"
206 -- is specific to the pattern used. For example, one can use
207 -- "radial:50,50,10:55,55,30:0,#ff0000:0.5,#00ff00:1,#0000ff"
208 -- Alternatively, patterns can be specified via tables. In this case, the
209 -- table's 'type' member specifies the type. For example:
210 -- { type = "radial", from = { 50, 50, 10 }, to = { 55, 55, 30 },
211 -- stops = { { 0, "#ff0000" }, { 0.5, "#00ff00" }, { 1, "#0000ff" } } }
212 -- Any argument that cannot be understood is passed to @{create_solid_pattern}.
214 -- Please note that you MUST NOT modify the returned pattern, for example by
215 -- calling :set_matrix() on it, because this function uses a cache and your
216 -- changes could thus have unintended side effects. Use @{create_pattern_uncached}
217 -- if you need to modify the returned pattern.
218 -- @see create_pattern_uncached, create_solid_pattern, create_png_pattern,
219 -- create_linear_pattern, create_radial_pattern
220 -- @param col The string describing the pattern.
221 -- @return a cairo pattern object
222 function color.create_pattern(col)
223 -- If it already is a cairo pattern, just leave it as that
224 if cairo.Pattern:is_type_of(col) then
225 return col
227 local col = col or "#000000"
228 local result = pattern_cache[col]
229 if not result then
230 result = color.create_pattern_uncached(col)
231 pattern_cache[col] = result
233 return result
236 --- Check if a pattern is opaque.
237 -- A pattern is transparent if the background on which it gets drawn (with
238 -- operator OVER) doesn't influence the visual result.
239 -- @param col An argument that @{create_pattern} accepts
240 -- @return The pattern if it is surely opaque, else nil
241 function color.create_opaque_pattern(col)
242 local pattern = color.create_pattern(col)
243 local type = pattern:get_type()
244 local extend = pattern:get_extend()
246 if type == "SOLID" then
247 local status, r, g, b, a = pattern:get_rgba()
248 if a ~= 1 then
249 return
251 return pattern
252 elseif type == "SURFACE" then
253 local status, surface = pattern:get_surface()
254 if status ~= "SUCCESS" or surface.content ~= "COLOR" then
255 -- The surface has an alpha channel which *might* be non-opaque
256 return
259 -- Only the "NONE" extend mode is forbidden, everything else doesn't
260 -- introduce transparent parts
261 if pattern:get_extend() == "NONE" then
262 return
265 return pattern
266 elseif type == "LINEAR" then
267 local status, stops = pattern:get_color_stop_count()
269 -- No color stops or extend NONE -> pattern *might* contain transparency
270 if stops == 0 or pattern:get_extend() == "NONE" then
271 return
274 -- Now check if any of the color stops contain transparency
275 for i = 0, stops - 1 do
276 local status, offset, r, g, b, a = pattern:get_color_stop_rgba(i)
277 if a ~= 1 then
278 return
281 return pattern
284 -- Unknown type, e.g. mesh or raster source or unsupported type (radial
285 -- gradients can do weird self-intersections)
288 function color.mt:__call(...)
289 return color.create_pattern(...)
292 return setmetatable(color, color.mt)
294 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80