Solvedreact image crop Retain Image Quality after Crop on front-end?

I am curious if this package retains the quality of the original image after the crop... if cropping happens on the front end as is shown in the demo?

The reason I ask is when I use the example (as shown below), the resulting crop looks a lot more pixelated and less quality image so I am curious if that is what the end result is on the front end?

screenshot

The workaround would be to pass the crop parameters and the original image to backend and have another package crop the image, but that would require a lot more work so is there a way to do this well on the front-end?

35 Answers

✔️Accepted Answer

Here is my code, @DominicTobias is right abour the quality parameter.

The problem with the original code of the following is that it was making the canvas size in relation to the cropper size and not the native image size. So if your Cropper was shrunk by css or smaller/bigger container size, it would effect the final canvas image size.

See belows changes, you may add the image back in parameters if you want, the only changes is the quality "1" parameter in toBlob, and the crop.width*scaleX, crop.height*scaleY.

Math.ceil to make sure the natural image isn't reduced by 1 pixel sometimes.

getCroppedImg(crop, fileName) {
        let image = this.imageRef;
        const canvas = document.createElement('canvas');
        const scaleX = image.naturalWidth / image.width;
        const scaleY = image.naturalHeight / image.height;
        canvas.width = Math.ceil(crop.width*scaleX);
        canvas.height = Math.ceil(crop.height*scaleY);
        const ctx = canvas.getContext('2d');
        ctx.drawImage(
          image,
          crop.x * scaleX,
          crop.y * scaleY,
          crop.width * scaleX,
          crop.height * scaleY,
          0,
          0,
          crop.width*scaleX,
          crop.height*scaleY,
        );
        // As Base64 string
        // const base64Image = canvas.toDataURL('image/jpeg');
        // As a blob
        return new Promise((resolve, reject) => {
          canvas.toBlob(blob => {
            blob.name = fileName;
            resolve(blob);
          }, 'image/jpeg',1);
        });
      }

Other Answers:

To retain image quality after crop on front-end, just make the canvas size larger. In my case, I also want to restrict the cropped image width under 1200px. Below is my code:

getCroppedImg(image, crop, fileName) {
    const canvas = document.createElement("canvas");
    const scaleX = image.naturalWidth / image.width;
    const scaleY = image.naturalHeight / image.height;
    var originWidth = crop.width * scaleX;
    var originHeight = crop.height * scaleY;
    // maximum width/height
    var maxWidth = 1200, maxHeight = 1200 / (16 / 9);
    var targetWidth = originWidth,
      targetHeight = originHeight;
    if (originWidth > maxWidth || originHeight > maxHeight) {
      if (originWidth / originHeight > maxWidth / maxHeight) {
        targetWidth = maxWidth;
        targetHeight = Math.round(maxWidth * (originHeight / originWidth));
      } else {
        targetHeight = maxHeight;
        targetWidth = Math.round(maxHeight * (originWidth / originHeight));
      }
    }
    // set canvas size
    canvas.width = targetWidth;
    canvas.height = targetHeight;
    const ctx = canvas.getContext("2d");

    ctx.drawImage(
      image, 
      crop.x * scaleX, 
      crop.y * scaleY, 
      crop.width * scaleX, 
      crop.height * scaleY, 
      0, 
      0, 
      targetWidth, 
      targetHeight 
    );

    return new Promise((resolve, reject) => {
      canvas.toBlob(
        blob => {
          if (!blob) {
            console.error("Canvas is empty");
            return;
          }
          blob.name = fileName;
          window.URL.revokeObjectURL(this.fileUrl);
          this.fileUrl = window.URL.createObjectURL(blob);
          resolve(this.fileUrl);
        },
        "image/png",
        1
      );
    });
  }

Hey @peter-hoa, could you provide us with a code example for your solution? Would really appreciate :)

Hello there! I have been trying out your project, and, have come across the exact same issue.

What I believe is happening is that the function provided to generate a crop needs, instead of using the image that's being displayed on-screen, to use the full-resolution image. When javascript uses the displayed image as the source for the cropping into the canvas, it's only being given access to the post-resample image, not the original image.

Think about what the web browser does to make the image fit into the available space for the user.
It's re-sampling the original image, and putting that into the image element that you're seeing on-screen. I guess behind the scenes, it's actually got the original image hidden somewhere (so it can resize without having to pull the data again).

The image data available in javascript however, is the image that's on the display, which means, it's not the 'full resolution' image. Unfortunate, it is, that we can not access the 'original' image data directly.

So, that's where the lack of resolution in the cropped image is coming from.

