Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make croppedAreaPixels more accurate and rounded #28

Merged
merged 1 commit into from
Mar 23, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
node_modules
dist
cypress/fixtures
cypress/videos
cypress/videos
cypress/screenshots
2 changes: 1 addition & 1 deletion cypress/integration/basic_spec.js
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ describe('Basic assertions', function() {
})

it('Display tall images and set the image and cropper with correct dimension', function() {
cy.visit('/?img=/images/cat.jpg')
cy.visit('/?img=/images/cat.jpeg')
cy.get('img').should('have.css', 'width', '338px')
cy.get('img').should('have.css', 'height', '600px')
cy.get('[data-testid=cropper]').should('have.css', 'width', '338px') // width of the image
File renamed without changes
53 changes: 42 additions & 11 deletions src/helpers.js
Original file line number Diff line number Diff line change
@@ -47,9 +47,10 @@ export function getDistanceBetweenPoints(pointA, pointB) {
* @param {{x: number, y number}} crop x/y position of the current center of the image
* @param {{width: number, height: number, naturalWidth: number, naturelHeight: number}} imageSize width/height of the src image (default is size on the screen, natural is the original size)
* @param {{width: number, height: number}} cropSize width/height of the crop area
* @param {number} aspect aspect value
* @param {number} zoom zoom value
*/
export function computeCroppedArea(crop, imgSize, cropSize, zoom) {
export function computeCroppedArea(crop, imgSize, cropSize, aspect, zoom) {
const croppedAreaPercentages = {
x: limitArea(
100,
@@ -62,16 +63,44 @@ export function computeCroppedArea(crop, imgSize, cropSize, zoom) {
width: limitArea(100, ((cropSize.width / imgSize.width) * 100) / zoom),
height: limitArea(100, ((cropSize.height / imgSize.height) * 100) / zoom),
}

// we compute the pixels size naively
const widthInPixels = limitArea(
imgSize.naturalWidth,
(croppedAreaPercentages.width * imgSize.naturalWidth) / 100,
true
)
const heightInPixels = limitArea(
imgSize.naturalHeight,
(croppedAreaPercentages.height * imgSize.naturalHeight) / 100,
true
)
const isImgWiderThanHigh = imgSize.naturalWidth >= imgSize.naturalHeight * aspect

// then we ensure the width and height exactly match the aspect (to avoid rounding approximations)
// if the image is wider than high, when zoom is 0, the crop height will be equals to iamge height
// thus we want to compute the width from the height and aspect for accuracy.
// Otherwise, we compute the height from width and aspect.
const sizePixels = isImgWiderThanHigh
? {
width: Math.round(heightInPixels * aspect),
height: heightInPixels,
}
: {
width: widthInPixels,
height: Math.round(widthInPixels / aspect),
}
const croppedAreaPixels = {
x: limitArea(imgSize.naturalWidth, (croppedAreaPercentages.x * imgSize.naturalWidth) / 100),
y: limitArea(imgSize.naturalHeight, (croppedAreaPercentages.y * imgSize.naturalHeight) / 100),
width: limitArea(
imgSize.naturalWidth,
(croppedAreaPercentages.width * imgSize.naturalWidth) / 100
...sizePixels,
x: limitArea(
imgSize.naturalWidth - sizePixels.width,
(croppedAreaPercentages.x * imgSize.naturalWidth) / 100,
true
),
height: limitArea(
imgSize.naturalHeight,
(croppedAreaPercentages.height * imgSize.naturalHeight) / 100
y: limitArea(
imgSize.naturalHeight - sizePixels.height,
(croppedAreaPercentages.y * imgSize.naturalHeight) / 100,
true
),
}
return { croppedAreaPercentages, croppedAreaPixels }
@@ -81,9 +110,11 @@ export function computeCroppedArea(crop, imgSize, cropSize, zoom) {
* Ensure the returned value is between 0 and max
* @param {number} max
* @param {number} value
* @param {boolean} shouldRound
*/
function limitArea(max, value) {
return Math.min(max, Math.max(0, value))
function limitArea(max, value, shouldRound = false) {
const v = shouldRound ? Math.round(value) : value
return Math.min(max, Math.max(0, v))
}

/**
9 changes: 6 additions & 3 deletions src/helpers.test.js
Original file line number Diff line number Diff line change
@@ -91,8 +91,9 @@ describe('Helpers', () => {
const crop = { x: 0, y: 0 }
const imgSize = { width: 1000, height: 600, naturalWidth: 2000, naturalHeight: 1200 }
const cropSize = { width: 500, height: 300 }
const aspect = 5 / 3
const zoom = 1
const areas = helpers.computeCroppedArea(crop, imgSize, cropSize, zoom)
const areas = helpers.computeCroppedArea(crop, imgSize, cropSize, aspect, zoom)
expect(areas.croppedAreaPercentages).toEqual({ x: 25, y: 25, width: 50, height: 50 })
expect(areas.croppedAreaPixels).toEqual({ height: 600, width: 1000, x: 500, y: 300 })
})
@@ -101,8 +102,9 @@ describe('Helpers', () => {
const crop = { x: 100, y: 30 }
const imgSize = { width: 1000, height: 600, naturalWidth: 2000, naturalHeight: 1200 }
const cropSize = { width: 500, height: 300 }
const aspect = 5 / 3
const zoom = 1
const areas = helpers.computeCroppedArea(crop, imgSize, cropSize, zoom)
const areas = helpers.computeCroppedArea(crop, imgSize, cropSize, aspect, zoom)
expect(areas.croppedAreaPercentages).toEqual({ height: 50, width: 50, x: 15, y: 20 })
expect(areas.croppedAreaPixels).toEqual({ height: 600, width: 1000, x: 300, y: 240 })
})
@@ -111,8 +113,9 @@ describe('Helpers', () => {
const crop = { x: 0, y: 0 }
const imgSize = { width: 1000, height: 600, naturalWidth: 2000, naturalHeight: 1200 }
const cropSize = { width: 500, height: 300 }
const aspect = 5 / 3
const zoom = 2
const areas = helpers.computeCroppedArea(crop, imgSize, cropSize, zoom)
const areas = helpers.computeCroppedArea(crop, imgSize, cropSize, aspect, zoom)
expect(areas.croppedAreaPercentages).toEqual({ height: 25, width: 25, x: 37.5, y: 37.5 })
expect(areas.croppedAreaPixels).toEqual({ height: 300, width: 500, x: 750, y: 450 })
})
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -226,6 +226,7 @@ class Cropper extends React.Component {
restrictedPosition,
this.imageSize,
this.state.cropSize,
this.props.aspect,
this.props.zoom
)
this.props.onCropComplete &&