Home > Net >  Get "Vips::Error Exception: VipsJpeg: out of order read at line 1440" in ActiveStorage cus
Get "Vips::Error Exception: VipsJpeg: out of order read at line 1440" in ActiveStorage cus

Time:06-28

My custom analyzer

class BlurhashAnalyzer < ActiveStorage::Analyzer::ImageAnalyzer::Vips
  def metadata
    read_image do |image|
      if rotated_image?(image)
        { width: image.height, height: image.width }
      else
        { width: image.width, height: image.height }
      end.merge blurhash(image)
    end
  end

  private

  def blurhash(vips_image)
    # Create a thumbnail first, otherwise the Blurhash encoding is very slow
    byebug
    processed_image = ImageProcessing::Vips.source(vips_image).resize_and_pad(200, 200).call
    thumbnail = ::Vips::Image.new_from_file processed_image.path

    {
      blurhash: Blurhash.encode(
        thumbnail.width,
        thumbnail.height,
        ::Vips::Region.new(thumbnail).fetch(0, 0, thumbnail.width, thumbnail.height).unpack('C*')
      )
    }
  rescue StandardError => e
    raise e if Rails.env.development?
    Rails.logger.error "Error while encoding Blurhash: #{e}"
    {}
  end
end

exception in blurhash method

(process:29640): VIPS-WARNING **: 17:25:01.323: error in tile 0 x 120
*** Vips::Error Exception: VipsJpeg: out of order read at line 1440

But if I create a new Vips::Image with the same file, it works:

(byebug) ImageProcessing::Vips.source(::Vips::Image.new_from_file vips_image.filename).resize_and_pad(200, 200).call
#<Tempfile:/var/folders/_j/m395qb5d2yscnx89dswmrxgm0000gn/T/image_processing20220624-29640-aflgbs.jpg>

(byebug) ImageProcessing::Vips.source(::Vips::Image.new_from_file vips_image.filename, access: :sequential).resize_and_pad(200, 200).call
#<Tempfile:/var/folders/_j/m395qb5d2yscnx89dswmrxgm0000gn/T/image_processing20220624-29640-eflx0v.jpg>

I checked Rails 7.0.3 Analyzer::ImageAnalyzer::Vips source code:

...
    def read_image
        download_blob_to_tempfile do |file|
          require "ruby-vips"

          image = instrument("vips") do
            ::Vips::Image.new_from_file(file.path, access: :sequential)
          end

it's the same way to create Vips::Image as I did above, but if I use it directly will get the exception.

I know this issue is related to https://github.com/libvips/pyvips/issues/96, but I did not rotate here.

CodePudding user response:

This happens when you open an image in streaming mode but then try to read from it more than once.

There's a chapter in the docs with some background:

https://www.libvips.org/API/current/How-it-opens-files.md.html

For example, if you process a file like this:

image = Vips::Image.new_from_file "something.jpg", access: :sequential
image = 255 - image
image.write_to_file "something-inverted.jpg"

libvips will delay all computation until the final write_to_file, and then stream the image. The decode, process and reencode will all execute at the same time and in parallel, and it will only keep a small part of the image in memory.

The downside is that you can only do one-shot processing. This will fail for example:

image = Vips::Image.new_from_file "something.jpg", access: :sequential
image = 255 - image
image.write_to_file "something-inverted.jpg"
avg = image.avg()

Since you can't compute the average after the pipeline has executed because the image has been read, processed and disposed, and there are no pixels left.

If you use the default random-access mode, it works fine:

image = Vips::Image.new_from_file "something.jpg"
image = 255 - image
image.write_to_file "something-inverted.jpg"
avg = image.avg()

Now the JPG file will be decoded to a memory array, only the process and save will run in parallel, and the pixels will still be there to compute the average later.

In your case, the image has been opened in sequential mode but you are trying to read the pixels twice. You need to either open the original in random acceess mode, or you need to do image = image.copy_memory() to make a copy in memory that you can reuse.

  • Related