class Gradient::GRD

Constants

COLOR_TERMS
PARSE_METHODS

Attributes

maps[R]

Public Class Methods

new() click to toggle source
# File lib/gradient/grd.rb, line 43
def initialize
  @maps = {}

  @gradient_names = []
  @color_gradients = []
  @transparency_gradients = []

  @current_object_name = ""
  @current_color_gradient = []
  @current_transparency_gradient = []
  @current_color = {}
  @current_transparency = {}

  @shift = 0
end
open(file) click to toggle source
# File lib/gradient/grd.rb, line 37
def open(file)
  read(file)
end
parse(string_buffer) click to toggle source
# File lib/gradient/grd.rb, line 23
def parse(string_buffer)
  new.tap do |parser|
    parser.parse(string_buffer)
  end.maps
end
read(file) click to toggle source
# File lib/gradient/grd.rb, line 29
def read(file)
  new.tap do |parser|
    File.open(file, "r") do |file|
      parser.parse(file.read)
    end
  end.maps
end

Public Instance Methods

parse(buffer) click to toggle source
# File lib/gradient/grd.rb, line 59
def parse(buffer)
  @buffer = buffer
  @offset = 28
  parse_entry while @offset < @buffer.length
  flush_current_gradient

  color_gradients = @color_gradients.map do |gradient|
    clean_color_gradient(gradient).map do |color_step|
      Gradient::ColorPoint.new(*color_step)
    end
  end

  transparency_gradients = @transparency_gradients.map do |gradient|
    clean_transparency_gradient(gradient).map do |transparency_step|
      Gradient::OpacityPoint.new(*transparency_step)
    end
  end

  gradients = color_gradients.zip(transparency_gradients).map do |color_points|
    Gradient::Map.new(Gradient::PointMerger.new(*color_points).call)
  end

  @maps = Hash[ @gradient_names.zip(gradients) ]
end

Private Instance Methods

clean_color_gradient(steps) click to toggle source
# File lib/gradient/grd.rb, line 94
        def clean_color_gradient(steps)
  locations = clean_gradient(steps)
  colors = steps.map do |step|
    convert_to_color(step)
  end
  locations.zip(colors)
end
clean_gradient(steps) click to toggle source
# File lib/gradient/grd.rb, line 84
        def clean_gradient(steps)
  locations = steps.map { |g| g.fetch("Lctn", 0.0) }
  min_location = locations.min
  max_location = locations.max

  locations = locations.map do |location|
    ((location - min_location) * (1.0 / (max_location - min_location))).round(3)
  end
end
clean_transparency_gradient(steps) click to toggle source
# File lib/gradient/grd.rb, line 102
        def clean_transparency_gradient(steps)
  locations = clean_gradient(steps)
  transparencies = steps.map do |step|
    convert_to_opacity(step)
  end
  locations.zip(transparencies)
end
continue!(steps=4) click to toggle source
# File lib/gradient/grd.rb, line 141
        def continue!(steps=4)
  @offset += steps
end
convert_to_color(color_data) click to toggle source
# File lib/gradient/grd.rb, line 114
        def convert_to_color(color_data)
  case format = color_data["palette"]
  when "CMYC" then Color::CMYK.from_percent(*color_data.values_at("Cyn", "Mgnt", "Ylw", "Blck").map(&:round)).to_rgb
  when "RGBC" then Color::RGB.new(*color_data.values_at("Rd", "Grn", "Bl").map(&:round))
  when "HSBC"
    h = color_data.fetch("H")
    s = color_data.fetch("Strt") / 100.0
    l = color_data.fetch("Brgh") / 100.0
    Color::HSL.from_fraction(h, s, l).to_rgb
  else
    raise NotImplementedError.new("The color #{format} is not supported")
  end
end
convert_to_opacity(opacity_data) click to toggle source
# File lib/gradient/grd.rb, line 110
        def convert_to_opacity(opacity_data)
  opacity_data["Opct"]
end
current_float_slice() click to toggle source

Unpack 8 bytes IEEE 754 value to floating point number

# File lib/gradient/grd.rb, line 129
        def current_float_slice
  @buffer.slice(@offset, 8).unpack("G").first
end
current_slice(length=4) click to toggle source
# File lib/gradient/grd.rb, line 137
        def current_slice(length=4)
  @buffer.slice(@offset, length)
end
current_slice_length() click to toggle source
# File lib/gradient/grd.rb, line 133
        def current_slice_length
  current_slice.unpack("L>").first
end
downshift!() click to toggle source
# File lib/gradient/grd.rb, line 149
        def downshift!
  @shift -= 4
end
flush_current_color() click to toggle source
# File lib/gradient/grd.rb, line 192
        def flush_current_color
  @current_color_gradient << @current_color if @current_color.any?
  @current_color = {}
end
flush_current_gradient() click to toggle source
# File lib/gradient/grd.rb, line 183
        def flush_current_gradient
  flush_current_color
  flush_current_transparency
  @color_gradients << @current_color_gradient if @current_color_gradient.any?
  @transparency_gradients << @current_transparency_gradient if @current_transparency_gradient.any?
  @current_color_gradient = []
  @current_transparency_gradient = []
end
flush_current_transparency() click to toggle source
# File lib/gradient/grd.rb, line 197
        def flush_current_transparency
  @current_transparency_gradient << @current_transparency if @current_transparency.any?
  @current_transparency = {}
end
log(name, type, *args) click to toggle source
# File lib/gradient/grd.rb, line 153
        def log(name, type, *args)
  puts "#{Array.new(@shift, " ").join}#{name}(#{type}) #{ Array(args).map(&:to_s).reject(&:empty?).join(", ") }" if ENV["ENABLE_LOG"]
