'expo-image-manipulator messes up aspect ratio of images
we've implemented image compression using Expo Image Manipulator. The problem, that we are facing, is that some photos that comes from the compression functionality are messed up. Example:
https://i.stack.imgur.com/JezvB.png
The images are never messed up on iOS devices, it happens randomly on Android (12, 11), mostly on Samsung phones.
Tech stack: EXPO SDK 43 (Managed workflow) React Native 0.64.3
The code which we are using to handle compression:
cameraParsing = async (result) => {
if (result.cancelled) {
return
}
const { uri, width, height } = result
const percentage = this.getResizePercentage(width, height)
const croppedImage = await manipulateAsync(
uri,
[
{
resize: {
width: width - width * percentage,
height: height - height * percentage,
},
},
],
{
compress: 0.4,
},
)
const fileName = uri.split('/')[uri.split('/').length - 1]
this.setState({
selectedFile: fileName,
selectedUri: croppedImage.uri,
})
this.props.setFieldValue(this.props.field.key, {
uri: croppedImage.uri,
type: mime.lookup(fileName),
name: fileName,
})
}
getResizePercentage = (width, height) => {
let percentage = 0
let newWidth = width
let newHeight = height
while (newWidth > 1200 || newHeight > 1150) {
percentage += 0.01
newWidth = width - width * percentage
newHeight = height - height * percentage
}
return percentage
}
Maybe someone had this issue and could help resolve this?
Solution 1:[1]
So I've managed to resolve the issue.
The issue is actually related to how the device manages image's aspect ratio. For example, iPhone camera uses aspect ratios 4:3, 16:9, etc (where 4 is height and 3 is width). However, some Android devices uses different aspect ratios: 3:4, 9:16 where 4 is height and 3 is width. The issue here is that expo camera always handles the images if they are 4:3, so in the end, image dimensions data are wrong.
Let's say I'm uploading a photo with the size of 3024x4032.
iPhone returns:
{
"cancelled": false,
"height": 4032,
"type": "image",
"uri": "...",
"width": 3024
}
Android returns:
{
"cancelled": false,
"height": 3024,
"type": "image",
"uri": "...",
"width": 4032
}
If you look close enough, you will see, that Android case returns mixed dimensions (height is put as width, and width as height). This makes compression mess up the image.
To resolve this, I've found that react native's built in method Image.getSize always returns the correct dimensions, so the final code looks like this:
getResizePercentage = (width, height) => {
let percentage = 0
let newWidth = width
let newHeight = height
while (newWidth > 1200 || newHeight > 1150) {
percentage += 0.01
newWidth = width - width * percentage
newHeight = height - height * percentage
}
return percentage
}
cameraParsing = async (result) => {
if (result.cancelled) {
return
}
this.setState({ compressionInProgress: true })
const { uri } = result
Image.getSize(result.uri, async (width, height) => {
const percentage = this.getResizePercentage(width, height)
const croppedImage = await manipulateAsync(
uri,
[
{
resize: {
width: width - width * percentage,
height: height - height * percentage,
},
},
],
{
compress: 0.5,
},
)
this.setState({ compressionInProgress: false })
const fileName = uri.split('/')[uri.split('/').length - 1]
this.setState({
selectedFile: fileName,
selectedUri: croppedImage.uri,
})
this.props.setFieldValue(this.props.field.key, {
uri: croppedImage.uri,
type: mime.lookup(fileName),
name: fileName,
})
})
}
Also, to whom it may concern, I did check the EXIF data returned by the expo camera, and it was wrong too.
I hope this helps to someone else.
Solution 2:[2]
this happens when you force a height + width to the image, it will stretch the image if the image is for example if you have a landscape image but want to have a square image (100 width 100 height) then it will crop it that way. I would suggest to only define width, that way it will have auto height.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Giedrius Rimkus |
| Solution 2 | Zerak Palani |
