Module: Applitools::Utils::ImageDeltaCompressor

Extended by:
ImageDeltaCompressor
Included in:
ImageDeltaCompressor
Defined in:
lib/applitools/utils/image_delta_compressor.rb

Defined Under Namespace

Classes: CompareAndCopyBlockChannelDataResult, Dimension

Constant Summary collapse

BLOCK_SIZE =
10

Instance Method Summary collapse

Instance Method Details

#compress_by_raw_blocks(target, target_encoded, source, block_size = BLOCK_SIZE) ⇒ Object

Compresses the target image based on the source image.

target

ChunkyPNG::Canvas The image to compress based on the source image.

target_encoded

Array The uncompressed image as binary string.

source

ChunkyPNG::Canvas The source image used as a base for compressing the target image.

block_size

Integer The width/height of each block.

Returns String The binary result (either the compressed image, or the uncompressed image if the compression is greater in length).



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/applitools/utils/image_delta_compressor.rb', line 18

def compress_by_raw_blocks(target, target_encoded, source, block_size = BLOCK_SIZE)
  # If we can't compress for any reason, return the target image as is.
  if source.nil? || (source.height != target.height) || (source.width != target.width)
    # Returning a COPY of the target binary string.
    return String.new(target_encoded)
  end

  # Preparing the variables we need.
  target_pixels = target.to_rgb_stream.unpack('C*')
  source_pixels = source.to_rgb_stream.unpack('C*')
  image_size = Dimension.new(target.width, target.height)
  block_columns_count = (target.width / block_size) + ((target.width % block_size).zero? ? 0 : 1)
  block_rows_count = (target.height / block_size) + ((target.height % block_size).zero? ? 0 : 1)

  # IMPORTANT: The "-Zlib::MAX_WBITS" tells ZLib to create raw deflate compression, without the
  # "Zlib headers" (this isn't documented in the Zlib page, I found this in some internet forum).
  compressor = Zlib::Deflate.new(Zlib::BEST_COMPRESSION, -Zlib::MAX_WBITS)

  compression_result = ''

  # Writing the data header.
  compression_result += PREAMBLE.encode('UTF-8')
  compression_result += [FORMAT_RAW_BLOCKS].pack('C')
  compression_result += [0].pack('S>') # Source id, Big Endian
  compression_result += [block_size].pack('S>') # Big Endian

  # We perform the compression for each channel.
  3.times do |channel|
    block_number = 0
    block_rows_count.times do |block_row|
      block_columns_count.times do |block_column|
        actual_channel_index = 2 - channel # Since the image bytes are BGR and the server expects RGB...
        compare_result = compare_and_copy_block_channel_data(source_pixels, target_pixels, image_size, 3,
          block_size, block_column, block_row, actual_channel_index)

        unless compare_result.identical
          channel_bytes = compare_result.channel_bytes
          string_to_compress = [channel].pack('C')
          string_to_compress += [block_number].pack('L>')
          string_to_compress += channel_bytes.pack('C*')

          compression_result += compressor.deflate(string_to_compress)

          # If the compressed data so far is greater than the uncompressed representation of the target, just return
          # the target.
          if compression_result.length > target_encoded.length
            compressor.finish
            compressor.close
            # Returning a copy of the target bytes.
            return String.new(target_encoded)
          end
        end

        block_number += 1
      end
    end
  end

  # Compress and flush any remaining uncompressed data in the input buffer.
  compression_result += compressor.finish
  compressor.close

  # Returning the compressed result as a byte array.
  compression_result
end