# 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= 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= 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