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

Please update the docs to include an explanation of how to access and use the cropped image #529

Open
andersr opened this issue Jan 2, 2023 · 15 comments

Comments

@andersr
Copy link

andersr commented Jan 2, 2023

I am not able to locate any information in the docs on how to actually access the cropped version of the image, so that it can be used. It appears I am not alone. These all seems to be variations on the same question: #525, #523, #522, #501, #457, and #501.

If you could please update the docs (or point it out in case I missed it) for how to actually access the cropped version of the image, that would great. Thank you.

@Joshuajrodrigues
Copy link

Hey did you figure thus out by any chance ?

@sekoyo
Copy link
Owner

sekoyo commented Jan 17, 2023 via email

@Joshuajrodrigues
Copy link

Is there a possibility to do this without canvas ? Like what if the user doesnt want a preview. Just drop, crop and submit ?

@sekoyo
Copy link
Owner

sekoyo commented Jan 17, 2023 via email

@andersr
Copy link
Author

andersr commented Jan 17, 2023

@dominictobias thanks for this tip. By offscreen, is this simply a css-based off screen, or are you referring to this: https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas

@sekoyo
Copy link
Owner

sekoyo commented Jan 17, 2023 via email

@Joshuajrodrigues
Copy link

Hey sorry to disturb here again but how would you handle this for a circular crop ?

@sekoyo
Copy link
Owner

sekoyo commented Jan 18, 2023

Hey sorry to disturb here again but how would you handle this for a circular crop ?

I’ve not personally tried to do a circle crop to canvas. Normally I’d say just do it with CSS for reasons explained here - #409 (comment)

but if you want to create a downloadable version with a circle you’d have to do it on the canvas as a black circular border or something ⚫️ which I haven’t tried to do before but if you can find some code to do it, then you can just paste it after the other canvas code to do it over the top

@sekoyo
Copy link
Owner

sekoyo commented Jan 18, 2023

I was thinking with CSS but this sounds like a better solution 👍

for example:

https://pqina.nl/blog/applying-a-circular-crop-mask-to-an-image/#applying-a-circular-mask-using-canvas

@lucasprins
Copy link

