Resize uploaded image file without creating temp file?


#1

Hi,

I need to take a single uploaded image file and save three resized
versions of it, a thumbnail, normal and large version. I was planning
on
doing this in my model by having an array of geometry strings and
looping
through them, each time saving a new image object resized to the correct
geometry. I know that file column does similar stuff, but I would
prefer
to have a separate database row for each image size, rather than one row
and multiple saved files, which is what file_column seems to want to do.

If I store the images as blobs (I use postgresql, so they will be bytea
columns…), is it possible to resize an uploaded image file before
assigning it to the model attribute corresponding to the blob without
writing it to a temp file first? On the other hand, is it better for
performance and maintenance reasons to use files and just save the
location of the image file?

Finally, does anyone have any working code snippet which does these kind
of things using either Magick or preferably, MiniMagick that they can
share, as a starting point?

Sorry for the braindump and flood of questions, but I was hoping people
could save me from spending a lot of time writing something only to
realise that I should have done it another way :slight_smile:

Many thanks,

Matt.


#2
else

Notice the special handling for the case where the incoming “file” isn’t
a file, but an “IOString”. This happens if it’s below a couple k in
size. Also note the “img = nil; GC.start” at the end. This is important
if you want to keep memory usage reasonable.

–Al Evans

Thanks, Al!

That has given me a good start on what I want to do.

Matt.


#3

Matthew L. wrote:

…is it possible to resize an uploaded image file before
assigning it to the model attribute corresponding to the blob without
writing it to a temp file first?

It will already be a temp file when you get it, unless it’s pretty
small.

On the other hand, is it better for
performance and maintenance reasons to use files and just save the
location of the image file?

Opinions vary. I think it’s better to save images to the file system,
rather than the database, but others disagree.

On the other hand, it’s easy enough to read the file and store it as a
blob, either with or without processing.

For example, in RMagick:

  photo_data = uploaded_file.read
  img = Image.from_blob(photo_data).first
  img.strip!.change_geometry("1024x1024") { |cols, rows, img|
    img.resize!(cols, rows)
  }
  blob = img.to_blob

Finally, does anyone have any working code snippet which does these kind
of things using either Magick or preferably, MiniMagick that they can
share, as a starting point?

Here’s one routine I use to process an incoming image. I’m converting it
to an intermediate “work” copy, no larger than 1024x1024. I’ve already
checked the incoming file to make sure I like it.

def cache_image_for_processing(uploaded_file)
img = nil
path = uploaded_file.local_path
if path.blank?
photo_data = uploaded_file.read
img = Image.from_blob(photo_data).first
else
img = Image.read(path).first
end
img.strip!.change_geometry(“1024x1024”) { |cols, rows, img|
img.resize!(cols, rows)
}
img.write(cache_file_name)
img = nil
GC.start
end

Notice the special handling for the case where the incoming “file” isn’t
a file, but an “IOString”. This happens if it’s below a couple k in
size. Also note the “img = nil; GC.start” at the end. This is important
if you want to keep memory usage reasonable.

–Al Evans


#4

unknown wrote:

…But I’ve been looking at the Wikimedia project source (it bears
similarities to my own project), and I like the way they do images.
They store them as blobs, and if you want to get an image of a
particular width, you put that as a parameter in the getImage URL, and
it’s resized dynamically per request.

Context is everything:-)

If you’re on your own server, with plenty of memory and processing
power, sure, why not.

But re-sizing images per request is very expensive, computationally.
Personally, I wouldn’t even think of doing it on the shared server I’m
using.

I guess you could have client-side Javascript get the screen dimensions
and give them to your server, then rewrite all the image tags to fit the
user’s screen. But it seems to me that you would have to have a Really
Good Reason to do that:-)

–Al Evans


#5

I would normally agree with Al, saying it’s better to use a temporary
file. But I’ve been looking at the Wikimedia project source (it bears
similarities to my own project), and I like the way they do images.
They store them as blobs, and if you want to get an image of a
particular width, you put that as a parameter in the getImage URL, and
it’s resized dynamically per request. (I think it’s cached as well to
speed things up). Using that method, you can just put “/Image/32/800”
(32 being the id and 800 being the width) in the src of your img tag.
Hope this helps.
-Nathan


#6

Al Evans wrote:

Good Reason to do that:-)

–Al Evan
That is a cool way of doing it, but I agree with Al on this. Although I
am on a dedicated server, it isn’t flush with horsepower and most of my
listing objects have a few image objects with 100x100, 320x320 and large
(800x600) versions plus a PDF. They all have to be loaded everytime a
listing is viewed because they are scrolled with javascript. Doing the
dynamic resizing alone could easily kill my server if the site gets
moderately busy.

I have not made up my mind if I can get away with storing the images in
the DB - I am not sure if the cache hit rate would be high enough to
justify caching the image objects given that the box doesn’t have enough
memory to give a lot over to the cache. Mmm…decisions, decisions.

Matt.


#7

I have a similar requirement for a site I am building. I am also on a
shared server so creating separate thumbnails is too resource
exhaustive for me for the time being. So, I ended up writing this
clientside javascript to dynamically resize the images:

function resize(which, max) {
var elem = document.getElementById(which);
if (elem == undefined || elem == null) return false;
if (max == undefined) max = 100;
if (elem.width > elem.height) {
if (elem.width > max) elem.width = max;
} else {
if (elem.height > max) elem.height = max;
}
}

This has been tested in IE 6 and Firefox 1.5. The interesting thing I
found is that both of these browsers will maintain the aspect ratio
for you if you just resize the largest dimension to fit your maximum
allowed size. To make this work you need to resize the images after
they’ve been loaded using the :onload option like so:

<%= image_tag(image_url, {:id => “image_x”, :onload =>
“resize(‘image_x’)”}) %>

Feel free to use this if it meets your needs.

Tom

On 5/30/06, Matthew L. removed_email_address@domain.invalid wrote:

Context is everything:-)
user’s screen. But it seems to me that you would have to have a Really


Rails mailing list
removed_email_address@domain.invalid
http://lists.rubyonrails.org/mailman/listinfo/rails


Tom D.

http://blog.atomgiant.com
http://gifthat.com


#8

Surely this would just resize the displayed image, not the actual file
site. If your source images were quite large and you had a whole page
of them, it would take ages to download.

Steve


#9

Yes, this just resizes the display of the image. For large images,
this is definitely an inferior solution to resizing them on the server
and storing them for future use. But for my current requirements, it
is a reasonable work around until I get the resources to afford
resizing on the server.

Good Luck Matt.

Tom

On 5/30/06, Matthew L. removed_email_address@domain.invalid wrote:

shared server so creating separate thumbnails is too resource
if (elem.height > max) elem.height = max;
“resize(‘image_x’)”}) %>

Feel free to use this if it meets your needs.

Tom


Rails mailing list
removed_email_address@domain.invalid
http://lists.rubyonrails.org/mailman/listinfo/rails


Tom D.

http://blog.atomgiant.com
http://gifthat.com


#10

That is my problem. I could have a paginated list of results, each with
a thumbnail. Viewing any one of these would yield at least three images
of up to 800x600 which would require resizing to 320x320 for display and
also be ready to place in a pop-up at full size if the user wants to see
the enlarged image.

Anyways, thanks for the javascript, Tom. It might come in handy
somewhere :slight_smile:

M.