class Color::Palette::AdobeColor

A class that can read an Adobe Color palette file (used for Photoshop swatches) and provide a Hash-like interface to the contents. Not all colour formats in ACO files are supported. Based largely off the information found by Larry Tesler.

Not all Adobe Color files have named colours; all named entries are returned as an array.

pal = Color::Palette::AdobeColor.from_file(my_aco_palette)
pal[0]          => Color::RGB<...>
pal["white"]    => [ Color::RGB<...> ]
pal["unknown"]  => [ Color::RGB<...>, Color::RGB<...>, ... ]

AdobeColor palettes are always indexable by insertion order (an integer key).

Version 2 palettes use UTF-16 colour names.

Attributes

lost[R]

Contains the "lost" colours in the palette. These colours could not be properly loaded (e.g., L*a*b* is not supported by Color, so it is "lost") or are not understood by the algorithms.

statistics[R]

Returns statistics about the nature of the colours loaded.

version[R]

Public Class Methods

from_file(filename) click to toggle source

Create an AdobeColor palette object from the named file.

# File lib/color/palette/adobecolor.rb, line 37
def from_file(filename)
  File.open(filename, "rb") { |io| Color::Palette::AdobeColor.from_io(io) }
end
from_io(io) click to toggle source

Create an AdobeColor palette object from the provided IO.

# File lib/color/palette/adobecolor.rb, line 42
def from_io(io)
  Color::Palette::AdobeColor.new(io.read)
end
new(palette) click to toggle source

Create a new AdobeColor palette from the palette file as a string.

# File lib/color/palette/adobecolor.rb, line 58
def initialize(palette)
  @colors     = []
  @names      = {}
  @statistics = Hash.new(0)
  @lost       = []
  @order      = []
  @version    = nil

  class << palette
    def readwords(count = 1)
      @offset ||= 0
      raise IndexError if @offset >= self.size
      val = self[@offset, count * 2]
      raise IndexError if val.nil? or val.size < (count * 2)
      val = val.unpack("n" * count)
      @offset += count * 2
      val
    end

    def readutf16(count = 1)
      @offset ||= 0
      raise IndexError if @offset >= self.size
      val = self[@offset, count * 2]
      raise IndexError if val.nil? or val.size < (count * 2)
      @offset += count * 2
      val
    end
  end

  @version, count = palette.readwords 2

  raise "Unknown AdobeColor palette version #@version." unless @version.between?(1, 2)

  count.times do
    space, w, x, y, z = palette.readwords 5
    name = nil
    if @version == 2
      raise IndexError unless palette.readwords == [ 0 ]
      len = palette.readwords
      name = palette.readutf16(len[0] - 1)
      raise IndexError unless palette.readwords == [ 0 ]
    end

    color = case space
            when 0 then # RGB
              @statistics[:rgb] += 1

              Color::RGB.new(w / 256, x / 256, y / 256)
            when 1 then # HS[BV] -- Convert to RGB
              @statistics[:hsb] += 1

              h = w / 65535.0
              s = x / 65535.0
              v = y / 65535.0

              if defined?(Color::HSB)
                Color::HSB.from_fraction(h, s, v)
              else
                @statistics[:converted] += 1
                if Color.near_zero_or_less?(s)
                  Color::RGB.from_fraction(v, v, v)
                else
                  if Color.near_one_or_more?(h)
                    vh = 0
                  else
                    vh = h * 6.0
                  end

                  vi = vh.floor
                  v1 = v.to_f * (1 - s.to_f)
                  v2 = v.to_f * (1 - s.to_f * (vh - vi))
                  v3 = v.to_f * (1 - s.to_f * (1 - (vh - vi)))

                  case vi
                  when 0 then Color::RGB.from_fraction(v, v3, v1)
                  when 1 then Color::RGB.from_fraction(v2, v, v1)
                  when 2 then Color::RGB.from_fraction(v1, v, v3)
                  when 3 then Color::RGB.from_fraction(v1, v2, v)
                  when 4 then Color::RGB.from_fraction(v3, v1, v)
                  else Color::RGB.from_fraction(v, v1, v2)
                  end
                end
              end
            when 2 then # CMYK
              @statistics[:cmyk] += 1
              Color::CMYK.from_percent(100 - (w / 655.35),
                                       100 - (x / 655.35),
                                       100 - (y / 655.35),
                                       100 - (z / 655.35))
            when 7 then # L*a*b*
              @statistics[:lab] += 1

              l = [w, 10000].min / 100.0
              a = [[-12800, UwToSw[x]].max, 12700].min / 100.0
              b = [[-12800, UwToSw[x]].max, 12700].min / 100.0

              if defined? Color::Lab
                Color::Lab.new(l, a, b)
              else
                [ space, w, x, y, z ]
              end
            when 8 then # Grayscale
              @statistics[:gray] += 1

              g = [w, 10000].min / 100.0
              Color::GrayScale.new(g)
            when 9 then # Wide CMYK
              @statistics[:wcmyk] += 1

              c = [w, 10000].min / 100.0
              m = [x, 10000].min / 100.0
              y = [y, 10000].min / 100.0
              k = [z, 10000].min / 100.0
              Color::CMYK.from_percent(c, m, y, k)
            else
              @statistics[space] += 1
              [ space, w, x, y, z ]
            end

    @order << [ color, name ]

    if color.kind_of? Array
      @lost << color
    else
      @colors << color

      if name
        @names[name] ||= []
        @names[name] << color
      end
    end
  end
end

Public Instance Methods

[](key) click to toggle source

If a Numeric key is provided, the single colour value at that position will be returned. If a String key is provided, the colour set (an array) for that colour name will be returned.

# File lib/color/palette/adobecolor.rb, line 200
def [](key)
  if key.kind_of?(Numeric)
    @colors[key]
  else
    @names[key]
  end
end
each() { |el| ... } click to toggle source

Loops through each colour.

# File lib/color/palette/adobecolor.rb, line 209
def each
  @colors.each { |el| yield el }
end
each_name() { |color_name, color_set| ... } click to toggle source

Loops through each named colour set.

# File lib/color/palette/adobecolor.rb, line 214
def each_name #:yields color_name, color_set:#
  @names.each { |color_name, color_set| yield color_name, color_set }
end
size() click to toggle source
# File lib/color/palette/adobecolor.rb, line 218
def size
  @colors.size
end
values_at(*selectors) click to toggle source

Provides the colour or colours at the provided selectors.

# File lib/color/palette/adobecolor.rb, line 193
def values_at(*selectors)
  @colors.values_at(*selectors)
end