136 lines
3.1 KiB
JavaScript
136 lines
3.1 KiB
JavaScript
"use strict";
|
|
import React, { useState, useRef } from "react";
|
|
import ReactCrop, {
|
|
centerCrop,
|
|
makeAspectCrop,
|
|
PixelCrop,
|
|
} from "react-image-crop";
|
|
import "react-image-crop/src/ReactCrop.scss";
|
|
|
|
export async function blobImgCrop(image, crop) {
|
|
const canvas = document.createElement("canvas");
|
|
canvasPreview(image, canvas, crop);
|
|
|
|
const blob = await toBlob(canvas);
|
|
|
|
if (!blob) {
|
|
console.error("Failed to create blob");
|
|
return "";
|
|
}
|
|
return blob;
|
|
}
|
|
|
|
function toBlob(canvas) {
|
|
return new Promise((resolve) => {
|
|
canvas.toBlob(resolve);
|
|
});
|
|
}
|
|
|
|
export async function canvasPreview(image, canvas, crop) {
|
|
const ctx = canvas.getContext("2d");
|
|
|
|
if (!ctx) {
|
|
throw new Error("No 2d context");
|
|
}
|
|
|
|
const scaleX = image.naturalWidth / image.width;
|
|
const scaleY = image.naturalHeight / image.height;
|
|
// devicePixelRatio slightly increases sharpness on retina devices
|
|
// at the expense of slightly slower render times and needing to
|
|
// size the image back down if you want to download/upload and be
|
|
// true to the images natural size.
|
|
const pixelRatio = window.devicePixelRatio;
|
|
// const pixelRatio = 1
|
|
|
|
canvas.width = Math.floor(crop.width * scaleX * pixelRatio);
|
|
canvas.height = Math.floor(crop.height * scaleY * pixelRatio);
|
|
|
|
ctx.scale(pixelRatio, pixelRatio);
|
|
ctx.imageSmoothingQuality = "high";
|
|
|
|
const cropX = crop.x * scaleX;
|
|
const cropY = crop.y * scaleY;
|
|
|
|
const centerX = image.naturalWidth / 2;
|
|
const centerY = image.naturalHeight / 2;
|
|
|
|
ctx.save();
|
|
|
|
// 5) Move the crop origin to the canvas origin (0,0)
|
|
ctx.translate(-cropX, -cropY);
|
|
// 4) Move the origin to the center of the original position
|
|
ctx.translate(centerX, centerY);
|
|
// 1) Move the center of the image to the origin (0,0)
|
|
ctx.translate(-centerX, -centerY);
|
|
ctx.drawImage(
|
|
image,
|
|
0,
|
|
0,
|
|
image.naturalWidth,
|
|
image.naturalHeight,
|
|
0,
|
|
0,
|
|
image.naturalWidth,
|
|
image.naturalHeight
|
|
);
|
|
|
|
ctx.restore();
|
|
}
|
|
|
|
export function AvatarCropper(props) {
|
|
const [crop, setCrop] = React.useState({
|
|
unit: "%",
|
|
x: 10,
|
|
y: 10,
|
|
width: 80,
|
|
height: 80,
|
|
});
|
|
if (props["onStart"]) {
|
|
props["onStart"](crop);
|
|
}
|
|
return React.createElement(
|
|
ReactCrop,
|
|
{
|
|
aspect: 1,
|
|
crop: crop,
|
|
onChange: (c) => {
|
|
setCrop(c);
|
|
if (props["onChange"]) props["onChange"](c);
|
|
},
|
|
minWidth: 50,
|
|
minHeight: 50,
|
|
onComplete: (local_crop, pcrop) => {
|
|
if (props["onComplete"]) {
|
|
props["onComplete"](local_crop);
|
|
}
|
|
},
|
|
},
|
|
React.createElement("img", {
|
|
src: props.src,
|
|
onLoad: (e) => {
|
|
const { naturalWidth: width, naturalHeight: height } = e.currentTarget;
|
|
|
|
const crop_local = centerCrop(
|
|
makeAspectCrop(
|
|
{
|
|
width: 50,
|
|
height: 50,
|
|
},
|
|
1,
|
|
width,
|
|
height
|
|
),
|
|
width,
|
|
height
|
|
);
|
|
|
|
setCrop(crop_local);
|
|
console.log(props["onLoad"]);
|
|
if (props["onLoad"]) {
|
|
props["onLoad"](crop_local);
|
|
}
|
|
},
|
|
})
|
|
);
|
|
}
|