RMagick problem

The following Rails/Ruby code gives me this error: "Zero-length blob
not permitted".

@magick_image = Magick::Image.from_blob(@file.read).first

Where @file is a StringIO object or a File object, depending on the
size of the uploaded file.

@file.read.size returns the expected file size (in bytes).

Any ideas?

Thanks,
Joe

Ah, good idea.

I still have no idea why it’s giving me “Zero-length blob
not permitted” for this line:
@magick_image = Magick::Image.from_blob(@file.read).first

What’s weird is that if I leave out the .first method call, I don’t
get that error. But if I print out the size of @file.read.size, and
then call the Image.from_blob line, then I get the Zero-length blob
error.

It’s driving me crazy. :frowning:

···

On 8/3/05, Sebastian Kanthak sebastian.kanthak-ZS8b95Whz3sUSW6y5lq3GQ@public.gmane.org wrote:

Joe Van Dyk wrote:

On 8/3/05, Sebastian Kanthak sebastian.kanthak-ZS8b95Whz3sUSW6y5lq3GQ@public.gmane.org wrote:

      if file.is_a?(StringIO)
           img = Magick::Image::from_blob(file.string).first
       else
           img = Magick::Image::read(file.local_path).first
       end

Also, File objects support #read, so why do you have that distinction in there?

mainly, because I’m not so sure if it is a good idea to read a (possibly
pretty big) image into main memory. I’m not sure what RMagick does, but
it could be optimized to only read the header information and process
the image in a “streaming” mode.

For a StringIO object it doesn’t really matter, as these are only
created if the upload is reasonably small, so I can use from_blob
safely, here.

Joe Van Dyk schrieb:

I still have no idea why it's giving me "Zero-length blob
not permitted" for this line:
@magick_image = Magick::Image.from_blob(@file.read).first

What's weird is that if I leave out the .first method call, I don't
get that error. But if I print out the size of @file.read.size, and
then call the Image.from_blob line, then I get the Zero-length blob
error.

It's driving me crazy. :frowning:

I hope it's not too late :slight_smile: If you call @file.read.size, shouldn't you then do a #rewind before reading again?

Regards,
Pit

Joe Van Dyk wrote:

The following Rails/Ruby code gives me this error: "Zero-length blob
not permitted".

@magick_image = Magick::Image.from_blob(@file.read).first

Where @file is a StringIO object or a File object, depending on the
size of the uploaded file.

@file.read.size returns the expected file size (in bytes).

Any ideas?

Well, it works for me. Here's my attempt to reproduce the problem.

require 'RMagick'
require 'stringio'

file = File.open('Flower_Hat.jpg')
img = Magick::Image.from_blob(file.read).first
p img

blob = IO.read('Flower_Hat.jpg')
sio = StringIO.new(blob)
img = Magick::Image.from_blob(sio.read).first
p img

The output is:
tim: ~> ruby test.rb
JPEG 200x250 DirectClass 8-bit 9kb
JPEG 200x250 DirectClass 8-bit 9kb

···

--
Tim Hunter

Hm, dunno. I'll try it when I get home. I just added the print
statement to debug it. It was failing before adding the 'puts
@file.read.size' line.

···

On 8/3/05, Pit Capitain <pit@capitain.de> wrote:

Joe Van Dyk schrieb:
> I still have no idea why it's giving me "Zero-length blob
> not permitted" for this line:
> @magick_image = Magick::Image.from_blob(@file.read).first
>
> What's weird is that if I leave out the .first method call, I don't
> get that error. But if I print out the size of @file.read.size, and
> then call the Image.from_blob line, then I get the Zero-length blob
> error.
>
> It's driving me crazy. :frowning:

I hope it's not too late :slight_smile: If you call @file.read.size, shouldn't you
then do a #rewind before reading again?

Yeah, I tried to do that test last night and it worked fine.

So I have no idea why the code is failing inside my Image model.

