предисловие
Эта статья основана наcanvasДля реализации инструмента обрезки изображения. потому чтоcanvasКод все еще относительно длинный, попробуйте написать идеи, полный код помещен вgithubначальство.
Проблема размытия холста
это написаноcanvasВопросы, с которыми надо обращаться, ответы на это в интернете тоже есть везде, поэтому подробно их представлять не буду.
потому чтоcanvasне векторная иллюстрация, вRetinaПод экраном браузер использует несколько пикселей для отображения пикселя, в результате чегоcanvasНаконец, есть проблема размытия.
решение:
- Получать
window.devicePixelRatioФизическое разрешение устройства в пикселях такое же, какCSSСоотношение разрешений пикселей. -
canvas contextимеет атрибутbackingStorePixelRatioвизуализация презентацииcanvasРаньше для хранения информации о холсте использовалось несколько пикселей. Но это доступно только в некоторых браузерах, например.safari - установив
canvas.width/heightа такжеcanvas.style.width/heightправильноcanvasМасштаб обработки, соотношениеdevicePixelRatio/backingStorePixelRatio(ratio). (canvas.width/heightпредставляет фактический размер холста, аcanvas.style.width/heightУказывает размер результата рендеринга в браузере) - наконец пройти
context.scale(ratio, ratio)правильноcanvasпроцесс, исправить его рендеринг
Если вы используетеtypescriptЕсли да, то сообщитbackingStorePixelRatioНет ошибки, плюс файл определения типа для решения.
export const getPixelRatio = (context: CanvasRenderingContext2D) => {
const backingStore =
context.backingStorePixelRatio ||
context.webkitBackingStorePixelRatio ||
context.mozBackingStorePixelRatio ||
context.msBackingStorePixelRatio ||
context.oBackingStorePixelRatio || 1;
return (window.devicePixelRatio || 1) / backingStore;
};
const calcCanvasSize = () => {
//...dosth.
canvasRef.current.style.width = `${canvasWidth}px`;
canvasRef.current.style.height = `${canvasHeight}px`;
canvasRef.current.width = canvasWidth * ratio;
canvasRef.current.height = canvasHeight * ratio;
ctx.scale(ratio, ratio);
};
//省略不必要代码
Нарисовать изображение на холсте
Это, собственно, черезinputПолучите локальный файл изображения черезwindow.URL.createObjectURLполучатьDOMString, прими какimgизsrc. пройти черезctx.drawImageнарисовать картину, чтобыcanvasначальство.
Потому что для инструмента обрезки изображенияimgОн должен быть окрашен в нижней части, поэтому он должен пройтиglobalCompositeOperation, нарисуйте его на нижнем слое. (globalCompositeOperationПредставляет, как нарисовать исходное (новое) изображение на целевом (существующем) изображении. )
const handleChoiseImg = () => {
if (createURL) {
window.URL.revokeObjectURL(createURL);
};
createURL = window.URL.createObjectURL(inputRef.current!.files![0]);
img = new Image();
img.onload = () => {
//initImageCanvas(img); 这个函数我是去获取img应该缩小比例和缩小宽高
// calcCanvasSize(); 这个我是去获取canvas应该呈现的size
drawImage(); //绘画img
};
img.src = createURL;
};
const drawImage = () => {
// todo sth.
ctx.save();
ctx.globalCompositeOperation = 'destination-over';
// ctx.translate(canvasWidth / 2, canvasHeight / 2);
// ctx.rotate(Math.PI / 180 * rotate);
// if (rotate % 180 !== 0) {
// [canvasWidth, canvasHeight] = [canvasHeight, canvasWidth];
// };
// ctx.translate(-canvasWidth / 2, - canvasHeight / 2);
ctx.drawImage(
img,
(canvasWidth - scaleImgWidth) / 2, (canvasHeight - scaleImgHeight) / 2,
scaleImgWidth, scaleImgHeight
);
// canvasWidth/Height表示canvas的宽高(style),scaleImgWidth/Height表示图片缩放后的宽高
ctx.restore();
};
Маски и флажки
маскировка
или использоватьglobalCompositeOperationНарисуйте его поверх существующего изображения.
const drawCover = () => {
ctx.save();
ctx.fillStyle = 'rgba(0,0,0,0.5)';
ctx.fillRect(0, 0, canvasSize.width, canvasSize.height);
ctx.globalCompositeOperation = 'source-atop';
ctx.restore();
};
флажок
На самом деле, установив флажок,clearRectОчистите область маски и раскрасьте собственную рамкуstyle, и наконецimgЖивопись находится на нижнем слое.
canvasАнимации рисуются кадр за кадром, процесс перетаскивания выделенного прямоугольника на самом деле постоянный.clearRectвесьcanvas, а затем снова пройти описанный выше процесс, то есть процесс перекраски.
const drawSelect = (x: number, y: number, w: number, h: number) => {
ctx.clearRect(0, 0, canvasSize.width, canvasSize.height);
//清空整个canvas
drawCover();
//绘画蒙层
ctx.save();
ctx.clearRect(x, y, w, h);
//清空选中区域
ctx.strokeStyle = '#5696f8';
ctx.strokeRect(x, y, w, h);
// 画选中框
// todo sth. 给选中框加一些style
ctx.restore();
drawImage();
// 绘画图片
};
Флажок перетащить растягивание и обработка границ
Установите флажок и перетащите и растяните его, даmouseобработка событий, вmouseDown, дайте ему идентификатор, вmouseMoveВыбранное поле постоянно обновляется и рисуется, вmouseUpФлаг отмены (это событие можно передать внешнему контейнеру).
Граница обработки праваmouseMoveОбработанное положение флажка обрабатывается и оценивается.Если оно превышает границу, оно будет исправлено.
вот такoffsetXа такжеoffsetYОбработайте его, а затем судите, как изменить флажок в разных направлениях.Поскольку объем кода относительно велик, его можно полностьюgithubСмотреть.
Изображение эффекта:
Обработка поворота изображения
canvasЦентром вращения является левый верхний угол как центр, если напрямую вызыватьrotate, то результат точно не тот, что мы хотим. затем используйте егоtranslateдвигатьсяcanvasв центральную точку, а затем вызовитеrotateВращение и повторное использование после вращенияtranslateБудуcanvasВернитесь на его место.
Единственная проблема - разобратьсяrotateПосле ВасtranslateСковородаcanvasэтот разx、yценность .
Моя обработка инструмента обрезки изображения здесь заключается в его изменении после поворота.canvasизwidth/height&style width/height. В настоящее время,canvasвращается, ноimageПри перерисовке вам также необходимо нарисовать повернутое изображение, а затем использовать метод, упомянутый выше, для поворота картины.
И не забудьте пройтиsave & restoreдля сохранения и восстановления состояния чертежа.
const drawImage = () => {
// todo sth.
ctx.save();
ctx.globalCompositeOperation = 'destination-over';
ctx.translate(canvasWidth / 2, canvasHeight / 2);
ctx.rotate(Math.PI / 180 * rotate);
if (rotate % 180 !== 0) {
[canvasWidth, canvasHeight] = [canvasHeight, canvasWidth];
};
ctx.translate(-canvasWidth / 2, - canvasHeight / 2);
ctx.drawImage(
img,
(canvasWidth - scaleImgWidth) / 2, (canvasHeight - scaleImgHeight) / 2,
scaleImgWidth, scaleImgHeight
);
ctx.restore();
};
Изображение эффекта:
Масштабирование изображения
scaleОн также основан на верхнем левом углу в качестве центра масштабирования, а затем также необходимо масштабировать.save & restore, иначе это повлияет на последующие операции.
Однако я не использовалscale, но вручную измените масштаб изображения, а затем получитеscaleImgWidthа такжеscaleImgHeight, перед вызовомdrawImage. Поскольку код отображается в центре, его можно вызвать сразу после модификации.
// 修改 scaleImg 得到scaleImgWidth & scaleImgHeight
ctx.drawImage(
img,
(canvasWidth - scaleImgWidth) / 2, (canvasHeight - scaleImgHeight) / 2,
scaleImgWidth, scaleImgHeight
);
Изображение эффекта:
Обработка изображений в градациях серого
Обработка оттенков серого выполняетсяgetImageDataПолучатьcanvasизImageDataТо есть пиксельные данные, которые можно обрабатывать. Затем снова передайте обработанные данные пикселей.putImageDataположить обратноcanvasначальство.
Пиксельные данные, есть четыре аспекта информации для каждого пикселя, а именноRed,Green,Blue,Alpha.
Формул обработки оттенков серого еще довольно много, и я буду использовать их здесь.(R + 2G + B) >> 2.
const imgData = ctx.getImageData(0, 0, canvasSize.width * ratio, canvasSize.height * ratio);
getGrayscaleData(imgData);
ctx.putImageData(imgData, 0, 0);
Кроме того, можно выполнять многие аналогичные обработки, такие как обработка контрастного цвета, палитра цветов и т. д.
Изображение эффекта:
Отображение выбранных изображений в реальном времени
Лишь бы перехватитьcanvasЧасть, показанная до сих пор, не очень дружелюбна. Оно должно соответствовать соответствующему положению исходной картинки, и выбирать картинку в этой позиции удобнее.
Обработка идей:
- Недавно созданный
canvas,БудуimgНа нем полная картина, и задача вращения решена. - установив флажок
x y w hзначение иimg width/heightа такжеcanvas width/heightЗначение , чтобы получить значение, соответствующее выбранной части исходного изображенияx y - пройти через
getImageDataполучатьImageData, и определите, требуется ли обработка оттенков серого - Затем повторно измените созданный выше
canvasизwidth/heightдля выбранной части изображенияputW putH - Буду
ImageDataпройти черезputImageDataположить вcanvasсредний проходtoBlobполучатьblob - наконец прошло
window.URL.createObjectURLполучатьDOMString
export const getPhotoData = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
// todo canvas处理
ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
// 处理获得putX putY putW putH
const imgData = ctx.getImageData(putX, putY, putW, putH);
if (grayscale) { //灰度处理
getGrayscaleData(imgData);
};
canvas.width = putW;
canvas.height = putH;
ctx.putImageData(imgData, 0, 0);
return new Promise(res => {
canvas.toBlob(e => res(e));
});
};
const cancelChangeSelect = async () => {
// todo sth.
dataUrl && (window.URL.revokeObjectURL(dataUrl));
const blob = await getPhotoData() as Blob;
const newDataUrl = window.URL.createObjectURL(blob);
setDataUrl(newDataUrl);
// todo sth.
};
// 省去不关键代码
Изображение эффекта:
Скачать выбранные изображения
На самом деле это было написано чуть ли не выше, и получилосьdataUrl, используйте его какaпомеченhref, загрузка завершена. (Конечно, есть много других способов загрузки, поэтому я не буду перечислять их по одному)
полный код
загруженоgithub