What I have been doing here is: create a hidden image element (which holds the full-resolution image because the browser is not trying to resize it to fit inside anything 'visible'), and when it comes time to create the crop, use that hidden image as the source instead of the visible image.

The crop object has details that are relative to the displayed image. Scale those numbers by the ratio of the original resolution : displayed resolution, and you now know what the coordinates need to be for populating the cropping canvas using the full resolution image.

Does that make sense?

Oh, and: the author did have somewhere that it was intended to be used to determine what crop details should be sent to a server-side service, to have the server do the work of cropping the image proper. So, I'd say it is 'working as designed'.

But, like others, I have a need to perform the cropping work entirely within the browser (on the client), which is why/where this issue becomes evident. So, yeah, it would be good if the project could provide a way to allow the crop to be created entirely on the client at maximum available resolution, without having to account for it separately.

Hi the quality is down to the settings of canvas.toBlob - https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob

You see I've set it to image/jpeg if you remove that it defaults to PNG which is lossless. Also the last argument is qualityArgument between 0 and 1, I'm not sure what the default value is but I'm using the default in the demo which may not be 1.

The library itself doesn't have a crop preview built in it's more "this is how you could do it", there are other ways on the server (not sure about the client though).

Edit: See my post near the bottom for a solution to blurriness. The HOOKS demo has also been adjusted to fix it, you can apply it to a class based instance too I just couldn't be bothered to update that one.

Related Issues:

