colors.lua

-- Coypright 2015-2017 Stefan Göbel.
--
-- This file is part of Corky.
--
-- Corky is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
-- License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
-- later version.
--
-- Corky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
-- warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
-- details.
--
-- You should have received a copy of the GNU General Public License along with Corky. If not, see
-- <http://www.gnu.org/licenses/>.

-------------------------------------------------------------------------------------------------------------------

--- Color handling for *Corky*.
--
--  This module may be used to store and retrieve colors using custom names.
--
--  **Loading the module:**
--
--    #: include, corky.colors
--
--  **Adding a named color:**
--
--  These configuration directives will add the specified color to the color table. It may then be accessed by its
--  name. In all the directives the `<name>` may be any arbitrary, non-empty string.
--
--    #: color, <name>, <red>, <green>, <blue>[, <alpha>]
--
--  The `<red>`, `<green>`, `<blue>` and `<alpha>` values specify the color. These must be floating point numbers
--  between `0` and `1` (inclusive). The `<alpha>` parameter is optional, it defaults to `1` if omitted.
--
--    #: color, <name>, <rgb>[, <alpha>]
--
--  The `<rgb>` parameter specifies the color. It must be a valid number, usually the hexadecimal representation of
--  the color (i.e. `0xRRGGBB`). It may include the alpha value (ARGB format: `0xAARRGGBB`). The alpha value may
--  also be specified by an additional parameter, in that case it has to be a floating point number between `0` and
--  `1` (inclusive). If no alpha value is specified at all, it defaults to `1`. If an alpha value is specified via
--  both parameters, the optional alpha parameter will override the alpha value of `<rgb>`. Note that to specify an
--  alpha value of `0`, it has to be supplied as the extra alpha parameter!
--
--  @MO corky.colors
--  @CO © 2015-2017 Stefan Göbel
--  @RE 2017033001
--  @LI [GPLv3](http://www.gnu.org/copyleft/gpl.html)
--  @AU Stefan Göbel [[⌂]](http://subtype.de/) [[✉]](mailto:corky@subtype.de)
--  @AL this

-------------------------------------------------------------------------------------------------------------------

--- Stores the user-defined colors.
--
--  The keys are the names of the colors, or the numbers/strings supplied to get() for automatically
--  added color entries. The values are tables with the color information `{<R>, <G>, <B>, <A>}`. Note that every
--  color accessed with get() will be cached in the table!
--
--  @l colors

-------------------------------------------------------------------------------------------------------------------

local utils  = require "corky.utils"                                          -- For table.pack() and .unpack().
local v      = require "corky.validator"
local colors = {}
local this   = {}

-------------------------------------------------------------------------------------------------------------------

do

   local function extract (s, p)                                              -- Extract a byte from a hex string
      return tonumber (string.sub (s, 2 * p - 1, 2 * p), 16) / 255            --   (2 chars -> "00" .. "ff") and
   end                                                                        --   convert it to 0 <= value <= 1.

   --- Get the individual R, G, B and A channels for a specified color.
   --
   --  There are two ways to call this function:
   --
   --  * If called with **one or two parameters**, the first parameter will be treated as a number, defining the
   --  color. This is usually a hexadecimal representation in the form `0xRRGGBB` (RGB) or `0xAARRGGBB` (ARGB),
   --  though any value that can be converted to a valid number between `0` and `0xffffffff` (inclusive) will work.
   --  The (optional) second parameter may be used to specify the alpha value, it must be a float between `0` and
   --  `1` (inclusive). If the first parameter includes an alpha value (i.e. if it is greater than `0x00ffffff`,
   --  ARGB format), the second parameter will override it! Note that for an alpha value of zero you have to use
   --  the two parameter form! If there is no value specified for alpha, it defaults to `1`.
   --  * If called with **three or four parameters**, the first three parameters will be treated as the R, G and B
   --  channel values (in that order), and the fourth (optional) parameter as the alpha value. All values must be
   --  floats between `0` and `1` (inclusive). If alpha is omitted, it defaults to `1`.
   --
   --  **Note:** All parameters will be validated, on error this function returns `nil`.
   --
   --  @p ... See above for a description of the parameters.
   --  @T An array containing the values for the red, green, blue and alpha channels, in that order. All values are
   --  floating point numbers between `0` and `1` (inclusive). Returns `nil` on error.

   this.to_rgba = function (...)

      local args = table.pack (...)

      if args.n == 1 or args.n == 2 then                                      -- <color>[, <alpha>]

         local valid, color, alpha = true

         valid, color = v { v (args [1], valid)          . n . ir (0, 0xffffffff) . d (1) }
         valid, alpha = v { v (args [2], valid) . dv (1) . n . ir (0,          1)         }

         if not valid then
            return nil
         end

         local hex = string.format ("%08x", color)                            -- As hexadecimal string: 0xAARRGGBB.

         if color > 0x00ffffff and args.n == 1 then                           -- Get alpha from c unless specified
            alpha = extract (hex, 1)                                          --    explicitely.
         end

         return { extract (hex, 2), extract (hex, 3), extract (hex, 4), alpha }

      elseif args.n == 3 or args.n == 4 then                                  -- <red>, <green>, <blue>[, <alpha>]

         local valid, r, g, b, a = true

         valid, r = v { v (args [1], valid)          . n . ir (0, 1) }        -- Check for valid values.
         valid, g = v { v (args [2], valid)          . n . ir (0, 1) }
         valid, b = v { v (args [3], valid)          . n . ir (0, 1) }
         valid, a = v { v (args [4], valid) . dv (1) . n . ir (0, 1) }

         if not valid then
            return nil
         end

         return { r, g, b, a }

      end

      return nil

   end

end

-------------------------------------------------------------------------------------------------------------------

--- Add a color to the color cache, or change an existing entry.
--
--  This function may be used to add a named or unnamed color to the color cache, or change an existing color.
--
--  * If called with only **one parameter**, this parameter is assumed to be a single color specification (usually
--  a hexadecimal string, but any value that can be converted to a valid integer between `0` and `0xffffffff` will
--  work). It will be processed by to_rgba() and added to the color cache, the key will be the
--  color as it is specified (i.e. no conversion will be performed for the key, it may be a string or a number).
--  * If called with **more than one parameter**, the first parameter will be used as the name of the color, and
--  all remaining parameters will be passed to to_rgba() to be processed, please see there for a
--  description of possible values. The color will be added to the cache with the color name (first parameter) as
--  the key.
--
--  Note: If the key already exists in the cache the existing entry will be overridden with the new color!
--
--  @p ... See above for a description of the parameters.
--  @T Returns an array containing the values for the red, green, blue and alpha channels of the color, in that
--  order. All values are floating point numbers between `0` and `1` (inclusive). Returns `nil` on error.

this.set = function (...)

   local args  = table.pack (...)
   local name  = args [1]
   local color = nil

   if args.n == 1 then
      color = this.to_rgba (name)
   else
      color = this.to_rgba (select (2, ...))
   end

   colors [name] = color

   return color

end

-------------------------------------------------------------------------------------------------------------------

--- Get a color from the cache.
--
--  This function will return the cached color, i.e. an array containg the R, G, B and alpha values, if it exists
--  in the color cache. If it does not exist, the specified name is assumed to be a color specification, usually a
--  hexadecimal representation in RGB or ARGB format (any other value that can be converted to a valid number may
--  be used though), and it is automatically converted to `{ <r>, <g>, <b>, <a> }`, added to the cache and then
--  returned. Note that the key to access it is the unaltered parameter value, that means that - for example - the
--  two parameters `0xabcdef` (a number) and `"0xabcdef"` (a string) will result in two cache entries, even though
--  the color will be the same.
--
--  @p color The name of the color or a color specification.
--  @T Returns an array containing the values for the red, green, blue and alpha channels of the requested color,
--  in that order. All values are floats between `0` and `1` (inclusive). Returns `nil` on error.

this.get = function (color)

   if colors [color] then
      return colors [color]
   else
      return this.set (color)
   end

end

-------------------------------------------------------------------------------------------------------------------

do

   --- Configuration handler for color settings.
   --
   --  This handler will be called automatically for every `color` directive in the configuration
   --  file.
   --
   --  @t setting Array containing the configuration directive split into its individual parts.
   --  @B If successful `true`, `false` in case of any error.
   --  @c corky

   local function config_handler (setting)

      local valid, _ = true

      valid, _ = v { v (#setting,    valid) . ir (3, 6) }                        -- Basic validation of the number
      valid, _ = v { v (setting [2], valid) . nm        }                        --    of parameters and the name.

      if not valid then
         return false
      end

      if not this.set (select (2, table.unpack (setting))) then                  -- Everything else is checked in
         return false                                                            --    to_rgba() which is called by
      end                                                                        --    set().

      return true

   end

   local config = require "corky.config"
   config.handler ("color", config_handler)

end

-------------------------------------------------------------------------------------------------------------------

return this

--                                                     :indentSize=3:tabSize=3:noTabs=true:mode=lua:maxLineLen=115:
generated by LDoc 1.4.6 Last updated 2019-02-14 20:51:07