lib/chunky_png/pixel.rb in chunky_png-0.0.3 vs lib/chunky_png/pixel.rb in chunky_png-0.0.4

- old
+ new

@@ -1,89 +1,171 @@ module ChunkyPNG + # The Pixel class represents a pixel, which has a single color. Within the + # ChunkyPNG library, the concepts of pixels and colors are both used, and + # they are both represented by the pixel class. + # + # Pixels/colors are represented in RGBA moduds. Each of the four components + # is stored with a depth of 8 biths (maximum value = 255). Together, these + # components are stored in a 4-bye Fixnum. class Pixel - + + # @return [Fixnum] The 4-byte fixnum representation of the pixel's + # color, where red compenent uses the most significant byte and the + # alpha component the least significant byte. attr_reader :value + alias :to_i :value + + # Initalizes a new pixel instance. Usually, it is more convenient to + # use one of the constructors below. + # @param [Fixnum] value The 4-byte fixnum representation of the pixel's + # color, where red compenent uses the most significant byte and the + # alpha component the least significant byte. def initialize(value) - @value = value + @value = value.to_i end + + #################################################################### + # PIXEL LOADING + #################################################################### - def self.rgb(r, g, b) - rgba(r, g, b, 255) + # Creates a new pixels using an r, g, b triple. + # @return [ChunkyPNG::Pixel] The newly constructed pixel. + def self.rgb(r, g, b, a = 255) + rgba(r, g, b, a) end + # Creates a new pixels using an r, g, b triple and an alpha value. + # @return [ChunkyPNG::Pixel] The newly constructed pixel. def self.rgba(r, g, b, a) - new(r << 24 | g << 16 | b << 8 | a) + self.new(r << 24 | g << 16 | b << 8 | a) end + # Creates a new pixels using a grayscale teint. + # @return [ChunkyPNG::Pixel] The newly constructed pixel. def self.grayscale(teint, a = 255) rgba(teint, teint, teint, a) end + + # Creates a new pixels using a grayscale teint and alpha value. + # @return [ChunkyPNG::Pixel] The newly constructed pixel. + def self.grayscale_alpha(teint, a) + rgba(teint, teint, teint, a) + end + # Creates a pixel by unpacking an rgb triple from a string + # @return [ChunkyPNG::Pixel] The newly constructed pixel. def self.from_rgb_stream(stream) self.rgb(*stream.unpack('C3')) end + # Creates a pixel by unpacking an rgba triple from a string + # @return [ChunkyPNG::Pixel] The newly constructed pixel. def self.from_rgba_stream(stream) self.rgba(*stream.unpack('C4')) end + #################################################################### + # COLOR CONSTANTS + #################################################################### + + BLACK = rgb( 0, 0, 0) + WHITE = rgb(255, 255, 255) + + TRANSPARENT = rgba(0, 0, 0, 0) + + #################################################################### + # PROPERTIES + #################################################################### + + # Returns the red-component from the pixel value + # @return [Fixnum] A value between 0 and 255 def r (@value & 0xff000000) >> 24 end + # Returns the green-component from the pixel value + # @return [Fixnum] A value between 0 and 255 def g (@value & 0x00ff0000) >> 16 end + # Returns the blue-component from the pixel value + # @return [Fixnum] A value between 0 and 255 def b (@value & 0x0000ff00) >> 8 end + # Returns the alpha channel value for the pixel + # @return [Fixnum] A value between 0 and 255 def a @value & 0x000000ff end + # Returns true if this pixel is fully opaque + # @return [true, false] True if the alpha channel equals 255 def opaque? a == 0x000000ff end + # Returns true if this pixel is fully transparent + # @return [true, false] True if the alpha channel equals 0 + def fully_transparent? + a == 0x000000ff + end + + # Returns true if this pixel is fully transparent + # @return [true, false] True if the r, g and b component are equal def grayscale? r == g && r == b end + #################################################################### + # COMPARISON + #################################################################### + + # Returns a nice string representation for this pixel using hex notation + # @return [String] def inspect '#%08x' % @value end + # Returns a hash for determining the uniqueness of a pixel + # @return [Fixnum] The hash of the fixnum that is representing this pixel. def hash @value.hash end - + + # Checks whether to pixels are the same (i.e. have the same color) + # @param [Object] The object to compare this pixel with + # @return [true, false] Returns true iff th pixels' fixnum representations is the same def eql?(other) other.kind_of?(self.class) && other.value == self.value end alias :== :eql? + # Compares to pixels for ordering, using the pixel's fixnum representation. + # @param [Object] The object to compare this pixel with. + # @return [Fixnum] A number used for sorting. def <=>(other) other.value <=> self.value end + #################################################################### + # CONVERSIONS + #################################################################### + def to_truecolor_alpha_bytes [r,g,b,a] end def to_truecolor_bytes [r,g,b] end - def index(palette) - palette.index(self) - end - def to_indexed_bytes(palette) [index(palette)] end def to_grayscale_bytes @@ -92,14 +174,91 @@ def to_grayscale_alpha_bytes [r, a] # Assumption: r == g == b end - BLACK = rgb( 0, 0, 0) - WHITE = rgb(255, 255, 255) + #################################################################### + # ALPHA COMPOSITION + #################################################################### - TRANSPARENT = rgba(0, 0, 0, 0) + # 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 + # pixels when using integer math. + # + # @param [Fixnum] a The first fraction. + # @param [Fixnum] b The second fraction. + # @return [Fixnum] The result of the multiplication. + def int8_mult(a, b) + t = a * b + 0x80 + ((t >> 8) + t) >> 8 + end + + # Composes two pixels with an alpha channel using integer math. + # + # The pixel instance is used as background color, the pixel provided as +other+ + # parameter is used as foreground pixel in the composition formula. + # + # This version is faster than the version based on floating point math, so this + # compositing function is used by default. + # + # @param [ChunkyPNG::Pixel] other The foreground pixel to compose with + # @return [ChunkyPNG::Pixel] The composited pixel + # @see ChunkyPNG::Pixel#compose_precise + def compose_quick(other) + if other.a == 0xff + other + elsif other.a == 0x00 + self + else + a_com = int8_mult(0xff - other.a, a) + new_r = int8_mult(other.a, other.r) + int8_mult(a_com, r) + new_g = int8_mult(other.a, other.g) + int8_mult(a_com, g) + new_b = int8_mult(other.a, other.b) + int8_mult(a_com, b) + new_a = other.a + a_com + ChunkyPNG::Pixel.rgba(new_r, new_g, new_b, new_a) + end + end + + # Composes two pixels with an alpha channel using floating point math. + # + # The pixel instance is used as background color, the pixel provided as +other+ + # parameter is used as foreground pixel in the composition formula. + # + # 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. + # + # @param [ChunkyPNG::Pixel] other The foreground pixel to compose with + # @return [ChunkyPNG::Pixel] The composited pixel + # @see ChunkyPNG::Pixel#compose_quick + def compose_precise(other) + if other.a == 255 + other + elsif other.a == 0 + self + else + alpha = other.a / 255.0 + other_alpha = a / 255.0 + alpha_com = (1.0 - alpha) * other_alpha + + new_r = (alpha * other.r + alpha_com * r).round + new_g = (alpha * other.g + alpha_com * g).round + new_b = (alpha * other.b + alpha_com * b).round + new_a = ((alpha + alpha_com) * 255).round + ChunkyPNG::Pixel.rgba(new_r, new_g, new_b, new_a) + end + end + alias :compose :compose_quick + alias :& :compose + + #################################################################### + # STATIC UTILITY METHODS + #################################################################### + + # Returns the size in bytes of a pixel whe it is stored using a given color mode + # @param [Fixnum] color_mode The color mode in which the pixels are stored + # @return [Fixnum] The number of bytes used per pixel in a datastream. def self.bytesize(color_mode) case color_mode when ChunkyPNG::COLOR_INDEXED then 1 when ChunkyPNG::COLOR_TRUECOLOR then 3 when ChunkyPNG::COLOR_TRUECOLOR_ALPHA then 4