Error Rotating a django InMemoryUploadedFile image based on exif data - django-admin

I am trying to rotate an image based on the image's exif data. The image is uploaded using the django admin request.FILES. If the file is large, and is uploaded as an TemporaryUploadedFile, and I can rotate the image correctly. However, I am having an issue when the image is smaller and uploaded as an InMemoryUploadedFile.
When the image is an InMemoryUploadedFile, I can rotate the image based on the exif data and save it to disk. The image on disk is rotated correctly. When I try to save the image as an io.BytesIO() array, the rotation seems to be lost.
The following code is called from save_model in the ModelAdmin code, and takes as input the UploadedFile from request.FILES, and returns an UploadedFile to be saved in the model FileField.
def rotate_image_from_exif(f):
logger.debug("rotate_image_from_exif START")
if isinstance(f, TemporaryUploadedFile): # this image is rotated correctly in the model
# large files in TemporaryUploadedFile
with open(f.temporary_file_path(), 'rb') as image_file:
image = pImage.open(image_file)
img = ImageOps.exif_transpose(image)
if 'exif' in image.info:
exif = image.info['exif']
img.save(f.temporary_file_path(), exif=exif)
img.save(f.temporary_file_path())
return f
elif isinstance(f, InMemoryUploadedFile):
image = pImage.open(f.file)
quality = 85
temp_path = '/tmp/fred.jpg'
img = ImageOps.exif_transpose(image)
img.save(temp_path, format=image.format, quality=quality) # this image is rotated correctly on the file system
img.seek(0)
output = io.BytesIO()
img.save(output, format=image.format, quality=quality)
output.seek(0)
return InMemoryUploadedFile(output,
'FileField', 'original_image', image.format,
sys.getsizeof(output), None) # this image is not rotated
else:
# can't read file
return None
I can solve this problem by setting FILE_UPLOAD_MAX_MEMORY_SIZE to something very small (say 1000), so all uploaded files are saved as TemporaryUploadedFiles, but I would really like to solve this issue with rotating an InMemoryUploadedFile.

Related

Rails ActiveStorage: Attach a Vips Image

I'm struggling to attach a Vips:Image object to an ActiveStorage object.
I use Vips to compress a PNG image. I'm then looking to save this compressed PNG version into a second ActiveStorage attachment. The code is failing when attempting to attach. All I get is: Process finished with exit code -1073741819 (0xC0000005)
# compress the image and save the compressed version of the file as a PNG
# both img and img_to_compress are active storage attachments
def compress_charlie(img, img_to_compress)
# load the image as a Vips Image object
vips_img = Vips::Image.new_from_buffer (URI.open(img.url) { |f| f.read }), ''
# do compression, etc ... not bothering to show this code as it has no impact on the issue I have
# save the compressed png
img_to_compress.attach(
io: StringIO.new(vips_img .write_to_buffer('.png')),
filename: img.filename
)
end
Any help is appreciated, Charlie
Ruby 3.1
Rails 7.0.1
You're decompressing and recompressing twice. How about (untested):
def convert_to_png(img, img_to_compress)
# load the image as a Vips Image object
vips_img = Vips::Image.new_from_buffer (URI.open(img.url) { |f| f.read }), ''
# save as a PNG string
png = StringIO.new(vips_img.write_to_buffer('.png')),
img_to_compress.attach(io: png, filename: img.filename)
end
pngsave_buffer gives you a String with binary encoding, you don't need to save a second time. Try this test program:
require "vips"
x = Vips::Image.new_from_file ARGV[0]
y = x.pngsave_buffer
puts "y.class = #{y.class}"
I see:
$ ./pngsave.rb ~/pics/k2.jpg
y.class = String
If the image is already a PNG you'll be wasting a lot of time, of course. You could add something to detect the format and skip the conversion.

jpg images without extension aren't displayed

from kivy.uix.image import Image
self.img = Image(source="image") # This works when image is an PNG image
self.img = Image(source="image.jpg") # This works when image.jpg is a JPG image
self.img = Image(source="image") # This doesn't work when image is a JPG image
I need to specify images without extention for the app to be generic (working with more image types). Can I achieve it somehow?
Kivy is using "imghdr" to determine the image type here, and as a fallback it uses the file extension here.
That explains why the image loads fine when it has a file extension, even though "imghdr" can't find the file type in the file's content.
I tested on a list of JPEG files, and each time "imghdr" was able to detect the file type each time. That is done here im imghdr. Notably, "imghdr" does not consider the file extension.
$ python
>>> import os, imghdr
... for f in os.listdir('.'):
... print('%s -- %s' % (f, imghdr.what(f)))
Maybe the JPEG file is missing the "JFIF" or "Exif" string that imghdr is looking for? You could use hexedit to see if one of those string is present at Byte 6 of the image file.

Is there a way to convert binary data into a data type that will allow ActiveStorage to attach it as an image to my User model

