class Origami::Graphics::ImageXObject

Public Class Methods

from_image_file(path, format = nil) click to toggle source
# File lib/origami/graphics/xobject.rb, line 657
def self.from_image_file(path, format = nil)
    if path.respond_to?(:read)
        data = fd.read
    else
        data = File.binread(File.expand_path(path))
        format ||= File.extname(path)[1..-1]
    end

    image = ImageXObject.new

    raise ArgumentError, "Missing file format" if format.nil?
    case format.downcase
    when 'jpg', 'jpeg', 'jpe', 'jif', 'jfif', 'jfi'
        image.setFilter :DCTDecode
        image.encoded_data = data

    when 'jp2','jpx','j2k','jpf','jpm','mj2'
        image.setFilter :JPXDecode
        image.encoded_data = data

    when '.b2', 'jbig', 'jbig2'
        image.setFilter :JBIG2Decode
        image.encoded_data = data
    else
        raise NotImplementedError, "Unknown file format: '#{format}'"
    end

    image
end

Public Instance Methods

to_image_file() click to toggle source

Converts an ImageXObject stream into an image file data. Output format depends on the stream encoding:

* JPEG for DCTDecode
* JPEG2000 for JPXDecode
* JBIG2 for JBIG2Decode
* PNG for everything else

Returns an array of the form [ _format_, _data_ ]
# File lib/origami/graphics/xobject.rb, line 697
def to_image_file
    encoding = self.Filter
    encoding = encoding[0] if encoding.is_a? ::Array

    case (encoding && encoding.value)
    when :DCTDecode then return [ 'jpg', self.data ]
    when :JBIG2Decode then return [ 'jbig2', self.data ]
    when :JPXDecode then return [ 'jp2', self.data ]
    end

    # Assume PNG data.

    raise InvalidColorError, "No colorspace specified" unless self.ColorSpace

    case cs = self.ColorSpace.value
    when Color::Space::DEVICE_GRAY
        color_type = 0
        components = 1
    when Color::Space::DEVICE_RGB
        color_type = 2
        components = 3
    when ::Array
        cs_type = cs[0]
        case cs_type
        when :Indexed
            color_type = 3
            components = 3
            cs_base = cs[1]
            lookup = cs[3]

        when :ICCBased
            icc_profile = cs[1]
            raise InvalidColorError,
                    "Invalid ICC Profile parameter" unless icc_profile.is_a?(Stream)

            case icc_profile.N
            when 1
                color_type = 0
                components = 1
            when 3
                color_type = 2
                components = 3
            else
                raise InvalidColorError,
                        "Invalid number of components in ICC profile: #{icc_profile.N}"
            end
        else
            raise InvalidColorError, "Unsupported color space: #{self.ColorSpace}"
        end
    else
        raise InvalidColorError, "Unsupported color space: #{self.ColorSpace}"
    end

    bpc = self.BitsPerComponent || 8
    w, h = self.Width, self.Height
    pixels = self.data

    hdr = [137, 80, 78, 71, 13, 10, 26, 10].pack('C*')
    chunks = []

    chunks <<
    [
        'IHDR',
        [
            w, h,
            bpc, color_type, 0, 0, 0
        ].pack("N2C5")
    ]


    if self.Intents
        intents =
            case self.Intents.value
            when Intents::PERCEPTUAL then 0
            when Intents::RELATIVE then 1
            when Intents::SATURATION then 2
            when Intents::ABSOLUTE then 3
            else
                3
            end

        chunks <<
        [
            'sRGB',
            [ intents ].pack('C')
        ]

        chunks << [ 'gAMA', [ 45455 ].pack("N") ]
        chunks <<
        [
            'cHRM',
            [
                31270,
                32900,
                64000,
                33000,
                30000,
                60000,
                15000,
                6000
            ].pack("N8")
        ]
    end

    if color_type == 3
        lookup =
            case lookup
            when Stream then lookup.data
            when String then lookup.value
            else
                raise InvalidColorError, "Invalid indexed palette table"
            end

        raise InvalidColorError, "Invalid base color space" unless cs_base
        palette = ""

        case cs_base
        when Color::Space::DEVICE_GRAY
            lookup.each_byte do |g|
                palette << Color.gray_to_rgb(g).pack("C3")
            end
        when Color::Space::DEVICE_RGB
            palette << lookup[0, (lookup.size / 3) * 3]

        when Color::Space::DEVICE_CMYK
            (lookup.size / 4).times do |i|
                cmyk = lookup[i * 4, 4].unpack("C4").map!{|c| c.to_f / 255}
                palette << Color.cmyk_to_rgb(*cmyk).map!{|c| (c * 255).to_i}.pack("C3")
            end
        when ::Array

            case cs_base[0]
            when :ICCBased
                icc_profile = cs_base[1]
                raise InvalidColorError,
                        "Invalid ICC Profile parameter" unless icc_profile.is_a?(Stream)

                case icc_profile.N
                when 1
                    lookup.each_byte do |g|
                        palette << Color.gray_to_rgb(g).pack("C3")
                    end
                when 3
                    palette << lookup[0, (lookup.size / 3) * 3]
                else
                    raise InvalidColorError,
                            "Invalid number of components in ICC profile: #{icc_profile.N}"
                end
            else
                raise InvalidColorError, "Unsupported color space: #{cs_base}"
            end
        else
            raise InvalidColorError, "Unsupported color space: #{cs_base}"
        end

        if icc_profile
            chunks <<
            [
                'iCCP',
                'ICC Profile' + "\x00\x00" + Zlib::Deflate.deflate(icc_profile.data, Zlib::BEST_COMPRESSION)
            ]
        end

        chunks <<
        [
            'PLTE',
            palette
        ]

        bpr = w

    else # color_type != 3
        if icc_profile
            chunks <<
            [
                'iCCP',
                'ICC Profile' + "\x00\x00" + Zlib::Deflate.deflate(icc_profile.data, Zlib::BEST_COMPRESSION)
            ]
        end

        bpr = (bpc >> 3) * components * w
    end

    nrows = pixels.size / bpr
    nrows.times do |irow|
        pixels.insert(irow * bpr + irow, "\x00")
    end

    chunks <<
    [
        'IDAT',
         Zlib::Deflate.deflate(pixels, Zlib::BEST_COMPRESSION)
    ]

    if self.Metadata.is_a?(Stream)
        chunks <<
        [
            'tEXt',
            "XML:com.adobe.xmp" + "\x00" + self.Metadata.data
        ]
    end

    chunks << [ 'IEND', '' ]

    [ 'png',
        hdr + chunks.map!{ |chk|
            [ chk[1].size, chk[0], chk[1], Zlib.crc32(chk[0] + chk[1]) ].pack("NA4A*N")
        }.join
    ]
end