function onSubmitCrop() {
    if (completedCrop) {
      // create a canvas element to draw the cropped image
      const canvas = document.createElement("canvas");

      // get the image element
      const image = imgRef.current;

      // draw the image on the canvas
      if (image) {
        const crop = completedCrop;
        const scaleX = image.naturalWidth / image.width;
        const scaleY = image.naturalHeight / image.height;
        const ctx = canvas.getContext("2d");
        const pixelRatio = window.devicePixelRatio;
        canvas.width = crop.width * pixelRatio * scaleX;
        canvas.height = crop.height * pixelRatio * scaleY;

        if (ctx) {
          ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
          ctx.imageSmoothingQuality = "high";

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

        const base64Image = canvas.toDataURL("image/png"); // can be changed to jpeg/jpg etc

        if (base64Image) {
          // @ts-ignore
          const fileType = base64Image.split(";")[0].split(":")[1];

          const buffer = Buffer.from(
            base64Image.replace(/^data:image\/\w+;base64,/, ""),
            "base64"
          );
          const file = new File([buffer], fileName, { type: fileType });
          onSubmit(file); // function passed as a prop
        }
      }
    }
  }

If anyone is still having trouble, this is how I did it.

@sekoyo
Copy link
Owner

sekoyo commented Feb 11, 2023

You can create a hidden link:

const hiddenAnchorRef = useRef<HTMLAnchorElement>(null)
const blobUrlRef = useRef('')

<a
  ref={hiddenAnchorRef}
  download
  style={{
    position: 'absolute',
    top: '-200vh',
    visibility: 'hidden',
  }}
>
  Hidden download
</a>

And you can create a blob, create a URL with the blob, assign it to the anchor, trigger a click:

function onDownloadCropClick() {
    if (!previewCanvasRef.current) {
      throw new Error('Crop canvas does not exist')
    }

    previewCanvasRef.current.toBlob((blob) => {
      if (!blob) {
        throw new Error('Failed to create blob')
      }
      if (blobUrlRef.current) {
        URL.revokeObjectURL(blobUrlRef.current)
      }
      blobUrlRef.current = URL.createObjectURL(blob)
      hiddenAnchorRef.current!.href = blobUrlRef.current
      hiddenAnchorRef.current!.click()
    })
  }

Note that if you doubled the size of the canvas on e.g. retina screens to get extra sharpness (I do this in canvasPreview.ts based on window.devicePixelRatio), then you also need to size the canvas down by a factor of window.devicePixelRatio which makes things more complicated. You would have to copy to an offscreen canvas that is a normal size first.

@AnnieTaylorCHEN
Copy link

function onSubmitCrop() {
    if (completedCrop) {
      // create a canvas element to draw the cropped image
      const canvas = document.createElement("canvas");

      // get the image element
      const image = imgRef.current;

      // draw the image on the canvas
      if (image) {
        const crop = completedCrop;
        const scaleX = image.naturalWidth / image.width;
        const scaleY = image.naturalHeight / image.height;
        const ctx = canvas.getContext("2d");
        const pixelRatio = window.devicePixelRatio;
        canvas.width = crop.width * pixelRatio * scaleX;
        canvas.height = crop.height * pixelRatio * scaleY;

        if (ctx) {
          ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
          ctx.imageSmoothingQuality = "high";

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

        const base64Image = canvas.toDataURL("image/png"); // can be changed to jpeg/jpg etc

        if (base64Image) {
          // @ts-ignore
          const fileType = base64Image.split(";")[0].split(":")[1];

          const buffer = Buffer.from(
            base64Image.replace(/^data:image\/\w+;base64,/, ""),
            "base64"
          );
          const file = new File([buffer], fileName, { type: fileType });
          onSubmit(file); // function passed as a prop
        }
      }
    }
  }

This works magic! For anyone with buffer error, just npm i buffer and import {Buffer} from 'buffer'; it should solve the issue.

If your backend is using multer package to parce, you can do

const formDataForImage = new FormData();
 formDataForImage.append('file', fileCropped);

then attach that formDataForImage as obj in the body in the post request, it should work.

@AnnieTaylorCHEN
Copy link

@dominictobias Can you also add the above solution in the documentation as reference? Thanks!

@AnnieTaylorCHEN
Copy link

Hi, it worked locally in dev but I ran into a lot of issues with the build because of the Buffer. 😅 Currently using Vite/React and I have referred to this solution:
https://stackoverflow.com/questions/72773373/buffer-is-not-exported-by-vite-browser-externalbuffer

I used the OP's inject method:

rollupOptions: {
        plugins: [inject({Buffer: ['Buffer', 'Buffer']})],
        external: ['Buffer'],
      },

but I am getting errors
Uncaught TypeError: Failed to resolve module specifier "Buffer". Relative references must start with either "/", "./", or "../".
in deployment ( it pass the build in pipeline). So I don't think I write it correctly.... I know this is probably a Vite related issue, but in case anyone knows, please give me some tip on how to fix this? Thanks! 🙏

@AnnieTaylorCHEN
Copy link

AnnieTaylorCHEN commented Mar 9, 2023

Hi, I solved my issues now:

By now I completely ditched the previous solution.

  1. npm i process buffer.
  2. add those to the index.html, just under
<script>
      window.global = window;
    </script>
    <script type="module">
      import {Buffer} from 'buffer/'; // <-- no typo here ("/")
      import process from 'process';
      window.Buffer = Buffer;
      window.process = process;
    </script>
  1. in vite.config.ts
 resolve: {
      alias: {
        process: 'process/browser',
      },
    },
    ```
4. in the component where you need buffer, `import {Buffer} from 'buffer/index.js';`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants