I have just built a relatively plex image editing UI in React Native.
The experience is intended to be quite similar to Instagram, and has pinch-to-zoom, pan, and rotate functionality.
Transformations are stored as data, eg:
transformation: {
bottomBoundary: 1989,
leftBoundary: 410,
rightBoundary: 1634,
topBoundary: 765,
rotation: 0,
},
Wherein topBoundary
and bottomBoundary
are both offsets from the top of the image, and leftBoundary
and rightBoundary
are both offsets from the left of the image.
When rotating, because React Native uses the centre of objects as a transform origin, images need to be offset when in the 90° or 270° orientations, so that they are still "sticky" to the top/left corner and can be offset:
calculateRotationOffset.js
export default function (width, height) {
const transform = [
{ rotate: `${this.rotation}deg` },
]
/*
RN rotates around centre point, so we need to
manually offset the rotation to stick the image
to the top left corner so that our offsets will
work.
*/
if (this.rotation === 90) {
transform.push(
{ translateX: -((width - height) / 2) },
{ translateY: -((width - height) / 2) },
)
} else if (this.rotation === 270) {
transform.push(
{ translateX: ((width - height) / 2) },
{ translateY: ((width - height) / 2) },
)
}
return transform
}
Something about my rotation and zoom implementation are not working together.
Here is the plete snack example..
To reproduce the bug:
- Rotate the image once
- Pinch to zoom in or out
- Rotate the image again
The image will now be flipped and offset incorrectly.
There are a minimum of three transformations happening sequentially here to reproduce the bug, so I have not been able to track down the issue.
This question will definitely be bountied if necessary, and I might also offer a monetary reward. Thanks!
Update: After a few suggestions, it seems the best way to solve this problem is via matrix transformations, and then somehow convert this matrix back to styling that RN can represent onscreen. The matrix transformations need to support pan, rotate, and zoom.
I have just built a relatively plex image editing UI in React Native.
The experience is intended to be quite similar to Instagram, and has pinch-to-zoom, pan, and rotate functionality.
Transformations are stored as data, eg:
transformation: {
bottomBoundary: 1989,
leftBoundary: 410,
rightBoundary: 1634,
topBoundary: 765,
rotation: 0,
},
Wherein topBoundary
and bottomBoundary
are both offsets from the top of the image, and leftBoundary
and rightBoundary
are both offsets from the left of the image.
When rotating, because React Native uses the centre of objects as a transform origin, images need to be offset when in the 90° or 270° orientations, so that they are still "sticky" to the top/left corner and can be offset:
calculateRotationOffset.js
export default function (width, height) {
const transform = [
{ rotate: `${this.rotation}deg` },
]
/*
RN rotates around centre point, so we need to
manually offset the rotation to stick the image
to the top left corner so that our offsets will
work.
*/
if (this.rotation === 90) {
transform.push(
{ translateX: -((width - height) / 2) },
{ translateY: -((width - height) / 2) },
)
} else if (this.rotation === 270) {
transform.push(
{ translateX: ((width - height) / 2) },
{ translateY: ((width - height) / 2) },
)
}
return transform
}
Something about my rotation and zoom implementation are not working together.
Here is the plete snack example..
To reproduce the bug:
- Rotate the image once
- Pinch to zoom in or out
- Rotate the image again
The image will now be flipped and offset incorrectly.
There are a minimum of three transformations happening sequentially here to reproduce the bug, so I have not been able to track down the issue.
This question will definitely be bountied if necessary, and I might also offer a monetary reward. Thanks!
Update: After a few suggestions, it seems the best way to solve this problem is via matrix transformations, and then somehow convert this matrix back to styling that RN can represent onscreen. The matrix transformations need to support pan, rotate, and zoom.
Share Improve this question edited May 2, 2018 at 8:09 j_d asked Apr 24, 2018 at 9:03 j_dj_d 3,09210 gold badges56 silver badges96 bronze badges 18- 2 This sounds like a mon problem that occurs when you represent a transform with separate subtransforms that should not be separated. Do you have the option to directly set a matrix and bine different matrices? Btw, I don't know the react framework. This will most likely make this much easier. For example, the offsets must also rotate when you rotate the image. This will be done implicitly with a single matrix and appropriate operators. – Nico Schertler Commented Apr 24, 2018 at 18:32
- 1 @NicoSchertler I suppose that would be an option, but the reason I am doing things this way is to bounce transformed images from the master image, given the transform data, as easily as possible. Specifically, using the Cloudinary API. If you have an idea for a pletely rewritten solution, please contact me via PM. – j_d Commented Apr 25, 2018 at 6:54
-
1
These kinds of transforms are order dependent so check your code if you are using the same order of transforms as intended (while editing) but as Nico pointed out using cumulative single 3x3 uniform matrix is much much easier and foul proof than this. for more info take a look at this: Understanding 4x4 homogenous transform matrices they are 3D so yours would not have Z axis and Z coordinate (3x3 matrix) dropping (
Xz,Yz,Zx,Zy,Zz
and one of the zeros inZ?
collumn ) – Spektre Commented Apr 29, 2018 at 9:24 - 1 @Spektre Yes, I've seen this mentioned several times now, but I fail to see how this is easier. – j_d Commented Apr 30, 2018 at 8:03
-
1
instead of handling of open ordered list of transform mands that affect each other you apply an or remember only single 3x3 matrix no matter what. no
if
conditions what so ever – Spektre Commented Apr 30, 2018 at 15:07
1 Answer
Reset to default 1I'd replace the bottomBoundary
etc. with a deposed transformation matrix (rotation, scale, translation) and use multitouch in the UI. When sliding a single finger over the image, you can update the translation ponent of the matrix, and with 2 fingers transform it so that the image appears to stick to the fingers. If you want to constrain rotation to 90 degree increments, you can snap it when the user lets go. This allows you to move freely and intuitively within the image without requiring the current rotation buttons.
Storing the matrix always in deposed form avoids having to depose or normalize it to avoid accumulating numerical inaccuracy (skew and changing aspect ratio).
Creating a new DeposedMatrix
class would help structuring the code. The key is to handle transforming it roughly like so:
scaleBy(scale) {
this.scale *= scale;
this.x *= scale;
this.y *= scale;
}