Skip to content

The little big difference between Vips::Image#thumbnail_image and Vips::Image::thumbnail #393

@Nakilon

Description

@Nakilon

The issue

I am comparing web page screenshots for testing purposes. The workflow is like this:

  1. obtain the screenshot blob from chromium and load it via Image.new_from_buffer
  2. assert the similarity with the reference.jpg asset and if it's significant, fail the test
  3. if in fact there was no error, update the asset, manually rewriting it with the saved blob

The issue is that even after I renew the asset file it fails again (with the perceptual difference of 11), BUT if I save the current blob to another file and then load it back, the perceptual difference is zero!

Why? Because of some unexpected (for me and for practical reason, because it looks stupid to fix it via save/load) difference that exists between Vips::Image#thumbnail_image and Vips::Image::thumbnail here:

      image = if input.is_a? Vips::Image
        input.thumbnail_image(size, height: size, size: :force)
      else
        Vips::Image.thumbnail(input, size, height: size, size: :force)
      end

The load-from-buffer feature was wanted by users two, times, and you would not notice the issue if all your fingerprinting was either from Image or from buffer, but in my workflow I need to compare practically the same blob loaded in both ways, and that's where the difference occures.

How to observe it?

vk_video.jpg.zip

blob = File.binread "vk_video.jpg"
puts Vips::Image.new_from_buffer(blob, "").thumbnail_image(8, height: 8, size: :force).
  colourspace("b-w")[0].to_a.map{ |_| _.map{ |_| "%3d" % _ }.join }
 47 42 47 50 47 45 46 47
  4  5  3  3  4  4  4  4
  0  0  0  0  0  0  0  0
  1  1  1  2  5  1  1  1
  0  0  0  0  0  0  0  0
103102 97101 99 99102111
248243243247251251250254
246238239254253253253252
puts Vips::Image.thumbnail("vk_video.jpg", 8, height: 8, size: :force).
  colourspace("b-w")[0].to_a.map{ |_| _.map{ |_| "%3d" % _ }.join }
 45 42 45 47 46 44 45 46
  4  4  4  3  3  4  4  4
  0  0  0  0  1  0  0  0
  4  4  4  5  8  4  4  4
  0  0  0  0  0  0  0  0
104102 99103 98 99101111
250247248250255255254254
245239241253253253253252

Or use this debug script:

require "dhash-vips"

ha = DHashVips::IDHash.fingerprint Vips::Image.new_from_buffer File.binread("vk_video.jpg"), ""
hb = DHashVips::IDHash.fingerprint "vk_video.jpg"

puts "distance: #{d1 = DHashVips::IDHash.distance ha, hb}"
size = 2 ** 3
shift = 2 * size * size
ai = ha >> shift
ad = ha - (ai << shift)
bi = hb >> shift
bd = hb - (bi << shift)

_127 = shift - 1
_63 = size * size - 1

d2 = 0
a, b = [[ad, ai], [bd, bi]].map do |xd, xi|
  puts ""
  hor = Array.new(size){Array.new(size){" "}}
  ver = Array.new(size){Array.new(size){" "}}
  _127.downto(0).each_with_index do |i, ii|
    if i > _63
      y, x = (_127 - i).divmod size
    else
      x, y = (_63 - i).divmod size
    end
    if xi[i] > 0
      target, c = if i > _63
        [ver, %w{ v ^ }[xd[i]]]
      else
        [hor, %w{ > < }[xd[i]]]
      end
      target[y][x] = c
    end
    if ai[i] + bi[i] > 0 && ad[i] != bd[i]
      d2 += 1
      target = if i > _63
        ver
      else
        hor
      end
      target[y][x] = "\e[7m#{target[y][x]}\e[27m"
    end
  end
  hor.zip(ver).each{ |_| puts _.join " " }
end
image

Expected behavior

The difference to be ≤5.

Screenshots

vk_video
temp1
temp2

Environment:

  • vips-8.11.3-Wed Aug 11 09:29:27 UTC 2021
  • ruby-vips 2.2.1

I could rework the fingerprinting algorithm specifically for such case (the case of non-photorealistic images with huge blank lines) by calculating the "significance median" not two times (for vertical and horizontal half of fingerprinting) but once for both added, and maybe I will do it but not soon. I just think you might want to double-check if there is something unexpected happening in libvips.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions