mirror of https://github.com/docusealco/docuseal
				
				
				
			
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							210 lines
						
					
					
						
							6.7 KiB
						
					
					
				
			
		
		
	
	
							210 lines
						
					
					
						
							6.7 KiB
						
					
					
				# frozen_string_literal: true
 | 
						|
 | 
						|
module LoadIco
 | 
						|
  BI_RGB = 0
 | 
						|
 | 
						|
  module_function
 | 
						|
 | 
						|
  # rubocop:disable Metrics
 | 
						|
  def call(ico_bytes)
 | 
						|
    io = StringIO.new(ico_bytes)
 | 
						|
    _reserved, type, count = io.read(6)&.unpack('S<S<S<')
 | 
						|
 | 
						|
    raise ArgumentError, 'Unable to load' unless type == 1 && count&.positive?
 | 
						|
 | 
						|
    ico_entries_parsed = []
 | 
						|
 | 
						|
    count.times do
 | 
						|
      entry_bytes = io.read(16)
 | 
						|
 | 
						|
      raise ArgumentError, 'Unable to load' unless entry_bytes && entry_bytes.bytesize == 16
 | 
						|
 | 
						|
      width_byte, height_byte, _num_colors_palette, _rsvd_entry, _planes_icon_entry, bpp_icon_entry,
 | 
						|
      img_data_size, img_data_offset = entry_bytes.unpack('CCCCS<S<L<L<')
 | 
						|
 | 
						|
      width = width_byte.zero? ? 256 : width_byte
 | 
						|
      height = height_byte.zero? ? 256 : height_byte
 | 
						|
      sort_bpp = bpp_icon_entry.zero? ? 32 : bpp_icon_entry
 | 
						|
 | 
						|
      ico_entries_parsed << {
 | 
						|
        width: width, height: height,
 | 
						|
        sort_bpp: sort_bpp,
 | 
						|
        size: img_data_size, offset: img_data_offset
 | 
						|
      }
 | 
						|
    end
 | 
						|
 | 
						|
    best_entry = ico_entries_parsed.min_by { |e| [-e[:width] * e[:height], -e[:sort_bpp]] }
 | 
						|
 | 
						|
    raise ArgumentError, 'Unable to load' unless best_entry
 | 
						|
 | 
						|
    io.seek(best_entry[:offset])
 | 
						|
    image_data_bytes = io.read(best_entry[:size])
 | 
						|
 | 
						|
    raise ArgumentError, 'Unable to load' unless image_data_bytes && image_data_bytes.bytesize == best_entry[:size]
 | 
						|
 | 
						|
    image = load_image_entry(image_data_bytes, best_entry[:width], best_entry[:height])
 | 
						|
 | 
						|
    raise ArgumentError, 'Unable to load' unless image
 | 
						|
 | 
						|
    image
 | 
						|
  end
 | 
						|
 | 
						|
  def load_image_entry(image_data_bytes, ico_entry_width, ico_entry_height)
 | 
						|
    dib_io = StringIO.new(image_data_bytes)
 | 
						|
 | 
						|
    dib_header_size_arr = dib_io.read(4)&.unpack('L<')
 | 
						|
    return nil unless dib_header_size_arr
 | 
						|
 | 
						|
    dib_header_size = dib_header_size_arr.first
 | 
						|
    return nil unless dib_header_size && dib_header_size >= 40
 | 
						|
 | 
						|
    dib_params_bytes = dib_io.read(36)
 | 
						|
    return nil unless dib_params_bytes && dib_params_bytes.bytesize == 36
 | 
						|
 | 
						|
    dib_width, dib_actual_height_field, dib_planes, dib_bpp,
 | 
						|
    dib_compression, _dib_image_size, _xpels, _ypels,
 | 
						|
    dib_clr_used, _dib_clr_important = dib_params_bytes.unpack('l<l<S<S<L<L<l<l<L<L<')
 | 
						|
 | 
						|
    return nil unless dib_width && dib_actual_height_field && dib_planes && dib_bpp && dib_compression && dib_clr_used
 | 
						|
    return nil unless dib_width == ico_entry_width
 | 
						|
 | 
						|
    image_pixel_height = ico_entry_height
 | 
						|
 | 
						|
    expected_dib_height_no_mask = image_pixel_height
 | 
						|
    expected_dib_height_with_mask = image_pixel_height * 2
 | 
						|
    actual_dib_pixel_rows_abs = dib_actual_height_field.abs
 | 
						|
 | 
						|
    unless actual_dib_pixel_rows_abs == expected_dib_height_no_mask ||
 | 
						|
           actual_dib_pixel_rows_abs == expected_dib_height_with_mask
 | 
						|
      return nil
 | 
						|
    end
 | 
						|
 | 
						|
    return nil unless dib_planes == 1
 | 
						|
    return nil unless dib_compression == BI_RGB
 | 
						|
    return nil unless [1, 4, 8, 24, 32].include?(dib_bpp)
 | 
						|
 | 
						|
    has_and_mask = (actual_dib_pixel_rows_abs == expected_dib_height_with_mask) && (dib_bpp < 32)
 | 
						|
 | 
						|
    dib_io.seek(dib_header_size, IO::SEEK_SET)
 | 
						|
 | 
						|
    palette = []
 | 
						|
    if dib_bpp <= 8
 | 
						|
      num_palette_entries = dib_clr_used.zero? ? (1 << dib_bpp) : dib_clr_used
 | 
						|
      num_palette_entries.times do
 | 
						|
        palette_color_bytes = dib_io.read(4)
 | 
						|
        return nil unless palette_color_bytes && palette_color_bytes.bytesize == 4
 | 
						|
 | 
						|
        b, g, r, _a_reserved = palette_color_bytes.unpack('CCCC')
 | 
						|
        palette << [r, g, b, 255]
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    xor_mask_data_offset = dib_io.pos
 | 
						|
    xor_scanline_stride = (((dib_width * dib_bpp) + 31) / 32) * 4
 | 
						|
 | 
						|
    and_mask_data_offset = 0
 | 
						|
    and_scanline_stride = 0
 | 
						|
    if has_and_mask
 | 
						|
      and_mask_data_offset = xor_mask_data_offset + (image_pixel_height * xor_scanline_stride)
 | 
						|
      and_scanline_stride = (((dib_width * 1) + 31) / 32) * 4
 | 
						|
    end
 | 
						|
 | 
						|
    flat_rgba_pixels = []
 | 
						|
 | 
						|
    (0...image_pixel_height).each do |y_row|
 | 
						|
      y_dib_row = image_pixel_height - 1 - y_row
 | 
						|
 | 
						|
      dib_io.seek(xor_mask_data_offset + (y_dib_row * xor_scanline_stride))
 | 
						|
      xor_scanline_bytes = dib_io.read(xor_scanline_stride)
 | 
						|
      min_xor_bytes_needed = ((dib_width * dib_bpp) + 7) / 8
 | 
						|
      return nil unless xor_scanline_bytes && xor_scanline_bytes.bytesize >= min_xor_bytes_needed
 | 
						|
 | 
						|
      and_mask_bits_for_row = []
 | 
						|
      if has_and_mask
 | 
						|
        dib_io.seek(and_mask_data_offset + (y_dib_row * and_scanline_stride))
 | 
						|
        and_mask_scanline_bytes = dib_io.read(and_scanline_stride)
 | 
						|
        min_and_bytes_needed = ((dib_width * 1) + 7) / 8
 | 
						|
        return nil unless and_mask_scanline_bytes && and_mask_scanline_bytes.bytesize >= min_and_bytes_needed
 | 
						|
 | 
						|
        (0...dib_width).each do |x_pixel|
 | 
						|
          byte_index = x_pixel / 8
 | 
						|
          bit_index_in_byte = 7 - (x_pixel % 8)
 | 
						|
          byte_val = and_mask_scanline_bytes.getbyte(byte_index)
 | 
						|
          and_mask_bits_for_row << ((byte_val >> bit_index_in_byte) & 1)
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      (0...dib_width).each do |x_pixel|
 | 
						|
        r = 0
 | 
						|
        g = 0
 | 
						|
        b = 0
 | 
						|
        a = 255
 | 
						|
 | 
						|
        case dib_bpp
 | 
						|
        when 32
 | 
						|
          offset = x_pixel * 4
 | 
						|
          blue = xor_scanline_bytes.getbyte(offset)
 | 
						|
          green = xor_scanline_bytes.getbyte(offset + 1)
 | 
						|
          red = xor_scanline_bytes.getbyte(offset + 2)
 | 
						|
          alpha_val = xor_scanline_bytes.getbyte(offset + 3)
 | 
						|
          r = red
 | 
						|
          g = green
 | 
						|
          b = blue
 | 
						|
          a = alpha_val
 | 
						|
        when 24
 | 
						|
          offset = x_pixel * 3
 | 
						|
          blue = xor_scanline_bytes.getbyte(offset)
 | 
						|
          green = xor_scanline_bytes.getbyte(offset + 1)
 | 
						|
          red = xor_scanline_bytes.getbyte(offset + 2)
 | 
						|
          r = red
 | 
						|
          g = green
 | 
						|
          b = blue
 | 
						|
        when 8
 | 
						|
          idx = xor_scanline_bytes.getbyte(x_pixel)
 | 
						|
          r_p, g_p, b_p, a_p = palette[idx] || [0, 0, 0, 0]
 | 
						|
          r = r_p
 | 
						|
          g = g_p
 | 
						|
          b = b_p
 | 
						|
          a = a_p
 | 
						|
        when 4
 | 
						|
          byte_val = xor_scanline_bytes.getbyte(x_pixel / 2)
 | 
						|
          idx = (x_pixel.even? ? (byte_val >> 4) : (byte_val & 0x0F))
 | 
						|
          r_p, g_p, b_p, a_p = palette[idx] || [0, 0, 0, 0]
 | 
						|
          r = r_p
 | 
						|
          g = g_p
 | 
						|
          b = b_p
 | 
						|
          a = a_p
 | 
						|
        when 1
 | 
						|
          byte_val = xor_scanline_bytes.getbyte(x_pixel / 8)
 | 
						|
          idx = (byte_val >> (7 - (x_pixel % 8))) & 1
 | 
						|
          r_p, g_p, b_p, a_p = palette[idx] || [0, 0, 0, 0]
 | 
						|
          r = r_p
 | 
						|
          g = g_p
 | 
						|
          b = b_p
 | 
						|
          a = a_p
 | 
						|
        end
 | 
						|
 | 
						|
        if has_and_mask && !and_mask_bits_for_row.empty?
 | 
						|
          a = and_mask_bits_for_row[x_pixel] == 1 ? 0 : 255
 | 
						|
        end
 | 
						|
        flat_rgba_pixels.push(r, g, b, a)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    pixel_data_string = flat_rgba_pixels.pack('C*')
 | 
						|
 | 
						|
    expected_bytes = dib_width * image_pixel_height * 4
 | 
						|
 | 
						|
    return nil unless pixel_data_string.bytesize == expected_bytes && expected_bytes.positive?
 | 
						|
 | 
						|
    Vips::Image.new_from_memory(
 | 
						|
      pixel_data_string,
 | 
						|
      dib_width,
 | 
						|
      image_pixel_height,
 | 
						|
      4,
 | 
						|
      :uchar
 | 
						|
    )
 | 
						|
  end
 | 
						|
  # rubocop:enable Metrics
 | 
						|
end
 |