Skip to content

Commit

Permalink
Make croppedAreaPixels more accurate and rounded (#28)
Browse files Browse the repository at this point in the history
In order to make croppedAreaPixels more accurate, we now compute the width
or the height (depending on the image ratio) based on the aspect property and
the width/height.

Also, we now round pixels values so they are integers.

Address #27

Demo of @raRaRa with this fix: https://codesandbox.io/s/0qk168vl10
  • Loading branch information
ValentinH committed Mar 23, 2019
1 parent b8651ba commit 744b795
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 16 deletions.
3 changes: 2 additions & 1 deletion .gitignore
@@ -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
Expand Up @@ -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
Expand Down
File renamed without changes
53 changes: 42 additions & 11 deletions src/helpers.js
Expand Up @@ -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,
Expand All @@ -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 }
Expand All @@ -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))
}

/**
Expand Down
9 changes: 6 additions & 3 deletions src/helpers.test.js
Expand Up @@ -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 })
})
Expand All @@ -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 })
})
Expand All @@ -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 })
})
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Expand Up @@ -226,6 +226,7 @@ class Cropper extends React.Component {
restrictedPosition,
this.imageSize,
this.state.cropSize,
this.props.aspect,
this.props.zoom
)
this.props.onCropComplete &&
Expand Down

0 comments on commit 744b795

Please sign in to comment.