I have a rails app and I am receiving a base64 encoded string that is an image from an ios app.
I am trying to convert it to a paperclip image.
So far I have:
module Api
module V1
class MyController < BaseController
def image_build
begin
token = request.headers["HTTP_TOKEN"]
#user = User.find_by(user_token: token)
#decoded_image = Base64.decode64(params[:image_data])
puts #decoded_image[0, 50]
#new_stand = Stand.create(:user_id => #user.id, :avatar => #decoded_image)
...
which outputs:
iVBORw0KGgoAAAANSUhEUgAAAPsAAACxCAYAAAACscusAAAAAX
�PNG
IHDR��ˬsRGB���
Completed 500 Internal Server Error in 90ms (Views: 3.1ms | ActiveRecord: 1.1ms)
How do I convert the base64 string to a proper image file in rails? I have paperclip running from this gem:
gem "paperclip", git: "git://github.com/thoughtbot/paperclip.git"
Notes
The images sent over can be any image from an ios phone so the type can change. I would like to support .gif, .png, .jpg, and jpeg.
In IOS I send over the information like this:
#IBOutlet var image: UIImage
#IBAction func Build(sender: AnyObject) {
let image_data = UIImagePNGRepresentation(image.image!)
self.service.createNewImage(notifier: notifier, image: image_data!)
}
then
let strBase64:String = image.base64EncodedStringWithOptions(.Encoding64CharacterLineLength)
let dataDictionary = ["image_data": strBase64]
self.post("image/build", data: dataDictionary).responseJSON
EDIT 1
I have started to attempt to use the paperclip io adapters but it still errors out. This is what I have so far:
#new_image = Stand.create(:user_id => #user.id)
encoded_picture = params[:image_data]
content_type = "image/jpg"
puts "HERE 1"
image = Paperclip.io_adapters.for("data:#{content_type};base64,#{encoded_picture}")
puts "HERE 2"
image.original_filename = "new_image.jpg"
puts "HERE 3"
#new_image.avatar = image
puts "HERE 4"
#new_image.cover_image_url = #new_image.avatar.url
puts "HERE 5"
#new_image.save
This code gives these errors:
HERE 1
HERE 2
HERE 3
Command :: file -b --mime '/tmp/082995477845923f853c5a48cc6e3cae20160712-26217-s1dngb.jpg'
Command :: identify -format '%wx%h,%[exif:orientation]' '/tmp/8d777f385d3dfec8815d20f7496026dc20160712-26217-1ht7ijm[0]' 2>/dev/null
Command :: identify -format %m '/tmp/8d777f385d3dfec8815d20f7496026dc20160712-26217-1ht7ijm[0]'
Command :: convert '/tmp/8d777f385d3dfec8815d20f7496026dc20160712-26217-1ht7ijm[0]' -auto-orient -resize "160x160>" '/tmp/4a98b13d8ce55141b173bb0ed5cb181220160712-26217-17sazsk'
Command :: identify -format '%wx%h,%[exif:orientation]' '/tmp/8d777f385d3dfec8815d20f7496026dc20160712-26217-1ht7ijm[0]' 2>/dev/null
Command :: identify -format %m '/tmp/8d777f385d3dfec8815d20f7496026dc20160712-26217-1ht7ijm[0]'
Command :: convert '/tmp/8d777f385d3dfec8815d20f7496026dc20160712-26217-1ht7ijm[0]' -auto-orient -resize "40x40>" '/tmp/4a98b13d8ce55141b173bb0ed5cb181220160712-26217-fhdb0w'
HERE 4
Completed 500 Internal Server Error in 654ms (Views: 2.5ms | Searchkick: 15.6ms | ActiveRecord: 7.3ms)
This seems to work, but I am still working on testing differing image types.
#new_image = Stand.create(:user_id => #user.id)
encoded_picture = params[:image_data]
content_type = "image/jpg"
image = Paperclip.io_adapters.for("data:#{content_type};base64,#{encoded_picture}")
image.original_filename = "new_image.jpg"
#new_image.avatar = image
#new_image.save
#new_image.cover_image_url = #new_image.avatar.url
#new_image.save
I have upvoted the previous answer because it helped me find other resources, but it does not contain an answer for my scenario.
A lot of useful information you can found in paperclip's repository, for your question i found this:
3.5.0:
Feature: Handle Base64-encoded data URIs as uploads
Paperclip::DataUriAdapter handles base64-encoded data.
Just works:
class Photo < ActiveRecord::Base
before_validation :set_image
has_attached_file :image
def set_image
StringIO.open(Base64.decode64(image_json)) do |str|
decorate_str(str)
str.original_filename = "THE_NAME_OF_FILE.jpg"
str.content_type = "image/jpeg"
self.image = data
end
end
def decorate_str(str)
str.class.class_eval { attr_accessor :original_filename, :content_type }
end
end
Similar way to solve your problem is here.
Paperclip's changelog
Related
I have an error with MiniMagick during my "after_upload" in my image_concern.rb
My code makes the image that is poster be converted to jpg. Then it will take the file which is in / tmp and will modify this file to have it also in smaller to display it for later. But I have an error during this step:
`mogrify -resize 360x200^ -gravity Center -crop 360x200+0+0 /tmp/RackMultipart20200211-5215-1lxtqf1.png` failed with error: mogrify: unable to open image '/tmp/RackMultipart20200211-5215-1lxtqf1.png':
It tries to open this file in .png but it is in jpg in my tmp /. How to say that it opens this file but not in png but in jpg. Thank you
This is my after_upload code:
def #{field}_after_upload
path = #{field}_path
options = #{options}
if #{field}_file.respond_to? :path
dir = File.dirname(path)
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
image = MiniMagick::Image.new(#{field}_file.path) do |b|
if options[:resize].ends_with?('!')
b.resize '#{options[:resize].delete('!')}^'
b.gravity 'Center'
b.crop '#{options[:resize].delete('!')}+0+0'
else
b.resize '#{options[:resize].delete('!')}\>'
end
end
image.format 'jpg'
image.write path
if options[:formats]
options[:formats].each do |k, v|
image = MiniMagick::Image.new(#{field}_file.path) do |b|
b.resize "\#{v}^"
b.gravity 'Center'
b.crop "\#{v}+0+0"
end
image.format 'jpg'
image.write path.gsub('.jpg', "_\#{k}.jpg")
end
end
end
end
The bug starts from "if options[:formats]"
Thank you !
I want to save an image from my react app in my rails app.
I post this to my api:
and when I try to save it i get this error:
{
"file":[
"has contents that are not what they are reported to be",
"file type is not acceptable"
],
"file_content_type":[
"file type is not acceptable"
]
}
my model looks like this:
class Photo < ApplicationRecord
validates :title, presence: true
belongs_to :user
has_attached_file :file, styles: { square: "350x233#", medium: "300x300>", thumb: "100x100>" }, default_url: "/images/:style/missing.png"
validates_attachment_content_type :file, content_type: /\Aimage\/.*\z/, message: 'file type is not acceptable'
validates_attachment_size :file, less_than: 10.megabytes, message: 'file size is more than 50MB'
end
and my controller:
def create
uploaded_file = false
if params[:photo][:image]
tempfile = Tempfile.new(params[:photo][:filename])
tempfile.binmode
tempfile.write(Base64.decode64(params[:photo][:image]))
uploaded_file = ActionDispatch::Http::UploadedFile.new(
:tempfile => tempfile,
:filename => params[:photo][:filename],
:original_filename => params[:photo][:filename],
:content_type => Mime::Type.lookup_by_extension(File.extname(params[:photo][:filename])[1..-1]).to_s
)
params[:photo][:file] = uploaded_file
end
params[:photo][:title] = params[:photo][:filename] if params[:photo][:title] == ""
# #picture = Photo.new(photo_params)
#photo = current_user.photos.build photo_params
#photo.save
render json: #photo.errors
end
and in my logs i found this:
Unpermitted parameters: :image, :filename
Command :: file -b --mime '/tmp/homepage.jpg20180415-7-1wa7yr3'
[paperclip] Trying to link /tmp/homepage.jpg20180415-7-1wa7yr3 to /tmp/ba3988db0a3167093b1f74e8ae4a8e8320180415-7-jpgu5m.jpg
[paperclip] Trying to link /tmp/ba3988db0a3167093b1f74e8ae4a8e8320180415-7-jpgu5m.jpg to /tmp/ba3988db0a3167093b1f74e8ae4a8e8320180415-7-147ntu1.jpg
Command :: file -b --mime '/tmp/ba3988db0a3167093b1f74e8ae4a8e8320180415-7-147ntu1.jpg'
[paperclip] Content Type Spoof: Filename homepage.jpg (application/octet-stream from Headers, ["image/jpeg"] from Extension), content type discovered from file command: application/octet-stream. See documentation to allow this combination.
[1m[35m (1.5ms)[0m [1m[35mBEGIN[0m
[paperclip] Trying to link /tmp/ba3988db0a3167093b1f74e8ae4a8e8320180415-7-jpgu5m.jpg to /tmp/ba3988db0a3167093b1f74e8ae4a8e8320180415-7-ndp9hd.jpg
Command :: file -b --mime '/tmp/ba3988db0a3167093b1f74e8ae4a8e8320180415-7-ndp9hd.jpg'
[paperclip] Content Type Spoof: Filename homepage.jpg (application/octet-stream from Headers, ["image/jpeg"] from Extension), content type discovered from file command: application/octet-stream. See documentation to allow this combination.
I didn't get my mistake. Can anyone help me with that upload?
I think the content_type is missing, but I don't know where.
Have you tried using Paperclip's io_adapter to decode the base64 into the attachment object? Something along the lines of:
def create
file = Paperclip.io_adapters.for(params[:photo][:image])
file.original_filename = params[:photo][:filename]
#photo = Photo.new(file: file, user: current_user)
#photo.save
end
I'm using Paperclip to convert images. I have noticed a severe degradation of quality of the produced image if its colorspace isn't set to srgb.
Is there a way to validate the colorspace of the uploaded image?
Ive got half an answer that might help...
You will need to install: https://github.com/rmagick/rmagick
Add to your model:
attr_accessor :image_colorspace
validates_exclusion_of :image_colorspace, in: [Magick::CMYKColorspace], message: 'is not RGB'
before_logo_post_process :read_image_colorspace
def read_image_colorspace
self.image_colorspace = Magick::Image.from_blob(image.queued_for_write[:original].read).first.colorspace
true
end
(Replace image with your attachment name.)
...or, if you prefer to not use rmagick, and you have a 'nix system, you could do this:
attr_accessor :image_colorspace
validates_exclusion_of :image_colorspace, in: ['CMYK'], message: 'is not RGB'
before_logo_post_process :read_image_colorspace
def read_image_colorspace
self.logo_colorspace = `identify -verbose %m "#{image.queued_for_write[:original].path}" | grep 'Colorspace'`.to_s.upcase.strip.split(' ').last
true
end
I have an Image model that requires me to have some data set up before I can save the image, because I determine where to upload the image to and what to name it depending on some data from the model. Right now I'm trying to do:
# ImagesController sets up data
img = Image.new
img.imageable = user
phashion_img = Phashion::Image.new(file.path)
img.image_hash = phashion_img.fingerprint.to_s
img.batch = img.latest_batch + 1
img.extension = 'jpg'
img.height = 640
img.width = 640
# ImageUploader uses it
version :original do
def full_filename
"#{model.image_hash}/orig/#{alnum_encode(model.imageable.id)}.#{model.extension}"
end
end
I've run into an issue trying to pass the uploaded image into the uploader.
img.uploader.store! file gives me the error wrong number of arguments (1 for 0)
img.uploader.store! file.tempfile gives me You are not allowed to upload "" files, allowed types: jpg, jpeg, gif, png
For reference, file is:
#<ActionDispatch::Http::UploadedFile:0x00000109ccdb70 #tempfile=#<Tempfile:/var/folders/lx/xk8vzr4s0fdd_m5w0syftfl80000gn/T/RackMultipart20140723-28731-1b5weu8>, #original_filename="20140522_164844.jpg", #content_type="image/jpeg", #headers="Content-Disposition: form-data; name=\"user[images_attributes][0][uploader]\"; filename=\"20140522_164844.jpg\"\r\nContent-Type: image/jpeg\r\n">
Which would make file.tempfile:
#<Tempfile:/var/folders/lx/xk8vzr4s0fdd_m5w0syftfl80000gn/T/RackMultipart20140723-28731-1b5weu8>
Is there any way I could pass either of these objects, ActionDispatch::Http::UploadedFile or Tempfile, to the Carrierwave uploader? Some transformation I can do to them so that the uploader can accept one of them?
Since my use case is so far off from what is intended with CarrierWave, or any of the other image upload gems out there, I ended up rolling my own solution using the AWS-SDK gem to upload to S3. It's pretty specific, but I'm posting the gist of it on the off chance that it helps someone else.
# Image processing
magick = Magick::Image.from_blob( file.read )[0]
if !["JPG", "JPEG", "GIF", "PNG"].include? magick.format
# Invalid format specified
end
orig = Image.new
orig.imageable = user
phashion_img = Phashion::Image.new(file.path)
orig.image_hash = phashion_img.fingerprint.to_s
orig.batch = orig.latest_batch + 1
orig.extension = magick.format.downcase
filename = "#{orig.image_hash}/orig/#{Alnum.encode(orig.imageable_id)}.#{orig.extension}"
orig.height = magick.rows
orig.width = magick.columns
orig.size = 'orig'
# Upload to S3
s3 = AWS::S3.new
uploaded_name = Rails.root.to_s+filename.gsub('/', '_')
magick.write( uploaded_name ) # Save file to filesystem so I can upload to S3
s3.buckets[bucket_name].objects[filename].write( open(uploaded_name), :acl => :public_read )
FileUtils.rm uploaded_name # Remove so it's not taking up space
# Crop
cropped = magick.crop(x.to_i, y.to_i, w.to_i, h.to_i, true) # Discard offset data to avoid borders
# Create Profile
# Resize_from is my own custom function to copy everything
# but the size into another object and resize the image
resize_from(s3, orig, cropped, 640, 640)
# Create Grid
resize_from(s3, orig, cropped, 220, 220)
# Create Thumbnail
resize_from(s3, orig, cropped, 72, 72)
I have big troubles...
I have uploaded around 500 images to S3 via my Rails app in a couple of different sizes. Now I realize that I need to store those images dimensions to my database. My approach to do this is with this migration:
class AddImageDimensionsToImages < ActiveRecord::Migration
def change
# add image columns
add_column :images, :small_width, :integer
add_column :images, :small_height, :integer
add_column :images, :medium_width, :integer
add_column :images, :medium_height, :integer
add_column :images, :large_width, :integer
add_column :images, :large_height, :integer
add_column :images, :original_width, :integer
add_column :images, :original_height, :integer
# loop all images
Image.all.each do |image|
# get sizes
geo_small = Paperclip::Geometry.from_file(image.image.to_file(:small))
geo_medium = Paperclip::Geometry.from_file(image.image.to_file(:medium))
geo_large = Paperclip::Geometry.from_file(image.image.to_file(:large))
geo_original = Paperclip::Geometry.from_file(image.image.to_file(:original))
# set sizes
image.small_width = geo_small.width if geo_small
image.small_height = geo_small.height if geo_small
image.medium_width = geo_medium.width if geo_medium
image.medium_height = geo_medium.height if geo_medium
image.large_width = geo_large.width if geo_large
image.large_height = geo_large.height if geo_large
image.original_width = geo_original.width if geo_original
image.original_height = geo_original.height if geo_original
# save image
image.save
end
end
end
When I run this migration it goes like this:
...
-- add_column(:images, :small_width, :integer)
-> 2.0866s
-- add_column(:images, :small_height, :integer)
-> 0.0092s
-- add_column(:images, :medium_width, :integer)
-> 0.0073s
...
Command :: identify -format %wx%h '/app/tmp/HayUnFinal_1620120408-4-qpzyym.jpg[0]'
[AWS S3 200 0.106467] get_object(:bucket_name=>"aa",:key=>"original/34/HayUnFinal_16.jpg")
Command :: identify -format %wx%h '/app/tmp/HayUnFinal_1620120408-4-1vqo71y.jpg[0]'
[paperclip] Saving attachments.
[AWS S3 200 0.152898] get_object(:bucket_name=>"aa",:key=>"small/64/Portrait_2.jpg")
Command :: identify -format %wx%h '/app/tmp/Portrait_220120408-4-i1ohlr.jpg[0]'
[AWS S3 200 0.055378] get_object(:bucket_name=>"aa",:key=>"medium/64/Portrait_2.jpg")
Command :: identify -format %wx%h '/app/tmp/Portrait_220120408-4-viizzf.jpg[0]'
[AWS S3 200 0.079235] get_object(:bucket_name=>"aa",:key=>"large/64/Portrait_2.jpg")
Command :: identify -format %wx%h '/app/tmp/Portrait_220120408-4-1d2u00o.jpg[0]'
[AWS S3 200 0.066724] get_object(:bucket_name=>"aa",:key=>"original/64/Portrait_2.jpg")
Command :: identify -format %wx%h '/app/tmp/Portrait_220120408-4-jq9wmg.jpg[0]'
[paperclip] Saving attachments.
[AWS S3 200 0.104891] get_object(:bucket_name=>"aa",:key=>"small/58/Bansah_24.jpg")
Command :: identify -format %wx%h '/app/tmp/Bansah_2420120408-4-an2mpt.jpg[0]'
[AWS S3 200 0.064148] get_object(:bucket_name=>"aa",:key=>"medium/58/Bansah_24.jpg")
Command :: identify -format %wx%h '/app/tmp/Bansah_2420120408-4-tzmnup.jpg[0]'
[AWS S3 200 0.051090] get_object(:bucket_name=>"aa",:key=>"large/58/Bansah_24.jpg")
Command :: identify -format %wx%h '/app/tmp/Bansah_2420120408-4-1pa0rxh.jpg[0]'
[AWS S3 200 0.063531] get_object(:bucket_name=>"aa",:key=>"original/58/Bansah_24.jpg")
Command :: identify -format %wx%h '/app/tmp/Bansah_2420120408-4-kogk2g.jpg[0]'
[paperclip] Saving attachments.
rake aborted!
An error has occurred, this and all later migrations canceled:
can't convert nil into String
rake aborted! It seems to work for a couple of images, but then the migration fails. I can't really figure out where that nil comes from, and how I can prevent it. Can anyone help me with this?
Bonus question: Is this a retared way to approach the task of saving image dimensions of already uploaded images?
Thanks a lot in advance!
Some image rows in the database had null image values. A check like this made the trick:
Image.all.each do |image|
# check that we have an image
if image.image_file_size
...
end
end