Module: Tessel::BMP

Defined in:
lib/tessel.rb

Class Method Summary collapse

Class Method Details

.decode(bytes, max_pixels: 16384 * 16384) ⇒ Object



764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
# File 'lib/tessel.rb', line 764

def decode(bytes, max_pixels: 16384 * 16384)
  bytes = String(bytes).b
  raise DecodeError, "invalid BMP" unless bytes.start_with?("BM") && bytes.bytesize >= 54
  offset = bytes.byteslice(10, 4).unpack1("L<")
  header_size = bytes.byteslice(14, 4).unpack1("L<")
  width, height, planes, depth, compression = bytes.byteslice(18, 16).unpack("l<l<S<S<L<")
  raise UnsupportedError, "unsupported BMP" unless header_size >= 40 && planes == 1 && [24, 32].include?(depth) && compression.zero?
  top_down = height.negative?
  height = height.abs
  raise DecodeError, "invalid BMP dimensions" unless width.positive? && height.positive?
  raise LimitError, "image exceeds pixel limit" if width * height > max_pixels
  row_size = ((width * depth + 31) / 32) * 4
  raise DecodeError, "truncated BMP pixel data" if offset < 54 || offset + row_size * height > bytes.bytesize
  output = "".b
  height.times do |row|
    source_y = top_down ? row : height - row - 1
    data = bytes.byteslice(offset + source_y * row_size, width * depth / 8)
    data.bytes.each_slice(depth / 8) { |b, g, r, a| output << [r, g, b, depth == 32 ? a : 255].pack("C4") }
  end
  Image.from_rgba(width, height, output)
rescue StandardError => e
  raise e if e.is_a?(Tessel::Error) || e.is_a?(ArgumentError)
  raise DecodeError, "invalid BMP: #{e.message}"
end

.encode(image) ⇒ Object



753
754
755
756
757
758
759
760
761
762
# File 'lib/tessel.rb', line 753

def encode(image)
  row_size = (image.width * 4 + 3) & ~3
  pixel_size = row_size * image.height
  header = "BM".b + [54 + pixel_size, 0, 0, 54].pack("L<S<S<L<")
  info = [40, image.width, image.height, 1, 32, 0, pixel_size, 2835, 2835, 0, 0].pack("L<l<l<S<S<L<L<l<l<L<L<")
  pixels = (image.height - 1).downto(0).map do |y|
    image.instance_variable_get(:@data).byteslice(y * image.width * 4, image.width * 4).bytes.each_slice(4).map { |r, g, b, a| [b, g, r, a] }.flatten.pack("C*")
  end.join
  header + info + pixels
end