RMagick problem

J

Joe Van Dyk

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

@magick_image =3D 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
 
J

Joe Van Dyk

Joe Van Dyk wrote:
=20
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.
=20
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.

Ah, good idea.

I still have no idea why it's giving me "Zero-length blob
not permitted" for this line:
@magick_image =3D 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. :(
 
P

Pit Capitain

Joe said:
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. :(

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

Regards,
Pit
 
J

Joe Van Dyk

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

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.
 
T

Tim Hunter

Joe said:
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
 
J

Joe Van Dyk

Joe Van Dyk wrote:
=20
=20
Well, it works for me. Here's my attempt to reproduce the problem.
=20
require 'RMagick'
require 'stringio'
=20
file =3D File.open('Flower_Hat.jpg')
img =3D Magick::Image.from_blob(file.read).first
p img
=20
blob =3D IO.read('Flower_Hat.jpg')
sio =3D StringIO.new(blob)
img =3D Magick::Image.from_blob(sio.read).first
p img
=20
=20
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.
 
J

Joe Van Dyk

=20
Yeah, I tried to do that test last night and it worked fine.
=20
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. =20

Say, I'm doing the Magick::Image.from_blob call in #after_save.=20
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 =3D "#{RAILS_ROOT}/public/media/"
DisplayLocation =3D "/media"
=20
def file=3D(file)
if file.size > 0
self.content_type =3D file.content_type.strip
@file =3D file
end
end

# Runs after Image is saved, saves the image to the file system.
def after_save
if @file
@magick_image =3D 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 =3D resize_image_to_exact_size(180,135)
thumbnailed_magick_image.write(thumbnail_file)
end

def save_rounded_thumbnail_image
thumbnail_image =3D resize_image_to_exact_size(180, 135)
color =3D "#000033"
corner_width =3D 15
corners =3D Magick::Draw.new
corners.stroke(color)
corners.fill_opacity(0)
corners.stroke_width(15)
corners.roundrectangle(0, 0,=20
thumbnail_image.columns, thumbnail_image.rows,=
=20
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 =3D width.to_f / height.to_f
old_aspect_ratio =3D @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 =3D "#{width}"
else
# Image too wide or just right, geometry string should=20
# restrict width
geometry_string =3D "x#{height}"
end

# Get the resized image
resized_image =3D 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 =3D resize_image("275")
medium_image.write(medium_file)
end

def save_large_image
large_image =3D 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 =3D File.open full_file
save
end
end
end
 
J

Joe Van Dyk

=20
Here's my image model. Perhaps that will give someone some insight.
=20
Say, I'm doing the Magick::Image.from_blob call in #after_save.
Perhaps something weird is happening with @file's lifetime or
something?
=20
class Image < ActiveRecord::Base
belongs_to :house
belongs_to :community
belongs_to :contractor
=20
FileLocation =3D "#{RAILS_ROOT}/public/media/"
DisplayLocation =3D "/media"
=20
def file=3D(file)
if file.size > 0
self.content_type =3D file.content_type.strip
@file =3D file
end
end
=20
# Runs after Image is saved, saves the image to the file system.
def after_save
if @file
@magick_image =3D 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
=20
# 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
=20
# Write a thumbnail to the filesystem
def save_thumbnail_image
thumbnailed_magick_image =3D resize_image_to_exact_size(180,135)
thumbnailed_magick_image.write(thumbnail_file)
end
=20
def save_rounded_thumbnail_image
thumbnail_image =3D resize_image_to_exact_size(180, 135)
color =3D "#000033"
corner_width =3D 15
corners =3D 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
=20
# Resizes a Magick image to an exact size, keeping the same aspect rati= o
def resize_image_to_exact_size(width, height)
new_aspect_ratio =3D width.to_f / height.to_f
old_aspect_ratio =3D @magick_image.columns.to_f / @magick_image.rows.= to_f
=20
if old_aspect_ratio < new_aspect_ratio
# Image too tall, geometry string should restrict height
geometry_string =3D "#{width}"
else
# Image too wide or just right, geometry string should
# restrict width
geometry_string =3D "x#{height}"
end
=20
# Get the resized image
resized_image =3D resize_image(geometry_string)
=20
# Return the resized, crop image
resized_image.crop(Magick::CenterGravity, width, height)
end
=20
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
=20
def save_medium_image
medium_image =3D resize_image("275")
medium_image.write(medium_file)
end
=20
def save_large_image
large_image =3D resize_image("500")
large_image.write(large_file)
end
=20
=20
=20
# 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
=20
# 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
=20
=20
# 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 =3D File.open full_file
save
end
end
end
=20


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

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

Joe Van Dyk

Hi !

Joe Van Dyk said the following on 2005-08-03 22:30:

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

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)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Staff online

Members online

Forum statistics

Threads
474,176
Messages
2,570,950
Members
47,501
Latest member
log5Sshell/alfa5

Latest Threads

Top