end
parse_bool(name) click to toggle source
# File lib/gradient/grd.rb, line 305
        def parse_bool(name)
  value = @buffer.slice(@offset, 1).ord
  log(name, "bool", value)
  continue!(1)
end
parse_desc(name) click to toggle source
# File lib/gradient/grd.rb, line 207
        def parse_desc(name)
  size = current_slice_length
  log(name, "desc", size)
  continue!(26)
end
parse_doub(name) click to toggle source
# File lib/gradient/grd.rb, line 326
        def parse_doub(name)
  value = current_float_slice
  log(name, "doub", value)

  if @current_object_name == "Clr" && COLOR_TERMS.include?(name.strip)
    @current_color[name.strip] = value
  end

  continue!(8)
end
parse_entry() click to toggle source
# File lib/gradient/grd.rb, line 165
        def parse_entry
  length = current_slice_length
  length = 4 if length.zero?
  length = 4 if length > 256

  rollback = @offset

  continue!

  name = current_slice
  continue!(length)

  type = current_slice
  continue!

  send_parse_method(type, name, rollback)
end
parse_enum(name) click to toggle source
# File lib/gradient/grd.rb, line 337
        def parse_enum(name)
  size_a = current_slice_length
  continue!
  size_a = 4 if size_a.zero?
  name_a = current_slice(size_a)
  continue!(size_a)

  size_b = current_slice_length
  continue!
  size_b = 4 if size_b.zero?
  name_b = current_slice(size_b)
  continue!(size_b)

  log(name, "enum", name_a, name_b)
end
parse_long(name) click to toggle source
# File lib/gradient/grd.rb, line 311
        def parse_long(name)
  size = current_slice_length
  log(name, "long", size)

  if @current_object_name == "Clr" && name == "Lctn"
    @current_color[name.strip] = size
  end

  if @current_object_name == "Trns" && name == "Lctn"
    @current_transparency[name.strip] = size
  end

  continue!
end
parse_objc(name) click to toggle source
# File lib/gradient/grd.rb, line 255
        def parse_objc(name)
  object_name_length = current_slice_length
  continue!

  object_name = current_slice(object_name_length * 2).strip
  continue!(object_name_length * 2)

  object_type_length = current_slice_length
  object_type_length = 4 if object_type_length.zero?
  continue!

  object_type = current_slice(object_type_length).strip
  continue!(object_type_length)

  object_size = current_slice_length
  continue!

  @current_object_name = name.strip
  log(@current_object_name, "objc", object_size, object_type, object_name)

  case @current_object_name
  when "Grad"
    flush_current_gradient
  when "Clr"
    flush_current_color
    @current_color = { "palette" => object_type }
  end

  upshift!
  object_size.times { parse_entry if @offset < @buffer.length }
  downshift!
end
parse_patt(name) click to toggle source
# File lib/gradient/grd.rb, line 202
        def parse_patt(name)
  # TODO: Figure out exactly what this is and implement it.
  log(name, "patt")
end
parse_tdta(name) click to toggle source
# File lib/gradient/grd.rb, line 353
        def parse_tdta(name)
  size = current_slice_length
  continue!
  string = current_slice(size)
  continue!(size)
  log(name, "tdta", size, string)
end
parse_text(name) click to toggle source
# File lib/gradient/grd.rb, line 234
        def parse_text(name)
  size = current_slice_length
  characters = []

  (0..size).each_with_index do |string, idx|
    a = @offset + 4 + idx * 2 + 1
    b = @offset + 4 + idx * 2 + 2
    characters << @buffer[a...b]
  end

  text = characters.join

  log(name, "text", size, text)

  if @current_object_name == "Grad" && name.strip == "Nm"
    @gradient_names << text.strip
  end

  continue!(4 + size * 2)
end
parse_unknown(name, rollback) click to toggle source

Sometimes the offset is off by one byte. We roll back to the point before parsing an entry to try and parse it again.

# File lib/gradient/grd.rb, line 363
        def parse_unknown(name, rollback)
  @offset = rollback - 1
  parse_entry if @offset < @buffer.length
end
parse_untf(name) click to toggle source
# File lib/gradient/grd.rb, line 288
        def parse_untf(name)
  type = current_slice
  value = @buffer.slice(@offset + 4, 8).unpack("G").first
  log(name, "untf", type, value)

  if @current_object_name == "Clr" && COLOR_TERMS.include?(name.strip)
    @current_color[name.strip] = value
  end

  if @current_object_name == "Trns" && name == "Opct" && type == "#Prc"
    flush_current_transparency
    @current_transparency[name.strip] = value / 100
  end

  continue!(12)
end
parse_vlls(name) click to toggle source
# File lib/gradient/grd.rb, line 213
        def parse_vlls(name)
  size = current_slice_length
  continue!
  log(name, "vlls", size)
  upshift!

  size.times do |i|
    type = current_slice
    continue!

    begin
      if parse_method = PARSE_METHODS.fetch(type.strip, nil)
        send(parse_method, name)
      end
    rescue ArgumentError => e
    end
  end

  downshift!
end
send_parse_method(type, name, rollback) click to toggle source
# File lib/gradient/grd.rb, line 157
        def send_parse_method(type, name, rollback)
  if parse_method = PARSE_METHODS.fetch(type, nil)
    send(parse_method, name)
  else
    parse_unknown(name, rollback)
  end
end
upshift!() click to toggle source
# File lib/gradient/grd.rb, line 145
        def upshift!
  @shift += 4
end