···

On 8/3/05, Tim Hunter <cyclists@nc.rr.com> wrote:

Joe Van Dyk wrote:

> The following Rails/Ruby code gives me this error: "Zero-length blob
> not permitted".
>
> @magick_image = Magick::Image.from_blob(@file.read).first
>
> Where @file is a StringIO object or a File object, depending on the
> size of the uploaded file.
>
> @file.read.size returns the expected file size (in bytes).
>
> Any ideas?

Well, it works for me. Here's my attempt to reproduce the problem.

require 'RMagick'
require 'stringio'

file = File.open('Flower_Hat.jpg')
img = Magick::Image.from_blob(file.read).first
p img

blob = IO.read('Flower_Hat.jpg')
sio = StringIO.new(blob)
img = Magick::Image.from_blob(sio.read).first
p img

The output is:
tim: ~> ruby test.rb
JPEG 200x250 DirectClass 8-bit 9kb
JPEG 200x250 DirectClass 8-bit 9kb

Here's my image model. Perhaps that will give someone some insight.

Say, I'm doing the Magick::Image.from_blob call in #after_save.
Perhaps something weird is happening with @file's lifetime or
something?

class Image < ActiveRecord::Base
  belongs_to :house
  belongs_to :community
  belongs_to :contractor

  FileLocation = "#{RAILS_ROOT}/public/media/"
  DisplayLocation = "/media"
  
def file=(file)
    if file.size > 0
      self.content_type = file.content_type.strip
      @file = file
    end
  end

  # Runs after Image is saved, saves the image to the file system.
  def after_save
    if @file
      @magick_image = Magick::Image.from_blob(@file.read).first
      save_full_image
      save_thumbnail_image
      save_rounded_thumbnail_image
      save_medium_image
      save_large_image
    end
  end

  # If image is already a jpeg, then just write it to a file.
  # If it's not, convert it to a jpeg and then write.
  def save_full_image
    @magick_image.write(full_file)
  end

  # Write a thumbnail to the filesystem
  def save_thumbnail_image
    thumbnailed_magick_image = resize_image_to_exact_size(180,135)
    thumbnailed_magick_image.write(thumbnail_file)
  end

  def save_rounded_thumbnail_image
    thumbnail_image = resize_image_to_exact_size(180, 135)
    color = "#000033"
    corner_width = 15
    corners = Magick::Draw.new
    corners.stroke(color)
    corners.fill_opacity(0)
    corners.stroke_width(15)
    corners.roundrectangle(0, 0,
                           thumbnail_image.columns, thumbnail_image.rows,
                           corner_width, corner_width)
    corners.draw(thumbnail_image)
    thumbnail_image.write(rounded_thumbnail_file)
  end

  # Resizes a Magick image to an exact size, keeping the same aspect ratio
  def resize_image_to_exact_size(width, height)
    new_aspect_ratio = width.to_f / height.to_f
    old_aspect_ratio = @magick_image.columns.to_f / @magick_image.rows.to_f

    if old_aspect_ratio < new_aspect_ratio
      # Image too tall, geometry string should restrict height
      geometry_string = "#{width}"
    else
      # Image too wide or just right, geometry string should
      # restrict width
      geometry_string = "x#{height}"
    end

    # Get the resized image
    resized_image = resize_image(geometry_string)

    # Return the resized, crop image
    resized_image.crop(Magick::CenterGravity, width, height)
  end

  def resize_image(geometry_string)
    logger.debug "resizing image to #{geometry_string}"
    @magick_image.change_geometry(geometry_string) do |w, h, img|
      img.resize(w, h)
    end
  end

  def save_medium_image
    medium_image = resize_image("275")
    medium_image.write(medium_file)
  end

  def save_large_image
    large_image = resize_image("500")
    large_image.write(large_file)
  end

  # Delete images from filesystem
  def after_destroy
    File.delete(full_file) if File.exist? "#{full_file}"
    File.delete(thumbnail_file) if File.exist? "#{thumbnail_file}"
    File.delete(rounded_thumbnail_file) if File.exist?
