Module: ChunkyPNG::Color

Extended by:
Color
Included in:
Color
Defined in:
lib/chunky_png/color.rb

Overview

The Color module defines methods for handling colors. Within the ChunkyPNG library, the concepts of pixels and colors are both used, and they are both represented by a Integer.

Pixels/colors are represented in RGBA componetns. Each of the four components is stored with a depth of 8 bits (maximum value = 255 = MAX). Together, these components are stored in a 4-byte Integer.

A color will always be represented using these 4 components in memory. When the image is encoded, a more suitable representation can be used (e.g. rgb, grayscale, palette-based), for which several conversion methods are provided in this module.

Constant Summary collapse

MAX =

The maximum value of each color component.

0xff
BLACK =

Black pixel/color

rgb(  0,   0,   0)
WHITE =

White pixel/color

rgb(255, 255, 255)
TRANSPARENT =

Fully transparent pixel/color

rgba(0, 0, 0, 0)

Instance Method Summary collapse

Instance Method Details

#a(value) ⇒ Integer

Returns the alpha channel value for the color value.



121
122
123
# File 'lib/chunky_png/color.rb', line 121

def a(value)
  value & 0x000000ff
end

#alpha_decomposable?(color, mask, bg, tolerance = 1) ⇒ Integer

Checks whether an alpha channel value can successfully be composed given the resulting color, the mask color and a background color, all of which should be opaque.

See Also:



273
274
275
276
277
278
# File 'lib/chunky_png/color.rb', line 273

def alpha_decomposable?(color, mask, bg, tolerance = 1)
  components = decompose_alpha_components(color, mask, bg)
  sum = components.inject(0) { |a,b| a + b } 
  max = components.max * 3
  return components.max <= 255 && components.min >= 0 && (sum + tolerance * 3) >= max
end

#b(value) ⇒ Integer

Returns the blue-component from the color value.



113
114
115
# File 'lib/chunky_png/color.rb', line 113

def b(value)
  (value & 0x0000ff00) >> 8
end

#blend(fg, bg) ⇒ Integer

Blends the foreground and background color by taking the average of the components.



228
229
230
# File 'lib/chunky_png/color.rb', line 228

def blend(fg, bg)
  (fg + bg) >> 1
end

#compose_precise(fg, bg) ⇒ Integer

Composes two colors with an alpha channel using floating point math.

This method uses more precise floating point math, but this precision is lost when the result is converted back to an integer. Because it is slower than the version based on integer math, that version is preferred.

See Also:



205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/chunky_png/color.rb', line 205

def compose_precise(fg, bg)
  return fg if opaque?(fg) || fully_transparent?(bg)
  return bg if fully_transparent?(fg)
  
  fg_a  = a(fg).to_f / MAX
  bg_a  = a(bg).to_f / MAX
  a_com = (1.0 - fg_a) * bg_a

  new_r = (fg_a * r(fg) + a_com * r(bg)).round
  new_g = (fg_a * g(fg) + a_com * g(bg)).round
  new_b = (fg_a * b(fg) + a_com * b(bg)).round
  new_a = ((fg_a + a_com) * MAX).round
  rgba(new_r, new_g, new_b, new_a)
end

#compose_quick(fg, bg) ⇒ Integer Also known as: compose

Composes two colors with an alpha channel using integer math.

This version is faster than the version based on floating point math, so this compositing function is used by default.

See Also:



183
184
185
186
187
188
189
190
191
192
193
# File 'lib/chunky_png/color.rb', line 183

def compose_quick(fg, bg)
  return fg if opaque?(fg) || fully_transparent?(bg)
  return bg if fully_transparent?(fg)
  
  a_com = int8_mult(0xff - a(fg), a(bg))
  new_r = int8_mult(a(fg), r(fg)) + int8_mult(a_com, r(bg))
  new_g = int8_mult(a(fg), g(fg)) + int8_mult(a_com, g(bg))
  new_b = int8_mult(a(fg), b(fg)) + int8_mult(a_com, b(bg))
  new_a = a(fg) + a_com
  rgba(new_r, new_g, new_b, new_a)
end

#decompose_alpha(color, mask, bg) ⇒ Integer

Decomposes the alpha channel value given the resulting color, the mask color and a background color, all of which should be opaque.

Make sure to call #alpha_decomposable? first to see if the alpha channel value can successfully decomposed with a given tolerance, otherwise the return value of this method is undefined.



292
293
294
295
# File 'lib/chunky_png/color.rb', line 292

def decompose_alpha(color, mask, bg)
  components = decompose_alpha_components(color, mask, bg)
  (components.inject(0) { |a,b| a + b } / 3.0).round
end

#decompose_alpha_component(channel, color, mask, bg) ⇒ Object

Decomposes an alpha channel for either the r, g or b color channel.