I am hitting an api to get an image that they have stored and use it as the profile pic for our application's users. I'm using Ruby on Rails and ActiveStorage with AWS to attach and store the image. What they send back is this:
{"status"=>"shared", "values"=>[{"$objectType"=>"BinaryData", "data"=>"/9j/4AAQSkZJRgABAQAASABIAAD/4QBMRXhpZgAAT.....KK5tT/9k=", "mime_type"=>"image/jpeg", "metadata"=>{"cropped"=>false}}]}
I tried a lot of different ways to attach it and manipulate the data such as just attaching it as it is, Base64.decode64, Base64.encode64. I also tried creating a new file and then attaching that. Here are some examples:
data = Base64.decode64(Base64.encode64(response[:selfie_image]["values"][0]["data"]))
user.profile_pic.attach!(
io: StringIO.new(open(data).read),
filename: "#{user.first_name}_#{user.last_name}_#{user.id}.jpg",
content_type: "image/jpeg"
)
data = Base64.decode64(Base64.encode64(response[:selfie_image]["values"][0]["data"]))
out_file = File.new("#{user.first_name}_#{user.last_name}_# . {user.id}.jpg", "w")
out_file.puts(data)
out_file.close
user.profile_pic.attach(
io: StringIO.new(open(out_file).read),
filename: "#{user.first_name}_#{user.last_name}_#{user.id}.jpg",
content_type: "image/jpeg"
)
I also tried:
user.profile_pic.attach(out_file)
It keeps either saying attachment is nil or depending on how I manipulate the data it will say not a jpeg file content header type wrong and throw that as an image magick error.
How can I manipulate this data to be able to attach it as an image for our users with ActiveStorage?
To get this to work I had to add gem "mini_magick" to my gemfile and then use it to decode the image data I was receiving from the api call and then turn it into a blob so that ActiveStorage could handle it.
data = response[:selfie_image]["values"][0]["data"]
decoded_data = Base64.decode64(data)
image = MiniMagick::Image.read(decoded_data)
image.format("png")
user.profile_pic.attach(
io: StringIO.new(image.to_blob),
filename: "#{user.id}_#{user.first_name}_#{user.last_name}.png",
content_type: "image/jpeg"
)
In command line ImageMagick you can use the inline: feature to decode base64 data into a gif or jpg. The base64 image here has transparency, it is most proper to save to gif or png.
convert 'inline:data:image/gif;base64,
R0lGODlhIAAgAPIEAAAAAB6Q/76+vvXes////wAAAAAAAAAAACH5BAEAAAUALAAA
AAAgACAAAAOBWLrc/jDKCYG1NBcwegeaxHkeGD4j+Z1OWl4Yu6mAYAu1ebpwL/OE
YCDA0YWAQuJqRwsSeEyaRTUwTlxUqjUymmZpmeI3u62Mv+XWmUzBrpeit7YtB1/r
pTAefv942UcXVX9+MjNVfheGCl18i4ddjwwpPjEslFKDUWeRGj2fnw0JADs=
' b64_noseguy.gif
But you can still save it to jpg. The transparency will be lost and the background will be black.
convert 'inline:data:image/jpeg;base64,
R0lGODlhIAAgAPIEAAAAAB6Q/76+vvXes////wAAAAAAAAAAACH5BAEAAAUALAAA
AAAgACAAAAOBWLrc/jDKCYG1NBcwegeaxHkeGD4j+Z1OWl4Yu6mAYAu1ebpwL/OE
YCDA0YWAQuJqRwsSeEyaRTUwTlxUqjUymmZpmeI3u62Mv+XWmUzBrpeit7YtB1/r
pTAefv942UcXVX9+MjNVfheGCl18i4ddjwwpPjEslFKDUWeRGj2fnw0JADs=
' b64_noseguy.jpg
See https://imagemagick.org/Usage/files/#inline
Sorry, I do not know the equivalent in RMagick

Images from iOS camera are sent rotated in web application

I am building an application in HTML5 for iPad to upload a picture to a server. When I click an input file element like this:
<input id='fileup' type='file' accept='image/*' name='upf' onchange='abv();'/>
It gives the possibility to either take a picture from the device camera or to upload from existing ones. However, when taking a picture, the resulting image is rotated based on the orientation of the device at the moment the photo is taken. What I want to do is to figure out the orientation of the captured image and try to rotate it on the server-side.
Notice that I do not have access to any iOS tools or frameworks, since this application is totally web-based.
Then, is there any information regarding the orientation of the picture that I can either access on the client or on the server-side, such that I would be able to rotate the images into the proper position? I have heard of EXIF data, but I am unsure on how to access it, and if it would give me the required information.
I am using python on the server-side, but a solution in C/C++ would also be appreciated.
Thanks.
I am using python on the server-side
So you can use jpegtran-cffi Python package that provides the ability to perform EXIF auto-transform:
# jpegtran can transform the image automatically according to the EXIF
# orientation tag
photo = JPEGImage(blob=requests.get("http://example.com/photo.jpg").content)
print photo.exif_orientation # "6" (= 270°)
print photo.width, photo.height # "4320 3240"
corrected = photo.exif_autotransform()
print corrected.exif_orientation # "1" (= "normal")
print corrected.width, corrected.height # "3240 4320"
Note: extracted from the README.
As an alternative there is also a convenient command-line tool called jhead that you can use for the same purpose:
# Remove EXIF orientation
# i.e. rotate the image accordingly and reset the orientation
# flag to 1 (default, i.e. origin = TopLeft)
# WARNING: the image file is overwritten!
# NOTE: it also works with a wildcard: jhead -autorot *.jpg
jhead -autorot myimage.jpg

CarrierWave Image URL

I have a model that has:
mount_uploader :image, ImageUploader
When uploading an Image I want to retrieve some width, height and some EXIF data from the image. In a before filter I am calling self.image.url but this will return something like:
/uploads/tmp/20110630-1316-10507-7899/emerica_wildinthestreets.jpg
The problem is that when I try to open this image using:
image = MiniMagick::Image.open(self.image.url)
I get "No such file or directory - /uploads/tmp/20110630-1312-10507-6638/emerica_wildinthestreets.jpg". It seems like the image has already been moved from the tmp folder to it's final location but self.image.url is not reflecting this change.
I've also tried this in an after_save method but the result is the same. Any ideas?
Turns out I needed to append "#{Rails.root.to_s}/public/" to self.image.url

Resources