"#{rounded_thumbnail_file}"
    File.delete(medium_file) if File.exist? "#{medium_file}"
    File.delete(large_file) if File.exist? "#{large_file}"
  end

  # Returns the filesystem location of the image file
  def full_file
    "#{FileLocation}/full/#{id}.jpg"
  end
  def thumbnail_file
    "#{FileLocation}/thumbnail/#{id}.jpg"
  end
  def rounded_thumbnail_file
    "#{FileLocation}/rounded_thumbnail/#{id}.jpg"
  end
  def medium_file
    "#{FileLocation}/medium/#{id}.jpg"
  end
  def large_file
    "#{FileLocation}/large/#{id}.jpg"
  end

  # Returns the location of the image to the browser
  def full
    "#{DisplayLocation}/full/#{id}.jpg"
  end
  def thumbnail
    "#{DisplayLocation}/thumbnail/#{id}.jpg"
  end
  def medium
    "#{DisplayLocation}/medium/#{id}.jpg"
  end
  def large
    "#{DisplayLocation}/large/#{id}.jpg"
  end
  def rounded_thumbnail
    "#{DisplayLocation}/rounded_thumbnail/#{id}.jpg"
  end def resize
    if File.exist? full_file
      @file = File.open full_file
      save
    end
  end
end

···

On 8/3/05, Joe Van Dyk <joevandyk@gmail.com> wrote:

On 8/3/05, Tim Hunter <cyclists@nc.rr.com> wrote:
> Joe Van Dyk wrote:
>
> > The following Rails/Ruby code gives me this error: "Zero-length blob
> > not permitted".
> >
> > @magick_image = Magick::Image.from_blob(@file.read).first
> >
> > Where @file is a StringIO object or a File object, depending on the
> > size of the uploaded file.
> >
> > @file.read.size returns the expected file size (in bytes).
> >
> > Any ideas?
>
> Well, it works for me. Here's my attempt to reproduce the problem.
>
> require 'RMagick'
> require 'stringio'
>
> file = File.open('Flower_Hat.jpg')
> img = Magick::Image.from_blob(file.read).first
> p img
>
> blob = IO.read('Flower_Hat.jpg')
> sio = StringIO.new(blob)
> img = Magick::Image.from_blob(sio.read).first
> p img
>
>
> The output is:
> tim: ~> ruby test.rb
> JPEG 200x250 DirectClass 8-bit 9kb
> JPEG 200x250 DirectClass 8-bit 9kb

Yeah, I tried to do that test last night and it worked fine.

So I have no idea why the code is failing inside my Image model.

I’ll be damned. Moving
@magick_image = Magick::Image.from_blob(@file.read).first
from Image#after_save to Image#file= fixed the problem.

So, apparently, something weird happens to @file before
Image#after_save is called. Any ideas?

···

On 8/3/05, Joe Van Dyk joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org wrote:

On 8/3/05, Joe Van Dyk joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org wrote:

On 8/3/05, Tim Hunter cyclists-P1JKnMZDrucAvxtiuMwx3w@public.gmane.org wrote:

Joe Van Dyk wrote:

The following Rails/Ruby code gives me this error: “Zero-length blob
not permitted”.

@magick_image = Magick::Image.from_blob(@file.read).first

Where @file is a StringIO object or a File object, depending on the
size of the uploaded file.

@file.read.size returns the expected file size (in bytes).

Any ideas?

Well, it works for me. Here’s my attempt to reproduce the problem.

require ‘RMagick’
require ‘stringio’

file = File.open(‘Flower_Hat.jpg’)
img = Magick::Image.from_blob(file.read).first
p img

blob = IO.read(‘Flower_Hat.jpg’)
sio = StringIO.new(blob)
img = Magick::Image.from_blob(sio.read).first
p img