85
react image crop Retain Image Quality after Crop on front-end?
Here is my code @DominicTobias is right abour the quality parameter I am curious if this package ret...
3
ksnip Drag and Drop from ksnip to other applications
This is now implemented with Left Mouse+Shift When taking screenshot it apears in the ksnipwindow an...
3831
axios Axios catch error returns javascript error not server response
I have exactly the same environment Try this: Modify from console.log(error) to console.log(error.re...
2346
jest Bug: Watch mode on Linux causes a ENOSPC Node.js error
From my findings its not related to Jest at all On Linux (or Mac) we have a max number of system wat...
1975
react RFClarification: why is setState asynchronous?
So here’s a few thoughts This is not a complete response by any means but maybe this is still more h...
1745
react Preventing rerenders with React.memo and useContext hook.
This is working as designed There is a longer discussion about this in #14110 if you're curious ...
1400
TypeScript Quick fix for 'unions can't be used in index signatures, use a mapped object type instead'
You can do this: Though Bar has no index signature (i.e. you can't then do (obj as Bar)[value as Foo...
811
webpack Cannot assign to read only property 'exports' of object '#<Object>' (mix require and export)
The code above is ok You can mix require and export You can't mix import and module.exports. ...
778
yarn Yarn Debian key expiry date updated (EXPKEYSIG 23E7166788B63E1E)
sudo apt-key adv --refresh-keys --keyserver keyserver.ubuntu.com also works to fix this issue for ma...
727
webpack How to exclude node_modules but one
@borm: a solution: Subj as example I create some module in another folder ( /projects/MY_MODULE ) MY...
720
yarn GPG error: https://dl.yarnpkg.com/debian stable InRelease NO_PUBKEY E074D16EB6FF4DE3
Doing curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - again was enough. ...
611
react React Fire: Modernizing React DOM
I love every of these points except the className change For latest status see an update from June 5...
608
angular Angular5.x lazyLoad problem, undefined is not a function
For others that find this issue via Google as i did: I had the same problem when trying to lazy load...
595
react starter kit How to call child component method from parent?
For example you can use Refs to Components approach like so: Demo: https://jsfiddle.net/frenzzy/z9c4...
561
react How should we set up apps for HMR now that Fast Refresh replaces react-hot-loader?
Okay here goes What Is Fast Refresh? It's a reimplementation of hot reloading with full support from...
548
svgo no such file or directory .svgo.yml
I noticed this problem with Yarn after someone had run yarn clean Deleting the .yarnclean file delet...
521
ant motion 模版代码下载下来导入到dva搭建的项目里面,启动时报错(npm start)
开启 JavaScript 就可以了 你好,我下载的Home项目引入到项目工程里面,为了方便,我全部放到了components下面,引入和基本流程没有问题。在家里的win10上面启动没有报错,在win...
516
react native gesture handler Unsupported top level event type "onGestureHandlerStateChange" dispatched
Because my navigators were created asynchronously the handler was registered too late and thus throw...
506
electron Requiring electron outside of main.js causes a TypeError
For anyone encountering this problem in the future and reading this thread Electron version: 1.3.5 O...
499
babel ReferenceError regeneratorRuntime is not defined
I had this issue using rollup with babel I just used this babel config to resolve it : ...
488
webpack nodejs 17: digital envelope routines::unsupported
workaround: Bug report What is the current behavior? Other relevant information: webpack version: 5....
474
meteor [1.4.2.1] Error: ENFILE: file table overflow
I was getting the same after an upgrade to macOS Sierra Turns out macOS have a harsh limit on number...
473
webpack TypeError: Data must be a string or a buffer
Here is a workaround to help you to find the wrong import Using the latest 2.2.0 release although th...
429
vue router how to change document.title in vue-router?
Hi thanks for filling this issue You can simply define title in route's meta I really want set title...
425
babel eslint ESLint: 2.10.0 - Parsing error: Unexpected token =
parser: babel-eslint is OK! I'm using: and getting the following error: Parsing error: Unexpected to...
425
jest Error in Async Example: ReferenceError: regeneratorRuntime is not defined
This worked for me to fix ReferenceError: regeneratorRuntime is not defined in Jest: Then in .babelr...
417
jest babel-jest does not transpile import/export in node_modules when Babel 7 is used
For the record 🐛 Bug Report I started getting the dreaded SyntaxError: Unexpected token import erro...
413
react Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
You're exporting a React element not a component Change to Alternatively at the call site change <Co...
411
react Trigger simulated input value change for React 16 (after react-dom 15.6.0 updated)?
After some research of react source code I got a hack method for react 16: NOTICE: JUST A HACK ...
408
jest "Syntax Error: Invalid or unexpected token" with .png
I had the same error and resolved it by creating a assetsTransformer.js: Then add this to your jest ...
407
jest Watch mode stopped working on macOS Sierra
I had the same issue Upgrading watchman with brew install watchman to v4.7.0 fixed it. ...
397
material ui Module not found: Can't resolve 'material-ui-icons/Menu' Martial Next
For anyone else experiencing this issue: npm install @material-ui/icons https://www.npmjs.com/packag...
391
javascript How to configure React Native (Expo) project to use AirBnB's React rules via ESLint?
This is what I do in React and React Native: Step 1 Step 2 Backup your eslintrc file in case you wan...
383
yarn ERROR: There are no scenarios; must have at least one.
You've got the wrong yarn 😄 The Yarn you're executing comes from the cmdtest package: http://manpag...
378
webpacker localIdentName option moved in css-loader configuration
I faced same issue after upading css-loader but I solved it If you check css-loader readme ...
376
react native gesture handler Execution failed for task ':react-native-gesture-handler:compileDebugJavaWithJavac'
@zmGitHub npm i jetifier npx jetify fixed it for me on rn 0.6 I am fixing a incompatibility issue re...
358
atom Sometimes opening a file, Atom does not open in a new tab
This feature is called Pending Pane Items (formerly known as Preview Tabs) – this allows you to quic...
352
axios POST request works in Browser but not on Node
This might be considered a duplicate of #789 I was able to use the form-data package with Axios in n...
348
angular Cyclic dependency error with HttpInterceptor
I resolved simply not setting authService in constructor but getting in the intercept function. ...
337
lodash Find if array includes all elements from another array
You can do _.difference(subset superset).length === 0 to achieve this currently ...
333
fetch Error when POST file multipart/form-data
Setting the Content-Type header manually means it's missing the boundary parameter Remove that heade...
328
amplify js Uncaught ReferenceError: global is not defined in latest Angular 6 RC
Just for reference I have passed through this issue with adding these lines on my index.html head: ...
328
yarn Installation Problem: .yarn-metadata.json: Unexpected end of JSON input
I triggered this condition after a failed install of lodash.isfunction: If you trigger this you can ...
320
sequelize Class constructor Model cannot be invoked without 'new' and how getter v4 work?
Intypescript case my solution was to change target from es5 to es6 Hi all ...
310
yarn ERROR: [Errno 2] No such file or directory: 'add'
I was facing the same problem I am using Ubuntu (17.04) and used sudo apt install yarn to install ya...
301
react TypeError: func.apply is not a function / Uncaught TypeError: destroy is not a function
TypeError: destroy is not a function This is the real error and indicates that you're returning a va...
296
jest requestAnimationFrame warning with React 16
@mbifulco I managed to get this working for all test cases by loading a simple shim before each spec...
296
webpack webpack 4: access the mode flag from webpack.config.js file
This seems to work correctly with --mode production -p and <no flag> Do you want to request a featur...
284
webpack Webpack gives $ is not defined or jQuery is not defined error in console
Just use like this or add to webpack Do you want to request a feature or report a bug? What is the c...
284
yarn There appears to be trouble with your network connection. Retrying...
Adding this comment for other Googlers: What worked for me was increasing the Yarn network timeout: ...