class Rbimg::PNG::Chunk

Attributes

data[R]
length[R]
type[R]

Public Class Methods

IDATs(pixels, color_type: ,bit_depth:, width: , height: , idat_size: 2 ** 20) click to toggle source
# File lib/image_types/png.rb, line 417
def self.IDATs(pixels, color_type: ,bit_depth:, width: , height: , idat_size: 2 ** 20)

    case color_type
    when 0
        pixel_width = width
    when 2
        pixel_width = width * 3
    when 3
        pixel_width = width 
    when 4
        pixel_width = width * 2
    when 6
        pixel_width = width * 4
    else
        raise ArgumentError.new("#{color_type} is not a valid color type. Must be 0,2,3,4, or 6")
    end
    expected_pixels = pixel_width * height
    raise ArgumentError.new("pixel count (#{pixels.length}) does not match expected pixel count (#{expected_pixels})") if pixels.length != expected_pixels
    pixel_square = Array.new(height, nil)
    pixel_square = pixel_square.map{ |_| [nil] * pixel_width}
    for i in 0...pixels.length
        row = i / pixel_width
        col = i % pixel_width
        pixel_square[row][col] = pixels[i]
    end

    scanlines = pixel_square.map do |bit_strm|

        case bit_depth
        when 1
            raise ArgumentError.new("If bit depth is 1, all pixel values must be a 1 or 0") if bit_strm.any?{ |b| b != 0 && b != 1 }
            bits = bit_strm.join('') + ("0" * ((-1 * bit_strm.length % 8) % 8 ))
            Byteman.hex(0) + Byteman.pad(num: Byteman.hex(bits.to_i(2)), len: bits.length / 8)
        when 2
            bits = bit_strm.map do |b| 
                raise ArgumentError.new("If bit depth is 2, all pixel values must be between 0 and 3") if !b.between?(0,3)
                Byteman.pad(num: b, len: 2, type: :bits)
            end.join('')
            padded_bits = bits + ("0" * ((-1 * bit_strm.length * 2) % 8) % 8)
            Byteman.hex(0) + Byteman.pad(num: Byteman.hex(padded_bits.to_i(2)), len: padded_bits.length / 8)
        when 4       
            bits = bit_strm.map do |b|
                raise ArgumentError.new("If bit depth is 4, all pixel values must be between 0 and 15") if !b.between?(0,15)
                Byteman.pad(num: b, len: 4, type: :bits)
            end.join('')
            padded_bits = bits + ("0" * ((-1 * bit_strm.length * 4) % 8) % 8)
            Byteman.hex(0) + Byteman.pad(num: Byteman.hex(padded_bits.to_i(2)), len: padded_bits.length / 8)
        when 8
            raise ArgumentError.new("If bit depth is 8, all pixel values must be between 0 and 255") if bit_strm.any?{|b| !b.between?(0,255)}
            ([0] + bit_strm).pack("C*")
        when 16
            raise ArgumentError.new("If bit depth is 16, all pixel values must be between 0 and 65535") if bit_strm.any?{|b| !b.between?(0,65535)}
            Byteman.hex(0) + bit_strm.map{|b| Byteman.pad(num: Byteman.hex(b), len: 2)}.join('')
        else
            ArgumentError.new("bit_depth can only be 1,2,4,8, or 16 bits")
        end

    end
    
    z = Zlib::Deflate.new(Zlib::BEST_COMPRESSION, Zlib::MAX_WBITS, Zlib::MAX_MEM_LEVEL, Zlib::RLE)
    zstrm = z.deflate(scanlines.join(''), Zlib::FINISH)
    z.close
    idats = []
    zstrm.bytes.each_slice(idat_size) do |dstrm|
        idats << Chunk.new(type: "IDAT", data: dstrm)
    end
    
    idats
end
IEND() click to toggle source
# File lib/image_types/png.rb, line 487
def self.IEND
    Chunk.new(type: "IEND", data: nil)