The output is:
tim: ~> ruby test.rb
JPEG 200x250 DirectClass 8-bit 9kb
JPEG 200x250 DirectClass 8-bit 9kb

Yeah, I tried to do that test last night and it worked fine.

So I have no idea why the code is failing inside my Image model.

Here’s my image model. Perhaps that will give someone some insight.

Say, I’m doing the Magick::Image.from_blob call in #after_save.
Perhaps something weird is happening with @file’s lifetime or
something?

class Image < ActiveRecord::Base
belongs_to :house
belongs_to :community
belongs_to :contractor

FileLocation = “#{RAILS_ROOT}/public/media/”
DisplayLocation = “/media”

def file=(file)
if file.size > 0
self.content_type = file.content_type.strip
@file = file
end
end

Runs after Image is saved, saves the image to the file system.

def after_save
if @file
@magick_image = Magick::Image.from_blob(@file.read).first
save_full_image
save_thumbnail_image
save_rounded_thumbnail_image
save_medium_image
save_large_image
end
end

If image is already a jpeg, then just write it to a file.

If it’s not, convert it to a jpeg and then write.

def save_full_image
@magick_image.write(full_file)
end

Write a thumbnail to the filesystem

def save_thumbnail_image
thumbnailed_magick_image = resize_image_to_exact_size(180,135)
thumbnailed_magick_image.write(thumbnail_file)
end

def save_rounded_thumbnail_image
thumbnail_image = resize_image_to_exact_size(180, 135)
color = “#000033
corner_width = 15
corners = Magick::Draw.new
corners.stroke(color)
corners.fill_opacity(0)
corners.stroke_width(15)
corners.roundrectangle(0, 0,
thumbnail_image.columns, thumbnail_image.rows,
corner_width, corner_width)
corners.draw(thumbnail_image)
thumbnail_image.write(rounded_thumbnail_file)
end

Resizes a Magick image to an exact size, keeping the same aspect ratio

def resize_image_to_exact_size(width, height)
new_aspect_ratio = width.to_f / height.to_f
old_aspect_ratio = @magick_image.columns.to_f / @magick_image.rows.to_f

if old_aspect_ratio < new_aspect_ratio
  # Image too tall, geometry string should restrict height
  geometry_string = "#{width}"
else
  # Image too wide or just right, geometry string should
  # restrict width
  geometry_string = "x#{height}"
end

# Get the resized image
resized_image = resize_image(geometry_string)

# Return the resized, crop image
resized_image.crop(Magick::CenterGravity, width, height)

end

def resize_image(geometry_string)
logger.debug “resizing image to #{geometry_string}”
@magick_image.change_geometry(geometry_string) do |w, h, img|
img.resize(w, h)
end
end

def save_medium_image
medium_image = resize_image(“275”)
medium_image.write(medium_file)
end

def save_large_image
large_image = resize_image(“500”)
large_image.write(large_file)
end

Delete images from filesystem

def after_destroy
File.delete(full_file) if File.exist? “#{full_file}”
File.delete(thumbnail_file) if File.exist? “#{thumbnail_file}”
File.delete(rounded_thumbnail_file) if File.exist?
“#{rounded_thumbnail_file}”
File.delete(medium_file) if File.exist? “#{medium_file}”
File.delete(large_file) if File.exist? “#{large_file}”
end

Returns the filesystem location of the image file

def full_file
“#{FileLocation}/full/#{id}.jpg”
end
def thumbnail_file
“#{FileLocation}/thumbnail/#{id}.jpg”
end
def rounded_thumbnail_file
“#{FileLocation}/rounded_thumbnail/#{id}.jpg”
end
def medium_file
“#{FileLocation}/medium/#{id}.jpg”
end
def large_file
“#{FileLocation}/large/#{id}.jpg”
end

Returns the location of the image to the browser

