module Origami::Filter::Predictor

Constants

NONE
PNG_AVERAGE
PNG_NONE
PNG_OPTIMUM
PNG_PAETH
PNG_SUB
PNG_UP
TIFF

Public Class Methods

included(receiver) click to toggle source
# File lib/origami/filters/predictors.rb, line 48
def self.included(receiver)
    raise TypeError, "Predictors only applies to Filters" unless receiver.include?(Filter)
end
new(parameters = {}) click to toggle source

Create a new predictive Filter.

parameters

A hash of filter options.

Calls superclass method
# File lib/origami/filters/predictors.rb, line 56
def initialize(parameters = {})
    super(DecodeParms.new(parameters))
end

Private Instance Methods

apply_post_prediction(data, predictor: NONE, colors: 1, bpc: 8, columns: 1) click to toggle source
# File lib/origami/filters/predictors.rb, line 102
def apply_post_prediction(data, predictor: NONE, colors: 1, bpc: 8, columns: 1)
    return data if predictor == NONE

    bpp, bpr = compute_bpp_bpr(data, columns, colors, bpc)

    if predictor == TIFF
        tiff_post_prediction(data, colors, bpc, columns)
    elsif predictor >= 10 # PNG
        # Each line has an extra predictor byte.
        png_post_prediction(data, bpp, bpr + 1)
    else
        raise PredictorError.new("Unknown predictor : #{predictor}", input_data: data)
    end
end
apply_pre_prediction(data, predictor: NONE, colors: 1, bpc: 8, columns: 1) click to toggle source
# File lib/origami/filters/predictors.rb, line 83
def apply_pre_prediction(data, predictor: NONE, colors: 1, bpc: 8, columns: 1)
    return data if predictor == NONE

    bpp, bpr = compute_bpp_bpr(data, columns, colors, bpc)

    unless data.size % bpr == 0
        raise PredictorError.new("Invalid data size #{data.size}, should be multiple of bpr=#{bpr}",
                                 input_data: data)
    end

    if predictor == TIFF
        tiff_pre_prediction(data, colors, bpc, columns)
    elsif predictor >= 10 # PNG
        png_pre_prediction(data, predictor, bpp, bpr)
    else
        raise PredictorError.new("Unknown predictor : #{predictor}", input_data: data)
    end
end
compute_bpp_bpr(data, columns, colors, bpc) click to toggle source

Computes the number of bytes per pixel and number of bytes per row.

# File lib/origami/filters/predictors.rb, line 120
def compute_bpp_bpr(data, columns, colors, bpc)
    unless (1..4) === colors
        raise PredictorError.new("Colors must be between 1 and 4", input_data: data)
    end

    unless [1,2,4,8,16].include?(bpc)
        raise PredictorError.new("BitsPerComponent must be in 1, 2, 4, 8 or 16", input_data: data)
    end

    # components per line
    nvals = columns * colors

    # bytes per pixel
    bpp = (colors * bpc + 7) >> 3

    # bytes per row
    bpr = (nvals * bpc + 7) >> 3

    [ bpp, bpr ]
end
png_apply_prediction(predictor, value, up, left, upleft) { |value, left| ... } click to toggle source

Computes the next component value given a predictor and adjacent components. A block must be passed to apply the operation.

# File lib/origami/filters/predictors.rb, line 222
def png_apply_prediction(predictor, value, up, left, upleft)

    result =
        case predictor
        when PNG_NONE
            value
        when PNG_SUB
            yield(value, left)
        when PNG_UP
            yield(value, up)
        when PNG_AVERAGE
            yield(value, (left + up) / 2)
        when PNG_PAETH
            yield(value, png_paeth_choose(up, left, upleft))
        else
            raise PredictorError, "Unsupported PNG predictor : #{predictor}"
        end

    (result & 0xFF).chr
end
png_paeth_choose(left, up, upleft) click to toggle source

Choose the preferred value in a PNG paeth predictor given the left, up and up left samples.

# File lib/origami/filters/predictors.rb, line 246
def png_paeth_choose(left, up, upleft)
    p = left + up - upleft
    pa, pb, pc = (p - left).abs, (p - up).abs, (p - upleft).abs

    case [pa, pb, pc].min
    when pa then left
    when pb then up
    when pc then upleft
    end
end
png_post_prediction(data, bpp, bpr) click to toggle source

Decodes the PNG input data. Each line should be prepended by a byte identifying a PNG predictor.

# File lib/origami/filters/predictors.rb, line 145
def png_post_prediction(data, bpp, bpr)
    result = ""
    uprow = "\0" * bpr
    thisrow = "\0" * bpr
    nrows = (data.size + bpr - 1) / bpr

    nrows.times do |irow|
        line = data[irow * bpr, bpr]
        predictor = 10 + line[0].ord
        line[0] = "\0"

        for i in (1..line.size-1)
            up = uprow[i].ord

            if bpp > i
                left = upleft = 0
            else
                left = line[i - bpp].ord
                upleft = uprow[i - bpp].ord
            end

            begin
                thisrow[i] = png_apply_prediction(predictor, line[i].ord, up, left, upleft, &:+)
            rescue PredictorError => error
                thisrow[i] = line[i] if Origami::OPTIONS[:ignore_png_errors]

                error.input_data = data
                error.decoded_data = result
                raise(error)
            end
        end

        result << thisrow[1..-1]
        uprow = thisrow
    end

    result
end
png_pre_prediction(data, predictor, bpp, bpr) click to toggle source

Encodes the input data given a PNG predictor.

# File lib/origami/filters/predictors.rb, line 187
def png_pre_prediction(data, predictor, bpp, bpr)
    result = ""
    nrows = data.size / bpr

    line = "\0" + data[-bpr, bpr]

    (nrows - 1).downto(0) do |irow|
        uprow =
        if irow == 0
            "\0" * (bpr + 1)
        else
            "\0" + data[(irow - 1) * bpr, bpr]
        end

        bpr.downto(1) do |i|
            up = uprow[i].ord
            left = line[i - bpp].ord
            upleft = uprow[i - bpp].ord

            line[i] = png_apply_prediction(predictor, line[i].ord, up, left, upleft, &:-)
        end

        line[0] = (predictor - 10).chr
        result = line + result

        line = uprow
    end

    result
end
post_prediction(data) click to toggle source
# File lib/origami/filters/predictors.rb, line 68
def post_prediction(data)
    return data unless @params.Predictor.is_a?(Integer)

    apply_post_prediction(data, prediction_parameters)
end
pre_prediction(data) click to toggle source
# File lib/origami/filters/predictors.rb, line 62
def pre_prediction(data)
    return data unless @params.Predictor.is_a?(Integer)

    apply_pre_prediction(data, prediction_parameters)
end
prediction_parameters() click to toggle source
# File lib/origami/filters/predictors.rb, line 74
def prediction_parameters
    {
        predictor:  @params.Predictor.to_i,
        colors:     @params.Colors.is_a?(Integer) ? @params.Colors.to_i : 1,
        bpc:        @params.BitsPerComponent.is_a?(Integer) ? @params.BitsPerComponent.to_i : 8,
        columns:    @params.Columns.is_a?(Integer) ? @params.Columns.to_i : 1,
    }
end