303
304
305
306
# File 'lib/chunky_png/color.rb', line 303

def decompose_alpha_component(channel, color, mask, bg)
  ((send(channel, bg) - send(channel, color)).to_f / 
      (send(channel, bg) - send(channel, mask)).to_f * MAX).round
end

#decompose_alpha_components(color, mask, bg) ⇒ Array<Integer>

Decomposes the alpha channels for the r, g and b color channel.



313
314
315
316
317
318
319
# File 'lib/chunky_png/color.rb', line 313

def decompose_alpha_components(color, mask, bg)
  [
    decompose_alpha_component(:r, color, mask, bg),
    decompose_alpha_component(:g, color, mask, bg),
    decompose_alpha_component(:b, color, mask, bg)
  ]
end

#decompose_color(color, mask, bg, tolerance = 1) ⇒ Integer

Decomposes a color, given a color, a mask color and a background color. The returned color will be a variant of the mask color, with the alpha channel set to the best fitting value. This basically is the reverse operation if alpha composition.

If the color cannot be decomposed, this method will return the fully transparentvariant of the mask color.



255
256
257
258
259
260
261
# File 'lib/chunky_png/color.rb', line 255

def decompose_color(color, mask, bg, tolerance = 1)
  if alpha_decomposable?(color, mask, bg, tolerance)
    mask & 0xffffff00 | decompose_alpha(color, mask, bg)
  else
    mask & 0xffffff00
  end
end

#fade(color, factor) ⇒ Integer

Lowers the intensity of a color, by lowering its alpha by a given factor.



236
237
238
239
# File 'lib/chunky_png/color.rb', line 236

def fade(color, factor)
  new_alpha = int8_mult(a(color), factor)
  (color & 0xffffff00) | new_alpha
end

#from_hex(str) ⇒ Object

Creates a color by converting it from a string in hex notation.