def full
“#{DisplayLocation}/full/#{id}.jpg”
end
def thumbnail
“#{DisplayLocation}/thumbnail/#{id}.jpg”
end
def medium
“#{DisplayLocation}/medium/#{id}.jpg”
end
def large
“#{DisplayLocation}/large/#{id}.jpg”
end
def rounded_thumbnail
“#{DisplayLocation}/rounded_thumbnail/#{id}.jpg”
end def resize
if File.exist? full_file
@file = File.open full_file
save
end
end
end

I’ve had this problem too, changing my setter to image_file= solved all
my problems, I suspect AR/CGI/something is using @file during the
after_save phase.

SIMEN BREKKEN / born to synthesize.

Joe Van Dyk wrote:

···

On 8/3/05, Joe Van Dyk joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org wrote:

On 8/3/05, Joe Van Dyk joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org wrote:

On 8/3/05, Tim Hunter cyclists-P1JKnMZDrucAvxtiuMwx3w@public.gmane.org wrote:

Joe Van Dyk wrote:

The following Rails/Ruby code gives me this error: “Zero-length blob
not permitted”.

@magick_image = Magick::Image.from_blob(@file.read).first

Where @file is a StringIO object or a File object, depending on the
size of the uploaded file.

@file.read.size returns the expected file size (in bytes).

Any ideas?

Well, it works for me. Here’s my attempt to reproduce the problem.

require ‘RMagick’
require ‘stringio’

file = File.open(‘Flower_Hat.jpg’)
img = Magick::Image.from_blob(file.read).first
p img

blob = IO.read(‘Flower_Hat.jpg’)
sio = StringIO.new(blob)
img = Magick::Image.from_blob(sio.read).first
p img

The output is:
tim: ~> ruby test.rb
JPEG 200x250 DirectClass 8-bit 9kb
JPEG 200x250 DirectClass 8-bit 9kb

Yeah, I tried to do that test last night and it worked fine.

So I have no idea why the code is failing inside my Image model.

Here’s my image model. Perhaps that will give someone some insight.

Say, I’m doing the Magick::Image.from_blob call in #after_save.
Perhaps something weird is happening with @file’s lifetime or
something?

class Image < ActiveRecord::Base
belongs_to :house
belongs_to :community
belongs_to :contractor

FileLocation = “#{RAILS_ROOT}/public/media/”
DisplayLocation = “/media”

def file=(file)
if file.size > 0
self.content_type = file.content_type.strip
@file = file
end
end

Runs after Image is saved, saves the image to the file system.

def after_save
if @file
@magick_image = Magick::Image.from_blob(@file.read).first
save_full_image
save_thumbnail_image
save_rounded_thumbnail_image
save_medium_image
save_large_image
end
end

If image is already a jpeg, then just write it to a file.

If it’s not, convert it to a jpeg and then write.

def save_full_image
@magick_image.write(full_file)
end

Write a thumbnail to the filesystem

def save_thumbnail_image
thumbnailed_magick_image = resize_image_to_exact_size(180,135)
thumbnailed_magick_image.write(thumbnail_file)
end

def save_rounded_thumbnail_image
thumbnail_image = resize_image_to_exact_size(180, 135)
color = “#000033
corner_width = 15
corners = Magick::Draw.new
corners.stroke(color)
corners.fill_opacity(0)
corners.stroke_width(15)
corners.roundrectangle(0, 0,
thumbnail_image.columns, thumbnail_image.rows,
corner_width, corner_width)
corners.draw(thumbnail_image)
thumbnail_image.write(rounded_thumbnail_file)
end

Resizes a Magick image to an exact size, keeping the same aspect ratio

def resize_image_to_exact_size(width, height)
new_aspect_ratio = width.to_f / height.to_f
old_aspect_ratio = @magick_image.columns.to_f / @magick_image.rows.to_f

