I have a Gallery model and a Post model. Each Post has an image, and I would like to create a composite image for each Gallery based on the posts in that gallery. I have a method for galleries that returns urls to the top 4 images in the gallery:
gallery.images_for_preview # => returns array of 4 image urls (200x200 images)
I'm using Carrierwave + RMagick to generate a composite image. I'm trying to follow along here: http://railscasts.com/episodes/374-image-manipulation but it seems my use case is slightly different. I have:
class GalleryImageUploader < CarrierWave::Uploader::Base
...
def store_dir
"galleries/#{model.obfuscated_id}"
end
version("full") { process :full_image }
def full_image
images = model.images_for_preview
puts images
manipulate! format: "png" do
image0 = Magick::Image.read(images[0]).first
image1 = Magick::Image.read(images[1]).first
image2 = Magick::Image.read(images[2]).first
image3 = Magick::Image.read(images[3]).first
underlay = Magick::Image.new(406, 406) { self.background_color = "#333333" }
underlay.composite!(image0, 2, 2, Magick::OverCompositeOp).composite!(image1, 204, 2, Magick::OverCompositeOp).composite!(image2, 2, 204, Magick::OverCompositeOp).composite!(image3, 204, 204, Magick::OverCompositeOp)
end
end
The full_image operation doesn't require a source file, but the only way I can seem to get it to generate the composite image is to do something like:
gallery.remote_image_url = "path/to/image"
which generates the image I need at /galleries/:id/full_image.png but also processes and generates an image at /galleries/:id/image.png
Is it possible to skip the "source" image and just generate a composite, then upload that as the ImageUploader's primary image?
Yes, just process the data on the go, no need for the version itself:
Replace this line:
version("full") { process :full_image }
With this one
process :full_image
Related
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.
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.
I realize this question has been asked a lot, but I could not find any information about how to do this in RoR. I am filling a PDF with form text fields using pdf-forms but this does not support adding images, and I need to be able to add an image of a customer's signature into the PDF. I have used prawn to render the image on the existing PDF, but I need to know the exact location to add the image on the signature line. So my question is how can I look at an arbitrary PDF and find the exact position of the "Signature" form field?
I ended up using pdf2json to find the x,y position of the form field. I generate a JSON file of the original pdf using this command:
%x{ pdf2json -f "#{form_path}" }
The JSON file is generated in the same directory as form_path. I find the field I want using these commands:
jsonObj = JSON.parse(File.read(json_path))
signature_fields = jsonObj["formImage"]["Pages"].first["Fields"].find_all do |f|
f["id"]["Id"] == 'signature'
end
I can use prawn to first create a new PDF with the image. Then using pdf-forms, I multistamp the image pdf onto the original PDF that I want to add the image to. But multistamp applies each page of the stamp PDF to the corresponding page of the input PDF so make sure your image PDF has the correct number of pages or else your image will get stamped on every page. I only want the image stamped onto the first page, so I do the following:
num_pages = %x{ #{Rails.configuration.pdftk_path} #{form_path} dump_data | grep "NumberOfPages" | cut -d":" -f2 }.to_i
signaturePDF = "/tmp/signature.pdf"
Prawn::Document.generate(signaturePDF) do
signature_fields.each do |field|
image Rails.root.join("signature.png"), at: [field["x"], field["y"]],
width: 50
end
[0...num_pages - 1].each{|p| start_new_page }
end
outputPDF = "/tmp/output.pdf"
pdftk.multistamp originalPDF, signaturePDF, outputPDF
You can use this gem 'wicked_pdf. You just write html, and this gem automatically convert it to pdf
Read more https://github.com/mileszs/wicked_pdf
Here's a pure ruby implementation that will return the field's name, page, x, y, height, and width using Origami https://github.com/gdelugre/origami
require "origami"
def pdf_field_metadata(file_path)
pdf = Origami::PDF.read file_path
field_to_page = {}
pdf.pages.each_with_index do |page, page_index|
(page.Annots || []).each do |annot|
field_to_page[annot.refno] = page_index
end
end
field_metas = []
pdf.fields.each do |field|
field_metas << {
name: field.T,
page_index: field_to_page[field.no],
x: field.Rect[0].to_f,
y: field.Rect[1].to_f,
h: field.Rect[3].to_f - field.Rect[1],
w: field.Rect[2].to_f - field.Rect[0]
}
end
field_metas
end
pdf_field_metadata "<path to pdf>"
I haven't tested it particularly thoroughly but the snippet can hopefully get you most of the way there.
Also -- keep in mind the above coordinates calculated are in points from the bottom left of the pdf page rather than the top right (and are not in pixels). I believe there's always 72 points per inch, and you can get the total page points by calling page.MediaBox in the pdf.pages loop above. If you're looking for pixel coordinates, you need to know the DPI of the resulting rendered document.
What should be the format for the parameter: media , in the call below, for updating with multiple images.
def twitter_status_update_with_media (twitter_client, text, media, opts)
twitter_client.update_with_media(self.text, media, opts)
end
For a single image, File.new(filepath) works fine..
To attach multiple images to a tweet, you first need to upload the images using the upload method:
media_ids = %w(image1.png image2.png image3.png image4.png).map do |filename|
Thread.new do
twitter_client.upload(File.new(filename))
end
end.map(&:value)
This will return media IDs, which you can pass into the media_ids parameter (as a comma-separated string) of the update method.
twitter_client.update("Tweet text", :media_ids => media_ids.join(','))
Im trying to implement some pics upload to my webapp.
The problem im having is that im trying to use Imagescalr to create thumbs of the original photos, and Im using AWS Plugin to upload them to my bucket.
So, the code i've is the following: (deleted validations and things that dont influence the question/posible answer)
def uploadPic() {
def f = request.getFile('file')
.
.
.
def s3file = f.inputStream.s3upload(filename) { //this is for the normal photo
path "POI/ID/"
}
def imageIn = ImageIO.read(???); //Dont know if I can put the f file here as parameter... do I have to store it somewhere first, call the s3 file, or I can resize on - the - fly?
BufferedImage scaledImage = Scalr.resize(imageIn, 150);
//Here I should upload the thumb. How can I call something like what is done for the normal photo?
So the problems/questions are explained along the code, hope someone knows how to do this. Thanks in advance.
in Grails, request.getFile() doesn't return a java.io.File object. You could use the input stream to write out a file but I'd probably do something like this, although I'd use services and break things up a little more. But this should get you started in the right direction. Consider this more of a pseudo code workflow suggestion.
def uploadPic() {
def f = request.getFile('file')
def tempFile = new File('/some/local/dir/myImage.png')
f.transferTo(tempFile)
// upload the original image to S3
whateverApi.s3Upload(tempFile)
def bufferedImage = ImageIO.read(tempFile)
def scaledBufferedImage = Scalr.resize(bufferedImage, 150)
// write the scaledImg to disk
def scaledImage = new File('/some/local/dir/myImage-150.png');
ImageIO.write(scaledBufferedImage, "png", scaledImage);
//upload scaled image to S3
whateverApi.s3Upload(scaledImage)
// clean up
tempFile.delete()
scaledImage.delete()
}
After a bit of investigation I decided to use Carrierwave and mini_magick on my new rail3 app.
I've set it up and it works perfectly. However I have a one question. I'd like to be able to access the width and height, so I can form the html correctly. However, there is no default data from which to get this information. Because of the way it stores the data I'm I cannot think of any way that I can add it to the database.
Can anyone suggest any tips or ideas? Is it even possible?
class Attachment
mount_uploader :file, FileUploader
def image
#image ||= MiniMagick::Image.open(file.path)
end
end
And use it like this:
Attachment.first.image['width'] # => 400
Attachment.first.image['height'] # => 300
Just for record, I have used a similar solution, however using files with Mongo GridFS, here it goes:
def image
#image ||= MiniMagick::Image.read(Mongo::GridFileSystem.new(Mongoid.database).open(file.path, 'r'))
end
Disadvantage of calculating Image height / width using RMagick or MiniMagick in run time:
Its CPU intensive
It requires Internet to get image and calculate the dimensions.
Its a slow process
FYI You can also calculate the Image Height, Width after the
Image is fully loaded by using the load event associated with the
<img> tag with the help of jQuery.
For Example:
$(document).ready(function(){
var $image = $('.fixed-frame img');
$image.load(function(){
rePositionLogo($image);
});
if($image.prop('complete')){
rePositionLogo($image);
}
});
function rePositionLogo($image){
var height = $image.height();
var width = $image.width();
if (width > height) {
$image.parents('.header').addClass('landscape');
var marginTop = (105 - $image.height())/2;
$image.css('margin-top', marginTop + 'px')
}else{
$image.parents('.header').addClass('portrait');
}
}
Be careful, because load() will not trigger when an image is already loaded. This can happens easily when an image is in the user's browser cache.
You can check if an image is already loaded using $('#myImage').prop('complete'), which returns true when an image is loaded.
I think the best way is to store the image dimensions in the model (database).
In my case, the model name is attachment. Then I created a migration:
rails g migration add_dimensions_to_attachments image_width:integer image_height:integer
After that, run the migration:
rake db:migrate
In my Image Uploader file app/uploaders/image_uploader.rb, I have:
class ImageUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
process :store_dimensions
private
def store_dimensions
if file && model
model.image_width, model.image_height = ::MiniMagick::Image.open(file.file)[:dimensions]
end
end
end
With this, the image dimensions is saved in the upload step.
To get the dimensions, I simply run attachment.image_width or attachment.image_height
See the reference here.