lib/color/cmyk.rb
# An CMYK colour object. CMYK (cyan, magenta, yellow, and black) colours are
# based on additive percentages of ink. A CMYK colour of (0.3, 0, 0.8, 0.3)
# would be mixed from 30% cyan, 0% magenta, 80% yellow, and 30% black.
# Primarily used in four-colour printing processes.
class Color::CMYK
include Color
# The format of a DeviceCMYK colour for PDF. In color-tools 2.0 this will
# be removed from this package and added back as a modification by the
# PDF::Writer package.
PDF_FORMAT_STR = "%.3f %.3f %.3f %.3f %s"
# Coerces the other Color object into CMYK.
def coerce(other)
other.to_cmyk
end
class << self
# Creates a CMYK colour object from fractional values 0..1.
#
# Color::CMYK.from_fraction(0.3, 0, 0.8, 0.3)
def from_fraction(c = 0, m = 0, y = 0, k = 0, &block)
new(c, m, y, k, 1.0, &block)
end
# Creates a CMYK colour object from percentages. Internally, the colour is
# managed as fractional values 0..1.
#
# Color::CMYK.new(30, 0, 80, 30)
def from_percent(c = 0, m = 0, y = 0, k = 0, &block)
new(c, m, y, k, &block)
end
end
# Creates a CMYK colour object from percentages. Internally, the colour is
# managed as fractional values 0..1.
#
# Color::CMYK.new(30, 0, 80, 30)
def initialize(c = 0, m = 0, y = 0, k = 0, radix = 100.0, &block) # :yields self:
@c, @m, @y, @k = [ c, m, y, k ].map { |v| Color.normalize(v / radix) }
block.call(self) if block
end
# Present the colour as a DeviceCMYK fill colour string for PDF. This will
# be removed from the default package in color-tools 2.0.
def pdf_fill
PDF_FORMAT_STR % [ @c, @m, @y, @k, "k" ]
end
# Present the colour as a DeviceCMYK stroke colour string for PDF. This
# will be removed from the default package in color-tools 2.0.
def pdf_stroke
PDF_FORMAT_STR % [ @c, @m, @y, @k, "K" ]
end
# Present the colour as an RGB HTML/CSS colour string (e.g., "#aabbcc").
# Note that this will perform a #to_rgb operation using the default
# conversion formula.
def html
to_rgb.html
end
# Present the colour as an RGB HTML/CSS colour string (e.g., "rgb(0%, 50%,
# 100%)"). Note that this will perform a #to_rgb operation using the
# default conversion formula.
def css_rgb
to_rgb.css_rgb
end
# Present the colour as an RGBA (with alpha) HTML/CSS colour string (e.g.,
# "rgb(0%, 50%, 100%, 1)"). Note that this will perform a #to_rgb
# operation using the default conversion formula.
def css_rgba(alpha = 1)
to_rgb.css_rgba(alpha)
end
# Present the colour as an HSL HTML/CSS colour string (e.g., "hsl(180,
# 25%, 35%)"). Note that this will perform a #to_hsl operation using the
# default conversion formula.
def css_hsl
to_hsl.css_hsl
end
# Present the colour as an HSLA (with alpha) HTML/CSS colour string (e.g.,
# "hsla(180, 25%, 35%, 1)"). Note that this will perform a #to_hsl
# operation using the default conversion formula.
def css_hsla
to_hsl.css_hsla
end
# Converts the CMYK colour to RGB. Most colour experts strongly suggest
# that this is not a good idea (some even suggesting that it's a very bad
# idea). CMYK represents additive percentages of inks on white paper,
# whereas RGB represents mixed colour intensities on a black screen.
#
# However, the colour conversion can be done, and there are two different
# methods for the conversion that provide slightly different results.
# Adobe PDF conversions are done with the first form.
#
# # Adobe PDF Display Formula
# r = 1.0 - min(1.0, c + k)
# g = 1.0 - min(1.0, m + k)
# b = 1.0 - min(1.0, y + k)
#
# # Other
# r = 1.0 - (c * (1.0 - k) + k)
# g = 1.0 - (m * (1.0 - k) + k)
# b = 1.0 - (y * (1.0 - k) + k)
#
# If we have a CMYK colour of [33% 66% 83% 25%], the first method will
# give an approximate RGB colour of (107, 23, 0) or #6b1700. The second
# method will give an approximate RGB colour of (128, 65, 33) or #804121.
# Which is correct? Although the colours may seem to be drastically
# different in the RGB colour space, they are very similar colours,
# differing mostly in intensity. The first is a darker, slightly redder
# brown; the second is a lighter brown.
#
# Because of this subtlety, both methods are now offered for conversion.
# The Adobe method is not used by default; to enable it, pass +true+ to
# #to_rgb.
#
# Future versions of Color may offer other conversion mechanisms that
# offer greater colour fidelity, including recognition of ICC colour
# profiles.
def to_rgb(use_adobe_method = false)
if use_adobe_method
Color::RGB.from_fraction(*adobe_cmyk_rgb)
else
Color::RGB.from_fraction(*standard_cmyk_rgb)
end
end
# Converts the CMYK colour to a single greyscale value. There are
# undoubtedly multiple methods for this conversion, but only a minor
# variant of the Adobe conversion method will be used:
#
# g = 1.0 - min(1.0, 0.299 * c + 0.587 * m + 0.114 * y + k)
#
# This treats the CMY values similarly to YIQ (NTSC) values and then adds
# the level of black. This is a variant of the Adobe version because it
# uses the more precise YIQ (NTSC) conversion values for Y (intensity)
# rather than the approximates provided by Adobe (0.3, 0.59, and 0.11).
def to_grayscale
c = 0.299 * @c.to_f
m = 0.587 * @m.to_f
y = 0.114 * @y.to_f
g = 1.0 - [1.0, c + m + y + @k].min
Color::GrayScale.from_fraction(g)
end
alias to_greyscale to_grayscale
def to_cmyk
self
end
def inspect
"CMYK [%.2f%%, %.2f%%, %.2f%%, %.2f%%]" % [ cyan, magenta, yellow, black ]
end
# Converts to RGB then YIQ.
def to_yiq
to_rgb.to_yiq
end
# Converts to RGB then HSL.
def to_hsl
to_rgb.to_hsl
end
# Returns the cyan (C) component of the CMYK colour as a percentage value.
def cyan
@c * 100.0
end
# Returns the cyan (C) component of the CMYK colour as a value in the
# range 0.0 .. 1.0.
def c
@c
end
# Sets the cyan (C) component of the CMYK colour as a percentage value.
def cyan=(cc)
@c = Color.normalize(cc / 100.0)
end
# Sets the cyan (C) component of the CMYK colour as a value in the range
# 0.0 .. 1.0.
def c=(cc)
@c = Color.normalize(cc)
end
# Returns the magenta (M) component of the CMYK colour as a percentage
# value.
def magenta
@m * 100.0
end
# Returns the magenta (M) component of the CMYK colour as a value in the
# range 0.0 .. 1.0.
def m
@m
end
# Sets the magenta (M) component of the CMYK colour as a percentage value.
def magenta=(mm)
@m = Color.normalize(mm / 100.0)
end
# Sets the magenta (M) component of the CMYK colour as a value in the
# range 0.0 .. 1.0.
def m=(mm)
@m = Color.normalize(mm)
end
# Returns the yellow (Y) component of the CMYK colour as a percentage
# value.
def yellow
@y * 100.0
end
# Returns the yellow (Y) component of the CMYK colour as a value in the
# range 0.0 .. 1.0.
def y
@y
end
# Sets the yellow (Y) component of the CMYK colour as a percentage value.
def yellow=(yy)
@y = Color.normalize(yy / 100.0)
end
# Sets the yellow (Y) component of the CMYK colour as a value in the range
# 0.0 .. 1.0.
def y=(kk)
@y = Color.normalize(kk)
end
# Returns the black (K) component of the CMYK colour as a percentage
# value.
def black
@k * 100.0
end
# Returns the black (K) component of the CMYK colour as a value in the
# range 0.0 .. 1.0.
def k
@k
end
# Sets the black (K) component of the CMYK colour as a percentage value.
def black=(kk)
@k = Color.normalize(kk / 100.0)
end
# Sets the black (K) component of the CMYK colour as a value in the range
# 0.0 .. 1.0.
def k=(kk)
@k = Color.normalize(kk)
end
def to_a
[ c, m, y, k ]
end
private
# Implements the Adobe PDF conversion of CMYK to RGB.
def adobe_cmyk_rgb
[ @c, @m, @y ].map { |v| 1.0 - [ 1.0, v + @k ].min }
end
# Implements the standard conversion of CMYK to RGB.
def standard_cmyk_rgb
[ @c, @m, @y ].map { |v| 1.0 - (v * (1.0 - k) + k) }
end
end