It supports colors with (#rrggbbaa) or without (#rrggbb) alpha channel. Color strings may include the prefix "0x" or "#".

converted color value.



81
82
83
84
85
86
87
# File 'lib/chunky_png/color.rb', line 81

def from_hex(str)
  case str
    when /^(?:#|0x)?([0-9a-f]{6})$/i; ($1.hex << 8) | 0xff
    when /^(?:#|0x)?([0-9a-f]{8})$/i; $1.hex
    else raise ChunkyPNG::ExpectationFailed, "Not a valid hex color notation: #{str.inspect}!"
  end
end

#from_rgb_stream(stream, pos = 0) ⇒ Integer

Creates a color by unpacking an rgb triple from a string.



60
61
62
# File 'lib/chunky_png/color.rb', line 60

def from_rgb_stream(stream, pos = 0)
  rgb(*stream.unpack("@#{pos}C3"))
end

#from_rgba_stream(stream, pos = 0) ⇒ Integer

Creates a color by unpacking an rgba triple from a string



70
71
72
# File 'lib/chunky_png/color.rb', line 70

def from_rgba_stream(stream, pos = 0)
  rgba(*stream.unpack("@#{pos}C4"))
end

#fully_transparent?(value) ⇒ true, false

Returns true if this color is fully transparent.



152
153
154
# File 'lib/chunky_png/color.rb', line 152

def fully_transparent?(value)
  a(value) == 0x00000000
end

#g(value) ⇒ Integer

Returns the green-component from the color value.



105
106
107
# File 'lib/chunky_png/color.rb', line 105

def g(value)
  (value & 0x00ff0000) >> 16
end

#grayscale(teint) ⇒ ChunkyPNG::Color

Creates a new color using a grayscale teint.



40
41
42
# File 'lib/chunky_png/color.rb', line 40

def grayscale(teint)
  teint << 24 | teint << 16 | teint << 8 | 0xff
end

#grayscale?(value) ⇒ true, false

Returns true if this color is fully transparent.



144
145
146
# File 'lib/chunky_png/color.rb', line 144

def grayscale?(value)
  r(value) == b(value) && b(value) == g(value)
end

#grayscale_alpha(teint, a) ⇒ Integer

Creates a new color using a grayscale teint and alpha value.



46
47
48
# File 'lib/chunky_png/color.rb', line 46

def grayscale_alpha(teint, a)
  teint << 24 | teint << 16 | teint << 8 | a
end

#int8_mult(a, b) ⇒ Integer

Multiplies two fractions using integer math, where the fractions are stored using an integer between 0 and 255. This method is used as a helper method for compositing colors using integer math.

This is a quicker implementation of ((a * b) / 255.0).round.



169
170
171
172
# File 'lib/chunky_png/color.rb', line 169

def int8_mult(a, b)
  t = a * b + 0x80
  ((t >> 8) + t) >> 8
end

#opaque!(value) ⇒ Integer

Returns the opaque value of this color by removing the alpha channel.



136
137
138
# File 'lib/chunky_png/color.rb', line 136

def opaque!(value)
  value | 0x000000ff
end

#opaque?(value) ⇒ true, false

Returns true if this color is fully opaque.



129
130
131
# File 'lib/chunky_png/color.rb', line 129

def opaque?(value)
  a(value) == 0x000000ff
end

#pass_bytesize(color_mode, depth, width, height) ⇒ Integer

Returns the number of bytes used for an image pass



434
435
436
437
# File 'lib/chunky_png/color.rb', line 434

def pass_bytesize(color_mode, depth, width, height)
  return 0 if width == 0 || height == 0
  (scanline_bytesize(color_mode, depth, width) + 1) * height
end

#pixel_bitsize(color_mode, depth = 8) ⇒ Integer

Returns the size in bits of a pixel when it is stored using a given color mode.



415
416
417
# File 'lib/chunky_png/color.rb', line 415

def pixel_bitsize(color_mode, depth = 8)
  samples_per_pixel(color_mode) * depth
end

#pixel_bytesize(color_mode, depth = 8) ⇒ Integer

Returns the size in bytes of a pixel when it is stored using a given color mode.



406
407
408
409
# File 'lib/chunky_png/color.rb', line 406

def pixel_bytesize(color_mode, depth = 8)
  return 1 if depth < 8
  (pixel_bitsize(color_mode, depth) + 7) >> 3
end

#r(value) ⇒ Integer

Returns the red-component from the color value.



97
98
99
# File 'lib/chunky_png/color.rb', line 97

def r(value)
  (value & 0xff000000) >> 24
end

#rgb(r, g, b) ⇒ Integer

Creates a new color using an r, g, b triple.



34
35
36
# File 'lib/chunky_png/color.rb', line 34

def rgb(r, g, b)
  r << 24 | g << 16 | b << 8 | 0xff
end

#rgba(r, g, b, a) ⇒ Integer

Creates a new color using an r, g, b triple and an alpha value.



28
29
30
# File 'lib/chunky_png/color.rb', line 28

def rgba(r, g, b, a)
  r << 24 | g << 16 | b << 8 | a
end

#samples_per_pixel(color_mode) ⇒ Integer

Returns the number of sample values per pixel.



392
393
394
395
396
397
398
399
400
401
# File 'lib/chunky_png/color.rb', line 392

def samples_per_pixel(color_mode)
  case color_mode
    when ChunkyPNG::COLOR_INDEXED;         1
    when ChunkyPNG::COLOR_TRUECOLOR;       3
    when ChunkyPNG::COLOR_TRUECOLOR_ALPHA; 4
    when ChunkyPNG::COLOR_GRAYSCALE;       1
    when ChunkyPNG::COLOR_GRAYSCALE_ALPHA; 2
    else raise ChunkyPNG::NotSupported, "Don't know the numer of samples for this colormode: #{color_mode}!"
  end
end

#scanline_bytesize(color_mode, depth, width) ⇒ Integer

Returns the number of bytes used per scanline.



424
425
426
# File 'lib/chunky_png/color.rb', line 424

def scanline_bytesize(color_mode, depth, width)
  ((pixel_bitsize(color_mode, depth) * width) + 7) >> 3
end

#to_grayscale_alpha_bytes(color) ⇒ Array<Integer>

Returns an array with the grayscale teint and alpha channel values for this color.

This method expects the r,g and b value to be equal.



368
369
370
# File 'lib/chunky_png/color.rb', line 368

def to_grayscale_alpha_bytes(color)
  [b(color), a(color)] # assumption r == g == b
end

#to_grayscale_bytes(color) ⇒ Array<Integer>

Returns an array with the grayscale teint value for this color.

This method expects the r,g and b value to be equal, and the alpha channel will be discarded.



357
358
359
# File 'lib/chunky_png/color.rb', line 357

def to_grayscale_bytes(color)
  [b(color)] # assumption r == g == b
end

#to_hex(color, include_alpha = true) ⇒ String

Returns a string representing this color using hex notation (i.e. #rrggbbaa).



329
330
331
# File 'lib/chunky_png/color.rb', line 329

def to_hex(color, include_alpha = true)
  include_alpha ? ('#%08x' % color) : ('#%06x' % [color >> 8])
end

#to_truecolor_alpha_bytes(color) ⇒ Array<Integer>

Returns an array with the separate RGBA values for this color.



337
338
339
# File 'lib/chunky_png/color.rb', line 337

def to_truecolor_alpha_bytes(color)
  [r(color), g(color), b(color), a(color)]
end

#to_truecolor_bytes(color) ⇒ Array<Integer>

Returns an array with the separate RGB values for this color. The alpha channel will be discarded.



346
347
348
# File 'lib/chunky_png/color.rb', line 346

def to_truecolor_bytes(color)
  [r(color), g(color), b(color)]
end