end
IHDR(width: , height: , bit_depth: , color_type: , compression_method: 0, filter_method: 0, interlace_method: 0) click to toggle source
# File lib/image_types/png.rb, line 383
def self.IHDR(width: , height: , bit_depth: , color_type: , compression_method: 0, filter_method: 0, interlace_method: 0)
    bit_depth_rules = {
        0 => [1,2,4,8,16],
        2 => [8, 16],
        3 => [1,2,4,8],
        4 => [8,16],
        6 => [8,16]
    }

    raise ArgumentError.new("Width or height cannot be 0") if width == 0 || height == 0
    test_length(width)
    test_length(height) 
    raise ArgumentError.new('Color code types must be 0, 2, 3, 4, or 6') if ![0,2,3,4,6].include?(color_type)
    raise ArgumentError.new("Bit depth must be related to color_type as such: color_value => bit_depth_options: #{bit_depth_rules}") if !bit_depth_rules[color_type].include?(bit_depth) 


    wbytes = Byteman.pad(len: 4, num: Byteman.int2buf(width))
    hbytes = Byteman.pad(len: 4, num: Byteman.int2buf(height))
    bdbyte = [bit_depth]
    ctbyte = [color_type]
    cmbyte = [compression_method]
    fmbyte = [filter_method]
    ilmbyte = [interlace_method]
    data = wbytes + hbytes + bdbyte + ctbyte + cmbyte + fmbyte + ilmbyte
    Chunk.new(type: "IHDR", data: data) 
end
PLTE(data) click to toggle source
# File lib/image_types/png.rb, line 410
def self.PLTE(data)
    data = data.bytes if data.is_a?(String)
    raise ArgumentError.new("Number of bytes must be a multiple of 3") if data.length % 3 != 0
    raise ArgumentError.new("Pallette length must be between 1 and 256") if data.length < 3 || data.length > (256 * 3)
    Chunk.new(type: "PLTE", data: data)
end
crc_valid?(type:, data:, crc:) click to toggle source
# File lib/image_types/png.rb, line 378
def self.crc_valid?(type:, data:, crc:)
    c = new(type: type, data: data)
    c.bytes[-4..-1].bytes == crc
end
new(type: ,data:) click to toggle source
# File lib/image_types/png.rb, line 494
def initialize(type: ,data:)
    raise ArgumentError.new("Type must be a string, symbol, or an array") if !type.is_a?(String) && !type.is_a?(Symbol) && !type.is_a?(Array)
    @data = data.nil? ? [] : data
    @length = Byteman.pad(len: 4, num: Byteman.int2buf(@data.length))
    self.class.test_length(@data.length)
 
    if type.is_a?(String) || type.is_a?(Symbol)
        @type = type.to_s.bytes
    else
        @type = type
    end
    @crc_strategy = Rbimg::Strategies::CRCTableLookup
    @crc = crc
end
readChunk(bytes) click to toggle source
# File lib/image_types/png.rb, line 342
def self.readChunk(bytes)
    bytes = bytes.bytes if bytes.is_a?(String)
    data = {}
    data[:length] = Byteman.buf2int(bytes[0...4])
    data[:type] = Byteman.buf2hex(bytes[4...8])
    data[:crc] = bytes[-4..-1]
    data[:chunk_data] = bytes[8...(8 + data[:length])]
    raise Rbimg::CRCError.new("CRC does not match expected") if !crc_valid?(type: data[:type].unpack("C*"), data: data[:chunk_data], crc: data[:crc])
    data
end
readIDAT(bytes) click to toggle source
# File lib/image_types/png.rb, line 368
def self.readIDAT(bytes)
    data = readChunk(bytes)
    data[:compressed_pixels] = data[:chunk_data]
    data
end
readIHDR(bytes) click to toggle source
# File lib/image_types/png.rb, line 353
def self.readIHDR(bytes)
    bytes = bytes.bytes if bytes.is_a?(String)
    raise ArgumentError.new("IHDR must be 25 bytes long") if bytes.length != 25
 
    data = readChunk(bytes)
    data[:width] = Byteman.buf2int(bytes[8...12])
    data[:height] = Byteman.buf2int(bytes[12...16])
    data[:bit_depth] = bytes[16]
    data[:color_type] = bytes[17]
    data[:compression_method] = bytes[18]
    data[:filter_method] = bytes[19]
    data[:interlace_method] = bytes[20]
    data
end
readPLTE(bytes) click to toggle source
# File lib/image_types/png.rb, line 374
def self.readPLTE(bytes)
    readChunk(bytes)
end

Private Class Methods

test_length(num, limit: 2 ** 31 - 1) click to toggle source
# File lib/image_types/png.rb, line 520
def self.test_length(num, limit: 2 ** 31 - 1)
    raise ArgumentError.new("Length cannot exceed 2^31 - 1") if num > limit
end

Public Instance Methods

bytes() click to toggle source
# File lib/image_types/png.rb, line 513
def bytes
    data.pack("C*")
end

Private Instance Methods

crc() click to toggle source
# File lib/image_types/png.rb, line 525
def crc
    @crc_strategy.crc(@type, @data)
end