if old_aspect_ratio < new_aspect_ratio
# Image too tall, geometry string should restrict height
geometry_string = “#{width}”
else
# Image too wide or just right, geometry string should
# restrict width
geometry_string = “x#{height}”
end

Get the resized image

resized_image = resize_image(geometry_string)

Return the resized, crop image

resized_image.crop(Magick::CenterGravity, width, height)
end

def resize_image(geometry_string)
logger.debug “resizing image to #{geometry_string}”
@magick_image.change_geometry(geometry_string) do |w, h, img|
img.resize(w, h)
end
end

def save_medium_image
medium_image = resize_image(“275”)
medium_image.write(medium_file)
end

def save_large_image
large_image = resize_image(“500”)
large_image.write(large_file)
end

Delete images from filesystem

def after_destroy
File.delete(full_file) if File.exist? “#{full_file}”
File.delete(thumbnail_file) if File.exist? “#{thumbnail_file}”
File.delete(rounded_thumbnail_file) if File.exist?
“#{rounded_thumbnail_file}”
File.delete(medium_file) if File.exist? “#{medium_file}”
File.delete(large_file) if File.exist? “#{large_file}”
end

Returns the filesystem location of the image file

def full_file
“#{FileLocation}/full/#{id}.jpg”
end
def thumbnail_file
“#{FileLocation}/thumbnail/#{id}.jpg”
end
def rounded_thumbnail_file
“#{FileLocation}/rounded_thumbnail/#{id}.jpg”
end
def medium_file
“#{FileLocation}/medium/#{id}.jpg”
end
def large_file
“#{FileLocation}/large/#{id}.jpg”
end

Returns the location of the image to the browser

def full
“#{DisplayLocation}/full/#{id}.jpg”
end
def thumbnail
“#{DisplayLocation}/thumbnail/#{id}.jpg”
end
def medium
“#{DisplayLocation}/medium/#{id}.jpg”
end
def large
“#{DisplayLocation}/large/#{id}.jpg”
end
def rounded_thumbnail
“#{DisplayLocation}/rounded_thumbnail/#{id}.jpg”
end def resize
if File.exist? full_file
@file = File.open full_file
save
end
end
end

I’ll be damned. Moving
@magick_image = Magick::Image.from_blob(@file.read).first
from Image#after_save to Image#file= fixed the problem.

So, apparently, something weird happens to @file before
Image#after_save is called. Any ideas?

If you look again, you'll notice I have two resize methods. One
resize method that just resizes an image, keeping the same aspect
ratio. And then the resize_to_exact_size method that resizes an image
to an exact size.

See http://jerrymahan.com/contents/show/front . You'll notice that
those thumbnails on the right are all the exact same size (same height
and width). But the original images all had differently aspect
ratios. To make sure that all the thumbnails are the exact same size,
I had to do some additional calculations. (or so it appeared to me)

···

On 8/4/05, rails@lists.rubyonrails.org <rails@lists.rubyonrails.org> wrote:

Hi !

Joe Van Dyk said the following on 2005-08-03 22:30:
> On 8/3/05, Joe Van Dyk <joevandyk@gmail.com> wrote:
> # Resizes a Magick image to an exact size, keeping the same aspect ratio
> def resize_image_to_exact_size(width, height)
> new_aspect_ratio = width.to_f / height.to_f
> old_aspect_ratio = @magick_image.columns.to_f / @magick_image.rows.to_f
>
> if old_aspect_ratio < new_aspect_ratio
> # Image too tall, geometry string should restrict height
> geometry_string = "#{width}"
> else
> # Image too wide or just right, geometry string should
> # restrict width
> geometry_string = "x#{height}"
> end

There's no need to do that. RMagick's documentation says about geometry
strings:

"By default, the width and height are maximum values. That is, the image
is expanded or contracted to fit the width and height value while
maintaining the aspect ratio of the image."

http://studio.imagemagick.org/RMagick/doc/imusage.html#geometry

Hope that helps !
François