Class: Rbimg::PNG::Chunk

Inherits:
Object
  • Object
show all
Defined in:
lib/image_types/png.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(type:, data:) ⇒ Chunk

Returns a new instance of Chunk.

Raises:

  • (ArgumentError)


494
495
496
497
498
499
500
501
502
503
504
505
506
507
# 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

Instance Attribute Details

#dataObject (readonly)

Returns the value of attribute data.



492
493
494
# File 'lib/image_types/png.rb', line 492

def data
  @data
end

#lengthObject (readonly)

Returns the value of attribute length.



492
493
494
# File 'lib/image_types/png.rb', line 492

def length
  @length
end

#typeObject (readonly)

Returns the value of attribute type.



492
493
494
# File 'lib/image_types/png.rb', line 492

def type
  @type
end

Class Method Details

.crc_valid?(type:, data:, crc:) ⇒ Boolean

Returns:

  • (Boolean)


378
379
380
381
# 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

.IDATs(pixels, color_type:, bit_depth:, width:, height:, idat_size: 2 ** 20) ⇒ Object

Raises:

  • (ArgumentError)


417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
# 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

.IENDObject



487
488
489
# 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) ⇒ Object

Raises:

  • (ArgumentError)


383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
# 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) ⇒ Object

Raises:

  • (ArgumentError)


410
411
412
413
414
415
# 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

.readChunk(bytes) ⇒ Object

Raises:



342
343
344
345
346
347
348
349
350
351
# 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) ⇒ Object



368
369
370
371
372
# 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) ⇒ Object

Raises:

  • (ArgumentError)


353
354
355
356
357
358
359
360
361
362
363
364
365
366
# 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) ⇒ Object



374
375
376
# File 'lib/image_types/png.rb', line 374

def self.readPLTE(bytes)
    readChunk(bytes)
end

Instance Method Details

#bytesObject



513
514
515
# File 'lib/image_types/png.rb', line 513

def bytes
    data.pack("C*")
end