предисловие
Эта статья основана на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